囲碁プログラムの作り方(基本編)

2002年2月23日:初版
2004年5月17日:加筆、修正


はじめに

囲碁プログラムの基本部分の作り方を解説します。

強い囲碁プログラムを作るのは非常に難しく、 どうすれば強いプログラムが作れるかまだ誰にもわかりません。 (一手目から終局まで読み切れるならば、それが最強の囲碁プログラムなのは わかりますが、何億年かけても読み切れないくらい囲碁は奥が深いので、 そんなプログラムは作っても意味がありません。) この基本編では、人間どうしの対局ができる囲碁プログラムを作ります。 つまり、コンピュータが碁盤と碁石の代わりになります。 そんなプログラムでも、囲んだら石を取るといった、囲碁の基本的な処理が必要です。 そして、人間が考えるところをコンピュータに置き換えれば、 人間対コンピュータの対局ができるようになります。 ですから、まずは人間どうしの対局ができる囲碁プログラムから作りましょう。 そのコンピュータが考える部分は、初級編以降で解説する予定です。

このページに書かれているプログラミング言語はC言語ですが、 C言語が囲碁プログラム作りに向いているというわけではありません。 私がC言語を使い慣れているという理由だけです。 実はどの言語が囲碁プログラム向きかはわかりません。 なぜなら、最強の囲碁プログラムでもアマ初段あるかどうかのレベルですから、 どの言語が適しているか実証されていないのです。 ですから、これから囲碁プログラムを作ろうという人は、 一番使い慣れている言語で書くことをおすすめします。 それから、ここに書かれているプログラムは囲碁プログラムの作り方を解説するためのもので、 実行時の速さとか使用メモリ量とかは気にせずに書かれています。

碁盤プログラムの作成

最初はコンピュータを碁盤と碁石の代わりにした、碁盤プログラムを作ります。

碁盤プログラムの機能

以上の機能が実現できれば、人対人の対局ができます。 また、思考プログラムを作成して人の代わりにすれば、人対プログラムの対局ができるようになります。

碁盤の表現(データ構造)

碁盤は二次元配列を使うのがわかりやすいでしょう。 碁盤は19×19なのですが、周囲を一回り大きくして21×21の配列を用意します。 実際に石を置くのは21×21の内側の19×19の範囲です。 理由はいろいろありますが、一番の理由は分かりやすさです。 だって、座標が0から始まるよりも1から始まる方が分かりやすいでしょう?

碁盤の各点には、黒/白/空点(石が置かれていない状態)の3つのうちのどれかを設定します。 ついでに盤外という状態もあると便利です。 21×21の一番外側には常に盤外を設定しておきましょう。

#define SPACE 0	           /* 空点(石が置かれていない) */
#define BLACK 1	           /* 黒                       */
#define WHITE 2	           /* 白                       */
#define OUT   3	           /* 盤外                     */

#define BOARD_SIZE 21      /* 碁盤の大きさ             */

/* 碁盤 */
int board[BOARD_SIZE][BOARD_SIZE];

/* 碁盤の初期化 */
void InitializeBoard()
{
  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;
  }
}
右上隅の星、つまり座標(16,4)に黒石を置くときは、次のようになります。
(X座標とY座標の順番をわざと逆にしていますので注意してください。)

board[4][16] = BLACK; 

碁盤の表示

次は碁盤の表示です。凝ったグラフィックで綺麗な画面を書き、 操作しやすいユーザーインタフェースを書きたいところですが、ここではそこまでしません。 囲碁の対局に最低限必要な部分だけにとどめます。
/* 碁盤の表示のための文字 */
char stone[] = { '+', '*', 'O', '?' };

/* 碁盤を表示する */
void DisplayBoard()
{
  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" );
  }
}
右上隅の星(16,4)に黒、左上隅小目(3,4)に白があるとき、次のように表示されます。
    [ 1 2 3 4 5 6 7 8 910111213141516171819]
[ 1]  + + + + + + + + + + + + + + + + + + +
[ 2]  + + + + + + + + + + + + + + + + + + +
[ 3]  + + + + + + + + + + + + + + + + + + +
[ 4]  + + O + + + + + + + + + + + + * + + +
[ 5]  + + + + + + + + + + + + + + + + + + +
[ 6]  + + + + + + + + + + + + + + + + + + +
[ 7]  + + + + + + + + + + + + + + + + + + +
[ 8]  + + + + + + + + + + + + + + + + + + +
[ 9]  + + + + + + + + + + + + + + + + + + +
[10]  + + + + + + + + + + + + + + + + + + +
[11]  + + + + + + + + + + + + + + + + + + +
[12]  + + + + + + + + + + + + + + + + + + +
[13]  + + + + + + + + + + + + + + + + + + +
[14]  + + + + + + + + + + + + + + + + + + +
[15]  + + + + + + + + + + + + + + + + + + +
[16]  + + + + + + + + + + + + + + + + + + +
[17]  + + + + + + + + + + + + + + + + + + +
[18]  + + + + + + + + + + + + + + + + + + +
[19]  + + + + + + + + + + + + + + + + + + +

