囲碁プログラムの作り方(初級編)

はじめに

囲碁プログラムの作り方(基本編)で、 人間どうしが対局ができるプログラムを作りました。 今度はコンピュータが考える部分(思考ルーチン)の作り方を解説します。 この初級編では、どのようにすればプログラムが次の一手を求めることができるか について解説し、簡単な思考ルーチンを作ります。

何をどんな順番で考える?

囲碁において次の一手を決めるとき、人間は何を考えているのでしょう。 考えることは人によって様々でしょうが、 ここでは心理的なことや囲碁に関係ないことは除きます。 それでも、何をどんな順番で考えているのか、人によって違うでしょう。

人間の考え方を囲碁プログラムで実現するとき、 最も簡単なのは次のアルゴリズムです。 多くの囲碁プログラムの思考ルーチンも、 このアルゴリズムを基本に作られています。 (強いプログラムはもっといろいろなことを考えていますし、 順番もこんなに単純ではありません。)

(1) 盤面の状況を認識する

プログラムが次の一手を考えるときの基になる情報ですから、 なるべく多くの情報を正確に認識しなければいけません。 しかし、考える時間には限りがあるので、すべてを正確に認識することは不可能です。 例え時間が無制限でも、どうすれば正しく認識できるのか、 その方法さえわからないものばかりです。 正しく認識する方法については、囲碁プログラムの作者がいろいろな試行錯誤を 繰り返しながら、その人なりのアルゴリズムを開発しています。 そのアルゴリズムが公開されていることはほとんど無いので、 ここで幾つか実験をしてみましょう。

地の認識の実験(その1)

どこが黒地でどこが白地なのかを認識する実験をします。

まず最初に、各石から距離3以内(二間トビまたはケイマまで)を、 その石の色の地と考えてみます。

(例)
盤面                    地(■で表している)
+++++++          +++■+++
+++++++          ++■■■++
+++++++          +■■■■■+
+++●+++          ■■■●■■■
+++++++          +■■■■■+
+++++++          ++■■■++
+++++++          +++■+++

