BonanzaソースコードのUSI対応化(2011/06/05版)

BonanzaのソースコードをUSIに対応する作業について説明します。
Bonanzaのバージョンは6.0とします。

現状では、将棋所で動作確認した結果は以下の通りです。

○実装していない機能

  1. 検討
  2. 詰将棋解答

○既知の不具合

  1. 持ち時間(TimeA)と秒読み(TimeB)の両方を指定したとき秒読みが無効になる(秒読みのみはOK)
  2. 予測読み(Ponder)に不具合があるので未実装

○仕様

  1. [エンジン共通設定]と[時間設定]は使用しない。([エンジン設定]で先後別に行うため)
  2. "setoption name USI_Ponder"行と"setoption name USI_Hash"行は使用しない。


将棋所の[エンジン設定]ウィンドウ


ソースコード変更箇所

1. Makefile
FLAGマクロの"/DCSASHOGI"を"/DUSI"に変えます。

2. main.c
39行目以下を以下のように変更します。
Bonanzaは引数でプロトコルを指定するようになっていますが、 将棋所は引数に対応していないためにプロトコルを固定します。
#if defined(USI)
/*
  if ( argc == 2 && ! strcmp( argv[1], "usi" ) ) { usi_mode = usi_on; }
  else                                           { usi_mode = usi_off; }
*/
  usi_mode = usi_on;
#endif

3. utility.c
521行目の次に以下の6行を挿入します。
これは投了するために必要です。
#if defined(USI)
      if ( usi_mode != usi_off )
        {
          USIOut( "bestmove resign\n" );
        }
#endif

4. searchr.c
375行目を以下のように変更します("&& ply <= 4"を削除します)。
これを行うと読み筋を末端まで出力するようになりますが、対局には必須ではありません。
	  if ( usi_mode != usi_off )

5. proce.c
大部分の変更はこのファイルで必要になります。 ソースコード(右クリック+保存)
処理の流れは以下のようになります。

  1. 最初にGUIから"usi"行を受信したときに、思考条件に既定値を代入し、 次に設定ファイル("usi.ini")があればそれを読み込み思考条件を変数に代入します。 その後、GUIに"option ..."行のすべてを送信します。
  2. GUIで[エンジン設定]を行うと"setoption ..."行が送信されるので、 その内容を解釈し思考条件を変数に代入します。 "setoption"行は終了マークがないので冗長ですが毎回設定ファイルを更新します。
  3. 対局開始時にGUIから"usinewgame"行が送信されるので、 設定ファイルを読み込み、その内容を解釈しBonanzaに送信します。
  4. 以上で対局が開始されます。

(1) グローバル変数(新規)
9行目以下に以下のグローバル変数を宣言します。(正しくはshogi.hで行う?)
これらは思考条件です。名前("name")と値("value")のペアから成ります。
#if defined(USI)
/* USI options */
const char usi_ini[] = "usi.ini";
struct {
	char time_control[ BUFSIZ ];
	char time_a[ BUFSIZ ];
	char time_b[ BUFSIZ ];
	char nodes[ BUFSIZ ];
	char depth[ BUFSIZ ];
	char use_book[ BUFSIZ ];
	char narrow_book[ BUFSIZ ];
	char strict_time[ BUFSIZ ];
	char resign[ BUFSIZ ];
	char memory[ BUFSIZ ];
	char ponder[ BUFSIZ ];
	char threads[ BUFSIZ ];
} usi_name;
struct {
	char time_control[ BUFSIZ ];
	int time_a;
	int time_b;
	int nodes;
	int depth;
	char use_book[ BUFSIZ ];
	char narrow_book[ BUFSIZ ];
	char strict_time[ BUFSIZ ];
	int resign;
	int memory;
	char ponder[ BUFSIZ ];
	int threads;
} usi_value;
#endif

