3
\$\begingroup\$

I am gaining a lot of interest in Game development. I created Chrome Dinosaur game in C++.

I have not paid much attention to UI/UX, just trying to see if i can apply some OOPs in game programming.

As I'm a beginner, I'm not sure whether I'm using good code design and logic.

I could use some code review and tips. Before I get my hands dirty in Game Programming.

The code is also in GitHub https://github.com/ankushTripathi/DinoBIT.

Config.h

here i define all my constants, to make code configurable from single file.

#pragma once
#define MAX_FRAME_RATE 400
#define CONSOLE_EXIT_KEY 'q'
#define CONSOLE_JUMP_KEY ' '
#define TREE_SYMBOL 'T'
#define DINO_SYMBOL 'x'
#define BLANK_SYMBOL '-'
#define INITAL_PADDING 2

#define PLAY_AREA_SIZE  22
#define MAX_TREE_DISTANCE  5
#define FRAME_RATE_REDUCTION 50
#define LEVEL_UP_FACTOR  3

#define PLAYER_START_POSITION 0
#define PLAYER_SPEED 1
#define PLAYER_JUMP_SPAN 3

Console.h

Abstract class, I understood consoles are handled differently in different OS. Implement Console class for required OS.

#pragma once
#include "Config.h"

class Console
{
protected:

    int frame_rate;
    char input_value;
    static const char exit_key = CONSOLE_EXIT_KEY;
    static const char jump_key = CONSOLE_JUMP_KEY;
    std::string output;

public:

    virtual void ClearScreen() = 0;
    virtual void Display(const std::string& str = "") = 0;
    virtual void Sleep() = 0;

    virtual void WaitForInput() = 0;
    virtual bool KeyPressed() = 0;
    virtual bool IsExitKey() = 0;
    virtual bool IsJumpKey() = 0;

    virtual void SetOutput(std::string) = 0;
    virtual void DecrementFameRate() = 0;
};

Win32Console.h

Extending Console class for Windows 32bit.

#pragma once
#ifdef _WIN32

#include<iostream>
#include <conio.h>
#include<thread>
#include<chrono>
#include <string>

#include "Console.h"

class Win32Console : public Console
{

public :

    Win32Console();

    void ClearScreen();
    void Display(const std::string& str);
    void Sleep();

    void SetOutput(std::string output);
    void DecrementFameRate();

    void WaitForInput();
    bool KeyPressed();
    bool IsExitKey();
    bool IsJumpKey();

    ~Win32Console();
};

#endif // _WIN32

Win32Console.cpp

Implementation of above class

#ifdef _WIN32
#include "Config.h"
#include "Win32Console.h"

Win32Console::Win32Console()
{
    frame_rate = MAX_FRAME_RATE;
    input_value = '\0';
    output = "";
}

void Win32Console::ClearScreen()
{
    std::cout << "\033[2J\033[1;1H" << std::flush;
}

void Win32Console::Display(const std::string& str)
{
    if (!str.length())
        std::cout << output << "\n";
    else
        std::cout << str << "\n";
}

void Win32Console::Sleep()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(frame_rate));
}

void Win32Console::SetOutput(std::string output)
{
    this->output = output;
}

void Win32Console::DecrementFameRate()
{
    this->frame_rate -= FRAME_RATE_REDUCTION;
}

void Win32Console::WaitForInput()
{
    std::cin.get(input_value);
}

bool Win32Console::KeyPressed()
{
    bool result = _kbhit();
    if (result) input_value = _getch();
    return result;
}

bool Win32Console::IsExitKey()
{
    return (input_value == exit_key);
}

bool Win32Console::IsJumpKey()
{
    return (input_value == jump_key);
}


Win32Console::~Win32Console()
{
}

#endif // _WIN32

Win64Console.h

Extending console class for Windows 64bit

#pragma once
#ifdef _WIN64

#include "Win32Console.h"

class Win64Console :
    public Win32Console
{
public :
    void ClearScreen() override;
};

#endif

Win64Console.cpp

#ifdef _WIN64

#include "Win64Console.h"

void Win64Console::ClearScreen()
{
    std::cout << std::string(100, '\n');
}

#endif // _WIN64

ConsoleFactory.h

Factory pattern to get Console of compatible OS and Architecture.

#pragma once
#include "Win32Console.h"
#include "Win64Console.h"

class ConsoleFactory
{
public:
    static Console* GetConsole();
};

ConsoleFactory.cpp

#include "ConsoleFactory.h"

