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

FEATURE - Negamax for Tic-Tac-Toe #1

Merged
merged 13 commits into from
Sep 4, 2019
24 changes: 24 additions & 0 deletions AdversarialSearch/Minimax/BitMath.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <math.h>

namespace BitMath
{
int reverseBits(const int value, int maxBits)
{
int reversedBits = 0;
maxBits = (maxBits % 2 == 0) ? maxBits : maxBits - 1;
int halfBits = floor(maxBits / 2);
for (int bit = 0; bit < halfBits; bit++)
{
int transposeDifference = (maxBits - (bit * 2) - 1);

int rBit = value & (1 << bit);
int lBit = rBit << transposeDifference;
reversedBits |= lBit;

lBit = value & (1 << (maxBits - 1 - bit));
rBit = lBit >> transposeDifference;
reversedBits |= rBit;
}
return reversedBits;
};
};
16 changes: 16 additions & 0 deletions AdversarialSearch/Minimax/Main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "MinimaxBenchmarker.h"
#include "BitMath.h"

int main()
{
TicTacToeMinimax* minimax = new TicTacToeMinimax();
MinimaxBenchmarker* benchmarker = new MinimaxBenchmarker();

benchmarker -> benchmarkMinimaxVsMinimax(minimax, 0, true);
benchmarker -> benchmarkEvaluate(minimax, 0, true);
benchmarker -> benchmarkEvaluate(minimax, 0b100000110000100011, true);
benchmarker -> benchmarkEvaluate(minimax, 0b100000110011100011, false);
benchmarker -> benchmarkEvaluate(minimax, 0b100000110011101011, true);

return 0;
};
104 changes: 104 additions & 0 deletions AdversarialSearch/Minimax/MinimaxBenchmarker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#include "MinimaxBenchmarker.h"

void MinimaxBenchmarker::benchmarkMinimaxVsMinimax(TicTacToeMinimax* ticTacToeMinimax, int board, bool isMaxTurn)
{
auto start = chrono::high_resolution_clock::now();
int firstBoard = board;

srand (time(NULL));

int currentBoard = firstBoard;
int nextBoard;

int state = ticTacToeMinimax -> getState(currentBoard);
while (state == Playing)
{
vector<int> bestMoves = ticTacToeMinimax -> evaluateAll(currentBoard, isMaxTurn);
isMaxTurn = !isMaxTurn;

int randomIndex = rand() % bestMoves.size();
currentBoard = bestMoves[randomIndex];

state = ticTacToeMinimax -> getState(currentBoard);
assert(state == Playing || state == Draw);
}

nextBoard = currentBoard;
auto finish = chrono::high_resolution_clock::now();

printBoard(firstBoard);
printBoard(nextBoard);
printState(ticTacToeMinimax -> getState(nextBoard));

auto milliseconds = chrono::duration_cast<chrono::milliseconds>(finish - start);
auto nanoseconds = chrono::duration_cast<chrono::nanoseconds>(finish - start);
cout << "benchmarkMinimaxVsMinimax: " << milliseconds.count() << "ms / " << nanoseconds.count() << "ns" << endl;
cout << endl << endl;
};

void MinimaxBenchmarker::benchmarkEvaluate(TicTacToeMinimax* ticTacToeMinimax, int board, bool isMaxTurn)
{
auto start = chrono::high_resolution_clock::now();
int nextBoard = ticTacToeMinimax -> evaluate(board, isMaxTurn);
auto finish = chrono::high_resolution_clock::now();
auto milliseconds = chrono::duration_cast<chrono::milliseconds>(finish - start);
auto nanoseconds = chrono::duration_cast<chrono::nanoseconds>(finish - start);

printBoard(board);
printBoard(nextBoard);
cout << endl << endl;

cout << "benchmarkEvaluate: " << milliseconds.count() << "ms / " << nanoseconds.count() << "ns" << endl;
};

void MinimaxBenchmarker::benchmarkEvaluateAll(TicTacToeMinimax* ticTacToeMinimax, int board, bool isMaxTurn)
{
auto start = chrono::high_resolution_clock::now();
vector<int> bestBoards = ticTacToeMinimax -> evaluateAll(board, isMaxTurn);
auto finish = chrono::high_resolution_clock::now();

printBoard(board);
for (int x = 0; x < bestBoards.size(); x++)
{
printBoard(bestBoards[x]);
}

auto milliseconds = chrono::duration_cast<chrono::milliseconds>(finish - start);
auto nanoseconds = chrono::duration_cast<chrono::nanoseconds>(finish - start);
cout << "benchmarkEvaluateAll: " << milliseconds.count() << "ms / " << nanoseconds.count() << "ns" << endl;
cout << endl << endl;
};

void MinimaxBenchmarker::printBoard(int board)
{
int crossMask = 3;
int circleMask = 2;

cout << endl;
for (int x = 0; x < 9; x++)
{
if (x > 0 && x % 3 == 0) cout << endl;

if ((board & crossMask) == 0)
{
cout << "[ ]";
}
else
{
if ((board & crossMask) == crossMask) cout << "[X]";
else cout << "[O]";
}

crossMask <<= 2;
circleMask <<= 2;
}
cout << endl;
};

void MinimaxBenchmarker::printState(int state)
{
if (state == Playing) cout << "Playing" << endl;
else if (state == Draw) cout << "Draw" << endl;
else if (state == CrossWins) cout << "CrossWins" << endl;
else cout << "CircleWins" << endl;
};
19 changes: 19 additions & 0 deletions AdversarialSearch/Minimax/MinimaxBenchmarker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include <vector>
#include <random>
#include <time.h>
#include <iostream>
#include <stdio.h>
#include <chrono>
#include "TicTacToeMinimax.h"

