patterncppMinor
Tic Tac Toe game AI in C++
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
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][
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
Why is “using namespace std;” considered bad practice?
Note that it is especially bad practice to put a
I personally find it easier to read
and to finish the loop
Consider
Everything in between could stay the same.
This will start playing without asking but will ask before subsequent runs. You save duplicating the
Also I checked for an uppercase Y, which seems a reasonable input.
Don't use
You could just say
Then you wouldn't have to go through the extra work of declaring the
I also added the
Keep it together
Please don't separate a
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
It's safer and easier to follow to just always use the block form.
I also find the
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/whilecin >> 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 structuretry {
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.