diff --git a/docs/games.md b/docs/games.md index cb145568c3..14b49eb648 100644 --- a/docs/games.md +++ b/docs/games.md @@ -33,6 +33,7 @@ Status | Game 🟒 | [Dots and Boxes](https://en.wikipedia.org/wiki/Dots_and_boxes) | 2 | βœ… | βœ… | Players put lines between dots to form boxes to get points. πŸ”Ά | [Dou Dizhu](https://en.wikipedia.org/wiki/Dou_dizhu) | 3 | ❌ | ❌ | A three-player games where one player (dizhu) plays against a team of two (peasants). πŸ”Ά | [Euchre](https://en.wikipedia.org/wiki/Euchre) | 4 | ❌ | ❌ | Trick-taking card game where players compete in pairs. +πŸ”Ά | [EinStein wΓΌrfelt nicht!](https://en.wikipedia.org/wiki/EinStein_w%C3%BCrfelt_nicht!) | 2 | ❌ | βœ… | Players control 6 numbered cubes, selected randomly by the roll of a die. The player that gets on the opponent's board corner, or captures all the opponent's cubes wins. 🟒 | [First-price Sealed-Bid Auction](https://en.wikipedia.org/wiki/First-price_sealed-bid_auction) | 2-10 | ❌ | ❌ | Agents submit bids simultaneously; highest bid wins, and that's the price paid. 🟒 | [Gin Rummy](https://en.wikipedia.org/wiki/Gin_rummy) | 2 | ❌ | ❌ | Players score points by forming specific sets with the cards in their hands. 🟒 | [Go](https://en.wikipedia.org/wiki/Go_\(game\)) | 2 | βœ… | βœ… | Players place tokens on the board with the goal of encircling territory. diff --git a/open_spiel/games/CMakeLists.txt b/open_spiel/games/CMakeLists.txt index 3500805a66..c2419ae6d8 100644 --- a/open_spiel/games/CMakeLists.txt +++ b/open_spiel/games/CMakeLists.txt @@ -71,6 +71,8 @@ set(GAME_SOURCES efg_game/efg_game.h efg_game/efg_game_data.cc efg_game/efg_game_data.h + einstein_wurfelt_nicht/einstein_wurfelt_nicht.cc + einstein_wurfelt_nicht/einstein_wurfelt_nicht.h euchre/euchre.cc euchre/euchre.h first_sealed_auction/first_sealed_auction.cc @@ -422,6 +424,10 @@ add_executable(efg_game_test efg_game/efg_game_test.cc ${OPEN_SPIEL_OBJECTS} $) add_test(efg_game_test efg_game_test) +add_executable(einstein_wurfelt_nicht_test einstein_wurfelt_nicht/einstein_wurfelt_nicht_test.cc ${OPEN_SPIEL_OBJECTS} + $) +add_test(einstein_wurfelt_nicht_test einstein_wurfelt_nicht_test) + add_executable(euchre_test euchre/euchre_test.cc ${OPEN_SPIEL_OBJECTS} $) add_test(euchre_test euchre_test) diff --git a/open_spiel/games/einstein_wurfelt_nicht/einstein_wurfelt_nicht.cc b/open_spiel/games/einstein_wurfelt_nicht/einstein_wurfelt_nicht.cc new file mode 100644 index 0000000000..0dae508e64 --- /dev/null +++ b/open_spiel/games/einstein_wurfelt_nicht/einstein_wurfelt_nicht.cc @@ -0,0 +1,508 @@ +// Copyright 2024 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/einstein_wurfelt_nicht/einstein_wurfelt_nicht.h" + +#include +#include +#include +#include + +#include "open_spiel/game_parameters.h" +#include "open_spiel/utils/tensor_view.h" + +namespace open_spiel { +namespace einstein_wurfelt_nicht { +namespace { + +const std::vector> kChanceOutcomes = { + std::pair(0, 1.0 / 6), + std::pair(1, 1.0 / 6), + std::pair(2, 1.0 / 6), + std::pair(3, 1.0 / 6), + std::pair(4, 1.0 / 6), + std::pair(5, 1.0 / 6) +}; + +// Number of unique directions each cube can take. +constexpr int kNumDirections = 6; + +// Direction offsets for black, then white. +constexpr std::array kDirRowOffsets = { + {1, 1, 0, -1, -1, 0}}; + +constexpr std::array kDirColOffsets = { + {1, 0, 1, 0, -1, -1}}; + +// Facts about the game +const GameType kGameType{/*short_name=*/"einstein_wurfelt_nicht", + /*long_name=*/"einstein_wurfelt_nicht", + GameType::Dynamics::kSequential, + GameType::ChanceMode::kExplicitStochastic, + GameType::Information::kPerfectInformation, + GameType::Utility::kZeroSum, + GameType::RewardModel::kTerminal, + /*max_num_players=*/2, + /*min_num_players=*/2, + /*provides_information_state_string=*/false, + /*provides_information_state_tensor=*/false, + /*provides_observation_string=*/true, + /*provides_observation_tensor=*/true, + /*parameter_specification=*/ + {{"seed", GameParameter(-1)}}}; + +std::shared_ptr Factory(const GameParameters& params) { + return std::shared_ptr(new EinsteinWurfeltNichtGame(params)); +} + +REGISTER_SPIEL_GAME(kGameType, Factory); + +RegisterSingleTensorObserver single_tensor(kGameType.short_name); + +Color PlayerToColor(Player player) { + SPIEL_CHECK_NE(player, kInvalidPlayer); + return static_cast(player); +} + +Player ColorToPlayer(Color color) { + switch (color) { + case Color::kBlack: + return kBlackPlayerId; + case Color::kWhite: + return kWhitePlayerId; + default: + SpielFatalError("No player for this color"); + } +} + +Color OpponentColor(Player player) { + Color player_color = PlayerToColor(player); + if (player_color == Color::kBlack) { + return Color::kWhite; + } else if (player_color == Color::kWhite) { + return Color::kBlack; + } else { + SpielFatalError("Player should be either black or white"); + } +} + +std::string CoordinatesToDirection(int row, int col) { + std::string direction; + if (row == col) { + direction = "diag"; + } else if (row == -1) { + direction = "up"; + } else if (row == 1) { + direction = "down"; + } else if (col == 1) { + direction = "right"; + } else if (col == -1) { + direction = "left"; + } else { + std::cout << "r2: " << row << "c2: " << col << std::endl; + SpielFatalError("Unrecognized cube's movement"); + } + return direction; +} + +} // namespace + +EinsteinWurfeltNichtState::EinsteinWurfeltNichtState( + std::shared_ptr game, int rows, int cols, int seed) + : State(game), + rows_(rows), + cols_(cols), + seed_(seed), + cur_player_(kChancePlayerId), + prev_player_(kBlackPlayerId) { + SPIEL_CHECK_GT(rows_, 1); + SPIEL_CHECK_GT(cols_, 1); + + std::vector> players_cubes{{1, 2, 3, 4, 5, 6}, + {1, 2, 3, 4, 5, 6}}; + int player_cube_seed = seed_; + for (int i = 0; i < 2; ++i) { + if (seed_ == -1) { + player_cube_seed = + std::chrono::system_clock::now().time_since_epoch().count(); + } + player_cube_seed += 1; // make sure to have different seeds for each player + std::default_random_engine rng(player_cube_seed); + std::shuffle(players_cubes[i].begin(), players_cubes[i].end(), rng); + } + + // Values in the upper-left corner (black cubes) have a postion identified + // as rows+cols <= 2. Values in the lower-right corner (white cubes) have a + // position identified as rows+cols >= 6. The rest of the board is empty. + for (int r = 0; r < kDefaultRows; r++) { + for (int c = 0; c < kDefaultColumns; c++) { + if (r+c <= 2) { + board_[r*kDefaultColumns+c] = + Cube{Color::kBlack, players_cubes[0].back()}; + players_cubes[0].pop_back(); + } else if (r+c >= 6) { + board_[r*kDefaultColumns+c] = + Cube{Color::kWhite, players_cubes[1].back()}; + players_cubes[1].pop_back(); + } else { + board_[r*kDefaultColumns+c] = Cube{Color::kEmpty, -1}; + } + } + } + + winner_ = kInvalidPlayer; + cubes_[0] = cubes_[1] = kNumPlayerCubes; +} + +int EinsteinWurfeltNichtState::CurrentPlayer() const { + if (IsTerminal()) { + return kTerminalPlayerId; + } else { + return cur_player_; + } +} + +int EinsteinWurfeltNichtState::Opponent(int player) const { return 1 - player; } + +std::vector> +EinsteinWurfeltNichtState::AvailableCubesPosition(Color player_color) const { + std::vector> player_cubes; + for (int r = 0; r < rows_; r++) { + for (int c = 0; c < cols_; c++) { + if (board(r, c).color == player_color) { + if (board(r, c).value == die_roll_) { + // If there is a cube with the same value as the die, + // return only this one + std::vector> player_cube; + player_cube.push_back({board(r, c).value, r, c}); + return player_cube; + } else { + player_cubes.push_back({r, c}); + } + } + } + } + + // Initialise lowest/highest cube values to out-of-bound cube's values + std::vector lowest_cube = {0, 0, 0}; // cube value, r, c + std::vector highest_cube = {7, 0, 0}; // cube value, r, c + for (int i = 0; i < player_cubes.size(); ++i) { + int r = player_cubes[i].first; + int c = player_cubes[i].second; + if (board(r, c).value > lowest_cube[0] && board(r, c).value < die_roll_) { + lowest_cube[0] = board(r, c).value; + lowest_cube[1] = r; + lowest_cube[2] = c; + } else if (board(r, c).value < highest_cube[0] && + board(r, c).value > die_roll_) { + highest_cube[0] = board(r, c).value; + highest_cube[1] = r; + highest_cube[2] = c; + } + } + + std::vector> selected_cubes; + if (lowest_cube[0] > 0) { + selected_cubes.push_back(lowest_cube); + } + if (highest_cube[0] < 7) { + selected_cubes.push_back(highest_cube); + } + + // Legal actions have to be sorted. Sort by row first, then by column + std::sort(selected_cubes.begin(), selected_cubes.end(), + [](const std::vector& a, const std::vector& b) { + if (a[1] != b[1]) return a[1] < b[1]; + return a[2] < b[2]; + }); + + return selected_cubes; +} + +void EinsteinWurfeltNichtState::DoApplyAction(Action action) { + if (IsChanceNode()) { + SPIEL_CHECK_GE(action, 0); + SPIEL_CHECK_LE(action, 5); + turn_history_info_.push_back(TurnHistoryInfo(kChancePlayerId, + prev_player_, + die_roll_, + action, + Cube{Color::kEmpty, -1})); + cur_player_ = Opponent(prev_player_); + prev_player_ = cur_player_; + die_roll_ = action + 1; + return; + } + + // The die should have been rolled at least once at this point + SPIEL_CHECK_GE(die_roll_, 1); + SPIEL_CHECK_LE(die_roll_, 6); + + std::vector values = + UnrankActionMixedBase(action, {rows_, cols_, kNumDirections, 2}); + int r1 = values[0]; + int c1 = values[1]; + int dir = values[2]; + bool capture = values[3] == 1; + int r2 = r1 + kDirRowOffsets[dir]; + int c2 = c1 + kDirColOffsets[dir]; + + SPIEL_CHECK_TRUE(InBounds(r1, c1)); + SPIEL_CHECK_TRUE(InBounds(r2, c2)); + + // Remove cubes if captured. + if (board(r2, c2).color == Color::kBlack) { + cubes_[ColorToPlayer(Color::kBlack)]--; + } else if (board(r2, c2).color == Color::kWhite) { + cubes_[ColorToPlayer(Color::kWhite)]--; + } + + Cube captured_cube = (capture) ? board(r2, c2) : Cube{Color::kEmpty, -1}; + turn_history_info_.push_back( + TurnHistoryInfo(cur_player_, + prev_player_, + die_roll_, + action, + captured_cube)); + + SetBoard(r2, c2, board(r1, c1)); + SetBoard(r1, c1, Cube{Color::kEmpty, -1}); + + // Check for winner. + if ((cur_player_ == 0 && r2 == (rows_ - 1) && c2 == (cols_ - 1)) || + (cubes_[ColorToPlayer(Color::kWhite)] == 0)) { + winner_ = 0; + } else if ((cur_player_ == 1 && r2 == 0 && c2 == 0) || + (cubes_[ColorToPlayer(Color::kBlack)] == 0)) { + winner_ = 1; + } + + cur_player_ = NextPlayerRoundRobin(cur_player_, kNumPlayers); + cur_player_ = kChancePlayerId; +} + +std::string EinsteinWurfeltNichtState::ActionToString(Player player, + Action action) const { + std::vector values = + UnrankActionMixedBase(action, {rows_, cols_, kNumDirections, 2}); + int r1 = values[0]; + int c1 = values[1]; + int dir = values[2]; + bool capture = values[3] == 1; + int r2 = kDirRowOffsets[dir]; + int c2 = kDirColOffsets[dir]; + + std::string action_string = ""; + + if (IsChanceNode()) { + absl::StrAppend(&action_string, "roll ", action+1); + return action_string; + } + + Cube cube = board(r1, c1); + std::string color = (cube.color == Color::kBlack) ? "B" : "W"; + + std::string direction = CoordinatesToDirection(r2, c2); + absl::StrAppend(&action_string, color); + absl::StrAppend(&action_string, cube.value); + absl::StrAppend(&action_string, "-"); + absl::StrAppend(&action_string, direction); + if (capture) { + absl::StrAppend(&action_string, "*"); + } + return action_string; +} + +std::vector EinsteinWurfeltNichtState::LegalActions() const { + if (IsChanceNode()) return LegalChanceOutcomes(); + if (IsTerminal()) return {}; + + std::vector movelist; + if (IsTerminal()) return movelist; + const Player player = CurrentPlayer(); + Color player_color = PlayerToColor(player); + std::vector action_bases = {rows_, cols_, kNumDirections, 2}; + std::vector action_values = {0, 0, 0, 0}; + + std::vector> available_cubes; + available_cubes = AvailableCubesPosition(player_color); + + for (int i = 0; i < available_cubes.size(); ++i) { + int r = available_cubes[i][1]; + int c = available_cubes[i][2]; + for (int o = 0; o < kNumDirections / 2; o++) { + int dir = player * kNumDirections / 2 + o; + int rp = r + kDirRowOffsets[dir]; + int cp = c + kDirColOffsets[dir]; + if (InBounds(rp, cp)) { + action_values[0] = r; + action_values[1] = c; + action_values[2] = dir; + if (board(rp, cp).color == Color::kEmpty) { + action_values[3] = 0; // no capture + movelist.push_back( + RankActionMixedBase(action_bases, action_values)); + } else { + action_values[3] = 1; // capture + movelist.push_back( + RankActionMixedBase(action_bases, action_values)); + } + } + } + } + return movelist; +} + +std::vector> +EinsteinWurfeltNichtState::ChanceOutcomes() const { + SPIEL_CHECK_TRUE(IsChanceNode()); + return kChanceOutcomes; +} + +bool EinsteinWurfeltNichtState::InBounds(int r, int c) const { + return (r >= 0 && r < rows_ && c >= 0 && c < cols_); +} + +std::string EinsteinWurfeltNichtState::ToString() const { + std::string W_result = ""; + + for (int r = 0; r < kDefaultRows; r++) { + for (int c = 0; c < kDefaultColumns; c++) { + if (board_[r*kDefaultColumns+c].color == Color::kBlack) { + absl::StrAppend(&W_result, "|b"); + absl::StrAppend(&W_result, board_[r*kDefaultColumns+c].value); + absl::StrAppend(&W_result, "|"); + } else if (board_[r*kDefaultColumns+c].color == Color::kWhite) { + absl::StrAppend(&W_result, "|w"); + absl::StrAppend(&W_result, board_[r*kDefaultColumns+c].value); + absl::StrAppend(&W_result, "|"); + } else { + absl::StrAppend(&W_result, "|__|"); + } + } + W_result.append("\n"); + } + return W_result; +} + +bool EinsteinWurfeltNichtState::IsTerminal() const { + return (winner_ >= 0 || (cubes_[0] == 0 || cubes_[1] == 0)); +} + +std::vector EinsteinWurfeltNichtState::Returns() const { + if (winner_ == 0 || cubes_[1] == 0) { + return {1.0, -1.0}; + } else if (winner_ == 1 || cubes_[0] == 0) { + return {-1.0, 1.0}; + } else { + return {0.0, 0.0}; + } +} + +std::string EinsteinWurfeltNichtState::ObservationString(Player player) const { + SPIEL_CHECK_GE(player, 0); + SPIEL_CHECK_LT(player, num_players_); + return ToString(); +} + +void EinsteinWurfeltNichtState::ObservationTensor(Player player, + absl::Span values) const { + SPIEL_CHECK_GE(player, 0); + SPIEL_CHECK_LT(player, num_players_); + + auto value_it = values.begin(); + + for (int cube_num = 1; cube_num < kNumPlayerCubes+1; ++cube_num) { + for (int player_idx = 0; player_idx < kNumPlayers; ++player_idx) { + for (int8_t y = 0; y < kDefaultRows; ++y) { + for (int8_t x = 0; x < kDefaultColumns; ++x) { + *value_it++ = + (board(x, y).value == cube_num && + board(x, y).color == PlayerToColor(player_idx) + ? 1.0 + : 0.0); + } + } + } + } +} + +void EinsteinWurfeltNichtState::UndoAction(Player player, Action action) { + const TurnHistoryInfo& thi = turn_history_info_.back(); + SPIEL_CHECK_EQ(thi.player, player); + SPIEL_CHECK_EQ(action, thi.action); + + if (player != kChancePlayerId) { + std::vector values = + UnrankActionMixedBase(action, {rows_, cols_, kNumDirections, 2}); + int r1 = values[0]; + int c1 = values[1]; + int dir = values[2]; + bool capture = values[3] == 1; + int r2 = r1 + kDirRowOffsets[dir]; + int c2 = c1 + kDirColOffsets[dir]; + Cube captured_cube = thi.captured_cube; + + SetBoard(r1, c1, board(r2, c2)); + if (captured_cube.value != -1) { + SetBoard(r2, c2, captured_cube); + if (captured_cube.color == Color::kBlack) { + cubes_[ColorToPlayer(Color::kBlack)]++; + } else if (captured_cube.color == Color::kWhite) { + cubes_[ColorToPlayer(Color::kWhite)]++; + } + } else { + SetBoard(r2, c2, Cube{Color::kEmpty, -1}); + } + } + // Undo win status. + winner_ = kInvalidPlayer; + + turn_history_info_.pop_back(); + history_.pop_back(); + --move_number_; +} + +std::unique_ptr EinsteinWurfeltNichtState::Clone() const { + return std::unique_ptr(new EinsteinWurfeltNichtState(*this)); +} + +// Setter function used for debugging and tests. Note: this does not set the +// historical information properly, so Undo likely will not work on states +// set this way! +void EinsteinWurfeltNichtState::SetState(int cur_player, + int die_roll, + const std::array board, + int cubes_black, + int cubes_white) { + cur_player_ = cur_player; + die_roll_ = die_roll; + board_ = board; + cubes_[ColorToPlayer(Color::kBlack)] = cubes_black; + cubes_[ColorToPlayer(Color::kWhite)] = cubes_white; +} + +EinsteinWurfeltNichtGame::EinsteinWurfeltNichtGame(const GameParameters& params) + : Game(kGameType, params), + rows_(kDefaultRows), + cols_(kDefaultColumns), + seed_(ParameterValue("seed")) {} + +int EinsteinWurfeltNichtGame::NumDistinctActions() const { + return rows_ * cols_ * kNumDirections * 2; +} + +} // namespace einstein_wurfelt_nicht +} // namespace open_spiel diff --git a/open_spiel/games/einstein_wurfelt_nicht/einstein_wurfelt_nicht.h b/open_spiel/games/einstein_wurfelt_nicht/einstein_wurfelt_nicht.h new file mode 100644 index 0000000000..6268566340 --- /dev/null +++ b/open_spiel/games/einstein_wurfelt_nicht/einstein_wurfelt_nicht.h @@ -0,0 +1,160 @@ +// Copyright 2024 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_EINSTEIN_WURFELT_NICHT_H_ +#define OPEN_SPIEL_GAMES_EINSTEIN_WURFELT_NICHT_H_ + +#include +#include +#include +#include +#include +#include + +#include "open_spiel/spiel.h" +#include "open_spiel/spiel_utils.h" + +// An implementation of the game EinStein wΓΌrfelt nicht! +// This is the implementation of the basic game with a 5x5 board and 6 cubes +// per player. +// https://en.wikipedia.org/wiki/EinStein_w%C3%BCrfelt_nicht! +// +// Parameters: +// "seed" int random seed for placement of cubes on the board [1] (default=-1) +// +// [1] When the seed is -1, the current time is used as the seed, so that the +// assignment of cubes is random each time the game is played. + +namespace open_spiel { +namespace einstein_wurfelt_nicht { + +enum class Color : int8_t { kBlack = 0, kWhite = 1, kEmpty = 2 }; + +struct Cube { + Color color; + int value; // player's die value +}; + +inline constexpr int kNumPlayers = 2; +inline constexpr int kBlackPlayerId = 0; +inline constexpr int kWhitePlayerId = 1; +inline constexpr int kNumPlayerCubes = 6; +inline constexpr int kDefaultRows = 5; +inline constexpr int kDefaultColumns = 5; +inline constexpr int k2dMaxBoardSize = kDefaultRows * kDefaultColumns; +inline constexpr const int kStateEncodingSize = kNumPlayers * kNumPlayerCubes * + kDefaultRows * kDefaultColumns; + +// This is a small helper to track historical turn info not stored in the moves. +// It is only needed for proper implementation of Undo. +struct TurnHistoryInfo { + int player; + int prev_player; + int die_roll_; + Action action; + Cube captured_cube; + TurnHistoryInfo(int _player, int _prev_player, int _die_roll, + int _action, Cube _captured_cube) + : player(_player), + prev_player(_prev_player), + die_roll_(_die_roll), + action(_action), + captured_cube(_captured_cube) {} +}; + +class EinsteinWurfeltNichtState : public State { + public: + explicit EinsteinWurfeltNichtState(std::shared_ptr game, int rows, + int cols, int seed); + Player CurrentPlayer() const override; + // Returns the opponent of the specified player. + int Opponent(int player) const; + std::vector> AvailableCubesPosition(Color color) const; + std::string ActionToString(Player player, Action action) const override; + std::string ToString() const override; + bool IsTerminal() const override; + std::vector Returns() 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; + + bool InBounds(int r, int c) const; + void SetBoard(int r, int c, Cube cube) { board_[r * cols_ + c] = cube; } + Cube board(int row, int col) const { return board_[row * cols_ + col]; } + std::vector LegalActions() const override; + std::vector> ChanceOutcomes() const override; + void SetState(int cur_player, int die_roll, + const std::array board, + int cubes_black, int cubes_white); + + protected: + void DoApplyAction(Action action) override; + + private: + Player cur_player_ = kInvalidPlayer; + Player prev_player_ = kInvalidPlayer; + int winner_ = kInvalidPlayer; + int total_moves_ = -1; + std::array cubes_; + int rows_ = -1; + int cols_ = -1; + int seed_ = -1; + int die_roll_ = 0; + std::array board_; // for (row,col) we use row*cols_+col + std::vector turn_history_info_; +}; + +class EinsteinWurfeltNichtGame : public Game { + public: + explicit EinsteinWurfeltNichtGame(const GameParameters& params); + int NumDistinctActions() const override; + std::unique_ptr NewInitialState() const override { + return std::unique_ptr( + new EinsteinWurfeltNichtState(shared_from_this(), rows_, cols_, seed_)); + } + + int MaxChanceOutcomes() const override { return 6; } + + 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 {kStateEncodingSize}; + } + + // Assuming that each cube is moved first along the horizontal axis and then + // along the vertical axis, which is the maximum number of moves for a cube + // (only the cubes in the corners). This accounts for (row-1) * (cols-1) + // moves. If we assume that each player makes all these moves we get + // (row-1) * (cols-1) * num_players. If we consider the chance player as + // the third player which makes the same number of moves, the upper bound + // for the number of moves is (row-1) * (cols-1) * (num_players + 1). + int MaxGameLength() const override { + return (kDefaultRows - 1) * (kDefaultColumns - 1) * (kNumPlayerCubes + 1); + } + + private: + int rows_ = -1; + int cols_ = -1; + int seed_ = -1; +}; + +} // namespace einstein_wurfelt_nicht +} // namespace open_spiel + +#endif // OPEN_SPIEL_GAMES_EINSTEIN_WURFELT_NICHT_H_ diff --git a/open_spiel/games/einstein_wurfelt_nicht/einstein_wurfelt_nicht_test.cc b/open_spiel/games/einstein_wurfelt_nicht/einstein_wurfelt_nicht_test.cc new file mode 100644 index 0000000000..50fd3da486 --- /dev/null +++ b/open_spiel/games/einstein_wurfelt_nicht/einstein_wurfelt_nicht_test.cc @@ -0,0 +1,277 @@ +// Copyright 2024 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/einstein_wurfelt_nicht/einstein_wurfelt_nicht.h" + +#include "open_spiel/spiel.h" +#include "open_spiel/tests/basic_tests.h" + +namespace open_spiel { +namespace einstein_wurfelt_nicht { +namespace { + +namespace testing = open_spiel::testing; + +void BasicEinsteinWurfeltNitchTests() { + open_spiel::GameParameters params; + params["seed"] = open_spiel::GameParameter(42); + std::shared_ptr game = + open_spiel::LoadGame("einstein_wurfelt_nicht", params); + testing::RandomSimTest(*game, 100, true, true); + testing::RandomSimTestWithUndo(*game, 1); +} + +void BlackPlayerSimpleWinTest() { + open_spiel::GameParameters params; + params["seed"] = open_spiel::GameParameter(42); + std::shared_ptr game = + open_spiel::LoadGame("einstein_wurfelt_nicht", params); + std::unique_ptr state = game->NewInitialState(); + EinsteinWurfeltNichtState* bstate = + static_cast(state.get()); + + int values[] = {-1, 2, -1, -1, -1, -1, -1, -1, 5, -1, 6, -1, -1, -1, -1, -1, + 3, -1, -1, 3, -1, -1, -1, -1, -1}; + Color colors[] = {Color::kEmpty, Color::kWhite, Color::kEmpty, Color::kEmpty, + Color::kEmpty, Color::kEmpty, Color::kEmpty, Color::kEmpty, + Color::kBlack, Color::kEmpty, Color::kBlack, Color::kEmpty, + Color::kEmpty, Color::kEmpty, Color::kEmpty, Color::kEmpty, + Color::kWhite, Color::kEmpty, Color::kEmpty, Color::kBlack, + Color::kEmpty, Color::kEmpty, Color::kEmpty, Color::kEmpty, + Color::kEmpty}; + std::array board; + for (int i = 0; i < k2dMaxBoardSize; i++) { + board[i] = {colors[i], values[i]}; + } + + bstate->SetState(kBlackPlayerId, 2, board, 3, 2); + + std::string expected_state = + "|__||w2||__||__||__|\n" + "|__||__||__||b5||__|\n" + "|b6||__||__||__||__|\n" + "|__||w3||__||__||b3|\n" + "|__||__||__||__||__|\n"; + SPIEL_CHECK_EQ(bstate->ToString(), expected_state); + SPIEL_CHECK_EQ(bstate->CurrentPlayer(), kBlackPlayerId); + SPIEL_CHECK_FALSE(bstate->IsTerminal()); + SPIEL_CHECK_EQ(bstate->LegalActions().size(), 1); + Action action = 230; // Move B3 down + SPIEL_CHECK_EQ(bstate->LegalActions()[0], 230); + SPIEL_CHECK_EQ(bstate->ActionToString(kBlackPlayerId, 230), "B3-down"); + + bstate->ApplyAction(230); + std::string expected_state_final = + "|__||w2||__||__||__|\n" + "|__||__||__||b5||__|\n" + "|b6||__||__||__||__|\n" + "|__||w3||__||__||__|\n" + "|__||__||__||__||b3|\n"; + SPIEL_CHECK_EQ(bstate->ToString(), expected_state_final); + std::vector returns = bstate->Returns(); + SPIEL_CHECK_TRUE(bstate->IsTerminal()); + SPIEL_CHECK_EQ(returns.size(), 2); + SPIEL_CHECK_EQ(returns[0], 1); + SPIEL_CHECK_EQ(returns[1], -1); +} + +void WhitePlayerSimpleWinTest() { + open_spiel::GameParameters params; + params["seed"] = open_spiel::GameParameter(42); + std::shared_ptr game = + open_spiel::LoadGame("einstein_wurfelt_nicht", params); + std::unique_ptr state = game->NewInitialState(); + EinsteinWurfeltNichtState* bstate = + static_cast(state.get()); + + int values[] = {-1, 2, -1, -1, -1, -1, -1, -1, 5, -1, 6, -1, -1, -1, -1, -1, + 3, -1, -1, 3, -1, -1, -1, -1, -1}; + Color colors[] = {Color::kEmpty, Color::kWhite, Color::kEmpty, Color::kEmpty, + Color::kEmpty, Color::kEmpty, Color::kEmpty, Color::kEmpty, + Color::kBlack, Color::kEmpty, Color::kBlack, Color::kEmpty, + Color::kEmpty, Color::kEmpty, Color::kEmpty, Color::kEmpty, + Color::kWhite, Color::kEmpty, Color::kEmpty, Color::kBlack, + Color::kEmpty, Color::kEmpty, Color::kEmpty, Color::kEmpty, + Color::kEmpty}; + std::array board; + for (int i = 0; i < k2dMaxBoardSize; i++) { + board[i] = {colors[i], values[i]}; + } + bstate->SetState(kWhitePlayerId, 2, board, 3, 2); + + std::string expected_state = + "|__||w2||__||__||__|\n" + "|__||__||__||b5||__|\n" + "|b6||__||__||__||__|\n" + "|__||w3||__||__||b3|\n" + "|__||__||__||__||__|\n"; + SPIEL_CHECK_EQ(bstate->ToString(), expected_state); + SPIEL_CHECK_EQ(bstate->CurrentPlayer(), kWhitePlayerId); + SPIEL_CHECK_FALSE(bstate->IsTerminal()); + SPIEL_CHECK_EQ(bstate->LegalActions().size(), 1); + Action action = 22; // Move W2 to the left + SPIEL_CHECK_EQ(bstate->LegalActions()[0], action); + SPIEL_CHECK_EQ(bstate->ActionToString(kWhitePlayerId, action), "W2-left"); + + bstate->ApplyAction(action); + std::string expected_state_final = + "|w2||__||__||__||__|\n" + "|__||__||__||b5||__|\n" + "|b6||__||__||__||__|\n" + "|__||w3||__||__||b3|\n" + "|__||__||__||__||__|\n"; + SPIEL_CHECK_EQ(bstate->ToString(), expected_state_final); + std::vector returns = bstate->Returns(); + SPIEL_CHECK_TRUE(bstate->IsTerminal()); + SPIEL_CHECK_EQ(returns.size(), 2); + SPIEL_CHECK_EQ(returns[0], -1); + SPIEL_CHECK_EQ(returns[1], 1); +} + +void WinByCapturingAllOpponentCubesTest() { + open_spiel::GameParameters params; + params["seed"] = open_spiel::GameParameter(42); + std::shared_ptr game = + open_spiel::LoadGame("einstein_wurfelt_nicht", params); + std::unique_ptr state = game->NewInitialState(); + EinsteinWurfeltNichtState* bstate = + static_cast(state.get()); + + int values[] = {-1, -1, -1, -1, -1, -1, -1, -1, 5, -1, 6, -1, -1, -1, -1, -1, + 3, -1, -1, 3, -1, -1, -1, -1, -1}; + Color colors[] = {Color::kEmpty, Color::kEmpty, Color::kEmpty, Color::kEmpty, + Color::kEmpty, Color::kEmpty, Color::kEmpty, Color::kEmpty, + Color::kBlack, Color::kEmpty, Color::kBlack, Color::kEmpty, + Color::kEmpty, Color::kEmpty, Color::kEmpty, Color::kEmpty, + Color::kWhite, Color::kEmpty, Color::kEmpty, Color::kBlack, + Color::kEmpty, Color::kEmpty, Color::kEmpty, Color::kEmpty, + Color::kEmpty}; + std::array board; + for (int i = 0; i < k2dMaxBoardSize; i++) { + board[i] = {colors[i], values[i]}; + } + bstate->SetState(kBlackPlayerId, 6, board, 3, 1); + + std::string expected_state = + "|__||__||__||__||__|\n" + "|__||__||__||b5||__|\n" + "|b6||__||__||__||__|\n" + "|__||w3||__||__||b3|\n" + "|__||__||__||__||__|\n"; + SPIEL_CHECK_EQ(bstate->ToString(), expected_state); + SPIEL_CHECK_EQ(bstate->CurrentPlayer(), kBlackPlayerId); + SPIEL_CHECK_FALSE(bstate->IsTerminal()); + SPIEL_CHECK_EQ(bstate->LegalActions().size(), 3); + Action action = 121; // Move B6 diagonally down-right + SPIEL_CHECK_EQ(bstate->LegalActions()[0], action); + SPIEL_CHECK_EQ(bstate->ActionToString(kBlackPlayerId, action), "B6-diag*"); + + bstate->ApplyAction(action); + std::string expected_state_final = + "|__||__||__||__||__|\n" + "|__||__||__||b5||__|\n" + "|__||__||__||__||__|\n" + "|__||b6||__||__||b3|\n" + "|__||__||__||__||__|\n"; + SPIEL_CHECK_EQ(bstate->ToString(), expected_state_final); + std::vector returns = bstate->Returns(); + SPIEL_CHECK_TRUE(bstate->IsTerminal()); + SPIEL_CHECK_EQ(returns.size(), 2); + SPIEL_CHECK_EQ(returns[0], 1); + SPIEL_CHECK_EQ(returns[1], -1); +} + +void CheckAlternateChancePlayerAndNormalPlayerTest() { + open_spiel::GameParameters params; + params["seed"] = open_spiel::GameParameter(42); + std::shared_ptr game = + open_spiel::LoadGame("einstein_wurfelt_nicht", params); + std::unique_ptr state = game->NewInitialState(); + + int previous_player = state->CurrentPlayer(); + + while (!state->IsTerminal()) { + if (state->CurrentPlayer() == open_spiel::kChancePlayerId) { + state->ApplyAction(state->LegalActions()[0]); + } else { + std::vector legal_actions = state->LegalActions(); + state->ApplyAction(legal_actions[0]); + } + int current_player = state->CurrentPlayer(); + if (current_player != open_spiel::kChancePlayerId) { + SPIEL_CHECK_NE(current_player, previous_player); + } + previous_player = current_player; + } +} + +void InitialStateTest() { + open_spiel::GameParameters params; + params["seed"] = open_spiel::GameParameter(42); + std::shared_ptr game = + open_spiel::LoadGame("einstein_wurfelt_nicht", params); + std::unique_ptr state = game->NewInitialState(); + SPIEL_CHECK_EQ(state->CurrentPlayer(), open_spiel::kChancePlayerId); + SPIEL_CHECK_FALSE(state->IsTerminal()); +} + +void LegalActionsTest() { + open_spiel::GameParameters params; + params["seed"] = open_spiel::GameParameter(42); + std::shared_ptr game = + open_spiel::LoadGame("einstein_wurfelt_nicht", params); + std::unique_ptr state = game->NewInitialState(); + + while (!state->IsTerminal()) { + std::vector legal_actions = state->LegalActions(); + SPIEL_CHECK_FALSE(legal_actions.empty()); + state->ApplyAction(legal_actions[0]); + } + + std::vector returns = state->Returns(); + SPIEL_CHECK_EQ(returns.size(), 2); + SPIEL_CHECK_TRUE(returns[0] == 1.0 || returns[1] == 1.0); +} + +void RandomBoardSetupTest() { + open_spiel::GameParameters params; + params["seed"] = open_spiel::GameParameter(-1); + std::shared_ptr game = + open_spiel::LoadGame("einstein_wurfelt_nicht", params); + std::unique_ptr state = game->NewInitialState(); + + std::shared_ptr game2 = + open_spiel::LoadGame("einstein_wurfelt_nicht", params); + std::unique_ptr state2 = game->NewInitialState(); + + SPIEL_CHECK_NE(state->ToString(), state2->ToString()); +} + +} // namespace +} // namespace einstein_wurfelt_nicht +} // namespace open_spiel + +int main(int argc, char** argv) { + open_spiel::testing::LoadGameTest("einstein_wurfelt_nicht"); + open_spiel::einstein_wurfelt_nicht::BasicEinsteinWurfeltNitchTests(); + open_spiel::einstein_wurfelt_nicht::WinByCapturingAllOpponentCubesTest(); + open_spiel::einstein_wurfelt_nicht:: + CheckAlternateChancePlayerAndNormalPlayerTest(); + open_spiel::einstein_wurfelt_nicht::InitialStateTest(); + open_spiel::einstein_wurfelt_nicht::LegalActionsTest(); + open_spiel::einstein_wurfelt_nicht::BlackPlayerSimpleWinTest(); + open_spiel::einstein_wurfelt_nicht::WhitePlayerSimpleWinTest(); + open_spiel::einstein_wurfelt_nicht::WinByCapturingAllOpponentCubesTest(); + open_spiel::einstein_wurfelt_nicht::RandomBoardSetupTest(); +} diff --git a/open_spiel/integration_tests/playthroughs/einstein_wurfelt_nicht.txt b/open_spiel/integration_tests/playthroughs/einstein_wurfelt_nicht.txt new file mode 100644 index 0000000000..87b125e1eb --- /dev/null +++ b/open_spiel/integration_tests/playthroughs/einstein_wurfelt_nicht.txt @@ -0,0 +1,424 @@ +game: einstein_wurfelt_nicht + +GameType.chance_mode = ChanceMode.EXPLICIT_STOCHASTIC +GameType.dynamics = Dynamics.SEQUENTIAL +GameType.information = Information.PERFECT_INFORMATION +GameType.long_name = "einstein_wurfelt_nicht" +GameType.max_num_players = 2 +GameType.min_num_players = 2 +GameType.parameter_specification = ["columns", "rows", "seed"] +GameType.provides_information_state_string = False +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 = "einstein_wurfelt_nicht" +GameType.utility = Utility.ZERO_SUM + +NumDistinctActions() = 300 +PolicyTensorShape() = [300] +MaxChanceOutcomes() = 6 +GetParameters() = {columns=5,rows=5,seed=-1} +NumPlayers() = 2 +MinUtility() = -1.0 +MaxUtility() = 1.0 +UtilitySum() = 0.0 +ObservationTensorShape() = [300] +ObservationTensorLayout() = TensorLayout.CHW +ObservationTensorSize() = 300 +MaxGameLength() = 71 +ToString() = "einstein_wurfelt_nicht()" + +# State 0 +# |b2||b1||b6||__||__| +# |b4||b5||__||__||__| +# |b3||__||__||__||w2| +# |__||__||__||w1||w6| +# |__||__||w4||w5||w3| +IsTerminal() = False +History() = [] +HistoryString() = "" +IsChanceNode() = True +IsSimultaneousNode() = False +CurrentPlayer() = -1 +ObservationString(0) = "|b2||b1||b6||__||__|\n|b4||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||__||w1||w6|\n|__||__||w4||w5||w3|\n" +ObservationString(1) = "|b2||b1||b6||__||__|\n|b4||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||__||w1||w6|\n|__||__||w4||w5||w3|\n" +ObservationTensor(0): binvec(300, 0x40000000010200000000000420000000000050000000004000200000000080008000000002) +ObservationTensor(1): binvec(300, 0x40000000010200000000000420000000000050000000004000200000000080008000000002) +ChanceOutcomes() = [(0,0.166667), (1,0.166667), (2,0.166667), (3,0.166667), (4,0.166667), (5,0.166667)] +LegalActions() = [0, 1, 2, 3, 4, 5] +StringLegalActions() = ["roll 1", "roll 2", "roll 3", "roll 4", "roll 5", "roll 6"] + +# Apply action "roll 3" +action: 2 + +# State 1 +# |b2||b1||b6||__||__| +# |b4||b5||__||__||__| +# |b3||__||__||__||w2| +# |__||__||__||w1||w6| +# |__||__||w4||w5||w3| +IsTerminal() = False +History() = [2] +HistoryString() = "2" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +ObservationString(0) = "|b2||b1||b6||__||__|\n|b4||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||__||w1||w6|\n|__||__||w4||w5||w3|\n" +ObservationString(1) = "|b2||b1||b6||__||__|\n|b4||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||__||w1||w6|\n|__||__||w4||w5||w3|\n" +ObservationTensor(0): binvec(300, 0x40000000010200000000000420000000000050000000004000200000000080008000000002) +ObservationTensor(1): binvec(300, 0x40000000010200000000000420000000000050000000004000200000000080008000000002) +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [295, 297, 299] +StringLegalActions() = ["W3-up*", "W3-diag*", "W3-left*"] + +# Apply action "W3-left*" +action: 299 + +# State 2 +# |b2||b1||b6||__||__| +# |b4||b5||__||__||__| +# |b3||__||__||__||w2| +# |__||__||__||w1||w6| +# |__||__||w4||w3||__| +IsTerminal() = False +History() = [2, 299] +HistoryString() = "2, 299" +IsChanceNode() = True +IsSimultaneousNode() = False +CurrentPlayer() = -1 +ObservationString(0) = "|b2||b1||b6||__||__|\n|b4||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||__||w1||w6|\n|__||__||w4||w3||__|\n" +ObservationString(1) = "|b2||b1||b6||__||__|\n|b4||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||__||w1||w6|\n|__||__||w4||w3||__|\n" +ObservationTensor(0): binvec(300, 0x40000000010200000000000420000000000810000000004000200000000000008000000002) +ObservationTensor(1): binvec(300, 0x40000000010200000000000420000000000810000000004000200000000000008000000002) +ChanceOutcomes() = [(0,0.166667), (1,0.166667), (2,0.166667), (3,0.166667), (4,0.166667), (5,0.166667)] +LegalActions() = [0, 1, 2, 3, 4, 5] +StringLegalActions() = ["roll 1", "roll 2", "roll 3", "roll 4", "roll 5", "roll 6"] + +# Apply action "roll 2" +action: 1 + +# State 3 +# |b2||b1||b6||__||__| +# |b4||b5||__||__||__| +# |b3||__||__||__||w2| +# |__||__||__||w1||w6| +# |__||__||w4||w3||__| +IsTerminal() = False +History() = [2, 299, 1] +HistoryString() = "2, 299, 1" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +ObservationString(0) = "|b2||b1||b6||__||__|\n|b4||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||__||w1||w6|\n|__||__||w4||w3||__|\n" +ObservationString(1) = "|b2||b1||b6||__||__|\n|b4||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||__||w1||w6|\n|__||__||w4||w3||__|\n" +ObservationTensor(0): binvec(300, 0x40000000010200000000000420000000000810000000004000200000000000008000000002) +ObservationTensor(1): binvec(300, 0x40000000010200000000000420000000000810000000004000200000000000008000000002) +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [1, 3, 5] +StringLegalActions() = ["B2-diag*", "B2-down*", "B2-right*"] + +# Apply action "B2-down*" +action: 3 + +# State 4 +# Apply action "roll 5" +action: 4 + +# State 5 +# |__||b1||b6||__||__| +# |b2||b5||__||__||__| +# |b3||__||__||__||w2| +# |__||__||__||w1||w6| +# |__||__||w4||w3||__| +IsTerminal() = False +History() = [2, 299, 1, 3, 4] +HistoryString() = "2, 299, 1, 3, 4" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +ObservationString(0) = "|__||b1||b6||__||__|\n|b2||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||__||w1||w6|\n|__||__||w4||w3||__|\n" +ObservationString(1) = "|__||b1||b6||__||__|\n|b2||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||__||w1||w6|\n|__||__||w4||w3||__|\n" +ObservationTensor(0): binvec(300, 0x40000000010100000000000420000000000800000000004000200000000000008000000002) +ObservationTensor(1): binvec(300, 0x40000000010100000000000420000000000800000000004000200000000000008000000002) +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [270, 272, 274, 235, 236, 239] +StringLegalActions() = ["W4-up", "W4-diag", "W4-left", "W6-up*", "W6-diag", "W6-left*"] + +# Apply action "W4-up" +action: 270 + +# State 6 +# Apply action "roll 1" +action: 0 + +# State 7 +# |__||b1||b6||__||__| +# |b2||b5||__||__||__| +# |b3||__||__||__||w2| +# |__||__||w4||w1||w6| +# |__||__||__||w3||__| +IsTerminal() = False +History() = [2, 299, 1, 3, 4, 270, 0] +HistoryString() = "2, 299, 1, 3, 4, 270, 0" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +ObservationString(0) = "|__||b1||b6||__||__|\n|b2||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||w4||w1||w6|\n|__||__||__||w3||__|\n" +ObservationString(1) = "|__||b1||b6||__||__|\n|b2||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||w4||w1||w6|\n|__||__||__||w3||__|\n" +ObservationTensor(0): binvec(300, 0x40000000010100000000000420000000000800000000008000200000000000008000000002) +ObservationTensor(1): binvec(300, 0x40000000010100000000000420000000000800000000008000200000000000008000000002) +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [12, 15, 17] +StringLegalActions() = ["B1-diag", "B1-down*", "B1-right*"] + +# Apply action "B1-right*" +action: 17 + +# State 8 +# Apply action "roll 3" +action: 2 + +# State 9 +# |__||__||b1||__||__| +# |b2||b5||__||__||__| +# |b3||__||__||__||w2| +# |__||__||w4||w1||w6| +# |__||__||__||w3||__| +IsTerminal() = False +History() = [2, 299, 1, 3, 4, 270, 0, 17, 2] +HistoryString() = "2, 299, 1, 3, 4, 270, 0, 17, 2" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +ObservationString(0) = "|__||__||b1||__||__|\n|b2||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||w4||w1||w6|\n|__||__||__||w3||__|\n" +ObservationString(1) = "|__||__||b1||__||__|\n|b2||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||w4||w1||w6|\n|__||__||__||w3||__|\n" +ObservationTensor(0): binvec(300, 0x2000000010100000000000420000000000800000000008000200000000000000000000002) +ObservationTensor(1): binvec(300, 0x2000000010100000000000420000000000800000000008000200000000000000000000002) +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [283, 285, 286] +StringLegalActions() = ["W3-up*", "W3-diag*", "W3-left"] + +# Apply action "W3-left" +action: 286 + +# State 10 +# Apply action "roll 4" +action: 3 + +# State 11 +# |__||__||b1||__||__| +# |b2||b5||__||__||__| +# |b3||__||__||__||w2| +# |__||__||w4||w1||w6| +# |__||__||w3||__||__| +IsTerminal() = False +History() = [2, 299, 1, 3, 4, 270, 0, 17, 2, 286, 3] +HistoryString() = "2, 299, 1, 3, 4, 270, 0, 17, 2, 286, 3" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +ObservationString(0) = "|__||__||b1||__||__|\n|b2||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||w4||w1||w6|\n|__||__||w3||__||__|\n" +ObservationString(1) = "|__||__||b1||__||__|\n|b2||b5||__||__||__|\n|b3||__||__||__||w2|\n|__||__||w4||w1||w6|\n|__||__||w3||__||__|\n" +ObservationTensor(0): binvec(300, 0x2000000010100000000000420000000010000000000008000200000000000000000000002) +ObservationTensor(1): binvec(300, 0x2000000010100000000000420000000010000000000008000200000000000000000000002) +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [120, 122, 124, 72, 74, 76] +StringLegalActions() = ["B3-diag", "B3-down", "B3-right", "B5-diag", "B5-down", "B5-right"] + +# Apply action "B5-right" +action: 76 + +# State 12 +# Apply action "roll 2" +action: 1 + +# State 13 +# Apply action "W2-left" +action: 178 + +# State 14 +# Apply action "roll 3" +action: 2 + +# State 15 +# Apply action "B3-diag" +action: 120 + +# State 16 +# Apply action "roll 6" +action: 5 + +# State 17 +# Apply action "W6-diag*" +action: 237 + +# State 18 +# Apply action "roll 2" +action: 1 + +# State 19 +# Apply action "B2-down" +action: 62 + +# State 20 +# Apply action "roll 3" +action: 2 + +# State 21 +# Apply action "W3-left" +action: 274 + +# State 22 +# Apply action "roll 3" +action: 2 + +# State 23 +# Apply action "B3-diag" +action: 192 + +# State 24 +# Apply action "roll 6" +action: 5 + +# State 25 +# Apply action "W6-diag*" +action: 165 + +# State 26 +# Apply action "roll 6" +action: 5 + +# State 27 +# Apply action "B3-right" +action: 268 + +# State 28 +# Apply action "roll 2" +action: 1 + +# State 29 +# Apply action "W1-diag" +action: 224 + +# State 30 +# Apply action "roll 1" +action: 0 + +# State 31 +# Apply action "B1-diag" +action: 24 + +# State 32 +# Apply action "roll 5" +action: 4 + +# State 33 +# Apply action "W4-diag" +action: 212 + +# State 34 +# Apply action "roll 2" +action: 1 + +# State 35 +# Apply action "B2-down" +action: 122 + +# State 36 +# Apply action "roll 4" +action: 3 + +# State 37 +# Apply action "W4-left" +action: 142 + +# State 38 +# Apply action "roll 2" +action: 1 + +# State 39 +# Apply action "B2-right" +action: 184 + +# State 40 +# Apply action "roll 1" +action: 0 + +# State 41 +# |__||__||__||__||__| +# |__||__||w6||b1||__| +# |w4||__||w1||__||__| +# |__||b2||__||__||__| +# |__||w3||__||b3||__| +IsTerminal() = False +History() = [2, 299, 1, 3, 4, 270, 0, 17, 2, 286, 3, 76, 1, 178, 2, 120, 5, 237, 1, 62, 2, 274, 2, 192, 5, 165, 5, 268, 1, 224, 0, 24, 4, 212, 1, 122, 3, 142, 1, 184, 0] +HistoryString() = "2, 299, 1, 3, 4, 270, 0, 17, 2, 286, 3, 76, 1, 178, 2, 120, 5, 237, 1, 62, 2, 274, 2, 192, 5, 165, 5, 268, 1, 224, 0, 24, 4, 212, 1, 122, 3, 142, 1, 184, 0" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +ObservationString(0) = "|__||__||__||__||__|\n|__||__||w6||b1||__|\n|w4||__||w1||__||__|\n|__||b2||__||__||__|\n|__||w3||__||b3||__|\n" +ObservationString(1) = "|__||__||__||__||__|\n|__||__||w6||b1||__|\n|w4||__||w1||__||__|\n|__||b2||__||__||__|\n|__||w3||__||b3||__|\n" +ObservationTensor(0): binvec(300, 0x80000400002000000000000001000200000000004000000000000000000000000002000) +ObservationTensor(1): binvec(300, 0x80000400002000000000000001000200000000004000000000000000000000000002000) +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [151, 152, 154] +StringLegalActions() = ["W1-up*", "W1-diag", "W1-left"] + +# Apply action "W1-up*" +action: 151 + +# State 42 +# Apply action "roll 6" +action: 5 + +# State 43 +# |__||__||__||__||__| +# |__||__||w1||b1||__| +# |w4||__||__||__||__| +# |__||b2||__||__||__| +# |__||w3||__||b3||__| +IsTerminal() = False +History() = [2, 299, 1, 3, 4, 270, 0, 17, 2, 286, 3, 76, 1, 178, 2, 120, 5, 237, 1, 62, 2, 274, 2, 192, 5, 165, 5, 268, 1, 224, 0, 24, 4, 212, 1, 122, 3, 142, 1, 184, 0, 151, 5] +HistoryString() = "2, 299, 1, 3, 4, 270, 0, 17, 2, 286, 3, 76, 1, 178, 2, 120, 5, 237, 1, 62, 2, 274, 2, 192, 5, 165, 5, 268, 1, 224, 0, 24, 4, 212, 1, 122, 3, 142, 1, 184, 0, 151, 5" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +ObservationString(0) = "|__||__||__||__||__|\n|__||__||w1||b1||__|\n|w4||__||__||__||__|\n|__||b2||__||__||__|\n|__||w3||__||b3||__|\n" +ObservationString(1) = "|__||__||__||__||__|\n|__||__||w1||b1||__|\n|w4||__||__||__||__|\n|__||b2||__||__||__|\n|__||w3||__||b3||__|\n" +ObservationTensor(0): binvec(300, 0x80000800002000000000000001000200000000004000000000000000000000000000000) +ObservationTensor(1): binvec(300, 0x80000800002000000000000001000200000000004000000000000000000000000000000) +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [280] +StringLegalActions() = ["B3-right"] + +# Apply action "B3-right" +action: 280 + +# State 44 +# |__||__||__||__||__| +# |__||__||w1||b1||__| +# |w4||__||__||__||__| +# |__||b2||__||__||__| +# |__||w3||__||__||b3| +IsTerminal() = True +History() = [2, 299, 1, 3, 4, 270, 0, 17, 2, 286, 3, 76, 1, 178, 2, 120, 5, 237, 1, 62, 2, 274, 2, 192, 5, 165, 5, 268, 1, 224, 0, 24, 4, 212, 1, 122, 3, 142, 1, 184, 0, 151, 5, 280] +HistoryString() = "2, 299, 1, 3, 4, 270, 0, 17, 2, 286, 3, 76, 1, 178, 2, 120, 5, 237, 1, 62, 2, 274, 2, 192, 5, 165, 5, 268, 1, 224, 0, 24, 4, 212, 1, 122, 3, 142, 1, 184, 0, 151, 5, 280" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = -4 +ObservationString(0) = "|__||__||__||__||__|\n|__||__||w1||b1||__|\n|w4||__||__||__||__|\n|__||b2||__||__||__|\n|__||w3||__||__||b3|\n" +ObservationString(1) = "|__||__||__||__||__|\n|__||__||w1||b1||__|\n|w4||__||__||__||__|\n|__||b2||__||__||__|\n|__||w3||__||__||b3|\n" +ObservationTensor(0): binvec(300, 0x80000800002000000000000000080200000000004000000000000000000000000000000) +ObservationTensor(1): binvec(300, 0x80000800002000000000000000080200000000004000000000000000000000000000000) +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 f34ad4f153..7b7f7a0f18 100644 --- a/open_spiel/python/tests/pyspiel_test.py +++ b/open_spiel/python/tests/pyspiel_test.py @@ -56,6 +56,7 @@ "dots_and_boxes", "dou_dizhu", "efg_game", + "einstein_wurfelt_nicht", "euchre", "first_sealed_auction", "gin_rummy",