DXライブラリを用いたAIオセロゲームの動作がおかしいです(´;ω;`)ウゥゥ

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
G4N

DXライブラリを用いたAIオセロゲームの動作がおかしいです(´;ω;`)ウゥゥ

#1

投稿記事 by G4N » 8年前

メインソース

コード:

#include "DxLib.h"
#include "othello.h"

int d = 50;//マスの間隔
int r = d / 2;//石の半径
int sx = 20, sy = 20;//盤左上の座標
int ex = sx + d * SIDE;//盤右下のx座標
int ey = sy + d * SIDE;//盤右下のy座標
int rx = ex + 10;//画面右 結果など表示するための場所x座標
int STRCOLOR = GetColor( 0, 0, 200 );//文字色
int STRFONTSIZE = 32;//文字フォントサイズ

//マウスの座標からオセロの座標に変換する関数
int GetPosfromMouse(int MouseX, int MouseY)
{ 
    int x,y;
    x = (MouseX-20)/50;
    y = (MouseY-20)/50;
  
    return getposition(x,y);
}
// GUI用のmanPlayer関数
void manPlayerGUI()
{
 	int num,move,Mouse,Mouse_x,Mouse_y;
	Mouse_x = 0;
	Mouse_y = 0;
	Move moves[MOVENUM];
	num = generateMoves(moves);
	//if(num == 0);
	//{
	//	nextmove = PASSMOVE;
	//	return;
	//}
	do{
	if(ProcessMessage() == 0 && (GetMouseInput() & MOUSE_INPUT_LEFT) != 0 )
	{

	GetMousePoint( &Mouse_x, &Mouse_y );
	move = GetPosfromMouse( Mouse_x, Mouse_y );
	    if( isLegalMove(move) )
	          break;
	}

	 
	}while(1);
	nextmove = move;

}

// 画面描画関数 ** 要変更!!**
void ShowBoard()
{
	int color;
	int x, y, stone;
	int ry = 40, rdy = STRFONTSIZE + 5;
	static int GrHandle = LoadGraph( "back.bmp" );//背景画像登録 640x480
	ClearDrawScreen();
	DrawGraph( 0 , 0 , GrHandle , FALSE );//背景を描く
	SetFontSize( STRFONTSIZE ) ;
	DrawBox( sx, sy, ex, ey, GetColor( 0, 255, 0 ), TRUE );//緑の盤を描画

	for ( y = 1; y <= SIDE; y++ )
	{
		for ( x = 1; x <= SIDE; x++ )
		{
			//ここから枠の描画
			//ここでは小さな四角形をたくさん書いているが、別のループで長い線を引いてももちろん良い
			DrawBox(  sx+d*(x-1),sy+d*(y-1),sx+d*x,sy+d*y, GetColor( 0, 0, 0 ), FALSE);
			//ここから石の描画
			stone = board[getposition(x,y)];//石の色を調べる
			if ( stone == 0 )
				continue;
			if ( stone == B )
				color = GetColor( 0, 0, 0 );//黒
			else
				color = GetColor( 255, 255, 255 );//白
			    DrawCircle( sx+d*x-r,sy+d*y-r,r, color, TRUE);//円で石を描画
		}
	}
	DrawFormatString( rx, ry, GetColor( 0, 0, 0 ),"●: %d", stonenum[BLACK_TURN] );
	ry += rdy;
	DrawFormatString( rx, ry, GetColor(  255, 255, 255 ),"○: %d", stonenum[WHITE_TURN] );
	ry += rdy * 2;
	DrawFormatString( rx, ry, STRCOLOR,"%d 手", ply );
}


