Skip to content

Commit

Permalink
First pass at expectimax implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Nathan committed Jun 28, 2021
1 parent 3b38d2b commit c149452
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 9 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The original project (as well as this one) was created with the goal of implemen
algorithms to achieve as high of a score as possible in the game 2048. Game.cpp
contains the code for the game logic, Heuristics.cpp contains several heuristic
metrics/functions to evaluate game state quality, and algorithm implementations are
split between MonteCarlo.cpp and Minimax.cpp.
split between MonteCarlo.cpp, Minimax.cpp, and Expectimax.cpp.

If unfamiliar with the game rules, check out [2048](https://play2048.co/).

Expand All @@ -34,7 +34,7 @@ AISolver < flag_1 > < flag_1_val > < flag_2 > < flag_2_val > ...
```

Flag list:
* -a: Integer/String value; Algorithm to run. 0 = MonteCarlo, 1 = Minimax
* -a: Integer/String value; Algorithm to run. 0 = MonteCarlo, 1 = Minimax, 2 = Expectimax
* -n: Integer value; # times to run the algorithm. Stats displayed at program completion
* -r: Integer value; # runs MonteCarlo completes for each move. Higher=better but slower. Recommend 10-100
* -d: Integer value; # depth level for Minimax / Expectimax
Expand Down
14 changes: 14 additions & 0 deletions include/Expectimax.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef EXPECTIMAX_H
#define EXPECTIMAX_H

#include <iostream>
#include <limits>
#include "Game.hpp"
#include "Heuristics.hpp"

float expectimaxScore(int depth, Game game, bool is_max);
std::pair<int, int> expectimaxSearch(int depth, int display_level, Game game);
int expectimaxSolve(int n, int depth, int display_level,
std::vector<int> *scores, std::vector<int> *highest_tiles);

#endif
151 changes: 151 additions & 0 deletions src/Expectimax.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#include "Expectimax.hpp"

float expectimaxScore(int depth, Game game, bool is_max) {
std::map<int, weightedmoves> possibilities = game.computePossibilities();
if (possibilities.size() == 0) {
return 0.0;
}
else if (is_max) {
if (depth == 0) {
return get_h_score(game);
}
else {
std::map<int, float> scores;
for (auto const& entry : possibilities) {
int move = entry.first; // Direction of move
weightedmoves weighted_subset = entry.second; // Vector of (prob, game)
int len = (int) weighted_subset.size();
if (len > 0) {
scores[move] = 0.0;
for (int j = 0; j < len; ++j) {
scores[move] += get_h_score(weighted_subset[j].second) * weighted_subset[j].first;
}
}
}

float max_score = -std::numeric_limits<float>::max();
int max_choice = UP;
for (auto const& score : scores) { // Determine best move choice based on tabulated scores
if (score.second > max_score) {
max_score = score.second;
max_choice = score.first;
}
}

switch (max_choice) {
case UP:
game.up(false);
break;
case DOWN:
game.down(false);
break;
case LEFT:
game.left(false);
break;
case RIGHT:
game.right(false);
}

return expectimaxScore(depth - 1, game, false);
}
}
else {
std::map<int, float> scores;
for (auto const& entry : possibilities) {
int move = entry.first; // Direction of move
weightedmoves weighted_subset = entry.second; // Vector of (prob, game)
int len = (int) weighted_subset.size();
if (len > 0) {
scores[move] = 0.0;
for (int j = 0; j < len; ++j) {
scores[move] += expectimaxScore(depth, weighted_subset[j].second, true) * weighted_subset[j].first;
}
}
}
float sum = 0.0;
for (auto const& score : scores) {
sum += score.second;
}
return sum / scores.size();
}
}

std::pair<int, int> expectimaxSearch(int depth, int display_level, Game game) {
std::cout << "Attempting to solve a new game with Expectimax... " << std::flush;
while (game.canContinue()) {
if (display_level >= 2) {
std::cout << std::endl << game;
}

std::map<int, weightedmoves> possibilities = game.computePossibilities();
std::map<int, float> scores;

for (auto const& entry : possibilities) {
int move = entry.first; // Direction of move
weightedmoves weighted_subset = entry.second; // Vector of (prob, game)
int len = (int) weighted_subset.size();
if (len > 0) {
scores[move] = 0.0;
for (int j = 0; j < len; ++j) {
scores[move] += expectimaxScore(depth, weighted_subset[j].second, true) * weighted_subset[j].first;
}
}
}
if (display_level >= 3) {
std::cout << "Heuristic score: " << get_h_score(game) << std::endl << "Move scores: ";
for (auto const& score : scores) {
std::cout << score.first << ": " << score.second << ", ";
}
std::cout << std::endl;
}

float max_score = -std::numeric_limits<float>::max();
int max_choice = UP;
for (auto const& score : scores) { // Determine best move choice based on tabulated scores
if (score.second > max_score) {
max_score = score.second;
max_choice = score.first;
}
}

switch (max_choice) {
case UP:
game.up(false);
break;
case DOWN:
game.down(false);
break;
case LEFT:
game.left(false);
break;
case RIGHT:
game.right(false);
}
}
if (display_level <= 1) {
std::cout << "Done!" << (display_level == 0 ? "\n" : "");
}
if (display_level >= 1) {
std::cout << std::endl << game << std::endl;
}
return std::pair<int, int>(game.getHighestTile(), game.score);
}

/**
* Creates and completes n-many games using the Expectimax simulation function.
* Tabulates data from game each in order to display results at completion.
*/
int expectimaxSolve(int n, int depth, int display_level,
std::vector<int> *scores, std::vector<int> *highest_tiles) {

int successes = 0;
for (int i = 0; i < n; ++i) {
std::pair<int, int> result = expectimaxSearch(depth, display_level, Game());
if (result.first >= WIN) {
successes++;
}
(*highest_tiles).push_back(result.first);
(*scores).push_back(result.second);
}
return successes;
}
9 changes: 6 additions & 3 deletions src/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "Game.hpp"
#include "MonteCarlo.hpp"
#include "Minimax.hpp"
#include "Expectimax.hpp"

using namespace std::chrono;

Expand Down Expand Up @@ -50,8 +51,8 @@ class CmdParser {
};

int main(int argc, char** argv) {
enum ALGORITHMS {MONTECARLO = 0, MINIMAX = 1};
std::map<std::string, int> alg_map = {{"montecarlo", MONTECARLO}, {"minimax", MINIMAX}};
enum ALGORITHMS {MONTECARLO, MINIMAX, EXPECTIMAX};
std::map<std::string, int> alg_map = {{"montecarlo", MONTECARLO}, {"minimax", MINIMAX}, {"expectimax", EXPECTIMAX}};
int alg_key = MINIMAX;
int num_games = 1;
int num_runs = 25;
Expand All @@ -65,7 +66,7 @@ int main(int argc, char** argv) {
std::string msg = "Usage:\
\n AISolver <flag> <flag_val> ...\
\n Flag list:\
\n -a: Integer/String value; Algorithm to run. 0=montecarlo, 1=minimax\
\n -a: Integer/String value; Algorithm to run. 0=montecarlo, 1=minimax, 2=expectimax\
\n -n: Integer value; # times to run the algorithm. Stats displayed at program completion\
\n -r: Integer value; # runs MonteCarlo completes for each move. Higher=better but slower. Recommend 10-100\
\n -d: Integer value; # depth level for Minimax / Expectimax\
Expand Down Expand Up @@ -121,6 +122,8 @@ int main(int argc, char** argv) {
case MINIMAX:
successes = minimaxSolve(num_games, depth, print_level, &scores, &highest_tiles);
break;
case EXPECTIMAX:
successes = expectimaxSolve(num_games, depth, print_level, &scores, &highest_tiles);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
Expand Down
4 changes: 0 additions & 4 deletions src/Minimax.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,6 @@ std::pair<int, int> minimaxSearch(int depth, int display_level, Game game) {
case RIGHT:
game.right(false);
}

// if (display_level >= 2) {
// std::cout << std::endl;
// }
}
if (display_level <= 1) {
std::cout << "Done!" << (display_level == 0 ? "\n" : "");
Expand Down

0 comments on commit c149452

Please sign in to comment.