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

asked Jun 12, 2018 at 10:52
\$\endgroup\$

1 Answer 1

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.

answered Jun 12, 2018 at 12:15
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Very big for you advices. I will try to apply this advice and improve the game. \$\endgroup\$ Commented Jun 12, 2018 at 13:45

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.