Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make action IDs relative to pawn position #1221

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
66 changes: 54 additions & 12 deletions open_spiel/games/quoridor/quoridor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const GameType kGameType{
GameParameter(GameParameter::Type::kInt, /*is_mandatory=*/false)},
{"ansi_color_output", GameParameter(false)},
{"players", GameParameter(kMinNumPlayers, false)},
{"relative_moves", GameParameter(GameParameter::Type::kBool, false)},
}};

std::shared_ptr<const Game> Factory(const GameParameters& params) {
Expand Down Expand Up @@ -142,11 +143,14 @@ std::string Move::ToString() const {
}

QuoridorState::QuoridorState(std::shared_ptr<const Game> game, int board_size,
int wall_count, bool ansi_color_output)
int wall_count, bool ansi_color_output, bool relative_moves)
: State(game),
board_size_(board_size),
board_diameter_(board_size * 2 - 1),
ansi_color_output_(ansi_color_output) {
ansi_color_output_(ansi_color_output),
relative_moves_(relative_moves),
// See ActionToMove for explanation of the below
base_for_relative_(2, board_diameter_+3, board_diameter_) {
board_.resize(board_diameter_ * board_diameter_, kPlayerNone);
players_.resize(num_players_);
// Account for order of turns (order of play is clockwise)
Expand Down Expand Up @@ -200,14 +204,33 @@ void QuoridorState::InitializePlayer(QuoridorPlayer p) {
}
}

/*
* The original implementation mapped action IDs to absolute board positions.
* This meant that moving "north" had a different ID for every pawn position.
* When the option to make move actions IDs indicate the relative pawn
* movement, the relative moves were mapped "off board" to illegal move
* locations that were at y values beyond the board_diameter. So when we
* get those action IDs in, we need to convert them back into the absolute
* position into which we need to place the pawn.
*/
Move QuoridorState::ActionToMove(Action action_id) const {
return GetMove(action_id % board_diameter_, action_id / board_diameter_);
Move move = GetMove(action_id % board_diameter_, action_id / board_diameter_);
if (!move.IsWall() && relative_moves_) {
Move target = player_loc_[current_player_] + (move - base_for_relative_);
if (GetPlayer(target) == kPlayerNone) {
return target;
} else {
// Jumping over a player is inferred - it has the same action ID as just stepping
return player_loc_[current_player_] + ((move - base_for_relative_) * 2);
}
}
return move;
}

std::vector<Action> QuoridorState::LegalActions() const {
std::vector<Action> moves;
if (IsTerminal()) return moves;
int max_moves = 5; // Max pawn moves, including jumps.
int max_moves = 6; // Max pawn moves, including jumps.
if (wall_count_[current_player_] > 0) {
max_moves += 2 * (board_size_ - 1) * (board_size_ - 1); // Max wall moves.
}
Expand Down Expand Up @@ -261,7 +284,11 @@ void QuoridorState::AddActions(Move cur, Offset offset,
Move forward = cur + offset * 2;
if (GetPlayer(forward) == kPlayerNone) {
// Normal single step in this direction.
moves->push_back(forward.xy);
if (relative_moves_) {
moves->push_back((base_for_relative_ + offset * 2).xy);
} else {
moves->push_back(forward.xy);
}
return;
}

Expand All @@ -271,7 +298,12 @@ void QuoridorState::AddActions(Move cur, Offset offset,
// In two-players: A normal jump is allowed. We know that spot is empty.
// In >2 players, must check.
if (GetPlayer(cur + offset * 4) == kPlayerNone) {
moves->push_back((cur + offset * 4).xy);
if (relative_moves_) {
// The relative action ID for jumping directly over is the same as moving
moves->push_back((base_for_relative_ + offset * 2).xy);
} else {
moves->push_back((cur + offset * 4).xy);
}
return;
} else {
return;
Expand All @@ -283,13 +315,21 @@ void QuoridorState::AddActions(Move cur, Offset offset,
Offset left = offset.rotate_left();
if (!IsWall(forward + left)) {
if (GetPlayer(forward + left * 2) == kPlayerNone) {
moves->push_back((forward + left * 2).xy);
if (relative_moves_) {
moves->push_back((base_for_relative_ + offset * 2 + left * 2).xy);
} else {
moves->push_back((forward + left * 2).xy);
}
}
}
Offset right = offset.rotate_right();
if (!IsWall(forward + right)) {
if (GetPlayer(forward + right * 2) == kPlayerNone) {
moves->push_back((forward + right * 2).xy);
if (relative_moves_) {
moves->push_back((base_for_relative_ + offset * 2 + right * 2).xy);
} else {
moves->push_back((forward + right * 2).xy);
}
}
}
}
Expand Down Expand Up @@ -582,14 +622,14 @@ void QuoridorState::ObservationTensor(Player player,
}

void QuoridorState::DoApplyAction(Action action) {
Move move = ActionToMove(action);
// If players is forced to pass it is valid to stay in place, on a field where
// there is already a player
if (board_[action] != current_player_) {
SPIEL_CHECK_EQ(board_[action], kPlayerNone);
if (board_[move.xy] != current_player_) {
SPIEL_CHECK_EQ(board_[move.xy], kPlayerNone);
}
SPIEL_CHECK_EQ(outcome_, kPlayerNone);

Move move = ActionToMove(action);
SPIEL_CHECK_TRUE(move.IsValid());

if (move.IsWall()) {
Expand Down Expand Up @@ -636,7 +676,9 @@ QuoridorGame::QuoridorGame(const GameParameters& params)
wall_count_(
ParameterValue<int>("wall_count", board_size_ * board_size_ / 8)),
ansi_color_output_(ParameterValue<bool>("ansi_color_output")),
num_players_(ParameterValue<int>("players")) {}
num_players_(ParameterValue<int>("players")),
relative_moves_(ParameterValue<bool>("relative_moves"))
{}

} // namespace quoridor
} // namespace open_spiel
12 changes: 10 additions & 2 deletions open_spiel/games/quoridor/quoridor.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
// "wall_count" int How many walls per side (default = size^2/8)
// "ansi_color_output" bool Whether to color the output for a terminal.
// "players" int Number of players (default = 2)
// "relative_moves" bool Whether move action IDs should be relative to the
// current pawn position or absolute based on target cell.
// The default is false/absolute as that was what was originally
// coded, though relative is probably better for learning.

namespace open_spiel {
namespace quoridor {
Expand Down Expand Up @@ -85,13 +89,14 @@ struct Move {

Move operator+(const Offset& o) const { return Move(x + o.x, y + o.y, size); }
Move operator-(const Offset& o) const { return Move(x - o.x, y - o.y, size); }
Offset operator-(const Move& o) const { return Offset(x - o.x, y - o.y); }
};

// State of an in-play game.
class QuoridorState : public State {
public:
QuoridorState(std::shared_ptr<const Game> game, int board_size,
int wall_count, bool ansi_color_output = false);
int wall_count, bool ansi_color_output = false, bool relative_moves = false);

QuoridorState(const QuoridorState&) = default;
void InitializePlayer(QuoridorPlayer);
Expand Down Expand Up @@ -155,6 +160,8 @@ class QuoridorState : public State {
const int board_size_;
const int board_diameter_;
const bool ansi_color_output_;
const bool relative_moves_;
const Move base_for_relative_;
};

// Game object.
Expand All @@ -165,7 +172,7 @@ class QuoridorGame : public Game {
int NumDistinctActions() const override { return Diameter() * Diameter(); }
std::unique_ptr<State> NewInitialState() const override {
return std::unique_ptr<State>(new QuoridorState(
shared_from_this(), board_size_, wall_count_, ansi_color_output_));
shared_from_this(), board_size_, wall_count_, ansi_color_output_, relative_moves_));
}
int NumPlayers() const override { return num_players_; }
int NumCellStates() const { return num_players_ + 1; }
Expand All @@ -188,6 +195,7 @@ class QuoridorGame : public Game {
const int wall_count_;
const bool ansi_color_output_ = false;
const int num_players_;
const bool relative_moves_ = false;
};

} // namespace quoridor
Expand Down
4 changes: 4 additions & 0 deletions open_spiel/games/quoridor/quoridor_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ void BasicQuoridorTests() {

testing::RandomSimTest(*LoadGame("quoridor(board_size=9,wall_count=5)"), 3);

testing::RandomSimTest(
*LoadGame("quoridor(board_size=9,wall_count=5)"),
3);

// Ansi colors!
testing::RandomSimTest(
*LoadGame("quoridor", {{"board_size", GameParameter(9)},
Expand Down