Created
February 27, 2017 02:27
-
-
Save JackDraak/304b31a78d033390a82e30aa90bba90a to your computer and use it in GitHub Desktop.
C++ TicTacToe (for the Windows console)
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
/* | |
Tic - Tac - Console | |
by: @JackDraak - Playing around: just a humble hobbyist | |
tinkerer, messing with C++ to have fun | |
and maybe even learn something. | |
This is my first working version of a one-player | |
take on the the classic pen and paper (or should I | |
say, "stick and dirt"?) game: tic - tac - toe | |
[designed to now be played on the Windows Console.] | |
Where to go from here: | |
- improve formatting, use colour(?) | |
- optimize source-code | |
- separate into MFC class and Game class, use a header file.... | |
*/ | |
// Used for Input and Output. | |
#include <iostream> | |
#include <sstream> | |
// Used by A.I. player. | |
#include <random> | |
// Used to treat the console display as a textbox. | |
#include <windows.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <stdlib.h> | |
// For quality randomness, use this Entropy. | |
std::mt19937 Entropy = std::mt19937{ std::random_device{}() }; | |
// The 9 cells of the traditional game grid will be box objects in memory. | |
class box | |
{ | |
private: | |
char value; | |
char identity; | |
int screenColumn; | |
int screenRow; | |
public: | |
box(); | |
char GetValue(); | |
char GetID(); | |
int GetScreenColumn(); | |
int GetScreenRow(); | |
void Reset(); | |
void SetValue(char); | |
void SetID(char); | |
void SetScreenColumn(int); | |
void SetScreenRow(int); | |
}; | |
// Default initializations for a new box object. | |
box::box() { value = 'U'; identity = '0'; screenColumn = 0; screenRow = 0; return; } | |
// Box getters and setters. | |
char box::GetValue() { return value; } | |
char box::GetID() { return identity; } | |
int box::GetScreenColumn() { return screenColumn; } | |
int box::GetScreenRow() { return screenRow; } | |
void box::Reset() { value = identity; return; } | |
void box::SetValue(char newValue) { value = newValue; return; } | |
void box::SetID(char newID) { identity = newID; return; } | |
void box::SetScreenColumn(int sColumn) { screenColumn = sColumn; return; } | |
void box::SetScreenRow(int sRow) { screenRow = sRow; return; } | |
// Global variables. | |
char const winSets[8][3] = { | |
{ '1','2','3' }, // 0 row 1 | |
{ '4','5','6' }, // 1 row 2 | |
{ '7','8','9' }, // 2 row 3 | |
{ '1','4','7' }, // 3 column 1 | |
{ '2','5','8' }, // 4 column 2 | |
{ '3','6','9' }, // 5 column 3 | |
{ '1','5','9' }, // 6 backslash | |
{ '7','5','3' }, // 7 foreslash | |
}; | |
box gameBoard[3][3]; | |
char activePlayer; | |
char blockingPlay; | |
char winingSet; | |
char winner; | |
bool playerXturn = false; | |
int blockingSet; | |
// Method prototypes. | |
bool CheckForBlock(); | |
bool CheckForWin(); | |
bool Continue(); | |
bool ValidatePlay(char); | |
char GetBlockingPlay(int); | |
char GetPlay(); | |
char GetBoxValue(char); | |
char Shuffle(char, char); | |
int Shuffle(int); | |
int Shuffle(int, int); | |
void Home(); | |
void Home(bool); | |
void Home(int, int); | |
void InitBoard(); | |
void PauseForInput(); | |
void PrintEmptyBoard(); | |
void PrintValues(); | |
void SwitchPlayer(); | |
// Switch active player from X to O or vice versa. | |
void SwitchPlayer() | |
{ | |
playerXturn = !playerXturn; | |
if (playerXturn) activePlayer = 'X'; | |
else activePlayer = 'O'; | |
} | |
// Print the game box::value's into the game-grid on screen. | |
void PrintValues() | |
{ | |
for (int row = 0; row < 3; row++) | |
{ | |
for (int column = 0; column < 3; column++) | |
{ | |
int boxRow = gameBoard[row][column].GetScreenRow(); | |
int boxColumn = gameBoard[row][column].GetScreenColumn(); | |
char boxValue = gameBoard[row][column].GetValue(); | |
// Output value to designated screen position. | |
Home(boxRow, boxColumn); | |
std::cout << boxValue; | |
} | |
} | |
Home(10, 0); | |
return; | |
} | |
// Initialize gameBoard array with content (numbered boxes) and position information. | |
void InitBoard() | |
{ | |
// Assign game grid rows and columns to on-screen coordinates. | |
int const R1 = 4; | |
int const R2 = 6; | |
int const R3 = 8; | |
int const C1 = 17; | |
int const C2 = 21; | |
int const C3 = 25; | |
char thisValue = '1'; | |
for (int row = 0; row < 3; row++) | |
{ | |
for (int column = 0; column < 3; column++) | |
{ | |
// Set initial value and reset ID <thisValue> of each box. | |
gameBoard[row][column].SetID(thisValue); | |
gameBoard[row][column].Reset(); | |
// Set designated on-screen row of each box. | |
if (row == 0) gameBoard[row][column].SetScreenRow(R1); | |
else if (row == 1) gameBoard[row][column].SetScreenRow(R2); | |
else if (row == 2) gameBoard[row][column].SetScreenRow(R3); | |
// Set designated on-screen column of each box. | |
if (column == 0) gameBoard[row][column].SetScreenColumn(C1); | |
else if (column == 1) gameBoard[row][column].SetScreenColumn(C2); | |
else if (column == 2) gameBoard[row][column].SetScreenColumn(C3); | |
thisValue++; | |
} | |
} return; | |
} | |
// Get a boxID <char 1-9> from the console user. | |
char GetPlay() | |
{ | |
std::string userInput; | |
char position; | |
bool outOfRange = true; | |
do | |
{ | |
getline(std::cin, userInput); | |
std::stringstream thisStream(userInput); | |
Home(15, 0); std::cout << " "; | |
if (thisStream >> position) { break; } | |
Home(15, 0); std::cout << " Invalid entry..."; | |
if (position >= '1' && position <= '9') outOfRange = false; | |
} while (outOfRange); | |
return position; | |
} | |
// Clear and home the console, and put up an empty game board. | |
void PrintEmptyBoard() | |
{ | |
Home(true); | |
std::cout << /// 10 20 30 4 | |
///0123456789-123456789-123456789-123456///0 | |
"\n -------------------" ///1 | |
"\n Tic - Tac - Console" ///2 | |
"\n" ///3 | |
"\n | | " ///4 | |
"\n ---|---|---" ///5 | |
"\n | | " ///6 | |
"\n ---|---|---" ///7 | |
"\n | | " ///8 | |
"\n"; ///9 | |
Home(10, 0); | |
return; | |
} | |
// Use this function to "Home" the cursor to the top left of the console. | |
void Home() | |
{ | |
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); | |
COORD coordScreen = { 0, 0 }; | |
SetConsoleCursorPosition(hConsole, coordScreen); | |
return; | |
} | |
// Use this function overload to "Home" the cursor to a specific row:column. | |
void Home(int row, int column) | |
{ | |
short thisRow = row; | |
short thisColumn = column; | |
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); | |
COORD coordScreen = { thisColumn, thisRow }; | |
SetConsoleCursorPosition(hConsole, coordScreen); | |
return; | |
} | |
// Use this overload with a true or false argument for a "Clear and Home" effect in the console. | |
void Home(bool clear) | |
{ | |
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); | |
COORD coordScreen = { 0, 0 }; | |
BOOL bSuccess; | |
DWORD cCharsWritten; | |
CONSOLE_SCREEN_BUFFER_INFO csbi; | |
DWORD dwConSize; | |
bSuccess = GetConsoleScreenBufferInfo(hConsole, &csbi); | |
if (!bSuccess) return; | |
dwConSize = csbi.dwSize.X * csbi.dwSize.Y; | |
bSuccess = FillConsoleOutputCharacter(hConsole, (TCHAR) ' ', dwConSize, coordScreen, &cCharsWritten); | |
if (!bSuccess) return; | |
bSuccess = GetConsoleScreenBufferInfo(hConsole, &csbi); | |
if (!bSuccess) return; | |
bSuccess = FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten); | |
if (!bSuccess) return; | |
bSuccess = SetConsoleCursorPosition(hConsole, coordScreen); | |
if (!bSuccess) return; | |
} | |
// Return a random int from 0-max. | |
int Shuffle(int max) | |
{ | |
std::uniform_int_distribution<> zIndexDist(0, max); | |
return zIndexDist(Entropy); | |
} | |
// Return a random int from min-max. | |
int Shuffle(int min, int max) | |
{ | |
std::uniform_int_distribution<> zIndexDist(min, max); | |
return zIndexDist(Entropy); | |
} | |
// Return a random char from begin to end. | |
char Shuffle(char begin, char end) | |
{ | |
std::uniform_int_distribution<> zIndexDist(begin, end); | |
return zIndexDist(Entropy); | |
} | |
// Delay for console input. | |
void PauseForInput() | |
{ | |
std::string userInput; | |
Home(10, 0); std::cout << " Please hit <Enter> to proceed... "; | |
getline(std::cin, userInput); | |
return; | |
} | |
// True if continued play is requested, false if exit requested. | |
bool Continue() | |
{ | |
bool validInput = false; | |
bool proceed = false; | |
do | |
{ | |
std::string userInput; | |
Home(10, 0); std::cout << " Please enter (P)lay or (Q)uit then <Enter>, thank you. "; | |
getline(std::cin, userInput); | |
if (userInput[0] == 'p' || userInput[0] == 'P') | |
{ | |
validInput = true; | |
proceed = true; | |
} | |
else if (userInput[0] == 'q' || userInput[0] == 'Q') | |
{ | |
validInput = true; | |
} | |
} while (!validInput); | |
Home(10, 0); std::cout << " "; | |
return proceed; | |
} | |
// Take a box ID and attempt to claim it for the activePlayer. | |
bool ValidatePlay(char id) | |
{ | |
bool validity = false; | |
for (int row = 0; row < 3; row++) | |
{ | |
for (int column = 0; column < 3; column++) | |
{ | |
if (id == gameBoard[row][column].GetID()) | |
{ | |
char boxValue = gameBoard[row][column].GetValue(); | |
if (boxValue == id) | |
{ | |
gameBoard[row][column].SetValue(activePlayer); | |
validity = true; | |
} | |
else if (boxValue == activePlayer && activePlayer == 'X') | |
{ | |
Home(12, 0); std::cout << " "; | |
Home(12, 0); std::cout << " Box " << id << "?!? You've already claimed that one! "; | |
Home(10, 0); | |
} | |
else if (activePlayer == 'X' && boxValue != activePlayer) | |
{ | |
Home(12, 0); std::cout << " "; | |
Home(12, 0); std::cout << " Box " << id << "?!? Your opponent already claimed that one! "; | |
Home(10, 0); | |
} | |
} | |
} | |
} return validity; | |
} | |
// Return the box::value of a box by box-ID. | |
char GetBoxValue(char id) | |
{ | |
char value = '-'; | |
for (int row = 0; row < 3; row++) | |
{ | |
for (int column = 0; column < 3; column++) | |
{ | |
if (gameBoard[row][column].GetID() == id) | |
{ | |
value = gameBoard[row][column].GetValue(); | |
return value; | |
} | |
} | |
} return value; | |
} | |
// Check winSets against gameBoard. | |
bool CheckForWin() | |
{ | |
bool won = false; | |
char thisSet[3]; | |
for (int winSetRow = 0; winSetRow <= 7; winSetRow++) | |
{ | |
for (int member = 0; member <= 2; member++) | |
{ | |
thisSet[member] = winSets[winSetRow][member]; | |
} | |
int xScore = 0; | |
int oScore = 0; | |
for (int member = 0; member <= 2; member++) | |
{ | |
char thisValue = thisSet[member]; | |
char thisBoxValue = GetBoxValue(thisValue); | |
if (thisBoxValue != thisValue) | |
{ | |
if (thisBoxValue == 'X') xScore++; | |
else oScore++; | |
} | |
} | |
if (xScore == 3 || oScore == 3) | |
{ | |
Home(16, 0); std::cout << " Congratulations, player " << activePlayer << " for the win! (WinPatternID:" << winSetRow << ")"; | |
won = true; | |
} | |
} return won; | |
} | |
// Check gameBoard array, looking for any prescribed blocking play; when true, follow with GetBlockingPlay() on the identified set. | |
bool CheckForBlock() | |
{ | |
bool block = false; | |
char thisSet[3]; | |
blockingSet = -1; | |
for (int winSetRow = 0; winSetRow <= 7; winSetRow++) | |
{ | |
for (int member = 0; member <= 2; member++) | |
{ | |
thisSet[member] = winSets[winSetRow][member]; | |
} | |
int xScore = 0; | |
int oScore = 0; | |
blockingPlay = 'U'; | |
for (int member = 0; member <= 2; member++) | |
{ | |
char thisValue = thisSet[member]; | |
char thisBoxValue = GetBoxValue(thisValue); | |
if (thisBoxValue != thisValue) | |
{ | |
if (thisBoxValue == 'X') xScore++; | |
else oScore++; | |
} | |
} | |
if ((xScore == 2 && oScore == 0) || (xScore == 0 && oScore == 2)) | |
{ | |
blockingSet = winSetRow; | |
block = true; | |
} | |
} return block; | |
} | |
// To be used if CheckForBlock identifies a blockingSet in order to return the blockingPlay. | |
char GetBlockingPlay(int blockingSet) | |
{ | |
char block = 'E'; | |
char thisSet[3]; | |
char thisValue; | |
for (int member = 0; member <= 2; member++) | |
{ | |
thisSet[member] = winSets[blockingSet][member]; | |
thisValue = thisSet[member]; | |
char thisBoxValue = GetBoxValue(thisValue); | |
if (thisBoxValue == thisValue) block = thisValue; | |
} return block; | |
} | |
// Application entry point. | |
int main() | |
{ | |
// Play (at least one) game of Tic-Tac-Toe | |
do { | |
PrintEmptyBoard(); | |
InitBoard(); | |
PrintValues(); | |
SwitchPlayer(); | |
if (activePlayer == 'O') SwitchPlayer(); // For simplicity, start each game with console player as X. | |
blockingPlay = 'U'; | |
winner = '-'; | |
bool gameInProgress = true; | |
int turnNumber = 1; | |
do { | |
// Player X plays. | |
bool validMove = false; | |
do { | |
Home(10, 0); std::cout << " "; | |
Home(10, 0); std::cout << " Make a move, player " << activePlayer << ": "; | |
char thisPlay = GetPlay(); | |
validMove = ValidatePlay(thisPlay); | |
} while (!validMove); | |
PrintValues(); | |
gameInProgress = (!CheckForWin()); | |
if (turnNumber == 9 && gameInProgress) { winner = '-'; gameInProgress = false; } | |
else | |
{ | |
if (CheckForBlock()) blockingPlay = GetBlockingPlay(blockingSet); | |
} | |
turnNumber++; | |
SwitchPlayer(); | |
// Player O plays. | |
if (gameInProgress) | |
{ | |
bool validRandom; | |
if (!ValidatePlay(blockingPlay)) validRandom = false; else validRandom = true; | |
if (!validRandom) do { | |
char randomPlay = Shuffle('1', '9'); | |
validRandom = ValidatePlay(randomPlay); | |
} while (!validRandom); | |
PrintValues(); | |
gameInProgress = (!CheckForWin()); | |
turnNumber++; | |
SwitchPlayer(); | |
} | |
// Repeat X play then O play as needed. | |
} while (turnNumber < 10 && gameInProgress); | |
// Would you like to play a game? -War Games | |
} while (Continue()); | |
// End Of Line -Tron | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment