C言語リバーシ 局面の表示

シェアする

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

こんにちは。昨日はアルバイトとAtCoder Beginner Contestがあったので更新できませんでした。

前回は局面の初期化を行いましたが、正しく初期化できているかを確認することができませんでした。

そこで、今回は局面を表示する関数を作成したいと思います。

前回までのコードを掲載しておきます

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

リバーシのものなのかオセロ特有のものなのかはわかりませんが、チェスと同じようにマスをアルファベットと数字で表現するのが一般的です。(ただしチェスは最下行が1で、オセロは最上行が1です。)

一番左の列がa列で、一番右の列がh列です。一番上の行が1行で、一番下の行が8行です。

それを用いて、f5やd6のように場所を指定します。
その表現方法を利用して、

.  a  b  c  d  e  f  g  h
1
2
3
4
5
6
7
8

というふうに盤面を表示し、その中に盤面の情報を表示したいと思います。

手順は、

①"   a  b  c  d  e  f  g  h\n"を表示(\nは改行です)

②行番号を表示

③その行の局面の情報(空白か黒石か白石か)を左から8つ表示

④改行

(②~④を合計8回繰り返し行う)

⑤現在の手番を表示

みたいな感じでいきます。(今後、表示する情報は増えますが、今回はこれだけで。)

盤面をビットボードで保持していますので、ある場所の状態を調べるには、「その場所のみ1で他は全部0である64ビット変数」をposとし、black & pos(&はビットANDです。)が0でないなら(0でなくなるのはblackにおける注目箇所の位置が1であるときのみ)、そこには黒石があり、そうでなくてwhite & posが1でないならそこには白石があり、どちらでもないならそこには石は無い、というようにします。

左上(64ビット変数で言うと最上ビットの場所)から左下へ探すので、最初にpos = (uint64_t)1 << 63を代入して、ループのたびにpos >>= 1をすれば良さそうです(a += 5 が a = a + 5 と同じであるように、pos >>= 1 は pos = pos >> 1 と同じです)。

マス表示が8回行われる毎に改行するので、ループ用インデックスをi(0から63まで)としてiを8で割った余りが7なら改行するようにします(同じくiを8で割った余りが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 >>= 1;
	}
	// 手番表示
	printf("\n手番: ");
	switch(board->teban){
		case SENTE: printf("先手\n"); break;
		case GOTE: printf("後手\n"); break;
		default: break;
	}
}

ですが、僕は使用フォントの関係で○と●と□が半角サイズで表示されてしまうので、表示には"黒"と"白"と"口"を使用しています。(口は漢字のくちです。mouthです。カッコ悪いですが全角幅で表示したいので仕方なく。ちなみに使用フォントはRicty Dminishedというプログラミング用フォントです。)

ちなみに、手番の表現にenumを使っているおかげで、手番の判断のswitch-case文で値をcase SENTEとcase GOTEにすることができるので非常に見やすいです(マクロ定義でも同じです)。手番がGAME_OVERの場合の処理は記述していません。必要があればその時にやりましょう。

全体のソースコードは以下になります。

#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);		// 局面を初期化する関数
void ShowBoard(BOARD *board);	// 盤面を表示する関数

int main(void){
	BOARD board;
	// 局面を初期化
	Init(&board);
	ShowBoard(&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 >>= 1;
	}
	// 手番表示
	printf("\n手番: ");
	switch(board->teban){
		case SENTE: printf("先手\n"); break;
		case GOTE: printf("後手\n"); break;
		default: break;
	}
}

これでリバーシの初期局面が表示されるはずです。

表示が半角サイズになる場合は、僕と同じく"●"を"黒"に、"○"を"白"に、"□"を"口"にしましょう。案外違和感ありません。

さて、今回は局面の表示を実装しました。リバーシはゲームなので表示は絶対に必要ですが、表示が不要なプログラムでも表示関数を作っておくとデバッグ(エラー修正)が楽になります。

次回からはついに石を置く処理を作っていきましょうか。一回では終わりません。

石を置く処理は、最終的には「座標を受け取る」「合法手(ルールに沿った場所)かどうか判断する」「合法手なら、そこに置いたときのひっくりかえる石のパターンを調べる」「石を置き、ひっくり返す処理を行う(手番も変更)」「合法手でないなら、再度入力を要求する」という複雑な処理になりますが、まずは「場所を受け取り、とりあえず置く」という関数を作ってから、それに肉付けする形になります。

複雑になってきますが、頑張りましょう。では。

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

シェアする

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

フォローする