127 lines
2.7 KiB
C++
127 lines
2.7 KiB
C++
#include "TicTacToeGame.h"
|
|
|
|
/**
|
|
* @brief Sets field cell to current player
|
|
* @param coord - Cell coordinate
|
|
*/
|
|
void TicTacToeGame::playerMove(UINT coord)
|
|
{
|
|
field[coord] = player;
|
|
}
|
|
|
|
/**
|
|
* @brief Calculates best move for computer using minimax algorithm
|
|
*/
|
|
UINT TicTacToeGame::findComputerMove()
|
|
{
|
|
INT bestScore = INT_MIN, score;
|
|
UINT bestCell = -1;
|
|
|
|
for (UINT i = 0; i < 9; i++)
|
|
if (field[i] == SqState::E) {
|
|
field[i] = SqState::O;
|
|
|
|
score = minimax(0, SqState::O);
|
|
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
bestCell = i;
|
|
}
|
|
|
|
field[i] = SqState::E;
|
|
}
|
|
|
|
return bestCell;
|
|
}
|
|
|
|
/**
|
|
* @brief Calculates score of current game board for player. If the game is ended, it returns its result minus depth of this state in game tree. Else, it returns
|
|
* @param depth - Number of steps made in game graph
|
|
* @param player - Player for which we want to calculate current score
|
|
*/
|
|
INT TicTacToeGame::minimax(UINT depth, SqState player)
|
|
{
|
|
if (player == SqState::E) throw "Called with Empty player";
|
|
|
|
SqState enemy = getEnemy(player);
|
|
|
|
INT winScore = checkWin(player);
|
|
|
|
if (winScore) return winScore - depth;
|
|
|
|
INT maxEnemyScore = INT_MIN;
|
|
|
|
for (int i = 0; i < 9; i++)
|
|
if (field[i] == SqState::E) {
|
|
field[i] = enemy;
|
|
INT score = minimax(depth + 1, enemy);
|
|
|
|
if (score > maxEnemyScore)
|
|
maxEnemyScore = score;
|
|
|
|
field[i] = SqState::E;
|
|
}
|
|
|
|
if (maxEnemyScore == INT_MIN) return 0;
|
|
|
|
return -maxEnemyScore;
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if current game state is wining for player or its enemy
|
|
* @param player - Player for which we check
|
|
* @returns 10 means win for player, -10 - for enemy, 0 - draw or unfinished
|
|
*/
|
|
INT TicTacToeGame::checkWin(SqState player)
|
|
{
|
|
if (player == SqState::E) throw "Called with Empty player";
|
|
|
|
SqState enemy = getEnemy(player);
|
|
|
|
for (INT i = 0; i < 3; i++) {
|
|
if ((field[i * 3] == field[1 + i * 3] && field[1 + i * 3] == field[2 + i * 3]) || // Row
|
|
(field[i] == field[3 + i] && field[3 + i] == field[6 + i])) { // Column
|
|
if (field[i * 4] == player)
|
|
return 10;
|
|
if (field[i * 4] == enemy)
|
|
return -10;
|
|
}
|
|
}
|
|
|
|
if ((field[0] == field[4] && field[4] == field[8]) || // first diagonal
|
|
(field[2] == field[4] && field[4] == field[6])) { // second diagonal
|
|
if (field[4] == player)
|
|
return 10;
|
|
if (field[4] == enemy)
|
|
return -10;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if current game state is a draw
|
|
*/
|
|
BOOL TicTacToeGame::checkDraw()
|
|
{
|
|
for (int i = 0; i < 9; i++)
|
|
if (field[i] == SqState::E)
|
|
return FALSE;
|
|
|
|
ended = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Utility for getting enemy of player
|
|
*/
|
|
SqState TicTacToeGame::getEnemy(SqState player)
|
|
{
|
|
if (player == SqState::E) throw "Called with Empty player";
|
|
|
|
if (player == SqState::X)
|
|
return SqState::O;
|
|
else
|
|
return SqState::X;
|
|
}
|