// WinMain関数
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
					 LPSTR lpCmdLine, int nCmdShow )
{
	int manturn = BLACK_TURN;
	// タイトルを 変更
	SetMainWindowText( "" ) ;
	// ウインドウモードに変更
	ChangeWindowMode( TRUE ) ;
	// DXライブラリ初期化処理
	if( DxLib_Init() == -1 )		// DXライブラリ初期化処理
		return -1 ;			// エラーが起きたら直ちに終了
	// マウスを表示状態にする
	SetMouseDispFlag( TRUE ) ;
	
	init();
	ShowBoard();
	SetFontSize( 64 ) ;
	DrawString( 80 , ey / 2 - 20, "Push Space Key!!", STRCOLOR );

	// スペースボタンが押されるまで待機
	while( !CheckHitKey( KEY_INPUT_SPACE ) )
	{
		// メッセージループに代わる処理をする
		if( ProcessMessage() == -1 )
			break;		// エラーが起きたらループを抜ける
	}
	//おいこら。あらま。
	ShowBoard();
	SetFontSize( 32 ) ;
	DrawString( rx , ey / 2 - 20, "GAME START!!", STRCOLOR );
	WaitTimer(2000);
	ShowBoard();
	// 対局メインループ
	while ( !CheckHitKey( KEY_INPUT_ESCAPE ) )//ESCでゲーム終了
	{

		int num, result;
		Move moves[MOVENUM];
		// メッセージループに代わる処理をする
		if( ProcessMessage() == -1 )
			break ;		// エラーが起きたらループを抜ける
		num = generateMoves( moves );
		if ( num == 0 && isTerminalNode(FALSE))//終局かチェック
		{
			SetFontSize( 32 ) ;
			//石の数で勝ち負け判定し表示
			result = stonenum[BLACK_TURN] - stonenum[WHITE_TURN];//こんな感じで
			// result により表示を変える
			if ( result == 0 )
				DrawString( rx , ey / 2 - 20, "DRAW!!", STRCOLOR );
			else 
				DrawFormatString( rx , ey / 2 - 20, STRCOLOR, "%s WIN!!", ( result > 0 ? "BLACK": "WHITE") );
			break;
		}
		if ( turn == manturn )
		manPlayerGUI();
			//randplayer();
		else
			comPlayer();
		makeMove( nextmove, 0 );
		if( nextmove == PASSMOVE)
			DrawString( rx , ey / 2 - 20, "PASS!!", STRCOLOR );
		else
			ply++;
		ShowBoard();
		WaitTimer(100);
	}
	DrawString( rx , ey / 2 + 50, "Push ANY Key!!", STRCOLOR );
	while( !CheckHitKeyAll())
	{
		// メッセージループに代わる処理をする
		if( ProcessMessage() == -1 )
			break ;		// エラーが起きたらループを抜ける
	}

	DxLib_End() ;				// DXライブラリ使用の終了処理
	return 0 ;				// ソフトの終了 
}
ヘッダーソース

コード:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIDE 8 // 一辺の長さ 
#define ASIDE (SIDE + 2) // 局面用配列の一辺 緩衝地帯分2を足す Side for array, add 2
#define BOARDSIZE (ASIDE * ASIDE) // ボードの大きさ Size of board
#define UP (-ASIDE)
#define DOWN ASIDE
#define RIGHT 1
#define LEFT (-1)
// Piece number
#define B 1 // BLACK 1
#define W 2 // WHITE 2
#define N 3 // 番兵 Sentinel (out of board) 3
#define BLACK_TURN 0 
#define WHITE_TURN 1

//座標からX座標またはY座標を計算するマクロ
#define X(pos) (pos % ASIDE)
#define Y(pos) (pos / ASIDE)
#define TURNCOLOR( turn ) (turn + 1)
#define OPPONENT(turn) !turn

#define TRUE 1
#define FALSE 0

#define PASSMOVE 99 //パス手には99を入れる 
#define MOVENUM 32	//置ける箇所は高々MOVENUMであることが保障される

///////////////////////////AI用に追加 ここから
#define MAXDEPTH 2 //探索を行う最大深さ
#define INFINITY 1000 //十分大きい数を無限大として扱う
#define SEARCH_LIMIT_DEPTH 128 //探索深さの上限
///////////////////////////AI用に追加 ここまで

///////////////////////////AI用に追加 ここから
struct Position //過去の局面などを記憶するための構造体
{
	unsigned char board[BOARDSIZE];
	unsigned char stonenum[2];
};
struct Position history[SEARCH_LIMIT_DEPTH];
//場所による評価を与える配列
//角が良いことを教えることが一番の目的
//本当はパターンによる評価が出来ればよいが、
//この程度でもそれっぽくすることは可能
int evalboard[BOARDSIZE] =
{
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
   0,  30, -12,   0,  -1,  -1,   0, -12,  30,   0,
   0, -12, -15,  -3,  -3,  -3,  -3, -15, -12,   0,
   0,   0,  -3,   0,  -1,  -1,   0,  -3,   0,   0,
   0,  -1,  -3,   0,  -1,  -1,  -1,  -3,  -1,   0,
   0,  -1,  -3,   0,  -1,  -1,  -1,  -3,  -1,   0,
   0,   0,  -3,   0,  -1,  -1,   0,  -3,   0,   0,
   0, -12, -15,  -3,  -3,  -3,  -3, -15, -12,   0,
   0,  30, -12,   0,  -1,  -1,   0, -12,  30,   0,
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
};
///////////////////////////AI用に追加 ここまで