+++++++++      +++■+++++
+++++++++      ++■■■■+++
+++++++++      +■■■■■■++
+++●+++++      ■■■●■■■■+
+++++●+++      +■■■■●■■■
+++++++++      ++■■■■■■+
+++++++++      +++■■■■++
+++++++++      +++++■+++
相手の石が近くにある場合は、勢力が打ち消しあって、 どちらの地でもないと考えてみます。
(例)
+++++++++      +++■+□+++
+++++++++      ++■■+□□++
+++++++++      +■■■+□□□+
+++●+○+++      ■■■●+○□□□
+++++++++      +■■■+□□□+
+++++++++      ++■■+□□++
+++++++++      +++■+□+++
これをプログラムで書くと、次のようになります。
/*------------------------------------------------------------------*/
/* 盤面の評価                                                       */
/*------------------------------------------------------------------*/
static int
EvalBoard( int board[][BOARD_SIZE] )
{
  int blackBoard[BOARD_SIZE][BOARD_SIZE];  /* 黒石の影響範囲 */
  int whiteBoard[BOARD_SIZE][BOARD_SIZE];  /* 白石の影響範囲 */
  int totalBoard[BOARD_SIZE][BOARD_SIZE];  /* 地 */
  int x, y, value;

  /* 初期化 */
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      blackBoard[y][x] = FALSE;
      whiteBoard[y][x] = FALSE;
      totalBoard[y][x] = SPACE;
    }
  }

  /* 距離3以内なら地とみなす */
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      if( board[y][x] == BLACK ){
        SetValue( blackBoard, x-3, y   );
        SetValue( blackBoard, x-2, y-1 );
        SetValue( blackBoard, x-2, y   );
        SetValue( blackBoard, x-2, y+1 );
        SetValue( blackBoard, x-1, y-2 );
        SetValue( blackBoard, x-1, y-1 );
        SetValue( blackBoard, x-1, y   );
        SetValue( blackBoard, x-1, y+1 );
        SetValue( blackBoard, x-1, y+2 );
        SetValue( blackBoard, x,   y-3 );
        SetValue( blackBoard, x,   y-2 );
        SetValue( blackBoard, x,   y-1 );
        SetValue( blackBoard, x,   y   );
        SetValue( blackBoard, x,   y+1 );
        SetValue( blackBoard, x,   y+2 );
        SetValue( blackBoard, x,   y+3 );
        SetValue( blackBoard, x+1, y-2 );
        SetValue( blackBoard, x+1, y-1 );
        SetValue( blackBoard, x+1, y   );
        SetValue( blackBoard, x+1, y+1 );
        SetValue( blackBoard, x+1, y+2 );
        SetValue( blackBoard, x+2, y-1 );
        SetValue( blackBoard, x+2, y   );
        SetValue( blackBoard, x+2, y+1 );
        SetValue( blackBoard, x+3, y   );
      }else if( board[y][x] == WHITE ){
        SetValue( whiteBoard, x-3, y   );
        SetValue( whiteBoard, x-2, y-1 );
        SetValue( whiteBoard, x-2, y   );
        SetValue( whiteBoard, x-2, y+1 );
        SetValue( whiteBoard, x-1, y-2 );
        SetValue( whiteBoard, x-1, y-1 );
        SetValue( whiteBoard, x-1, y   );
        SetValue( whiteBoard, x-1, y+1 );
        SetValue( whiteBoard, x-1, y+2 );
        SetValue( whiteBoard, x,   y-3 );
        SetValue( whiteBoard, x,   y-2 );
        SetValue( whiteBoard, x,   y-1 );
        SetValue( whiteBoard, x,   y   );
        SetValue( whiteBoard, x,   y+1 );
        SetValue( whiteBoard, x,   y+2 );
        SetValue( whiteBoard, x,   y+3 );
        SetValue( whiteBoard, x+1, y-2 );
        SetValue( whiteBoard, x+1, y-1 );
        SetValue( whiteBoard, x+1, y   );
        SetValue( whiteBoard, x+1, y+1 );
        SetValue( whiteBoard, x+1, y+2 );
        SetValue( whiteBoard, x+2, y-1 );
        SetValue( whiteBoard, x+2, y   );
        SetValue( whiteBoard, x+2, y+1 );
        SetValue( whiteBoard, x+3, y   );
      }
    }
  }

  value = 0;
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      if( board[y][x] == BLACK ){
        totalBoard[y][x] = BLACK;
      }else if( board[y][x] == WHITE ){
        totalBoard[y][x] = WHITE;
      }else{
        if( blackBoard[y][x] == TRUE ){
          if( whiteBoard[y][x] == TRUE ){
            /* 黒地と白地が同じ点ならば相殺される */
            continue;
          }else{
            /* 黒地としてカウント */
            totalBoard[y][x] = BLACK;
              value++;
          }
        }else{
          if( whiteBoard[y][x] == TRUE ){
            /* 白地としてカウント */
            totalBoard[y][x] = WHITE;
            value--;
          }
        }
      }
    }
  }

  return( value );
}

/*------------------------------------------------------------------*/
/* 地の設定                                                         */
/*------------------------------------------------------------------*/
static void
SetValue( int board[][BOARD_SIZE], int x, int y )
{
  if( x > 0 && x < (BOARD_SIZE-1) && y > 0 && y < (BOARD_SIZE-1) ){
    board[y][x] = TRUE;
  }
}
このプログラムで次の盤面を認識させてみると、次のような結果になりました。
盤面                                      地
+++++++++++++++++++    +■++□+++□□□□□□□++++
+++++++++++++++○+++    ■++++□+□□+□□□□+○+++
++●○++++++○++○++●++    ++●○++□□++○□□○++●+■
++●○++++○++++++●+++    ++●○++□+○++++++●+■■
+++○+++++●+++++++++    +++○+□+++●++++++■■+
+●●++++++++++++●+++    ■●●+++□++++■■+■●■■■
++++○++++++++++++++    ++++○□□□+■■++■■■■■+
++○++++++++++++++++    ++○+□□□++■++++■■■++
+++++++++++++++++++    □++□□□++++++++■■■++
+++++++++++++++++++    +□□□□++++++++■■■■■+
+++++++++++++++●+++    ++□□++++++++■■■●■■■
+++++++++++++++++++    ++□□□++++++++■■■■■+
+++++++++++++++++++    +□□□□□++++++++■■■++
+++○+++++++++++++++    □□□○□+□++++++++■+++
+++++++++++++++++++    +□□□++■+++++++■■■++
+++○+++++++++++++++    □□□○+++■+++++■■■■■+
+++++●+++++++++●+++    +□+++●■■■+++■■■●■■■
+++++++++++++++++++    ++□++■■■+++++■■■■■+
+++++++++++++++++++    +++□■■■+++++++■■■++

