第327章 勝ちを判定する


今回は、勝ちを判定する機能を付けます。



「五目並べ」には、いろいろなローカル・ルールがあるようですが ここでは、簡単のために先手でも後手でも

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以上なら禁じ手で負けです。

同様に垂直方向と、対角線方向(右上がりと左上がり)を調べます。

ちょっと回りくどいプログラムのようですね。もう少しすっきりしたアルゴリズムを考えてみてください。


[SDK第4部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 02/Jan/2002 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。