Skip to content

Commit

Permalink
Improved Basic Minimax for 3x3
Browse files Browse the repository at this point in the history
  • Loading branch information
Broderick-Westrope committed Apr 12, 2021
1 parent f451b28 commit 0b93192
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 292 deletions.
74 changes: 58 additions & 16 deletions Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ class Board

bool CheckDFS(int playerType, int x, int y);

bool CanWin()
{
if ((freeCellsSize() + (boardSize * 2 - 1)) <= (boardSize * boardSize))
return true;
return false;
}

void PrintBoard();

bool isBoardFull();
Expand All @@ -153,6 +160,8 @@ class Board
bool isInVector(vector<Cell> v, Cell e);

bool AddTestMove(int playerIndex, int x, int y);

int Evaluation(int x, int y, int player, int opponent);
};

void Board::addCells()
Expand Down Expand Up @@ -415,9 +424,26 @@ bool Board::isFullThisTurn()
return false;
}

int Board::Evaluation(int x, int y, int player, int opponent)
{
if (CanWin())
{
//Check both players for a winner, first checking for a line, then using DFS
if (CheckForWin(player, x, y))
return (player);
if (CheckForWin(opponent, x, y))
return (opponent);
}

if (freeCellsSize() > 0)
return 0; //continue value

return 5; //Error Check (shouldn't reach this point)
}

bool Board::CheckForWin(int playerType, int x, int y)
{
if ((emptyCells.size() + (boardSize * 2 - 1)) <= (boardSize * boardSize))
if (CanWin())
{
if (CheckLine(playerType) || CheckDFS(playerType, x, y))
return true;
Expand Down Expand Up @@ -465,46 +491,62 @@ bool Board::CheckLine(int playerType)
return true;
}

int diagTally = 0;
for (int i = 0; i < boardSize - 1; i++)
{
if (grid[i][boardSize - 1 - i] != grid[i + 1][boardSize - 2 - i] || grid[i][boardSize - 1 - i] != playerType)
{
return false;
}
else
diagTally++;
}
if (diagTally == boardSize)
return true;

return true;
return false;
}

