今までに作った盤面に4がないか、3がないかを調べる関数を利用すれば
簡単に作れそうに思いますが、タイミングが結構面倒です。
では、プログラムを見てみましょう。
// gomoku06.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(F)" BEGIN MENUITEM "ゲーム開始(&S)", IDM_START MENUITEM "コンピュータと対戦する(&C)", IDM_COMP MENUITEM "対戦を読み込む(&R)", IDM_READ MENUITEM SEPARATOR MENUITEM "終了(&X)", IDM_END END ENDリソース・スクリプトのうち、メニュー部分に「コンピュータと対戦する」が増えています。
他の部分は同じです。
// gomoku06.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include <time.h> #include "resource.h" #define SHUI 30 //碁盤の周囲の幅 #define KANKAKU 20 //碁盤のマス目の間隔 #define STONESIZE 10 //碁石の半径 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyNameProc(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); int Is4(HWND); //4があるかどうか調べその数を返す int Is3(HWND); //3があるかどうか調べその数を返す BOOL MyRecord(HWND, char *, char *); //対戦を記録する BOOL MyGetTime(char *); BOOL MyRead(HWND); //対戦をファイルから読み込む int SetRecFromFile(char *); //ファイルからの情報を元にRec配列をセットする BOOL Replay(HWND, int); //対戦を再現する BOOL CompPlay(HWND); // コンピュータと対戦する時、先手かどうか決める BOOL CompPut(HWND); //コンピュータが打つ BOOL SearchPos(HWND, int *, int *); //コンピュータが打つべき位置を探す int Comp3, Comp4; //コンピュータの3,4の数 char szClassName[] = "gomoku06"; //ウィンドウクラス HINSTANCE hInst; char szBuf[128]; BOOL bSente = TRUE; //現在の差し手 先手:TRUE 後手:FALSE BOOL bStart = FALSE; //対戦中かどうか BOOL bShoHai = FALSE; //勝敗がついているか int ban[15][15]; //0:石無し 1:先手 2:後手 int nTe; //何手目か BOOL bName = FALSE; //対戦者氏名を記録するかどうか BOOL bComp = FALSE; //コンピュータと対戦するかどうか BOOL bCompSente; //コンピュータが先手かどうか char szSenteName[32], szGoteName[32]; //対戦者氏名 char szStartTime[32], szEndTime[32]; //対戦開始、終了日時 struct _tagRec{ int row; int col; } Rec[15 * 15];乱数を使う関係でtime.hをインクルードしました。
いくつかの関数、変数が追加となりました。
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; BOOL bRet; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { if (bRet == -1) { MessageBox(NULL, "GetMessage Error!", "Error", MB_OK); break; } else { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; }いつものところですが、メッセージループがちょっと変更になっています。 GetMessage関数が-1を返したときループを抜けてプログラムが終了するようにしてあります。 (*)
InitApp, InitInstanceの各関数に変更はありません。
//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id, x, y; PAINTSTRUCT ps; HDC hdc; char szSashite[32]; static HMENU hMenu; switch (msg) { case WM_CREATE: hMenu = GetMenu(hWnd); break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); MyMakeBan(hdc); MyStoneDraw(hdc); if (!bShoHai) { if (bSente) { if (!bName) { strcpy(szSashite, "先手●"); } else { wsprintf(szSashite, "先手[%s]●", szSenteName); } } else { if (!bName) { strcpy(szSashite, "後手○"); } else { wsprintf(szSashite, "後手[%s]○", szGoteName); } } 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)); } else{ TextOut(hdc, 30, SHUI + KANKAKU * 14 + 30, 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) { if (SetStone(hWnd, x, y) == FALSE) { MyGetTime(szEndTime); bShoHai = TRUE; if (bComp) bComp = FALSE; InvalidateRect(hWnd, NULL, TRUE); if (MessageBox(hWnd, "対戦を記録しますか", "記録の確認", MB_ICONQUESTION | MB_YESNO) == IDYES) { MyRecord(hWnd, szStartTime, szEndTime); } else { break; } } else { nTe++; } } if (bComp && bCompSente == bSente) { CompPut(hWnd); } break; case WM_MENUSELECT: if (bStart) { EnableMenuItem(hMenu, IDM_START, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, IDM_READ, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, IDM_COMP, MF_BYCOMMAND | MF_GRAYED); DrawMenuBar(hWnd); } else { EnableMenuItem(hMenu, IDM_START, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, IDM_READ, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, IDM_COMP, MF_BYCOMMAND | MF_ENABLED); DrawMenuBar(hWnd); } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_START: if (DialogBox(hInst, "MYNAME", hWnd, (DLGPROC)MyNameProc) == IDCANCEL) break; bStart = TRUE; bShoHai = FALSE; MyGetTime(szStartTime); nTe = 0; memset(ban, 0, sizeof(ban)); bSente = TRUE; InvalidateRect(hWnd, NULL, TRUE); break; case IDM_READ: bStart = TRUE; bShoHai = FALSE; nTe = 0; memset(ban, 0, sizeof(ban)); bSente = TRUE; InvalidateRect(hWnd, NULL, TRUE); MyRead(hWnd); bStart = FALSE; break; case IDM_COMP: bStart = TRUE; bShoHai = FALSE; nTe = 0; memset(ban, 0, sizeof(ban)); bSente = TRUE; InvalidateRect(hWnd, NULL, TRUE); CompPlay(hWnd); 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; }左ボタンが押されて、人間が石を置いた後、コンピュータと対戦中(bCompがTRUE) で、なおかつコンピュータの打つ順番であるとき(bSenteとbCompSenteがともにTRUEまたは、FALSEの時)は、 CompPut関数を呼んで、コンピュータが石を打ちます。
人間が石を置いた後はコンピュータの番なので、bSenteとbCompSenteの比較は 不要のように思われるかもしれませんが、これがないといろいろ不都合が起こります。 実験してみてください。
WM_MENUSELECTメッセージがきたときの処理が少し増えています。
メニューからIDM_COMPが選択されたら、いろいろな変数を初期化してCompPlay関数を呼んでいます。
MyMakeBan, MyCircle, SetStone, MyStoneDraw, Is5, Is4, Is3, MyRecord, MyGetTime, MyNameProc, MyRead, SetRecFromFile, Replayの各関数に変更はありません。
BOOL CompPlay(HWND hWnd) { int nId; Comp3 = 0; Comp4 = 0; nId = MessageBox(hWnd, "あなたが先手になりますか", "先手の確認", MB_YESNO | MB_ICONQUESTION); if (nId == IDYES) bCompSente = FALSE; else bCompSente = TRUE; bComp = TRUE; if (bCompSente) { strcpy(szSenteName, "Computor"); strcpy(szGoteName, "You"); } else { strcpy(szSenteName, "You"); strcpy(szGoteName, "Computor"); } bName = TRUE; MyGetTime(szStartTime); if (bCompSente) CompPut(hWnd); return FALSE; }メニューからIDM_COMPが選択されたとき、1度だけ実行される関数です。
コンピュータが先手になるか、後手になるかを決めます。
そして、対戦記録用の氏名の変数に書き込んでおきます。(注:コンピュータのスペルは computer,computorどちらでもよいのですが、現在は殆ど前者が使われています)
コンピュータが先手の場合は、直ちにCompPut関数を呼んで、コンピュータが石を打ちます。 後手の場合は、人間が左クリックをした後コンピュータが石を打ちます。この辺は 簡単そうで、いざプログラムを書くと予想外の事態に遭遇します。「同期オブジェクトと待機関数を 使って・・」などと考えるとかなり複雑なプログラムとなってしまいます。
BOOL CompPut(HWND hWnd) { int x, y; srand((unsigned)time( NULL )); while (1) { if (SearchPos(hWnd, &x, &y) == FALSE) { x = rand() % 15; y = rand() % 15; } if (ban[x][y] == 0) { if (SetStone(hWnd, x * KANKAKU + SHUI, y * KANKAKU + SHUI) == FALSE) { bComp = FALSE; bStart = FALSE; bShoHai = FALSE; bName = FALSE; return FALSE; } break; } } nTe++; return TRUE; }コンピュータが石を打つ関数です。
まず、SearchPos関数を呼んで、コンピュータが3または4または5になるところがないかどうか 調べます。 ちょうどよいところがない場合は、乱数で適当なところに石を置いてしまいます。
SetStone関数で石を置きます。このとき勝敗が決まってしまったらFALSEを返して戻ります。
SetStone関数を実行して勝敗が決まらないときはnTeを1増やして戻ります。
BOOL SearchPos(HWND hWnd, int *x, int *y) { int i, j; int Jibun, Aite; if (bCompSente) { Jibun = 1; Aite = 2; } else { Jibun = 2; Aite = 1; } if ((nTe == 0 || nTe == 1) && ban[7][7] == 0) { { char szBuf[256]; wsprintf(szBuf, "nTe = %d", nTe); MessageBox(hWnd, szBuf, "OK", MB_OK); } *x = 7; *y = 7; return TRUE; } for (j = 0; j < 15; j++) { for (i = 0; i < 15; i++) { if (ban[i][j] == 0) { ban[i][j] = Jibun; if (Is5(hWnd, i, j) == 1) { *x = i; *y = j; ban[i][j] = 0; return TRUE; } else if (Is4(hWnd) >= 1 && Comp4 == 0){ *x = i; *y = j; ban[i][j] = 0; Comp4++; return TRUE; } else if (Is3(hWnd) >= 1 && Comp3 == 0) { *x = i; *y = j; ban[i][j] = 0; Comp3++; return TRUE; } else { ban[i][j] = 0; } } } } Comp4 = 0; Comp3 = 0; return FALSE; }コンピュータが石を置いて、5,4,3になるところがないか調べる関数です。 たとえば3になるところが見つかったらComp3を1にして戻ります。
次にこの関数が呼ばれたとき、別な場所で3になるところを調べても仕方がないので 3になる場所は探しません。4になる場所を探します。
コンピュータが3を作った後、人間がこれを止めた場合、この関数は最後まで 行くので、Comp3は0に戻しておきます。
これは、まずコンピュータがでたらめに石を置いて、偶然3が出来ないと いつまでたってもでたらめな所に起き続けてしまいます。また、人間が3を作っても これを止めようとはしません。人間が3を作ったときこれを止めるようにしてみてください。
Update 06/Mar/2002 By Y.Kumei