Console* ConsoleFactory::GetConsole()
{
#ifdef __unix
    return new Console();
#endif // __unix
#ifdef __APPLE__
    return new Console();
#endif // __APPLE__
#ifdef _WIN64 
    return new Win64Console();
#endif  //_WIN64
#ifdef _WIN32 
    return new Win32Console();
#endif // _WIN32 
}

Player.h

Instance of this class represents Dino.

#pragma once
#include "Config.h"

class Player
{
protected:

    int position;
    int speed;
    int jump_span;
    int score;
    static int high_score;

public :

    Player();

    int GetPosition();
    int GetJumpSpan();

    void Run();
    void Jump();

    int GetScore();
    void SetScore(int);

    int GetHighScore();

    ~Player();
};

Player.cpp

#include "Player.h"

int Player::high_score = 0;

Player::Player() :
    position(PLAYER_START_POSITION),
    speed(PLAYER_SPEED),
    jump_span(PLAYER_JUMP_SPAN),
    score(0)
{
}

int Player::GetPosition()
{
    return position;
}

int Player::GetJumpSpan()
{
    return jump_span;
}

void Player::Run()
{
    position += speed;
    SetScore(position);
}

void Player::Jump()
{
    position += jump_span;
}

int Player::GetScore()
{
    return score;
}

void Player::SetScore(int score)
{
    this->score = score;
    high_score = (high_score > score)? high_score : score;
}

int Player::GetHighScore()
{
    return high_score;
}

Player::~Player()
{
}

Game.h

the main class for this game.

#pragma once
#include <string>
#include <deque>
#include<random>

#include "Player.h"
#include "ConsoleFactory.h"
#include "Config.h"


class Game
{

private:

    static Console* console;
    static Player player;

    static bool jump_flag;
    static int last_frame;
    static int last_tree_position;
    static const int play_area_size = PLAY_AREA_SIZE;
    static std::deque<bool> play_area;

    static std::mt19937 eng;

    static bool GameOverConditions();

    static bool IsLevelUp();
    static void LevelUp();

    static bool ShouldPlaceTree(int);
    static void Move();
    static std::string GenerateFrame();

    static bool Run();

public:

    static void Start();
    static void Loop();
    static bool ShouldRestart();
    static void Restart();

    static void End();
};

Game.cpp

#include "Game.h"

Console* Game::console = ConsoleFactory::GetConsole();
Player Game::player = Player();

std::mt19937 Game::eng{ std::random_device{}() };

bool Game::jump_flag = false;
int Game::last_frame = Game::play_area_size - 1;
int Game::last_tree_position = 0;
std::deque<bool> Game::play_area(Game::play_area_size, false);

std::string Game::GenerateFrame()
{

    std::string str = "Current Score :" + std::to_string(player.GetScore()) + "\t High Score :" + std::to_string(player.GetHighScore()) + "\n";
    std::string result = "";
    for (auto it = play_area.begin(); it != play_area.end(); it++)
    {
        result += (*it) ? TREE_SYMBOL : BLANK_SYMBOL;
    }

    result[0] = DINO_SYMBOL;
    result = std::string(INITAL_PADDING, BLANK_SYMBOL) + result;
    return str + result;
}

void Game::Start()
{
    console->ClearScreen();
    console->Display("Welcome To DinBIT (press [space] to jump ..)");
    console->WaitForInput();

    for (int i = 1;i < play_area_size; i++)
    {
        if (ShouldPlaceTree(i))
        {
            play_area[i] = true;
            last_tree_position = i;
        }
    }

    console->SetOutput(GenerateFrame());
}

void Game::Loop()
{
    while (1)
    {

        console->ClearScreen();
        console->Display();
        console->Sleep();

        if (!Run())
        {
            if (ShouldRestart())
                Restart();
            else
                break;
        }
    }
}

bool Game::ShouldRestart()
{
    console->ClearScreen();
    console->Display();
    console->WaitForInput();

    console->ClearScreen();
    console->Display("Press [Enter] to restart, 'q' to quit..");

    console->WaitForInput();

    return !(console->IsExitKey());
}

void Game::Restart()
{
    Game::console = ConsoleFactory::GetConsole();
    Game::player = Player();

    Game::jump_flag = false;
    Game::last_frame = Game::play_area_size - 1;
    Game::last_tree_position = 0;
    Game::play_area = std::deque<bool>(Game::play_area_size, false);

    Game::Start();
}

void Game::End()
{
    console->ClearScreen();
    console->Display();

    console->WaitForInput();

    delete console;
}

Logic.cpp