(2) init_usi_option関数(新規)
思考条件を既定値で初期化します。
GUIから"usi"コマンドを受信したときに呼ばれます。
#if defined(USI)
/* initialize usi options */
static void CONV
init_usi_option( void )
{
	strcpy( usi_name.time_control, "TimeControl" );
	strcpy( usi_name.time_a,       "TimeA[min]" );
	strcpy( usi_name.time_b,       "TimeB[sec]" );
	strcpy( usi_name.nodes,        "Nodes" );
	strcpy( usi_name.depth,        "Depth" );
	strcpy( usi_name.use_book,     "UseBook" );
	strcpy( usi_name.narrow_book,  "NarrowBook" );
	strcpy( usi_name.strict_time,  "StrictTime" );
	strcpy( usi_name.resign,       "Resign" );
	strcpy( usi_name.memory,       "Memory" );
	strcpy( usi_name.ponder,       "Ponder" );
	strcpy( usi_name.threads,      "Threads" );

	strcpy( usi_value.time_control, "Time" );
	usi_value.time_a      = 0;
	usi_value.time_b      = 3;
	usi_value.nodes       = 100000;
	usi_value.depth       = 5;
	strcpy( usi_value.use_book,    "true" );
	strcpy( usi_value.narrow_book, "false" );
	strcpy( usi_value.strict_time, "true" );
	usi_value.resign      = 3000;
	usi_value.memory      = 12;
	strcpy( usi_value.ponder,      "false" );
	usi_value.threads     = 1;
}

(3) set_usi_option関数(新規)
思考条件を設定します。
cmd=1のときはBonanzaに思考条件を送信します。
最初に設定ファイルを読み込んだとき、 GUIから"setoption"行を受信したとき、 対局開始時の3箇所で使用されます。
/* set usi_options, cmd : send command to engine ? */
static void CONV
set_usi_option( tree_t * restrict ptree, const char *name, const char *value, int cmd )
{
  char str[ SIZE_CMDLINE ];
  char *last, *token, *ptr;

  if ( ! strcmp( name, usi_name.time_control ) )
    {
      strcpy( usi_value.time_control, value );
    }
  else if ( ! strcmp( name, usi_name.time_a ) && ! strcmp( usi_value.time_control, "Time") )
    {
      usi_value.time_a = (int)strtol( value, &ptr, 0 );
    }
  else if ( ! strcmp( name, usi_name.time_b ) && ! strcmp( usi_value.time_control, "Time") )
    {
      usi_value.time_b = (int)strtol( value, &ptr, 0 );
      if ( cmd )
        {
          sprintf( str, "limit time %d %d", usi_value.time_a, usi_value.time_b );
          token = strtok_r( str, str_delimiters, &last );
          cmd_limit( &last );
        }
    }
  else if ( ! strcmp( name, usi_name.nodes ) && ! strcmp( usi_value.time_control, "Nodes") )
    {
      usi_value.nodes = (int)strtol( value, &ptr, 0 );
      if ( cmd )
        {
          sprintf( str, "limit nodes %d", usi_value.nodes );
          token = strtok_r( str, str_delimiters, &last );
          cmd_limit( &last );
        }
    }
  else if ( ! strcmp( name, usi_name.depth ) && ! strcmp( usi_value.time_control, "Depth") )
    {
      usi_value.depth = (int)strtol( value, &ptr, 0 );
      if ( cmd )
        {
          sprintf( str, "limit depth %d", usi_value.depth );
          token = strtok_r( str, str_delimiters, &last );
          cmd_limit( &last );
        }
    }
  else if ( ! strcmp( name, usi_name.use_book ) )
    {
      strcpy( usi_value.use_book, value );
      if ( cmd )
        {
          sprintf( str, "book %s", ( ! strcmp( usi_value.use_book, "true" ) ? str_on : str_off ) );
          token = strtok_r( str, str_delimiters, &last );
          CmdBook( ptree, &last );
        }
    }
  else if ( ! strcmp( name, usi_name.narrow_book ) )
    {
      strcpy( usi_value.narrow_book, value );
      if ( cmd )
        {
          sprintf( str, "book %s", ( ! strcmp( usi_value.narrow_book, "true" ) ? "narrow" : "wide" ) );
          token = strtok_r( str, str_delimiters, &last );
          CmdBook( ptree, &last );
        }
    }
  else if ( ! strcmp( name, usi_name.strict_time ) )
    {
      strcpy( usi_value.strict_time, value );
      if ( cmd )
        {
          sprintf( str, "limit time %s", ( !strcmp( usi_value.strict_time, "true" ) ? "strict" : "extendable" ) );
          token = strtok_r( str, str_delimiters, &last );
          cmd_limit( &last );
        }
    }
  else if ( ! strcmp( name, usi_name.resign ) )
    {
      usi_value.resign = (int)strtol( value, &ptr, 0 );
      if ( cmd )
        {
          sprintf( str, "resign %d", usi_value.resign );
          token = strtok_r( str, str_delimiters, &last );
          cmd_resign( ptree, &last );
        }
    }
  else if ( ! strcmp( name, usi_name.memory ) )
    {
      usi_value.memory = (int)strtol( value, &ptr, 0 );
      if ( cmd )
        {
          int hash = (int)(log(usi_value.memory * 1e6 / 48) / log(2)) + 1;
          sprintf( str, "hash %d", hash );
          token = strtok_r( str, str_delimiters, &last );
          cmd_hash ( &last );
        }
    }
  else if ( ! strcmp( name, usi_name.ponder ) )
    {
/* not implemented
      strcpy( usi_value.ponder, value );
      if ( cmd )
        {
          sprintf( str, "ponder %s", ( ! strcmp( usi_value.ponder, "true" ) ? str_on : str_off ) );
          token = strtok_r( str, str_delimiters, &last );
          cmd_ponder( &last );
        }
*/
    }
  else if ( ! strcmp( name, usi_name.threads ) )
    {
      usi_value.threads = (int)strtol( value, &ptr, 0 );
      if ( cmd )
        {
#if defined(TLP)
          sprintf( str, "tlp num %d", usi_value.threads );
          token = strtok_r( str, str_delimiters, &last );
          cmd_thread( &last );
#endif
        }
    }
}

