今回は、表題の通りコンピュータを少しだけ強くします。
強くするといっても、人間が3を作ったらこれを止めます。
4が出来たら5にならないように止めます。これだけです。
しかし、実際にプログラムを書いてみると結構面倒くさくなります。
では、プログラムを見てみましょう。
リソース・スクリプトに変更はありません。
// gomoku07x.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[] = "gomoku07"; //ウィンドウクラス
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];
//4の時の空いている場所
struct _tagTobi4{
int i;
int j;
} Akiin4[4];
BOOL bAkiin4[4];
新しく4が出来たときの空きの位置を格納する構造体を作りました。
これは、どういうことかというと、4の存在を調査するとき連続する5つのますについて
1つの空きと、同じ石4個が存在するかどうかを調べました。この時の「空き」を意味しています。
そして、これが存在すればbAkiin4配列をTRUEにします。4方向について調べるので配列としています。
WinMain, InitApp, InitInstance, WndProc, MyMakeBan, MyCircle, SetStone, MyStoneDraw, Is5の各関数に変更はありません。
int Is4(HWND hWnd) //4になっていないか調べる
{
int i, j, k, wa = 0, nCount0 = 0, nCount4 = 0, nJibunwa;
BOOL bLoop = TRUE;
bAkiin4[0] = bAkiin4[1] = bAkiin4[2] = bAkiin4[3] = FALSE;
if (bSente)
nJibunwa = 4;
else
nJibunwa = 8;
for (i = 0; i < 11; i++) {
for (j = 0; j < 15; j++) {
nCount0 = 0;
wa = 0;
for (k = 0; k < 5; k++) {
if (ban[i + k][j] == 0) {
nCount0++;
Akiin4[0].i = i + k;
Akiin4[0].j = j;
}
wa = wa + ban[i + k][j];
}
if (nCount0 == 1 && wa == nJibunwa) {
nCount4++;
bAkiin4[0] = TRUE;
bLoop = FALSE;
break;
}
}
if (bLoop != TRUE)
break;
}
bLoop = TRUE;
for (i = 0; i < 15; i++) {
for (j = 0; j < 11; j++) {
nCount0 = 0;
wa = 0;
for (k = 0; k < 5; k++) {
if (ban[i][j + k] == 0) {
nCount0++;
Akiin4[1].i = i;
Akiin4[1].j = j + k;
}
wa += ban[i][j + k];
}
if (nCount0 == 1 && wa == nJibunwa) {
nCount4++;
bAkiin4[1] = TRUE;
bLoop = FALSE;
break;
}
}
if (bLoop != TRUE)
break;
}
bLoop = TRUE;
for (i = 0; i < 11; i++) {
for (j = 0; j < 11; j++) {
nCount0 = 0;
wa = 0;
for (k = 0; k < 5; k++) {
if (ban[i + k][j + k] == 0) {
nCount0++;
Akiin4[2].i = i + k;
Akiin4[2].j = j + k;
}
wa += ban[i + k][j + k];
}
if (nCount0 == 1 && wa == nJibunwa) {
nCount4++;
bAkiin4[2] = TRUE;
bLoop = FALSE;
break;
}
}
if (bLoop != TRUE)
break;
}
bLoop = TRUE;
for (i = 4; i < 15; i++) {
for (j = 0; j < 11; j++) {
nCount0 = 0;
wa = 0;
for (k = 0; k < 5; k++) {
if (ban[i - k][j + k] == 0) {
nCount0++;
Akiin4[3].i = i - k;
Akiin4[3].j = j + k;
}
wa += ban[i - k][j + k];
}
if (nCount0 == 1 && wa == nJibunwa) {
nCount4++;
bAkiin4[3] = TRUE;
bLoop = FALSE;
break;
}
}
if (bLoop != TRUE)
break;
}
return nCount4;
}
コンピュータと対戦する時、コンピュータが石を置く位置を考えるときにも使えるよう
少し変更しました。縦、横、対角線方向で4が出来た時bAkiin4[x]にTRUEが入ります。また、この時 5つの並びの空きの位置をAkiin4[x]構造体に格納します。
Is3, MyRecord, MyGetTime, MyNameProc, MyRead, SetRecFromFile, Replay, CompPlay, CompPutの各関数に変更はありません。
BOOL SearchPos(HWND hWnd, int *x, int *y)
{
int i, j, n = 0;
int Jibun, Aite;
if (bCompSente) {
Jibun = 1;
Aite = 2;
} else {
Jibun = 2;
Aite = 1;
}
if ((nTe == 0 || nTe == 1) && ban[7][7] == 0) {
*x = 7;
*y = 7;
return TRUE;
}
//自分に5が出来ないか調べ、出来るのであれば5にして勝つ
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 {
ban[i][j] = 0;
}
}
}
}
//相手に5が出来ないか調べもし出来るのであればこれを防ぐ
bSente = !bSente;
for (j = 0; j < 15; j++) {
for (i = 0; i < 15; i++) {
if (ban[i][j] == 0) {
ban[i][j] = Aite;
if (Is5(hWnd, i, j) == 1) {
*x = i;
*y = j;
ban[i][j] = 0;
bSente = !bSente;
return TRUE;
} else {
ban[i][j] = 0;
}
}
}
}
bSente = !bSente;
//相手に4が出来ないか調べ出来るのであればこれを防ぐ
bSente = !bSente;
for (j = 0; j < 15; j++) {
for (i = 0; i < 15; i++) {
if (ban[i][j] == 0) {
ban[i][j] = Aite;
if (Is4(hWnd) >= 1) {
for (n = 0; n < 4; n++) {
if (bAkiin4[n] == TRUE) {
*x = Akiin4[n].i;
*y = Akiin4[n].j;
} else {
continue;
}
ban[i][j] = 0;
bSente = !bSente;
return TRUE;
}
} else {
ban[i][j] = 0;
}
}
}
}
bSente = !bSente;
for (j = 0; j < 15; j++) {
for (i = 0; i < 15; i++) {
if (ban[i][j] == 0) {
ban[i][j] = Jibun;
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になって勝たないかを調べます。 勝つ位置が存在するなら、そこに石を置いて勝ちます。
次に、相手が5になる位置がないか調べて、もしあるならそこに石を置いて妨害します。
次に、相手が4になる位置がないか調べます。これは、相手に止めていない3が出来ていないか 調べることに相当します。そして、存在するならこれを止めます。
これには、Is4関数を変更して連続した5つの並びの1つの空きの位置を調べていることが役立ちます。 この空きの位置に石を置くと、相手の3を止めたことになります。
あとは、自分に4や3が出来ないかを調べています。
ここでは、5並べのアルゴリズムは何も使っていません。人間の打った3や4は止めますが、 後は偶然に頼る打ち方なので非常に弱いです。改良して強くしてみてください。
Update 08/Apr/2002 By Y.Kumei