6
\$\begingroup\$

I made my first console game in C++. It is a simple game. The game is also known as Concentration, Pelmanism, Shinkei-suijaku, Pexeso and Pairs. This is a simple console implementation of this game. There are 3 levels: easy, normal and complex. Also after the game the game statistics is displayed (for how many moves the game was passed).

I tried to use STL and object-oriented programming. Therefore, first of all I would like to know how to improve exactly in this. I would also like to know how neat and understandable my code is.

Code
Link to github

or

main.cpp

#include <iostream>
#include "include/game_control.hpp"

int main()
{
    // Start game
    GameControl memoryPuzzle;
    memoryPuzzle.startMenu();
    return 0;
}

GameControl - class for controlling the IR board and data I / O

game_control.hpp

#ifndef GAME_CONTROL_HPP
#define GAME_CONTROL_HPP

#include "../include/game_board.hpp"
#include <memory>

class GameControl
{
  public:
    GameControl() = default;
    ~GameControl() = default;

    void startMenu();
    void exitGame();
    void startGame();
    void nextTurn();

  private:
    std::size_t numberOfSteps = 0;
    std::unique_ptr<GameBoard> gameBoard;
};

#endif // !GAME_CONTROL

game_control.cpp

#include "../include/game_control.hpp"
#include <iostream>
#include <iomanip>
#include <cstdlib>

// Cleaning the console ( for Windows and Linux )
void clearScreen();
// Erase input from errors and invalid input
void clearInput();

void GameControl::startMenu()
{
    clearScreen();
    std::cout << "#   # #### #   # ##### #### #   # . #### #  # #### #### #    ####\n"
              << "## ## #    ## ## #   # #  #  # #  . #  # #  #   ##   ## #    #   \n"
              << "# # # #### # # # #   # ####   #   . #### #  #  ##   ##  #    ####\n"
              << "#   # #    #   # #   # # #    #   . #    #  # ##   ##   #    #   \n"
              << "#   # #### #   # ##### #  #   #   . #    #### #### #### #### ####\n";

    // Show game menu
    unsigned int selection;
    enum class MenuItems
    {
        START_GAME,
        EXIT
    };
    do
    {
        std::cout << "\nGame menu ( To select, enter 1, 2 or 3 )\n"
                  << std::setw(5) << "1. "
                  << "Start game\n"
                  << std::setw(5) << "3. "
                  << "Exit\n"
                  << ">> ";
        std::cin >> selection;
        std::cin.clear();
        std::cin.sync();
        selection--;
    } while (selection > 2);

    switch ((MenuItems)selection)
    {
    case MenuItems::START_GAME:
        startGame();
        break;
    case MenuItems::EXIT:
        exitGame();
        break;
    default:
        break;
    }
}

// Cleaning the console ( for Windows and Linux )
void clearScreen()
{
#ifdef __WIN32
    system("cls");
#else
    system("clear");
#endif // __WIN32
}

// Erase input from errors and invalid input
void clearInput()
{
    std::cin.clear();
    std::cin.sync();
}

void GameControl::exitGame()
{
    clearScreen();
    std::cout << "Thanks for the game!";
    exit(EXIT_SUCCESS);
}

void GameControl::startGame()
{
    clearScreen();
    unsigned int selection;
    enum class Level
    {
        EASY,
        NORMAL,
        HARD
    };
    do
    {
        std::cout << "\nPlease choose difficulty level ( To select, enter 1, 2 or 3 )\n"
                  << std::setw(5) << "1. "
                  << "Easy\n"
                  << std::setw(5) << "2. "
                  << "Normal\n"
                  << std::setw(5) << "3. "
                  << "Hard\n"
                  << ">> ";
        std::cin >> selection;
        clearInput();
        selection--;
    } while (selection > 2);

    switch ((Level)selection)
    {
    case Level::EASY:
        gameBoard = std::unique_ptr<GameBoard>(new GameBoard(3, 4));
        break;
    case Level::NORMAL:
        gameBoard = std::unique_ptr<GameBoard>(new GameBoard(4, 4));
        break;
    case Level::HARD:
        gameBoard = std::unique_ptr<GameBoard>(new GameBoard(4, 5));
        break;
    default:
        break;
    }

    while (!gameBoard->allGuessed())
    {
        nextTurn();
    }
    // Show the result of the game
    std::cout << "Excellent! You Won!\n"
              << "Your score = " << numberOfSteps << '\n';
    system("pause");
    startMenu();
}

