オセロプログラミング、パスができない

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
tonari
記事: 28
登録日時: 8年前

オセロプログラミング、パスができない

#1

投稿記事 by tonari » 8年前

オセロプログラミングの質問をしていたものです。
あれからいろいろとコードをいじっていたところ、無事AIを実装できました。
ですが今度は置けなくなったときににパスが発生しなくなってしまいました。
パスをするcheckEnd関数はあるのですが、どのように関連させるのかわかりません。
どのように関連させればいいでしょうか。
以下がコードの内容です

コード:

 
//CPUと自分がパスがされない
#include "DxLib.h"
#include <stdio.h>

int key[256];

int gpUpdatekey()
{
	char tmpkey[256];				//現在のキーの入力状態を格納
	GetHitKeyStateAll(tmpkey);		//すべてのキーの入力状態を得る
	for (int i = 0; i < 256; ++i)
	{
		if (tmpkey[i] != 0)
		{
			++key[i];
		}
		else
		{
			key[i] = 0;
		}
	}
	return 0;
}
struct Obj {
	int x,
		y,
		w,
		h,
		Handle;
};
enum Game_Data {
	Me = 0,
	CPU = 1,
	End = 2,
	EMPTY = 0,
	BLACK = 1,
    WHITE = 2,
	SCREEN_WIDIH = 840,
	SCREEN_HEIGHT = 640,
	CURSOR_MAX = 480,
	CELLSIZE = 80,
	BOARDSIZE = 8

};
int board_x,
	board_y;
int b,	//ゲーム終了時にコマの数を数えるための変数
	w;