地の認識の実験(その2)

実験の結果は、あなたが思う地と一致していますか? 特に左上隅がほとんど黒地と認識されていないのは変ですよね。 まだ認識の方法が稚拙なようですので、改良しましょう。

こんどは、各石から距離3以内(二間トビまたはケイマまで)を、 その石の影響範囲と考えます。 そして、各点において、その点に対して影響力の強い方 (その点に影響を及ぼす黒石の数と白石の数の多い方)の色の地と認識します。

これをプログラムで書くと、次のようになります。

/*------------------------------------------------------------------*/
/* 盤面の評価                                                       */
/*------------------------------------------------------------------*/
static int
EvalBoard( int board[][BOARD_SIZE] )
{
  int blackBoard[BOARD_SIZE][BOARD_SIZE];  /* 黒石の影響範囲 */
  int whiteBoard[BOARD_SIZE][BOARD_SIZE];  /* 白石の影響範囲 */
  int totalBoard[BOARD_SIZE][BOARD_SIZE];  /* 地 */
  int x, y, value;

  /* 初期化 */
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      blackBoard[y][x] = 0;
      whiteBoard[y][x] = 0;
      totalBoard[y][x] = SPACE;
    }
  }

  /* 距離3以内なら地とみなす */
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      if( board[y][x] == BLACK ){
        SetValue( blackBoard, x-3, y   );
        SetValue( blackBoard, x-2, y-1 );
        SetValue( blackBoard, x-2, y   );
        SetValue( blackBoard, x-2, y+1 );
        SetValue( blackBoard, x-1, y-2 );
        SetValue( blackBoard, x-1, y-1 );
        SetValue( blackBoard, x-1, y   );
        SetValue( blackBoard, x-1, y+1 );
        SetValue( blackBoard, x-1, y+2 );
        SetValue( blackBoard, x,   y-3 );
        SetValue( blackBoard, x,   y-2 );
        SetValue( blackBoard, x,   y-1 );
        SetValue( blackBoard, x,   y   );
        SetValue( blackBoard, x,   y+1 );
        SetValue( blackBoard, x,   y+2 );
        SetValue( blackBoard, x,   y+3 );
        SetValue( blackBoard, x+1, y-2 );
        SetValue( blackBoard, x+1, y-1 );
        SetValue( blackBoard, x+1, y   );
        SetValue( blackBoard, x+1, y+1 );
        SetValue( blackBoard, x+1, y+2 );
        SetValue( blackBoard, x+2, y-1 );
        SetValue( blackBoard, x+2, y   );
        SetValue( blackBoard, x+2, y+1 );
        SetValue( blackBoard, x+3, y   );
      }else if( board[y][x] == WHITE ){
        SetValue( whiteBoard, x-3, y   );
        SetValue( whiteBoard, x-2, y-1 );
        SetValue( whiteBoard, x-2, y   );
        SetValue( whiteBoard, x-2, y+1 );
        SetValue( whiteBoard, x-1, y-2 );
        SetValue( whiteBoard, x-1, y-1 );
        SetValue( whiteBoard, x-1, y   );
        SetValue( whiteBoard, x-1, y+1 );
        SetValue( whiteBoard, x-1, y+2 );
        SetValue( whiteBoard, x,   y-3 );
        SetValue( whiteBoard, x,   y-2 );
        SetValue( whiteBoard, x,   y-1 );
        SetValue( whiteBoard, x,   y   );
        SetValue( whiteBoard, x,   y+1 );
        SetValue( whiteBoard, x,   y+2 );
        SetValue( whiteBoard, x,   y+3 );
        SetValue( whiteBoard, x+1, y-2 );
        SetValue( whiteBoard, x+1, y-1 );
        SetValue( whiteBoard, x+1, y   );
        SetValue( whiteBoard, x+1, y+1 );
        SetValue( whiteBoard, x+1, y+2 );
        SetValue( whiteBoard, x+2, y-1 );
        SetValue( whiteBoard, x+2, y   );
        SetValue( whiteBoard, x+2, y+1 );
        SetValue( whiteBoard, x+3, y   );
      }
    }
  }

  value = 0;
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      if( board[y][x] == BLACK ){
        totalBoard[y][x] = BLACK;
      }else if( board[y][x] == WHITE ){
        totalBoard[y][x] = WHITE;
      }else{
        if( blackBoard[y][x] > whiteBoard[y][x] ){
          /* 黒地としてカウント */
          totalBoard[y][x] = BLACK;
          value++;
        }else if( blackBoard[y][x] < whiteBoard[y][x] ){
          /* 白地としてカウント */
          totalBoard[y][x] = WHITE;
          value--;
        }
      }
    }
  }

  return( value );
}