bool Board::CheckDFS(int playerType, int x, int y) //DFS
{
stack<Cell> search = CheckNeighbours(playerType, x, y);
bool start = false, finish = false;
int startGoal = 0, endGoal = boardSize - 1;

stack<Cell> search;
vector<Cell> visited;

if (x < 0 && y < 0)
{
if (playerType == -1) // O
for (int r = 0; r < boardSize; r++)
search.push(Cell(r, 0));
else if (playerType == 1) // X
for (int r = 0; r < boardSize; r++)
search.push(Cell(0, r));
else
cout << " ERROR: Unknown type being searched in Board's CheckDFS" << endl;
}
else if (x >= 0 && y >= 0)
search = CheckNeighbours(playerType, x, y);
else
cout << " ERROR: CheckDFS in Board was told to check a coordinate with one negative value." << endl;

if (search.empty())
return false;

bool start = false, finish = false;
int startGoal = 0, endGoal = boardSize - 1;

while (!search.empty())
{
Cell s = search.top();
search.pop();
visited.push_back(s);

if (playerType == -1)
if ((playerType == -1 && s.y == startGoal) || (playerType == 1 && s.x == startGoal))
{
if (s.y == startGoal)
start = true;
else if (s.y == endGoal)
finish = true;
start = true;
}
else if (playerType == 1)
else if ((playerType == -1 && s.y == endGoal) || (playerType == 1 && s.x == endGoal))
{
if (s.x == startGoal)
start = true;
else if (s.x == endGoal)
finish = true;
finish = true;
}

if (start && finish)
Expand Down
8 changes: 4 additions & 4 deletions Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Setup Human()

//SECTION - Monte-Carlo AI Accuracy Input
double accuracy = 2.5;
if (p2Type == 2)
if (p2Type == 2 || p2Type == 3)
{
cout << "How accurate do you want the AI to be? (Between 1 and 10)" << endl;
cin >> accuracy;
Expand Down Expand Up @@ -212,7 +212,7 @@ Setup Human()
}
case 3: //Minimax Player 2 (Hard AI that simulates all possible moves from the current state and chooses the one that leads to the fastes win)
{
p2 = new MinimaxPlayer(-1, "Naughts (O)");
p2 = new MinimaxPlayer(-1, "Naughts (O)", static_cast<int>(accuracy * 1000));
break;
}
case 4: //Negascout Player 2 (Experimental variation of Minimax with aimed at being more efficient whilst also being more accurate, hopefully allowing for better play on big boards
Expand Down Expand Up @@ -322,7 +322,7 @@ Setup Simulation()
}
case 3: //Minimax Player 1 (Hard AI that simulates all possible moves from the current state and chooses the one that leads to the fastes win)
{
p1 = new MinimaxPlayer(-1, "Naughts (O)", (double) (accuracy1 * 10 / 2));
p1 = new MinimaxPlayer(-1, "Naughts (O)", static_cast<int>(accuracy1 * 1000));
break;
}
case 4: //Negascout Player 1 (Experimental variation of Minimax with aimed at being more efficient whilst also being more accurate, hopefully allowing for better play on big boards
Expand Down Expand Up @@ -353,7 +353,7 @@ Setup Simulation()
}
case 3: //Minimax Player 2 (Hard AI that simulates all possible moves from the current state and chooses the one that leads to the fastes win)
{
p2 = new MinimaxPlayer(-1, "Naughts (O)", (double) (accuracy1 * 10 / 2));
p2 = new MinimaxPlayer(-1, "Naughts (O)", static_cast<int>(accuracy1 * 1000));
break;
}
case 4: //Negascout Player 2 (Experimental variation of Minimax with aimed at being more efficient whilst also being more accurate, hopefully allowing for better play on big boards
Expand Down
101 changes: 38 additions & 63 deletions Players/MinimaxPlayer.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <random>
#include "../Cell.h"
#include "queue"
#include <unistd.h>

