C言語リバーシ 終了・パス判定

シェアする

  • このエントリーをはてなブックマークに追加

こんにちは。だいぶ久しぶりのプログラミング更新になってしまいました。

先日発売されたV4 flowerというVOCAOIDを使ってカバー作品を制作しているのですが、それが楽しくてつい。

さて、前回何やったか忘れましたが、とりあえずゲームをできるようにしたかと思います。

今回は終了とパスの判定を行って、完全にプレイできるようにしましょう。

リバーシにおいて、対局が終了する条件とは何でしょうか。

64マスすべて埋まった時、ではないですね。それもありますが、0-54とかでも終了するので、「両者の合法手が無くなったとき」ですね。

また、リバーシにはパスがあります。合法手が無くなったときに何もできずに手番が相手に渡る状態ですね。

したがって、終了・パス判定は以下のように実装します。

①現局面の現手番の合法手を取得

②合法手が無ければ、手番を反転させて合法手を取得する

③手番反転で合法手が無ければ、2を返す

④手番反転で合法手があれば、「パス」と表示して1を返す(このとき局面は手番反転が起こったまま)

⑤最初の時点(①で取得した合法手について)で合法手があれば何もせず0を返す

この関数をChechFinishPass()関数とし、返り値で状況を判断します。返り値はマクロ定義します。

#define PASS 1
#define FINISH 2

// 終了・パス判定
int CheckFinishPass(BOARD *board){
	uint64_t valid;

	valid = GenValidMove(board);

	// 終了・パス判定
	if( valid == 0 ){
		board->teban *= -1;
		if(GenValidMove(board) == 0){
			//終了
			board->teban = GAME_OVER;
			return FINISH;
		}
		printf("パス\n");
		ShowBoard(board);
		return PASS;
	}
	return 0;
}

合法手が無いというのは、合法手ビットボードに1が立っていない、つまりGenValidMove()の戻り値が数字として0であるということになります。

これで目的の関数はできたので、これをmain関数の石を置くループ内に実装しましょう。

現在はこれです。

	// 石を置く
	for (i = 0; i < 5; i++){
		// 合法手を得る
		valid = GenValidMove(&board);
		// 手を受け取る
		pos = GetPos();
		if( pos == INPUT_ERROR ){
			printf("エラーです。\n");
			continue;
		}else if( (pos & valid) == 0){
			printf("非合法手です。\n");
			continue;
		}
		Put(&board, pos);
		ShowBoard(&board);
	}

まず、ループをwhileループに変えて、繰り返し条件を(board.teban != GAME_OVER)にしましょう。

	// 石を置く
	while(board.teban != GAME_OVER){
		// 合法手を得る
		valid = GenValidMove(&board);
		// 手を受け取る
		pos = GetPos();
		if( pos == INPUT_ERROR ){
			printf("エラーです。\n");
			continue;
		}else if( (pos & valid) == 0){
			printf("非合法手です。\n");
			continue;
		}
		Put(&board, pos);
		ShowBoard(&board);
	}

そして、処理の最後に終了・パス判定を入れましょう。

	// 石を置く
	while(board.teban != GAME_OVER){
		// 合法手を得る
		valid = GenValidMove(&board);
		// 手を受け取る
		pos = GetPos();
		if( pos == INPUT_ERROR ){
			printf("エラーです。\n");
			continue;
		}else if( (pos & valid) == 0){
			printf("非合法手です。\n");
			continue;
		}
		Put(&board, pos);
		ShowBoard(&board);
		
		CheckFinishPass(&board);
	}

これだけで大丈夫です。大丈夫なはずです。確認はしてません(最後まで打つの面倒だしパスの確認は特に面倒なので。)

さて、これでちゃんと遊べるオセロ盤が完成しました。

となると次回はついに人工知能の実装になりますか。

と言いたいところですが、先に石数計算を用意して、コードを整理してからにしましょうか。

最後に全体のソースコードを掲載しておきます。

#include <stdio.h>

#define INPUT_ERROR 3

#define PASS 1
#define FINISH 2

// 手番を表す列挙型
typedef enum TEBAN{
	SENTE = -1,
	GOTE = 1,
	GAME_OVER = 0
}TEBAN;

// 局面を表す構造体
typedef struct BOARD{
	uint64_t black, white;	// 黒石・白石のビットボード
	TEBAN teban;		// 手番
	int move_num;			// 何手動いたか(手数)
}BOARD;

// 関数プロトタイプ宣言
void Init(BOARD *board);							// 局面を初期化する関数
void ShowBoard(BOARD *board);						// 盤面を表示する関数
uint64_t GetPos();									// 座標を入力させ、posを返す関数
uint64_t PosTranslate(char file, int rank);			// 座標をuint64_tのposに変換する関数
void Put(BOARD *board, uint64_t pos);				// 石を置く関数 posは絶対に合法手
uint64_t GenValidMove(const BOARD *board);			// 合法手を生成する関数
uint64_t GetReverse(BOARD *board, uint64_t pos);	// 反転パターンを求める関数
int CheckFinishPass(BOARD *board);					// 終了・パス判定

int main(void){
	BOARD board;
	uint64_t pos, valid;
	int i;
	
	// 局面を初期化
	Init(&board);
	
	// 局面を表示
	ShowBoard(&board);
	
	// 石を置く
	while(board.teban != GAME_OVER){
		// 合法手を得る
		valid = GenValidMove(&board);
		// 手を受け取る
		pos = GetPos();
		if( pos == INPUT_ERROR ){
			printf("エラーです。\n");
			continue;
		}else if( (pos & valid) == 0){
			printf("非合法手です。\n");
			continue;
		}
		Put(&board, pos);
		ShowBoard(&board);
		
		CheckFinishPass(&board);
	}
	return 0;
}

// 局面を初期化
void Init(BOARD *board){
	board->black = ((uint64_t)1<<28) + ((uint64_t)1<<35);
	board->white = ((uint64_t)1<<27) + ((uint64_t)1<<36);
	board->teban = SENTE;
	board->move_num = 0;
}

// 盤面を表示する関数
void ShowBoard(BOARD *board){
	int i, rank = 1;
	uint64_t pos = (uint64_t)1<<63;
	printf("  a b c d e f g h\n");
	// 盤面表示
	for ( i = 0; i < 64 ; i++){
		// 行番号
		if(i % 8 == 0) printf("%d", rank++);
		// 盤面状態表示
		if( ( board->black & pos )!= 0) printf("黒");
		else if( ( board->white & pos ) != 0) printf("白");
		else printf("口");
		// 8回表示が終わるごとに改行
		if(i % 8 == 7) printf("\n");
		// posを一つずらす
		pos >>= 1;
	}
	// 手番表示
	printf("\n手番: ");
	switch(board->teban){
		case SENTE: printf("先手\n"); break;
		case GOTE: printf("後手\n"); break;
		default: break;
	}
}

// 座標を入力させ、posを返す関数
uint64_t GetPos(){
	char file;	// 列番号(アルファベット)
	int rank;	// 行番号(数字)
	uint64_t pos;	// 指定箇所を示すビットボード
	
	printf("座標を入力してください。(例:f5)\n");
	scanf(" %c%d", &file, &rank);
	
	// 不正値ならエラーを返す
	if(file < 'a' || file > 'h' || rank < 1 || rank > 8) return INPUT_ERROR;
	
	
	// 受け取った座標からビットボードを生成
	pos = PosTranslate(file, rank);
	return pos;
}

// 座標をuint64_tのposに変換する関数
uint64_t PosTranslate(char file, int rank){
	int file_num;
	uint64_t pos;
	
	file_num = 7 - file + 'a';
	
	pos = ( (uint64_t)1 << ( file_num + 8 * (8 - rank) ) );
	
	return pos;
}

// 石を置く関数 posは絶対に合法手
void Put(BOARD *board, uint64_t pos){
	uint64_t rev;
	
	// 反転パターン取得
	rev = GetReverse(board, pos);
	
	switch(board->teban){
		case SENTE:
			board->black ^= pos | rev;
			board->white ^= rev;
			board->teban = GOTE;
			break;
		case GOTE:
			board->white ^= pos | rev;
			board->black ^= rev;
			board->teban = SENTE;
			break;
		default:
			break;
	}
	board->move_num++;
	return;
}

// 合法手を生成する関数
uint64_t GenValidMove(const BOARD *board){
	int i;
	uint64_t me, enemy, masked_enemy, t, valid = 0, blank;
	
	// 現在手番の方をme、相手をenemyにする
	if(board->teban == SENTE){
		me = board->black;
		enemy = board->white;
	}else{
		me = board->white;
		enemy = board->black;
	}
	
	// 空マスのビットボードを(黒または白)のビットNOTで得る
	blank = ~(board->black | board->white);
	
	// 右方向
	masked_enemy = enemy & 0x7e7e7e7e7e7e7e7e; //端列を除く敵石
	t = masked_enemy & (me << 1); //自石の左隣にある敵石を調べる
	for(i = 0; i < 5; i++){
		t |= masked_enemy & (t << 1);
	}
	valid = blank & (t << 1);
	
	// 左方向
	masked_enemy = enemy & 0x7e7e7e7e7e7e7e7e;
	t = masked_enemy & (me >> 1);
	for(i = 0; i < 5; i++){
		t |= masked_enemy & (t >> 1);
	}
	valid |= blank & (t >> 1);
	
	// 上方向
	masked_enemy = enemy & 0x00ffffffffffff00;
	t = masked_enemy & (me << 8);
	for (i = 0; i < 5; i++){
		t |= masked_enemy & (t << 8);
	}
	valid |= blank & (t << 8);
	
	// 下方向
	masked_enemy = enemy & 0x00ffffffffffff00;
	t = masked_enemy & (me >> 8);
	for (i = 0; i < 5; i++){
		t |= masked_enemy & (t >> 8);
	}
	valid |= blank & (t >> 8);
	
	// 右上方向
	masked_enemy = enemy & 0x007e7e7e7e7e7e00;
	t = masked_enemy & (me << 7);
	for (i = 0; i < 5; i++){
		t |= masked_enemy & (t << 7);
	}
	valid |= blank & (t << 7);
	
	// 左上方向
	masked_enemy = enemy & 0x007e7e7e7e7e7e00;
	t = masked_enemy & (me << 9);
	for (i = 0; i < 5; i++){
		t |= masked_enemy & (t << 9);
	}
	valid |= blank & (t << 9);
	
	// 右下方向
	masked_enemy = enemy & 0x007e7e7e7e7e7e00;
	t = masked_enemy & (me >> 9);
	for (i = 0; i < 5; i++){
		t |= masked_enemy & (t >> 9);
	}
	valid |= blank & (t >> 9);
	
	// 左下方向
	masked_enemy = enemy & 0x007e7e7e7e7e7e00;
	t = masked_enemy & (me >> 7);
	for (i = 0; i < 5; i++){
		t |= masked_enemy & (t >> 7);
	}
	valid |= blank & (t >> 7);

	return valid;
}

