プログラムを実行すると左の図のようなものが出てきます。猫のアイコンとか
「猫でもわかる357ゲーム」のロゴは適当に作ってください。あるいは無くてもかまいません。
メニューの「ファイル」「ゲーム開始」でゲームを始めます。
先手になるか、後手になるかを聞かれます。
自分の番になったら左の図のようなダイアログが出るので、どの山から
いくつ取るかを指定します。
もちろん、山の石がマイナスになったり、すべての山の石が0になるような取り方は できません。今回はコンピュータは乱数を発生してでたらめに石を取るので弱いです。
では、プログラムを見てみましょう。
// game35701.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "ゲーム開始(&S)", IDM_START MENUITEM SEPARATOR MENUITEM "終了(&X)", IDM_END END END ///////////////////////////////////////////////////////////////////////////// // // Bitmap // MYBMP BITMAP "bitmap1.bmp" ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYDLG DIALOGEX 0, 200, 131, 78 STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "石を取ってください" FONT 10, "MS ゴシック", 400, 0, 0x80 BEGIN DEFPUSHBUTTON "OK",IDOK,44,51,50,14 LTEXT "取る山",IDC_STATIC,15,14,25,8 LTEXT "石の個数",IDC_STATIC,15,33,33,8 COMBOBOX IDC_COMBO1,73,14,51,57,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP COMBOBOX IDC_COMBO2,73,33,51,49,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP END ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. MYICON ICON "icon1.ico"普通のリソース・スクリプトです。ビットマップやアイコンは適当に作ってください。
// 35701.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include <stdlib.h> #include <time.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); BOOL StartGame(HWND); BOOL ShowStone(HWND); int comp_take(HWND); int human_take(HWND); int judge(HWND); char szClassName[] = "game35701"; //ウィンドウクラス HINSTANCE hInst; int nStone[3]; //各山の石の数 char szTxt[3][8], szStone[3][32]; BOOL bSente, bOrder;//コンピュータが先手かどうか, 現在の差し手 int nMtOrder, nTakeStone; 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 (int)msg.wParam; }bSenteがTRUEならコンピュータが先手、FALSEなら人間が先手です。
bOrderは誰の番かを示します。TRUEなら先手の番、FALSEなら後手の番です。
いつも通りのWinMain関数ですが、.netになってからはreturn msg.wParam;とすると 文句を言われるのでintに型キャストしました。(文句を言われて当然といえば当然ですが・・)
さて、メッセージループのところはGetMessage関数が-1を返すこともあり得ることを考慮して 書きましたが、.netに自動的にひな形を作らせるとやはり従来通りのループを作ります。
//ウィンドウ・クラスの登録 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, "猫でもわかる357ゲーム", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 300, //幅 230, //高さ 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; HDC hdc, hdc_mem; PAINTSTRUCT ps; HBITMAP hBmp; BITMAP bmp_info; int nW, nH, nX, nY; RECT rc; switch (msg) { case WM_CREATE: nStone[0] = 3; nStone[1] = 5; nStone[2] = 7; ShowStone(hWnd); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_START: nStone[0] = 3; nStone[1] = 5; nStone[2] = 7; bOrder = TRUE; ShowStone(hWnd); StartGame(hWnd); break; } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); hBmp = LoadBitmap(hInst, "MYBMP"); GetObject(hBmp, sizeof(BITMAP), &bmp_info); nW = bmp_info.bmWidth; nH = bmp_info.bmHeight; GetClientRect(hWnd, &rc); hdc_mem = CreateCompatibleDC(hdc); SelectObject(hdc_mem, hBmp); nX = (rc.right - rc.left - nW) / 2; nY = 10; BitBlt(hdc, nX, nY, nW, nH, hdc_mem, 0, 0, SRCCOPY); DeleteDC(hdc_mem); DeleteObject(hBmp); TextOut(hdc, 10, 80, szTxt[0], (int)strlen(szTxt[0])); TextOut(hdc, 30, 80, szStone[0], (int)strlen(szStone[0])); TextOut(hdc, 10, 100, szTxt[1], (int)strlen(szTxt[1])); TextOut(hdc, 30, 100, szStone[1], (int)strlen(szStone[1])); TextOut(hdc, 10, 120, szTxt[2], (int)strlen(szTxt[2])); TextOut(hdc, 30, 120, szStone[2], (int)strlen(szStone[2])); EndPaint(hWnd, &ps); 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_CREATEメッセージが来たら、石山の石を初期化してShowStone関数を 呼んで、石山を表示します。
メニューから「ゲーム開始」(IDM_START)が選択されたらゲームの初期化をします。 そしてStartGame関数を呼んでゲームをスタートさせます。
WM_PAINTメッセージが来たら、画面に表示するいろいろなものを描画します。
まずはビットマップのロゴを表示させます。LoadBitmap関数はちょっと古いので 気にくわない人はLoadImage関数を使ってください。
テキストの表示は[A][B][C]と石(●)を別々に表示しています。 別に深い意味はありませんが、フォントによってはA,B,Cの幅が違うため 石が縦にそろわず、みっともなくなるからです。
WM_PAINTメッセージが来たときに何をするかがわからないと、SDKのプログラム 全体がわからなくなるので十分復習しておいてください。
BOOL StartGame(HWND hWnd) { int nID; nID = MessageBox(hWnd, "あなたが先手にりますか", "先手/後手", MB_ICONQUESTION | MB_YESNO); if (nID == IDYES) bSente = FALSE; else bSente = TRUE; while (1) { if (bSente == bOrder) { if (comp_take(hWnd) != 0) break; } else { if (human_take(hWnd) != 0) break; } } return TRUE; }ゲームを進行する関数です。
最初に人間が先手になるかどうかを聞きます。
後は勝敗がつくまで無限ループです。 bSenteとbOrderが等しいときはコンピュータの番なのでcomp_take関数を呼びます。 そうでないときは人間の番なのでhuman_take関数を呼びます。どちらの関数も 勝負がつかないときは0を返します。勝負がついたときは-1を返します。
BOOL ShowStone(HWND hWnd) { int i, j; memset(szTxt, '\0', sizeof(szTxt)); memset(szStone, '\0', sizeof(szStone)); for (i = 0; i < 3; i++) wsprintf(szTxt[i], "[%c] ", 'A' + i); for (i = 0; i < 3; i++) { for (j = 0; j < nStone[i]; j++) strcat(szStone[i], "●"); } InvalidateRect(hWnd, NULL, TRUE); return TRUE; }クライアント領域に石を表示させる関数です。各山の個数は nStone[x]を調べます。その数だけバッファに黒丸を書き込んでいます。 最後にInvalidateRect関数を呼んで無効領域を発生させて、WM_PAINTメッセージを 呼び出すようにしています。
LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static HWND hMt, hNo; char szMt[8], szNo[8]; switch (msg) { case WM_INITDIALOG: hMt = GetDlgItem(hDlg, IDC_COMBO1); hNo = GetDlgItem(hDlg, IDC_COMBO2); ComboBox_InsertString(hMt, 0, "A"); ComboBox_InsertString(hMt, 1, "B"); ComboBox_InsertString(hMt, 2, "C"); ComboBox_InsertString(hNo, 0, "1"); ComboBox_InsertString(hNo, 1, "2"); ComboBox_InsertString(hNo, 2, "3"); ComboBox_InsertString(hNo, 3, "4"); ComboBox_InsertString(hNo, 4, "5"); ComboBox_InsertString(hNo, 5, "6"); ComboBox_InsertString(hNo, 6, "7"); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: ComboBox_GetText(hMt, szMt, sizeof(szMt)); ComboBox_GetText(hNo, szNo, sizeof(szNo)); if (strcmp(szMt, "") == 0 || strcmp(szNo, "") == 0) { MessageBox(hDlg, "山および取る個数を指定してください", "注意", MB_OK); return FALSE; } nMtOrder = szMt[0] - 'A'; nTakeStone = atoi(szNo); nStone[nMtOrder] -= nTakeStone; if (nStone[nMtOrder] < 0) { MessageBox(hDlg, "石の取り方が不正です", "警告", MB_OK); nStone[nMtOrder] += nTakeStone; return FALSE; } if (nStone[0] + nStone[1] + nStone[2] == 0) { MessageBox(hDlg, "その取り方では山が全部0になってしまいます", "警告", MB_OK); nStone[nMtOrder] += nTakeStone; return FALSE; } EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: MessageBox(hDlg, "キャンセルはできません", "注意", MB_OK); return FALSE; } return FALSE; } return FALSE; }人間が取るときのダイアログのプロシージャです。
ComboBox_InsertStringなどのマクロはwindowsx.hをインクルードしていないと 使えないので注意してください。気にくわない人はCB_INSERTSTRING をSendMessageしてください。
int comp_take(HWND hWnd) { int mtorder, stone; char szBuf[128]; srand((unsigned)time(NULL)); while (1) { mtorder = rand() % 3; if (nStone[mtorder] == 0) continue; else break; } while (1) { stone = rand() % (nStone[mtorder]) + 1; nStone[mtorder] -= stone; if (nStone[0] + nStone[1] + nStone[2] == 0) { nStone[mtorder] += stone; continue; } else { break; } } wsprintf(szBuf, "コンピュータは%cから%d個取りました\n", mtorder + 'A', stone); MessageBox(hWnd, szBuf, "コンピュータ", MB_OK); ShowStone(hWnd); if (judge(hWnd) == 0) { MessageBox(hWnd, "コンピュータの勝ちです", "判定", MB_OK); return -1; } bOrder = !bOrder; return 0; }コンピュータが石を取る関数です。
最初にどの山から取るかを決めますが、その山の石がすでに0の時は 再度乱数を発生させて山を決めます。
山が決まったら乱数で取る石の数を決めます。 1からその山の石の個数までとなります。しかし、場合によっては すべての山が0になってしまうこともあり得ます。この場合は 再度やり直します。
取る石の数が決まったら、メッセージボックスで表示します。 そして、ShowStone関数を呼んで石を表示します。その後judge関数を 呼んで勝負がついたか(勝ったかどうか)を調べます。
勝敗がつかなかったときはbOrderを反転させます。
int human_take(HWND hWnd) { DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); ShowStone(hWnd); if (judge(hWnd) == 0) { MessageBox(hWnd, "あなたのの勝ちです", "判定", MB_OK); return -1; } bOrder = !bOrder; return 0; }人間が石を取る関数です。大部分はダイアログボックスでやっているので簡単ですね。
取る石が決まったらjudge関数で勝ったかどうかを判定します。
勝敗がつかないときはbOrderを反転します。
int judge(HWND hWnd) { if (nStone[0] + nStone[1] + nStone[2] == 1) return 0; else return 1; }勝ったかどうかを判定する関数です。
これは簡単です。すべての山の石の個数を合計して1になれば勝ちですね。
今回はコンピュータがランダムに石を取るため、人間が勝つことが多いでしょう。 C言語編第77章を参照して「必勝もどき」アルゴリズムを導入して コンピュータを強くしてみてください。
Update 27/May/2002 By Y.Kumei