This is a follow up to my question here. Well, it ain't exactly a follow-up, but more like my next project after the last one
I created a tic tac toe game using object-oriented programming
You all already know how the tic tac toe works so I won't be wasting your time by explaining to you how it works
I'm looking for feedback on absolutely everything that could make me a better programmer, especially a better C++ programmer, also how to use class, function better, and how to use OOP correctly, and these:
- Optimization
- Bad practice and good practice
- Code structure
- Functions and variable naming
- Bugs
- Improving class and function usage
- How to correctly use OOP
- Oh, also how to add comment properly
- etc
Thank you very much!
I'm using Visual Studio Community 2019 ver 16.7.7
Globals.h
#ifndef GUARD_GLOBALS_H
#define GUARD_GLOBALS_H
namespace
{
enum class Players : char
{
PLAYER_X = 'X',
PLAYER_O = 'O'
};
}
#endif // !GUARD_GLOBALS_H
board.h
#ifndef GUARD_BOARD_H
#define GUARD_BOARD_H
#include "player.h"
class Board
{
private:
char board[9];
// This is suppose to be a place to put the score
// But I don't know how to implement it yet
int scoreX{};
int scoreO{};
public:
Board();
void printBoard() const;
void markBoard(const size_t& choseNum, const char& player, bool& inputPass);
char checkWin(bool& isDone, int& countTurn);
void printWinner(bool& isDone, int& countTurn);
};
#endif // !GUARD_BOARD_H
board.cpp
#include "board.h"
#include <iostream>
// To set the board with numbers
Board::Board()
{
int j{ 1 };
for (int i = 0; i < 9; i++)
{
board[i] = '0' + j++;
}
}
void Board::printBoard() const
{
system("cls");
std::cout << " | | " << "\n";
std::cout << " " << board[0] << " | " << board[1] << " | " << board[2] << "\tPlayer X: " << scoreX << "\n";
std::cout << "___|___|__" << "\tPlayer O: " << scoreO << "\n";
std::cout << " | | " << "\n";
std::cout << " " << board[3] << " | " << board[4] << " | " << board[5] << "\n";
std::cout << "___|___|__" << "\n";
std::cout << " | | " << "\n";
std::cout << " " << board[6] << " | " << board[7] << " | " << board[8] << "\n";
std::cout << " | | " << "\n\n";
}
// To change the board to which the player choose the number
void Board::markBoard(const size_t& choseNum, const char& player, bool& inputPass)
{
char checkNum = board[choseNum - 1];
// To check if the number that the player choose is available or not
if (checkNum != (char)Players::PLAYER_X && checkNum != (char)Players::PLAYER_O)
{
// To check if the number that the player input
if (choseNum >= 1 && choseNum <= 9)
{
board[choseNum - 1] = player;
inputPass = true;
}
else
{
std::cout << "CHOOSE THE AVAILABLE NUMBER!\nTRY AGAIN: ";
}
}
else
{
std::cout << "SPACE HAS ALREADY BEEN OCCUPIED\nTry again: ";
}
}
/*
There is probably a better way to do this. But, I don't know how tho
Maybe someday I could improve the checking for win but right now
this is good enough
Also, there are a lot of magic number here such as 8, 2, 6 and 7.
I've tried to remove the magic number but I don't know how.
*/
// Check the board if there is player with parallel set or not
char Board::checkWin(bool &isDone, int &countTurn)
{
/*
I use middleboard and initialize it to board[4] because in order
for a player to win diagonally they have to acquire the
middle board first. So, I initialize middleboard to board[4]
hoping it could remove the magic number
and I initialize i to 0 and j to 8 because the checking is
begin from the top left corner-middle-bottom right corner
if it false then I add add 2 to i and substract 2 from j
because now the checking is top right corner-middle-bottom left corner
*/
// Check diagonal win
size_t middleBoard = board[4];
for (size_t i = 0, j = 8; i <= 2 && j >= 6; i+=2, j-=2)
{
// If all the board is occupied by the same player then the same player win
if (middleBoard == board[i] && board[i] == board[j])
{
//This is suppose to add score, but I don't know how to implement it yet
board[middleBoard] == (char)Players::PLAYER_X ? scoreX++ : scoreO++;
isDone = true;
return middleBoard; // To return the character of the player who won
}
}
/*
I initialize initialNum to 0 as a starting point for the checking.
Initilialized i to 1 and j to 2
The checking is like this, top left corner-middle top-top right corner
If it false then the I add 3 to initialNum to make middle left as the
starting point, then add 3 to i and j so it the next checking is
middle left-middle-middle right, and so on
*/
// Check horizontal win
size_t initialNum = 0;
for (size_t i = 1, j = 2; i <= 7 && j <= 8; i += 3, j += 3)
{
if (board[initialNum] == board[i] && board[i] == board[j])
{
board[initialNum] == (char)Players::PLAYER_X ? scoreX++ : scoreO++;
isDone = true;
return board[initialNum];
}
else
{
initialNum += 3;
}
}
/*
I reset the initialNum to 0 and initialized i to 3 and j 6 so
the first check will be like this: top left corner-middle left-bottom left corner
if it fails then i add 1 to initialNum, i, and j, so the next check will be
middle top-middle-middle bottom and so on
*/
// Check vertical win
initialNum = 0;
for (size_t i = 3, j = 6; i <= 5 && j <= 8; i++, j++)
{
if (board[initialNum] == board[i] && board[i] == board[j])
{
board[initialNum] == (char)Players::PLAYER_X ? scoreX++ : scoreO++;
isDone = true;
return board[initialNum];
}
else
{
initialNum++;
}
}
// If the countTurn is 8 then there're no place to occupy anymore, thus a draw
if (countTurn == 8)
{
isDone = true;
return 'D'; // As a check for printWinner() function
}
countTurn++;
}
// To print who's the winner or draw
void Board::printWinner(bool& isDone, int& countTurn)
{
if (checkWin(isDone, countTurn) == 'D')
{
std::cout << "It's a Draw!\n";
}
else
{
std::cout << "Congratulations!\nPlayer " << checkWin(isDone, countTurn) << " won the game!\n";
}
}
player.h
#ifndef GUARD_PLAYER_H
#define GUARD_PLAYER_H
#include "Globals.h"
#include "board.h"
class Board;
class Player
{
private:
char mainPlayer;
char secondPlayer;
char turnPlayer = mainPlayer;
public:
void choosePlayer(bool &choosePass);
void movePlayer(Board& myBoard);
void switchPlayer();
};
#endif // !GUARD_PLAYER_H
player.cpp
#include "player.h"
#include "board.h"
#include <iostream>
#include <random>
// To give a choice for the player if they want to be X or O
void Player::choosePlayer(bool& choosePass)
{
char chosePlayer;
std::cout << "Do you want to be player X or O? ";
while (!choosePass)
{
std::cin >> chosePlayer;
// If the player type X uppercase or lowercase then they will be
// X and the computer will be O, vice versa
if (chosePlayer == 'x' || chosePlayer == 'X')
{
mainPlayer = (char)Players::PLAYER_X;
secondPlayer = (char)Players::PLAYER_O;
choosePass = true;
}
else if (chosePlayer == 'o' || chosePlayer == 'O')
{
mainPlayer = (char)Players::PLAYER_O;
secondPlayer = (char)Players::PLAYER_X;
choosePass = true;
}
else
{
std::cout << "Invalid choice\n Try again: ";
}
}
}
// To make a player choose a number to which they want to occupy
void Player::movePlayer(Board &myBoard)
{
size_t choseNum;
bool inputPass = false;
/*
I make it turnPlayer != mainPlayer because if I make it
turnPlayer == mainPlayer then the computer will make the first move
I don't know why. Probably should find out the why. But it'll do for now
*/
// If turnPlayer is not mainPlayer then it's the player's move
if (turnPlayer != mainPlayer)
{
std::cout << "Player " << mainPlayer << " choose a number: ";
while (!inputPass)
{
if (std::cin >> choseNum)
{
myBoard.markBoard(choseNum, mainPlayer, inputPass); //Go to markBoard function in board.cpp
}
else
{
std::cout << "Invalid input type (Type only number)\nTry again: ";
std::cin.clear(); // To clear the input so
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // the player can input again
}
}
}
// If the turnPlayer is mainPlayer then it's the computer's move
else
{
while (!inputPass)
{
// To make a random move for the computer
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> distrib(1, 9);
choseNum = distrib(gen);
myBoard.markBoard(choseNum, secondPlayer, inputPass);
}
}
}
// To change turn, if the player finishes then the computer will make the move
void Player::switchPlayer()
{
turnPlayer = (turnPlayer == mainPlayer) ? secondPlayer : mainPlayer;
}
main.cpp
#include "board.h"
#include "player.h"
int main()
{
Board myBoard;
Player mainPlayer;
int countTurn{ 0 };
bool choosePass = false;
bool isDone = false;
myBoard.printBoard(); // To print the initial board with numbered spaces
while (!isDone)
{
if (!choosePass)
{
mainPlayer.choosePlayer(choosePass);
}
mainPlayer.movePlayer(myBoard);
myBoard.printBoard();
mainPlayer.switchPlayer();
myBoard.checkWin(isDone, countTurn);
}
myBoard.printWinner(isDone, countTurn);
}
// This is suppose to be a place to put the score // But I don't know how to implement it yet
. There are also 4 compiler errors for me in board.cpp where this code is(char)Players::PLAYER_X ?
(hint there is a problem withPlayers
here). Does this game compile and run for you? \$\endgroup\$system('cls')
? \$\endgroup\$// This is suppose to be a place to put the score // But I don't know how to implement it yet
as such, it's off-topic. \$\endgroup\$