/*------------------------------------------------------------------*/
/* 地の設定                                                         */
/*------------------------------------------------------------------*/
static void
SetValue( int board[][BOARD_SIZE], int x, int y )
{
  if( x > 0 && x < (BOARD_SIZE-1) && y > 0 && y < (BOARD_SIZE-1) ){
    board[y][x] += 1;
  }
}
この改良をしたプログラムでの認識です。
盤面                                      地
+++++++++++++++++++    +■■□□+++□□□□□□□■+++
+++++++++++++++○+++    ■■+□□□+□□□□□□□+○■++
++●○++++++○++○++●++    ■■●○□□□□□□○□□○+■●■■
++●○++++○++++++●+++    ■■●○□□□+○□□□□+■●■■■
+++○+++++●+++++++++    ■■+○□□□++●□++■■■■■+
+●●++++++++++++●+++    ■■●□□□□++++■■+■●■■■
++++○++++++++++++++    ■++□○□□□+■■++■■■■■+
++○++++++++++++++++    +■○□□□□++■++++■■■++
+++++++++++++++++++    □++□□□++++++++■■■++
+++++++++++++++++++    +□□□□++++++++■■■■■+
+++++++++++++++●+++    ++□□++++++++■■■●■■■
+++++++++++++++++++    ++□□□++++++++■■■■■+
+++++++++++++++++++    +□□□□□++++++++■■■++
+++○+++++++++++++++    □□□○□+□++++++++■+++
+++++++++++++++++++    +□□□□□■+++++++■■■++
+++○+++++++++++++++    □□□○□++■+++++■■■■■+
+++++●+++++++++●+++    +□+□+●■■■+++■■■●■■■
+++++++++++++++++++    ++□++■■■+++++■■■■■+
+++++++++++++++++++    +++□■■■+++++++■■■++
左上隅は良くなったと思いますが、それでもまだまだ認識結果に不満が残ります。 他にも、認識結果がおかしくなる理由はあるはずです。
ここから先の改良は、みなさんが行なって下さい。 前にも書きましたが、正しい認識の方法はありません。 みなさんが自由に考えて作ってください。

(2) 候補手を生成する

認識した盤面の状況と戦略から、候補手を生成します。
例えば、盤面に自分の危ない石があれば、 その石を助けるための手(逃げる手や、眼を作る手など)を生成します。 しかし、どんな種類どんな目的の手を幾つ生成すれば良いのかは誰もわかりません。
また、石を助けようなどという目的を考えずに、 知識として持っているパターン(定石や手筋など)と一致したから、 という理由で候補手を生成することもあります。
勿論、何も考えずに盤面上のすべての点を候補手にしてしまうという方法もあります。

この部分は、囲碁プログラムの開発者の考え方の特徴が最も出やすい部分だと 言えるでしょう。 みなさんが自由に考えてください。

(3) 候補手を評価して最善手を選ぶ