All Game Logic is implemented Here..

#include "Game.h"

bool Game::GameOverConditions()
{
    return (play_area[0] || play_area[1]);
}

bool Game::IsLevelUp()
{
    return !((last_frame + 1) % (LEVEL_UP_FACTOR * play_area_size));
}


void Game::LevelUp()
{
    console->DecrementFameRate();
}

bool Game::Run()
{
    jump_flag = false;

    if(console->KeyPressed() && console->IsJumpKey())
    {
        jump_flag = true;
        player.Jump();
    }


     if (IsLevelUp())
    {
         Game::LevelUp();
    }


    Move();

    std::string str = GenerateFrame();
    if (jump_flag) str += "\n JUMPED !";

    if (GameOverConditions())
    {
        str += "\nGame Over";
        console->SetOutput(str);
        return false;
    }

    console->SetOutput(str);
    return true;
}


bool Game::ShouldPlaceTree(int frame)
{
    if (frame - last_tree_position >= MAX_TREE_DISTANCE)
        return std::uniform_int_distribution<std::mt19937::result_type>{0,1}(eng);

    return false;
}

void Game::Move()
{
    if (!jump_flag)
        player.Run();

    play_area.pop_front();
    last_frame++;

    if (ShouldPlaceTree(last_frame))
    {
        play_area.push_back(true);
        last_tree_position = last_frame;
    }
    else
        play_area.push_back(false);

    if (jump_flag)
    {
        int j = player.GetJumpSpan();

        while (--j)
        {
            play_area.pop_front();
            last_frame++;

            if (ShouldPlaceTree(last_frame))
            {
                play_area.push_back(true);
                last_tree_position = last_frame;
            }
            else
                play_area.push_back(false);
        }
    }
}

main.cpp

#include "Game.h"

int main()
{
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    std::cout.tie(NULL);

    Game::Start();
    Game::Loop();
    Game::End();
}

There is a mini ReadMe https://github.com/ankushTripathi/DinoBIT/blob/master/README.md

\$\endgroup\$
2
  • \$\begingroup\$ I assume you're referring to this game? \$\endgroup\$
    – Mast
    Apr 19 '20 at 11:12
  • \$\begingroup\$ Yes that one..I don't know it's name.. sorry. \$\endgroup\$ Apr 19 '20 at 12:44
2
\$\begingroup\$

Defines as defines

I do not understand why you copy these precompiler macros to class constants:

static const char exit_key = CONSOLE_EXIT_KEY;
static const char jump_key = CONSOLE_JUMP_KEY;

Can't you use the macros directly?

Const methods

Any method that does not modify any member variable in this should be made const; the most obvious example is this:

virtual void Display(const std::string &str = "") const = 0;

Const arguments

It's better to pass const references when possible, i.e.

virtual void SetOutput(const std::string &output) = 0;

Typo

DecrementFameRate is most likely supposed to be DecrementFrameRate.

Parens

return does not need parens in either of these cases:

return (input_value == exit_key);

return (input_value == jump_key);

Combining preprocessor predicates

#ifdef __unix
    return new Console();
#endif // __unix
#ifdef __APPLE__
    return new Console();
#endif // __APPLE__

can be

#if defined(__unix) || defined(__APPLE__)
    return new Console();
#endif

see https://gcc.gnu.org/onlinedocs/cpp/Defined.html for more details.

High scores

I question the placement of this variable:

static int high_score;

If it truly is a high score per player, then it should not be made static. If this is not per-player, perhaps it deserves to live in Game instead.

Object ownership

Your Game::console is passed in from the ConsoleFactory, where you are then responsible for deleting it. The safer way to track this, rather than a bare pointer, is a shared_ptr (what used to be an auto_ptr). Among other things, this will simplify your destructor.

Re-entrance

As it is currently implemented, Game does not deserve to be a class; at most just a collection of methods and variables in a namespace. What if - down the road - you need to refactor this so that you're running a game server that can host multiple games? You should remove static from this, and if there is the current need for this to be a singleton, there are better ways to represent singletons. There are many, many different methods.

For loops

I find this:

    int j = player.GetJumpSpan();

    while (--j)

easier to read as

for (int j = player.GetJumpSpan(); j > 0; j--)
\$\endgroup\$
1
  • \$\begingroup\$ "As it is currently implemented, Game does not deserve to be a class; at most just a collection of methods and variables in a namespace" , thats exactly how i felt but wasnt sure how to handle it.. \$\endgroup\$ Apr 21 '20 at 2:35

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.