(4) read_usi_ini関数(新規)
思考条件をファイル"usi.ini"から入力します。
GUIから"usi"コマンドを受信したときと、対局開始時に呼ばれます。
/* read usi.ini */
static void CONV
read_usi_ini( tree_t * restrict ptree, int cmd )
{
	FILE *fp;
	char name[ BUFSIZ ], value[ BUFSIZ ];

	if ( (fp = fopen(usi_ini, "r")) == NULL ) return;

	while ( ( fscanf( fp, "%s %s", name, value ) ) != EOF )
	  {
	    set_usi_option( ptree, name, value, cmd );
	  }

	fclose( fp );
}

(5) write_usi_ini関数(新規)
思考条件をファイル"usi.ini"に出力します。
GUIから"setoption"行を受信したときに呼ばれます。
/* write usi.ini */
static void CONV
write_usi_ini( void )
{
	FILE *fp;
	const char fmt_d[] = "%s %d\n";
	const char fmt_s[] = "%s %s\n";

	if ( (fp = fopen(usi_ini, "w")) == NULL ) return;

	fprintf( fp, fmt_s, usi_name.time_control, usi_value.time_control );
	fprintf( fp, fmt_d, usi_name.time_a,       usi_value.time_a );
	fprintf( fp, fmt_d, usi_name.time_b,       usi_value.time_b );
	fprintf( fp, fmt_d, usi_name.nodes,        usi_value.nodes );
	fprintf( fp, fmt_d, usi_name.depth,        usi_value.depth );
	fprintf( fp, fmt_s, usi_name.use_book,     usi_value.use_book );
	fprintf( fp, fmt_s, usi_name.narrow_book,  usi_value.narrow_book );
	fprintf( fp, fmt_s, usi_name.strict_time,  usi_value.strict_time );
	fprintf( fp, fmt_d, usi_name.resign,       usi_value.resign );
	fprintf( fp, fmt_d, usi_name.memory,       usi_value.memory );
	fprintf( fp, fmt_s, usi_name.ponder,       usi_value.ponder );
	fprintf( fp, fmt_d, usi_name.threads,      usi_value.threads );

	fclose( fp );
}