const char* piece[3] = { "  ", "●", "○" };
const char* abc[8] = { "a","b","c","d","e","f","g","h"};
int turn;
int ply; 

typedef unsigned char Move;
Move nextmove;
unsigned char stonenum[2];

//2次元の座標を一次元に変換
int getposition( int x, int y ){ return y * ASIDE + x; };

//一番大事な変数、ここでは一次元で表現
unsigned char board[BOARDSIZE] = //intでも良いが、データ格納のことを考えてcharにしている
{//とりあえずはこんな感じで初期化
	N, N, N, N, N, N, N, N, N, N, 
	N, 0, 0, 0, 0, 0, 0, 0, 0, N,
	N, 0, 0, 0, 0, 0, 0, 0, 0, N,
	N, 0, 0, 0, 0, 0, 0, 0, 0, N,
	N, 0, 0, 0, B, W, 0, 0, 0, N,
	N, 0, 0, 0, W, B, 0, 0, 0, N,
	N, 0, 0, 0, 0, 0, 0, 0, 0, N,
	N, 0, 0, 0, 0, 0, 0, 0, 0, N,
	N, 0, 0, 0, 0, 0, 0, 0, 0, N,
	N, N, N, N, N, N, N, N, N, N 
};
// 表示関数 display function
void output()
{//ヒントを参考に、とにかく表示する関数を作りましょう
	int x, y;
	printf( "   a  b  c  d  e  f  g  h\n" );
	printf( "  ------------------------\n" );
	for( y = 1; y <= SIDE; y++ ){
		printf( "%d|", y );
		for ( x = 1; x <= SIDE; x++ )
			printf( "%s|", piece[board[getposition(x, y)]] );
		printf("\n");
		printf( "  ------------------------\n" );
	}
}

//合法手なら1を返し、非合法手なら0を返す。配列外参照は起こらないものとする. 番兵の位置に石を置こうとしたらfalseが返る.
int isLegalMove(Move pos)
{
	int dirx, diry, dir;
	int pos1;
	// 自分の色、相手の色は何か変数に入れておく
	int color = TURNCOLOR( turn );						//置こうとしている石色の盤面表現(黒…1, 白…2, 一般的にTURNCOLOR( turn ))
	int opponentcolor = TURNCOLOR( OPPONENT(turn) );	//相手の石色の盤面表現(OPPOENT(turn)で相手のターンを取得して、TURNCOLORで石の値にしている)

	//空きマスでないかCheck
	if ( board[pos] != 0 )
		return FALSE;
	
	// posの周り8方向を調べ相手石が取れるか調べる
	for ( dirx = -1; dirx <= 1; dirx++ )
	{
		for ( diry = -ASIDE; diry <= ASIDE; diry += ASIDE )
		{
			dir = dirx + diry;	//1マス進むためのインクリメント値
			if ( dir == 0 )		//方向0は意味ないのでパス
				continue;
			pos1 = pos + dir;//posの隣のマス

			//まず、posの隣のマスに相手の石があるか調べる
			if( board[pos1] != opponentcolor )	
				continue;
			do // 相手の石がある間は次を調べる
			{
				//次の石
				pos1 += dir;
			}while ( board[pos1] == opponentcolor );
			// ひっくり返すためには最後に自分の石がないといけない
			if( board[pos1] != color )
				continue;

			//最後まで来たら成功
			return TRUE;
		}
	}
	return FALSE;	
}

//手の生成 generation of moves
//生成した手の数を返している 
int generateMoves( Move moves[] )	
{
	int num = 0;//生成する合法手の数
	int pos;
	// 左上から順に石を置き、合法手か調べる
	for ( pos = 0; pos < BOARDSIZE; pos++ )
	{
		//位置posに置けるかどうかを調べる
		if ( isLegalMove( pos ) )
			moves[num++] = pos;//num番目の配列にposを入れる
	}
	return num;
}

// ゲーム終了局面ならTRUEを返す Return TRUE in case of GAME OVER
// isPass = 自分がパスをしたかどうか
int isTerminalNode( int isPass )
{
	int num;
	Move moves[MOVENUM];
	if ( !isPass )
	{
		num = generateMoves(moves);
		if ( num > 0 )
			return FALSE;

	}
	turn = OPPONENT(turn);
	num = generateMoves(moves);
	turn = OPPONENT(turn);
	if ( num == 0 )
		return TRUE;
	return FALSE;
}

