Skip to content

Commit

Permalink
Add ability for UCI bot to use the game history to set the state to r…
Browse files Browse the repository at this point in the history
…etain ability to check repeated states.

PiperOrigin-RevId: 698508320
Change-Id: Ib008b1b081a2b4d5397d00108393a1e175db7ebd
  • Loading branch information
lanctot committed Nov 22, 2024
1 parent 3100235 commit 0241824
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 15 deletions.
4 changes: 4 additions & 0 deletions open_spiel/bots/uci/random_uci_bot.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
#include <iostream>
#include <memory>
#include <random>
#include <sstream>
#include <string>
#include <vector>

#include "open_spiel/abseil-cpp/absl/flags/flag.h"
#include "open_spiel/abseil-cpp/absl/flags/parse.h"
Expand Down Expand Up @@ -57,6 +60,7 @@ void RandomUciBot() {
while (pos < tokens.size()) {
if (tokens[pos] == "moves") {
has_moves = true;
++pos;
break;
}
if (pos > 2) fen << ' ';
Expand Down
33 changes: 25 additions & 8 deletions open_spiel/bots/uci/uci_bot.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ namespace uci {

UCIBot::UCIBot(const std::string& bot_binary_path, int search_limit_value,
bool ponder, const Options& options,
SearchLimitType search_limit_type)
: ponder_(ponder) {
SearchLimitType search_limit_type,
bool use_game_history_for_position)
: ponder_(ponder),
use_game_history_for_position_(use_game_history_for_position) {
SPIEL_CHECK_GT(search_limit_value, 0);
SPIEL_CHECK_GT(bot_binary_path.size(), 0);
search_limit_type_ = search_limit_type;
Expand Down Expand Up @@ -91,6 +93,19 @@ UCIBot::~UCIBot() {
close(output_fd_);
}

void UCIBot::PositionFromState(const chess::ChessState& state,
const std::vector<std::string>& extra_moves) {
if (use_game_history_for_position_) {
std::pair<std::string, std::vector<std::string>> fen_and_moves =
state.ExtractFenAndMaybeMoves();
fen_and_moves.second.insert(fen_and_moves.second.end(),
extra_moves.begin(), extra_moves.end());
Position(fen_and_moves.first, fen_and_moves.second);
} else {
Position(state.Board().ToFEN(), extra_moves);
}
}

Action UCIBot::Step(const State& state) { return StepVerbose(state).first; }

std::pair<Action, std::string> UCIBot::StepVerbose(const State& state) {
Expand All @@ -101,13 +116,13 @@ std::pair<Action, std::string> UCIBot::StepVerbose(const State& state) {
if (ponder_ && ponder_move_) {
if (!was_ponder_hit_) {
Stop();
Position(chess_state.Board().ToFEN());
PositionFromState(chess_state);
tie(move_str, ponder_move_) = Go(&info_str);
} else {
tie(move_str, ponder_move_) = ReadBestMove(&info_str);
}
} else {
Position(chess_state.Board().ToFEN());
PositionFromState(chess_state);
tie(move_str, ponder_move_) = Go(&info_str);
}
was_ponder_hit_ = false;
Expand All @@ -118,7 +133,7 @@ std::pair<Action, std::string> UCIBot::StepVerbose(const State& state) {
}

if (ponder_ && ponder_move_) {
Position(chess_state.Board().ToFEN(), {move_str, *ponder_move_});
PositionFromState(chess_state, {move_str, *ponder_move_});
GoPonder();
}

Expand All @@ -136,7 +151,7 @@ void UCIBot::RestartAt(const State& state) {
ponder_move_ = absl::nullopt;
was_ponder_hit_ = false;
auto chess_state = down_cast<const chess::ChessState&>(state);
Position(chess_state.Board().ToFEN());
PositionFromState(chess_state);
}

void UCIBot::InformAction(const State& state, Player player_id, Action action) {
Expand Down Expand Up @@ -320,9 +335,11 @@ std::string UCIBot::ReadLine() {
std::unique_ptr<Bot> MakeUCIBot(const std::string& bot_binary_path,
int search_limit_value, bool ponder,
const Options& options,
SearchLimitType search_limit_type) {
SearchLimitType search_limit_type,
bool use_game_history_for_position) {
return std::make_unique<UCIBot>(bot_binary_path, search_limit_value, ponder,
options, search_limit_type);
options, search_limit_type,
use_game_history_for_position);
}

} // namespace uci
Expand Down
10 changes: 8 additions & 2 deletions open_spiel/bots/uci/uci_bot.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "open_spiel/spiel.h"
#include "open_spiel/spiel_bots.h"
#include "open_spiel/spiel_utils.h"
#include "open_spiel/games/chess/chess.h"

// **IMPORTANT NOTE** The basic test currently hangs, so consider this bot
// currently experimental. The original authors claimed to have verified it with
Expand All @@ -50,7 +51,8 @@ class UCIBot : public Bot {
// "go depth", or "go nodes".
UCIBot(const std::string& bot_binary_path, int search_limit_value,
bool ponder, const Options& options,
SearchLimitType search_limit_type = SearchLimitType::kMoveTime);
SearchLimitType search_limit_type = SearchLimitType::kMoveTime,
bool use_game_history_for_position = false);
~UCIBot() override;

Action Step(const State& state) override;
Expand Down Expand Up @@ -84,6 +86,8 @@ class UCIBot : public Bot {
void Quit();
std::pair<std::string, absl::optional<std::string>> ReadBestMove(
absl::optional<std::string*> info_string = absl::nullopt);
void PositionFromState(const chess::ChessState& state,
const std::vector<std::string>& extra_moves = {});

pid_t pid_ = -1;
int output_fd_ = -1;
Expand All @@ -94,6 +98,7 @@ class UCIBot : public Bot {
bool was_ponder_hit_ = false;

bool ponder_;
bool use_game_history_for_position_ = false;

// Input stream member variables for the bot.
FILE* input_stream_ = nullptr;
Expand All @@ -119,7 +124,8 @@ class UCIBot : public Bot {
std::unique_ptr<Bot> MakeUCIBot(
const std::string& bot_binary_path, int search_limit_value,
bool ponder = false, const Options& options = {},
SearchLimitType search_limit_type = SearchLimitType::kMoveTime);
SearchLimitType search_limit_type = SearchLimitType::kMoveTime,
bool use_game_history_for_position = false);

} // namespace uci
} // namespace open_spiel
Expand Down
16 changes: 11 additions & 5 deletions open_spiel/bots/uci/uci_bot_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
#include "open_spiel/spiel_utils.h"
#include "open_spiel/utils/init.h"

ABSL_FLAG(std::string, binary, "random_uci_bot", "Name of the binary to run.");
ABSL_FLAG(std::string, binary, "random_uci_bot",
"Name of the binary to run for chess.");

namespace open_spiel {
namespace uci {
Expand All @@ -37,14 +38,18 @@ namespace {
inline constexpr const int kNumGames = 3;
inline constexpr const int kSeed = 12874681;

void RandomUciBotTest() {
void RandomUciBotTest(bool use_game_history_for_position) {
std::string binary = absl::GetFlag(FLAGS_binary);
std::shared_ptr<const Game> game = LoadGame("chess");
Options options = {};
auto bot1 = std::make_unique<UCIBot>(binary, /*move_time*/ 10,
/*ponder*/ false, /*options*/ options);
/*ponder*/ false, /*options*/ options,
/*search_limit_type*/ SearchLimitType::kMoveTime,
use_game_history_for_position);
auto bot2 = std::make_unique<UCIBot>(binary, /*move_time*/ 10,
/*ponder*/ false, /*options*/ options);
/*ponder*/ false, /*options*/ options,
/*search_limit_type*/ SearchLimitType::kMoveTime,
use_game_history_for_position);
std::vector<Bot *> bots = {bot1.get(), bot2.get()};
for (int i = 0; i < kNumGames; ++i) {
std::unique_ptr<State> state = game->NewInitialState();
Expand Down Expand Up @@ -73,5 +78,6 @@ int main(int argc, char **argv) {
open_spiel::Init("", &argc, &argv, false);
absl::ParseCommandLine(argc, argv);
open_spiel::uci::CheckVerboseOutput();
open_spiel::uci::RandomUciBotTest();
open_spiel::uci::RandomUciBotTest(/*use_history*/false);
open_spiel::uci::RandomUciBotTest(/*use_history*/true);
}
14 changes: 14 additions & 0 deletions open_spiel/games/chess/chess.cc
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,20 @@ int ChessState::NumRepetitions(const ChessState& state) const {
}
}

std::pair<std::string, std::vector<std::string>>
ChessState::ExtractFenAndMaybeMoves() const {
SPIEL_CHECK_FALSE(IsChanceNode());
std::string initial_fen = start_board_.ToFEN(ParentGame()->IsChess960());
std::vector<std::string> move_lans;
std::unique_ptr<State> state = ParentGame()->NewInitialState(initial_fen);
ChessBoard board = down_cast<const ChessState&>(*state).Board();
for (const Move& move : moves_history_) {
move_lans.push_back(move.ToLAN(ParentGame()->IsChess960(), &board));
board.ApplyMove(move);
}
return std::make_pair(initial_fen, move_lans);
}

absl::optional<std::vector<double>> ChessState::MaybeFinalReturns() const {
if (!Board().HasSufficientMaterial()) {
return std::vector<double>{DrawUtility(), DrawUtility()};
Expand Down
4 changes: 4 additions & 0 deletions open_spiel/games/chess/chess.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ class ChessState : public State {
// history.
int NumRepetitions(const ChessState& state) const;

// Get the FEN for this move and the list of moves in UCI format.
std::pair<std::string, std::vector<std::string>> ExtractFenAndMaybeMoves()
const;

const ChessGame* ParentGame() const {
return down_cast<const ChessGame*>(GetGame().get());
}
Expand Down
1 change: 1 addition & 0 deletions open_spiel/python/pybind11/bots.cc
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ void init_pyspiel_bots(py::module& m) {
py::arg("search_limit_value"), py::arg("ponder"), py::arg("options"),
py::arg("search_limit_type") =
open_spiel::uci::SearchLimitType::kMoveTime,
py::arg("use_game_history_for_position") = false,
"Bot that can play chess using UCI chess engine.");
#endif

Expand Down

0 comments on commit 0241824

Please sign in to comment.