int board[BOARDSIZE][BOARDSIZE];
int boardGraph;
int vec_y[] = { -1,-1,0,1,1,1,0,-1 };
int vec_x[] = { 0,1,1,1,0,-1,-1,-1 };
Obj black;
Obj white;
Obj cursor;
int Meturn;
int turn;
//画像のロード
void Graph_Ini()
{
	 boardGraph   = LoadGraph("画像/オセロ.bmp");
	 black.Handle = LoadGraph("画像/クロ駒.bmp");
	 white.Handle = LoadGraph("画像/シロ駒.bmp");
	 cursor.Handle = LoadGraph("画像/選択.png");
}
//ゲーム情報の初期化
void Game_Ini()
{
	turn = Me;	//ターン、0は黒が先手
	board_x = BOARDSIZE,
	board_y = BOARDSIZE;
}
//盤面の初期化
void Board_Ini()
{
	
	for (int y = 0; y < BOARDSIZE; ++y)
	{
		for (int x = 0; x < BOARDSIZE; ++x)
		{
			board[y][x] = EMPTY;
		}
	}
	board[BOARDSIZE / 2 - 1][BOARDSIZE / 2]		= BLACK;	//[3][4]
	board[BOARDSIZE / 2][BOARDSIZE / 2 - 1]		= BLACK;	//[4][3]
	board[BOARDSIZE / 2][BOARDSIZE / 2]  		= WHITE;	//[4][4]
	board[BOARDSIZE / 2 - 1][BOARDSIZE / 2 - 1] = WHITE;	//[3][3]
}
//カーソルの初期化
void Cursor_Ini()
{
	cursor.x = 0;
	cursor.y = 0;
}
//カーソルの移動
void Cursor_Move()
{
	if (key[KEY_INPUT_RIGHT] == 1 && cursor.x <= CURSOR_MAX)
	{
		cursor.x += CELLSIZE;
	}
	if (key[KEY_INPUT_LEFT] == 1 && cursor.x > 0)
	{
		cursor.x -= CELLSIZE;
	}
	if (key[KEY_INPUT_UP] == 1 && cursor.y > 0)
	{
		cursor.y -= CELLSIZE;
	}
	if (key[KEY_INPUT_DOWN] == 1 && cursor.y <= CURSOR_MAX)
	{
		cursor.y += CELLSIZE;
	}
}
//vecで指定された向きについてひっくり返るコマがあるか確認する(8方向のチェック)
int CheckFlip(int y, int x, int turn, int vec)
{
	int flag = 0;
	while (1)
	{
		y += vec_y[vec];
		x += vec_x[vec];

		//盤面の外に出ていたら終了(盤面内か?)
		if (x < 0 || y < 0 || x > BOARDSIZE - 1 || y > BOARDSIZE - 1)
		{
			return 0;
		}
		//空きマス(置けるマス)だったら終了(その場所はおけるか?)
		if (board[y][x] == EMPTY)
		{
			return 0;
		}
		//相手のコマがあったらフラグを立てる
		if (board[y][x] == (turn ? BLACK : WHITE))	//真なら黒(1)、偽なら白(0)
		{
			flag = 1;
			//(ループを続ける)
		}
		//自分のコマがあった場合にフラグが立っていれば置ける
		if (board[y][x] == (turn ? WHITE : BLACK))
		{
			if (flag == 1)
			{
				return 1;
			}
			//置けない
			else
			{
				return 0;
			}
		}
	}
	return 1;
}
//その場所に置くことができるかを確認
int Check(int y, int x, int turn)	//座標とターン
{
	int vec;

	//どれか一方向でもひっくり返るか確認
	for (vec = 0; vec < 8; ++vec)
	{
		if (CheckFlip(y, x, turn, vec) == 1)
		{
			return 1;	//ひっくり返るなら1を返す
		}
	}
	return 0;			//0は返せない
}
//実際に裏返す処理
void Flip(int y, int x, int turn, int vec)
{
	while (1)
	{
		y += vec_y[vec];
		x += vec_x[vec];

		//自分のコマがあったら終了
		if (board[y][x] == (turn ? WHITE : BLACK))
		{
			break;
		}
		//それ以外なら自分の駒で塗りつぶす
		board[y][x] = (turn ? WHITE : BLACK);
	}
}
//入力を受けて裏返せるか確かめる関数
int Put(int y, int x, int turn)
{
	int vec,
		flag = 0;
	//空白でなければ終了
	if (board[y][x] != EMPTY)
	{
		return 0;
	}
	//全方向について確認
	for (vec = 0; vec < 8; ++vec)
	{
		if (CheckFlip(y, x, turn, vec) == 1)
		{
			//裏返す
			Flip(y, x, turn, vec);
			flag = 1;
		}
	}
	if (flag == 1)
	{
		//この場所にコマを置く
		board[y][x] = (turn ? WHITE : BLACK);
		return 1;
	}
	return 0;
}
//終了判定
int CheckEnd(int turn)
{
	int y,
		x;
	//おける場所があるか確認
	for (y = 0; y < BOARDSIZE; ++y)
	{
		for (x = 0; x < BOARDSIZE; ++x)
		{
			//あれば普通に続行
			if (board[y][x] == EMPTY && Check(y, x, turn) == 1)
			{
				return Me;
			}
		}
	}
	//場所がなかったので交替して探す
	turn = (turn + 1) % 2;
	for (y = 0; y < BOARDSIZE; ++y)
	{
		for (x = 0; x < BOARDSIZE; ++x)
		{
			//あればpassして続行
			if (board[y][x] == EMPTY && Check(y, x, turn) == 1)
			{
				return CPU;
			}
		}
	}
	//なかったのでゲーム終了
	return End;
}
//コマを置く処理
void Me_PutPice(int by, int bx, int &turn)
{
	for (int y = 0; y < BOARDSIZE; ++y)
	{
		for (int x = 0; x < BOARDSIZE; ++x)
		{
			if (key[KEY_INPUT_Z] == 1 && CELLSIZE * x == cursor.x && CELLSIZE * y == cursor.y && turn == Me)
			{
				if (Put(y, x, turn) == 1)
				{
					board[by][bx] = BLACK;
					turn = CPU;
				}
			}
		}
	}
}
//CPU置ける所に適当に置く
void AI(int by, int bx, int &turn)
{
	for (int y = 0; y < BOARDSIZE; ++y)
	{
		for (int x = 0; x < BOARDSIZE; ++x)
		{
			if (key[KEY_INPUT_X] == 1 && turn == CPU)
			{
				if (Put(y, x, turn) == 1)
				{
					board[by][bx] = WHITE;
					turn = Me;
				}
			}
		}
	}
}
//勝敗判定
void CheckWinner()
{
	b = 0,
	w = 0;
	//コマを数え上げる
	for (int y = 0; y < BOARDSIZE; ++y)
	{
		for (int x = 0; x < BOARDSIZE; ++x)
		{
			switch (board[y][x])
			{
			case BLACK:
				++b;
				break;
			case WHITE:
				++w;
				break;
			default:
				break;
			}
		}
	}
	//勝敗決定時
	if (b > w)
	{
		DrawFormatString(680, 100, GetColor(255, 255, 255), "黒の勝ちです");
	}
	else if (b < w)
	{
		DrawFormatString(680, 100, GetColor(255, 255, 255), "白の勝ちです");
	}
	else
	{
		DrawFormatString(680, 100, GetColor(255, 255, 255), "引き分けです");
	}
}
//盤面の表示
void Draw_Board()
{
	//盤面
	DrawGraph(0, 0, boardGraph, false);
	//文字の表示
	if (turn == Me)
	{
		DrawFormatString(680, 50, GetColor(255, 255, 255), "黒(自分)のターン");
	}
	if (turn == CPU)
	{
		DrawFormatString(680, 50, GetColor(255, 255, 255), "白(CPU)のターン");
		DrawFormatString(680, 70, GetColor(255, 255, 255), "Xキーで置きます");
	}
	//コマ
	for (int y = 0; y < BOARDSIZE; ++y)
	{
		for (int x = 0; x < BOARDSIZE; ++x)
		{
				//ボードの情報がWHITEなら白コマが置かれる
				if (board[y][x] == WHITE)
				{
					DrawGraph(CELLSIZE * x, CELLSIZE * y, white.Handle, false);
				}
				//ボードの情報がBLACKなら黒コマが置かれる
				if (board[y][x] == BLACK)
				{
					DrawGraph(CELLSIZE * x, CELLSIZE * y, black.Handle, false);
				}
		}
	}
	//グリッド線を引く
	for (int x = 0; x < 9; ++x)
	{
		DrawLine(CELLSIZE * x, 0, CELLSIZE * x, SCREEN_HEIGHT, GetColor(255, 255, 255), true);
	}
	for (int y = 0; y < 9; ++y)
	{
		DrawLine(0, CELLSIZE * y, SCREEN_HEIGHT, CELLSIZE * y, GetColor(255, 255, 255), true);
	}
	//カーソル
	DrawGraph(cursor.x, cursor.y, cursor.Handle,true);
}
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int)
{
	ChangeWindowMode(TRUE), DxLib_Init(), SetDrawScreen(DX_SCREEN_BACK);	//ウィンドウモード変更と初期化と裏画面設定
	SetGraphMode(SCREEN_WIDIH, SCREEN_HEIGHT, 32);

	Graph_Ini();
	Game_Ini();
	Cursor_Ini();
	Board_Ini();

	// while(裏画面を表画面に反映、メッセージ処理、画面クリア、キーの更新)
	while(ScreenFlip() == 0 && ProcessMessage() == 0 && ClearDrawScreen() == 0 && gpUpdatekey() == 0)						//無限ループ
	{
		//ここにゲームの処理を描く------------------------------------------------
		if (turn < End)
		{
			Cursor_Move();
			Me_PutPice(board_y, board_x, turn);
			AI(board_y, board_x, turn);

			switch(CheckEnd(turn))
			{
			case End:
				CheckWinner();
				break;
			}
		}
		Draw_Board();
		//----------------------------------------------------------------------
	}
        DxLib_End();							// DXライブラリ終了処理
        return 0;
}

