今回から「357ゲーム」を作ります。と、言ってもゲームの中身そのものは
C言語編第76章と同じです。これのGUI版と言うわけです。ゲームのルール等については
C言語編を参照してください。
プログラムを実行すると左の図のようなものが出てきます。猫のアイコンとか
「猫でもわかる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