最後は、生成した候補手を評価して最善手を選びます。 評価の方法はいろいろあります。 目数に換算して、この手は何目の手である、というのが普通でしょう。 しかし、常に目数に換算できるとは限りません。 石が生きる可能性が何%高くなるとか、 先手か後手かなど、いろいろな評価項目があるでしょう。 また、大模様を作ろうとしているのであれば、地を取る手よりも中央を打つ手の 評価が高くなることもあるでしょう。 さらに、手を評価するときに、何十手も先読みをして評価する場合もあれば、 一手しか先読みをしない場合もあるでしょう。
この部分も、どのように評価するのが良いかという標準的な方法はありません。
/* 現在の盤面の評価値を求める */
initValue = EvalBoard( 現在の盤面 );

/* すべての候補手を評価する */
for( すべての候補手 ){

  /* (1) その手は合法手でないならば次の候補手の評価へ */
  if( 合法手でない ){
    continue;
  }

  /* (2) その手を評価する */
  value = EvalBoard( その手を仮に打った盤面 );

  /* (3) 評価が最高のものを残す */
  if( maxValue < value ){
    maxValue = value;
    move_x = x;
    move_y = y;
  }
}

/* 現在の盤面の評価値と候補手を仮に打った盤面の評価値とを比較 */
if( maxValue > initValue ){
  /* 候補手を打った方が評価値が高いのならば( move_x, move_y )が次の一手 */
}else{
  /* 候補手を打つと現在よりも悪くなるならばパスが次の一手 */
}
ここで注意するのは、現在の盤面と候補手を打った盤面との比較です。 打つ前より悪くなるなら、パスを選択します。 ダメを詰めることにより、次の相手の手で石が取られてしまう場合、 現在の盤面の方が評価値が高くなります。 また、ダメしか残っていないならば、現在の盤面と候補手を打った盤面では 評価値が同じになるでしょう。このときもパスをします。 そして、黒と白の両者がパスをしたら終局になります。 (パスをしなかったらいつまでたっても終りません。)

候補手の評価の実験

地の認識の実験(その2)で使った関数を使って、候補手の評価をしてみます。 地の認識の実験で使った盤面で、黒番とします。 評価値は、地の数です。

実験結果は次の通りです。 数字は、(その点に候補手を打ったときの地 − 現在の地)を表します。
この候補手の評価の実験では、中央付近に打つ手が最善(24目の手)という結果が出ました。

盤面                                      評価値(地の増分)
+++++++++++++++++++    2  4  6 10 12 12 11 12 13 13 10  7  7  8  7  6  5  4  4
+++++++++++++++○+++    2  4  8 10 12 13 15 16 16 13 13 12 11  8  5  0  6  5  5
++●○++++++○++○++●++    1  4  0  0 11 14 16 18 16 14  0 16 12  0  3  4  0  6  4
++●○++++○++++++●+++    1  2  0  0 12 14 17 15  0 16 16 13 11  7  5  0  4  3  4
+++○+++++●+++++++++    2  2  4  0 11 15 15 16 17  0 15 13 10  8  6  4  0  3  3
+●●++++++++++++●+++    4  0  0  8 10 13 17 18 18 17 16 14 13 10  5  0  3  4  4
++++○++++++++++++++    5  7  8 10  0 15 18 20 19 19 17 16 14  9  5  4  3  5  6
++○++++++++++++++++    8  9  0 14 15 17 19 20 20 20 20 17 13 10  7  5  5  5  6
+++++++++++++++++++   11 13 15 16 18 19 20 21 22 21 21 18 15 10  6  5  5  5  6
+++++++++++++++++++   13 18 19 18 19 20 22 24 23 22 20 20 16 10  4  3  3  6  7
+++++++++++++++●+++   15 19 20 20 19 22 24 24 24 22 22 19 16 11  5  0  4  7  7
+++++++++++++++++++   15 17 20 19 21 22 23 24 24 24 22 20 16 12  5  5  4  8  7
+++++++++++++++++++   12 16 16 19 20 21 21 22 24 24 24 21 18 11  9  7  8  7  9
+++○+++++++++++++++   12 13 16  0 18 17 20 20 21 24 24 24 17 13  9  9  8  9  8
+++++++++++++++++++   10 15 16 16 14 15 16 18 20 21 24 21 18 11  9  7  8  7  9
+++○+++++++++++++++   12 15 18  0 13 12 13 16 18 21 20 20 16 12  5  5  4  8  7
+++++●+++++++++●+++   12 16 15 15 12  0 12 13 16 17 19 17 15 10  5  0  4  6  6
+++++++++++++++++++   12 14 15 12 11 11  8 10 12 16 16 16 12  8  3  3  2  4  4
+++++++++++++++++++    9 11 11 11  9  7  6  5  9 12 15 12  9  5  4  3  3  2  3