using namespace std;

class MinimaxBenchmarker
{
public:
void benchmarkMinimaxVsMinimax(TicTacToeMinimax* ticTacToeMinimax, int board, bool isMaxTurn);
void benchmarkEvaluate(TicTacToeMinimax* ticTacToeMinimax, int board, bool isMaxTurn);
void benchmarkEvaluateAll(TicTacToeMinimax* ticTacToeMinimax, int board, bool isMaxTurn);
void printBoard(int board);
void printState(int state);
};
203 changes: 203 additions & 0 deletions AdversarialSearch/Minimax/TicTacToeMinimax.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#include "TicTacToeMinimax.h"

const int DrawBoardState = 0b101010101010101010;

const int winnerMasks[8] =
{
0b101010000000000000,
0b000000101010000000,
0b000000000000101010,
0b100000100000100000,
0b001000001000001000,
0b000010000010000010,
0b100000001000000010,
0b000010001000100000
};

int TicTacToeMinimax::evaluate(int board, bool isMaxTurn)
{
return isMaxTurn ? evaluateMaxDecision(board)
: evaluateMinDecision(board);
};

std::vector<int> TicTacToeMinimax::evaluateAll(int board, bool isMaxTurn)
{
return isMaxTurn ? evaluateMaxDecisions(board)
: evaluateMinDecisions(board);
};

std::vector<int> TicTacToeMinimax::evaluateMaxDecisions(int board)
{
std::vector<int> bestMoves;
int bestMoveScore = INT_MIN;
int mask = 3;

for (int x = 0; x < 9; x++)
{
if ((board & mask) == 0)
{
int newBoard = (board | mask);
int score = getMinTreeScore(newBoard, -100, 100);

if (score > bestMoveScore)
{
bestMoveScore = score;

bestMoves.clear();
bestMoves.push_back(newBoard);
}
else if (score == bestMoveScore)
{
bestMoves.push_back(newBoard);
}
}
mask <<= 2;
}
return bestMoves;
};

std::vector<int> TicTacToeMinimax::evaluateMinDecisions(int board)
{
std::vector<int> bestMoves;
int bestMoveScore = INT_MAX;
int mask = 2;

for (int x = 0; x < 9; x++)
{
if ((board & mask) == 0)
{
int newBoard = (board | mask);
int score = getMaxTreeScore(newBoard, -100, 100);

if (score < bestMoveScore)
{
bestMoveScore = score;

bestMoves.clear();
bestMoves.push_back(newBoard);
}
else if (score == bestMoveScore)
{
bestMoves.push_back(newBoard);
}
}
mask <<= 2;
}
return bestMoves;
};

int TicTacToeMinimax::evaluateMaxDecision(int board)
{
int bestMove = 0;
int bestMoveScore = INT_MIN;
int mask = 3;

for (int x = 0; x < 9; x++)
{
if ((board & mask) == 0)
{
int score = getMinTreeScore(board | mask, -100, 100);

if (score > bestMoveScore)
{
bestMoveScore = score;
bestMove = (board | mask);
}
}
mask <<= 2;
}
return bestMove;
};

int TicTacToeMinimax::evaluateMinDecision(int board)
{
int bestMove = 0;
int bestMoveScore = INT_MAX;
int mask = 2;

for (int x = 0; x < 9; x++)
{
if ((board & mask) == 0)
{
int score = getMaxTreeScore(board | mask, -100, 100);

if (score < bestMoveScore)
{
bestMoveScore = score;
bestMove = (board | mask);
}
}
mask <<= 2;
}
return bestMove;
};

int TicTacToeMinimax::getMaxTreeScore(int board, int alpha, int beta)
{
int state = getState(board);
if (state != Playing) return getScore(state);

int bestScore = INT_MIN;
int mask = 3;

for (int x = 0; x < 9; x++)
{
if ((board & mask) == 0)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this a constant

{
int score = getMinTreeScore(board | mask, alpha, beta);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest make this bitshift operation a constant variable inside the loop so it is easier to debug its value

if (score >= beta) return score;
alpha = fmax(alpha, score);
bestScore = fmax(bestScore, score);
}
mask <<= 2;
}
return bestScore;
};

int TicTacToeMinimax::getMinTreeScore(int board, int alpha, int beta)
{
int state = getState(board);
if (state != Playing) return getScore(state);

int bestScore = INT_MAX;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it const. It can make the compiler use the raw value instead of a pointer to a variable

int mask = 2;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing here. Make it const

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't make this field constant because we are using it on bitshift operations inside this method.


for (int x = 0; x < 9; x++)
{
if ((board & mask) == 0)
{
int score = getMaxTreeScore(board | mask, alpha, beta);
if (score <= alpha) return score;
beta = fmin(beta, score);
bestScore = fmin(bestScore, score);
}
mask <<= 2;
}
return bestScore;
};

int TicTacToeMinimax::getState(int board)
{
for(int x = 0; x < 8; x++)
{
// check if the board represent a winner state
int winnerMask = winnerMasks[x];
if ((winnerMask & board) != winnerMask) continue;

// check if pieces are the same
winnerMask = winnerMask >> 1;
int piecesXor = (winnerMask ^ board) & winnerMask;

if (piecesXor == 0) return CrossWins;
if (piecesXor == winnerMask) return CircleWins;
}
if ((board & DrawBoardState) == DrawBoardState) return Draw;
return Playing;
};

int TicTacToeMinimax::getScore(int state)
{
if (state == CrossWins) return 1;
if (state == CircleWins) return -1;
return 0;
};
Loading