I have written a working minimax tree for Tic-Tac-Toe. What I have done so far is create a tree with end node having value 10, 0 or -10 base on win, lose or draw. After I create a tree I then determine the move based on depth first search which bubbles the end node value to root node. Based on the value received at the root node I determine which ever has the max possibility of winning (10 win or -10 loss or 0 for draw).
Before going into coding detail I would like to explain that I have another class called TicTacToe.cpp/hpp which runs the game engine. When the user selects single player and it's the computer's turn it calls
minimax(int &y, int &y, std::vector<std::vector<TicTacToe::state>> v);
function which will eventually determine x and y co-ordinates.
The minimax function takes x and y co-ordinate and the 2d-vector which has the present state of the tictactoe ( enum of HASX, HASO and EMPTY). This helps to determine which space is taken by what.
void Tree::minimax(int& x, int& y, std::vector<std::vector<TicTacToe::state> > v){
bool turn = true;
std::shared_ptr<Node> n = std::make_shared<Node>();
n->v = v;
n->val = 9999;
int max = -10000;
// creates the tree
create_tree(n, find_empty_vec(v), turn);
// does the depth first search on the above tree
depth_first_search(n, turn);
for(auto it = n->collect_nodes.begin(); it != n->collect_nodes.end(); it++){
if((*it)->val == 10 || (*it)->val == 0) {
if( (*it)->val > max){
max = (*it)->val;
x = (*it)->x;
y = (*it)->y;
}
}
}
}
When minimax is called it eventually calls create_tree which create a tree. Tree is made of node
struct Node{
// basically set of children this node have
std::set<std::shared_ptr<Node>> collect_nodes;
// stores current game state with each index
// containing either EMPTY, HASX or HASO
std::vector<std::vector<TicTacToe::state> > v;
int val;
int x;
int y;
};
The create_tree function takes the node which has snap shot of the tictactoe state.
// This function creates the minimax tree, ev is just the vector of
// EMPTY space that are in n->v ( see struct node above )
void Tree::create_tree(std::shared_ptr<Node> n, std::vector<std::pair<int, int>> ev, bool turn){
static int count = 0;
// loss
// game_state is function that determine who won, loss or draw
// by looking at the 2d vector that I have passed here.
if(game_state(n->v) == 1){
n->val = -10;
return;
}
// win
else if(game_state(n->v) == 2){
n->val = 10;
return;
}
// draw
else if(game_state(n->v) == 3){
n->val = 0;
return;
}
for(auto it = ev.begin(); it != ev.end(); it++){
if(turn) {
// HASO turn
std::shared_ptr<Node> node = std::make_shared<Node>();
node->v = n->v;
node->val = -9999;
node->v[it->first][it->second] = TicTacToe::HASO;
node->x = it->first;
node->y = it->second;
n->collect_nodes.insert(node);
create_tree(node, find_empty_vec(node->v), false);
}else{
std::shared_ptr<Node> node = std::make_shared<Node>();
node->v = n->v;
node->val = 9999;
node->v[it->first][it->second] = TicTacToe::HASX;
node->x = it->first;
node->y = it->second;
n->collect_nodes.insert(node);
create_tree(node, find_empty_vec(node->v), true);
}
}
}
After the tree has been made then I do the dfs over the tree
int Tree::depth_first_search(std::shared_ptr<Node> n, bool turn){
if(n->val == -10 || n->val == 0 || n->val == 10) return n->val;
else if(turn){
int max = -10000000; // I could use numeric_limits here
for(auto it = n->collect_nodes.begin(); it != n->collect_nodes.end(); it++){
if(depth_first_search(*it, false) > max)
max = depth_first_search(*it, false);
}
n->val = max;
return max;
}else{
int min = 10000000;
for(auto it = n->collect_nodes.begin(); it != n->collect_nodes.end(); it++){
if(depth_first_search(*it, true) < min)
min = depth_first_search(*it, true);
}
n->val = min;
return min;
}
}
I do have few concerns about the way I have done things here:
I create tree every time its computer's turn. I was thinking about reusing previously dynamically allocated node tree by reassigning with new state. ( I am still confused what should be root in those case, since I have to determine which node in the tree determine the current state of tictactoe)
Is the dfs algorithm I have used a good design or good way to do ? It would be great if someone critique on it.
I also wanted to create a difficulty level like easy, moderate and hard so user can pick those level to play.
For more detail you can visit the my git repo.