対局の流れ

本来は、日本の囲碁ルールである日本囲碁規約の通りに対局すべきなのですが、 ここでは簡略化したルールを使います。 (あなたは、対局の停止と終局の違いを知っていますか? ルールを完全に知らなくても、誰でも対局ができるのです。)

ここでは、黒と白の両者が連続してパスをしたときに終局としましょう。

/*--------------------*/
/* 終局するまでループ */
/*--------------------*/

while( 1 ){

  /*------*/
  /* 黒番 */
  /*------*/

  /* (1) 黒の着手位置を入力 */

  /* (2) 黒が投了なら白の勝ち */
  /*     このループを抜ける   */

  /* (3) 黒白ともにパスなら地を数えて勝者を表示 */
  /*     このループを抜ける                     */

  /* (4) 碁盤に黒石を置く */

  /*------*/
  /* 白番 */
  /*------*/

  /* (1) 白の着手位置を入力 */

  /* (2) 白が投了なら黒の勝ち */
  /*     このループを抜ける   */

  /* (3) 黒白ともにパスなら地を数えて勝者を表示 */
  /*     このループを抜ける                     */

  /* (4) 碁盤に白石を置く */
}

石を取る

四方を囲まれたら、囲まれた石を取り除く処理です。
ある座標(x,y)の石が囲まれているかどうか調べるということは、 ある座標の石の隣の4点(x-1,y),(x,y-1),(x+1,y),(x,y+1)に空点がある(ダメがある)かどうかを 探すことです。もしも空点があれば、その石は囲まれていないので、石を取ることはできません。 もしも空点が一つもなかったら、その石は碁盤から取り除きます。

