I have added a transposition table to my TicTacToe minmax algorithm
int AI::findBestMove()
{
hash = tTable->recalculateHash();
int bestMove = minMax().second;
return bestMove;
}
std::pair<int, int> AI::minMax(int reverseDepth, std::pair<int, int> bestScoreMove, player currentPlayer, int alpha, int beta, int lastPlay)
{
Entry e = (*tTable)[hash];
if (e && e.depth == reverseDepth)
return e.scoreMove;
if (reverseDepth == 0)
return { 0, -2 };
else if (field->canDrawOrWin() && lastPlay != -1)
{
if (field->hasWon(lastPlay))
return { evaluateScore(currentPlayer), -1 };
else if (field->isDraw())
return { 0, -1 };
}
bestScoreMove.first = currentPlayer == player::AI ? INT_MIN : INT_MAX;
for (int i = 0; i < field->size(); i++)
{
if ((*field)[i] == player::None && field->isCoordWorthChecking(i))
{
(*field)[i] = currentPlayer;
hash = tTable->calculateHash(hash, i);
std::pair<int, int> scoreMove = minMax(reverseDepth - 1, bestScoreMove, getOpponent(currentPlayer), alpha, beta, i);
if (currentPlayer == player::AI)
{
alpha = std::max(alpha, scoreMove.first);
if (bestScoreMove.first < scoreMove.first)
bestScoreMove = { scoreMove.first, i };
}
else
{
beta = std::min(beta, scoreMove.first);
if (bestScoreMove.first > scoreMove.first)
bestScoreMove = { scoreMove.first, i };
}
hash = tTable->calculateHash(hash, i);
(*field)[i] = player::None;
if (beta <= alpha)
break;
}
}
tTable->placeEntry(hash, bestScoreMove, reverseDepth);
return bestScoreMove;
}
To test it I made an acceptance test that plays every possible board and checks for human wins
TEST(AcceptanceTest, EveryBoard)
{
int winstate = 0;
std::shared_ptr<Field> field = std::make_shared<Field>(4);
AI ai(field);
playEveryBoard(ai, field, winstate);
std::cout <<"Human wins: " << winstate << std::endl;
}
void playEveryBoard(AI& ai, std::shared_ptr<Field> f, int& winstate)
{
int bestMove = 0;
auto it = f->begin();
while (true)
{
it = std::find(it, f->end(), player::None);
if (it == f->end())
break;
*it = player::Human;
if (f->hasWon())
winstate++;
EXPECT_TRUE(!f->hasWon());
bestMove = ai.findBestMove();
if (bestMove == -1)//TIE
{
*it = player::None;
break;
}
(*f)[bestMove] = player::AI;
if (f->hasWon())//AI WIN
{
*it = player::None;
(*f)[bestMove] = player::None;
break;
}
playEveryBoard(ai, f, winstate);
*it = player::None;
(*f)[bestMove] = player::None;
if (it == f->end())
break;
it++;
}
}
The test never returned any loosing states until I added the transposition table, to test when the loosing state appears I made a test that plays every permutation of the loosing field, but it never found a loosing state, what could cause the AI to loose only in the EveryBoard test?
TEST(LoosePossible, AllPermutations)
{
std::vector<int> loosingField = { 2, 3, 7, 11, 12, 13, 15 };
do{
std::shared_ptr<Field> field = std::make_shared<Field>(4);
AI *ai = new AI(field);
for (auto i : loosingField)
{
if ((*field)[i] != player::None || field->hasWon())
break;
(*field)[i] = player::Human;
EXPECT_TRUE(!field->hasWon());
(*field)[ai->findBestMove()] = player::AI;
}
delete ai;
} while (next_permutation(loosingField.begin(), loosingField.end()));
}