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

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

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

○実装していない機能

  1. 検討 (Bonanzaがsfenに対応していないため)
  2. 詰将棋解答 (同上)

○既知の不具合

  1. 持ち時間(TimeA)を設定したときは秒読み(TimeB)を0とする。
  2. 秒読み(TimeB)を設定したときは持ち時間(TimeA)を0とする。
  3. 予測読み(Ponder)に不具合があるので未実装

○仕様

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


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


ソースコード変更箇所

1. Makefile.vs
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) グローバル変数(新規)
9行目以下に以下のグローバル変数を宣言します。(正しくはshogi.hで行う?)
これらは思考条件です。名前("name")と値("value")のペアから成ります。
#if defined(USI)
/* USI options */
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[MB]" );
	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関数(新規)
Bonanzaの思考条件を設定します。
"usi_option"関数から呼ばれます。
/* set usi_options */
static void CONV
set_usi_option( tree_t * restrict ptree, const char *name, const char *value )
{
  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 );
      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 );
      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 );
      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 );
      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 );
      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 );
      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 );
      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 ) )
    {
      int hash;
      usi_value.memory = (int)strtol( value, &ptr, 0 );
      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 );
      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 defined(TLP)
      sprintf( str, "tlp num %d", usi_value.threads );
      token = strtok_r( str, str_delimiters, &last );
      cmd_thread( &last );
#endif
    }
}

(4) 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 );

  return 1;
}

(5) proce_usi関数(既存)
GUIから"usi"行を受信したとき、下記の処理を行います。
      /* options */
      init_usi_option();
      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 36000\n",                                               usi_name.time_b,       usi_value.time_b );
      USIOut( "option name %s type spin default %d min 1000 max 100000000\n",                                        usi_name.nodes,        usi_value.nodes );
      USIOut( "option name %s type spin default %d min 1 max 30\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 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 24 var 48 var 96 var 192 var 384 var 768 var 1536\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 256\n",                                                 usi_name.threads,      usi_value.threads );

GUIから"setoption"行を受信したとき、下記の処理を行います。
  /* set options */
  if ( ! strcmp( token, "setoption" ) )
    {
      return usi_option( ptree, &lasts );
    }

GUIから"usinewgame"行を受信したときは何もする必要はありません。
  if ( ! strcmp( token, "usinewgame" ) )
    {
      return 1;
    }

GUIから"gameover"行を受信したときは何もする必要はありません。
  if ( ! strcmp( token, "gameover" ) )
    {
      return 1;
    }

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

トップページへ