Jump to content
  • Advertisement
Sign in to follow this  
thebigbossman

A C++ TicTacToe without minimax AI : comments?

This topic is 2611 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've not written a C++ program in ages and thought that a simple TicTacToe game would be fun to cook up. Mind you, I've never written a TicTacToe game before (at least not one that you play against the computer) so this is a first for me. I realize that the most optimal AI uses the Minimax algorithm, but I wanted to try out my own AI after reading the "rules" of the game on Wikipedia.

Nothing really special this code, particularly, but I have a sense of achievement in writing C++ code after such a long

[source lang="cpp"]// Simple command line TicTacToe game

// (C) 2011 V.Harishankar

// Released under the GNU GPL v3



#include <iostream>

#include <cstdlib>

#include <ctime>



class TicTacToeGame

{

protected:

enum Turn { PLAYER = 0, AI = 1 };



// Represents the current turn

Turn current_turn;



// Character (O or X for player)

char player_char, ai_char;



// Represents the board

char board[3][3];



// Clear the board

void initialize_board ();



// Display the board

void print_board ();



// Chekc if all slots are filled

bool all_slots_filled ();



// Find a winning position for given player (O or X) and fill the

// row and column parameter

void find_winning_position (char noughtorcross, int &row, int &col);



// Check if the specified player has won (O or X)

bool check_if_won (char ch);



// Human player move

void player_move ();



// Computer player move

void ai_move ();



// Game loop

void play_game ();



public:

// Constructor

TicTacToeGame ()

{

// Note that these values are flipped when the game is initialized

// for the first time

current_turn = PLAYER;

player_char = 'X';

ai_char = 'O';

main_menu ();

}



// Main game menu loop

void main_menu ();

};



void TicTacToeGame::player_move ()

{

int row, col;



// Get the row and column from human player

while (1)

{

std::cout << "Enter row for " << player_char << " [1..3]: ";

std::cin >> row;

std::cout << "Enter col for " << player_char << " [1..3]: ";

std::cin >> col;



row -= 1;

col -= 1;



// Row and column must be valid

if ((row >= 3 || row < 0) || (col >= 3 || col < 0))

std::cout << "Invalid row/column.\n";

else

{

// if it is a blank this is a valid move

if (board[row][col] == ' ')

break;

else

std::cout << "Cell is occupied.\n";

}

}



// Set the player move

board[row][col] = player_char;

}



void TicTacToeGame::find_winning_position (char noughtorcross, int &row, int &col)

{

// check if specified character (nought or cross) can win along any row

int i, j, chcount, emptycol, emptyrow;



// Loop through rows

for (i = 0; i < 3; i ++)

{

// Chcount -number of either O or X along the row

chcount = 0;

emptycol = -1;



// Loop through columns

for (j = 0; j < 3; j ++)

{

// If the specified player is occupying the cell, add to chcount

if (board[j] == noughtorcross)

chcount ++;

// If specified cell is blank mark the column as empty

else if (board[j] == ' ')

emptycol = j;

}



// If there are two of the specified player in the row and there is an

// empty cell, then the empty cell is the winning position

if (chcount == 2 && emptycol != -1)

{

row = i;

col = emptycol;

return;

}

}



// The same logic as above applies for columns

// check if specified character can win along any column

for (j = 0; j < 3; j ++)

{

chcount = 0;

emptyrow = -1;

for (i = 0; i < 3; i ++)

{

if (board[j] == noughtorcross)

chcount ++;

else if (board[j] == ' ')

emptyrow = i;

}

if (chcount == 2 && emptyrow != -1)

{

row = emptyrow;

col = j;

return;

}

}

// Check if specified character can win along diagonal top-left to bottom-right

chcount = 0;

emptyrow = -1;

emptycol = -1;



// Loop through the diagonal

for (i = 0; i < 3; i ++)

{

if (board == noughtorcross)

chcount ++;

else if (board == ' ')

emptyrow = emptycol = i;

}

if (chcount == 2 && emptyrow != -1 && emptycol != -1)

{

row = emptyrow;

col = emptycol;

return;

}



// Check if specified character can win along diagonal top-right to bottom-left

chcount = 0;

emptyrow = -1;

emptycol = -1;



// Loop through the diagonal

for (i = 0; i < 3; i ++)

{

if (board[2-i] == noughtorcross)

chcount ++;

else if (board[2-i] == ' ')

{

emptyrow = i;

emptycol = 2 - i;

}

}

if (chcount == 2 && emptyrow != -1 && emptycol != -1)

{

row = emptyrow;

col = emptycol;

return;

}

}



void TicTacToeGame::ai_move ()

