For my rags-to-riches submission, I've decided to improve this code:
Text-based RPG game using classes
However, I've decided to start off with something different. The original code is more interactive, but mine just runs automatically. I may consider making my code the same, but I wanted to try this first. I think my chosen approach is less error-prone as I don't have to worry about input validation and such.
I am proud to declare that my code appears more encapsulated than the original code, which was full of getters and setters. Mine does not have any at all, and instead uses inheritance, which was inspired by this answer to the same question. As it's my first real attempt at it, I'd especially like that aspect reviewed. I feel that my attempt isn't quite right, also because the derived classes don't have their own functions.
Other than inheritance, I'm concerned about maintainability. I will expand on this, such as by adding player/monster stats (attack and defense) and different monsters. I just didn't want to include it until I had this cleaned up first, especially if my use of inheritance could be problematic.
I've kept everything as integers to make things easier, and because RPG games already have numbers (such as health and experience) as integers. I'm also not sure if I should worry about making anything unsigned
instead, except for health. I think it's okay for the player or a monster to end up with negative health after damage calculations, as it would still consider the creature to be dead.
I'm sort of playing around with the stats and combat numbers, which is also why the player ends up dying quickly sometimes (but they might still be reasonable). I do also plan on studying them further.
Please feel free to comment on everything. I want this to be cleaned up well at the start so that it's easier to keep it that way as it gets larger.
LifeForm.hpp
#ifndef LIFEFORM_HPP
#define LIFEFORM_HPP
#include <ostream>
#include <random>
namespace
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> attack_dist(20, 50);
}
class LifeForm
{
protected:
int level;
int exp;
int req_EXP;
int max_health;
int health;
LifeForm(int level, int exp, int req_EXP, int max_health)
: level(level)
, exp(exp)
, req_EXP(req_EXP)
, max_health(max_health)
, health(max_health)
{}
void level_up()
{
level++;
exp = 0;
req_EXP += level * 100;
max_health += 50 * level;
health = max_health;
}
public:
bool health_depleted() const { return health <= 0; }
void attack(LifeForm& attacked_lifeform)
{
attacked_lifeform.health -= level * attack_dist(gen);
}
void add_EXP(LifeForm const& life_form)
{
exp += life_form.level * 50;
if (exp >= req_EXP)
{
level_up();
}
}
friend std::ostream& operator<<(std::ostream& out, LifeForm const& lifeform)
{
out << "Level: " << lifeform.level;
out << "\nHP : (" << lifeform.health << '/' << lifeform.max_health << ')';
out << "\nEXP : (" << lifeform.exp << '/' << lifeform.req_EXP << ')';
return out;
}
};
class Player : public LifeForm
{
public:
Player() : LifeForm(1, 0, 100, 200) {}
};
class Monster : public LifeForm
{
public:
Monster() : LifeForm(1, 50, 0, 100) {}
};
#endif
Game.hpp
#ifndef GAME_HPP
#define GAME_HPP
#include "LifeForm.hpp"
#include <vector>
class Game
{
private:
Player player;
std::vector<Monster> monsters;
void battle();
Monster current_monster() { return monsters.back(); }
bool all_monsters_dead() const { return monsters.empty(); }
public:
Game();
void play();
};
#endif
Game.cpp
#include "Game.hpp"
#include <iostream>
Game::Game() : monsters(50) {}
void Game::play()
{
std::cout << "Number of monsters: " << monsters.size() << "\n\n";
while (!player.health_depleted() && !monsters.empty())
{
std::cout << "\nPlayer\n" << player;
battle();
if (all_monsters_dead())
{
std::cout << "\n\n> All monsters killed!!!";
}
if (player.health_depleted())
{
std::cout << "> Player dead...\n\n";
}
}
}
void Game::battle()
{
std::cout << "\n\n> Player attacks!\n";
player.attack(current_monster());
if (current_monster().health_depleted())
{
std::cout << "> Monster killed!\n";
player.add_EXP(current_monster());
monsters.pop_back();
}
std::cout << "\nMonster " << monsters.size() << "\n" << current_monster();
std::cout << "\n\n> Monster attacks!\n";
current_monster().attack(player);
}
Main.cpp
#include "Game.hpp"
int main()
{
Game game;
game.play();
}