snippetcppMinor
Brief TicTacToe C++ example
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
I would love to know what looks legal, and what I should never do again.
Board.h
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(
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
This line should start every header file (the alternative is include guards; see Wikipedia's explanation.
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.
You should always document your classes.
Functions that do not modify their class should be marked
OK, so you see a couple things -- one,
BoardView.h
You can imagine multiple implementations of
I'll leave the rest of the implementation as an exercise to the reader.
Board.h
#pragma onceThis 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 tictactoeOK, 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 tictactoeYou 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 tictactoeContext
StackExchange Code Review Q#115871, answer score: 3
Revisions (0)
No revisions yet.