// ticTacToe.cpp
// Plays a 2-user tic tac toe game.
#include "textUtility2.h"
#include <fstream>
#include <cassert>
using namespace std;
typedef char TicTacToeBoard[3][3];
// ----------------------------
// ***** Output functions *****
// ----------------------------
void printBoard(const TicTacToeBoard board,
ostream& out);
void announceWinner(char winner, ostream& out);
void displayPlayingScreen(const TicTacToeBoard board,
bool turnX,
bool nag);
void displayFinalScreen(const TicTacToeBoard board,
char winner);
// ---------------------------
// ***** Input functions *****
// ---------------------------
bool readMove(int& row, int& column);
void readMoveInRange(int& row,
int& column,
const TicTacToeBoard board,
bool turnX);
void readMoveNotYetTaken(int& row,
int& column,
const TicTacToeBoard board,
bool turnX);
// ---------------------------------
// ***** Computation functions *****
// ---------------------------------
bool isPositionTaken(const TicTacToeBoard board,
int row,
int column);
bool winnerYet(const TicTacToeBoard board,
char& winner);
bool isValidBoard(const TicTacToeBoard board);
bool isGameOver(const TicTacToeBoard board,
char& winner);
/*
* Plays a 2-user Tic Tac Toe game.
* Interacts with the user via the
* terminal window, and prints a log
* of all moves to file ticTacToe.txt.
*
* Global variables:
* cin - an object of class istream,
* declared in <iostream>
* cout - an object of class ostream,
* declared in <iostream>
*/
int main()
{
ofstream logFile;
logFile.open("ticTacToe.txt");
logFile << "Beginning Tic Tac Toe game."
<< endl << endl;
TicTacToeBoard board = {
{ ' ', ' ', ' ' },
{ ' ', ' ', ' ' },
{ ' ', ' ', ' ' }
};
char winner; // will be set to 'X' or 'O' to
// indicate winner, or ' ' if
// there is no winner.
bool turnX = true;
do {
// Display board and ask user to select move:
displayPlayingScreen(board, turnX, false);
// Obtain, from the user, a valid selection
// of a move to a position not yet taken:
int row;
int column;
readMoveNotYetTaken(row, column, board, turnX);
// Make the move selected by the user:
board[row][column] = (turnX ? 'X' : 'O');
// Print board with current move to text file:
printBoard(board, logFile);
logFile << endl;
// It will now be the other player's turn:
turnX = ! turnX;
// Check whether game is over.
// If so, determine winner, if any:
} while ( !isGameOver(board, winner));
// Display board with announcement of winner
// and without prompt:
displayFinalScreen(board, winner);
// Conclude log file:
logFile << "End of Tic Tac Toe game." << endl;
announceWinner(winner, logFile);
return 0;
} // function main
/*
* void printBoard(const TicTacToeBoard board,
* ostream& out)
*
* Prints a tic tac toe board to the specified
* output stream, taking 11 lines of text.
*
* Parameters:
* board - a 3 x 3 array of characters
* representing the board. It is
* assumed that the characters are all
* 'X', 'O', or ' '.
* out - a reference to an output stream.
* Precondition: the state of out is true.
* Postcondition: the state of out is still true.
*/
void printBoard(const TicTacToeBoard board,
ostream& out)
{
out << " | | " << endl;
out << " " << board[0][0] << " | "
<< board[0][1] << " | "
<< board[0][2] << endl;
out << " | | " << endl;
out << "-------|-------|-------" << endl;
out << " | | " << endl;
out << " " << board[1][0] << " | "
<< board[1][1] << " | "
<< board[1][2] << endl;
out << " | | " << endl;
out << "-------|-------|-------" << endl;
out << " | | " << endl;
out << " " << board[2][0] << " | "
<< board[2][1] << " | "
<< board[2][2] << endl;
out << " | | " << endl;
} // function printBoard
/*
* void announceWinner(char winner, ostream& out)
*
* Prints a one-sentence announcement of the
* winner, if any, or an announcement that
* there is no winner.
*
* Parameters:
* winner - an indication of who won:
* 'X', if the winner is X.
* 'O', if the winner is O.
* A blank space if there is no winner.
* out - a reference to an output stream.
* Precondition: the state of out is true.
* Postcondition: the state of out is still true.
*/
void announceWinner(char winner, ostream& out)
{
if ( (winner == 'X') || (winner == 'O') )
out << "The winner is "
<< winner << "." << endl;
else
out << "Neither player won." << endl;
} // function announceWinner
/*
* void displayPlayingScreen(const TicTacToeBoard board,
* bool turnX,
* bool nag)
*
* Displays current game board and prompts
* user to enter a move.
*
* Parameters:
* board - a 3 x 3 array of characters
* representing the board. It is
* assumed that the characters are all
* 'X', 'O', or ' '.
* turnX - true if it is now X's turn,
* false if it is now O's turn.
* nag - true if current player is being
* prompted again in response to an
* invalid entry.
* false if current player is being
* prompted the first time within
* a given turn.
*
* Global variable:
* cout - an object of class ostream,
* declared in <iostream>
*
*
*/
void displayPlayingScreen(const TicTacToeBoard board,
bool turnX,
bool nag)
{
if ( !nag )
cout << endl << "The game so far:" << endl;
cout << endl;
printBoard(board, cout);
const char nextPlayer = (turnX ? 'X' : 'O');
cout << endl << "It is " << (nag?"still":"now")
<< " " << nextPlayer
<< "\'s turn. " << nextPlayer <<
", select your move:" << endl << endl;
cout << " 1 | 2 | 3 " << endl;
cout << "---|---|---" << endl;
cout << " 4 | 5 | 6 " << endl;
cout << "---|---|---" << endl;
cout << " 7 | 8 | 9 " << endl;
cout << endl << "Enter your selection "
<< "(1, 2, 3, 4, 5, 6, 7, 8, 9):>";
} // function displayPlayingScreen
/*
* void displayFinalScreen(const TicTacToeBoard board,
* char winner)
*
* Displays the board and an announcement of
* the winner. This function should be called
* when the game is over.
*
* Parameters:
* board - a 3 x 3 array of characters
* representing the board. It is
* assumed that the characters are all
* 'X', 'x', 'O', 'o', ' ', or '_'.
* winner - 'X', if X is the winner.
* 'O', if O is the winner.
* ' ' otherwise.
*
* Global variable:
* cout - an object of class ostream,
* declared in <iostream>
*
*
*/
void displayFinalScreen(const TicTacToeBoard board,
char winner)
{
cout << endl;
announceWinner(winner, cout);
cout << endl;
printBoard(board, cout);
} // function displayFinalScreen
/*
* bool readMove(int& row, int& column)
*
* Prompts user to select a move, and reads
* move. The user should enter a digit in
* the range [1, 9], which this function
* then interprets as designating a row
* and column.
*
* Parameters:
* row - row of the user's selected move.
* Precondition: none.
* Postcondition: row represents the
* selected row, in range [0, 2],
* if user typed a valid selection.
* Unchanged otherwise.
* column - column of the user's selected move.
* Precondition: none.
* Postcondition: column represents the
* selected column, in range [0, 2],
* if user typed a valid selection.
* Unchanged otherwise.
*
* Global variable:
* cin - an object of class istream,
* declared in <iostream>
* Precondition: The state of cin is true.
* Postcondition: The state of cin is
* still true (because cin has been
* used to read characters only),
* and a line of text has been
* completely read.
*
* Returns:
* True if user typed a character in range
* ['1','9'], false otherwise.
*/
bool readMove(int& row, int& column)
{
// Read a character:
char x;
cin.get(x);
// Determine whether x is in the
// appropriate range for a move,
// i.e. whether it represents a digit
// between 1 and 9, inclusive:
const bool valid = ( x >= '1' && x <= '9' );
// If x is within range, convert it to
// the represented row and column:
if ( valid ) {
int move = toDigitValue(x) - 1;
row = move / 9;
column = move % 9;
} // if
// Skip to end of line:
while ( x != '\n' ) // works on Unix and Windows
cin.get(x);
// Assertion: If we have reached this
// point, valid is true if, and only if,
// the first character read represents an
// integer in the range '1' to '9'.
return valid;
} // function readMove
/*
* void readMoveInRange(int& row,
* int& column,
* const TicTacToeBoard board,
* bool turnX)
*
* Prompts user to enter a move, and reads
* the move. If the user's response is illegal
* in the sense of being out of range, the user
* is prompted repeatedly until a valid
* response is obtained.
*
* Parameters:
* row - row of the user's selected move.
* Precondition: none.
* Postcondition: row represents the
* selected row, in range [0, 2],
* if user typed a valid selection.
* Unchanged otherwise.
* column - column of the user's selected move.
* Precondition: none.
* Postcondition: column represents the
* selected column, in range [0, 2],
* if user typed a valid selection.
* Unchanged otherwise.
* board - a 3 x 3 array of characters
* representing the board. It is
* assumed that the characters are all
* 'X', 'x', 'O', 'o', ' ', or '_'.
* turnX - true if it is now X's turn.
* false if it is now O's turn.
*
* Global variables:
* cin - an object of class istream,
* declared in <iostream>
* Precondition: The state of cin is true.
* Postcondition: The state of cin is
* still true, and a line of text
* has been completely read.
* cout - an object of class ostream,
* declared in <iostream>
*/
void readMoveInRange(int& row,
int& column,
const TicTacToeBoard board,
bool turnX)
{
while ( ! readMove(row, column) ) { // uses cin
cout << endl << "Invalid entry. You must enter "
<< "an integer in the range [1, 9]." << endl;
displayPlayingScreen(board, turnX, true); // uses cout
} // while
} // function readMoveInRange
/*
* void readMoveNotYetTaken(int& row,
* int& column,
* const TicTacToeBoard board,
* bool turnX)
*
* Prompts user to enter a move, and reads
* the move. If the user's response is illegal
* in the sense of either being out of range
* or attempting a move already taken, the user
* is prompted repeatedly until a valid
* response is obtained.
*
* Parameters:
* row - row of the user's selected move.
* Precondition: none.
* Postcondition: row represents the
* selected row, in range [0, 2],
* if user typed a valid selection.
* Unchanged otherwise.
* column - column of the user's selected move.
* Precondition: none.
* Postcondition: column represents the
* selected column, in range [0, 2],
* if user typed a valid selection.
* Unchanged otherwise.
* board - a 3 x 3 array of characters
* representing the board. It is
* assumed that the characters are all
* 'X', 'x', 'O', 'o', ' ', or '_'.
* turnX - true if it is now X's turn.
* false if it is now O's turn.
*
* Global variables:
* cin - an object of class istream,
* declared in <iostream>
* Precondition: The state of cin is true.
* Postcondition: The state of cin is
* still true, and a line of text
* has been completely read.
* cout - an object of class ostream,
* declared in <iostream>
*/
void readMoveNotYetTaken(int& row,
int& column,
const TicTacToeBoard board,
bool turnX)
{
while (true) {
readMoveInRange(row, column, board, turnX); // uses cin
// and cout
if ( ! isPositionTaken(board, row, column) )
return;
cout << endl << "Invalid entry. You entered a "
<< "position already taken by "
<< board[row][column] << " ." << endl;
displayPlayingScreen(board, turnX, true); // uses cout
} // while
} // function readMoveNotYetTaken
/*
* bool isPositionTaken(const TicTacToeBoard board,
* int row,
* int column)
*
* Determines whether a specified position
* on the board is already occupied by
* either X or O.
*
* Parameters:
* board - a 3 x 3 array of characters
* representing the board. It is
* assumed that the characters are all
* 'X', 'x', 'O', 'o', ' ', or '_'.
* row - row of a position on board,
* assumed to be in range [0, 2].
* column - column of a position on board,
* assumed to be in range [0, 2].
*
* Returns:
* true if the position indicated by row and
* column is already taken by X or O.
* false otherwise.
*/
bool isPositionTaken(const TicTacToeBoard board,
int row,
int column)
{
return ( (board[row][column] == 'X')
|| (board[row][column] == 'O') );
} // function isPositionTaken
/*
* bool winnerYet( const TicTacToeBoard board,
* char& winner )
*
* Determines which player (if any) has won.
* Checks every column and row, plus the
* diagonals, to see if they contain three
* matching symbols (except ' ', which can not win).
*
* Parameters:
* board - a 3 x 3 array of characters representing
* the board. The characters must all be
* 'X', 'x', 'O', 'o', ' ', or '_'.
* winner - an indication of who won.
* Precondition: none.
* Postcondition: winner is one of the following:
* 'X' or 'x', if the winner is X.
* 'O' or 'o', if the winner is O.
* A blank space if there is no winner.
*
* Global variable:
* cout - an object of class ostream,
* declared in <iostream>
*
* Returns true if there is a winner yet,
* false otherwise.
*/
bool winnerYet( const TicTacToeBoard board,
char& winner )
{
assert(isValidBoard(board)); // for debugging
// Loop through 0, 1, 2. Simultaneously check the
// corresponding row and column. After this loop is
// complete we will know if any player has gotten
// three in a row horizontally or vertically.
for ( int i = 0 ; i < 3 ; i++ ) {
// Case 1: Search row i:
if ( (board[i][0] == board[i][1])
&& (board[i][1] == board[i][2])
&& (board[i][0] != ' ') )
{
winner = board[i][0];
return true;
} // if
// Case 2: Search column i:
if ( (board[0][i] == board[1][i])
&& (board[1][i] == board[2][i])
&& (board[0][i] != ' ') )
{
winner = board[0][i];
return true;
} // if
} // for i
// Case 3: Search top-left-to-bottom-right diagonal:
// There is only one diagonal, so no need for a loop here.
if ( (board[0][0] == board[1][1])
&& (board[1][1] == board[2][2])
&& (board[1][1] != ' ') )
{
winner = board[1][1];
return true;
} // if
// Case 4: Search top-right-to-bottom-left diaginal:
if ( (board[0][2] == board[1][1])
&& (board[1][1] == board[2][0])
&& (board[1][1] != ' ') )
{
winner = board[1][1];
return true;
} // if
// If we reach this point, obviously nobody has won.
// Return false, meaning no player
// has successfully gotten 3 in a row.
winner = ' ';
return false;
} // function winnerYet
/*
* bool isValidBoard(const TicTacToeBoard board)
*
* Determines whether all the characters
* on the board are appropriate to a
* Tic Tac Toe game. (This function needed
* for debugging purposes only.)
*
* Parameter:
* board - a 3 x 3 array of characters
* representing the board.
*
* Returns true if the characters are all
* 'X', 'x', 'O', 'o', ' ', or '_'.
* false otherwise.
*/
bool isValidBoard(const TicTacToeBoard board)
{
for ( int i = 0; i < 3; i++ )
for ( int j = 0; j < 3; j++ )
if ( (board[i][j] != 'x') && (board[i][j] != 'X')
&& (board[i][j] != 'o') && (board[i][j] != 'O')
&& (board[i][j] != ' ') && (board[i][j] != '_') )
return false;
return true;
} // function isValidBoard
/*
* bool isGameOver(const TicTacToeBoard board,
* char& winner )
*
* Determines whether the game is over.
* The game is over if either (1) a player
* has won or (2) all positions on the
* board are filled with X's and O's.
*
* Parameters:
* board - a 3 x 3 array of characters representing
* the board. The characters must all be
* 'X', 'x', 'O', 'o', ' ', or '_'.
* winner - an indication of who won.
* Precondition: none.
* Postcondition: winner is one of the following:
* 'X' or 'x', if the winner is X.
* 'O' or 'o', if the winner is O.
* A blank space if there is no winner.
*
* Global variable:
* cout - an object of class ostream,
* declared in <iostream>
*
* @return true if the game is over,
* false otherwise.
*/
bool isGameOver(const TicTacToeBoard board,
char& winner )
{
if ( winnerYet(board, winner) ) // uses cout
return true;
winner = ' ';
for ( int i = 0; i < 3; i++ )
for ( int j = 0; j < 3; j++ )
if ( (board[i][j] != 'X') && (board[i][j] != 'O') )
return false;
return true;
} // function isGameOver