ここまでのまとめ

囲碁プログラムが何をどのように考えているのか、 その説明と簡単な思考ルーチンを作りました。

基本編と同じように、サンプルプログラムを置きます。 このプログラムも書き方に工夫すべきところがたくさんありますが、 プログラミングのテクニックについて解説するのが目的ではないので、 そこはみなさんが改良してください。

それから、基本編の対局プログラムと、この思考ルーチンで、 人間とコンピュータが対局できるプログラムを作ることができます。 勿論そのままではダメで、対局プログラムから思考ルーチンを呼ぶようにしたり、 人間とコンピュータのどちらが黒番かを設定するためのインターフェースなど、 手を加えないといけないところがいくつかあります。 そこはみなさんが作成してください。

このプログラムは、"figure"という名前のファイルを読んで、 次の一手を返すプログラムです。"figure"というファイルには、 次の一手を求めたい盤面を描いておきます。

"figure"の例
+++++++++++++++++++
+++++++++++++++O+++
++*O++++++O++O++*++
++*O++++O++++++*+++
+++O+++++*+++++++++
+**++++++++++++*+++
++++O++++++++++++++
++O++++++++++++++++
+++++++++++++++++++
+++++++++++++++++++
+++++++++++++++*+++
+++++++++++++++++++
+++++++++++++++++++
+++O+++++++++++++++
+++++++++++++++++++
+++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 ThinkMove( int board[][BOARD_SIZE], int color, int *x, int *y );

/* 盤面の評価 */
static int  EvalBoard( int board[][BOARD_SIZE] );

/* 地の設定 */
static void SetValue( int board[][BOARD_SIZE], int x, int y );

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

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

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

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

/* 碁盤を複写 */
static void CopyBoard( int source[][BOARD_SIZE], int copied[][BOARD_SIZE] );

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

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

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

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

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

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

/*------------------------------------------------------------------*/
/* メイン関数                                                       */
/*------------------------------------------------------------------*/
void
main( void )
{
  int board[BOARD_SIZE][BOARD_SIZE];  /* 碁盤 */
  int xBlack, yBlack;                 /* 黒の着手位置 */

  printf( "Sample Program Start\n" );

  /* 盤面図の入力 */
  InputFigure( board );

  /* 黒の次の一手を求める */
  ThinkMove( board, BLACK, &xBlack, &yBlack );

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

  /* 結果の表示 */
  printf( "Computer -> (%2d,%2d)\n", xBlack, yBlack);

  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
ThinkMove( int board[][BOARD_SIZE], int color, int *xBlack, int *yBlack )
{
  int tmpBoard[BOARD_SIZE][BOARD_SIZE];  /* 複写用碁盤 */
  int evalBoard[BOARD_SIZE][BOARD_SIZE]; /* 評価値格納用碁盤 */
  int resultX, resultY;
  int x, y;
  int eval, init_eval, max_eval;

  /* 評価値格納用碁盤の初期化 */
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      evalBoard[y][x] = 0;
    }
  }

  /* 盤面の評価 */
  init_eval = EvalBoard( board );

  /* 候補手を仮りに盤上に打って評価 */
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      /* 着手禁止点なら次の点へ */
      if( CheckLegal( board, color, x, y ) == FALSE ){
        continue;
      }
      /* 碁盤を複写 */
      CopyBoard( board, tmpBoard );
      /* 候補手を盤上に打つ */
      SetStone( tmpBoard, color, x, y );
      /* 着手後の盤面の評価 */
      eval = EvalBoard( tmpBoard );
      /* 着手前よりどのくらい良くなったかを評価値格納用碁盤に記録 */
      evalBoard[y][x] = eval - init_eval;
    }
  }

  /* 評価値が最高のものを選ぶ */
  max_eval = 0;
  resultX = resultY = 0;
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      if( max_eval < evalBoard[y][x] ){
        max_eval = evalBoard[y][x];
        resultX = x;
        resultY = y;
      }
    }
  }

  *xBlack = resultX;
  *yBlack = resultY;
}

