C言語リバーシ 局面の初期化

シェアする

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

こんにちは。最近はウィンドウプログラミング(コンソールではなくGUIでリバーシをする)に精を出していたので、すっかり更新を忘れていました。GUIリバーシはAI未実装というところ(石数計算と勝敗判定をしてくれるオセロ盤)までできました。いつか解説します。

さて、前回は局面の定義に手番と手数の情報を追加しました。

前回までのソースコードはこちらです。

#include <stdio.h>

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

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

int main(void){
	return 0;
}

今回は局面を対局開始前の初期局面で初期化します。

Wikipediaの「オセロ (遊戯)」の「オセロとリバーシ」の項目によると、オセロの初期局面は真ん中4つのうち左上と右下が白、右上と左下が黒だそうです。

リバーシは初期局面に決まりはないみたいですが、オセロの通りに行きましょう。

ビット演算

ビットボードを用いたボードゲームでは必須の「ビット演算」が今回から必要になってきます。

これを紹介しましょう。

例えば、int型の変数a,bがあって、今aには10が、bには7が入っているとします。

このとき、コンピュータ上では2進数で

a: 00001010

b: 00000111

と表現されています。(実際はint型は32ビット変数である場合が多いので、これより左側にOがあと24個続きます。)

C言語には、このビットの並びを直接操作する演算が用意されています。

詳しい解説はググっていただきたいのですが、簡単に説明します。

ビットANDは両方1が立っている位置のみ1にします。

a&bは00000010

ビットORはどちらか一方、あるいは両方で1が立っている位置のみ1にします。

a|bは00001111

ビットXORは、どちらか一方だけで1が立っている位置のみ1にします。

a^bは00001101

ビットNOTはすべてのビットを反転します。

~aは11110101

ビットシフトはすべてのビットを指定数ずらします。

a<<2は00101000 a>>2は00000010

以上の&,|,^,~,<<,>>が主なビット演算です。

では、これを用いて盤面を初期化しましょう。

スクリーンショット 2015-06-26 20.05.02

初期局面の黒石の位置を左下から順に数えていくと、29番目と36番目にあることが分かります。

ですので、000...0001という64ビットの数字(すなわち1)を28回左にシフトしたもの(000...010...000で、1の右に0が28個)と、1を35回左にシフトしたものの、ビットORをとれば、黒石が存在するマスを表すビットボードが得られます。

つまり、board.black = 000...0100000010...0ということですが、実際には

board.black = ((uint64_t)1<<28) + ((uint64_t)1<<35);

で表現します。

ただ単に1<<35と書くと、1がint型(32ビット変数)に解釈されてしまうので、35回左シフトするとはみ出て0になってしまいます。そのため、(uint64_t)で64ビット変数にキャストしています。(型キャスト、知らない方はググってください。)

同じく、

board.white = ((uint64_t)1<<27) + ((uint64_t)1<<36);

となります。

ですが、今回、この初期化はInit関数という、main関数の外で行うので、局面に変更を加えるためにポインタで渡します。

つまり、

int main(void){
	BOARD board;
	Init(&board);
	/* その他の処理 */
}

void Init(BOARD *board){
	/* boardを初期局面にする処理 */
	return;
}

となります。

このとき、Init関数ではboardはポインタで渡されているので、board.blackではだめで、(*board).blackとすると大丈夫になります。

ですが、この記法は面倒なのでC言語に用意されている上記と全く同じ意味になる記法board->blackと使いましょう。

つまり、

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;
}

となります。(手番を先手にし、手数を0にしました。)

前回までのソースコードと合わせると、

#include <stdio.h>

// 手番を表す列挙型
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);	// 局面を初期化する関数

int main(void){
	BOARD board;

	// 局面を初期化
	Init(&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;
}

となります。

さて、今回は局面をリバーシの初期局面に初期化する関数を作りました。次回は局面を表示する関数を作ります。表示関数を作って初めて、今回の初期化がうまくいったか分かります。

それでは、また。

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

シェアする

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

フォローする