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

Adding algorithm TD Learning with N-Tuple Networks for 2048 #1107

Merged
merged 12 commits into from
Sep 12, 2023
34 changes: 31 additions & 3 deletions open_spiel/games/twenty_forty_eight/2048.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ namespace open_spiel {
namespace twenty_forty_eight {
namespace {

enum Move { kMoveUp = 0, kMoveRight = 1, kMoveDown = 2, kMoveLeft = 3 };

constexpr std::array<Action, 4> kPlayerActions = {kMoveUp, kMoveRight,
kMoveDown, kMoveLeft};

Expand Down Expand Up @@ -228,6 +226,29 @@ void TwentyFortyEightState::DoApplyAction(Action action) {
total_actions_++;
}

bool TwentyFortyEightState::DoesActionChangeBoard(Action action) const {
const std::array<std::array<int, 4>, 2>& traversals = kTraversals[action];
for (int r : traversals[0]) {
for (int c : traversals[1]) {
int tile = GetCellContent(r, c);
if (tile > 0) {
std::array<Coordinate, 2> positions =
FindFarthestPosition(r, c, action);
Coordinate farthest_pos = positions[0];
Coordinate next_pos = positions[1];
int next_cell = GetCellContent(next_pos.row, next_pos.column);
if (next_cell > 0 && next_cell == tile &&
!BoardAt(next_pos).is_merged) {
return true;
} else if (farthest_pos.row != r || farthest_pos.column != c) {
return true;
}
}
}
}
return false;
}

std::string TwentyFortyEightState::ActionToString(Player player,
Action action_id) const {
if (player == kChancePlayerId) {
Expand Down Expand Up @@ -295,7 +316,14 @@ std::vector<Action> TwentyFortyEightState::LegalActions() const {
}

// Construct a vector from the array.
return std::vector<Action>(kPlayerActions.begin(), kPlayerActions.end());
std::vector<Action> actions = std::vector<Action>(kPlayerActions.begin(), kPlayerActions.end());

std::vector<Action> actions_allowed = {};
for (Action action: actions) {
if (DoesActionChangeBoard(action))
actions_allowed.push_back(action);
}
return actions_allowed;
}

std::string TwentyFortyEightState::ToString() const {
Expand Down
3 changes: 3 additions & 0 deletions open_spiel/games/twenty_forty_eight/2048.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
namespace open_spiel {
namespace twenty_forty_eight {

enum Move { kMoveUp = 0, kMoveRight = 1, kMoveDown = 2, kMoveLeft = 3 };

constexpr int kNumPlayers = 1;
constexpr int kRows = 4;
constexpr int kColumns = 4;
Expand Down Expand Up @@ -124,6 +126,7 @@ class TwentyFortyEightState : public State {
bool TileMatchesAvailable() const;
void PrepareTiles();
int GetCellContent(int r, int c) const;
bool DoesActionChangeBoard(Action action) const;

const TwentyFortyEightGame& parent_game_;
Player current_player_ = kChancePlayerId;
Expand Down
22 changes: 6 additions & 16 deletions open_spiel/games/twenty_forty_eight/2048_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ void MultipleMergePossibleTest() {
TwentyFortyEightState* cstate =
static_cast<TwentyFortyEightState*>(state.get());
cstate->SetCustomBoard({0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0});
cstate->ApplyAction(cstate->LegalActions()[2]);
cstate->ApplyAction(kMoveDown);
SPIEL_CHECK_EQ(cstate->BoardAt(3, 0).value, 4);
}

Expand All @@ -78,7 +78,7 @@ void OneMergePerTurnTest() {
TwentyFortyEightState* cstate =
static_cast<TwentyFortyEightState*>(state.get());
cstate->SetCustomBoard({2, 4, 0, 4, 0, 2, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0});
cstate->ApplyAction(cstate->LegalActions()[2]);
cstate->ApplyAction(kMoveDown);
SPIEL_CHECK_EQ(cstate->BoardAt(2, 1).value, 4);
SPIEL_CHECK_EQ(cstate->BoardAt(3, 1).value, 4);
}
Expand Down Expand Up @@ -112,7 +112,7 @@ void GameWonTest() {
static_cast<TwentyFortyEightState*>(state.get());
cstate->SetCustomBoard(
{4, 8, 2, 4, 2, 4, 8, 16, 1024, 128, 64, 128, 1024, 8, 2, 8});
cstate->ApplyAction(cstate->LegalActions()[2]);
cstate->ApplyAction(kMoveDown);
SPIEL_CHECK_EQ(cstate->IsTerminal(), true);
SPIEL_CHECK_EQ(cstate->Returns()[0], 2048);
}
Expand All @@ -122,26 +122,16 @@ void GameWonTest() {
// 0 0 0 0
// 0 0 0 0
// 2 0 0 2
// No random tiles should appear if the board didn't change after player move
// Down should not be a legal action here as it does not change the board
void BoardNotChangedTest() {
std::shared_ptr<const Game> game = LoadGame("2048");
std::unique_ptr<State> state = game->NewInitialState();
TwentyFortyEightState* cstate =
static_cast<TwentyFortyEightState*>(state.get());
cstate->SetCustomBoard({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2});
cstate->ApplyAction(cstate->LegalActions()[2]);
// Check the board remained the same after player move
for (int r = 0; r < kRows; r++) {
for (int c = 0; c < kColumns; c++) {
if (!(r == 3 && c == 0) && !(r == 3 || c == 3)) {
SPIEL_CHECK_EQ(cstate->BoardAt(r, c).value, 0);
}
}
for (Action action : cstate->LegalActions()) {
SPIEL_CHECK_NE(action, kMoveDown);
}
SPIEL_CHECK_EQ(cstate->BoardAt(3, 0).value, 2);
SPIEL_CHECK_EQ(cstate->BoardAt(3, 3).value, 2);
// Check move didn't go to random player since board didn't change
SPIEL_CHECK_EQ(cstate->CurrentPlayer(), 0);
}

} // namespace
Expand Down
Loading