void GameControl::nextTurn()
{
    clearScreen();
    gameBoard->show();
    std::size_t row;
    std::size_t column;
    std::cout << "Enter coordinates of first card:\n";
    std::size_t firstChoice;
    do
    {
        do
        {
            std::cout << "row ( 1 - " << gameBoard->WIDTH << " ) : ";
            std::cin >> row;
            clearInput();
            row--;
        } while (row > gameBoard->WIDTH - 1);
        do
        {
            std::cout << "column ( 1 - " << gameBoard->LENGTH << " ) : ";
            std::cin >> column;
            clearInput();
            column--;
        } while (column > gameBoard->LENGTH - 1);
        // Calculate the array index from matrix indices
        firstChoice = (row * (gameBoard->LENGTH)) + column;

        if (gameBoard->isGuessed(firstChoice))
        {
            std::cout << "You already guessed this card!\nRe-enter:\n";
        }
        else
        {
            break;
        }
    } while (true);
    gameBoard->turn(firstChoice);
    gameBoard->show();

    std::cout << "Enter coordinates of second card:\n";
    std::size_t secondChoice;
    do
    {
        do
        {
            std::cout << "row ( 1 - " << gameBoard->WIDTH << " ) : ";
            std::cin >> row;
            clearInput();
            row--;
        } while (row > gameBoard->WIDTH - 1);
        do
        {
            std::cout << "column ( 1 - " << gameBoard->LENGTH << " ) : ";
            std::cin >> column;
            clearInput();
            column--;
        } while (column > gameBoard->LENGTH - 1);
        // Calculate the array index from matrix indices
        secondChoice = (row * (gameBoard->LENGTH)) + column;

        if (gameBoard->isGuessed(secondChoice))
        {
            std::cout << "You already guessed this card!\nRe-enter:\n";
        }
        else if (gameBoard->isTurned(secondChoice))
        {
            std::cout << "You already turned this card!\nRe-enter:\n";
        }
        else
        {
            break;
        }
    } while (true);
    gameBoard->turn(secondChoice);
    gameBoard->show();

    if (gameBoard->isPair(firstChoice, secondChoice))
    {
        std::cout << "You are guessed!\n";
        gameBoard->guess(firstChoice);
        gameBoard->guess(secondChoice);
    }
    else
    {
        std::cout << "You are mistaken!\n";
        gameBoard->turn(firstChoice);
        gameBoard->turn(secondChoice);
    }
    system("pause");
    numberOfSteps++;
}

game_board.hpp

#ifndef GAME_BOARD_HPP
#define GAME_BOARD_HPP

#include <vector>
#include <array>

class GameBoard
{
public:
  GameBoard() = delete;
  GameBoard(const GameBoard &gameBoard) = delete;
  GameBoard &operator=(const GameBoard &gameBoard) = delete;
  GameBoard(std::size_t width, std::size_t length);
  ~GameBoard() = default;

  void turn(std::size_t index);
  bool isTurned(std::size_t index);

  void guess(std::size_t index);
  bool isGuessed(std::size_t index);

  bool isPair(std::size_t first, std::size_t second);
  bool allGuessed();
  void show();

  // Sizes of board
  const std::size_t WIDTH;
  const std::size_t LENGTH;
  // Sizes of card
  const static std::size_t CARD_WIDTH = 3;
  const static std::size_t CARD_LENGTH = 5;

private:
  // Default card shirt
  const std::vector<std::vector<char>> DEFAULT_PATTERN =
  {
          {'+', '-', '-', '-', '+'},
          {'|', 'C', '+', '+', '|'},
          {'+', '-', '-', '-', '+'},
  };
  // Already guessed cards
  std::vector<bool> guessed;
  // Upside down cards
  std::vector<bool> turned;
  // Array of cards pictures
  std::vector<std::vector<std::vector<char>>> cards;
};

#endif // !GAME_BOARD

game_board.cpp

#include "../include/game_board.hpp"
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <iostream>