// 反転パターンを求める関数
uint64_t GetReverse(BOARD *board, uint64_t pos){
	int i;
	uint64_t me, enemy, mask, rev = 0, rev_cand;
	
	// 現在手番の方をme、相手をenemyにする
	if(board->teban == SENTE){
		me = board->black;
		enemy = board->white;
	}else{
		me = board->white;
		enemy = board->black;
	}
	
	// 右方向
	rev_cand = 0;
	mask = 0x7e7e7e7e7e7e7e7e;
	for( i = 1; ( (pos >> i) & mask & enemy ) != 0; i++ ){
		rev_cand |= (pos >> i);
	}
	if( ( (pos >> i) & me) != 0 ) rev |= rev_cand;
	
	// 左方向
	rev_cand = 0;
	mask = 0x7e7e7e7e7e7e7e7e;
	for( i = 1; ( (pos << i) & mask & enemy ) != 0; i++ ){
		rev_cand |= (pos << i);
	}
	if( ( (pos << i) & me) != 0 ) rev |= rev_cand;
	
	// 上方向
	rev_cand = 0;
	mask = 0x00ffffffffffff00;
	for( i = 1; ( (pos << 8 * i) & mask & enemy ) != 0; i++ ){
		rev_cand |= (pos << 8 * i);
	}
	if( ( (pos << 8 * i) & me) != 0 ) rev |= rev_cand;
	
	// 下方向
	rev_cand = 0;
	mask = 0x00ffffffffffff00;
	for( i = 1; ( (pos >> 8 * i) & mask & enemy ) != 0; i++ ){
		rev_cand |= (pos >> 8 * i);
	}
	if( ( (pos >> 8 * i) & me) != 0 ) rev |= rev_cand;

	// 右上方向
	rev_cand = 0;
	mask = 0x007e7e7e7e7e7e00;
	for( i = 1; ( (pos << 7 * i) & mask & enemy ) != 0; i++ ){
		rev_cand |= (pos << 7 * i);
	}
	if( ( (pos << 7 * i) & me) != 0 ) rev |= rev_cand;
	
	// 左上方向
	rev_cand = 0;
	mask = 0x007e7e7e7e7e7e00;
	for( i = 1; ( (pos << 9 * i) & mask & enemy ) != 0; i++ ){
		rev_cand |= (pos << 9 * i);
	}
	if( ( (pos << 9 * i) & me) != 0 ) rev |= rev_cand;
	
	// 右下方向
	rev_cand = 0;
	mask = 0x007e7e7e7e7e7e00;
	for( i = 1; ( (pos >> 9 * i) & mask & enemy ) != 0; i++ ){
		rev_cand |= (pos >> 9 * i);
	}
	if( ( (pos >> 9 * i) & me) != 0 ) rev |= rev_cand;
	
	// 左下方向
	rev_cand = 0;
	mask = 0x007e7e7e7e7e7e00;
	for( i = 1; ( (pos >> 7 * i) & mask & enemy ) != 0; i++ ){
		rev_cand |= (pos >> 7 * i);
	}
	if( ( (pos >> 7 * i) & me) != 0 ) rev |= rev_cand;
	
	return rev;
}

// 終了・パス判定
int CheckFinishPass(BOARD *board){
	uint64_t valid;

	valid = GenValidMove(board);

	// 終了・パス判定
	if( valid == 0 ){
		board->teban *= -1;
		if(GenValidMove(board) == 0){
			//終了
			board->teban = GAME_OVER;
			return FINISH;
		}
		printf("パス\n");
		ShowBoard(board);
		return PASS;
	}
	return 0;
}

次回は石数計算をします。では。

スポンサーリンク
レクタングル(大)
レクタングル(大)

シェアする

  • このエントリーをはてなブックマークに追加

フォローする