From 440856154122273d34cff4bc0b376d3b26e2999f Mon Sep 17 00:00:00 2001 From: wannesm Date: Mon, 7 Aug 2023 22:15:01 +0200 Subject: [PATCH 1/3] Added game Dots and Boxes Developed for the KU Leuven course "Machine Learning: Project" https://onderwijsaanbod.kuleuven.be/syllabi/e/H0T25AE.htm Contributed by Wannes Meert, Giuseppe Marra, Pieter Robberechts (Dept. Computer Science, Fac. of Engineering, KU Leuven) --- open_spiel/games/CMakeLists.txt | 6 ++++++ open_spiel/python/CMakeLists.txt | 2 ++ open_spiel/python/pybind11/pyspiel.cc | 2 ++ open_spiel/python/tests/games_sim_test.py | 9 +++++++++ open_spiel/python/tests/pyspiel_test.py | 1 + 5 files changed, 20 insertions(+) diff --git a/open_spiel/games/CMakeLists.txt b/open_spiel/games/CMakeLists.txt index 89b778606b..c5a07e415f 100644 --- a/open_spiel/games/CMakeLists.txt +++ b/open_spiel/games/CMakeLists.txt @@ -58,6 +58,8 @@ set(GAME_SOURCES dark_hex.h deep_sea.cc deep_sea.h + dots_and_boxes.cc + dots_and_boxes.h dynamic_routing/dynamic_routing_data.cc dynamic_routing/dynamic_routing_data.h dynamic_routing/dynamic_routing_utils.cc @@ -385,6 +387,10 @@ add_executable(deep_sea_test deep_sea_test.cc ${OPEN_SPIEL_OBJECTS} $) add_test(deep_sea_test deep_sea_test) +add_executable(dots_and_boxes_test dots_and_boxes_test.cc ${OPEN_SPIEL_OBJECTS} + $) +add_test(dots_and_boxes_test dots_and_boxes_test) + add_executable(dynamic_routing_data_test dynamic_routing/dynamic_routing_data_test.cc ${OPEN_SPIEL_OBJECTS} $) add_test(dynamic_routing_data_test dynamic_routing_data_test) diff --git a/open_spiel/python/CMakeLists.txt b/open_spiel/python/CMakeLists.txt index a30686e9be..cc876b7e2a 100644 --- a/open_spiel/python/CMakeLists.txt +++ b/open_spiel/python/CMakeLists.txt @@ -93,6 +93,8 @@ set(PYTHON_BINDINGS ${PYTHON_BINDINGS} pybind11/games_chess.h pybind11/games_colored_trails.cc pybind11/games_colored_trails.h + pybind11/games_dots_and_boxes.cc + pybind11/games_dots_and_boxes.h pybind11/games_euchre.cc pybind11/games_euchre.h pybind11/games_gin_rummy.cc diff --git a/open_spiel/python/pybind11/pyspiel.cc b/open_spiel/python/pybind11/pyspiel.cc index 0977080615..a6a49e5d89 100644 --- a/open_spiel/python/pybind11/pyspiel.cc +++ b/open_spiel/python/pybind11/pyspiel.cc @@ -36,6 +36,7 @@ #include "open_spiel/python/pybind11/games_bridge.h" #include "open_spiel/python/pybind11/games_chess.h" #include "open_spiel/python/pybind11/games_colored_trails.h" +#include "open_spiel/python/pybind11/games_dots_and_boxes.h" #include "open_spiel/python/pybind11/games_euchre.h" #include "open_spiel/python/pybind11/games_gin_rummy.h" #include "open_spiel/python/pybind11/games_kuhn_poker.h" @@ -642,6 +643,7 @@ PYBIND11_MODULE(pyspiel, m) { init_pyspiel_games_bridge(m); // Game-specific functions for bridge. init_pyspiel_games_chess(m); // Chess game. init_pyspiel_games_colored_trails(m); // Colored Trails game. + init_pyspiel_games_dots_and_boxes(m); // Dots-and-Boxes game. init_pyspiel_games_euchre(m); // Game-specific functions for euchre. init_pyspiel_games_gin_rummy(m); // Game-specific functions for gin_rummy. init_pyspiel_games_kuhn_poker(m); // Kuhn Poker game. diff --git a/open_spiel/python/tests/games_sim_test.py b/open_spiel/python/tests/games_sim_test.py index dcb4a74f60..cfa9382d13 100644 --- a/open_spiel/python/tests/games_sim_test.py +++ b/open_spiel/python/tests/games_sim_test.py @@ -350,6 +350,15 @@ def test_leduc_get_and_set_private_cards(self): private_cards = state.get_private_cards() self.assertEqual(private_cards, [2, 3]) + def test_dots_and_boxes_with_notation(self): + game = pyspiel.load_game("dots_and_boxes") + state = game.new_initial_state() + state.apply_action(0) # horizontal 0, 0 + state.apply_action(1) # horizontal 0, 1 + # check that we can retrieve the notiation + dbn = state.dbn_string() + self.assertEqual(dbn, "110000000000") + @parameterized.parameters( {"game_name": "blotto"}, {"game_name": "goofspiel"}, diff --git a/open_spiel/python/tests/pyspiel_test.py b/open_spiel/python/tests/pyspiel_test.py index efe74d174d..263c9fca80 100644 --- a/open_spiel/python/tests/pyspiel_test.py +++ b/open_spiel/python/tests/pyspiel_test.py @@ -51,6 +51,7 @@ "dark_hex", "dark_hex_ir", "deep_sea", + "dots_and_boxes", "dou_dizhu", "efg_game", "euchre", From b58133151fedc7b553119908db2c24f564726af0 Mon Sep 17 00:00:00 2001 From: wannesm Date: Tue, 8 Aug 2023 17:29:26 +0200 Subject: [PATCH 2/3] Game to separate folder --- open_spiel/games/CMakeLists.txt | 2 +- .../games/dots_and_boxes/dots_and_boxes.cc | 695 ++++++++++++++++++ .../games/dots_and_boxes/dots_and_boxes.h | 193 +++++ .../dots_and_boxes/dots_and_boxes_test.cc | 42 ++ .../playthroughs/dots_and_boxes.txt | 358 +++++++++ .../python/examples/dotsandboxes_example.py | 94 +++ 6 files changed, 1383 insertions(+), 1 deletion(-) create mode 100644 open_spiel/games/dots_and_boxes/dots_and_boxes.cc create mode 100644 open_spiel/games/dots_and_boxes/dots_and_boxes.h create mode 100644 open_spiel/games/dots_and_boxes/dots_and_boxes_test.cc create mode 100644 open_spiel/integration_tests/playthroughs/dots_and_boxes.txt create mode 100644 open_spiel/python/examples/dotsandboxes_example.py diff --git a/open_spiel/games/CMakeLists.txt b/open_spiel/games/CMakeLists.txt index 91dd709a13..6af3133c2c 100644 --- a/open_spiel/games/CMakeLists.txt +++ b/open_spiel/games/CMakeLists.txt @@ -387,7 +387,7 @@ add_executable(deep_sea_test deep_sea/deep_sea_test.cc ${OPEN_SPIEL_OBJECTS} $) add_test(deep_sea_test deep_sea_test) -add_executable(dots_and_boxes_test dots_and_boxes_test.cc ${OPEN_SPIEL_OBJECTS} +add_executable(dots_and_boxes_test dots_and_boxes/dots_and_boxes_test.cc ${OPEN_SPIEL_OBJECTS} $) add_test(dots_and_boxes_test dots_and_boxes_test) diff --git a/open_spiel/games/dots_and_boxes/dots_and_boxes.cc b/open_spiel/games/dots_and_boxes/dots_and_boxes.cc new file mode 100644 index 0000000000..ac07c7b029 --- /dev/null +++ b/open_spiel/games/dots_and_boxes/dots_and_boxes.cc @@ -0,0 +1,695 @@ +// 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. +// +// Contributed by Wannes Meert, Giuseppe Marra, and Pieter Robberechts +// for the KU Leuven course Machine Learning: Project. + +#include "open_spiel/games/dots_and_boxes/dots_and_boxes.h" + +#include +#include +#include +#include +#include + +#include "open_spiel/spiel_utils.h" +#include "open_spiel/utils/tensor_view.h" + +namespace open_spiel { +namespace dots_and_boxes { +namespace { + +// Facts about the game. +const GameType kGameType{ + /*short_name=*/"dots_and_boxes", + /*long_name=*/"Dots and Boxes", + GameType::Dynamics::kSequential, + GameType::ChanceMode::kDeterministic, + GameType::Information::kPerfectInformation, + GameType::Utility::kZeroSum, //kGeneralSum, + 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=*/{ + {"num_rows", GameParameter(kDefaultNumRows)}, + {"num_cols", GameParameter(kDefaultNumCols)}, + {"utility_margin", GameParameter(kDefaultUtilityMargin)} + } +}; + +std::shared_ptr Factory(const GameParameters& params) { + return std::shared_ptr(new DotsAndBoxesGame(params)); +} + +REGISTER_SPIEL_GAME(kGameType, Factory); + +} // namespace + + +CellState PlayerToState(Player player) { + switch (player) { + case 0: + return CellState::kPlayer1; + case 1: + return CellState::kPlayer2; + default: + SpielFatalError(absl::StrCat("Invalid player id ", player)); + return CellState::kEmpty; + } +} + +std::string StateToString(CellState state) { + switch (state) { + case CellState::kEmpty: + return "."; + case CellState::kPlayer1: + return "1"; + case CellState::kPlayer2: + return "2"; + default: + SpielFatalError("Unknown state."); + } +} + +std::string OrientationToString(CellOrientation orientation) { + switch (orientation) { + case CellOrientation::kHorizontal: + return "h"; + case CellOrientation::kVertical: + return "v"; + default: + SpielFatalError("Unknown orientation."); + } +} + + + +// Move Methods ================================================================ + +Move::Move(int row, int col, CellOrientation orientation, int rows, int cols) { + row_ = row; + col_ = col; + orientation_ = orientation; + num_rows_ = rows; + num_cols_ = cols; +} + +Move::Move() { + row_ = 0; + col_ = 0; + orientation_ = CellOrientation::kVertical; + num_rows_ = 0; + num_cols_ = 0; +} + +Move::Move(Action action, int rows, int cols) { + num_rows_ = rows; + num_cols_ = cols; + int maxh = (num_rows_ + 1) * num_cols_; + int maxv = num_rows_ * (num_cols_ + 1); + if (action < maxh) { + // Horizontal + orientation_ = CellOrientation::kHorizontal; + row_ = action / num_cols_; + col_ = action % num_cols_; + //std::cout << "Action3[h," << row_ << "," << col_ << "] = " << action << std::endl; + } else { + // Vertical + action -= maxh; + orientation_ = CellOrientation::kVertical; + row_ = action / (num_cols_ + 1); + col_ = action % (num_cols_ + 1); + //std::cout << "Action3[v," << row_ << "," << col_ << "] = " << action << std::endl; + } + SPIEL_CHECK_LT(row_, num_rows_ + 1); + SPIEL_CHECK_LT(col_, num_cols_ + 1); +} + +void Move::Set(int row, int col, CellOrientation orientation) { + row_ = row; + col_ = col; + SPIEL_CHECK_LT(row_, num_rows_ + 1); + SPIEL_CHECK_LT(col_, num_cols_ + 1); + orientation_ = orientation; +} + +int Move::GetRow() const { return row_; } +int Move::GetCol() const { return col_; } +CellOrientation Move::GetOrientation() const { + return orientation_; +} + +Action Move::ActionId() { + // First bit is horizontal (0) or vertical (1) + Action action = 0; + int maxh = (num_rows_ + 1) * num_cols_; + if (orientation_ == CellOrientation::kHorizontal) { + action = row_ * num_cols_ + col_; + std::cout << "Action2[h," << row_ << "," << col_ << "] = " << action << std::endl; + } else { + action = maxh + row_ * (num_cols_ + 1) + col_; + std::cout << "Action2[v," << row_ << "," << col_ << "] = " << action << std::endl; + } + return action; +} + +int Move::GetCell() { + return row_ * (num_cols_ + 1) + col_; +} + +int Move::GetCellLeft() { + if (col_ == 0) { + return -1; + } + return row_ * (num_cols_ + 1) + (col_ - 1); +} + +int Move::GetCellRight() { + if (col_ == num_cols_) { + return -1; + } + return row_ * (num_cols_ + 1) + (col_ + 1); +} + +int Move::GetCellAbove() { + if (row_ == 0) { + return -1; + } + return (row_ - 1) * (num_cols_ + 1) + col_; +} + +int Move::GetCellBelow() { + if (row_ == num_rows_) { + return -1; + } + return (row_ + 1) * (num_cols_ + 1) + col_; +} + +int Move::GetCellAboveLeft() { + if (row_ == 0 || col_ == 0) { + return -1; + } + return (row_ - 1) * (num_cols_ + 1) + (col_ - 1); +} + +int Move::GetCellAboveRight() { + if (row_ == 0 || col_ == num_cols_) { + return -1; + } + return (row_ - 1) * (num_cols_ + 1) + (col_ + 1); +} + +int Move::GetCellBelowLeft() { + if (row_ == num_rows_ || col_ == 0) { + return -1; + } + return (row_ + 1) * (num_cols_ + 1) + (col_ - 1); +} + +int Move::GetCellBelowRight() { + if (row_ == num_rows_ || col_ == num_cols_) { + return -1; + } + return (row_ + 1) * (num_cols_ + 1) + (col_ + 1); +} + + +// DotsAndBoxesState Methods =================================================== + +void DotsAndBoxesState::DoApplyAction(Action action) { + Move move = Move(action, num_rows_, num_cols_); + int cell = move.GetCell(); + bool won_cell = false; + if (move.GetOrientation() == CellOrientation::kVertical) { + SPIEL_CHECK_EQ(v_[cell], CellState::kEmpty); + v_[cell] = PlayerToState(CurrentPlayer()); + + // Left + if (move.GetCol() > 0) { + if (v_[move.GetCellLeft()] != CellState::kEmpty + && h_[move.GetCellLeft()] != CellState::kEmpty + && h_[move.GetCellBelowLeft()] != CellState::kEmpty) { + won_cell = true; + p_[move.GetCellLeft()] = PlayerToState(CurrentPlayer()); + points_[current_player_]++; + } + } + + // Right + if (move.GetCol() < num_cols_) { + if (v_[move.GetCellRight()] != CellState::kEmpty + && h_[move.GetCellBelow()] != CellState::kEmpty + && h_[cell] != CellState::kEmpty) { + won_cell = true; + p_[cell] = PlayerToState(CurrentPlayer()); + points_[current_player_]++; + } + } + + } else { // move.GetOrientation() == kHorizontal + SPIEL_CHECK_EQ(h_[cell], CellState::kEmpty); + h_[cell] = PlayerToState(CurrentPlayer()); + + // Above + if (move.GetRow() > 0) { + if (v_[move.GetCellAbove()] != CellState::kEmpty + && v_[move.GetCellAboveRight()] != CellState::kEmpty + && h_[move.GetCellAbove()] != CellState::kEmpty) { + won_cell = true; + p_[move.GetCellAbove()] = PlayerToState(CurrentPlayer()); + points_[current_player_]++; + } + } + // Below + if (move.GetRow() < num_rows_) { + if (v_[cell] != CellState::kEmpty + && v_[move.GetCellRight()] != CellState::kEmpty + && h_[move.GetCellBelow()] != CellState::kEmpty) { + won_cell = true; + p_[cell] = PlayerToState(CurrentPlayer()); + points_[current_player_]++; + } + } + } + + if (Wins(current_player_)) { + outcome_ = current_player_; + } + if (!won_cell) { + // If box is scored, current player keeps the turn + current_player_ = 1 - current_player_; + } + num_moves_ += 1; +} + +std::vector DotsAndBoxesState::LegalActions() const { + if (IsTerminal()) return {}; + std::vector actions; + int action = 0; + Move move; + move.SetRowsCols(num_rows_, num_cols_); + int maxh = (num_rows_ + 1) * num_cols_; + int maxv = num_rows_ * (num_cols_ + 1); + // Horizontal lines + for (int row=0; row <= num_rows_; ++row) { + for (int col = 0; col < num_cols_; ++col) { + move.Set(row, col, CellOrientation::kHorizontal); + // std::cout << "Action[h," << row << "," << col << "]"; + if (h_[move.GetCell()] == CellState::kEmpty) { + actions.push_back(action); + // std::cout << " = "; + } else { + // std::cout << " x "; + } + // std::cout << action << std::endl; + action++; + } + } + assert(action == maxh); + // Vertical lines + for (int row=0; row < num_rows_; ++row) { + for (int col = 0; col <= num_cols_; ++col) { + move.Set(row, col, CellOrientation::kVertical); + // std::cout << "Action[v," << row << "," << col << "]"; + if (v_[move.GetCell()] == CellState::kEmpty) { + actions.push_back(action); + // std::cout << " = "; + } else { + // std::cout << " x "; + } + // std::cout << action << std::endl; + action++; + } + } + assert(action == maxh + maxv); + return actions; +} + +std::string DotsAndBoxesState::DbnString() const { + // A string representing which lines have been set. + // This corresponds to an unscored state representation + // (Barker and Korf 2012). + // For a scored state, use the ObservationTensor function. + std::string str; + int cell = 0; + int idx = 0; + for (int row=0; row points_[1]; + } else { + return points_[0] < points_[1]; + } + } + return false; +} + +bool DotsAndBoxesState::IsFull() const { + return num_moves_ == (num_rows_ + 1) * num_cols_ + num_rows_ * (num_cols_ + 1); +} + +DotsAndBoxesState::DotsAndBoxesState(std::shared_ptr game, + int num_rows, int num_cols, bool utility_margin) : + State(game), + num_rows_(num_rows), + num_cols_(num_cols), + num_cells_((1 + num_rows) * (1 + num_cols)), + utility_margin_(utility_margin) { + SPIEL_CHECK_GE(num_rows_, 1); + /* SPIEL_CHECK_LE(num_rows_, 1000); */ + SPIEL_CHECK_GE(num_cols_, 1); + /* SPIEL_CHECK_LE(num_cols_, 1000); */ + h_.resize(num_cells_); + v_.resize(num_cells_); + p_.resize(num_cells_); + std::fill(begin(h_), end(h_), CellState::kEmpty); + std::fill(begin(v_), end(v_), CellState::kEmpty); + std::fill(begin(p_), end(p_), CellState::kEmpty); + std::fill(begin(points_), end(points_), 0); +} + +// Create initial board from the Dots-and-Boxes Notation. +// A vector with: +// [b | for r in [0,num_rows+1], for c in [0,num_cols]: +// b=1 if horizontal line[r,c] set else 0] + +// [b | for r in [0,num_rows_], for c in [0,num_cols+1]: +// b=1 if vertical line[r,c] set else 0] +DotsAndBoxesState::DotsAndBoxesState(std::shared_ptr game, + int num_rows, int num_cols, bool utility_margin, + const std::string& dbn) : + State(game), + num_rows_(num_rows), + num_cols_(num_cols), + num_cells_((1 + num_rows) * (1 + num_cols)), + utility_margin_(utility_margin) { + /* std::cout << "Init dots and boxes state with dbn\n"; */ + SPIEL_CHECK_GE(num_rows_, 1); + /* SPIEL_CHECK_LE(num_rows_, 1000); */ + SPIEL_CHECK_GE(num_cols_, 1); + /* SPIEL_CHECK_LE(num_cols_, 1000); */ + h_.resize(num_cells_); + v_.resize(num_cells_); + p_.resize(num_cells_); + std::fill(begin(h_), end(h_), CellState::kEmpty); + std::fill(begin(v_), end(v_), CellState::kEmpty); + std::fill(begin(p_), end(p_), CellState::kEmpty); + std::fill(begin(points_), end(points_), 0); + int cell = 0; + int idx = 0; + for (int row=0; row DotsAndBoxesState::Returns() const { + if (utility_margin_) { + if (IsTerminal()) { + double margin = (double)(points_[0] - points_[1]); + return {margin, -margin}; + } else { + return {0.0, 0.0}; + } + } else { + if (Wins(Player{0})) { + return {1.0, -1.0}; + } else if (Wins(Player{1})) { + return {-1.0, 1.0}; + } else { + // Game is not finished + return {0.0, 0.0}; + } + } +} + +std::string DotsAndBoxesState::InformationStateString(Player player) const { + // Cannot be used when starting from a non-empty initial state. + // If the game is started from a non-empty initial state + // there are no previous moves and thus the history is empty. + // And moves cannot be inferred as different orderings can lead + // to different scores for the players. + SPIEL_CHECK_GE(player, 0); + SPIEL_CHECK_LT(player, num_players_); + return HistoryString(); +} + +std::string DotsAndBoxesState::ObservationString(Player player) const { + SPIEL_CHECK_GE(player, 0); + SPIEL_CHECK_LT(player, num_players_); + return ToString(); +} + +void DotsAndBoxesState::ObservationTensor(Player player, + absl::Span values) const { + SPIEL_CHECK_GE(player, 0); + SPIEL_CHECK_LT(player, num_players_); + + // Treat `values` as a 3-d tensor. + TensorView<3> view(values, + {/*cellstates=*/3, + num_cells_, + /*part of cell (h, v, p)=*/3}, true); + for (int cell = 0; cell < num_cells_; ++cell) { + view[{static_cast(h_[cell]), cell, 0}] = 1.0; + view[{static_cast(v_[cell]), cell, 1}] = 1.0; + view[{static_cast(p_[cell]), cell, 2}] = 1.0; + } +} + +void DotsAndBoxesState::UndoAction(Player player, Action action) { + Move move(action, num_rows_, num_cols_); + int cell = move.GetCell(); + if (p_[cell] != CellState::kEmpty) { + points_[current_player_]--; + } + h_[cell] = CellState::kEmpty; + v_[cell] = CellState::kEmpty; + p_[cell] = CellState::kEmpty; + current_player_ = player; + outcome_ = kInvalidPlayer; + num_moves_ -= 1; + history_.pop_back(); + --move_number_; +} + +std::unique_ptr DotsAndBoxesState::Clone() const { + return std::unique_ptr(new DotsAndBoxesState(*this)); +} + +std::string DotsAndBoxesState::StateToStringH(CellState state, int row, int col) const { + if (row == 0 && col == 0) { + if (state == CellState::kEmpty) { + return "┌╴ ╶"; + } else { + return "┌───"; + } + } + if (row == num_rows_ && col == 0) { + if (state == CellState::kEmpty) { + return "└╴ ╶"; + } else { + return "└───"; + } + } + if (row == 0 && col == num_cols_) { + return "┐"; + } + if (row == num_rows_ && col == num_cols_) { + return "┘"; + } + if (col == num_cols_) { + return "┤"; + } + if (col == 0) { + if (state == CellState::kEmpty) { + return "├╴ ╶"; + } else { + return "├───"; + } + } + if (row == 0) { + if (state == CellState::kEmpty) { + return "┬╴ ╶"; + } else { + return "┬───"; + } + } + if (row == num_rows_) { + if (state == CellState::kEmpty) { + return "┴╴ ╶"; + } else { + return "┴───"; + } + } + if (state == CellState::kEmpty) { + return "┼╴ ╶"; + } else { + return "┼───"; + } +} + +std::string DotsAndBoxesState::StateToStringV(CellState state, int row, int col) const { + if (state == CellState::kEmpty) { + return " ";//"┊"; + } else { + return "│"; + } +} + +std::string DotsAndBoxesState::StateToStringP(CellState state, int row, int col) const { + if (state == CellState::kEmpty) { + return " "; + } + if (state == CellState::kPlayer1) { + return " 1 "; + } + if (state == CellState::kPlayer2) { + return " 2 "; + } + return " x "; +} + +DotsAndBoxesGame::DotsAndBoxesGame(const GameParameters& params) + : Game(kGameType, params), + num_rows_(ParameterValue("num_rows", kDefaultNumRows)), + num_cols_(ParameterValue("num_cols", kDefaultNumCols)), + num_cells_((1 + ParameterValue("num_rows", kDefaultNumRows)) * (1 + ParameterValue("num_cols", kDefaultNumCols))), + utility_margin_(ParameterValue("utility_margin", kDefaultUtilityMargin)) +{ + //std::cout << "Init dots and boxes game\n"; + //if (utility_margin_) { + // game_type_.utility = GameType::Utility::kZeroSum; + //} +} + + +double DotsAndBoxesGame::MinUtility() const { + // If win/lose is the utility, this is -1. + if (utility_margin_) { + return -num_rows_ * num_cols_; + } else { + return -1; + } +} + +absl::optional DotsAndBoxesGame::UtilitySum() const { + return 0; + //SpielFatalError( + // "Called `UtilitySum()` on a general Dots-and-Boxes game: " + // "set `utility_margin` to true for a zero-sum game."); +} + +double DotsAndBoxesGame::MaxUtility() const { + if (utility_margin_) { + return num_rows_ * num_cols_; + } else { + return 1; + } +} + +} // namespace dots_and_boxes +} // namespace open_spiel diff --git a/open_spiel/games/dots_and_boxes/dots_and_boxes.h b/open_spiel/games/dots_and_boxes/dots_and_boxes.h new file mode 100644 index 0000000000..93921667d8 --- /dev/null +++ b/open_spiel/games/dots_and_boxes/dots_and_boxes.h @@ -0,0 +1,193 @@ +// 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. +// +// Contributed by Wannes Meert, Giuseppe Marra, and Pieter Robberechts +// for the KU Leuven course Machine Learning: Project. + + +#ifndef OPEN_SPIEL_GAMES_DOTS_AND_BOXES_H_ +#define OPEN_SPIEL_GAMES_DOTS_AND_BOXES_H_ + +#include +#include +#include +#include +#include + +#include "open_spiel/spiel.h" + +// Dots and Boxes: +// https://en.wikipedia.org/wiki/Dots_and_Boxes +// +// Parameters: +// - num_rows: Number of rows on the board +// - num_cols: Number of columns on the board +// - utility_margin: Return as payoff the margin achieved (if true) or +// return -1/0/1 to indicate win/tie/loss. + +namespace open_spiel { +namespace dots_and_boxes { + +// Constants. +inline constexpr int kNumPlayers = 2; +inline constexpr int kDefaultNumRows = 2; +inline constexpr int kDefaultNumCols = 2; +inline constexpr int kMaskSize = 10; +inline constexpr int kMask = (1 << kMaskSize) - 1; +inline constexpr bool kDefaultUtilityMargin = false; + +// State of a cell. +enum class CellState { + kEmpty, // Not set + kPlayer1, // Set by player 1 + kPlayer2, // Set by player 2 + kSet // Set by default start state +}; + +enum class CellOrientation { + kHorizontal, // = 0 + kVertical, // = 1 +}; + + +class Move { + public: + Move(void); + Move(int row, int col, CellOrientation orientation, int rows, int cols); + explicit Move(Action action, int rows, int cols); + + void SetRowsCols(int rows, int cols) {num_rows_ = rows; num_cols_ = cols;} + void Set(int row, int col, CellOrientation orientation); + int GetRow() const; + int GetCol() const; + CellOrientation GetOrientation() const; + + Action ActionId(); + int GetCell(); + int GetCellLeft(); + int GetCellRight(); + int GetCellAbove(); + int GetCellBelow(); + int GetCellAboveLeft(); + int GetCellAboveRight(); + int GetCellBelowLeft(); + int GetCellBelowRight(); + + protected: + int row_; + int col_; + CellOrientation orientation_; + int num_rows_; + int num_cols_; +}; + + +// State of an in-play game. +class DotsAndBoxesState : public State { + public: + DotsAndBoxesState(std::shared_ptr game, + int num_rows, int num_cols, bool utility_margin); + DotsAndBoxesState(std::shared_ptr game, + int num_rows, int num_cols, bool utility_margin, const std::string& dbn); + DotsAndBoxesState(const DotsAndBoxesState&) = default; + DotsAndBoxesState& operator=(const DotsAndBoxesState&) = default; + + Player CurrentPlayer() const override { + return IsTerminal() ? kTerminalPlayerId : current_player_; + } + std::string DbnString() const; + std::string ActionToString(Player player, Action action_id) const override; + std::string ToString() const override; + bool IsTerminal() const override; + 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 move) override; + std::vector LegalActions() const override; + Player outcome() const { return outcome_; } + + std::string StateToStringV(CellState state, int row, int col) const; + std::string StateToStringH(CellState state, int row, int col) const; + std::string StateToStringP(CellState state, int row, int col) const; + + void SetCurrentPlayer(Player player) { current_player_ = player; } + + protected: + std::vector v_; // Who set the vertical line + std::vector h_; // Who set the horizontal line + std::vector p_; // Who won the cell + void DoApplyAction(Action move) override; + + private: + bool Wins(Player player) const; + bool IsFull() const; + Player current_player_ = 0; // Player zero goes first + Player outcome_ = kInvalidPlayer; + int num_moves_ = 0; + const int num_rows_; + const int num_cols_; + const int num_cells_; + std::array points_; + const bool utility_margin_; +}; + +// Game object. +class DotsAndBoxesGame : public Game { + public: + explicit DotsAndBoxesGame(const GameParameters& params); + int NumDistinctActions() const override { + return (num_rows_ + 1) * num_cols_ + num_rows_ * (num_cols_ + 1); + } + std::unique_ptr NewInitialState() const override { + return std::unique_ptr(new DotsAndBoxesState(shared_from_this(), + num_rows_, num_cols_, + utility_margin_)); + } + std::unique_ptr NewInitialState(const std::string& str) const override { + return absl::make_unique(shared_from_this(), + num_rows_, num_cols_, + utility_margin_, str); + } + int NumPlayers() const override { return kNumPlayers; } + double MinUtility() const override; + absl::optional UtilitySum() const override; + double MaxUtility() const override; + std::vector ObservationTensorShape() const override { + return {3, num_cells_, 3}; + } + int MaxGameLength() const override { + return (num_rows_ + 1) * num_cols_ + num_cols_ * (num_rows_ + 1); + } + private: + const int num_rows_; + const int num_cols_; + const int num_cells_; + const bool utility_margin_; +}; + +// CellState PlayerToState(Player player); +std::string StateToString(CellState state); +std::string OrientationToString(CellOrientation orientation); + +inline std::ostream& operator<<(std::ostream& stream, const CellState& state) { + return stream << StateToString(state); +} + +} // namespace dots_and_boxes +} // namespace open_spiel + +#endif // OPEN_SPIEL_GAMES_DOTS_AND_BOXES_H_ diff --git a/open_spiel/games/dots_and_boxes/dots_and_boxes_test.cc b/open_spiel/games/dots_and_boxes/dots_and_boxes_test.cc new file mode 100644 index 0000000000..f036c4ae71 --- /dev/null +++ b/open_spiel/games/dots_and_boxes/dots_and_boxes_test.cc @@ -0,0 +1,42 @@ +// 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. +// +// Contributed by Wannes Meert, Giuseppe Marra, and Pieter Robberechts +// for the KU Leuven course Machine Learning: Project. + +#include "open_spiel/spiel.h" +#include "open_spiel/tests/basic_tests.h" + +#include + +namespace open_spiel { +namespace dots_and_boxes { +namespace { + +namespace testing = open_spiel::testing; + +void BasicDotsAndBoxesTests() { + std::cout << "Test dots and boxes\n"; + testing::LoadGameTest("dots_and_boxes"); + testing::NoChanceOutcomesTest(*LoadGame("dots_and_boxes")); + testing::RandomSimTest(*LoadGame("dots_and_boxes"), 100); +} + +} // namespace +} // namespace dots_and_boxes +} // namespace open_spiel + +int main(int argc, char** argv) { + open_spiel::dots_and_boxes::BasicDotsAndBoxesTests(); +} diff --git a/open_spiel/integration_tests/playthroughs/dots_and_boxes.txt b/open_spiel/integration_tests/playthroughs/dots_and_boxes.txt new file mode 100644 index 0000000000..ab946f68fa --- /dev/null +++ b/open_spiel/integration_tests/playthroughs/dots_and_boxes.txt @@ -0,0 +1,358 @@ +game: dots_and_boxes + +GameType.chance_mode = ChanceMode.DETERMINISTIC +GameType.dynamics = Dynamics.SEQUENTIAL +GameType.information = Information.PERFECT_INFORMATION +GameType.long_name = "Dots and Boxes" +GameType.max_num_players = 2 +GameType.min_num_players = 2 +GameType.parameter_specification = ["num_cols", "num_rows", "utility_margin"] +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 = "dots_and_boxes" +GameType.utility = Utility.ZERO_SUM + +NumDistinctActions() = 12 +PolicyTensorShape() = [12] +MaxChanceOutcomes() = 0 +GetParameters() = {num_cols=2,num_rows=2,utility_margin=False} +NumPlayers() = 2 +MinUtility() = -1.0 +MaxUtility() = 1.0 +UtilitySum() = 0.0 +ObservationTensorShape() = [3, 9, 3] +ObservationTensorLayout() = TensorLayout.CHW +ObservationTensorSize() = 81 +MaxGameLength() = 12 +ToString() = "dots_and_boxes()" + +# State 0 +# ┌╴ ╶┬╴ ╶┐ +# +# ├╴ ╶┼╴ ╶┤ +# +# └╴ ╶┴╴ ╶┘ +IsTerminal() = False +History() = [] +HistoryString() = "" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +InformationStateString(0) = "" +InformationStateString(1) = "" +ObservationString(0) = "┌╴ ╶┬╴ ╶┐\n \n├╴ ╶┼╴ ╶┤\n \n└╴ ╶┴╴ ╶┘\n" +ObservationString(1) = "┌╴ ╶┬╴ ╶┐\n \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] +StringLegalActions() = ["P1(h,0,0)", "P1(h,0,1)", "P1(h,1,0)", "P1(h,1,1)", "P1(h,2,0)", "P1(h,2,1)", "P1(v,0,0)", "P1(v,0,1)", "P1(v,0,2)", "P1(v,1,0)", "P1(v,1,1)", "P1(v,1,2)"] + +# Apply action "P1(v,1,1)" +action: 10 + +# State 1 +# ┌╴ ╶┬╴ ╶┐ +# +# ├╴ ╶┼╴ ╶┤ +# │ +# └╴ ╶┴╴ ╶┘ +IsTerminal() = False +History() = [10] +HistoryString() = "10" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +InformationStateString(0) = "10" +InformationStateString(1) = "10" +ObservationString(0) = "┌╴ ╶┬╴ ╶┐\n \n├╴ ╶┼╴ ╶┤\n │ \n└╴ ╶┴╴ ╶┘\n" +ObservationString(1) = "┌╴ ╶┬╴ ╶┐\n \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, 11] +StringLegalActions() = ["P2(h,0,0)", "P2(h,0,1)", "P2(h,1,0)", "P2(h,1,1)", "P2(h,2,0)", "P2(h,2,1)", "P2(v,0,0)", "P2(v,0,1)", "P2(v,0,2)", "P2(v,1,0)", "P2(v,1,2)"] + +# Apply action "P2(h,0,1)" +action: 1 + +# State 2 +# ┌╴ ╶┬───┐ +# +# ├╴ ╶┼╴ ╶┤ +# │ +# └╴ ╶┴╴ ╶┘ +IsTerminal() = False +History() = [10, 1] +HistoryString() = "10, 1" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +InformationStateString(0) = "10, 1" +InformationStateString(1) = "10, 1" +ObservationString(0) = "┌╴ ╶┬───┐\n \n├╴ ╶┼╴ ╶┤\n │ \n└╴ ╶┴╴ ╶┘\n" +ObservationString(1) = "┌╴ ╶┬───┐\n \n├╴ ╶┼╴ ╶┤\n │ \n└╴ ╶┴╴ ╶┘\n" +ObservationTensor(0): +◉◉◉ ◯◯◯ ◯◯◯ +◯◉◉ ◯◯◯ ◉◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +ObservationTensor(1): +◉◉◉ ◯◯◯ ◯◯◯ +◯◉◉ ◯◯◯ ◉◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 2, 3, 4, 5, 6, 7, 8, 9, 11] +StringLegalActions() = ["P1(h,0,0)", "P1(h,1,0)", "P1(h,1,1)", "P1(h,2,0)", "P1(h,2,1)", "P1(v,0,0)", "P1(v,0,1)", "P1(v,0,2)", "P1(v,1,0)", "P1(v,1,2)"] + +# Apply action "P1(v,0,2)" +action: 8 + +# State 3 +# ┌╴ ╶┬───┐ +# │ +# ├╴ ╶┼╴ ╶┤ +# │ +# └╴ ╶┴╴ ╶┘ +IsTerminal() = False +History() = [10, 1, 8] +HistoryString() = "10, 1, 8" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +InformationStateString(0) = "10, 1, 8" +InformationStateString(1) = "10, 1, 8" +ObservationString(0) = "┌╴ ╶┬───┐\n │\n├╴ ╶┼╴ ╶┤\n │ \n└╴ ╶┴╴ ╶┘\n" +ObservationString(1) = "┌╴ ╶┬───┐\n │\n├╴ ╶┼╴ ╶┤\n │ \n└╴ ╶┴╴ ╶┘\n" +ObservationTensor(0): +◉◉◉ ◯◯◯ ◯◯◯ +◯◉◉ ◯◯◯ ◉◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +ObservationTensor(1): +◉◉◉ ◯◯◯ ◯◯◯ +◯◉◉ ◯◯◯ ◉◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 2, 3, 4, 5, 6, 7, 9, 11] +StringLegalActions() = ["P2(h,0,0)", "P2(h,1,0)", "P2(h,1,1)", "P2(h,2,0)", "P2(h,2,1)", "P2(v,0,0)", "P2(v,0,1)", "P2(v,1,0)", "P2(v,1,2)"] + +# Apply action "P2(v,1,2)" +action: 11 + +# State 4 +# ┌╴ ╶┬───┐ +# │ +# ├╴ ╶┼╴ ╶┤ +# │ │ +# └╴ ╶┴╴ ╶┘ +IsTerminal() = False +History() = [10, 1, 8, 11] +HistoryString() = "10, 1, 8, 11" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +InformationStateString(0) = "10, 1, 8, 11" +InformationStateString(1) = "10, 1, 8, 11" +ObservationString(0) = "┌╴ ╶┬───┐\n │\n├╴ ╶┼╴ ╶┤\n │ │\n└╴ ╶┴╴ ╶┘\n" +ObservationString(1) = "┌╴ ╶┬───┐\n │\n├╴ ╶┼╴ ╶┤\n │ │\n└╴ ╶┴╴ ╶┘\n" +ObservationTensor(0): +◉◉◉ ◯◯◯ ◯◯◯ +◯◉◉ ◯◯◯ ◉◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◯◉ ◯◯◯ ◯◉◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +ObservationTensor(1): +◉◉◉ ◯◯◯ ◯◯◯ +◯◉◉ ◯◯◯ ◉◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◯◉ ◯◯◯ ◯◉◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 2, 3, 4, 5, 6, 7, 9] +StringLegalActions() = ["P1(h,0,0)", "P1(h,1,0)", "P1(h,1,1)", "P1(h,2,0)", "P1(h,2,1)", "P1(v,0,0)", "P1(v,0,1)", "P1(v,1,0)"] + +# Apply action "P1(v,1,0)" +action: 9 + +# State 5 +# ┌╴ ╶┬───┐ +# │ +# ├╴ ╶┼╴ ╶┤ +# │ │ │ +# └╴ ╶┴╴ ╶┘ +IsTerminal() = False +History() = [10, 1, 8, 11, 9] +HistoryString() = "10, 1, 8, 11, 9" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +InformationStateString(0) = "10, 1, 8, 11, 9" +InformationStateString(1) = "10, 1, 8, 11, 9" +ObservationString(0) = "┌╴ ╶┬───┐\n │\n├╴ ╶┼╴ ╶┤\n│ │ │\n└╴ ╶┴╴ ╶┘\n" +ObservationString(1) = "┌╴ ╶┬───┐\n │\n├╴ ╶┼╴ ╶┤\n│ │ │\n└╴ ╶┴╴ ╶┘\n" +ObservationTensor(0): +◉◉◉ ◯◯◯ ◯◯◯ +◯◉◉ ◯◯◯ ◉◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◯◉ ◯◯◯ ◯◉◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +ObservationTensor(1): +◉◉◉ ◯◯◯ ◯◯◯ +◯◉◉ ◯◯◯ ◉◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◉◯◉ ◯◯◯ ◯◉◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 2, 3, 4, 5, 6, 7] +StringLegalActions() = ["P2(h,0,0)", "P2(h,1,0)", "P2(h,1,1)", "P2(h,2,0)", "P2(h,2,1)", "P2(v,0,0)", "P2(v,0,1)"] + +# Apply action "P2(h,1,1)" +action: 3 + +# State 6 +# Apply action "P1(h,2,1)" +action: 5 + +# State 7 +# Apply action "P1(h,0,0)" +action: 0 + +# State 8 +# Apply action "P2(h,1,0)" +action: 2 + +# State 9 +# Apply action "P1(v,0,1)" +action: 7 + +# State 10 +# Apply action "P1(v,0,0)" +action: 6 + +# State 11 +# Apply action "P1(h,2,0)" +action: 4 + +# State 12 +# ┌───┬───┐ +# │ 1 │ 1 │ +# ├───┼───┤ +# │ 1 │ 1 │ +# └───┴───┘ +IsTerminal() = True +History() = [10, 1, 8, 11, 9, 3, 5, 0, 2, 7, 6, 4] +HistoryString() = "10, 1, 8, 11, 9, 3, 5, 0, 2, 7, 6, 4" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = -4 +InformationStateString(0) = "10, 1, 8, 11, 9, 3, 5, 0, 2, 7, 6, 4" +InformationStateString(1) = "10, 1, 8, 11, 9, 3, 5, 0, 2, 7, 6, 4" +ObservationString(0) = "┌───┬───┐\n│ 1 │ 1 │\n├───┼───┤\n│ 1 │ 1 │\n└───┴───┘\n" +ObservationString(1) = "┌───┬───┐\n│ 1 │ 1 │\n├───┼───┤\n│ 1 │ 1 │\n└───┴───┘\n" +ObservationTensor(0): +◯◯◯ ◉◉◉ ◯◯◯ +◯◯◯ ◯◉◉ ◉◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◯◯◯ ◯◉◉ ◉◯◯ +◯◯◯ ◯◉◉ ◉◯◯ +◉◯◉ ◯◯◯ ◯◉◯ +◯◉◉ ◉◯◯ ◯◯◯ +◯◉◉ ◉◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +ObservationTensor(1): +◯◯◯ ◉◉◉ ◯◯◯ +◯◯◯ ◯◉◉ ◉◯◯ +◉◯◉ ◯◉◯ ◯◯◯ +◯◯◯ ◯◉◉ ◉◯◯ +◯◯◯ ◯◉◉ ◉◯◯ +◉◯◉ ◯◯◯ ◯◉◯ +◯◉◉ ◉◯◯ ◯◯◯ +◯◉◉ ◉◯◯ ◯◯◯ +◉◉◉ ◯◯◯ ◯◯◯ +Rewards() = [1, -1] +Returns() = [1, -1] diff --git a/open_spiel/python/examples/dotsandboxes_example.py b/open_spiel/python/examples/dotsandboxes_example.py new file mode 100644 index 0000000000..5e564c5326 --- /dev/null +++ b/open_spiel/python/examples/dotsandboxes_example.py @@ -0,0 +1,94 @@ +# 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. +# +# Contributed by Wannes Meert, Giuseppe Marra, and Pieter Robberechts +# for the KU Leuven course Machine Learning: Project. + + +"""Python spiel example.""" + +from absl import app +from absl import flags +import numpy as np + +from open_spiel.python.bots import human +from open_spiel.python.bots import uniform_random +import pyspiel + +FLAGS = flags.FLAGS + +flags.DEFINE_integer("seed", 12761381, "The seed to use for the RNG.") + +# Supported types of players: "random", "human", "check_call", "fold" +flags.DEFINE_string("player0", "random", "Type of the agent for player 0.") +flags.DEFINE_string("player1", "random", "Type of the agent for player 1.") + + +def LoadAgent(agent_type, game, player_id, rng): + """Return a bot based on the agent type.""" + if agent_type == "random": + return uniform_random.UniformRandomBot(player_id, rng) + elif agent_type == "human": + return human.HumanBot() + else: + raise RuntimeError("Unrecognized agent type: {}".format(agent_type)) + + +def main(_): + rng = np.random.RandomState(FLAGS.seed) + games_list = pyspiel.registered_names() + assert "dots_and_boxes" in games_list + + game_string = "dots_and_boxes(num_rows=2,num_cols=2)" + print("Creating game: {}".format(game_string)) + game = pyspiel.load_game(game_string) + + agents = [ + LoadAgent(FLAGS.player0, game, 0, rng), + LoadAgent(FLAGS.player1, game, 1, rng) + ] + + state = game.new_initial_state() + + # Print the initial state + print("INITIAL STATE") + print(str(state)) + + while not state.is_terminal(): + current_player = state.current_player() + # Decision node: sample action for the single current player + legal_actions = state.legal_actions() + for action in legal_actions: + print("Legal action: {} ({})".format( + state.action_to_string(current_player, action), action)) + action = agents[current_player].step(state) + action_string = state.action_to_string(current_player, action) + print("Player ", current_player, ", chose action: ", + action_string) + state.apply_action(action) + + print("") + print("NEXT STATE:") + print(str(state)) + if not state.is_terminal(): + print(str(state.observation_tensor())) + + # Game is now done. Print utilities for each player + returns = state.returns() + for pid in range(game.num_players()): + print("Utility for player {} is {}".format(pid, returns[pid])) + + +if __name__ == "__main__": + app.run(main) From c256c41d4afa0dc2705be017f37075c7f734fc14 Mon Sep 17 00:00:00 2001 From: wannesm Date: Tue, 8 Aug 2023 22:52:18 +0200 Subject: [PATCH 3/3] pybind11 files for dots and boxes --- .../python/pybind11/games_dots_and_boxes.cc | 43 +++++++++++++++++++ .../python/pybind11/games_dots_and_boxes.h | 25 +++++++++++ 2 files changed, 68 insertions(+) create mode 100644 open_spiel/python/pybind11/games_dots_and_boxes.cc create mode 100644 open_spiel/python/pybind11/games_dots_and_boxes.h diff --git a/open_spiel/python/pybind11/games_dots_and_boxes.cc b/open_spiel/python/pybind11/games_dots_and_boxes.cc new file mode 100644 index 0000000000..e9c753ac2c --- /dev/null +++ b/open_spiel/python/pybind11/games_dots_and_boxes.cc @@ -0,0 +1,43 @@ +// Copyright 2021 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/python/pybind11/games_dots_and_boxes.h" + +#include "open_spiel/games/dots_and_boxes/dots_and_boxes.h" +#include "open_spiel/spiel.h" +#include "open_spiel/python/pybind11/pybind11.h" + +namespace py = ::pybind11; +using open_spiel::Game; +using open_spiel::State; +using open_spiel::dots_and_boxes::DotsAndBoxesState; + +PYBIND11_SMART_HOLDER_TYPE_CASTERS(DotsAndBoxesState); + +void open_spiel::init_pyspiel_games_dots_and_boxes(py::module& m) { + py::classh(m, "DotsAndBoxesState") + .def("dbn_string", &DotsAndBoxesState::DbnString) + // .def("debug_string", &DotsAndBoxesState::DebugString) + //.def("parse_move_to_action", &DotsAndBoxesState::ParseMoveToAction) + // Pickle support + .def(py::pickle( + [](const DotsAndBoxesState& state) { // __getstate__ + return SerializeGameAndState(*state.GetGame(), state); + }, + [](const std::string& data) { // __setstate__ + std::pair, std::unique_ptr> + game_and_state = DeserializeGameAndState(data); + return dynamic_cast(game_and_state.second.release()); + })); +} diff --git a/open_spiel/python/pybind11/games_dots_and_boxes.h b/open_spiel/python/pybind11/games_dots_and_boxes.h new file mode 100644 index 0000000000..a15691bfa4 --- /dev/null +++ b/open_spiel/python/pybind11/games_dots_and_boxes.h @@ -0,0 +1,25 @@ +// Copyright 2021 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_PYTHON_PYBIND11_GAMES_DOTS_AND_BOXES_H_ +#define OPEN_SPIEL_PYTHON_PYBIND11_GAMES_DOTS_AND_BOXES_H_ + +#include "open_spiel/python/pybind11/pybind11.h" + +// Initialze the Python interface for games/negotiation. +namespace open_spiel { +void init_pyspiel_games_dots_and_boxes(::pybind11::module &m); +} + +#endif // OPEN_SPIEL_PYTHON_PYBIND11_GAMES_DOTS_AND_BOXES_H_