今回は指し手をUCIの指し手文字列に変換する部分を見ていきます。将棋ではUCIではなくUSIプロトコルとなっています。以下、UCIと書いてある部分はUSIと読み換えて理解してください。
UCIプロトコルでは、思考を開始する局面は、普通、startpos(平手の初期局面)からの指し手を示す文字列が送られてきます。と言うことは、常識的には、UCI表記での指し手文字列を思考エンジンが内部で使っている指し手構造体に変換する必要があります。ところが、Stockfishにはこの変換をダイレクトに行なう関数は存在しません。さて、どうやっているのでしょうか。
実は、現在の局面の合法手すべてを生成して、それをmove_to_uci( )でそれをUCIの指し手文字列に変換し、それが与えられた文字列と一致したら、そのmove_to_uci( )で変換する前の指し手構造体を返すという実装になっています。少し遅いのが気になりますが、思考開始のときに送られてくるだけなのでこの部分が多少遅くとも誤差の範疇です。
これは、大変エレガントな実装だと私は思います。
ところが、将棋倶楽部24にオートパイロットでやねうら王を参戦させていたとき(2014年5月のことで、将棋倶楽部24公認のイベントでした。念のため。)、将棋倶楽部24ではサーバー側から王手放置の局面が送られてくるんです。将棋倶楽部24にPCで対局していると王手放置で逆王手とか出来るので知っている人も多いかと思います。こんな指し手は非合法手なので普通サーバー側判定で弾くべきですが、将棋倶楽部24ではそうはなっていません。
そこで、将棋倶楽部24に参加する思考エンジンは非合法手でも指し手を受け取れるようにする必要があり、そのためにはUSIの指し手文字列をダイレクトに思考エンジンが使っている指し手構造体に変換する処理が必要になります。
「大変エレガント」だったはずの実装が、将棋倶楽部24のせいで書き直しを迫られたという話でした。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2013 Marco Costalba, Joona Kiiski, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see */ #ifndef NOTATION_H_INCLUDED #define NOTATION_H_INCLUDED #include #include "types.h" class Position; // -- UCIプロトコルで与えられた指し手などをこのプログラムで用いている指し手形式に変換するためのヘルパー群。 // score_to_uci()は評価値をUCIプロトコル仕様で使われるのに適した文字列に変換する。 // cp // mate // yをマイナスにした値を用いる。 std::string score_to_uci(Value v, Value alpha = -VALUE_INFINITE, Value beta = VALUE_INFINITE); // move_from_uci()は、局面と(UCIプロトコルの)単純な座標記法で指し手を表現する文字列を受け取り、 // もし可能なら等価で合法な指し手を返す。(合法でないときはMOVE_NONE) // ToDo : ※ 第二パラメーターはconstつけ忘れか? Move move_from_uci(const Position& pos, std::string& str); // move_to_uci()は指し手を座標記法 (g1f3, a7a8q, など)の文字列に変換する。 // 特別な場合は、キャスリングの指し手であり、我々は普通のチェスモードであればe1g1記法 // で出力し、chess960モードであればe1h1記法で行う。内部的には、キャッスルの手は // つねに"王が飛車を取る"とコード化される。 const std::string move_to_uci(Move m, bool chess960); // move_to_san()は局面と合法な指し手を入力として受け取り、 // 短いアラビア数字の表現で返す。 // ※ pretty_pvで盤面を表示したときに使う。 const std::string move_to_san(Position& pos, Move m); // PV(最善応手列)を盤面などをASCII出力して人間にわかりやすく出力する。 // ※ ログファイルへの書き出し時に用いる。 std::string pretty_pv(Position& pos, int depth, Value score, int64_t msecs, Move pv[]); #endif // #ifndef NOTATION_H_INCLUDED |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2013 Marco Costalba, Joona Kiiski, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see */ #include #include #include #include #include "movegen.h" #include "notation.h" #include "position.h" using namespace std; // 駒に対応する文字。UCIで定義されている。 static const char* PieceToChar[COLOR_NB] = { " PNBRQK", " pnbrqk" }; /// score_to_uci() converts a value to a string suitable for use with the UCI /// protocol specifications: /// /// cp /// mate /// use negative values for y. // score_to_uci()は評価値をUCIプロトコル仕様で使われるのに適した文字列に変換する。 // // cp // mate // yをマイナスにした値を用いる。 string score_to_uci(Value v, Value alpha, Value beta) { stringstream s; if (abs(v) < VALUE_MATE_IN_MAX_PLY) s << "cp " << v * 100 / int(PawnValueMg); else s << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; // チェスだとfulldepthなので2で割って詰みまでの手数を出力しているが、将棋だとこの処理は不要。 s << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); // ※ fail lowしているスコアを表示すると将棋所での表示がおかしくなるので将棋用に修正が必要。 // 修正例) s << (v >= beta ? "↑" : v <= alpha ? "↓" : ""); return s.str(); } /// move_to_uci() converts a move to a string in coordinate notation /// (g1f3, a7a8q, etc.). The only special case is castling moves, where we print /// in the e1g1 notation in normal chess mode, and in e1h1 notation in chess960 /// mode. Internally castle moves are always coded as "king captures rook". // move_to_uci()は指し手を座標記法 (g1f3, a7a8q, など)の文字列に変換する。 // 特別な場合は、キャスリングの指し手であり、我々は普通のチェスモードであればe1g1記法 // で出力し、chess960モードであればe1h1記法で行う。内部的には、キャッスルの手は // つねに"王が飛車を取る"とコード化される。 const string move_to_uci(Move m, bool chess960) { // 与えられた指し手mの移動元 Square from = from_sq(m); // 与えられた指し手mの移動先 Square to = to_sq(m); // 指し手はMOVE_NONEやMOVE_NULLでありうる(NULL MOVEなどで?) // MOVE_NONEは、指し手がない状態。チェスだとスティールメイト扱い。将棋だと詰みなのだが。 if (m == MOVE_NONE) return "(none)"; if (m == MOVE_NULL) return "0000"; // キャスリングの指し手用の表記に変形 if (type_of(m) == CASTLE && !chess960) to = (to > from ? FILE_G : FILE_C) | rank_of(from); // fromからtoへの文字を連結 string move = square_to_string(from) + square_to_string(to); // 成りであるなら成ったあとの文字を付与 if (type_of(m) == PROMOTION) move += PieceToChar[BLACK][promotion_type(m)]; // Lower case return move; } /// move_from_uci() takes a position and a string representing a move in /// simple coordinate notation and returns an equivalent legal Move if any. // move_from_uci()は、局面と(UCIプロトコルの)単純な座標記法で指し手を表現する文字列を受け取り、 // もし可能なら等価で合法な指し手を返す。(合法でないときはMOVE_NONE) Move move_from_uci(const Position& pos, string& str) { if (str.length() == 5) // Junior could send promotion piece in uppercase str[4] = char(tolower(str[4])); // 全合法手のなかからuci文字列に変換したときにstrと一致する指し手を探す for (const ExtMove& ms : MoveList if (str == move_to_uci(ms.move, pos.is_chess960())) return ms.move; return MOVE_NONE; } /// move_to_san() takes a position and a legal Move as input and returns its /// short algebraic notation representation. // move_to_san()は局面と合法な指し手を入力として受け取り、 // 短いアラビア数字の表現で返す。 // ※ sanはコンピューター将棋で言うCSAフォーマットみたいなものか? // GUIで対局させるときにはUCIプロトコルが用いられており、sanは関係ないようだ。 const string move_to_san(Position& pos, Move m) { if (m == MOVE_NONE) return "(none)"; if (m == MOVE_NULL) return "(null)"; // 全合法手のなかにこの指し手が含まれなければおかしい。 assert(MoveList Bitboard others, b; string san; Color us = pos.side_to_move(); Square from = from_sq(m); Square to = to_sq(m); Piece pc = pos.piece_on(from); PieceType pt = type_of(pc); if (type_of(m) == CASTLE) san = to > from ? "O-O" : "O-O-O"; else { if (pt != PAWN) { san = PieceToChar[WHITE][pt]; // Upper case // Disambiguation if we have more then one piece of type 'pt' that can // reach 'to' with a legal move. others = b = (pos.attacks_from(pc, to) & pos.pieces(us, pt)) ^ from; while (b) { Move move = make_move(pop_lsb(&b), to); if (!pos.legal(move, pos.pinned_pieces(pos.side_to_move()))) others ^= from_sq(move); } if (others) { if (!(others & file_bb(from))) san += file_to_char(file_of(from)); else if (!(others & rank_bb(from))) san += rank_to_char(rank_of(from)); else san += square_to_string(from); } } else if (pos.capture(m)) san = file_to_char(file_of(from)); if (pos.capture(m)) san += 'x'; san += square_to_string(to); if (type_of(m) == PROMOTION) san += string("=") + PieceToChar[WHITE][promotion_type(m)]; } if (pos.gives_check(m, CheckInfo(pos))) { StateInfo st; pos.do_move(m, st); // 合法手があるなら'+' , ない(詰み?)なら'#' san += MoveList pos.undo_move(m); } return san; } /// pretty_pv() formats human-readable search information, typically to be /// appended to the search log file. It uses the two helpers below to pretty /// format time and score respectively. // pretty_pv()は人間が読みやすいフォーマットの探索情報を出力する。これは // 典型的には探索ログ・ファイルに出力される。これは綺麗なフォーマットでの時間と // スコアをそれぞれについて以下の2つのヘルパー関数を用いる。 // 時間を人間にわかりやく表示するためのもの。 // "00:50:12"のような形式で出力する。 static string time_to_string(int64_t msecs) { const int MSecMinute = 1000 * 60; const int MSecHour = 1000 * 60 * 60; int64_t hours = msecs / MSecHour; int64_t minutes = (msecs % MSecHour) / MSecMinute; int64_t seconds = ((msecs % MSecHour) % MSecMinute) / 1000; stringstream s; if (hours) s << hours << ':'; s << setfill('0') << setw(2) << minutes << ':' << setw(2) << seconds; return s.str(); } // 評価値を人間にわかりやすく出力する。 // 詰みを表すスコアであれば、何手で詰むのかを"#5"(先手が後手を5手で詰ませることが出来る)のような // 形式で出力する。また、点数は歩を1.0と正規化して出力する。 static string score_to_string(Value v) { stringstream s; if (v >= VALUE_MATE_IN_MAX_PLY) s << "#" << (VALUE_MATE - v + 1) / 2; else if (v <= VALUE_MATED_IN_MAX_PLY) s << "-#" << (VALUE_MATE + v) / 2; else s << setprecision(2) << fixed << showpos << double(v) / PawnValueMg; return s.str(); } // PV(最善応手列)を盤面などをASCII出力して人間にわかりやすく出力する。 // ※ ログファイルへの書き出し時に用いる。 string pretty_pv(Position& pos, int depth, Value value, int64_t msecs, Move pv[]) { const int64_t K = 1000; const int64_t M = 1000000; std::stack Move* m = pv; string san, padding; size_t length; stringstream s; s << setw(2) << depth << setw(8) << score_to_string(value) << setw(8) << time_to_string(msecs); // 探索したノードを"3.5M","1.2K"のようにわかりやすい単位で出力する。 if (pos.nodes_searched() < M) s << setw(8) << pos.nodes_searched() / 1 << " "; else if (pos.nodes_searched() < K * M) s << setw(7) << pos.nodes_searched() / K << "K "; else s << setw(7) << pos.nodes_searched() / M << "M "; padding = string(s.str().length(), ' '); length = padding.length(); // 与えられた指し手を表示しないといけないが、 // このときに局面が必要になるので指し手で局面を進めながら文字列を生成する。 while (*m != MOVE_NONE) { san = move_to_san(pos, *m); // 80文字を超えるならpaddingして改行。 if (length + san.length() > 80) { s << "\n" + padding; length = padding.length(); } s << san << ' '; length += san.length() + 1; st.push(StateInfo()); pos.do_move(*m++, st.top()); } while (m != pv) pos.undo_move(*--m); return s.str(); } |