{

// Check if AI can win in this move

int row=-1, col=-1;



// See if AI can win in this move

find_winning_position (ai_char, row, col);



// if AI can win, play the winning move

if (row != -1 && col != -1)

{

board[row][col] = ai_char;

return;

}

row = col = -1;

// see if player can win next move and block

// if possible

find_winning_position (player_char, row, col);

if (row != -1 && col != -1)

{

board[row][col] = ai_char;

return;

}



// check if the centre can be played

if (board[1][1] == ' ')

{

board[1][1] = ai_char;

return;

}



// Corner positions

int corners[4][2] = {{0, 0}, {0, 2}, {2, 0}, {2, 2}};

// If any one or more corners are blank

if (board[0][0] == ' ' || board[0][2] == ' ' || board[2][0] == ' ' || board[2][2] == ' ')

{

// randomly select corner

while (1)

{

int corner = rand () % 4;

row = corners[corner][0];

col = corners[corner][1];



if (board[row][col] == ' ')

{

board[row][col] = ai_char;

return;

}

}

}



// Edge positions

int edges[4][2] = {{0, 1}, {1, 0}, {1, 2}, {2, 1}};

// if any one or more edges are blank

if (board[0][1] == ' ' || board[1][0] == ' ' || board[1][2] == ' ' || board[2][1] == ' ')

{

// randomly select edge

while (1)

{

int edge = rand () % 4;

row = edges[edge][0];

col = edges[edge][1];



if (board[row][col] == ' ')

{

board[row][col] = ai_char;

return;

}

}

}

}



bool TicTacToeGame::check_if_won (char ch)

{

bool win = false;

// Check across

int i;

for (i = 0; i < 3; i ++)

if (board[0] == ch && board[1] == ch && board[2] == ch)

{

win = true;

break;

}



// Check down

for (i = 0; i < 3; i ++)

if (board[0] == ch && board[1] == ch && board[2] == ch)

{

win = true;

break;

}

// check diagonals

if (board[0][0] == ch && board[1][1] == ch && board[2][2] == ch)

win = true;



if (board[0][2] == ch && board[1][1] == ch && board[2][0] == ch)

win = true;



return win;

}



bool TicTacToeGame::all_slots_filled ()

{

bool filled = true;

for (int i=0; i < 3; i++)

{

for (int j=0; j < 3; j++)

if (board[j] == ' ')

filled = false;

if (filled == false)

break;

}



return filled;

}



void TicTacToeGame::print_board ()

{

std::cout << "\n";

for (int i=0; i < 3; i ++)

{

for (int j=0; j < 3; j++)

{

std::cout << board[j] << "\t";

if (j < 2)

std::cout << "|\t";

}

if (i < 2)

std::cout << "\n---------------------------------\n";

}

std::cout << "\n";

}



void TicTacToeGame::play_game ()

{

// Initialize the board first

initialize_board ();



// If player char was X then make it O

if (player_char == 'X')

{

player_char = 'O';

ai_char = 'X';

current_turn = AI;

}

else

{

ai_char = 'O';

player_char = 'X';

current_turn = PLAYER;

}



// So long as gameover is false, keep playing

bool gameover = false;



while (! gameover)

{

print_board ();

// if turn is that of the player

if (current_turn == PLAYER)

{

player_move ();

current_turn = AI;

}

else

{

std::cout << "\nComputer played: \n";

ai_move ();

current_turn = PLAYER;

}



// check for winner nought

if (check_if_won ('O'))

{

print_board ();

std::cout << "\nNoughts won! Game over...\n";

break;

}

else if (check_if_won ('X'))

{

print_board ();

std::cout << "\nCrosses won! Game over...\n";

break;

}

else if (all_slots_filled ())

{

print_board ();

std::cout << "\nGame drawn.\n";

break;

}

}

}



void TicTacToeGame::initialize_board ()

{

for (int i = 0; i < 3; i++)

for (int j=0; j < 3; j++)

board[j] = ' ';

}



void TicTacToeGame::main_menu ()

{

while (1)

{

std::cout << "\n -------------------------";

std::cout << "\n Welcome to TicTacToe game";

std::cout << "\n -------------------------";

std::cout << "\n 1. Play";

std::cout << "\n 2. Exit";

char ch;

std::cout << "\n Your choice: ";

std::cin >> ch;



switch (ch)

{

case '1': play_game (); break;

case '2': return;

}

}

}



int main ()

{

// initialize randomizer

srand (time (NULL));



TicTacToeGame game;

return 0;

}[/source]


I made this deliberately verbose and preferred to use the "rules" of the game, rather than a technique like the Minimax algorithm (which I confess, I don't fully grasp except in a theoritical sense).

Any comments? I am not sure whether this is unbeatable or not. Could any of you try this out and see if you can beat the "AI"?

Share this post


Link to post
Share on other sites
Advertisement
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!