Hari's Corner
Humour, comics, tech, law, software, reviews, essays, articles and HOWTOs intermingled with random philosophy now and thenA simple Tic Tac Toe program in C++ (without Minimax)
Filed under:
Tutorials and HOWTOs by
Hari
Posted on Sun, Oct 23, 2011 at 18:45 IST (last updated: Mon, Oct 24, 2011 @ 16:03 IST)
Yes, it's not the prettiest code and it's probably more verbose than you would expect, but the logic is relatively simple to follow as it uses game-specific rules with a little randomization. A lot of Tic Tac Toe tutorials prefer the more sophisticated and general Minimax algorithm (which is really useful for more complex decision algorithms based on a zero-sum formula), but I found few examples with just a normal "AI". Note that the above logic is not perfect. The AI is relatively dumb in that it doesn't start off with a goal of winning, trying instead to pick up desirable positions (centre, corners, then the other squares), but will win or block the human player from winning if it sees the chance. In actuality, this approximation doesn't affect the game all that much because of the limited scope of Tic Tac Toe board and the duration of gameplay. So far, I've not been able to beat it yet, but I am not sure whether it is an unbeatable strategy. You can take the above code, save it in a file called// 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[i][j] == noughtorcross) chcount ++; // If specified cell is blank mark the column as empty else if (board[i][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[i][j] == noughtorcross) chcount ++; else if (board[i][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[i][i] == noughtorcross) chcount ++; else if (board[i][i] == ' ') 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[i][2-i] == noughtorcross) chcount ++; else if (board[i][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[i][0] == ch && board[i][1] == ch && board[i][2] == ch) { win = true; break; } // Check down for (i = 0; i < 3; i ++) if (board[0][i] == ch && board[1][i] == ch && board[2][i] == 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[i][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[i][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[i][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; }
tictactoe.cpp
, compile it with your favourite C++ compiler and run the resultant executable from the command line to test the program.
Your feedback is much appreciated.
Comments closed
The blog owner has closed further commenting on this entry.
4 comment(s)
What's your take on Santhosh Pandit?
Comment by Vinu Varghese (visitor) on Mon, Oct 24, 2011 @ 10:36 IST #
P.S. from what I saw of a google video, I think he is Kerala's Sam Anderson, right?
Comment by Hari (blog owner) on Mon, Oct 24, 2011 @ 14:46 IST #
Comment by M imran (visitor) on Wed, Jan 22, 2014 @ 14:09 IST #
Comment by shikha (visitor) on Tue, Dec 1, 2015 @ 19:46 IST #