//region Definitions
/*
Expand All @@ -17,25 +18,23 @@
class MinimaxPlayer : public Player
{
public:
MinimaxPlayer(int t, string symbol = "Undefined (ERROR)", double depth = 12, string name = "Minimax") :
MinimaxPlayer(int t, string symbol = "Undefined (ERROR)", int depth = 2500, string name = "Minimax") :
Player(t, symbol, name), maxDepth(depth)
{
}

const int MAX = 1000;
const int MIN = -1000;
const int WIN_VAL = 1000;
const int MAX = round_toward_infinity;
const int MIN = -round_toward_neg_infinity;
const double WIN_VAL = 2.5; //250/100 (2.5) because 225 is the largest possible depth that can be simulated since we cap bs at 15 (15^2)

int bs;
double maxDepth;
int maxDepth = 2500;

int player = type, opponent = type * -1;

//region Functions
bool GetMove(Board *, int &, int &);

int Evaluate(Board board, int x, int y);

Move BestMove(Board *board);

//endregion
Expand Down Expand Up @@ -70,9 +69,8 @@ bool MinimaxPlayer::GetMove(Board *board, int &x, int &y)
//region Minimax Algorithm
Move MinimaxPlayer::BestMove(Board *board)
{
//todo random_shuffle(freeCells.begin(), freeCells.end());

priority_queue<Move> moves;
cout << "Values of Moves: " << endl;
for (int r = 0; r < bs; r++)
{
for (int c = 0; c < bs; c++)
Expand All @@ -96,10 +94,10 @@ Move MinimaxPlayer::BestMove(Board *board)
if (tempBoard.CheckForWin(player, r, c)) //if the board is full
{
printf("Winning move found!");
return {r, c, 0};
return {r, c, 1};
}

double value = Minimax(tempBoard, maxDepth, false);
double value = Minimax(tempBoard, 0, false);

Move m(r, c, value);
moves.push(m);
Expand All @@ -110,82 +108,59 @@ Move MinimaxPlayer::BestMove(Board *board)

if (!moves.empty())
{
cout << moves.size() << "Best Value " << moves.top().v << " at (" << (moves.top().x + 1) << "," << (moves.top().y + 1) << ")" << endl;
cout << moves.size() << "Best Value " << moves.top().v << endl;
return moves.top();
}

cout << "ERROR: No appropriate move was found by Minimax" << endl;
return Move(-1, -1, 0);
return {-1, -1, 0};
}

double MinimaxPlayer::Minimax(Board board, int depth, bool isMax)
{
vector<Cell> emptyCells = board.getFreeCells();
if (depth <= 0 || emptyCells.empty())
if (emptyCells.empty())
{
return -0.01;
printf("not R??");
printf("FATAL ERROR: All cells used but no win was detected by Minimax!");
return (board.Evaluation(-1, -1, player, opponent)) - depth;
}

random_shuffle(emptyCells.begin(), emptyCells.end());
double bestUtil = (isMax) ? MIN : MAX;
for (Cell i : emptyCells)
//random_shuffle(emptyCells.begin(), emptyCells.end());
double bestVal = (isMax) ? MIN : MAX;
int playerType = (isMax) ? player : opponent;

for (Cell i: emptyCells)
{
int x = i.x, y = i.y;
Board tempBoard(board);
if (isMax)
{
if (!tempBoard.AddTestMove(player, x, y)) //todo opponent or dynamic player variable?
printf("ERROR: Invalid input created by Minimax's Max Move function\n");
}
else
{
if (!tempBoard.AddTestMove(opponent, x, y)) //todo opponent or dynamic player variable?
printf("ERROR: Invalid input created by Minimax's Max Move function\n");
}
int status = Evaluate(board, 0, 0);
//If this player is winning
if (!tempBoard.AddTestMove(playerType, i.x, i.y))
printf("ERROR: Invalid input created by Minimax's Max Move function\n");

int status = tempBoard.Evaluation(i.x, i.y, player, opponent);

//If this player has won
if (status == player)
{
return (double) WIN_VAL - depth;
return WIN_VAL - depth;
}
else if (status == opponent)
{
return (double) -WIN_VAL - depth;
return -WIN_VAL - depth;
}
else if (status != 0) //Continue if we aren't finished and no winner
return 0.0;

double utility = Minimax(board, depth - 1, !isMax);

if ((isMax && utility > bestUtil) || (!isMax && utility < bestUtil))
else if (status != 0)
{
bestUtil = utility;
cout << "ERROR: Invalid evaluation status found in Minimax. Free Cells: " << tempBoard.freeCellsSize() << " Can Win: " << tempBoard.CanWin() << " Status: " << status << endl;
return 0.0;
}
}
return bestUtil;
}
//endregion

//region Heuristic Evaluation
int MinimaxPlayer::Evaluate(Board board, int x, int y)
{
int freeCount = board.freeCellsSize();
double thisVal = Minimax(tempBoard, depth + 0.01, !isMax);

//Only check for a win if enough cells have been occupied
if ((freeCount + (bs * 2 - 1)) <= (bs * bs))
{
//Check both players for a winner, first checking for a line, then using DFS
if (board.CheckForWin(player, x, y))
return (player);
if (board.CheckForWin(opponent, x, y))
return (opponent);
if ((isMax && thisVal > bestVal) || (!isMax && thisVal < bestVal))
bestVal = thisVal;
}

if (freeCount > 0)
return 0; //continue value

return 5; //Error Check (shouldn't reach this point)
// if ((bestVal == MAX || bestVal == MIN))
// printf("\nERROR: BestUtil was never changed so no value was given to the move.");
return bestVal;
}
//endregion
#endif /* MINIMAX_H_ */
#endif /* MINIMAX_H_ */
// N 3 10 4 2 2
Loading

0 comments on commit 0b93192

Please sign in to comment.