/*------------------------------------------------------------------*/
/* 盤面の評価                                                       */
/*------------------------------------------------------------------*/
static int
EvalBoard( int board[][BOARD_SIZE] )
{
  int blackBoard[BOARD_SIZE][BOARD_SIZE];  /* 黒石の影響範囲 */
  int whiteBoard[BOARD_SIZE][BOARD_SIZE];  /* 白石の影響範囲 */
  int totalBoard[BOARD_SIZE][BOARD_SIZE];  /* 地 */
  int x, y, value;

  /* 初期化 */
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      blackBoard[y][x] = 0;
      whiteBoard[y][x] = 0;
      totalBoard[y][x] = SPACE;
    }
  }

  /* 距離3以内なら地とみなす */
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      if( board[y][x] == BLACK ){
        SetValue( blackBoard, x-3, y   );
        SetValue( blackBoard, x-2, y-1 );
        SetValue( blackBoard, x-2, y   );
        SetValue( blackBoard, x-2, y+1 );
        SetValue( blackBoard, x-1, y-2 );
        SetValue( blackBoard, x-1, y-1 );
        SetValue( blackBoard, x-1, y   );
        SetValue( blackBoard, x-1, y+1 );
        SetValue( blackBoard, x-1, y+2 );
        SetValue( blackBoard, x,   y-3 );
        SetValue( blackBoard, x,   y-2 );
        SetValue( blackBoard, x,   y-1 );
        SetValue( blackBoard, x,   y   );
        SetValue( blackBoard, x,   y+1 );
        SetValue( blackBoard, x,   y+2 );
        SetValue( blackBoard, x,   y+3 );
        SetValue( blackBoard, x+1, y-2 );
        SetValue( blackBoard, x+1, y-1 );
        SetValue( blackBoard, x+1, y   );
        SetValue( blackBoard, x+1, y+1 );
        SetValue( blackBoard, x+1, y+2 );
        SetValue( blackBoard, x+2, y-1 );
        SetValue( blackBoard, x+2, y   );
        SetValue( blackBoard, x+2, y+1 );
        SetValue( blackBoard, x+3, y   );
      }else if( board[y][x] == WHITE ){
        SetValue( whiteBoard, x-3, y   );
        SetValue( whiteBoard, x-2, y-1 );
        SetValue( whiteBoard, x-2, y   );
        SetValue( whiteBoard, x-2, y+1 );
        SetValue( whiteBoard, x-1, y-2 );
        SetValue( whiteBoard, x-1, y-1 );
        SetValue( whiteBoard, x-1, y   );
        SetValue( whiteBoard, x-1, y+1 );
        SetValue( whiteBoard, x-1, y+2 );
        SetValue( whiteBoard, x,   y-3 );
        SetValue( whiteBoard, x,   y-2 );
        SetValue( whiteBoard, x,   y-1 );
        SetValue( whiteBoard, x,   y   );
        SetValue( whiteBoard, x,   y+1 );
        SetValue( whiteBoard, x,   y+2 );
        SetValue( whiteBoard, x,   y+3 );
        SetValue( whiteBoard, x+1, y-2 );
        SetValue( whiteBoard, x+1, y-1 );
        SetValue( whiteBoard, x+1, y   );
        SetValue( whiteBoard, x+1, y+1 );
        SetValue( whiteBoard, x+1, y+2 );
        SetValue( whiteBoard, x+2, y-1 );
        SetValue( whiteBoard, x+2, y   );
        SetValue( whiteBoard, x+2, y+1 );
        SetValue( whiteBoard, x+3, y   );
      }
    }
  }

  value = 0;
  for( y=1; y < (BOARD_SIZE-1); y++ ){
    for( x=1; x < (BOARD_SIZE-1); x++ ){
      if( board[y][x] == BLACK ){
        totalBoard[y][x] = BLACK;
      }else if( board[y][x] == WHITE ){
        totalBoard[y][x] = WHITE;
      }else{
        if( blackBoard[y][x] > whiteBoard[y][x] ){
          /* 黒地としてカウント */
          totalBoard[y][x] = BLACK;
          value++;
        }else if( blackBoard[y][x] < whiteBoard[y][x] ){
          /* 白地としてカウント */
          totalBoard[y][x] = WHITE;
          value--;
        }
      }
    }
  }

  return( value );
}