///////////////////////////AI用に追加 ここから

// ゲーム終了局面の評価値を返す 勝ち ∞ 引き分け 0 負け -∞
int getTerminalValue()
{
	//石数の差を計算 自分の石ー相手の石
	int diff = stonenum[turn] - stonenum[OPPONENT(turn)];
	
	return diff * INFINITY;
}

// 評価関数の計算 Calculation of evaluation function
int getEvaluationValue()
{
	int pos, value, c;
	Move moves[MOVENUM];
	// 合法手数の差を評価関数とする(自由度)
	value = generateMoves(moves);//自分の合法手数を足す
	turn = OPPONENT(turn);
	value -= generateMoves(moves);
	turn = OPPONENT(turn);

	value *= 30;//自由度1を30点としておく(適当)
	for ( pos = 11; pos <= 88; pos++ )	//盤面評価
	{
		c = board[pos];
		if ( c == 0 )
			continue;
		else if ( c == TURNCOLOR(turn) )
			value += evalboard[pos];
		else
			value -= evalboard[pos];
	}
	return value;
}
///////////////////////////AI用に追加 ここまで

// 実際に手を進める。合法手が打たれていると仮定してよい。
void makeMove( Move pos, int depth )
{
	int dirx, diry, dir;
	int pos1;
	int color = TURNCOLOR( turn );	//盤面の表現…黒石 = 1, 白石 = 2(自分の石 = turn + 1 = TURNCOLOR( turn ))
	int opponentcolor = TURNCOLOR( OPPONENT(turn) );

	///////////////////////////AI用に追加 ここから
	// 局面を配列historyに保存
	memcpy( history[depth].board, board, sizeof( board ));// 配列の中身をコピーするのはmemcpy()を使うと簡単
	memcpy( history[depth].stonenum, stonenum, sizeof( stonenum ));
	///////////////////////////AI用に追加 ここまで

	if ( pos == PASSMOVE )//パス手の場合(一応追加)
	{
		turn = OPPONENT(turn);
		return;
	}

	//とりあえず、指定された場所に石を置く
	board[pos] = color;
	//石の数を更新
	stonenum[turn]++;

	//石をひっくり返す
	// posの周り8方向を調べ相手石が取れるか調べる
	for ( dirx = -1; dirx <= 1; dirx++ )
	{
		for ( diry = -ASIDE; diry <= ASIDE; diry += ASIDE )
		{
			dir = dirx + diry;	//1マス進むためのインクリメント値
			if ( dir == 0 )		//方向0は意味ないのでパス
				continue;
			pos1 = pos + dir;	//posの隣のマス

			//まず、posの隣のマスに相手の石があるか調べる
			if( board[pos1] != opponentcolor )	
				continue;
			do // 相手の石がある間は次を調べる
			{
				//次の石
				pos1 += dir;
			}while ( board[pos1] == opponentcolor );
			// ひっくり返すためには最後に自分の石がないといけない
			if( board[pos1] != color )
				continue;

			//自分の石で相手の石を挟んでいるので、石をひっくり返す
			pos1 = pos + dir;
			while( board[pos1] == opponentcolor )
			{
				//石をひっくり返す
				board[pos1] = color;
				//石の数を更新
				stonenum[turn]++;
				stonenum[OPPONENT(turn)]--;
				//次を調べる
				pos1 += dir;
			}
		}
	}

	//手番交代
	turn = OPPONENT( turn );//0→1、1→0となる
}

///////////////////////////AI用に追加 ここから

// 手を戻す
void unmakeMove(int depth)
{
	// 配列の中身をコピーするのはmemcpy()を使うと簡単
	// 逆の事をmakeMove()でしないといけない
	// 局面を配列historyから復元
	memcpy( board, history[depth].board, sizeof( board ));
	memcpy( stonenum, history[depth].stonenum, sizeof( stonenum ));
	turn = OPPONENT(turn);
}