調べようとしている座標(x,y)の隣(例えば(x+1,y))に同じ色の石があったら、 その石の四方も調べないといけません。 その石(x+1,y)の隣(x+2,y)も同じ色の石があったら、 その石の四方も調べないといけません。 同じ色の石が連続している間は、隣、隣、、、と調べ続けます。 もしも、その石の隣に相手の石があるか、隣が盤外ならば、 方向を変えて(例えば((x,y-1))同じように空点があるかどうか調べていきます。

(例) 白3子がつながっている場合において、空点があるかどうかを調べる順番

┼┼┼┼┼┼
┼┼┼┼┼┼
┼┼@○┼┼ @の石から調べはじめる場合
┼┼○┼┼┼
┼┼┼┼┼┼
┼┼┼┼┼┼

┼┼┼┼┼┼
┼┼┼4┼┼
┼┼@A3┼ 右が同じ色なので、さらに右へと進み
┼┼○┼┼┼ Bが空点なので、方向転換して上へ
┼┼┼┼┼┼
┼┼┼┼┼┼

┼┼┼┼┼┼
┼┼┼4┼┼
┼┼DA3┼ 上も空点なので、また方向転換してDへ
┼┼○6┼┼ Dは既に調査済み(@)なので、方向転換して下へ
┼┼┼┼┼┼
┼┼┼┼┼┼

┼┼┼┼┼┼
┼┼┼4┼┼
┼┼FA3┼ Aの石の四方を全て調べたので、@の位置へ戻り(F)
┼┼○6┼┼
┼┼┼┼┼┼
┼┼┼┼┼┼

┼┼┼┼┼┼
┼┼84┼┼
┼9FA3┼ Fから見て、残りの三方向を調べる
┼┼I6┼┼
┼┼┼┼┼┼
┼┼┼┼┼┼

┼┼┼┼┼┼
┼┼84┼┼
┼9KA3┼ Iの四方向を調べたら、全ての石の四方向を調べ終わったので終了
┼13I11┼┼
┼┼14┼┼┼
┼┼┼┼┼┼
このように、同じ処理を繰り返すときは、再帰的アルゴリズムを使うと良いでしょう。
【再帰的アルゴリズム】

 「その定義の中に、更にその定義されるべきものが簡単化されて含まれているとき」
 それを再帰的という。

 再帰の例(階乗計算)
   0!=1
   K > 0ならば、K!=K ×(K−1)!

 プログラムで書くと、
   F( K: integer )
     if( K > 0 ){
            return( K × F(K-1) )
      }else{
            return( 1 )
         }
空点があるかどうかを調べる処理を、再帰の形で書き表すと次のようになります。
( x, y )が空点ならば、ダメがある
          敵石ならば、ダメがない
     盤外ならば、ダメがない
     味方ならば、隣の座標を調べるために再帰
隣の座標は4つあること、 同じ座標を何度も調べないようにすること、 を考慮して、プログラムの形で書くと次のようになります。
/*-----------------------------------------------*/
/* 座標(x,y)の石が囲まれているか調べる関数       */
/* 囲まれていない(ダメがある)ならばFALSEを返す */
/*-----------------------------------------------*/

Function( x, y )
{
  /* (1) (x,y)が既に調べた点ならTRUEを返す                              */
  /*     まだ調べてない点なら調べたことを覚えておく                     */
  /*     覚えておかないと、同じ場所を何度も調べることになってしまうから */

  /* (2) (x,y)が空点ならばFALSEを返す */

  /* (3) (x,y)が同じ色の石ならばその石の四方を調べるために */
  /*     Functionを再帰呼び出し                            */
  /* (3.1)  rtn = Function( x+1, y );                      */
  /*        rtnがFALSEならばFALSEを返す                    */
  /* (3.2)  rtn = Function( x, y-1 );                      */
  /*        rtnがFALSEならばFALSEを返す                    */
  /* (3.3)  rtn = Function( x-1, y );                      */
  /*        rtnがFALSEならばFALSEを返す                    */
  /* (3.4)  rtn = Function( x, y+1 );                      */
  /*        rtnがFALSEならばFALSEを返す                    */

  /* (4) (x,y)が相手の色の石ならばTRUEを返す */  

  /* (5) (x,y)が盤外ならばTRUEを返す */  
}
次は石を取り除く関数です。 囲まれているかどうかを調べるのと同じように再帰的アルゴリズムを使って書くことができます。
( x, y )が空点ならば、何もしないで終了
          敵石ならば、何もしないで終了
     盤外ならば、何もしないで終了
     味方ならば、( x, y )に空点を設定し、アゲハマを1つカウントし、
     隣の座標を調べるために再帰
なお、石を取り除く関数を呼ぶ前に、ダメがあるかどうかを調べる関数を呼んでおきましょう。 ダメが無いことがわかったら、石を取り除く関数を呼びます。

合法手

合法手とは、ルールで認められた手のことです。ルールで認められていない手とは、次の3種類です。
  1. 既に石があるところ
  2. 四方が相手の石に囲まれているところ
  3. 劫だてを打たなければいけないときの劫
この3種類のどれかにあてはまるかどうかを調べます。

(1) 既に石があるところ

これは調べるのは簡単です。

if( board[y][x] != SPACE ){
  /* 空点じゃないので打てません */
}
(2) 四方が相手の石に囲まれているところ

これは実際に碁盤の上に置いてみて、その石が囲まれているかどうかを調べるのが簡単でしょう。 石が囲まれているかどうかを調べる処理は前章で書いた関数を使います。 しかし、囲まれていても、相手の石を取れるなら打っても良いので、打った石の隣に相手の石があるならば、 その石が打った石と同じ色の石で囲まれているかどうかも調べなければいけません。

/* (1) 仮に石を置く                                   */
/*                                                    */
/* (2) その石は相手に囲まれているか調べる             */
/*                                                    */
/* (3) if( 囲まれている )                             */
/*         その石の隣の相手の石が取れるかどうか調べる */
/*         if( 相手の石が取れる )                     */
/*             合法手                                 */
/*         else                                       */
/*             合法手ではない                         */
/*     else                                           */
/*         囲まれていないので合法手                   */
/*                                                    */
/* (4) 仮に置いた石をはがす                           */
(3) 劫だてを打たなければいけないときの劫

劫の場所に打ってよいかどうか調べるには、直前の相手の着手位置がわからないといけません。 直前に相手が劫の場所に打ったのなら、自分はそこに打てないからです。 そのためには、対局のときに1手目から順番にどこに打ったのかを覚えておきましょう。

劫かどうかを調べるにはどうすればよいのでしょう。 実は、劫の形かどうかを調べなくても良いのです。 それから、劫による着手禁止かどうかは、手を打ったときに求めて覚えておく方が簡単です。 これから打とうとする座標が、覚えた座標と同じならば、着手できないというように処理します。

では、手を打ったときに劫による着手禁止の求め方です。

実は、この二つの処理では、劫でないところも劫と判定してしまいます。 しかし劫ではなくても、四方が相手の石に囲まれているので、置くことができません。 ですから、劫だから着手禁止点と判定しても何の問題も起きないのです。
(A) 置いた石の隣に同じ色の石があるので劫ではない例
    ┼●○┼      ┼●○○┼
    ●○×○      ●○×●○
    ┼●●┼      ┼●○○┼

(B) 取った石数が1ではないので劫ではない例
    ┼┼●┼      ┼●●○┼
    ┼●○●      ●○○×○
    ●○×○      ┼●●○┼
    ┼●○┼

(C) 劫ではないのに劫と判定してしまう例
    しかし抜き跡に白は打てないので問題ない
    ┼●○┼
    ●○×○
    ┼●┼┼

地の計算

実はここが一番難しい。地の計算ができるということは、石の生死がちゃんと判定できて、 どこが地で、どこがダメかわからないといけない。つまり、地の計算が正確にできるためには、 碁が強くなければならない。逆に言うと、プログラムが地の計算を正確にできるなら、 そのプログラムはもうすでに強い囲碁プログラムなのです。

この基本編では、強いプログラムの作り方を書くのが目的ではないので、地の計算は省略しましょう。 (市販の囲碁プログラムでも、地の計算が正しくできるものはありません。 囲碁プログラムが計算した後に、人間が手直しする方式のプログラムが多い。)

最も単純な思考ルーチン(ランダムに次の一手を求める)

碁盤プログラムができたら、人の代わりに思考プログラムを作ります。 思考プログラムの最も単純なのは、ランダムに次の一手を求めるプログラムでしょう。 (ランダムじゃ、思考しているとは言えないかも知れませんけど)
  1. 乱数関数を使うことによりランダムに数を生成する
  2. 数を19×19の碁盤の座標に割り当てる
    (例)生成された数を361で割った余り=19×Y+XとなるX,Yを求める
  3. 割り当てた座標が着手禁止点かどうか調べる
  4. 着手禁止点ならば、乱数関数を再び呼ぶ
    着手禁止点でなくなるまで呼び、着手禁止点でなければそれを次の一手の座標とする
人間プレーヤーの代わりにこの関数を呼べば、人対コンピュータの対局ができます。 ただし、弱いだけじゃなく、着手禁止点がなくなるまで打ち続けるので、 相手をしてると嫌になっちゃいます。 でも、これからあなたが作るプログラムのテスト対局の 相手にはなるでしょう。

碁盤プログラムのサンプル

サンプルとして以下にプログラムを置きますが、決して良いプログラムとは言えません。 書き方に工夫すべきところがたくさんあります。 ここでは、プログラミングのテクニックについて解説するのが目的ではないので、 そこはみなさんが改良してください。
/*------------------------------------------------------------------*/
/* コンピュータ上で人間どうしが対局するプログラム                   */
/*------------------------------------------------------------------*/

#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 )

/*------------------------------------------------------------------*/
/* 構造体                                                           */
/*------------------------------------------------------------------*/

/* 棋譜 */
typedef struct {
  int color;
  int x;
  int y;
} type_log;

/*------------------------------------------------------------------*/
/* 関数                                                             */
/*------------------------------------------------------------------*/

/* メイン関数 */
int main( int argc, char *argv[] );                          

/* 碁盤の初期化 */
static void InitializeBoard( void );

/* 碁盤を表示する */
static void DisplayBoard( void );

/* 着手位置の入力 */
static void ThinkMove( int color, int *x, int *y );

/* 画面から数値を入力 */
static void InputCoordinate( int color, int *x, int *y );

/* 合法手がどうか調べる */
static int  CheckLegal( int color, int x, int y );

/* 自殺手かどうか調べる */
static int  CheckSuicide( int color, int x, int y );

/* チェック用の碁盤をクリア */
static void ClearCheckBoard( void );

/* 相手に囲まれているか調べる */
static int  DoCheckRemoveStone( int color, int x, int y );

/* 勝敗の判定 */
static int  CountScore( void );

/* 碁盤に石を置く*/
static void SetStone( int color, int x, int y );

/* 囲まれた石を取り除く */
static int  RemoveStone( int color, int x, int y );

/* 囲まれた石を取り除く */
static int  DoRemoveStone( int color, int x, int y, int prisoner );

/* 棋譜に記録 */
static void RecordMove( int color, int x, int y );

/*------------------------------------------------------------------*/
/* 変数                                                             */
/*------------------------------------------------------------------*/

/* 碁盤の表示のための文字 */
static char stone[] = { '+', '*', 'O', '?' };

/* 碁盤 */
static int board[BOARD_SIZE][BOARD_SIZE];

/* 手数 */
static int move;

/* アゲハマ */
static int black_prisoner;
static int white_prisoner;

/* 劫の位置 */
static int ko_x;
static int ko_y;

/* 劫が発生した手数 */
static int ko_num;

/* 棋譜(499手まで覚える) */
static type_log logData[500];

/* 合法手かどうか調べるのに使う */
static int checkBoard[BOARD_SIZE][BOARD_SIZE];

/*------------------------------------------------------------------*/
/* メイン関数                                                       */
/*------------------------------------------------------------------*/
int
main( int argc, char *argv[] )
{
  int xBlack, yBlack;  /* 黒の着手位置 */
  int xWhite, yWhite;  /* 白の着手位置 */
  int score;           /* 黒地−白地   */

  /* 碁盤の初期化 */
  InitializeBoard();

  printf( "Sample Program Start\n" );

  xBlack = yBlack = 999;  /* 最初は0以外の数字を書いておく */
  xWhite = yWhite = 999;  /* 最初は0以外の数字を書いておく */

  /* アゲハマ */
  black_prisoner = 0;
  white_prisoner = 0;

  /* 手数 */
  move = 1;

  /* 終局するまでループ */
  while( 1 ){

    /*------*/
    /* 黒番 */
    /*------*/

    /* 碁盤の表示 */
    DisplayBoard();

    /* 黒の着手位置を入力 */
    ThinkMove( BLACK, &xBlack, &yBlack );

    /* 黒が投了なら白の勝ち */
    if( xBlack >= (BOARD_SIZE-1) || yBlack >= (BOARD_SIZE-1) ){
      printf( "Black Resign. White Win.\n" );
      break;
    }

    /* 黒白ともにパスなら地を数えて勝者を表示 */
    if( ( xBlack < 1 || yBlack < 1 ) && ( xWhite < 1 || yWhite < 1 ) ){
      score = CountScore();
      if( score > 0 ){
	/* 黒が多ければ黒の勝ち */
	printf( "Black Win\n" );
      }else if( score < 0 ){
	/* 黒が少なければ白の勝ち */
	printf( "White Win\n" );
      }else{
	/* 同数ならば引分け */
	printf( "Draw\n" );
      }
      break;
    }

    /* 座標が1〜19なら碁盤に石を置く */
    SetStone( BLACK, xBlack, yBlack );

    /* 棋譜に記録 */
    RecordMove( BLACK, xBlack, yBlack );

    /* 手数が1増える */
    move++;

    /*------*/
    /* 白番 */
    /*------*/

    /* 碁盤の表示 */
    DisplayBoard();

    /* 白の着手位置を入力 */
    ThinkMove( WHITE, &xWhite, &yWhite );

    /* 白が投了なら黒の勝ち */
    if( xWhite >= (BOARD_SIZE-1) || yWhite >= (BOARD_SIZE-1) ){
      printf( "White Resign. Black Win.\n" );
      break;
    }

    /* 黒白ともにパスなら地を数えて勝者を表示 */
    if( ( xBlack < 1 || yBlack < 1 ) && ( xWhite < 1 || yWhite < 1 ) ){
      score = CountScore();
      if( score > 0 ){
	/* 黒が多ければ黒の勝ち */
	printf( "Black Win\n" );
      }else if( score < 0 ){
	/* 黒が少なければ白の勝ち */
	printf( "White Win\n" );
      }else{
	/* 同数ならば引分け */
	printf( "Draw\n" );
      }
      break;
    }

    /* 座標が1〜19なら碁盤に石を置く */
    SetStone( WHITE, xWhite, yWhite );

    /* 棋譜に記録 */
    RecordMove( WHITE, xWhite, yWhite );

    /* 手数が1増える */
    move++;
  }

  printf( "Sample Program End\n" );
}

/*------------------------------------------------------------------*/
/* 碁盤の初期化                                                     */
/*------------------------------------------------------------------*/
static void
InitializeBoard( void )
{
  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( void )
{
  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
ThinkMove( int color, int *x, int *y )
{
  int inputX, inputY;

  while( 1 ){
    /* 着手位置の入力 */
    InputCoordinate( color, &inputX, &inputY );

    if( inputX > 0 && inputX < (BOARD_SIZE-1) &&
        inputY > 0 && inputY < (BOARD_SIZE-1) ){
      /* 座標が1〜19ならば合法手がどうか調べる */
      if( CheckLegal( color, inputX, inputY ) == TRUE ){
	break;
      }
    }else{
      break;
    }
  }

  *x = inputX;
  *y = inputY;
}

/*------------------------------------------------------------------*/
/* 画面から数値を入力                                               */
/* X座標が1〜19ならばY座標も入力する                                */
/* X座標が1より小さければパスとみなし、Y座標は入力しない            */
/* X座標が19より大きければ投了とみなし、Y座標は入力しない           */
/*------------------------------------------------------------------*/
static void
InputCoordinate( int color, int *x, int *y )
{
  int val;

  printf( "\n" );
  if( color == BLACK ){
    printf( "Please Input Black Coordinates.\n" );
  }else{
    printf( "Please Input White Coordinates.\n" );
  }
  printf( " Pass -> 0, Quit -> 20\n" );

  /* X座標の入力 */
  while( 1 ){
    printf( "InputX:" );
    fflush( stdout );
    val = scanf( "%d", x );
    if( val == 1 ){
      break;
    }
  }

  if( *x >= 1 && *x < (BOARD_SIZE-1) ){
    /* X座標が1〜19なのでY座標の入力 */
    while( 1 ){
      printf( "InputY:" );
      fflush( stdout );
      val = scanf( "%d", y );
      if( val == 1 ){
	break;
      }
    }
  }else if( *x < 1 ){
    /* X座標が1より小さいのでパスとみなし、Y座標は入力しない */
    *y = 0;
  }else{
    /* X座標が19より大きいので投了とみなし、Y座標は入力しない */
    *y = 20;
  }
}

/*------------------------------------------------------------------*/
/* 合法手かどうか調べる                                             */
/*------------------------------------------------------------------*/
static int
CheckLegal( int color, int x, int y )
{
  /* 空点じゃないと置けません */
  if( board[y][x] != SPACE ){
    return( FALSE );
  }

  /* 一手前に劫を取られていたら置けません */
  if( move > 1 ){
    if( ko_x == x && ko_y == y && ko_num == (move-1) ){
      return( FALSE );
    }
  }

  /* 自殺手なら置けません */
  if( CheckSuicide( color, x, y ) == TRUE ){
    return( FALSE );
  }

  /* 以上のチェックをすべてクリアできたので置けます */
  return( TRUE );
}

/*------------------------------------------------------------------*/
/* 自殺手かどうか調べる                                             */
/*------------------------------------------------------------------*/
static int
CheckSuicide( int color, int x, int y )
{
  int rtnVal;
  int opponent;  /* 相手の色 */

  /* 仮に石を置く */
  board[y][x] = color;

  /* マークのクリア */
  ClearCheckBoard();
  
  /* その石は相手に囲まれているか調べる */
  rtnVal = DoCheckRemoveStone( color, x, y );

  /* 囲まれているならば自殺手の可能性あり */
  if( rtnVal == TRUE ){

    /* 相手の色を求める */
    opponent = reverseColor( color );

    /* その石を置いたことにより、隣の相手の石が取れるなら自殺手ではない */
    if( x > 1 ){
      /* 隣は相手? */
      if( board[y][x-1] == opponent ){
	/* マークのクリア */
	ClearCheckBoard();
	/* 相手の石は囲まれているか? */
        rtnVal = DoCheckRemoveStone( opponent, x-1, y );
	/* 相手の石を取れるので自殺手ではない */
        if( rtnVal == TRUE ){
	  /* 盤を元に戻す */
	  board[y][x] = SPACE;
          return( FALSE );
        }
      }
    }
    if( y > 1 ){
      /* 隣は相手? */
      if( board[y-1][x] == opponent ){
	/* マークのクリア */
	ClearCheckBoard();
	/* 相手の石は囲まれているか? */
        rtnVal = DoCheckRemoveStone( opponent, x, y-1 );
	/* 相手の石を取れるので自殺手ではない */
        if( rtnVal == TRUE ){
	  /* 盤を元に戻す */
	  board[y][x] = SPACE;
          return( FALSE );
        }
      }
    }
    if( x < (BOARD_SIZE-2) ){
      /* 隣は相手? */
      if( board[y][x+1] == opponent ){
	/* マークのクリア */
	ClearCheckBoard();
	/* 相手の石は囲まれているか? */
        rtnVal = DoCheckRemoveStone( opponent, x+1, y );
	/* 相手の石を取れるので自殺手ではない */
        if( rtnVal == TRUE ){
	  /* 盤を元に戻す */
	  board[y][x] = SPACE;
          return( FALSE );
        }
      }
    }
    if( y < (BOARD_SIZE-2) ){
      /* 隣は相手? */
      if( board[y+1][x] == opponent ){
	/* マークのクリア */
	ClearCheckBoard();
	/* 相手の石は囲まれているか? */
        rtnVal = DoCheckRemoveStone( opponent, x, y+1 );
	/* 相手の石を取れるので自殺手ではない */
        if( rtnVal == TRUE ){
	  /* 盤を元に戻す */
	  board[y][x] = SPACE;
          return( FALSE );
        }
      }
    }

    /* 盤を元に戻す */
    board[y][x] = SPACE;

    /* 相手の石を取れないなら自殺手 */
    return( TRUE );

  }else{

    /* 盤を元に戻す */
    board[y][x] = SPACE;

    /* 囲まれていないので自殺手ではない */
    return( FALSE );
  }
}

/*------------------------------------------------------------------*/
/* チェック用の碁盤をクリア                                         */
/*------------------------------------------------------------------*/
static void
ClearCheckBoard( void )
{
  int x, y;

  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      checkBoard[y][x] = FALSE;
    }
  }
}

/*------------------------------------------------------------------*/
/* 座標(x,y)にあるcolor石が相手に囲まれているか調べる               */
/*------------------------------------------------------------------*/
static int     /* 空点があればFALSEを返し、空点がなければTRUEを返す */
DoCheckRemoveStone( int color, int x, int y )
{
  int rtn;

  /* その場所は既に調べた点ならおしまい */  
  if( checkBoard[y][x] == TRUE ){
    return( TRUE );
  }
  
  /* 調べたことをマークする */
  checkBoard[y][x] = TRUE;

  /* 何も置かれていないならばおしまい */
  if( board[y][x] == SPACE ){
    return( FALSE );
  }

  /* 同じ色の石ならばその石の隣も調べる */  
  if( board[y][x] == color ){
    /* その石の左(x-1,y)を調べる */
    if( x > 1 ){
      rtn = DoCheckRemoveStone( color, x-1, y );
      if( rtn == FALSE ){
	return( FALSE );
      }
    }
    /* その石の上(x,y-1)を調べる */
    if( y > 1 ){
      rtn = DoCheckRemoveStone( color, x, y-1 );
      if( rtn == FALSE ){
	return( FALSE );
      }
    }
    /* その石の右(x+1,y)を調べる */
    if( x < (BOARD_SIZE-2) ){
      rtn = DoCheckRemoveStone( color, x+1, y );
      if( rtn == FALSE ){
	return( FALSE );
      }
    }
    /* その石の下(x,y+1)を調べる */
    if( y < (BOARD_SIZE-2) ){
      rtn = DoCheckRemoveStone( color, x, y+1 );
      if( rtn == FALSE ){
	return( FALSE );
      }
    }
  }

  /* 相手の色の石があった */  
  return( TRUE );
}

/*------------------------------------------------------------------*/
/* 勝敗の判定                                                       */
/* このプログラムでは地を数えていません                             */
/* アゲハマの多い方を勝ちと判定しています                           */
/*------------------------------------------------------------------*/
static int
CountScore()
{
  int black_score;
  int white_score;

  /* 碁盤上の地を数えるべきところだが省略 */
  black_score = 0;
  white_score = 0;

  /* アゲハマを加える */
  black_score += black_prisoner;
  white_score += white_prisoner;

  /* 黒−白を返す */
  return( black_score - white_score );
}

/*------------------------------------------------------------------*/
/* 碁盤に石を置く                                                   */
/*------------------------------------------------------------------*/
static void
SetStone( int color, int x, int y )
{
  int prisonerN;    /* 取り除かれた石の数(上) */
  int prisonerE;    /* 取り除かれた石の数(右) */
  int prisonerS;    /* 取り除かれた石の数(下) */
  int prisonerW;    /* 取り除かれた石の数(左) */
  int prisonerAll;  /* 取り除かれた石の総数 */
  int koFlag;       /* 劫かどうか */

  /* 座標(x,y)に石を置く */
  board[y][x] = color;

  /* 置いた石の隣に同じ色の石はあるか? */
  if( board[y+1][x] != color &&
      board[y-1][x] != color &&
      board[y][x+1] != color &&
      board[y][x-1] != color ){
    /* 同じ色の石がないならば劫かもしれない */
    koFlag = TRUE;
  }else{
    /* 同じ色の石があるならば劫ではない */
    koFlag = FALSE;
  }

  /* 取り除かれた石の数 */
  prisonerN = prisonerE = prisonerS = prisonerW = 0;

  /* 置いた石の周囲の相手の石が死んでいれば碁盤から取り除く */
  if( y > 1 ){
    prisonerN = RemoveStone( color, x, y-1 );
  }
  if( x > 1 ){
    prisonerW = RemoveStone( color, x-1, y );
  }
  if( y < (BOARD_SIZE-2) ){
    prisonerS = RemoveStone( color, x, y+1 );
  }
  if( x < (BOARD_SIZE-2) ){
    prisonerE = RemoveStone( color, x+1, y );
  }

  /* 取り除かれた石の総数 */
  prisonerAll = prisonerN + prisonerE + prisonerS + prisonerW;

  /* 置いた石の隣に同じ色の石がなく、取り除かれた石も1つならば劫 */
  if( koFlag == TRUE && prisonerAll == 1 ){
    /* 劫の発生した手数を覚える */
    ko_num = move;
    /* 劫の座標を覚える */
    if( prisonerE == 1 ){
      /* 取り除かれた石が右 */
      ko_x = x+1;
      ko_y = y;
    }else if( prisonerS == 1 ){
      /* 取り除かれた石が下 */
      ko_x = x;
      ko_y = y+1;
    }else if( prisonerW == 1 ){
      /* 取り除かれた石が左 */
      ko_x = x-1;
      ko_y = y;
    }else if( prisonerN == 1 ){
      /* 取り除かれた石が上 */
      ko_x = x;
      ko_y = y-1;
    }
  }

  /* アゲハマの更新 */
  if( prisonerAll > 0 ){
    if( color == BLACK ){
      black_prisoner += prisonerAll;
    }else if( color == WHITE ){
      white_prisoner += prisonerAll;
    }
  }
}

/*------------------------------------------------------------------*/
/* 座標(x,y)の石が死んでいれば碁盤から取り除く                      */
/*------------------------------------------------------------------*/
static int		/* 碁盤から取り除かれた石数 */
RemoveStone( int color, int x, int y )
{
  int prisoner;  /* 取り除かれた石数 */

  /* 置いた石と同じ色なら取らない */
  if( board[y][x] == color ){
    return( 0 );
  }

  /* 空点なら取らない */
  if( board[y][x] == SPACE ){
    return( 0 );
  }

  /* マークのクリア */
  ClearCheckBoard();

  /* 囲まれているなら取る */
  if( DoCheckRemoveStone( board[y][x], x, y ) == TRUE ){
    prisoner = DoRemoveStone( board[y][x], x, y, 0 );
    return( prisoner );
  }

  return( 0 );
}

/*------------------------------------------------------------------*/
/* 座標(x,y)のcolor石を碁盤から取り除き、取った石の数を返す         */
/*------------------------------------------------------------------*/
static int	 /* アゲハマ */
DoRemoveStone( int color, int x, int y, int prisoner )
{
  /* 取り除かれる石と同じ色ならば石を取る */
  if( board[y][x] == color ){

    /* 取った石の数を1つ増やす */
    prisoner++;

    /* その座標に空点を置く */
    board[y][x] = SPACE;

    /* 左を調べる */
    if( x > 1 ){
      prisoner = DoRemoveStone( color, x-1, y, prisoner );
    }
    /* 上を調べる */
    if( y > 1 ){
      prisoner = DoRemoveStone( color, x, y-1, prisoner );
    }
    /* 右を調べる */
    if( x < (BOARD_SIZE-2) ){
      prisoner = DoRemoveStone( color, x+1, y, prisoner );
    }
    /* 下を調べる */
    if( y < (BOARD_SIZE-2) ){
      prisoner = DoRemoveStone( color, x, y+1, prisoner );
    }
  }

  /* 取った石の数を返す */
  return( prisoner );
}

/*------------------------------------------------------------------*/
/* 棋譜に記録                                                       */
/*------------------------------------------------------------------*/
static void
RecordMove( int color, int x, int y )
{
  if( move < 500 ){
    logData[move].color = color;
    logData[move].x     = x;
    logData[move].y     = y;
  }
}

/*---------------------- < end of program > ------------------------*/

勝也のページへ戻る