「五目並べ」には、いろいろなローカル・ルールがあるようですが
ここでは、簡単のために先手でも後手でも
1.43は勝ち 2.33は禁じ手で負け 3.6連以上は負け 4.44は勝ち |
ということにします。
ただし、1.については例外があります。4を止めて止めた石で 相手に「4」が出来た場合は勝敗はわかりません。
結局「5」が出来たときが勝ちでしょう。
では、プログラムを見てみましょう。
gomoku02.rcに変更はありません。
// gomoku02.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include "resource.h" #define SHUI 30 //碁盤の周囲の幅 #define KANKAKU 20 //碁盤のマス目の間隔 #define STONESIZE 10 //碁石の半径 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); BOOL MyMakeBan(HDC); BOOL MyCircle(HDC, int, int, int); //盤(X,Y)に半径Rの円を描画 BOOL SetStone(HWND, int, int); BOOL MyStoneDraw(HDC); int Is5(HWND, int, int); char szClassName[] = "gomoku02"; //ウィンドウクラス HINSTANCE hInst; BOOL bSente = TRUE; //現在の差し手 先手:TRUE 後手:FALSE BOOL bStart = FALSE; //対戦中かどうか int ban[15][15]; //0:石無し 1:先手 2:後手Is5関数が増えました。縦・横・斜めに石が5つ並んでいるかどうかを検査します。
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;//インスタンス wc.hIcon = LoadIcon(hInst, "MYICON"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(hInst, "MYICON"); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかる五目並べ", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }このへんは、いつもと同じです。
//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id, x, y; static int nTe = 0; PAINTSTRUCT ps; HDC hdc; char szBuf[64], szSashite[16]; static HMENU hMenu; switch (msg) { case WM_CREATE: hMenu = GetMenu(hWnd); break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); MyMakeBan(hdc); MyStoneDraw(hdc); if (bSente) strcpy(szSashite, "先手●"); else strcpy(szSashite, "後手○"); wsprintf(szBuf, "差し手 = %s", szSashite); TextOut(hdc, 30, SHUI + KANKAKU * 14 + 30, szBuf, strlen(szBuf)); wsprintf(szBuf, "第 %02d 手終了 現在 %02d 手目待ち", nTe, nTe + 1); TextOut(hdc, 30, SHUI + KANKAKU * 14 + 50, szBuf, strlen(szBuf)); EndPaint(hWnd, &ps); break; case WM_LBUTTONDOWN: if (bStart == FALSE) break; x = LOWORD(lp); y = HIWORD(lp); if (x >= SHUI && y >= SHUI && x <= KANKAKU * 14 + SHUI && y <= KANKAKU * 14 + SHUI) { SetStone(hWnd, x, y); nTe++; } break; case WM_MENUSELECT: if (bStart) { EnableMenuItem(hMenu, IDM_START, MF_BYCOMMAND | MF_GRAYED); DrawMenuBar(hWnd); } else { EnableMenuItem(hMenu, IDM_START, MF_BYCOMMAND | MF_ENABLED); DrawMenuBar(hWnd); } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_START: bStart = TRUE; nTe = 0; memset(ban, 0, sizeof(ban)); bSente = TRUE; InvalidateRect(hWnd, NULL, TRUE); break; case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }メインウィンドウのプロシージャです。
WM_MENUSELECTメッセージを処理することにしました。
bStartがTRUEなら(対戦中)メニューのIDM_STARTを使用不能にし、
そうでないなら使用可能にします。なお、WM_MENUSELECTメッセージについては、
第275章を参照してみてください。
メニューからIDM_STARTが選択されたとき、いろいろ初期化をします。 これは、勝敗がついたあと新しい対戦を始めるときに必要な処理です。また、 この時InvalidateRect関数の最後の引数はTRUEにしておかないと 以前の石が残ったままとなってしまいます。
BOOL MyMakeBan(HDC hdc) { int i; HBRUSH hBrush, hOldBrush; for (i = 0; i < 15; i++) { MoveToEx(hdc, SHUI, i * KANKAKU + SHUI, NULL); LineTo(hdc, KANKAKU * (15 - 1) + SHUI, i * KANKAKU + SHUI); } for (i = 0; i < 15; i++) { MoveToEx(hdc, KANKAKU * i + SHUI, SHUI, NULL); LineTo(hdc, KANKAKU * i + SHUI, KANKAKU * (15 - 1) + SHUI); } hBrush = (HBRUSH)GetStockObject(BLACK_BRUSH); hOldBrush = (HBRUSH)SelectObject(hdc, hBrush); MyCircle(hdc, 3, 3, 3); MyCircle(hdc, 11, 3, 3); MyCircle(hdc, 3, 11, 3); MyCircle(hdc, 11, 11, 3); MyCircle(hdc, 7, 7, 3); SelectObject(hdc, hOldBrush); return TRUE; } BOOL MyCircle(HDC hdc, int x, int y, int r) { int x1, x2, y1, y2; x1 = x * KANKAKU + SHUI - r; x2 = x * KANKAKU + SHUI + r + 1; y1 = y * KANKAKU + SHUI - r; y2 = y * KANKAKU + SHUI + r + 1; Ellipse(hdc, x1, y1, x2, y2); return TRUE; }これらの関数に変更はありません。
BOOL SetStone(HWND hWnd, int x, int y) { int banx, bany; banx = (x - SHUI + KANKAKU / 2) / KANKAKU; bany = (y - SHUI + KANKAKU / 2) / KANKAKU; if (ban[banx][bany] != 0) { MessageBox(hWnd, "そこは置けません", "注意", MB_OK); return TRUE; } if (bSente) { ban[banx][bany] = 1; } else { ban[banx][bany] = 2; } InvalidateRect(hWnd, NULL, FALSE); if (Is5(hWnd, banx, bany) != -1) { bStart = FALSE; return FALSE; } bSente = !bSente; return TRUE; }置いた石をban配列に記録して、画面を再描画したあとIs5関数を呼んで 勝敗がついていないかどうかを検査します。この関数は 石を置いた人が勝った場合は1, 負けた場合は0, まだ勝負がつかないときは -1を返します。勝負がついたときはbStartをFALSEにしてFALSEを返します。 勝負がつかないときはTRUEを返します。
BOOL MyStoneDraw(HDC hdc) { int i, j; HBRUSH hBrush, hOldBrush; for (i = 0; i < 15; i++) { for (j = 0; j < 15; j++) { if (ban[i][j] != 0) { if (ban[i][j] == 1) { hBrush = (HBRUSH)GetStockObject(BLACK_BRUSH); hOldBrush = (HBRUSH)SelectObject(hdc, hBrush); } if (ban[i][j] == 2) { hBrush = (HBRUSH)GetStockObject(WHITE_BRUSH); hOldBrush = (HBRUSH)SelectObject(hdc, hBrush); } MyCircle(hdc, i, j, STONESIZE); SelectObject(hdc, hOldBrush); } } } return TRUE; }この関数に変更はありません。
int Is5(HWND hWnd, int x, int y) { int nCount = 1, nJibun, i = 1; char szSashite[8], szBuf[32]; if (bSente) { nJibun = 1; strcpy(szSashite, "先手"); } else { nJibun = 2; strcpy(szSashite, "後手"); } while (x - i >= 0) { if (ban[x - i][y] == nJibun) { nCount++; i++; } else break; } i = 1; while (x + i < 15) { if (ban[x + i][y] == nJibun) { nCount++; i++; } else break; } if (nCount == 5) { wsprintf(szBuf, "%sの勝ち!", szSashite); MessageBox(hWnd, szBuf, "5連", MB_OK); return 1; } else if (nCount > 5) { wsprintf(szBuf, "%sの反則負け", szSashite); MessageBox(hWnd, szBuf, "多連", MB_OK); return 0; } nCount = 1, i = 1; while (y - i >= 0) { if (ban[x][y - i] == nJibun) { nCount++; i++; } else break; } i = 1; while (y + i < 15) { if (ban[x][y + i] == nJibun) { nCount++; i++; } else break; } if (nCount == 5) { wsprintf(szBuf, "%sの勝ち!", szSashite); MessageBox(hWnd, szBuf, "5連", MB_OK); return 1; } else if (nCount > 5) { wsprintf(szBuf, "%sの反則負け", szSashite); MessageBox(hWnd, szBuf, "多連", MB_OK); return 0; } nCount = 1; i = 1; while (x - i >= 0 && y - i >= 0) { if (ban[x - i][y - i] == nJibun) { nCount++; i++; } else break; } i = 1; while(x + i < 15 && y + i < 15) { if (ban[x + i][y + i] == nJibun) { nCount++; i++; } else break; } if (nCount == 5) { wsprintf(szBuf, "%sの勝ち!", szSashite); MessageBox(hWnd, szBuf, "5連", MB_OK); return 1; } else if (nCount > 5) { wsprintf(szBuf, "%sの反則負け", szSashite); MessageBox(hWnd, szBuf, "多連", MB_OK); return 0; } nCount = 1, i = 1; while (x - i >= 0 && y + i < 15) { if (ban[x - i][y + i] == nJibun) { nCount++; i++; } else break; } i = 1; while (x + i < 15 && y - i >= 0) { if (ban[x + i][y - i] == nJibun) { nCount++; i++; } else break; } if (nCount == 5) { wsprintf(szBuf, "%sの勝ち!", szSashite); MessageBox(hWnd, szBuf, "5連", MB_OK); return 1; } else if (nCount > 5) { wsprintf(szBuf, "%sの反則負け", szSashite); MessageBox(hWnd, szBuf, "多連", MB_OK); return 0; } return -1; }石が5つ並んでいるか、または6個以上並んでいないかを検査します。
今置いた人(自分)が先手か、後手かを調べます。先手ならnJibunを1に、後手なら2に します。そして、ban配列の並びにnJibunが連続して5個ないか調べます。
今置いた石の位置は碁盤座標で(x,y)です。ban[x][y]はnJibunです。
ban[n-1][y]が自分であればnCountを1増やします。次にban[n-2][y]を調べます。 これもnJibunならnCountを1増やします。どこまで調べるかというと盤の端までです。 しかし、端に来るまでにban[x-n][y]がnJibunでない場合はこの方向の調べをやめて ban[x+1][y]を調べます。そしてban[x+1][y]がnJibunならnCountをさらに1増やします。 どこまで調べるかというとやはり盤の端までです。しかしban[x+n][y]がnJibunでなくなったときは 数えるのをやめます。これで、水平方向に自分の石がいくつ並んだかがわかります。 5なら自分の勝ちです。6以上なら禁じ手で負けです。
同様に垂直方向と、対角線方向(右上がりと左上がり)を調べます。
ちょっと回りくどいプログラムのようですね。もう少しすっきりしたアルゴリズムを考えてみてください。
Update 02/Jan/2002 By Y.Kumei