// 探索して最も評価の高い手を選ぶ( 1手読み )
int search(int depth)
{
	int i;
	int movenum;//手の数
	Move moves[MOVENUM];//手を入れる配列 an array of moves
	int value;
	int bestvalue = -INFINITY;//まず最小値を入れる

	//手を生成
	movenum = generateMoves( moves );
	if ( movenum == 0 )
	{
		if ( isTerminalNode(TRUE) )// Game Over
			return getTerminalValue();
		else // パス
			moves[movenum++] = PASSMOVE;
	}
	for ( i = 0; i < movenum; i++ )
	{
		makeMove( moves[i], depth );//一手進め
		value = getEvaluationValue();
		//output();//for Debug
		//printf("i = %d, value = %d, move = %d\n", i, value, moves[i]);//for Debug
		unmakeMove( depth );//一手戻る
		//最大かどうか調べ、最大ならnextmoveに代入する(スライド参照)
		if( value > bestvalue ){
			bestvalue = value;
			nextmove = moves[i];	//手(moves[i])を代入
		}
	}
	return bestvalue;	//評価値を返す
}

//MIN-MAX法でmaxdepth手先まで読む(実装はNEGA-MAX)
//戻り値 = 評価値、手もここで入れる
int negamax( int depth )
{
	int i;
	int movenum;		
	Move moves[MOVENUM];
	int value;
	int bestvalue = -INFINITY;

	
	if( depth >= MAXDEPTH ){
		return getEvaluationValue();	
	}


	movenum = generateMoves( moves );
	if ( movenum == 0 )
	{
		if ( isTerminalNode(TRUE) )
			return getTerminalValue();
		else // パス
			moves[movenum++] = PASSMOVE;
	}
	for ( i = 0; i < movenum; i++ )
	{
		makeMove( moves[i], depth );	
		
		value = -negamax( depth + 1 );
		
		if( value > bestvalue ){
			bestvalue = value;
			if( depth == 0 ){
				nextmove = moves[i];	
			}
		}
		unmakeMove( depth );
	}
	return bestvalue;	
}

void comPlayer()
{
	int value;
	printf( "Com Thinking...\n");
	value = negamax(0);		
	printf( "value = %d\n", value );
	if ( value == INFINITY )
		printf( "Computer Finds Win!!\n" );
}

///////////////////////////AI用に追加 ここまで


Move manPlayer()
{
	char line[256];
	int x, y, num, move;
	Move moves[MOVENUM];
	num = generateMoves( moves );
	if(num == 0)
	{
		gets(line);
		return PASSMOVE;
	}
	while(1)
	{
		do
		{
			printf("x(a-h):");
			gets(line);
		}while(line[0] < 'a' || line[0] > 'h');
		x = line[0] - 'a' + 1;
		
		do
		{
			printf("y(1-8):");
			gets(line);
		}while(line[0] < '1' || line[0] > '8');
		y = line[0] - '0';
		
		move = getposition( x, y );	
		if( isLegalMove( move ) )	
			break;
	};
	return (Move)move;
}


void init()
{
	turn = BLACK_TURN;
	ply = 0;
	stonenum[BLACK_TURN] = 2;
	stonenum[WHITE_TURN] = 2;
}

//int main()
//{
//	
//	int result;
//	char line[256];
//	int manturn = BLACK_TURN;
//	init();
//	output();
//	while(1)
//	{
//		if ( isTerminalNode(FALSE))
//		{
//			
//			result = stonenum[BLACK_TURN] - stonenum[WHITE_TURN];
//			if ( result == 0 )
//				printf ( "GAMEOVER!  DRAW!!\n");
//			else 
//				printf ( "GAMEOVER! %s WIN!!\n", ( result > 0 ? "BLACK": "WHITE"));
//			return 0;
//		}
//		if ( turn == manturn )
//			nextmove = manPlayer();
//		else
//			comPlayer();
//		makeMove( nextmove, 0 );
//		ply++;
//		printf( "ply = %d\n", ply );
//		output();
//
//		if( nextmove == PASSMOVE ){
//			printf("パス!\n");	
//		}
//		else {
//			printf ( "%s -> %s%d\n\n", ( turn != BLACK_TURN? "BLACK": "WHITE"), abc[X(nextmove)-1], Y(nextmove));
//		}
//	}
//	printf( "Press any key and Enter\n");
//	gets(line);
//	return 0;
//}
コメント文やコメントアウトでごちゃごちゃしていて申し訳ありません
DXライブラリを使いVsComのオセロゲームを作っているのですが上手く動作しません。。。
具体的には合法手なのに置けなかったり必ず三手目で処理が止まってしまいます
雑で申し訳ありませんがよろしくお願いします

“C言語何でも質問掲示板” へ戻る