For the purpose of becoming better at C++ programming, I wanted to program Ultimate Tic-Tac-Toe as a console application for Windows in C++. However, I thought it would be sensible to create the plain version of Tic-Tac-Toe first. So I made this simple program which I'll later extend/modify for the Ultimate version. I'll have some learning to do, but my goal is to write "flawless" code.
I'd like to know how I can improve my code, what some best practices are (that I'm omitting) and last but not least, what I'm already doing well.
main.cpp
#include "stdafx.h"
#include <iostream>
#include <windows.h>
constexpr int cols = 3;
constexpr int rows = 3;
constexpr char cellValueX = 'X';
constexpr char cellValueO = 'O';
/*
Clear console on Windows.
Code used from: https://stackoverflow.com/a/6487534/1964817
*/
void clearConsole()
{
COORD topLeft = {0, 0};
HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO screen;
DWORD written;
GetConsoleScreenBufferInfo(console, &screen);
FillConsoleOutputCharacterA(console, ' ',
screen.dwSize.X * screen.dwSize.Y,
topLeft, &written);
FillConsoleOutputAttribute(console,
FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE,
screen.dwSize.X * screen.dwSize.Y, topLeft, &written);
SetConsoleCursorPosition(console, topLeft);
}
class Player
{
private:
char value;
bool isAvailable{true};
public:
Player() {}
char getValue() { return value; }
bool operator==(const Player& compareTo) { return value == compareTo.value; }
void setValue(char newValue)
{
if(isAvailable && !(value == cellValueX || value == cellValueO))
{
value = newValue;
isAvailable = false;
}
else
{
std::cout << "Can't change value (value already set)." << std::endl;
}
}
};
class Grid
{
private:
class Cell
{
private:
int gridIndexGenerator()
{
static int index{0};
return index++;
};
const int id{gridIndexGenerator() + 1};
char value{static_cast<char>('0' + id)};
bool isAvailable{true};
public:
Cell() {}
bool getIsAvailable() { return isAvailable; }
char getValue() { return value; }
void setValue(char newValue)
{
if(isAvailable && !(value == cellValueX || value == cellValueO))
{
value = newValue;
isAvailable = false;
}
else
{
std::cout << "Can't change value (value already set)." << std::endl;
}
};
};
enum WinDirection
{
HORIZONTAL,
VERTICAL,
DIAGONAL,
LAST
};
public:
Grid() {}
Cell cells[rows][cols];
void display();
bool checkWin(char playerValue, WinDirection direction);
bool checkWin(Player player);
};
void Grid::display()
{
clearConsole();
for(int row = 0; row < rows; row++)
{
std::cout << std::endl;
for(int col = 0; col < cols; col++)
{
const char value{cells[row][col].getValue()};
std::cout << "[" << value << "]";
}
}
std::cout << std::endl << std::endl;
}
bool Grid::checkWin(char playerValue, WinDirection direction)
{
switch(direction)
{
case WinDirection::DIAGONAL:
{
return ((cells[0][0].getValue() == playerValue &&
cells[1][1].getValue() == playerValue &&
cells[2][2].getValue() == playerValue) ||
(cells[2][0].getValue() == playerValue &&
cells[1][1].getValue() == playerValue &&
cells[0][2].getValue() == playerValue));
break;
}
case WinDirection::HORIZONTAL:
case WinDirection::VERTICAL:
{
bool result = false;
for(int i = 0; i < rows; i++)
{
switch(direction)
{
case WinDirection::HORIZONTAL:
{
result =
(cells[i][0].getValue() == playerValue &&
cells[i][1].getValue() == playerValue &&
cells[i][2].getValue() == playerValue);
break;
}
case WinDirection::VERTICAL:
{
result =
(cells[0][i].getValue() == playerValue &&
cells[1][i].getValue() == playerValue &&
cells[2][i].getValue() == playerValue);
break;
}
}
if(result)
{
return result;
}
}
return result;
}
}
}
bool Grid::checkWin(Player player)
{
bool result{false};
char playerValue{player.getValue()};
WinDirection direction = static_cast<WinDirection>(0);
for(int i = 0; i != WinDirection::LAST; direction = static_cast<WinDirection>(++i))
{
result = checkWin(playerValue, direction);
if(result)
{
return result;
}
}
return result;
}
class Game
{
public:
Game()
{
playerX.setValue(cellValueX);
playerO.setValue(cellValueO);
player = playerX;
}
void play();
void switchPlayer();
void getInput();
private:
Player playerX;
Player playerO;
Player player;
Grid gameGrid;
};
void Game::play()
{
bool isGameWon = false;
gameGrid.display();
for(int i = 0; i < cols * rows && !isGameWon; i++)
{
getInput();
gameGrid.display();
isGameWon = gameGrid.checkWin(player);
if(isGameWon)
{
std::cout << "Player " << player.getValue() << " won!" << std::endl;
}
else { switchPlayer(); }
}
if(!isGameWon)
{
std::cout << "It's a tie!" << std::endl;
}
}
void Game::switchPlayer()
{
if(player == playerX) { player = playerO; }
else if(player == playerO) { player = playerX; }
}
void Game::getInput()
{
std::cout << "Your turn, Player " << player.getValue() << "!" << std::endl;
int inputCell{0};
int col{-1};
int row{-1};
bool badInput{false};
bool isAvailable{false};
do
{
std::cout << "Select a cell (number, 1 - 9): ";
std::cin >> inputCell;
col = (inputCell - 1) % cols;
row = (inputCell - 1) / cols;
isAvailable = gameGrid.cells[row][col].getIsAvailable();
badInput = (!(inputCell >= 1 && inputCell <= 9) || !isAvailable);
std::cin.clear();
std::cin.ignore((std::numeric_limits<std::streamsize>::max)(), '\n');
}
while(badInput);
gameGrid.cells[row][col].setValue(player.getValue());
}
int main()
{
Game game;
game.play();
}
break;
in the for loop incheckWin(char playerValue, WinDirection direction)
. \$\endgroup\$