アバター
asd
記事: 319
登録日時: 15年前

Re: オセロプログラミング、パスができない

#2

投稿記事 by asd » 8年前

今更感満載ですが返信します。
tonari さんが書きました:オセロプログラミングの質問をしていたものです。
あれからいろいろとコードをいじっていたところ、無事AIを実装できました。
ですが今度は置けなくなったときににパスが発生しなくなってしまいました。
パスをするcheckEnd関数はあるのですが、どのように関連させるのかわかりません。
ソースを見る限り、checkEnd関数内でパスかどうかの判定を行い、パスなら次のturnを書き換えるという挙動を期待しているように思いますがただしいでしょうか?
その認識で回答しますと問題点は2点あります。

1.checkEnd関数内で返す値がおかしい
2.checkEnd関数内でチェック判定後にその結果で手番を入れ替えられていない

まず1ですが、引数turnを受け取りその手番で置く場所があるかを確認しあれば問答無用でMeを返しています。
ところがこのturnはプレーヤーの手番ではMe、AIの手番ではCPUが入るため、入力されたturnにより返すべき値も変えなくてはいけません。

turnがMeでおける場所があるならMeを返す必要があり、turnがCPUでおける場所があるならCPUを返す必要があるかと思います。
そのため以下のような書き換えが必要になるのかなと思います。

