以下にその例を描きます。左は盤面で、右は連ごとに同じ番号を付けたものです。 この盤面には8つの連が存在していることがわかります。
盤面 連番号 +++++++ +++++++ +●●●+++ +111+++ ++○●○++ ++213++ +●●○●○+ +44567+ +++○+++ +++5+++ ++++●++ ++++8++ +++++++ +++++++
/* 碁盤の大きさ */ #define BOARD_SIZE 21 /* 色 */ #define BLACK 1 /* 黒 */ #define WHITE 2 /* 白 */ /* 生死 */ #define ALIVE 1 /* 生 */ #define DEAD 2 /* 死 */ #define NEUTRAL 3 /* 中立 */ /* 連 */ struct go_string { int id; /* 識別番号 */ int color; /* 色 (BLACK/WHITE) */ int size; /* サイズ */ int dame; /* ダメ数 */ int status; /* 生死 (ALIVE/DEAD/NEUTRAL) */ int offense_x; /* 取る手のX座標 */ int offense_y; /* 取る手のY座標 */ int defense_x; /* 逃げる手のX座標 */ int defense_y; /* 逃げる手のY座標 */ struct go_string *neighbors; /* 隣接する連 */ struct go_string *next; /* 次の連 */ }; /* 連識別番号(ID) */ static int stringIdBoard[BOARD_SIZE][BOARD_SIZE];連の構造体の要素に、連を構成する各石の座標を格納してもよいのですが、 ここでは連の識別番号(ID)を格納することにします。 そして、連の識別番号(ID)を格納するための2次元配列を、別に用意しました。
盤面 識別番号(ID) +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++○●●+●+++++++++++ +++122+3+++++++++++ +++○+○●++++++++●+++ +++1+45++++++++6+++ +++++○+++++++++++++ +++++4+++++++++++++ +++○+++++++++++++++ +++7+++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++○++++++++++●++++ +++8++++++++++9++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++
/*------------------------------*/ /* ある黒の連の生死を調べる場合 */ /*------------------------------*/ /* (1) 白から先に打ったときに、黒石を取れるか調べる */ status = SearchStatus( 生死を調べる黒石, 次は白が打つ ); /* (2) 結果によって分類 */ if( status == ALIVE ){ /* 黒石を取れないならば、その黒石は生き */ return( ALIVE ); }else if( status == DEAD ){ /* 黒石が取れるならば、黒から先に打ったときに、黒石が取られないか調べる */ status = SearchStatus( 生死を調べる黒石, 次は黒が打つ ); /* (3) 結果によって分類 */ if( status == ALIVE ){ /* 黒石が取られないならば、その黒石は中立 */ return( NEUTRAL ); }else if( status == DEAD ){ /* 黒石が取られるならば、その黒石は死に */ return( DEAD ); } }以上のように、黒から先に打った場合と、白から先に打った場合の結果がわかれば、 連の生死の判定は簡単にわかるのですが、実はここには大きな問題が隠れています。
それは、「どのような状態の時、連を取れないと判断するのか?」です。
もしも、取るのはダメが無くなった時、取れないのは合法手がなくなった時、 と定義したら、どうなるでしょう。以下の図の4番の連が取れないことがわかるまでに いったい何手先を読むことになるでしょう。 きっと、10手や20手じゃわからないでしょうね。
盤面 識別番号(ID) +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++○●●+●+++++++++++ +++122+3+++++++++++ +++○+○●++++++++●+++ +++1+45++++++++6+++ +++++○+++++++++++++ +++++4+++++++++++++ +++○+++++++++++++++ +++7+++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++○++++++++++●++++ +++8++++++++++9++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++思考時間は無限ではありません。 ですから、「多分この連は取れないだろう。」と判断して、 読みを打ち切らなければいけません。 人間も、ある程度読んだところで、「多分この連は取れないだろう。」と判断しています。
以下にその例を描きます。 左図の黒(一間)は接続していると考えられるので、結線と呼びます。 中図の黒は白に覗かれている(白から切断可能)ので、結線と呼びません。 覗かれているという情報付きの結線と呼ぶプログラムもあるようです。 右図の黒は白によって完全に接続が絶たれているので、結線と呼ぶプログラムはないようです。
結線 覗かれている結線 結線とは呼ばない +++++++ +++++++ +++++++ +++++++ +++○+++ +++○+++ ++●+●++ ++●+●++ ++●○●++ +++++++ +++++++ +++○+++ +++++++ +++++++ +++++++
++++++++ +++++++ ++++++++ +++++++ ++●■■●++ +++■●++ ++++++++ ++●■+++ ++++++++ +++++++位置関係は、一間、二間、三間、コスミやケイマなどです。 しかし、あまり石と石が離れている場合は認識する必要はないでしょう。
また、石と石だけでなく、石と盤端も結線として認識しています。 以下にその例を描きます。
二線 三線 四線 +++++ +++++ +++++ +++++ +++++ ++●++ +++++ ++●++ ++■++ ++●++ ++■++ ++■++ −−■−− −−■−− −−■−−盤面全体では、以下のようになります。 覗かれているかどうかの定義はプログラムによって異なるようです。 例えば、右下の(14,17)の黒と(15,17)の白のように隣あっている場合、 例では三線と認識しましたが、相手からハネる手があるから結線ではないと 認識するプログラムもあります。
盤面 結線 +++++++++++++++++++ +++■+■++■++++++■+++ +++●+++++++++++++++ +++●■■++■++++++■+++ ++○++●++●++++++++++ □□○+■●■■●++++++■+++ +++○+++++++++++●+++ +++○+++++++++++●■■■ +++++++++++++++++++ +++□+++++++++++++++ +++○+++++++++++++++ □□□○+++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ ++++++++++++++++○++ ++++++++++++++++○□□ ++●++++++++++++++++ ■■●+++++++++++++□++ +++++++++++++●●+○++ +++++++++++++●●+○□□ +++○+++++●+++●○○+++ +++○+++++●■■■●○○□□□ +++++++++++++++++++ +++□+++++■+++■□□□++ +++++++++++++++++++ +++□+++++■+++■□□□++
以下に例を描きます。左の盤面の石の配置に対して、群ごとに識別番号を付けたのが右の図です。
1番の群について見てみましょう。
1番の群は3つの黒石と、その黒石に囲まれた部分から成っています。
普通の囲碁プレーヤーなら、この3つの石について別々に生死を考えたりはしません。
3つの石をひとまとまりとして考えるだけでなく、この3つの石が囲んでいると考えられる部分も一緒にして、生死を考えています。
盤面 群の識別番号 +++++++++++++++++++ +++111111++++++2222 +++●+++++++++++++++ +++111111++++++2222 ++○++●++●++++++++++ 333+11111++++++2222 +++○+++++++++++●+++ 3333+++++++++++2222 +++++++++++++++++++ 3333+++++++++++++++ +++○+++++++++++++++ 3333+++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++ ++++++++++++++++○++ ++++++++++++++++444 ++●++++++++++++++++ 555+++++++++++++444 +++++++++++++●●+○++ +++++++++++++66+444 +++○+++++●+++●○○+++ +++7+++++6666644444 +++++++++++++++++++ +++7+++++6666644444 +++++++++++++++++++ +++7+++++6666644444
この定義は、石が少ないうちは人間の認識結果に近いのですが、 石が混みあってくるといろいろと問題が出てきます。 (と言うか、ここでは厳密な定義まで書いていませんが)。 盤面の認識のうち、群の定義は非常に難しい問題なのです。 この群の定義と、群の生死の認識がどのくらい正確かによって、 プログラムの強さが決まると言ってもいいくらいです。
このプログラムは、"figure"という名前のファイルを読んで、 連の識別番号を表示するプログラムです。
"figure"の例 ++++++++++++*++++++ ++O+++++O**++*OOO++ ++*O++++OO+**O++**+ ++O++OO+++O+*+O*+++ +++*+*+++***+O+O*++ +O+++++OO++++++*+++ ++OO**+O+O+++O+++++ ++*++++*O++++++*+++ ++O+**+*+++++O+++++ ++++*O+++++++++*+++ +++OO+++++O+++++*++ +++++++O+O+++++++++ +++++++++*O++O+O+++ +++*+++++*O++++*+++ +*+++++++*++OO+++++ +OO+**+++*OO**O*+++ +++OO*+OOO***O++*++ +++++++***+++++++++ +++++++++++++++++++
/*------------------------------------------------------------------*/ /* ファイルから盤面図を読み込み、連の識別番号を出力するプログラム */ /*------------------------------------------------------------------*/ #include <stdio.h> #include <string.h> #define TRUE 1 #define FALSE 0 #define SPACE 0 /* 空点(石が置かれていない) */ #define BLACK 1 /* 黒 */ #define WHITE 2 /* 白 */ #define OUT 3 /* 盤外 */ #define BOARD_SIZE 21 /* 碁盤の大きさ */ /* 相手の色 */ #define reverseColor( color ) ( color == BLACK ? WHITE : BLACK ) /*------------------------------------------------------------------*/ /* 関数 */ /*------------------------------------------------------------------*/ /* メイン関数 */ void main( void ); /* 盤面図入力 */ static void InputFigure( int board[][BOARD_SIZE] ); /* 碁盤の初期化 */ static void InitializeBoard( int board[][BOARD_SIZE] ); /* 碁盤を表示する */ static void DisplayBoard( int board[][BOARD_SIZE] ); /* 連の認識 */ static void MakeString( int board[][BOARD_SIZE] ); /* 連の認識 */ static int ClassificationString( int board[][BOARD_SIZE], int x, int y, int color, int number ); /* 連の表示 */ static void DisplayString( void ); /*------------------------------------------------------------------*/ /* 変数 */ /*------------------------------------------------------------------*/ /* 碁盤の表示のための文字 */ static char stone[] = { '+', '*', 'O', '?' }; /* 連識別番号 */ static int stringIdBoard[BOARD_SIZE][BOARD_SIZE]; /*------------------------------------------------------------------*/ /* メイン関数 */ /*------------------------------------------------------------------*/ void main( void ) { int board[BOARD_SIZE][BOARD_SIZE]; /* 碁盤 */ printf( "Sample Program Start\n" ); /* 盤面図の入力 */ InputFigure( board ); /* 碁盤の表示 */ DisplayBoard( board ); /* 連の認識 */ MakeString( board ); /* 連の表示 */ DisplayString(); printf( "Sample Program End\n" ); } /*------------------------------------------------------------------*/ /* 盤面図入力 */ /* ファイル名"figure"というファイルに盤面図が書かれている */ /*------------------------------------------------------------------*/ static void InputFigure( int board[][BOARD_SIZE] ) { FILE *fp; char buf[256]; int x, y, i; /* ファイルを開く */ if( (fp = fopen( "figure", "r" )) == NULL ){ fprintf( stderr, "ERROR: file(figure) open fail\n" ); exit(1); } /* 碁盤の初期化 */ InitializeBoard( board ); /* 各座標を読む */ for( y=1; y < (BOARD_SIZE-1); y++ ){ buf[0] = '\0'; fgets( buf, 256, fp ); for( x=1, i=0; x < (BOARD_SIZE-1); x++ ){ if( buf[i] == 'O' ){ board[y][x] = WHITE; }else if( buf[i] == '*' ){ board[y][x] = BLACK; }else if( buf[i] == '+' ){ board[y][x] = SPACE; }else{ board[y][x] = SPACE; } i++; } } /* ファイルを閉じる */ fclose( fp ); } /*------------------------------------------------------------------*/ /* 碁盤の初期化 */ /*------------------------------------------------------------------*/ static void InitializeBoard( int board[][BOARD_SIZE] ) { int x, y; for( y=1; y < (BOARD_SIZE-1); y++ ){ for( x=1; x < (BOARD_SIZE-1); x++ ){ board[y][x] = SPACE; } } for( y=0; y < BOARD_SIZE; y++ ){ board[y][0]= OUT; board[y][BOARD_SIZE-1] = OUT; board[0][y] = OUT; board[BOARD_SIZE-1][y] = OUT; } } /*------------------------------------------------------------------*/ /* 碁盤を表示する */ /*------------------------------------------------------------------*/ static void DisplayBoard( int board[][BOARD_SIZE] ) { int x, y; printf( " [ 1 2 3 4 5 6 7 8 910111213141516171819]\n" ); for( y=1; y < (BOARD_SIZE-1); y++ ){ printf( "[%2d] ", y ); for( x=1; x < (BOARD_SIZE-1); x++ ){ printf( " %c", stone[board[y][x]] ); } printf( "\n" ); } } /*------------------------------------------------------------------*/ /* 連の認識 */ /*------------------------------------------------------------------*/ static void MakeString( int board[][BOARD_SIZE] ) { int x, y; int stringId; for( y=0; y < BOARD_SIZE; y++ ){ for( x=0; x < BOARD_SIZE; x++ ){ stringIdBoard[y][x] = 0; } } /* string id */ stringId = 1; for( y=1; y<(BOARD_SIZE-1); y++ ){ for( x=1; x<(BOARD_SIZE-1); x++ ){ if( board[y][x] != SPACE ){ if( ClassificationString( board, x, y, board[y][x], stringId ) == TRUE ){ stringId++; } } } } /* 連情報を登録 */ } /*------------------------------------------------------------------*/ /* 連を区別する */ /*------------------------------------------------------------------*/ static int ClassificationString( int board[][BOARD_SIZE], int x, int y, int color, int number ) { /* 既にIDが付いているので対象外 */ if( stringIdBoard[y][x] != 0 ){ return( FALSE ); } if( board[y][x] == color ){ /* 同じ色なので同じ連である */ stringIdBoard[y][x] = number; if( x > 1 ){ ClassificationString( board, x-1, y, color, number ); } if( x < (BOARD_SIZE-2) ){ ClassificationString( board, x+1, y, color, number ); } if( y > 1 ){ ClassificationString( board, x, y-1, color, number ); } if( y < (BOARD_SIZE-2) ){ ClassificationString( board, x, y+1, color, number ); } return( TRUE ); }else{ /* 同じ色ではないので終了 */ return( FALSE ); } } /*------------------------------------------------------------------*/ /* 連の表示 */ /*------------------------------------------------------------------*/ static void DisplayString( void ) { int x, y; printf( "### String ID ###\n" ); for( y=1; y < (BOARD_SIZE-1); y++ ){ for( x=1; x < (BOARD_SIZE-1); x++ ){ if( stringIdBoard[y][x] == 0 ){ printf( " +" ); }else{ printf( "%3d", stringIdBoard[y][x] ); } } printf( "\n" ); } } /*---------------------- < end of program > ------------------------*/勝也のページへ戻る