From 84380380160134bd0c86ea00059eb524923a296a Mon Sep 17 00:00:00 2001 From: Musa <176777775+edawite@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:01:04 -0400 Subject: [PATCH] Add Quarto game --- docs/games.md | 1 + open_spiel/games/CMakeLists.txt | 6 + open_spiel/games/quarto/quarto.cc | 412 ++++++++++++ open_spiel/games/quarto/quarto.h | 171 +++++ open_spiel/games/quarto/quarto_test.cc | 160 +++++ .../integration_tests/playthroughs/quarto.txt | 611 ++++++++++++++++++ open_spiel/python/tests/pyspiel_test.py | 1 + 7 files changed, 1362 insertions(+) create mode 100644 open_spiel/games/quarto/quarto.cc create mode 100644 open_spiel/games/quarto/quarto.h create mode 100644 open_spiel/games/quarto/quarto_test.cc create mode 100644 open_spiel/integration_tests/playthroughs/quarto.txt diff --git a/docs/games.md b/docs/games.md index 076aa8fde5..209db8b3b7 100644 --- a/docs/games.md +++ b/docs/games.md @@ -84,6 +84,7 @@ Status | Game 🟢 | [Pig](https://en.wikipedia.org/wiki/Pig_\(dice_game\)) | 2-10 | ❌ | ✅ | Each player rolls a dice until they get a 1 or they 'hold'; the rolled total is added to their score. 🟢 | [Prisoner's Dilemma](https://en.wikipedia.org/wiki/Prisoner%27s_dilemma) | 2 | ✅ | ✅ | Players decide on whether to cooperate or defect given a situation with different payoffs. 🟢 | [Poker (Hold 'em)](https://en.wikipedia.org/wiki/Texas_hold_%27em) | 2-10 | ❌ | ❌ | Players bet on whether their hand of cards plus some communal cards will form a special set. Implemented via [ACPC](http://www.computerpokercompetition.org/). +🔶 | [Quarto](https://en.wikipedia.org/wiki/Quarto_\(board_game\)) | 2 | ✅ | ✅ | Players place pieces chosen by their opponent and try to form a line whose pieces share an attribute. ❌ ([#1158](https://github.com/google-deepmind/open_spiel/issues/1158)) | [Quoridor](https://en.wikipedia.org/wiki/Quoridor) | 2-4 | ✅ | ✅ | Each turn, players can either move their agent or add a small wall to the board. ❌ ([#811](https://github.com/google-deepmind/open_spiel/issues/811)) | Reconnaissance Blind Chess | 2 | ✅ | ❌ | Chess with opponent's pieces unknown, with sensing moves. Chess variant, invented by John Hopkins University Applied Physics Lab. Used in NeurIPS competition and Hidden Information Game Competition. References: [Markowitz et al. '18, On the Complexity of Reconnaissance Blind Chess](https://arxiv.org/abs/1811.03119). [Newman et al. '16, Reconnaissance blind multi-chess: an experimentation platform for ISR sensor fusion and resource management](https://www.spiedigitallibrary.org/conference-proceedings-of-spie/9842/984209/Reconnaissance-blind-multi-chess--an-experimentation-platform-for-ISR/10.1117/12.2228127.short?SSO=1). 🟢 | Routing game | 1+ | ✅ | ✅ | Players choose at each node where they go. They have an origin, a destination and a departure time and choose their route to minimize their travel time. Time spent on each link is a function of the number of players on the link when the player reaches the link. References: [Cabannes et. al. '21, Solving N-player dynamic routing games with congestion: a mean field approach](https://arxiv.org/pdf/2110.11943.pdf). diff --git a/open_spiel/games/CMakeLists.txt b/open_spiel/games/CMakeLists.txt index 59d2a77802..b1c8b04d1a 100644 --- a/open_spiel/games/CMakeLists.txt +++ b/open_spiel/games/CMakeLists.txt @@ -193,6 +193,8 @@ set(GAME_SOURCES pig/pig.h pokerkit_wrapper/pokerkit_wrapper_struct.h repeated_pokerkit/repeated_pokerkit_struct.h + quarto/quarto.cc + quarto/quarto.h quoridor/quoridor.cc quoridor/quoridor.h rbc/rbc.cc @@ -680,6 +682,10 @@ add_executable(repeated_pokerkit_struct_test repeated_pokerkit/repeated_pokerkit $) add_test(repeated_pokerkit_struct_test repeated_pokerkit_struct_test) +add_executable(quarto_test quarto/quarto_test.cc ${OPEN_SPIEL_OBJECTS} + $) +add_test(quarto_test quarto_test) + add_executable(quoridor_test quoridor/quoridor_test.cc ${OPEN_SPIEL_OBJECTS} $) add_test(quoridor_test quoridor_test) diff --git a/open_spiel/games/quarto/quarto.cc b/open_spiel/games/quarto/quarto.cc new file mode 100644 index 0000000000..73a4277b0b --- /dev/null +++ b/open_spiel/games/quarto/quarto.cc @@ -0,0 +1,412 @@ +// Copyright 2019 DeepMind Technologies Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "open_spiel/games/quarto/quarto.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "open_spiel/abseil-cpp/absl/strings/str_cat.h" +#include "open_spiel/abseil-cpp/absl/strings/str_format.h" +#include "open_spiel/abseil-cpp/absl/types/span.h" +#include "open_spiel/observer.h" +#include "open_spiel/spiel.h" +#include "open_spiel/spiel_utils.h" +#include "open_spiel/utils/tensor_view.h" + +namespace open_spiel { +namespace quarto { +namespace { + +const GameType kGameType{/*short_name=*/"quarto", + /*long_name=*/"Quarto", + GameType::Dynamics::kSequential, + GameType::ChanceMode::kDeterministic, + GameType::Information::kPerfectInformation, + GameType::Utility::kZeroSum, + GameType::RewardModel::kTerminal, + /*max_num_players=*/2, + /*min_num_players=*/2, + /*provides_information_state_string=*/true, + /*provides_information_state_tensor=*/false, + /*provides_observation_string=*/true, + /*provides_observation_tensor=*/true, + /*parameter_specification=*/{}}; + +std::shared_ptr Factory(const GameParameters& params) { + return std::make_shared(params); +} + +REGISTER_SPIEL_GAME(kGameType, Factory); + +RegisterSingleTensorObserver single_tensor(kGameType.short_name); + +std::string PlayerToString(Player player) { + switch (player) { + case 0: + return "player_0"; + case 1: + return "player_1"; + default: + SpielFatalError(absl::StrCat("Invalid player: ", player)); + } +} + +Player StringToPlayer(const std::string& player) { + if (player == "player_0") return 0; + if (player == "player_1") return 1; + SpielFatalError(absl::StrCat("Invalid player string: ", player)); +} + +std::string PhaseToString(Phase phase) { + switch (phase) { + case Phase::kSelect: + return "select"; + case Phase::kPlace: + return "place"; + case Phase::kTerminal: + return "terminal"; + } + SpielFatalError("Invalid Quarto phase."); +} + +Phase StringToPhase(const std::string& phase) { + if (phase == "select") return Phase::kSelect; + if (phase == "place") return Phase::kPlace; + if (phase == "terminal") return Phase::kTerminal; + SpielFatalError(absl::StrCat("Invalid phase string: ", phase)); +} + +std::string OutcomeToString(Phase phase, Player outcome) { + if (phase != Phase::kTerminal) return ""; + return outcome == kInvalidPlayer ? "draw" : PlayerToString(outcome); +} + +std::string PieceToString(int piece) { + if (piece == kNoPiece) return "-"; + return absl::StrFormat("%X", piece); +} + +} // namespace + +bool LineHasQuarto(const std::array& pieces) { + if (std::any_of(pieces.begin(), pieces.end(), + [](int piece) { return piece == kEmptyCell; })) { + return false; + } + int common_ones = pieces[0] & pieces[1] & pieces[2] & pieces[3]; + int common_zeros = (~(pieces[0] | pieces[1] | pieces[2] | pieces[3])) & 0xf; + return common_ones != 0 || common_zeros != 0; +} + +bool BoardHasQuarto(const std::array& board) { + for (int row = 0; row < kNumRows; ++row) { + std::array line; + for (int col = 0; col < kNumCols; ++col) { + line[col] = board[row * kNumCols + col]; + } + if (LineHasQuarto(line)) return true; + } + for (int col = 0; col < kNumCols; ++col) { + std::array line; + for (int row = 0; row < kNumRows; ++row) { + line[row] = board[row * kNumCols + col]; + } + if (LineHasQuarto(line)) return true; + } + return LineHasQuarto({board[0], board[5], board[10], board[15]}) || + LineHasQuarto({board[3], board[6], board[9], board[12]}); +} + +QuartoState::QuartoState(std::shared_ptr game) : State(game) { + board_.fill(kEmptyCell); +} + +void QuartoState::DoApplyAction(Action action) { + SPIEL_CHECK_GE(action, 0); + SPIEL_CHECK_LT(action, kNumPieces); + if (phase_ == Phase::kSelect) { + SPIEL_CHECK_FALSE(IsPieceUsed(action)); + selected_piece_ = action; + used_pieces_ |= uint16_t{1} << action; + current_player_ = 1 - current_player_; + phase_ = Phase::kPlace; + return; + } + + SPIEL_CHECK_EQ(phase_, Phase::kPlace); + SPIEL_CHECK_EQ(board_[action], kEmptyCell); + board_[action] = selected_piece_; + selected_piece_ = kNoPiece; + ++num_placements_; + + if (HasQuarto()) { + outcome_ = current_player_; + phase_ = Phase::kTerminal; + } else if (num_placements_ == kNumCells) { + outcome_ = kInvalidPlayer; + phase_ = Phase::kTerminal; + } else { + phase_ = Phase::kSelect; + } +} + +std::vector QuartoState::LegalActions() const { + if (IsTerminal()) return {}; + std::vector actions; + actions.reserve(kNumPieces - num_placements_); + if (phase_ == Phase::kSelect) { + for (int piece = 0; piece < kNumPieces; ++piece) { + if (!IsPieceUsed(piece)) actions.push_back(piece); + } + } else { + for (int cell = 0; cell < kNumCells; ++cell) { + if (board_[cell] == kEmptyCell) actions.push_back(cell); + } + } + return actions; +} + +std::string QuartoState::ActionToString(Player player, Action action_id) const { + if (phase_ == Phase::kSelect) { + return absl::StrCat("select ", PieceToString(action_id)); + } + if (phase_ == Phase::kPlace) { + return absl::StrCat("place ", PieceToString(selected_piece_), " at (", + action_id / kNumCols, ",", action_id % kNumCols, ")"); + } + return game_->ActionToString(player, action_id); +} + +std::string QuartoState::ToString() const { + std::ostringstream stream; + if (IsTerminal()) { + stream << (outcome_ == kInvalidPlayer + ? "Draw" + : absl::StrCat(PlayerToString(outcome_), " wins")); + } else { + stream << PlayerToString(current_player_) << " to " + << PhaseToString(phase_); + } + stream << "\nSelected piece: " << PieceToString(selected_piece_) << "\n"; + for (int row = 0; row < kNumRows; ++row) { + for (int col = 0; col < kNumCols; ++col) { + if (col > 0) stream << " "; + int piece = BoardAt(row, col); + stream << (piece == kEmptyCell ? "." : PieceToString(piece)); + } + if (row + 1 < kNumRows) stream << "\n"; + } + return stream.str(); +} + +bool QuartoState::HasQuarto() const { return BoardHasQuarto(board_); } + +std::vector QuartoState::Returns() const { + if (!IsTerminal() || outcome_ == kInvalidPlayer) return {0.0, 0.0}; + return outcome_ == 0 ? std::vector{1.0, -1.0} + : std::vector{-1.0, 1.0}; +} + +std::string QuartoState::InformationStateString(Player player) const { + SPIEL_CHECK_GE(player, 0); + SPIEL_CHECK_LT(player, num_players_); + return HistoryString(); +} + +std::string QuartoState::ObservationString(Player player) const { + SPIEL_CHECK_GE(player, 0); + SPIEL_CHECK_LT(player, num_players_); + return ToString(); +} + +void QuartoState::ObservationTensor(Player player, + absl::Span values) const { + SPIEL_CHECK_GE(player, 0); + SPIEL_CHECK_LT(player, num_players_); + TensorView<2> view(values, {kNumPieces, kNumCells + 1}, true); + for (int cell = 0; cell < kNumCells; ++cell) { + if (board_[cell] != kEmptyCell) { + view[{board_[cell], cell}] = 1.0; + } + } + if (selected_piece_ != kNoPiece) { + view[{selected_piece_, kNumCells}] = 1.0; + } +} + +std::unique_ptr QuartoState::Clone() const { + return std::make_unique(*this); +} + +void QuartoState::UndoAction(Player player, Action action) { + if (phase_ == Phase::kPlace) { + SPIEL_CHECK_EQ(selected_piece_, action); + used_pieces_ &= ~(uint16_t{1} << selected_piece_); + selected_piece_ = kNoPiece; + current_player_ = player; + phase_ = Phase::kSelect; + } else { + SPIEL_CHECK_NE(board_[action], kEmptyCell); + selected_piece_ = board_[action]; + board_[action] = kEmptyCell; + --num_placements_; + current_player_ = player; + outcome_ = kInvalidPlayer; + phase_ = Phase::kPlace; + } + history_.pop_back(); + --move_number_; +} + +std::unique_ptr QuartoState::ToStruct() const { + auto state_struct = std::make_unique(); + state_struct->current_player = PlayerToString(current_player_); + state_struct->phase = PhaseToString(phase_); + state_struct->board.assign(board_.begin(), board_.end()); + state_struct->selected_piece = selected_piece_; + state_struct->outcome = OutcomeToString(phase_, outcome_); + return state_struct; +} + +std::unique_ptr QuartoState::ToObservationStruct( + Player player) const { + SPIEL_CHECK_GE(player, 0); + SPIEL_CHECK_LT(player, num_players_); + return std::make_unique(ToJson()); +} + +std::unique_ptr QuartoState::ActionToStruct( + Player player, Action action_id) const { + SPIEL_CHECK_EQ(player, CurrentPlayer()); + auto action_struct = std::make_unique(); + if (phase_ == Phase::kSelect) { + action_struct->action_type = "select"; + action_struct->piece = action_id; + action_struct->row = -1; + action_struct->col = -1; + } else { + SPIEL_CHECK_EQ(phase_, Phase::kPlace); + action_struct->action_type = "place"; + action_struct->piece = selected_piece_; + action_struct->row = action_id / kNumCols; + action_struct->col = action_id % kNumCols; + } + return action_struct; +} + +std::vector QuartoState::StructToActions( + const ActionStruct& action_struct) const { + const auto* action = SafeActionCast(action_struct); + if (phase_ == Phase::kSelect) { + SPIEL_CHECK_EQ(action->action_type, "select"); + SPIEL_CHECK_GE(action->piece, 0); + SPIEL_CHECK_LT(action->piece, kNumPieces); + return {action->piece}; + } + SPIEL_CHECK_EQ(phase_, Phase::kPlace); + SPIEL_CHECK_EQ(action->action_type, "place"); + SPIEL_CHECK_EQ(action->piece, selected_piece_); + SPIEL_CHECK_GE(action->row, 0); + SPIEL_CHECK_LT(action->row, kNumRows); + SPIEL_CHECK_GE(action->col, 0); + SPIEL_CHECK_LT(action->col, kNumCols); + return {action->row * kNumCols + action->col}; +} + +QuartoState::QuartoState(std::shared_ptr game, + const QuartoStateStruct& state_struct) + : State(game) { + if (state_struct.board.size() != kNumCells) { + SpielFatalError(absl::StrFormat("Invalid board size: expected %d, got %d", + kNumCells, state_struct.board.size())); + } + + board_.fill(kEmptyCell); + for (int cell = 0; cell < kNumCells; ++cell) { + int piece = state_struct.board[cell]; + if (piece < kEmptyCell || piece >= kNumPieces) { + SpielFatalError(absl::StrCat("Invalid piece: ", piece)); + } + if (piece != kEmptyCell) { + if (IsPieceUsed(piece)) { + SpielFatalError(absl::StrCat("Duplicate piece: ", piece)); + } + board_[cell] = piece; + used_pieces_ |= uint16_t{1} << piece; + ++num_placements_; + } + } + + phase_ = StringToPhase(state_struct.phase); + current_player_ = StringToPlayer(state_struct.current_player); + selected_piece_ = state_struct.selected_piece; + + if (phase_ == Phase::kPlace) { + if (selected_piece_ < 0 || selected_piece_ >= kNumPieces || + IsPieceUsed(selected_piece_)) { + SpielFatalError("Place phase requires a valid unused selected piece."); + } + used_pieces_ |= uint16_t{1} << selected_piece_; + } else if (selected_piece_ != kNoPiece) { + SpielFatalError("Only the place phase can have a selected piece."); + } + + Player expected_player = phase_ == Phase::kPlace + ? (num_placements_ % 2 == 0 ? 1 : 0) + : (num_placements_ % 2 == 0 ? 0 : 1); + if (current_player_ != expected_player) { + SpielFatalError(absl::StrCat("Invalid current player: expected ", + PlayerToString(expected_player), ", got ", + state_struct.current_player)); + } + + bool has_quarto = HasQuarto(); + if (phase_ == Phase::kTerminal) { + if (has_quarto) { + Player expected_outcome = num_placements_ % 2 == 0 ? 0 : 1; + outcome_ = StringToPlayer(state_struct.outcome); + if (outcome_ != expected_outcome) { + SpielFatalError("Outcome does not match the player who placed last."); + } + } else { + if (num_placements_ != kNumCells || state_struct.outcome != "draw") { + SpielFatalError("A terminal state must be a win or a full-board draw."); + } + outcome_ = kInvalidPlayer; + } + } else { + if (has_quarto || num_placements_ == kNumCells || + !state_struct.outcome.empty()) { + SpielFatalError("Non-terminal Quarto state has a terminal outcome."); + } + } + + starting_state_str_ = ToJson(); +} + +QuartoGame::QuartoGame(const GameParameters& params) + : Game(kGameType, params) {} + +std::string QuartoGame::ActionToString(Player player, Action action_id) const { + return absl::StrCat("action ", action_id); +} + +} // namespace quarto +} // namespace open_spiel diff --git a/open_spiel/games/quarto/quarto.h b/open_spiel/games/quarto/quarto.h new file mode 100644 index 0000000000..5db6d5dce9 --- /dev/null +++ b/open_spiel/games/quarto/quarto.h @@ -0,0 +1,171 @@ +// Copyright 2019 DeepMind Technologies Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef OPEN_SPIEL_GAMES_QUARTO_H_ +#define OPEN_SPIEL_GAMES_QUARTO_H_ + +#include +#include +#include +#include +#include +#include + +#include "open_spiel/abseil-cpp/absl/types/optional.h" +#include "open_spiel/abseil-cpp/absl/types/span.h" +#include "open_spiel/game_parameters.h" +#include "open_spiel/json/include/nlohmann/json.hpp" +#include "open_spiel/spiel.h" + +// Quarto is a two-player board game played with 16 unique pieces. Each piece +// has four binary attributes. On a turn, a player places the piece chosen by +// their opponent, then chooses the piece that the opponent must place. The +// player who completes a row, column, or diagonal whose pieces share any +// attribute wins. +// +// Parameters: none. + +namespace open_spiel { +namespace quarto { + +inline constexpr int kNumPlayers = 2; +inline constexpr int kNumRows = 4; +inline constexpr int kNumCols = 4; +inline constexpr int kNumCells = kNumRows * kNumCols; +inline constexpr int kNumPieces = 16; +inline constexpr int kNumAttributes = 4; +inline constexpr int kEmptyCell = -1; +inline constexpr int kNoPiece = -1; + +enum class Phase { + kSelect, + kPlace, + kTerminal, +}; + +inline std::ostream& operator<<(std::ostream& stream, Phase phase) { + return stream << static_cast(phase); +} + +struct QuartoStructContents { + std::string current_player; + std::string phase; + std::vector board; + int selected_piece; + std::string outcome; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(QuartoStructContents, current_player, phase, + board, selected_piece, outcome); +}; + +SPIEL_DEFINE_STRUCT(QuartoStateStruct, StateStruct, QuartoStructContents); +SPIEL_DEFINE_STRUCT(QuartoObservationStruct, ObservationStruct, + QuartoStructContents); + +struct QuartoActionStruct : public ActionStruct { + std::string action_type; + int piece; + int row; + int col; + SPIEL_STRUCT_BOILERPLATE(QuartoActionStruct, action_type, piece, row, col); +}; + +class QuartoState : public State { + public: + explicit QuartoState(std::shared_ptr game); + QuartoState(std::shared_ptr game, + const QuartoStateStruct& state_struct); + + QuartoState(const QuartoState&) = default; + QuartoState& operator=(const QuartoState&) = default; + + Player CurrentPlayer() const override { + return IsTerminal() ? kTerminalPlayerId : current_player_; + } + std::string ActionToString(Player player, Action action_id) const override; + std::string ToString() const override; + bool IsTerminal() const override { return phase_ == Phase::kTerminal; } + std::vector Returns() const override; + std::string InformationStateString(Player player) const override; + std::string ObservationString(Player player) const override; + void ObservationTensor(Player player, + absl::Span values) const override; + std::unique_ptr Clone() const override; + void UndoAction(Player player, Action action) override; + std::vector LegalActions() const override; + + std::unique_ptr ToStruct() const override; + std::unique_ptr ToObservationStruct( + Player player) const override; + std::unique_ptr ActionToStruct(Player player, + Action action_id) const override; + std::vector StructToActions( + const ActionStruct& action_struct) const override; + + int BoardAt(int row, int col) const { return board_[row * kNumCols + col]; } + int SelectedPiece() const { return selected_piece_; } + Phase CurrentPhase() const { return phase_; } + Player Outcome() const { return outcome_; } + + protected: + void DoApplyAction(Action action) override; + + private: + bool HasQuarto() const; + bool IsPieceUsed(int piece) const { + return (used_pieces_ & (uint16_t{1} << piece)) != 0; + } + + std::array board_; + uint16_t used_pieces_ = 0; + int selected_piece_ = kNoPiece; + int num_placements_ = 0; + Player current_player_ = 0; + Player outcome_ = kInvalidPlayer; + Phase phase_ = Phase::kSelect; +}; + +class QuartoGame : public Game { + public: + explicit QuartoGame(const GameParameters& params); + int NumDistinctActions() const override { return kNumPieces; } + using Game::NewInitialState; + std::unique_ptr NewInitialState() const override { + return std::make_unique(shared_from_this()); + } + std::unique_ptr NewInitialState( + const QuartoStateStruct& state_struct) const { + return std::make_unique(shared_from_this(), state_struct); + } + std::unique_ptr NewInitialState( + const nlohmann::json& json) const override { + return NewInitialState(QuartoStateStruct(json)); + } + int NumPlayers() const override { return kNumPlayers; } + double MinUtility() const override { return -1; } + absl::optional UtilitySum() const override { return 0; } + double MaxUtility() const override { return 1; } + std::vector ObservationTensorShape() const override { + return {kNumPieces, kNumCells + 1}; + } + int MaxGameLength() const override { return 2 * kNumPieces; } + std::string ActionToString(Player player, Action action_id) const override; +}; + +bool LineHasQuarto(const std::array& pieces); +bool BoardHasQuarto(const std::array& board); + +} // namespace quarto +} // namespace open_spiel + +#endif // OPEN_SPIEL_GAMES_QUARTO_H_ diff --git a/open_spiel/games/quarto/quarto_test.cc b/open_spiel/games/quarto/quarto_test.cc new file mode 100644 index 0000000000..9d3c6850fb --- /dev/null +++ b/open_spiel/games/quarto/quarto_test.cc @@ -0,0 +1,160 @@ +// Copyright 2019 DeepMind Technologies Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "open_spiel/games/quarto/quarto.h" + +#include +#include +#include +#include + +#include "open_spiel/json/include/nlohmann/json.hpp" +#include "open_spiel/spiel.h" +#include "open_spiel/spiel_utils.h" +#include "open_spiel/tests/basic_tests.h" +#include "open_spiel/utils/status.h" + +namespace open_spiel { +namespace quarto { +namespace { + +namespace testing = open_spiel::testing; + +void BasicTests() { + testing::LoadGameTest("quarto"); + testing::NoChanceOutcomesTest(*LoadGame("quarto")); + testing::RandomSimTest(*LoadGame("quarto"), 100); +} + +void TestLines() { + SPIEL_CHECK_TRUE(LineHasQuarto({0, 2, 4, 6})); + SPIEL_CHECK_TRUE(LineHasQuarto({8, 9, 10, 11})); + SPIEL_CHECK_FALSE(LineHasQuarto({0, 1, 2, 12})); + SPIEL_CHECK_FALSE(LineHasQuarto({0, 1, 2, kEmptyCell})); +} + +void TestTurnSequenceAndWin() { + auto state = LoadGame("quarto")->NewInitialState(); + SPIEL_CHECK_EQ(state->CurrentPlayer(), 0); + SPIEL_CHECK_EQ(state->LegalActions().size(), kNumPieces); + + state->ApplyAction(0); + auto* quarto_state = static_cast(state.get()); + SPIEL_CHECK_EQ(state->CurrentPlayer(), 1); + SPIEL_CHECK_EQ(quarto_state->CurrentPhase(), Phase::kPlace); + SPIEL_CHECK_EQ(quarto_state->SelectedPiece(), 0); + SPIEL_CHECK_EQ(state->LegalActions().size(), kNumCells); + + state->ApplyAction(0); + SPIEL_CHECK_EQ(state->CurrentPlayer(), 1); + SPIEL_CHECK_EQ(quarto_state->CurrentPhase(), Phase::kSelect); + SPIEL_CHECK_EQ(quarto_state->BoardAt(0, 0), 0); + SPIEL_CHECK_EQ(state->LegalActions().size(), kNumPieces - 1); + + // Complete the top row with even-numbered pieces, which all share their + // least-significant attribute. The player placing piece 6 wins. + for (Action action : {2, 1, 4, 2, 6, 3}) { + state->ApplyAction(action); + } + SPIEL_CHECK_TRUE(state->IsTerminal()); + SPIEL_CHECK_EQ(quarto_state->Outcome(), 0); + SPIEL_CHECK_EQ(state->Returns(), std::vector({1.0, -1.0})); + + auto restored = + state->GetGame()->NewInitialState(nlohmann::json::parse(state->ToJson())); + SPIEL_CHECK_TRUE(restored->IsTerminal()); + SPIEL_CHECK_EQ(restored->ToString(), state->ToString()); + + state->UndoAction(0, 3); + SPIEL_CHECK_FALSE(state->IsTerminal()); + SPIEL_CHECK_EQ(quarto_state->CurrentPhase(), Phase::kPlace); + SPIEL_CHECK_EQ(quarto_state->SelectedPiece(), 6); + state->ApplyAction(3); + SPIEL_CHECK_TRUE(state->IsTerminal()); + SPIEL_CHECK_EQ(state->Returns(), std::vector({1.0, -1.0})); +} + +void TestUndo() { + auto state = LoadGame("quarto")->NewInitialState(); + state->ApplyAction(5); + state->UndoAction(0, 5); + SPIEL_CHECK_EQ(state->CurrentPlayer(), 0); + SPIEL_CHECK_EQ(state->LegalActions().size(), kNumPieces); + + state->ApplyAction(5); + state->ApplyAction(7); + state->UndoAction(1, 7); + auto* quarto_state = static_cast(state.get()); + SPIEL_CHECK_EQ(state->CurrentPlayer(), 1); + SPIEL_CHECK_EQ(quarto_state->CurrentPhase(), Phase::kPlace); + SPIEL_CHECK_EQ(quarto_state->SelectedPiece(), 5); + SPIEL_CHECK_EQ(quarto_state->BoardAt(1, 3), kEmptyCell); +} + +void TestStructs() { + auto game = LoadGame("quarto"); + auto state = game->NewInitialState(); + state->ApplyAction(9); + + auto state_struct = state->ToStruct(); + SPIEL_CHECK_EQ(state_struct->ToJson(), state->ToJson()); + auto restored = + game->NewInitialState(nlohmann::json::parse(state_struct->ToJson())); + SPIEL_CHECK_EQ(restored->ToString(), state->ToString()); + + auto observation_struct = state->ToObservationStruct(0); + SPIEL_CHECK_EQ(observation_struct->ToJson(), state_struct->ToJson()); + + auto action_struct = state->ActionToStruct(1, 6); + auto* quarto_action = SafeActionCast(*action_struct); + SPIEL_CHECK_EQ(quarto_action->action_type, "place"); + SPIEL_CHECK_EQ(quarto_action->piece, 9); + SPIEL_CHECK_EQ(quarto_action->row, 1); + SPIEL_CHECK_EQ(quarto_action->col, 2); + SPIEL_CHECK_TRUE(state->ValidateActionStruct(*action_struct).ok()); + SPIEL_CHECK_EQ(state->StructToActions(*action_struct), + std::vector({6})); + + auto state2 = game->NewInitialState(); + auto selection_struct = state2->ActionToStruct(0, 12); + SPIEL_CHECK_TRUE(state2->ApplyActionStruct(*selection_struct).ok()); + SPIEL_CHECK_EQ(static_cast(state2.get())->SelectedPiece(), 12); +} + +void TestObservationTensor() { + auto game = LoadGame("quarto"); + auto state = game->NewInitialState(); + state->ApplyAction(3); + + std::vector tensor = state->ObservationTensor(0); + SPIEL_CHECK_EQ(tensor[3 * (kNumCells + 1) + kNumCells], 1.0); + + state->ApplyAction(5); + tensor = state->ObservationTensor(0); + SPIEL_CHECK_EQ(tensor[3 * (kNumCells + 1) + 5], 1.0); + SPIEL_CHECK_EQ(tensor[3 * (kNumCells + 1) + kNumCells], 0.0); +} + +} // namespace +} // namespace quarto +} // namespace open_spiel + +int main(int argc, char** argv) { + open_spiel::quarto::BasicTests(); + open_spiel::quarto::TestLines(); + open_spiel::quarto::TestTurnSequenceAndWin(); + open_spiel::quarto::TestUndo(); + open_spiel::quarto::TestStructs(); + open_spiel::quarto::TestObservationTensor(); +} diff --git a/open_spiel/integration_tests/playthroughs/quarto.txt b/open_spiel/integration_tests/playthroughs/quarto.txt new file mode 100644 index 0000000000..c961b7b910 --- /dev/null +++ b/open_spiel/integration_tests/playthroughs/quarto.txt @@ -0,0 +1,611 @@ +game: quarto + +GameType.chance_mode = ChanceMode.DETERMINISTIC +GameType.dynamics = Dynamics.SEQUENTIAL +GameType.information = Information.PERFECT_INFORMATION +GameType.long_name = "Quarto" +GameType.max_num_players = 2 +GameType.min_num_players = 2 +GameType.parameter_specification = [] +GameType.provides_information_state_string = True +GameType.provides_information_state_tensor = False +GameType.provides_observation_string = True +GameType.provides_observation_tensor = True +GameType.provides_factored_observation_string = False +GameType.reward_model = RewardModel.TERMINAL +GameType.short_name = "quarto" +GameType.utility = Utility.ZERO_SUM + +NumDistinctActions() = 16 +PolicyTensorShape() = [16] +MaxChanceOutcomes() = 0 +GetParameters() = {} +NumPlayers() = 2 +MinUtility() = -1.0 +MaxUtility() = 1.0 +UtilitySum() = 0.0 +ObservationTensorShape() = [16, 17] +ObservationTensorLayout() = TensorLayout.CHW +ObservationTensorSize() = 272 +MaxGameLength() = 32 +ToString() = "quarto()" + +# State 0 +# player_0 to select +# Selected piece: - +# . . . . +# . . . . +# . . . . +# . . . . +IsTerminal() = False +History() = [] +HistoryString() = "" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +InformationStateString(0) = "" +InformationStateString(1) = "" +ObservationString(0) = "player_0 to select\nSelected piece: -\n. . . .\n. . . .\n. . . .\n. . . ." +ObservationString(1) = "player_0 to select\nSelected piece: -\n. . . .\n. . . .\n. . . .\n. . . ." +ObservationTensor(0): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ +ObservationTensor(1): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] +StringLegalActions() = ["select 0", "select 1", "select 2", "select 3", "select 4", "select 5", "select 6", "select 7", "select 8", "select 9", "select A", "select B", "select C", "select D", "select E", "select F"] + +# Apply action "select F" +action: 15 + +# State 1 +# player_1 to place +# Selected piece: F +# . . . . +# . . . . +# . . . . +# . . . . +IsTerminal() = False +History() = [15] +HistoryString() = "15" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +InformationStateString(0) = "15" +InformationStateString(1) = "15" +ObservationString(0) = "player_1 to place\nSelected piece: F\n. . . .\n. . . .\n. . . .\n. . . ." +ObservationString(1) = "player_1 to place\nSelected piece: F\n. . . .\n. . . .\n. . . .\n. . . ." +ObservationTensor(0): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉ +ObservationTensor(1): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] +StringLegalActions() = ["place F at (0,0)", "place F at (0,1)", "place F at (0,2)", "place F at (0,3)", "place F at (1,0)", "place F at (1,1)", "place F at (1,2)", "place F at (1,3)", "place F at (2,0)", "place F at (2,1)", "place F at (2,2)", "place F at (2,3)", "place F at (3,0)", "place F at (3,1)", "place F at (3,2)", "place F at (3,3)"] + +# Apply action "place F at (2,1)" +action: 9 + +# State 2 +# player_1 to select +# Selected piece: - +# . . . . +# . . . . +# . F . . +# . . . . +IsTerminal() = False +History() = [15, 9] +HistoryString() = "15, 9" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +InformationStateString(0) = "15, 9" +InformationStateString(1) = "15, 9" +ObservationString(0) = "player_1 to select\nSelected piece: -\n. . . .\n. . . .\n. F . .\n. . . ." +ObservationString(1) = "player_1 to select\nSelected piece: -\n. . . .\n. . . .\n. F . .\n. . . ." +ObservationTensor(0): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +ObservationTensor(1): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] +StringLegalActions() = ["select 0", "select 1", "select 2", "select 3", "select 4", "select 5", "select 6", "select 7", "select 8", "select 9", "select A", "select B", "select C", "select D", "select E"] + +# Apply action "select 9" +action: 9 + +# State 3 +# player_0 to place +# Selected piece: 9 +# . . . . +# . . . . +# . F . . +# . . . . +IsTerminal() = False +History() = [15, 9, 9] +HistoryString() = "15, 9, 9" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +InformationStateString(0) = "15, 9, 9" +InformationStateString(1) = "15, 9, 9" +ObservationString(0) = "player_0 to place\nSelected piece: 9\n. . . .\n. . . .\n. F . .\n. . . ." +ObservationString(1) = "player_0 to place\nSelected piece: 9\n. . . .\n. . . .\n. F . .\n. . . ." +ObservationTensor(0): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +ObservationTensor(1): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15] +StringLegalActions() = ["place 9 at (0,0)", "place 9 at (0,1)", "place 9 at (0,2)", "place 9 at (0,3)", "place 9 at (1,0)", "place 9 at (1,1)", "place 9 at (1,2)", "place 9 at (1,3)", "place 9 at (2,0)", "place 9 at (2,2)", "place 9 at (2,3)", "place 9 at (3,0)", "place 9 at (3,1)", "place 9 at (3,2)", "place 9 at (3,3)"] + +# Apply action "place 9 at (0,1)" +action: 1 + +# State 4 +# player_0 to select +# Selected piece: - +# . 9 . . +# . . . . +# . F . . +# . . . . +IsTerminal() = False +History() = [15, 9, 9, 1] +HistoryString() = "15, 9, 9, 1" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +InformationStateString(0) = "15, 9, 9, 1" +InformationStateString(1) = "15, 9, 9, 1" +ObservationString(0) = "player_0 to select\nSelected piece: -\n. 9 . .\n. . . .\n. F . .\n. . . ." +ObservationString(1) = "player_0 to select\nSelected piece: -\n. 9 . .\n. . . .\n. F . .\n. . . ." +ObservationTensor(0): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +ObservationTensor(1): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14] +StringLegalActions() = ["select 0", "select 1", "select 2", "select 3", "select 4", "select 5", "select 6", "select 7", "select 8", "select A", "select B", "select C", "select D", "select E"] + +# Apply action "select 1" +action: 1 + +# State 5 +# player_1 to place +# Selected piece: 1 +# . 9 . . +# . . . . +# . F . . +# . . . . +IsTerminal() = False +History() = [15, 9, 9, 1, 1] +HistoryString() = "15, 9, 9, 1, 1" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +InformationStateString(0) = "15, 9, 9, 1, 1" +InformationStateString(1) = "15, 9, 9, 1, 1" +ObservationString(0) = "player_1 to place\nSelected piece: 1\n. 9 . .\n. . . .\n. F . .\n. . . ." +ObservationString(1) = "player_1 to place\nSelected piece: 1\n. 9 . .\n. . . .\n. F . .\n. . . ." +ObservationTensor(0): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +ObservationTensor(1): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15] +StringLegalActions() = ["place 1 at (0,0)", "place 1 at (0,2)", "place 1 at (0,3)", "place 1 at (1,0)", "place 1 at (1,1)", "place 1 at (1,2)", "place 1 at (1,3)", "place 1 at (2,0)", "place 1 at (2,2)", "place 1 at (2,3)", "place 1 at (3,0)", "place 1 at (3,1)", "place 1 at (3,2)", "place 1 at (3,3)"] + +# Apply action "place 1 at (2,3)" +action: 11 + +# State 6 +# Apply action "select D" +action: 13 + +# State 7 +# Apply action "place D at (1,3)" +action: 7 + +# State 8 +# Apply action "select 8" +action: 8 + +# State 9 +# Apply action "place 8 at (3,3)" +action: 15 + +# State 10 +# Apply action "select E" +action: 14 + +# State 11 +# Apply action "place E at (0,3)" +action: 3 + +# State 12 +# Apply action "select 6" +action: 6 + +# State 13 +# Apply action "place 6 at (3,0)" +action: 12 + +# State 14 +# Apply action "select 3" +action: 3 + +# State 15 +# Apply action "place 3 at (1,1)" +action: 5 + +# State 16 +# Apply action "select 2" +action: 2 + +# State 17 +# Apply action "place 2 at (3,2)" +action: 14 + +# State 18 +# Apply action "select 4" +action: 4 + +# State 19 +# Apply action "place 4 at (2,0)" +action: 8 + +# State 20 +# player_0 to select +# Selected piece: - +# . 9 . E +# . 3 . D +# 4 F . 1 +# 6 . 2 8 +IsTerminal() = False +History() = [15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8] +HistoryString() = "15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +InformationStateString(0) = "15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8" +InformationStateString(1) = "15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8" +ObservationString(0) = "player_0 to select\nSelected piece: -\n. 9 . E\n. 3 . D\n4 F . 1\n6 . 2 8" +ObservationString(1) = "player_0 to select\nSelected piece: -\n. 9 . E\n. 3 . D\n4 F . 1\n6 . 2 8" +ObservationTensor(0): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯ + ◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯ + ◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯ + ◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +ObservationTensor(1): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯ + ◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯ + ◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯ + ◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 5, 7, 10, 11, 12] +StringLegalActions() = ["select 0", "select 5", "select 7", "select A", "select B", "select C"] + +# Apply action "select B" +action: 11 + +# State 21 +# player_1 to place +# Selected piece: B +# . 9 . E +# . 3 . D +# 4 F . 1 +# 6 . 2 8 +IsTerminal() = False +History() = [15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8, 11] +HistoryString() = "15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8, 11" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +InformationStateString(0) = "15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8, 11" +InformationStateString(1) = "15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8, 11" +ObservationString(0) = "player_1 to place\nSelected piece: B\n. 9 . E\n. 3 . D\n4 F . 1\n6 . 2 8" +ObservationString(1) = "player_1 to place\nSelected piece: B\n. 9 . E\n. 3 . D\n4 F . 1\n6 . 2 8" +ObservationTensor(0): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯ + ◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯ + ◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯ + ◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +ObservationTensor(1): ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯ + ◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯ + ◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯ + ◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 2, 4, 6, 10, 13] +StringLegalActions() = ["place B at (0,0)", "place B at (0,2)", "place B at (1,0)", "place B at (1,2)", "place B at (2,2)", "place B at (3,1)"] + +# Apply action "place B at (1,0)" +action: 4 + +# State 22 +# Apply action "select 0" +action: 0 + +# State 23 +# Apply action "place 0 at (1,2)" +action: 6 + +# State 24 +# Apply action "select A" +action: 10 + +# State 25 +# Apply action "place A at (3,1)" +action: 13 + +# State 26 +# player_1 wins +# Selected piece: - +# . 9 . E +# B 3 0 D +# 4 F . 1 +# 6 A 2 8 +IsTerminal() = True +History() = [15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8, 11, 4, 0, 6, 10, 13] +HistoryString() = "15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8, 11, 4, 0, 6, 10, 13" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = -4 +InformationStateString(0) = "15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8, 11, 4, 0, 6, 10, 13" +InformationStateString(1) = "15, 9, 9, 1, 1, 11, 13, 7, 8, 15, 14, 3, 6, 12, 3, 5, 2, 14, 4, 8, 11, 4, 0, 6, 10, 13" +ObservationString(0) = "player_1 wins\nSelected piece: -\n. 9 . E\nB 3 0 D\n4 F . 1\n6 A 2 8" +ObservationString(1) = "player_1 wins\nSelected piece: -\n. 9 . E\nB 3 0 D\n4 F . 1\n6 A 2 8" +ObservationTensor(0): ◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯ + ◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯ + ◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯ + ◯◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯ + ◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +ObservationTensor(1): ◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯ + ◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯ + ◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◉◯◯◯ + ◯◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯◯◯ + ◯◯◯◉◯◯◯◯◯◯◯◯◯◯◯◯◯ + ◯◯◯◯◯◯◯◯◯◉◯◯◯◯◯◯◯ +Rewards() = [-1, 1] +Returns() = [-1, 1] diff --git a/open_spiel/python/tests/pyspiel_test.py b/open_spiel/python/tests/pyspiel_test.py index 041fbe80a3..207bf0a59b 100644 --- a/open_spiel/python/tests/pyspiel_test.py +++ b/open_spiel/python/tests/pyspiel_test.py @@ -143,6 +143,7 @@ "python_team_dominoes", "python_tic_tac_toe", "python_liars_poker", + "quarto", "quoridor", "repeated_game", "repeated_leduc_poker",