HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Tic Tac Toe game AI in C++

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
tacgametictoe

Problem

I wrote a Tic Tac Toe game from scratch a couple days ago.

My AI doesn't always make the best moves, so I'd like some advice on how to improve it. I also hardcoded a move in a very specific situation to prevent it from losing, so I'd like some advice on that. I read about minimax, but I haven't learned algorithms yet, so I don't know how to implement (I will try to do a minimax Tic Tac Toe later).

Any other advice are welcome too!

main.cpp

#include 
#include 

#include "Board.h"

using namespace std;
using namespace std::chrono;

int main()
{
    cout > answer;

    while (answer == "y") {

        Board board;
        bool firstMoveRandom = true;
        string winner;

        while (!board.gameEnd() && !board.gameWinner(winner)) {

            cout > x;

                    cout > y;
                    if (y  2 || x  2) {
                        throw runtime_error("Error: must enter between 0 and 2");
                    }
                }
                catch (runtime_error& e) {
                    cout ( t2 - t1 ).count();
                    cout > answer;
    }

    return 0;
}


Board.cpp

```
#include "Board.h"

Board::Board()
{
for (int i = 0; i bestScore) {
bestScore = boardState[i][j];
x = j;
y = i;
}
// If two squares have the same points, randomize the choice
else if (boardState[i][j] == bestScore && rand()%2) {
bestScore = boardState[i][j];
x = j;
y = i;
}
}
}
// If the center is the best score, don't randomize
if (boardState[1][1] == bestScore) {
x = 1;
y = 1;
}
}

void Board::clearBoard()
{
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
board_[i][j] = " ";
}
}
}

void Board::displayBoard()
{
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
cout << "" << board_[i][

Solution

Optimal strategy

One of the comments on this answer points to http://xkcd.com/832/ which gives the complete optimal strategy for Tic-Tac_Toe.

This is not to say that it wouldn't be a good programming exercise to implement a Minimax solution. But it isn't necessary.

using namespace std

using namespace std;


Why is “using namespace std;” considered bad practice?

Note that it is especially bad practice to put a using namespace in a .h file, as those may be included by other files that did not intend to use std.

I personally find it easier to read std::string than figure out that string is supposed to be std::string. Another reason to avoid this habit.

do/while

cin >> answer;

    while (answer == "y") {


and to finish the loop

cin >> answer;
    }


Consider

do {


Everything in between could stay the same.

std::cin >> answer;
    } while (answer == "y" || answer == "Y");


This will start playing without asking but will ask before subsequent runs. You save duplicating the cin code.

Also I checked for an uppercase Y, which seems a reasonable input.

Don't use try/catch as a control structure

try {
                    cout > x;

                    cout > y;
                    if (y  2 || x  2) {
                        throw runtime_error("Error: must enter between 0 and 2");
                    }
                }
                catch (runtime_error& e) {
                    cout << e.what() << endl;
                }


You could just say

std::cout > x;

                std::cout > y;
                if (y  2 || x  2) {
                    std::cout << "Error: must enter between 0 and 2" << std::endl;
                    continue;
                }


Then you wouldn't have to go through the extra work of declaring the try block and catching the exception.

I also added the continue since otherwise it calls board.userPlay with invalid values.

Keep it together

}

            else if (board.getTurn() == "c") {


Please don't separate a } from its else. Personally I prefer them all on the same line, but if you do want to do it this way, please always write it

}
            else if (board.getTurn() == "c") {


That way it's easy to see that the two go together. The compiler won't care, but human readers will find this easier to follow.

Also please don't put comments in there.

Avoid the single statement forms of control structures

if (winner.size() != 0) {
            cout << "The " << winner << " has won the game!" << endl;
        }

        else cout << "The game was a tie!" << endl;


It's safer and easier to follow to just always use the block form.

if (winner.size() == 0) {
            std::cout << "The game was a tie!" << std::endl;
        }
        else {
            std::cout << "The " << winner << " has won the game!" << std::endl;
        }


I also find the else logic easier if it is a positive statement in the if.

Code Snippets

using namespace std;
cin >> answer;

    while (answer == "y") {
cin >> answer;
    }
std::cin >> answer;
    } while (answer == "y" || answer == "Y");
try {
                    cout << "Please enter x (0 to 2): ";
                    cin >> x;

                    cout << "Please enter y (0 to 2): ";
                    cin >> y;
                    if (y < 0 || y > 2 || x < 0 || x > 2) {
                        throw runtime_error("Error: must enter between 0 and 2");
                    }
                }
                catch (runtime_error& e) {
                    cout << e.what() << endl;
                }

Context

StackExchange Code Review Q#126183, answer score: 2

Revisions (0)

No revisions yet.