(6) usi_option関数(新規)
GUIから"setoption"行を受信したときに呼ばれます。
/* "setoption name XXX value YYY" */
static int CONV
usi_option( tree_t * restrict ptree, char **lasts )
{
  const char *str1, *str2, *str3, *str4;

  str1 = strtok_r( NULL, str_delimiters, lasts );
  if ( str1 == NULL || strcmp( str1, "name" ) ) { str_error = str_bad_cmdline; return -1; }

  str2 = strtok_r( NULL, str_delimiters, lasts );
  if ( str2 == NULL ) { str_error = str_bad_cmdline; return -1; }

  str3 = strtok_r( NULL, str_delimiters, lasts );
  if ( str3 == NULL || strcmp( str3, "value" ) ) { str_error = str_bad_cmdline; return -1; }

  str4 = strtok_r( NULL, str_delimiters, lasts );
  if ( str4 == NULL ) { str_error = str_bad_cmdline; return -1; }

  set_usi_option( ptree, str2, str4, 0 );

  write_usi_ini();

  return 1;
}

(7) proce_usi関数(既存)
GUIから"usi"行を受信したとき、下記の処理を行います。
      /* options */
      init_usi_option();
      read_usi_ini( ptree, 0 );
      USIOut( "option name %s type combo default %s var Time var Nodes var Depth\n", usi_name.time_control, usi_value.time_control );
      USIOut( "option name %s type spin default %d min 0 max 600\n", usi_name.time_a, usi_value.time_a );
      USIOut( "option name %s type spin default %d min 0 max 600\n", usi_name.time_b, usi_value.time_b );
      USIOut( "option name %s type spin default %d min 1000 max 1000000\n", usi_name.nodes, usi_value.nodes );
      USIOut( "option name %s type spin default %d min 1 max 20\n", usi_name.depth, usi_value.depth );
      USIOut( "option name %s type check default %s\n", usi_name.use_book, usi_value.use_book );
      USIOut( "option name %s type check default %s\n", usi_name.narrow_book, usi_value.narrow_book );
      USIOut( "option name %s type check default %s\n", usi_name.strict_time, usi_value.strict_time );
      USIOut( "option name %s type combo default %d var 500 var 1000 var 2000 var 3000 var 5000 var 32596\n", usi_name.resign, usi_value.resign );
      USIOut( "option name %s type combo default %d var 12 var 25 var 50 var 100 var 200 var 400 var 800\n", usi_name.memory, usi_value.memory );
      USIOut( "option name %s type check default %s\n", usi_name.ponder, usi_value.ponder );
      USIOut( "option name %s type spin default %d min 1 max 32\n", usi_name.threads, usi_value.threads );
GUIから"setoption"行を受信したとき、下記の処理を行います。
  /* set options */
  if ( ! strcmp( token, "setoption" ) )
    {
      return usi_option( ptree, &lasts );
    }
GUIから"usinewgame"行を受信したとき、下記の処理を行います。
  /* new game */
  if ( ! strcmp( token, "usinewgame" ) )
    {
      read_usi_ini( ptree, 1 );
      return 1;
    }
GUIから"gameover"行を受信したときは何もする必要はありません。
  if ( ! strcmp( token, "gameover" ) )
    {
      /* no operation */
      return 1;
    }

(8) usi_go関数(既存)
GUIから"go btime xxx wtime xxx byoyomi xxx"行を受信したら思考を開始します。 ただし、ここでのパラメータは使用しません。 また、原コードでは"go byoyomi xxx"行で思考を開始するようになっていますが、 将棋所はこのような行を送信しません。
  else if ( ! strcmp( token, "btime" ) )
    {
    }

トップページへ