From a0b329f94d3f11f2948290df59a9503345d86a80 Mon Sep 17 00:00:00 2001 From: yaneurao Date: Tue, 4 Jul 2023 21:10:27 +0900 Subject: [PATCH] =?UTF-8?q?-=20FlippedBook=20=E3=82=A8=E3=83=B3=E3=82=B8?= =?UTF-8?q?=E3=83=B3=E3=82=AA=E3=83=97=E3=82=B7=E3=83=A7=E3=83=B3=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=80=82=20=20=20-=20=E5=8F=8D=E8=BB=A2=E3=81=95?= =?UTF-8?q?=E3=81=9B=E3=81=9F=E5=B1=80=E9=9D=A2=E3=81=8C=E5=AE=9A=E8=B7=A1?= =?UTF-8?q?DB=E3=81=AB=E7=99=BB=E9=8C=B2=E3=81=95=E3=82=8C=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=9F=E3=82=89=E3=80=81=E3=81=9D=E3=82=8C=E3=81=AB?= =?UTF-8?q?=E3=83=92=E3=83=83=E3=83=88=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=82=8B=E3=82=AA=E3=83=97=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=80=82=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB=E3=83=88?= =?UTF-8?q?true=E3=80=82=20-=20BookOnTheFly=E3=81=A7=E5=AE=9A=E8=B7=A1DB?= =?UTF-8?q?=E3=81=AB=E5=B1=80=E9=9D=A2=E3=81=8C1=E3=81=A4=E3=81=97?= =?UTF-8?q?=E3=81=8B=E7=84=A1=E3=81=84=E6=99=82=E3=81=ABhit=E3=81=97?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=81=93=E3=81=A8=E3=81=8C=E3=81=82=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3=E3=80=82=20-=20Bo?= =?UTF-8?q?okOnTheFly=E3=81=A7=E6=9C=80=E5=BE=8C=E3=81=AB=E7=99=BB?= =?UTF-8?q?=E9=8C=B2=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=82=8B=E5=B1=80?= =?UTF-8?q?=E9=9D=A2=E3=81=ABhit=E3=81=97=E3=81=9F=E6=99=82=E3=81=AB?= =?UTF-8?q?=E7=A9=BA=E3=81=AE=E6=8C=87=E3=81=97=E6=89=8B=E3=81=8C=E8=BF=94?= =?UTF-8?q?=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82(illegal=20move=E3=81=AE=E3=83=81=E3=82=A7=E3=83=83?= =?UTF-8?q?=E3=82=AF=E3=81=A7=E5=BC=BE=E3=81=8B=E3=82=8C=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=81=A7=E5=AE=9F=E5=AE=B3=E3=81=AF=E3=81=AA?= =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=8C)=20-=20=E3=82=84=E3=81=AD?= =?UTF-8?q?=E3=81=86=E3=82=89=E7=8E=8B=E3=81=AE=E5=AE=9A=E8=B7=A1DB?= =?UTF-8?q?=E3=81=A7=E6=8C=87=E3=81=97=E6=89=8B=E3=81=8Bponder=E3=81=AE?= =?UTF-8?q?=E6=8C=87=E3=81=97=E6=89=8B=E3=81=AB"None"("none"=E3=81=A7?= =?UTF-8?q?=E3=81=AF=E3=81=AA=E3=81=8F)=E3=81=8C=E6=9B=B8=E3=81=8B?= =?UTF-8?q?=E3=82=8C=E3=81=A6=E3=81=84=E3=82=8B=E3=81=8B=E3=81=AE=E5=88=A4?= =?UTF-8?q?=E5=AE=9A=E8=BF=BD=E5=8A=A0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/book/book.cpp | 330 +++++++++++++++++++++++++------------------ source/book/book.h | 3 + 2 files changed, 194 insertions(+), 139 deletions(-) diff --git a/source/book/book.cpp b/source/book/book.cpp index 1a1f63086..18804ca8f 100644 --- a/source/book/book.cpp +++ b/source/book/book.cpp @@ -124,8 +124,8 @@ namespace Book // 起動時なので変換に要するオーバーヘッドは最小化したいので合法かのチェックはしない。 - move = (move_str == "none" || move_str == "resign") ? MOVE_NONE : USI::to_move16(move_str); - ponder = (ponder_str == "none" || ponder_str == "resign") ? MOVE_NONE : USI::to_move16(ponder_str); + move = (move_str == "none" || move_str == "None" || move_str == "resign") ? MOVE_NONE : USI::to_move16(move_str ); + ponder = (ponder_str == "none" || ponder_str == "None" || ponder_str == "resign") ? MOVE_NONE : USI::to_move16(ponder_str); return BookMove(move,ponder,value,depth,move_count); } @@ -519,6 +519,172 @@ namespace Book book_body[sfen] = ptr; } + // 反転された指し手を登録した新規エントリーを作成するヘルパー関数。 + BookMovesPtr make_flipped_bookmoves(BookMovesPtr pt) + { + BookMovesPtr entry(new BookMoves()); + pt->foreach([&](BookMove& bm) + { + // 盤面を反転させた指し手として設定する。 + // ponderがMOVE_NONEでもflip_move()がうまく動作することは保証されている。 + BookMove flip_book_move(flip_move(bm.move), flip_move(bm.ponder), bm.value , bm.depth , bm.move_count); + entry->push_back(flip_book_move); + } + ); + entry->sort_moves(); + return entry; + }; + + // sfenで指定された局面の情報を定跡DBファイルにon the flyで探して、それを返す。 + BookMovesPtr MemoryBook::find_bookmoves_on_the_fly(string sfen) + { + // ディスクから読み込むなら、いずれにせよ、新規エントリーを作成してそれを返す必要がある。 + BookMovesPtr pml_entry(new BookMoves()); + + // IgnoreBookPlyのときは末尾の手数は取り除いておく。 + // read_book()で取り除くと、そのあと書き出すときに手数が消失するのでまずい。(気がする) + sfen = trim(sfen); + + // ファイル自体はオープンされてして、ファイルハンドルはfsだと仮定して良い。 + + // ファイルサイズ取得 + // C++的には未定義動作だが、これのためにsys/stat.hをincludeしたくない。 + // ここでfs.clear()を呼ばないとeof()のあと、tellg()が失敗する。 + fs.clear(); + fs.seekg(0, std::ios::beg); + auto file_start = fs.tellg(); + + fs.clear(); + fs.seekg(0, std::ios::end); + + // ファイルサイズ + auto file_size = s64(fs.tellg() - file_start); + + // 与えられたseek位置から"sfen"文字列を探し、それを返す。どこまでもなければ""が返る。 + // hackとして、seek位置は-2しておく。(1行読み捨てるので、seek_fromぴったりのところに + // "sfen"から始まる文字列があるとそこを読み捨ててしまうため。-2してあれば、そこに + // CR+LFがあるはずだから、ここを読み捨てても大丈夫。) + + // last_posには、現在のファイルポジションが返ってくる。 + // ※ 実際の位置より改行コードのせいで少し手前である可能性はある。 + // ftell()を用いると、MSYS2 + g++ 環境でtellgが嘘を返す(getlineを呼び出した時に内部的に + // bufferingしているため(?)、かなり先のファイルポジションを返す)ので自前で計算する。 + auto next_sfen = [&](s64 seek_from , s64& last_pos) + { + string line; + + seek_from = std::max( s64(0), seek_from - 2); + + // 前回のgetline()でファイル末尾までいくとeofフラグが立つのでこれをクリアする必要がある。 + fs.clear(); + fs.seekg(seek_from , fstream::beg); + + // --- 1行読み捨てる + + // seek_from == 0の場合も、ここで1行読み捨てられるが、1行目は + // ヘッダ行であり、問題ない。 + getline(fs, line); + + last_pos = seek_from + (s64)line.size() + 1; + // 改行コードが1文字はあるはずだから、+1しておく。 + + // getlineはeof()を正しく反映させないのでgetline()の返し値を用いる必要がある。 + while (getline(fs, line)) + { + last_pos += s64(line.size()) + 1; + + if (!line.compare(0, 4, "sfen")) + { + // ios::binaryつけているので末尾に'\r'が付与されている。禿げそう。 + // → trim()で吸収する。(trimがStringExtension::trim_number()を呼び出すがそちらで吸収される) + return trim(line.substr(5)); + // "sfen"という文字列は取り除いたものを返す。 + // IgnoreBookPly == trueのときは手数の表記も取り除いて比較したほうがいい。 + } + } + return string(); + }; + + // バイナリサーチ + // + // 区間 [s,e) で解を求める。現時点での中間地点がm。 + // 解とは、探しているsfen文字列が書いてある行の先頭のファイルポジションのことである。 + // + // next_sfen()でm以降にある"sfen"で始まる行を読み込んだ時、そのあとのファイルポジションがlast_pos。 + + s64 s = 0, e = file_size, m , last_pos; + // s,eは無符号型だと、s - 2のような式が負にならないことを保証するのが面倒くさい。 + // こういうのを無符号型で扱うのは筋が悪い。 + + while (true) + { + m = (s + e) / 2; + + auto sfen2 = next_sfen(m, last_pos); + if (sfen2 == "" || sfen < sfen2) { + + // 左(それより小さいところ)を探す + e = m; + + } else if (sfen > sfen2) { + + // 右(それより大きいところ)を探す + + // next_sfen()のなかでgetline()し終わった時の位置より後ろに解がある。 + // ここでftell()を使いたいが、上に書いた理由で嘘が返ってくるようだ。 + s = last_pos; + + } else { + // 見つかった! + break; + } + + // 40バイトより小さなsfenはありえないので、この範囲に2つの"sfen"で始まる文字列が + // 入っていないことは保証されている。 + // ゆえに、探索範囲がこれより小さいならsの先頭から調べて("sfen"と書かれている文字列を探して)終了。 + if (s + 40 > e) + { + if ( next_sfen(s, last_pos) == sfen) + // 見つかった! + break; + + // 見つからなかった + return BookMovesPtr(); + } + + } + // 見つけた処理 + + // read_bookとほとんど同じ読み込み処理がここに必要。辛い。 + + // sfen文字列が合致したところまでは確定しており、そこまでfileのseekは完了している。 + // その直後に指し手が書かれているのでそれをgetline()で読み込めば良い。 + + while (!fs.eof()) + { + string line; + getline(fs, line); + + // バージョン識別文字列(とりあえず読み飛ばす) + if (line.length() >= 1 && line[0] == '#') + continue; + + // コメント行(とりあえず読み飛ばす) + if (line.length() >= 2 && line.substr(0, 2) == "//") + continue; + + // 次のsfenに遭遇したらこれにて終了。 + if ( (line.length() >= 5 && line.substr(0, 5) == "sfen ") + || line.length() == 0 /* 空行かeofか.. */) + { + break; + } + + pml_entry->push_back(BookMove::from_string(line)); + } + pml_entry->sort_moves(); + return pml_entry; + } BookMovesPtr MemoryBook::find(const Position& pos) { @@ -587,147 +753,19 @@ namespace Book if (on_the_fly) { - // ディスクから読み込むなら、いずれにせよ、新規エントリーを作成してそれを返す必要がある。 - BookMovesPtr pml_entry(new BookMoves()); - - // IgnoreBookPlyのときは末尾の手数は取り除いておく。 - // read_book()で取り除くと、そのあと書き出すときに手数が消失するのでまずい。(気がする) - sfen = trim(sfen); - - // ファイル自体はオープンされてして、ファイルハンドルはfsだと仮定して良い。 - - // ファイルサイズ取得 - // C++的には未定義動作だが、これのためにsys/stat.hをincludeしたくない。 - // ここでfs.clear()を呼ばないとeof()のあと、tellg()が失敗する。 - fs.clear(); - fs.seekg(0, std::ios::beg); - auto file_start = fs.tellg(); - - fs.clear(); - fs.seekg(0, std::ios::end); - - // ファイルサイズ - auto file_size = s64(fs.tellg() - file_start); - - // 与えられたseek位置から"sfen"文字列を探し、それを返す。どこまでもなければ""が返る。 - // hackとして、seek位置は-2しておく。(1行読み捨てるので、seek_fromぴったりのところに - // "sfen"から始まる文字列があるとそこを読み捨ててしまうため。-2してあれば、そこに - // CR+LFがあるはずだから、ここを読み捨てても大丈夫。) - - // last_posには、現在のファイルポジションが返ってくる。 - // ※ 実際の位置より改行コードのせいで少し手前である可能性はある。 - // ftell()を用いると、MSYS2 + g++ 環境でtellgが嘘を返す(getlineを呼び出した時に内部的に - // bufferingしているため(?)、かなり先のファイルポジションを返す)ので自前で計算する。 - auto next_sfen = [&](s64 seek_from , s64& last_pos) - { - string line; - - seek_from = std::max( s64(0), seek_from - 2); - fs.seekg(seek_from , fstream::beg); - - // --- 1行読み捨てる - - // seek_from == 0の場合も、ここで1行読み捨てられるが、1行目は - // ヘッダ行であり、問題ない。 - getline(fs, line); - last_pos = seek_from + (s64)line.size() + 1; - // 改行コードが1文字はあるはずだから、+1しておく。 - - // getlineはeof()を正しく反映させないのでgetline()の返し値を用いる必要がある。 - while (getline(fs, line)) - { - last_pos += s64(line.size()) + 1; - - if (!line.compare(0, 4, "sfen")) - { - // ios::binaryつけているので末尾に'\r'が付与されている。禿げそう。 - // → trim()で吸収する。(trimがStringExtension::trim_number()を呼び出すがそちらで吸収される) - return trim(line.substr(5)); - // "sfen"という文字列は取り除いたものを返す。 - // IgnoreBookPly == trueのときは手数の表記も取り除いて比較したほうがいい。 - } - } - return string(); - }; - - // バイナリサーチ - // - // 区間 [s,e) で解を求める。現時点での中間地点がm。 - // 解とは、探しているsfen文字列が書いてある行の先頭のファイルポジションのことである。 - // - // next_sfen()でm以降にある"sfen"で始まる行を読み込んだ時、そのあとのファイルポジションがlast_pos。 - - s64 s = 0, e = file_size, m , last_pos; - // s,eは無符号型だと、s - 2のような式が負にならないことを保証するのが面倒くさい。 - // こういうのを無符号型で扱うのは筋が悪い。 - - while (true) - { - m = (s + e) / 2; - - auto sfen2 = next_sfen(m, last_pos); - if (sfen2 == "" || sfen < sfen2) { - - // 左(それより小さいところ)を探す - e = m; - - } else if (sfen > sfen2) { - - // 右(それより大きいところ)を探す - - // next_sfen()のなかでgetline()し終わった時の位置より後ろに解がある。 - // ここでftell()を使いたいが、上に書いた理由で嘘が返ってくるようだ。 - s = last_pos; - - } else { - // 見つかった! - break; - } - - // 40バイトより小さなsfenはありえないので、この範囲に2つの"sfen"で始まる文字列が - // 入っていないことは保証されている。 - // ゆえに、探索範囲がこれより小さいなら先頭から調べて("sfen"と書かれている文字列を探して)終了。 - if (s + 40 > e) - { - if ( next_sfen(s, last_pos) == sfen) - // 見つかった! - break; - - // 見つからなかった - return BookMovesPtr(); - } - - } - // 見つけた処理 - - // read_bookとほとんど同じ読み込み処理がここに必要。辛い。 - - // sfen文字列が合致したところまでは確定しており、そこまでfileのseekは完了している。 - // その直後に指し手が書かれているのでそれをgetline()で読み込めば良い。 - - while (!fs.eof()) + auto entry = find_bookmoves_on_the_fly(sfen); + if (entry == nullptr) { - string line; - getline(fs, line); - - // バージョン識別文字列(とりあえず読み飛ばす) - if (line.length() >= 1 && line[0] == '#') - continue; - - // コメント行(とりあえず読み飛ばす) - if (line.length() >= 2 && line.substr(0, 2) == "//") - continue; - - // 次のsfenに遭遇したらこれにて終了。 - if (line.length() >= 5 && line.substr(0, 5) == "sfen ") + // FlippedBookが有効なら、反転させた局面にhitするか調べる。 + if (Options["FlippedBook"]) { - break; + entry = find_bookmoves_on_the_fly(Position::sfen_to_flipped_sfen(sfen)); + // 指し手をflipさせる + if (entry != nullptr) + entry = make_flipped_bookmoves(entry); } - - pml_entry->push_back(BookMove::from_string(line)); } - pml_entry->sort_moves(); - return pml_entry; + return entry; } else { @@ -740,6 +778,17 @@ namespace Book return BookMovesPtr(it->second); } + // FlippedBookが有効なら、反転させた局面にhitするか調べる。 + if (Options["FlippedBook"]) + { + it = book_body.find(trim(Position::sfen_to_flipped_sfen(sfen))); + if (it != book_body.end()) + { + // hitしたので反転された指し手を登録した新規エントリーを作成してそれを返す。 + return make_flipped_bookmoves(it->second); + } + } + // 空のentryを返す。 return BookMovesPtr(); } @@ -1004,6 +1053,9 @@ namespace Book // 例) 局面図が同じなら、DBの36手目の局面に40手目でもヒットする。 // これ変更したときに定跡ファイルの読み直しが必要になるのだが…(´ω`) o["IgnoreBookPly"] << Option(false); + + // 反転させた局面が定跡DBに登録されていたら、それにヒットするようになるオプション。 + o["FlippedBook"] << Option(true); } // 与えられたmで進めて定跡のpv文字列を生成する。 diff --git a/source/book/book.h b/source/book/book.h index 64fb8fcdd..f83e23d2e 100644 --- a/source/book/book.h +++ b/source/book/book.h @@ -216,6 +216,9 @@ namespace Book // sfen文字列の末尾にある手数を除去する目的。 std::string trim(std::string input) const; + // sfenで指定された局面の情報を定跡DBファイルにon the flyで探して、それを返すヘルパー関数。 + BookMovesPtr find_bookmoves_on_the_fly(std::string sfen); + // メモリに丸読みせずにfind()のごとにファイルを調べにいくのか。 // これは思考エンジン設定のOptions["BookOnTheFly"]の値を反映したもの。 // ただし、read_book()のタイミングで定跡ファイルのopenに失敗したならfalseのままである。