HiveBrain v1.2.0
Get Started
← Back to all entries
snippetcppMinor

Brief TicTacToe C++ example

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
brieftictactoeexample

Problem

Recently, I ventured into the realm of C++ programming. I have extensive knowledge in C and C#, but very basic knowledge of C++. I decided to build a brief TicTacToe example to test my knowledge.

It works by typing any comma-separated value (i.e. 2,1) and decides whose turn it is by odds and evens. X always goes first.

Problems: Does not claim a winner. There is no fail-safe for picking the same spot twice. I realize this, but I wanted the semantics over the algorithm. And I don't know of a sure-fire way to find a winner with a vector since I imagine a sea of nested if-statements.

I would love to know what looks legal, and what I should never do again.

Board.h

#include 
#include 
#include 
#include 
#include 

class Board
{
    /*
    ** Protected boolean for checking for winner
    */
    bool gameWon = false;

    /*
    ** Use odd or even to tell whos turn it is
    */
    int turnCount = 0;

public:

    /*
    ** Pair vector for locations of X's and O's
    */
    std::vector> locations;

    bool GameWon(void){ return gameWon; }
    void DrawBoard(void);
    void NextTurn(void);

private:
    int FindLocation(std::pair);

};


Board.cpp

```
#include "Board.h"

const std::string line = " ------------- ";
const std::string wall = " | ";

/*
** Brute Force Drawing
**
** Iterate over the 9 squares and
** decide whether or not an X belongs
** there or if it's an O. Upon each
** square, check if the pair-location
** (x,y) matches an item in our vector
*/
void Board::DrawBoard()
{
// Clear screen on windows systems, throws an error
// on Unix and OS X
system("CLS");

std::cout 0)
{

for (std::pair p : locations)
{

if (p.first == i && p.second == j)
{
tileOpen = false;

if (FindLocation(p) % 2 == 0)
{
std::cout > input;

locations.push_back(std::pair(

Solution

I would reorganize the code somewhat in order to separate concerns a bit more. Right now, game rules, I/O, etc, are all mixed together.

Board.h

#pragma once


This line should start every header file (the alternative is include guards; see Wikipedia's explanation.

#include 

namespace tictactoe {


All C++ code should go into a namespace; this is essentially a package name. It prevents naming conflicts with other libraries and has other benefits as well.

/* Represents the current state of a tic-tac-toe board. */
class Board {


You should always document your classes.

public:
    Board();

    enum Square {
      BLANK,
      X_SQUARE,
      O_SQUARE,
    }

    enum Player {
      NEITHER,
      X_PLAYER,
      O_PLAYER,
    }

    /* Throws std::out_of_range if x or y are not in [1,3]. */
    Square GetSquare(int x, int y) const;
    /* Whose turn is it? */
    Player GetCurrentPlayer() const;


Functions that do not modify their class should be marked const.

/* Updates the board with the current player's move.
     *
     * Returns true if the game is over.
     *
     * Throws std::out_of_range if x or y are not in [1,3].
     * Throws std::invalid_argument if the square is already occupied.
     * Throws game_over if called after the game is over. */
    bool PlayTurn(int x, int y);

    Player GameWinner() const;

  private:
    void UpdateGameWinner();

    Square board[3][3] = {{BLANK, BLANK, BLANK},
                          {BLANK, BLANK, BLANK},
                          {BLANK, BLANK, BLANK}};
    Player current_player;
    Player game_winner;
    bool game_over;
};

struct game_over : public std::exception {
  const char* what() const override;
};

}  // namespace tictactoe


OK, so you see a couple things -- one, Board is not responsible for reading and writing from console. It is the model in a Model-View-Presenter pattern. Two, we're not representing state as you did. There's nothing that says that you have to store data in the same format you receive it (e.g. a sequence of pair). Instead, we transform it into something more useful - the actual board pattern. Here we use a two-dimensional array instead of a vector because we know that the size won't change.

BoardView.h

#pragma once

#include 

#include "Board.h"

namespace tictactoe {

/* Interacts with the user in a Tic-Tac-Toe game. */
class BoardView {
  public:
    /* Gets the next move for the given player. */
    virtual std::pair GetMove(Board::Player player) = 0;
    /* Reasons a move may have been bad. */
    enum BadMoveReason {
      OUT_OF_RANGE,  // The coordinates were off the board.
      ALREADY_TAKEN,  // The square is already taken.
    };
    /* Tell the user that the move was bad. */
    virtual void DisplayBadMove(BadMoveReason reason) = 0;
    /* Displays the board. */
    virtual void DrawBoard(const Board& board) = 0;
    /* The game is over. Display the winner. */
    virtual void DisplayWinner(Board::Player player) = 0;
};
}  // namespace tictactoe


You can imagine multiple implementations of BoardView; one for a console game, one for a Windows GUI, one for a Unix GUI, etc. But note that BoardView doesn't really need to know anything about the rules of the game - except possibly that coordinates should be between 1 and 3.

I'll leave the rest of the implementation as an exercise to the reader.

Code Snippets

#pragma once
#include <exception>

namespace tictactoe {
/* Represents the current state of a tic-tac-toe board. */
class Board {
public:
    Board();

    enum Square {
      BLANK,
      X_SQUARE,
      O_SQUARE,
    }

    enum Player {
      NEITHER,
      X_PLAYER,
      O_PLAYER,
    }

    /* Throws std::out_of_range if x or y are not in [1,3]. */
    Square GetSquare(int x, int y) const;
    /* Whose turn is it? */
    Player GetCurrentPlayer() const;
/* Updates the board with the current player's move.
     *
     * Returns true if the game is over.
     *
     * Throws std::out_of_range if x or y are not in [1,3].
     * Throws std::invalid_argument if the square is already occupied.
     * Throws game_over if called after the game is over. */
    bool PlayTurn(int x, int y);

    Player GameWinner() const;

  private:
    void UpdateGameWinner();

    Square board[3][3] = {{BLANK, BLANK, BLANK},
                          {BLANK, BLANK, BLANK},
                          {BLANK, BLANK, BLANK}};
    Player current_player;
    Player game_winner;
    bool game_over;
};

struct game_over : public std::exception {
  const char* what() const override;
};

}  // namespace tictactoe

Context

StackExchange Code Review Q#115871, answer score: 3

Revisions (0)

No revisions yet.