Skip to content

Commit

Permalink
Change the string representations of Hex to support standard notation…
Browse files Browse the repository at this point in the history
… (and set it as the default).

Keep support for the old string representation (under game parameter string_rep=explicit) for backwards compatibility, if needed.

PiperOrigin-RevId: 708308838
Change-Id: I3e3d39b9c08a4d76da05e71f8eca38e6b4de2146
  • Loading branch information
lanctot committed Jan 6, 2025
1 parent d04c13c commit aae2c1e
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 200 deletions.
7 changes: 4 additions & 3 deletions open_spiel/games/dark_hex/dark_hex.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ DarkHexState::DarkHexState(std::shared_ptr<const Game> game, int num_cols,
int num_rows, GameVersion game_version,
ObservationType obs_type)
: State(game),
state_(game, num_cols, num_rows),
state_(game, num_cols, num_rows, hex::StringRep::kStandard),
obs_type_(obs_type),
game_version_(game_version),
num_cols_(num_cols),
Expand Down Expand Up @@ -145,7 +145,7 @@ void DarkHexState::DoApplyAction(Action move) {
}
}

SPIEL_CHECK_EQ(cur_view[move], CellState::kEmpty);
SPIEL_CHECK_TRUE(cur_view[move] == CellState::kEmpty);
// Update the view - only using CellState::kBlack and CellState::kWhite
if (state_.BoardAt(move) == CellState::kBlack ||
state_.BoardAt(move) == CellState::kBlackNorth ||
Expand Down Expand Up @@ -185,7 +185,8 @@ std::string DarkHexState::ViewToString(Player player) const {

for (int r = 0; r < num_rows_; ++r) {
for (int c = 0; c < num_cols_; ++c) {
absl::StrAppend(&str, StateToString(cur_view[r * num_cols_ + c]));
absl::StrAppend(
&str, StateToString(cur_view[r * num_cols_ + c], state_.StringRep()));
}
if (r < (num_rows_ - 1)) {
absl::StrAppend(&str, "\n");
Expand Down
87 changes: 74 additions & 13 deletions open_spiel/games/hex/hex.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@

#include "open_spiel/games/hex/hex.h"

#include <algorithm>
#include <memory>
#include <utility>
#include <string>
#include <vector>

#include "open_spiel/abseil-cpp/absl/strings/str_cat.h"
#include "open_spiel/game_parameters.h"
#include "open_spiel/observer.h"
#include "open_spiel/spiel.h"
#include "open_spiel/spiel_utils.h"
#include "open_spiel/utils/tensor_view.h"

namespace open_spiel {
Expand All @@ -44,6 +48,7 @@ const GameType kGameType{/*short_name=*/"hex",
{"board_size", GameParameter(kDefaultBoardSize)},
{"num_cols", GameParameter(kDefaultBoardSize)},
{"num_rows", GameParameter(kDefaultBoardSize)},
{"string_rep", GameParameter(kDefaultStringRep)},
}};

std::shared_ptr<const Game> Factory(const GameParameters& params) {
Expand All @@ -54,6 +59,16 @@ REGISTER_SPIEL_GAME(kGameType, Factory);

RegisterSingleTensorObserver single_tensor(kGameType.short_name);

StringRep StringRepStrToEnum(const std::string& string_rep) {
if (string_rep == "standard") {
return StringRep::kStandard;
} else if (string_rep == "explicit") {
return StringRep::kExplicit;
} else {
SpielFatalError(absl::StrCat("Invalid string_rep ", string_rep));
}
}

} // namespace

CellState PlayerToState(Player player) {
Expand Down Expand Up @@ -133,7 +148,27 @@ CellState HexState::PlayerAndActionToState(Player player, Action move) const {
}
}

std::string StateToString(CellState state) {
std::string StateToStringStandard(CellState state) {
switch (state) {
case CellState::kEmpty:
return ".";
case CellState::kWhite:
case CellState::kWhiteWin:
case CellState::kWhiteWest:
case CellState::kWhiteEast:
return "o";
case CellState::kBlack:
case CellState::kBlackWin:
case CellState::kBlackNorth:
case CellState::kBlackSouth:
return "x";
default:
SpielFatalError("Unknown state.");
return "This will never return.";
}
}

std::string StateToStringExplicit(CellState state) {
switch (state) {
case CellState::kEmpty:
return ".";
Expand All @@ -159,8 +194,18 @@ std::string StateToString(CellState state) {
}
}

std::string StateToString(CellState state, StringRep string_rep) {
if (string_rep == StringRep::kExplicit) {
return StateToStringExplicit(state);
} else if (string_rep == StringRep::kStandard) {
return StateToStringStandard(state);
} else {
SpielFatalError("Unknown string_rep.");
}
}

void HexState::DoApplyAction(Action move) {
SPIEL_CHECK_EQ(board_[move], CellState::kEmpty);
SPIEL_CHECK_TRUE(board_[move] == CellState::kEmpty);
CellState move_cell_state = PlayerAndActionToState(CurrentPlayer(), move);
board_[move] = move_cell_state;
if (move_cell_state == CellState::kBlackWin) {
Expand Down Expand Up @@ -208,11 +253,21 @@ std::vector<Action> HexState::LegalActions() const {
}

std::string HexState::ActionToString(Player player, Action action_id) const {
// This does not comply with the Hex Text Protocol
// TODO(author8): Make compliant with HTP
return absl::StrCat(StateToString(PlayerAndActionToState(player, action_id)),
"(", action_id % num_cols_, ",", action_id / num_cols_,
")");
int row = action_id % num_cols_;
int col = action_id / num_cols_;
if (StringRep() == StringRep::kStandard) {
char row_char = static_cast<char>(static_cast<int>('a') + row);
std::string row_str;
row_str += row_char;
std::string ret = absl::StrCat(row_str, col + 1);
return ret;
} else if (StringRep() == StringRep::kExplicit) {
return absl::StrCat(
StateToString(PlayerAndActionToState(player, action_id), StringRep()),
"(", row, ",", col, ")");
} else {
SpielFatalError("Unknown string_rep.");
}
}

std::vector<int> HexState::AdjacentCells(int cell) const {
Expand All @@ -230,8 +285,12 @@ std::vector<int> HexState::AdjacentCells(int cell) const {
return neighbours;
}

HexState::HexState(std::shared_ptr<const Game> game, int num_cols, int num_rows)
: State(game), num_cols_(num_cols), num_rows_(num_rows) {
HexState::HexState(std::shared_ptr<const Game> game, int num_cols, int num_rows,
enum StringRep string_rep)
: State(game),
num_cols_(num_cols),
num_rows_(num_rows),
string_rep_(string_rep) {
// for all num_colss & num_rowss -> num_colss_ >= num_rowss_
board_.resize(num_cols * num_rows, CellState::kEmpty);
}
Expand All @@ -249,7 +308,7 @@ std::string HexState::ToString() const {
line_num++;
absl::StrAppend(&str, std::string(line_num, ' '));
}
absl::StrAppend(&str, StateToString(board_[cell]));
absl::StrAppend(&str, StateToString(board_[cell], string_rep_));
absl::StrAppend(&str, " ");
}
return str;
Expand Down Expand Up @@ -296,7 +355,9 @@ HexGame::HexGame(const GameParameters& params)
num_cols_(
ParameterValue<int>("num_cols", ParameterValue<int>("board_size"))),
num_rows_(
ParameterValue<int>("num_rows", ParameterValue<int>("board_size"))) {}
ParameterValue<int>("num_rows", ParameterValue<int>("board_size"))),
string_rep_(StringRepStrToEnum(
ParameterValue<std::string>("string_rep", kDefaultStringRep))) {}

} // namespace hex
} // namespace open_spiel
36 changes: 27 additions & 9 deletions open_spiel/games/hex/hex.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
#ifndef OPEN_SPIEL_GAMES_HEX_H_
#define OPEN_SPIEL_GAMES_HEX_H_

#include <array>
#include <map>
#include <memory>
#include <ostream>
#include <string>
#include <vector>

#include "open_spiel/abseil-cpp/absl/types/optional.h"
#include "open_spiel/game_parameters.h"
#include "open_spiel/spiel.h"
#include "open_spiel/spiel_globals.h"
#include "open_spiel/spiel_utils.h"

// The classic game of Hex: https://en.wikipedia.org/wiki/Hex_(board_game)
// Does not implement pie rule to balance the game
Expand All @@ -30,6 +33,9 @@
// "board_size" int size of the board (default = 11)
// "num_cols" int number of columns (optional)
// "num_rows" int number of rows (optional)
// "string_rep" string representation of the action and board strings
// ("standard" (default) | "explicit"). See below
// for details.

namespace open_spiel {
namespace hex {
Expand All @@ -41,6 +47,8 @@ inline constexpr int kMaxNeighbours =
6; // Maximum number of neighbours for a cell
inline constexpr int kCellStates = 1 + 4 * kNumPlayers;
inline constexpr int kMinValueCellState = -4;
inline constexpr const char* kDefaultStringRep = "standard";

// State of a cell.
// Describes if a cell is
// - empty, black or white
Expand All @@ -62,10 +70,19 @@ enum class CellState {
kBlack = 1, // Black and not edge connected
};

// The string representations of the game. Standard uses normal stones and
// chess-like action coordinates ('a1'). Explicit uses different stones
// depending on the state of each stone and uses the full cell coordinates.
enum class StringRep {
kStandard = 0,
kExplicit = 1,
};

// State of an in-play game.
class HexState : public State {
public:
HexState(std::shared_ptr<const Game> game, int num_cols, int num_rows);
HexState(std::shared_ptr<const Game> game, int num_cols, int num_rows,
StringRep string_rep);

HexState(const HexState&) = default;

Expand All @@ -85,19 +102,22 @@ class HexState : public State {

CellState BoardAt(int cell) const { return board_[cell]; }
void ChangePlayer() { current_player_ = current_player_ == 0 ? 1 : 0; }
StringRep StringRep() const { return string_rep_; }

protected:
std::vector<CellState> board_;
void DoApplyAction(Action move) override;

private:
CellState PlayerAndActionToState(Player player, Action move) const;

Player current_player_ = 0; // Player zero goes first
double result_black_perspective_ = 0; // 1 if Black (player 0) wins
std::vector<int> AdjacentCells(int cell) const; // Cells adjacent to cell

const int num_cols_; // x
const int num_rows_; // y
const enum StringRep string_rep_;
};

// Game object.
Expand All @@ -107,7 +127,7 @@ class HexGame : public Game {
int NumDistinctActions() const override { return num_cols_ * num_rows_; }
std::unique_ptr<State> NewInitialState() const override {
return std::unique_ptr<State>(
new HexState(shared_from_this(), num_cols_, num_rows_));
new HexState(shared_from_this(), num_cols_, num_rows_, string_rep_));
}
int NumPlayers() const override { return kNumPlayers; }
double MinUtility() const override { return -1; }
Expand All @@ -117,18 +137,16 @@ class HexGame : public Game {
return {kCellStates, num_cols_, num_rows_};
}
int MaxGameLength() const override { return num_cols_ * num_rows_; }
StringRep string_rep() const { return string_rep_; }

private:
const int num_cols_;
const int num_rows_;
const enum StringRep string_rep_;
};

CellState PlayerToState(Player player);
std::string StateToString(CellState state);

inline std::ostream& operator<<(std::ostream& stream, const CellState& state) {
return stream << StateToString(state);
}
std::string StateToString(CellState state, StringRep string_rep);

} // namespace hex
} // namespace open_spiel
Expand Down
5 changes: 5 additions & 0 deletions open_spiel/games/hex/hex_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <iostream>
#include <memory>

#include "open_spiel/game_parameters.h"
#include "open_spiel/spiel.h"
#include "open_spiel/spiel_utils.h"
#include "open_spiel/tests/basic_tests.h"

namespace open_spiel {
Expand Down
Loading

0 comments on commit aae2c1e

Please sign in to comment.