今回は、勝ちを判定する機能を付けます。
「五目並べ」には、いろいろなローカル・ルールがあるようですが
ここでは、簡単のために先手でも後手でも
|
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