/*------------------------------------------------------------------*/
/* 地の設定                                                         */
/*------------------------------------------------------------------*/
static void
SetValue( int board[][BOARD_SIZE], int x, int y )
{
  if( x > 0 && x < (BOARD_SIZE-1) && y > 0 && y < (BOARD_SIZE-1) ){
    board[y][x] += 1;
  }
}

/*------------------------------------------------------------------*/
/* 合法手かどうか調べる                                             */
/* このサンプルプログラムでは劫のチェックは省略                     */
/*------------------------------------------------------------------*/
static int
CheckLegal( int board[][BOARD_SIZE], int color, int x, int y )
{
  /* 空点じゃないと置けません */
  if( board[y][x] != SPACE ){
    return( FALSE );
  }

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

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

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

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

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

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

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

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

/*------------------------------------------------------------------*/
/* 座標(x,y)にあるcolor石が相手に囲まれているか調べる               */
/*------------------------------------------------------------------*/
static int     /* 空点があればFALSEを返し、空点がなければTRUEを返す */
DoCheckRemoveStone( int board[][BOARD_SIZE], 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( board, color, x-1, y );
      if( rtn == FALSE ){
        return( FALSE );
      }
    }
    /* その石の上(x,y-1)を調べる */
    if( y > 1 ){
      rtn = DoCheckRemoveStone( board, color, x, y-1 );
      if( rtn == FALSE ){
        return( FALSE );
      }
    }
    /* その石の右(x+1,y)を調べる */
    if( x < (BOARD_SIZE-2) ){
      rtn = DoCheckRemoveStone( board, color, x+1, y );
      if( rtn == FALSE ){
        return( FALSE );
      }
    }
    /* その石の下(x,y+1)を調べる */
    if( y < (BOARD_SIZE-2) ){
      rtn = DoCheckRemoveStone( board, color, x, y+1 );
      if( rtn == FALSE ){
        return( FALSE );
      }
    }
  }

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

/*------------------------------------------------------------------*/
/* 碁盤を複写                                                       */
/*------------------------------------------------------------------*/
static void
CopyBoard( int source[][BOARD_SIZE], int copied[][BOARD_SIZE] )
{
  int x, y;

  for( y=0; y < BOARD_SIZE; y++ ){
    for( x=0; x < BOARD_SIZE; x++ ){
      copied[y][x] = source[y][x];
    }
  }
}

/*------------------------------------------------------------------*/
/* 碁盤に石を置く                                                   */
/* このサンプルプログラムでは取った石数は使わない                   */
/*------------------------------------------------------------------*/
static void
SetStone( int board[][BOARD_SIZE], int color, int x, int y )
{
  int prisonerN;    /* 取り除かれた石の数(上) */
  int prisonerE;    /* 取り除かれた石の数(右) */
  int prisonerS;    /* 取り除かれた石の数(下) */
  int prisonerW;    /* 取り除かれた石の数(左) */
  int prisonerAll;  /* 取り除かれた石の総数 */

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

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

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

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

/*------------------------------------------------------------------*/
/* 座標(x,y)の石が死んでいれば碁盤から取り除く                      */
/*------------------------------------------------------------------*/
static int    /* 碁盤から取り除かれた石数 */
RemoveStone( int board[][BOARD_SIZE], 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, board[y][x], x, y ) == TRUE ){
    prisoner = DoRemoveStone( board, board[y][x], x, y, 0 );
    return( prisoner );
  }

  return( 0 );
}

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

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

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

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

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

/*---------------------- < end of program > ------------------------*/
勝也のページへ戻る