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