コード:

 
//終了判定
int CheckEnd(int turn)
{
    int y,
        x;
    //おける場所があるか確認
    for (y = 0; y < BOARDSIZE; ++y)
    {
        for (x = 0; x < BOARDSIZE; ++x)
        {
            //あれば普通に続行
            if (board[y][x] == EMPTY && Check(y, x, turn) == 1)
            {
                //return Me;
                return (turn == Me ? Me : CPU);
            }
        }
    }

    //場所がなかったので交替して探す
    //turn = (turn + 1) % 2;
	turn = (turn == Me ? CPU : Me);

    for (y = 0; y < BOARDSIZE; ++y)
    {
        for (x = 0; x < BOARDSIZE; ++x)
        {
            //あればpassして続行
            if (board[y][x] == EMPTY && Check(y, x, turn) == 1)
            {
                    //return CPU;
                    return (turn == Me ? Me : CPU);
            }
        }
    }
    //なかったのでゲーム終了
    return End;
}
続いて2についてですが、おそらくcheckEnd関数内で変数turnに手番を代入しているため、
自然とグローバル変数のturnが書き換わっているものと思い込んでいると思っています。
なのでメインループ側でcheckEnd関数の戻り値を受け取ってグローバル変数のturnを書き換えるようにすることで
パスの実現は可能ではないかなと思ってます。

以下はこのままだと若干不具合があるのでそれは頑張って直してみてください。

コード:

 
    // while(裏画面を表画面に反映、メッセージ処理、画面クリア、キーの更新)
    while(ScreenFlip() == 0 && ProcessMessage() == 0 && ClearDrawScreen() == 0 && gpUpdatekey() == 0)                       //無限ループ
    {
        //ここにゲームの処理を描く------------------------------------------------
        if (turn < End)
        {
            Cursor_Move();
            Me_PutPice(board_y, board_x, turn);
            AI(board_y, board_x, turn);
            turn = CheckEnd(turn);

            switch(turn)
            {
            case End:
                CheckWinner();
                break;
            }
        }
        Draw_Board();
        //----------------------------------------------------------------------
    }
この2点を変えることでパスの実装はできるかと思います。
ちなみにデバッグ用に#ifを使ってパスが起きるような駒配置でスタートできるようにもできますので、
パスのデバッグ用によければ活用してみてください。
(RELEASEビルドの場合は#elseの通常駒位置が、DEBUGビルドの場合は#ifの黒しか置けない配置になります)

コード:

#if _DEBUG
    board[0][0]     = BLACK;    //[3][4]
    board[BOARDSIZE-1][0]     = BLACK;    //[4][3]
    board[0][1]         = WHITE;    //[4][4]
    board[BOARDSIZE-1][1] = WHITE;    //[3][3]
#else
    board[BOARDSIZE / 2 - 1][BOARDSIZE / 2]     = BLACK;    //[3][4]
    board[BOARDSIZE / 2][BOARDSIZE / 2 - 1]     = BLACK;    //[4][3]
    board[BOARDSIZE / 2][BOARDSIZE / 2]         = WHITE;    //[4][4]
    board[BOARDSIZE / 2 - 1][BOARDSIZE / 2 - 1] = WHITE;    //[3][3]
#endif
修正後のソースでちょっと遊ばせてもらいほっこりさせてもらいました(*´ヮ`)

今後はAIの難易度を変えるために何種類か用意してみたり、プレーヤーが先手と後手を選べたりするなどいろいろ発展はできそうですね。
長くなりましたがわかりにくい場所があれば遠慮なく聞いてくださいね。
Advanced Supporting Developer
無理やりこじつけ(ぉ

tonari
記事: 28
登録日時: 8年前

Re: オセロプログラミング、パスができない

#3

投稿記事 by tonari » 8年前

返信ありがとうございます。
わかりやすい説明で助かりました。
教えていただいた通りに打ち込んでみたところ、無事にパスができました。
今度はもう一度最初から作り直し、ゲームの完成度を高めたり、
AIの作り方の勉強もするつもりです。
ずっと悩んでいたのですっきりしました。

アバター
asd
記事: 319
登録日時: 15年前

Re: オセロプログラミング、パスができない

#4

投稿記事 by asd » 8年前

tonari さんが書きました:返信ありがとうございます。
わかりやすい説明で助かりました。
教えていただいた通りに打ち込んでみたところ、無事にパスができました。
今度はもう一度最初から作り直し、ゲームの完成度を高めたり、
AIの作り方の勉強もするつもりです。
ずっと悩んでいたのですっきりしました。
少し返信が遅くなってしまっていましたが、無事にパスができるようになったようで何よりです(*´ヮ`)
今後も機能追加やAI強化などを経てよりよいオセロプログラムができるとよいですね。
陰ながら応援してますのでがんばってください。
Advanced Supporting Developer
無理やりこじつけ(ぉ

返信

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