Created
November 16, 2015 06:46
-
-
Save andrewfhart/95ed0b5a99b7505b8c65 to your computer and use it in GitHub Desktop.
Tic-tac-toe Tutorial (Step 6)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <cstdlib> | |
#include <ctime> | |
using namespace std; | |
/** | |
* Draw a 3x3 tic-tac-toe board with row and column labels | |
* The cell data in board will be interpreted as follows: | |
* 1 = owned by 'x' | |
* 2 = owned by 'o' | |
* all other values = empty | |
* | |
* @param int[3][3] board The current state of each board cell | |
* @return void | |
*/ | |
void drawBoard(int board[][3]) { | |
cout << " " << " A B C " << endl; | |
cout << " " << "+---+---+---+" << endl; | |
for (int row = 0; row < 3; row++) { | |
cout << row << " "; | |
for (int col = 0; col < 3; col++) { | |
switch (board[row][col]) { | |
case 1: cout << "| " << "x" << " "; break; | |
case 2: cout << "| " << "o" << " "; break; | |
default:cout << "| " << " " << " "; break; | |
} | |
} | |
cout << "|" << endl; | |
cout << " " << " " << "+---+---+---+" << endl; | |
} | |
} | |
/** | |
* Get the next move from the player, and make sure that the | |
* desired move is valid. Validity means: | |
* - column value is within bounds [A, B, C] | |
* - row value is within bounds [0, 1, 2] | |
* - the selected cell is empty board[row][col] == 0 | |
* Once input has been validated, update the board. | |
* | |
* @param int[3][3] board The current state of the board | |
* @return void | |
*/ | |
void nextPlayerMove(int board[][3]) { | |
bool col_valid = true; // assume correct input until proven wrong | |
bool row_valid = true; // ... | |
char col_c; // container for the character value of the column | |
int col; // container for the integer value of the column | |
int row; // container for the integer value of the row | |
cout << "Your turn. Where would you like to move next?" << endl; | |
cout << "Type your move as two characters separated by a space (ex: A 1)" << endl; | |
do { | |
// Obtain input from the user (one character for column, one int for row) | |
cin >> col_c >> row; | |
// Validate the provided column value | |
if (col_c == 'a' || col_c == 'A') { col = 0; } | |
else if (col_c == 'b' || col_c == 'B') { col = 1; } | |
else if (col_c == 'c' || col_c == 'C') { col = 2; } | |
else { | |
cout << "! Invalid column value entered. Your choices are: [A, B, C] " << endl; | |
col_valid = false; | |
} | |
// Validate the provided row value | |
if (row < 0 || row > 2) { | |
cout << "! Invalid row value entered. Your choices are: [0, 1, 2] " << endl; | |
row_valid = false; | |
} | |
// Ensure that the choice corresponds to an empty cell | |
if (row_valid && col_valid && board[row][col] != 0) { | |
cout << "! That cell is not empty. Please try a different cell " << endl; | |
col_valid = false; | |
row_valid = false; | |
} | |
} while (!col_valid || !row_valid); | |
// Update the board with the user's latest choice | |
board[row][col] = 1; // user is always 'x' | |
} | |
/** | |
* Determine the next move the computer should make. For now | |
* the strategy will be simple: randomly pick an available | |
* cell. | |
* Once a cell has been picked, update the board | |
* | |
* @param int[3][3] board The current state of the board | |
* @return void | |
*/ | |
void nextComputerMove(int board[][3]) { | |
int row, col; | |
do { | |
row = rand() % 3; // Choose a random row | |
col = rand() % 3; // Choose a random column | |
} while (board[row][col] != 0); // If taken, try again | |
// Update the board state | |
board[row][col] = 2; // computer is always 'o' | |
} | |
/** | |
* Determine the status of the game given the current state | |
* of the board. The status can be one of 4 values: | |
* 0 - valid: no one has won yet, and there are valid moves remaining | |
* 1 - invalid, user has won | |
* 2 - invalid, computer has won | |
* 3 - invalid, draw - no one has won but there are no valid moves remaining | |
* | |
* @param int[3][3] board The current state of the board | |
* @return int The status of the board in its current state | |
*/ | |
int isGameOver (int board[][3]) { | |
// Strategy: examine the "middle" square for each axis. There are only a | |
// limited number of these squares on the board. Specifically: | |
// A B C | |
// +---+---+---+ | |
// 0 | | x | | | |
// +---+---+---+ | |
// 1 | x | x | x | | |
// +---+---+---+ | |
// 2 | | x | | | |
// +---+---+---+ | |
// If a given user owns one of these middle squares, and also owns two | |
// neighbors along the same axis, then that user has won. Each "middle | |
// square" has only one axis to check, except B1, which has four axes. | |
// In other words you win if you own: | |
// B0 (and A0 and C0) | |
// A1 (and A0 and A2) | |
// B2 (and A2 and C2) | |
// C1 (and C0 and C2) | |
// B1 (and B0 and B2), (and A1 and C1), (and A0 and C2), (and A2 and C0) | |
// Check each player (user=1 and computer=2): | |
for (int player=1; player<=2; player++) { | |
// Determine if the current player won | |
if ((board[0][1] == player && board[0][0] == player && board[0][2] == player) | |
||(board[1][0] == player && board[0][0] == player && board[2][0] == player) | |
||(board[2][1] == player && board[2][0] == player && board[2][2] == player) | |
||(board[1][2] == player && board[0][2] == player && board[2][2] == player) | |
||(board[1][1] == player && ( | |
(board[0][1] == player && board[2][1] == player) | |
||(board[1][0] == player && board[1][2] == player) | |
||(board[0][0] == player && board[2][2] == player) | |
||(board[2][0] == player && board[0][2] == player)) | |
) | |
){ | |
return player; | |
} | |
} | |
// Determine if there are no further moves available | |
bool moreMoves = false; | |
for (int col = 0; col < 3; col++) { | |
for (int row = 0; row < 3; row++) { | |
if (board[row][col] == 0) { moreMoves = true; } | |
} | |
} | |
if (moreMoves) { | |
return 0;// game still valid; | |
} else { | |
return 3;// draw | |
} | |
} | |
int main (int argc, char* argv[]) | |
{ | |
// State variables | |
// | |
// Board: | |
// 0 1 2 | |
// +---+---+---+ | |
// 0 | | | x | x = board[0][2] | |
// +---+---+---+ | |
// 1 | | | | | |
// +---+---+---+ | |
// 2 | | | | | |
// +---+---+---+ | |
// | |
// Each board square will be in one of three possible | |
// states: 0=empty, 1=owned by 'x', 2=owned by 'o'. | |
int board[3][3] = {}; | |
// Flag to determine whether or not the game is finished | |
// false: game should end: someone has won or there are no more valid moves | |
// true: game may continue | |
bool gameIsValid = true; | |
// Flag to determine whose turn it is | |
// false: it is the computer's turn to make a move | |
// true: it is the player's turn to make a move | |
bool playerTurn = true; | |
/* Game Flow */ | |
// 1. determine who goes first | |
playerTurn = rand() % 2; // coin flip | |
// 2. Enter the main game "loop" | |
while (gameIsValid) { | |
// a. draw the board | |
drawBoard(board); | |
// b. current player makes a move | |
// the logic here depends on whether or not the | |
// computer or the player is the current player | |
if (playerTurn) { | |
// Some function for asking the user what the | |
// next move should be... | |
nextPlayerMove(board); | |
} else { | |
// Some function for determining what the next | |
// move should be... | |
nextComputerMove(board); | |
} | |
// c. check if the current player has just won | |
if (isGameOver(board) > 0) { | |
break; | |
} else { | |
// d. swap current player | |
playerTurn = (playerTurn) ? false : true; | |
} | |
} | |
// 3. print final game result message | |
cout << "Game over" << endl; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment