3
\$\begingroup\$

It's been a month or so since I started learning C++ by myself online on youtube.

This is my first ever "game". I call it "game", because you don't do anything other than choose which attack you do, and there are just three enemies. You fight first a goblin, then a crocodile, then the boss. If you beat them, you win!

This is the latest version I made. In my first version, I made a class for each character, and I used rand() %, and other things that I found out later to be bad.

I'd like to know how my code is, if there are any bad habits forming, or any error.

#include <iostream>
#include <random>
#include <string>

class Random {
private:
  std::mt19937 gen;

public:
  Random(unsigned int seed);
  int number(int min, int max);
};

class Model {
private:
  std::string name;
  int health;
  int attack;
  int spattack1;
  int spattack2;

public:
  Model(std::string a, int b, int c, int d, int e);
  friend void fight(int advType, int &spaCounter, Model &player, Model &enemy,
                    Random &rnd);
  friend int getDamage(Random &rnd, Model &a, int advType, int &spaCounter);
  friend bool isDead(Model &adv);
};

void fight(int advType, int &spaCounter, Model &player, Model &enemy,
           Random &rnd);
int getDamage(Random &rnd, Model &a, int advType, int &spaCounter);
bool isDead(Model &a);
//--------------------------------------------------//
int main() {
  std::random_device seed;
  Random rnd(seed());

  Model adv("Daniele", 30, 3, 7, 0);
  Model goblin("Goblin", 4, 2, 0, 0);
  Model crocodile("Crocodile", 9, 4, 7, 0);
  Model boss("BigBoss", 25, 8, 11, 15);

  int advType;
  int spaCounter = 5;
  std::cout << "THE FIGHT BEAGIN!" << '\n';
  while (!isDead(adv)) {
    do {
      std::cout << '\n'
                << "1 Normal Attack - 2 Special Attack(" << spaCounter
                << ") : ";
      std::cin >> advType;
    } while (advType < 1 || advType > 2);

    if (isDead(boss)) {
      std::cout << "You Win!" << '\n';
      return 0;
    } else if (isDead(crocodile))
      fight(advType, spaCounter, adv, boss, rnd);
    else if (isDead(goblin))
      fight(advType, spaCounter, adv, crocodile, rnd);
    else
      fight(advType, spaCounter, adv, goblin, rnd);
  }

  std::cout << "You are DEAD!" << '\n';
  return 0;
}
//--------------------------------------------------//
Random::Random(unsigned int seed) : gen(seed) {}

int Random::number(int min, int max) {
  std::uniform_int_distribution<int> dist(min, max);
  return (dist(gen));
}

Model::Model(std::string a, int b, int c, int d, int e)
    : name(a), health(b), attack(c), spattack1(d), spattack2(e) {}

void fight(int advType, int &spaCounter, Model &player, Model &enemy,
           Random &rnd) {
  // player attack
  int damage = getDamage(rnd, player, advType, spaCounter);
  enemy.health -= damage;
  std::cout << player.name << " deals : " << damage << " to " << enemy.name
            << " -- health left : " << enemy.health << '\n';

  if (isDead(enemy))
    return;

  // enemy attack
  damage = getDamage(rnd, enemy, 0, spaCounter);
  player.health -= damage;
  std::cout << enemy.name << " deals : " << damage << " to " << player.name
            << " -- health left : " << player.health << '\n';
}

int getDamage(Random &rnd, Model &a, int advType, int &spaCounter) {
  // crit
  int crit = 1;
  if (rnd.number(0, 99) == 99)
    crit = 2;

  // adv damage
  if (advType == 1 && spaCounter == 0)
    return (a.attack * crit);
  else if (advType == 2 && spaCounter > 0) {
    spaCounter--;
    return (a.spattack1 * crit);
  }

  // enemy damage
  int enemyType = rnd.number(0, 99);
  if (enemyType > 94 && enemyType <= 99 && a.name == "BigBoss")
    return (a.spattack2 * crit);
  else if (enemyType > 79 && enemyType <= 99 &&
           (a.name == "Crocodile" || a.name == "BigBoss"))
    return (a.spattack1 * crit);
  else
    return (a.attack * crit);
}

bool isDead(Model &a) { return (a.health <= 0 ? true : false); }
\$\endgroup\$
1
\$\begingroup\$

I have looked over code and here are some notes

Item 1. Member vars - use some prefix of postfix to uniquely distinguish them

for example

std::string m_name;

or

int health_;

Use same coding conventions for all member vars for all your classes

Item 2. Function parameters - give meaningful names. It is especially important when arguments have same type. a, b, c, d are horrible.

Item 3. Instead of friend functions use member functions

Item 4. Use const specifier as much as possible. For example isDead function will not modify the object - declare it as

bool isDead() const; //member function

Item 5. Declare variables in the smallest scope possible as close to the usage as possible For example advType variable should be declared inside while loop

while (!isDead(adv)) {
    int advType;
    do {
        std::cout << '\n'
                  << "1 Normal Attack - 2 Special Attack(" << spaCounter
                  << ") : ";
        std::cin >> advType;
    } while (advType < 1 || advType > 2);

Item 6. your fight function does 2 similar things enemy attack and player attack. So it looks like you have to write function attacked which will calculate the health of the object as a result of the attack from other object

fight function will look like (it should not be friend - since it will use only public members of the Model)

 void fight(int advType, int &spaCounter, Model &player, Model &enemy,
        Random &rnd) {
    // player attack
    enemy.attacked(rnd, player, advType, spaCounter);

    if (enemy.isDead())
        return;

    // enemy attack
    player.attacked(rnd, enemy, 0, spaCounter);
}
| improve this answer | |
\$\endgroup\$
  • 3
    \$\begingroup\$ 1. Decorating variables is a controversial choice. Most times, it just adds clutter. 2. Naming is for readability. Use the least you need to convey any relevant info. Yes, that specific example is bad. 3. The relevant gotw, monoliths unstrung. Take heed that it is only prefer, not use. \$\endgroup\$ – Deduplicator Sep 9 '17 at 12:46
  • \$\begingroup\$ About point 2: if i decide to keep the member vars names unchanged, is this bad:Model::Model(std::string name, int health, int attack, int spAttack1, int spAttack2) : name(name), health(health) ...? \$\endgroup\$ – DiDi Sep 10 '17 at 13:16
  • \$\begingroup\$ Compiler will understand it. I as programmer prefer to see slightly different names for parameters and members \$\endgroup\$ – Artemy Vysotsky Sep 10 '17 at 14:23

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.