//this stores the evaluation of a move and the move
struct SMove{
SMove(int s, int m){score = s; move = m;}
int score;
int move;
};
struct SBoard{
//board values can be: 1-computer, -1-player, 0-emty
int Position[7][6];
//the hieght of the lowest emty square in each coloum
int Heights[7];
int Evaluation;
int MaxHeight;
bool Win;
SBoard();
void UpdateBoard(int move, int turn);
void EvaluateBoard();
};
//////////////////EvaluateGroup//////////////////////
//
// evaluates a group of 4 cells
//
/////////////////////////////////////////////////////
int EvaluateGroup(int NumberOfMe, int NumberOfEnemy)
{
int Evaluation=0;
if(NumberOfEnemy == 0)
{
switch(NumberOfMe)
{
case 2:
Evaluation+=5;
break;
case 3:
Evaluation+=15;
break;
}
}
if(NumberOfMe == 0)
{
switch(NumberOfEnemy)
{
case 2:
Evaluation-=5;
break;
case 3:
Evaluation-=15;
break;
}
}
return Evaluation;
}
//ctor
SBoard :: SBoard()
{
Evaluation = 0;
MaxHeight = 0;
Win = false;
memset(Position, 0, sizeof(int)*7*6); //init the boads to 0;
memset(Heights, 0, sizeof(int)*7);
}
/////////////////UpdateBoard/////////////////////////
//
// Updates the board with a move, it also checks for a win
//
/////////////////////////////////////////////////////
void SBoard :: UpdateBoard(int move, int turn)
{
if((Heights[move] < 6)&&(move<7)&&(move>-1))
{
Position[move][Heights[move]] = turn;
//--Check if someone won
//check coloums
int InARow = 0;
for(int r=0; r<6; r++)
{
if(Position[move][r] == turn)
InARow++;
else
InARow=0;
if(InARow==4)
Win=true;
}
//check rows
InARow = 0;
for(int c=0; c<7; c++)
{
if(Position[c][Heights[move]] == turn)
InARow++;
else
InARow=0;
if(InARow==4)
Win=true;
}
//check right diagonal
int first_coloum;
int first_row;
InARow = 0;
if(move > Heights[move])
{
first_coloum = move - Heights[move];
first_row = 0;
}
else
{
first_coloum = 0;
first_row = Heights[move] - move;
}
int r=first_row;
int c=first_coloum;
while((r<6)&&(c<7))
{
if(Position[c][r] == turn)
InARow++;
else
InARow=0;
if(InARow==4)
Win=true;
r++;
c++;
}
//check left diagonal
InARow = 0;
if(7-move > Heights[move])
{
first_coloum = move + Heights[move];
first_row = 0;
}
else
{
first_coloum = 7;
first_row = Heights[move] + move - 7;
}
r=first_row;
c=first_coloum;
while((r<6)&&(c>-1))
{
if(Position[c][r] == turn)
InARow++;
else
InARow=0;
if(InARow==4)
Win=true;
r++;
c--;
}
//update the max height
Heights[move]++;
if(Heights[move] > MaxHeight)
MaxHeight++;
}
}
////////////////////UpdateBoard////////////////////
//
// Evaluates the board by checking all groups of 4
//
///////////////////////////////////////////////////
void SBoard :: EvaluateBoard()
{
//---ROWS
//Loop through all the rows up till the highest hieght
int ScoreForAllRows = 0;
for(int r=0; r<MaxHeight; r++)
{
//Loop through all of the groups of 4
for(int c=0; c<4; c++)
{
int NumberOfEnemy = 0;
int NumberOfMe = 0;
//Loop through the 4 in the group
for(int i=0; i<4; i++)
{
if(Position[c+i][r] == 1)
NumberOfMe++;
if(Position[c+i][r] == -1)
NumberOfEnemy++;
}
//Evaluate that group
ScoreForAllRows += EvaluateGroup(NumberOfMe, NumberOfEnemy);
}//c
}//r
//---COLOUMS
//Loop through all the colums up till the highest hieght
int ScoreForAllColoums = 0;
for(int c=0; c<7; c++)
{
//Loop through all of the groups of 4
for(int r=0; r<3; r++)
{
int NumberOfEnemy = 0;
int NumberOfMe = 0;
//Loop through the 4 in the group
for(int i=0; i<4; i++)
{
if(Position[c][r+i] == 1)
NumberOfMe++;
if(Position[c][r+i] == -1)
NumberOfEnemy++;
}
//Evaluate that group
ScoreForAllColoums += EvaluateGroup(NumberOfMe, NumberOfEnemy);
}//c
}//r
//---DIAGONALS
//Loop through all the bottom rows and check which diagonals they have
int ScoreForAllDiagonals = 0;
for(int c=0; c<7; c++)
{
for(int r=0; r<6; r++)
{
//if we can diagonal up to the right
if((c<4)&&(r<3))
{
int NumberOfEnemy = 0;
int NumberOfMe = 0;
//Loop through the 4 in the group
for(int i=0; i<4; i++)
{
if(Position[c+i][r+i] == 1)
NumberOfMe++;
if(Position[c+i][r+i] == -1)
NumberOfEnemy++;
}
//Evaluate that group
ScoreForAllDiagonals += EvaluateGroup(NumberOfMe, NumberOfEnemy);
}//UR
//if we can diagonal up to the left
if((c>2)&&(r<3))
{
int NumberOfEnemy = 0;
int NumberOfMe = 0;
//Loop through the 4 in the group
for(int i=0; i<4; i++)
{
if(Position[c-i][r+i] == 1)
NumberOfMe++;
if(Position[c-i][r+i] == -1)
NumberOfEnemy++;
}
//Evaluate that group
ScoreForAllDiagonals += EvaluateGroup(NumberOfMe, NumberOfEnemy);
}//UL
/*
//if we can diagonal down to the right
if((c<4)&&(r>2))
{
int NumberOfEnemy = 0;
int NumberOfMe = 0;
//Loop through the 4 in the group
for(int i=0; i<4; i++)
{
if(Position[c+i][r-i] == 1)
NumberOfMe++;
if(Position[c+i][r-i] == -1)
NumberOfEnemy++;
}
//Evaluate that group
ScoreForAllDiagonals += EvaluateGroup(NumberOfMe, NumberOfEnemy);
}//DR
//if we can diagonal down to the left
if((c>2)&&(r>2))
{
int NumberOfEnemy = 0;
int NumberOfMe = 0;
//Loop through the 4 in the group
for(int i=0; i<4; i++)
{
if(Position[c-i][r-i] == 1)
NumberOfMe++;
if(Position[c-i][r-i] == -1)
NumberOfEnemy++;
}
//Evaluate that group
ScoreForAllDiagonals += EvaluateGroup(NumberOfMe, NumberOfEnemy);
}//DL
*/
}//r
}//c
Evaluation = ScoreForAllRows + ScoreForAllColoums + ScoreForAllDiagonals;
}
class CMinMax{
private:
int m_iTotalDepth;
SMove AlphaBeta(int depth, SBoard board, int alpha, int beta, int turn);
vector<int> GenerateMoves(SBoard board, int depth);
public:
CMinMax(int d){m_iTotalDepth = d;}
int GetNextMove(SBoard board);
};
////////////////////AlphaBeta/////////////////////////
//
// This is the heart of the AI. It is a recursive
// negamax function with AB implemented.
//
//////////////////////////////////////////////////////
SMove CMinMax :: AlphaBeta(int depth, SBoard board, int alpha, int beta, int turn)
{
counter++;
//if we won return a huge number or a tiny one, depending on who won
if(board.Win)
return SMove(-turn*100000,0);
//if we got to the end of the search return the static evaluation
if(depth == 0)
{
board.EvaluateBoard();
return SMove(board.Evaluation,0);
}
//if we havn't gotten to the end, search for best node
else
{
int best = -100000; int move_score = 0; int best_move = 0;
vector<int> moves = GenerateMoves(board, depth);
//loop through all possible moves
for(int i=0; i<moves.size(); i++)
{
SBoard new_board = board; //IS THIS A PROBLEM??
new_board.UpdateBoard(moves, turn);
move_score = -AlphaBeta(depth-1, new_board, -beta, -alpha, -turn).score;
//if this is better that the best move sofar
if(move_score>best)
{best=move_score; best_move=moves;}
//alphabeta pruning
if(best>alpha)
alpha = best;
if(alpha>beta)
return SMove(beta, best_move);
}//for i
//if we're still in the tree 9not at the base node
//if(depth<m_iTotalDepth)
return SMove(best, best_move);
//else
//return best_move;
}//else end
}
vector<int> CMinMax :: GenerateMoves(SBoard board, int depth)
{
vector<int> moves;
//go through all the coloums
for(int c=0; c<7; c++)
{
if(board.Heights[c]<6)
moves.push_back(c);
}
return moves;
}
int CMinMax :: GetNextMove(SBoard board)
{
counter = 0;
int move = AlphaBeta(m_iTotalDepth, board, -100000, 100000, -1).move;
return move;
}