GameBoard::GameBoard(std::size_t width, std::size_t length) : WIDTH(width), LENGTH(length)
{
    srand(time(nullptr));
    // Calculating the number of cards and the number of pairs
    const std::size_t numberOfCards = WIDTH * LENGTH;
    const std::size_t numberOfPairs = numberOfCards / 2;
    // Mark everything as unguessed and not turned
    turned = std::vector<bool>(numberOfCards, false);
    guessed = std::vector<bool>(numberOfCards, false);
    // Creating drawings and mixing them
    for (std::size_t i = 0; i < numberOfPairs; ++i)
    {
        char picture = (char(i + 33));
        std::vector<std::vector<char>> card =
            {
                {'#', '-', '-',     '-', '#'},
                {'|', ' ', picture, ' ', '|'},
                {'#', '-', '-',     '-', '#'}
            };
        cards.push_back(card);
        cards.push_back(card);
    }
    std::random_shuffle(cards.begin(), cards.end());
}

void GameBoard::turn(std::size_t index)
{
    turned[index] = !turned[index];
}

bool GameBoard::isTurned(std::size_t index)
{
    return turned[index];
}

void GameBoard::guess(std::size_t index)
{
    guessed[index] = true;
}

bool GameBoard::isGuessed(std::size_t index)
{
    return guessed[index];
}

bool GameBoard::isPair(std::size_t first, std::size_t second)
{
    return (cards[first] == cards[second]);
}

bool GameBoard::allGuessed()
{
    return std::all_of(guessed.begin(), guessed.end(), [](bool x) { return x; });
}

void GameBoard::show()
{
    // Here the matrix of matrices is printed.
    // There is a matrix of cards.
    // And each card is a matrix of symbols.
    for(std::size_t row = 0; row < WIDTH; ++row)
    {
        for(std::size_t internalRow = 0; internalRow < CARD_WIDTH; ++internalRow)
        {
            for(std::size_t column = 0; column < LENGTH; ++column)
            {
                // Calculate the array index from matrix indices
                std::size_t cardIndex = (row * LENGTH) + column;
                for(std::size_t internalColumn = 0; internalColumn < CARD_LENGTH; ++internalColumn)
                {
                    if(guessed[cardIndex] || turned[cardIndex])
                    {
                        std::cout << cards[cardIndex][internalRow][internalColumn];
                    }
                    else
                    {
                        std::cout << DEFAULT_PATTERN[internalRow][internalColumn];
                    }
                }
                std::cout << ' ';
            }
            std::cout << '\n';
        }
        std::cout << '\n';
    }
}

I will be very grateful for the advice
PS Sorry for bad english

\$\endgroup\$
7
\$\begingroup\$
  1. You should comment your code more. You write very clear code, but still you should try and explain the reasoning behind the design decisions you made to help you remember n years down the line.

  2. GameControl::startMenu() - Rather than use the loop like you have just pass selection directly into the switch statement. Place the whole function body in a loop and if the result is not 1 or 2 then let the code loop. If it is 1, you probably want to loop again, if it is 2 you can call break and drop out.

  3. GameControl::exitGame() - Don't use exit, rework you logic so you drop back into main and this will let you free everything up properly. This is not important now, but might be in future programs, so its more about good habits now.

  4. GameControl::startMenu() - This is another menu. You should refactor both this and the main menu code into a common function and pass the data into it using a vector of structures {std::wstring menuOption; std::function<BLAH> action;}

  5. GameControl::nextTurn() - You are declaring variable and not assigning initial values to them. In debug builds this is 'OK' but in release builds its really bad, because most compilers don't assign initial values in release configuration. I appreciate that at the moment, its perfectly safe because you are reading a value into them before reading them, but you might modify the code and break that.

  6. GameControl::nextTurn() - The scope of row and column seems to be too wide, it looks like you could move them inside the first do loop.

  7. GameControl::nextTurn() - IMO while(true) is lazy. There must be away of using a bool to break the loop.

  8. GameControl::nextTurn() - first choice and second choice should be refactored out into a function.

  9. std::vector has a slower access than 'std::map' so you might get a performance improvement by switching to maps, but I'm not sure how you would rework the way your store the data to take advantage of that.

\$\endgroup\$
  • 1
    \$\begingroup\$ Very big for you advices. I will try to apply this advice and improve the game. \$\endgroup\$ – Марк Стариков Jun 12 '18 at 13:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.