4
\$\begingroup\$

This is my first attempt at a big project in C++ (made in Visual Studios). It is a culmination of concepts I have been learning, like classes, inheritance, and the STL (only vectors).

The basic idea of the game is that the player fights randomly generated monsters (16 in total). They level up and get gold, spend that gold at a shop on upgrades and items, and win when they become level 20.

I do have to give credit for the site learncpp, as this project was expanded from one of their quiz questions (challenge 3). I added in more monsters, a shop, items, and an inventory system though.

Any feedback (particularly on my Inventory class implementation and use of vectors) on how I can improve the program is appreciated as I want to get better at designing and making projects like these. I also want to stop any bad coding practices (that I am not aware of), and I strive to code like a professional one day.

Fighting Simulation.cpp (the main file)

// Fighting Simulation.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "Player.h"
#include "FightLogic.h"
#include "Shop.h"
#include "Utility.h"
#include <iostream>
#include <chrono>
#include <thread>
#include <cstdlib>
#include <ctime>


int main()
{
    //so introduction isn't printed out when player starts over

    bool firstTime = true; 

    while (1)
    {
        srand(static_cast<unsigned int>(time(0)));
        //set initial seed value to system clock

        rand();
        //get rid of first result

        std::cout << "Enter your name (no spaces): ";
        std::string playerName;
        std::cin >> playerName;
        Player p(playerName);

        if (firstTime)
        {
            std::cout << std::endl << "Welcome, " << p.getName() << '.' << std::endl;

            std::this_thread::sleep_for(std::chrono::milliseconds(1500));

            std::cout << std::endl << "The goal of this game is to reach level 20. To level up, fight monsters." << std::endl;

            std::this_thread::sleep_for(std::chrono::milliseconds(1500));

            std::cout << std::endl << "You have an inventory system." << std::endl;

            std::this_thread::sleep_for(std::chrono::milliseconds(1500));
        }

        while (!p.isDead() && !p.hasWon())
        {
            fightMonster(p);

            if (p.hasStatus())
            {
                p.resetStatus();
            }

            //Player must progress 5 levels before entering
            if (p.getLevel() > 0 && p.getLevel() % 5 == 0)
            {
                enterShop(p);
            }

            //every two levels player can access their inventory after entering the shop
            else if (p.getLevel() > 5 && p.getLevel() % 2 == 0)
            {
                printActionScreen(p);
            }

        }

        if (p.isDead())
        {
            std::cout << std::endl << "You died at level " << p.getLevel() << " and with " <<
                p.getGold() << " gold." << std::endl;
            std::cout << "You were " << 20 - p.getLevel() << " levels away from winning." << std::endl;
            std::cout << "Game over!" << std::endl;
        }

        else
        {
            std::cout << std::endl << "You won the game with " << p.getGold() << " gold!" << std::endl;
            std::cout << "Live to fight another day!" << std::endl;
        }

        std::cout << std::endl << "Play again? (Y/N):";
        char choice;
        std::cin >> choice;
        std::cout << std::endl;

        if (isChoice(choice, 'Y'))
        {
            firstTime = false;
            continue;
        }

        else if (isChoice(choice, 'N'))
        {
            break;
        }

        else
        {
            firstTime = false;
            continue;
        }
    }
    return 0;
}

Utility.h

#pragma once
#include "stdafx.h"
#include <vector>

bool isChoice(const char input, const char uppercaseChoice);

bool doProbability(int percent);

int getRandomNumber(int min, int max);

Utility.cpp

#include "stdafx.h"
#include "Utility.h"
#include <ctype.h>

//Generate a random # between min and max (inclusive)
// Assumes srand() has already been called
int getRandomNumber(int min, int max)
{
    //static to ensure value calculated only once
    static const double fraction = 1.0 / (static_cast<double> (RAND_MAX) + 1.0);

    //evenly distributes the random number across the range
    return static_cast<int>(rand() * fraction * (max - min + 1) + min);
}

//returns true if the uppercase or lowercase choice is inputted.
bool isChoice(const char input, const char uppercaseChoice)
{
    return (input == uppercaseChoice || input == static_cast<char>(tolower(uppercaseChoice)));
}

//randomly outputs a success or failure based on
//percentage given
//works only with integers (including 0)
bool doProbability(int percent)
{
    return (getRandomNumber(1, 100) <= percent);
}

Creature.h

#pragma once
#include "stdafx.h"
#include "EntityType.h"
#include <string>

class Item;

enum Attributes
{
    DAMAGE,
    HEALTH,
    MAX_HEALTH,
    MAX_ATTRIBUTES
};


class Creature
{
public:

    Creature
    (
        const std::string &name,
        char symbol,
        int health,
        int maxHealth,
        int damage,
        int gold
    );

    const std::string &getName() const;
    char getSymbol() const;

    int getDamage() const;
    void addDamage(int damage);

    int getHealth() const;
    void addHealth(int health);
    void restoreFullHealth();

    int getMaxHealth();
    int addMaxHealth(int maxHealth);

    int getGold() const;

    void reduceHealth(int health);
    bool isDead() const;

protected:
    std::string m_name;
    char m_symbol;
    int m_health;
    //m_maxHealth tracks the maximum (not current) health of the creature.
    //that is m_Health's job
    int m_maxHealth;
    int m_damage;
    int m_gold;
};

Creature.cpp

#include "stdafx.h"
#include "Creature.h"
#include "Item.h"
#include <iostream>

Creature::Creature
(
    const std::string &name,
    char symbol,
    int health,
    int maxHealth,
    int damage,
    int gold
)
    :
    m_name(name),
    m_symbol(symbol),
    m_health(health),
    m_maxHealth(maxHealth),
    m_damage(damage),
    m_gold(gold)
{
}

const std::string & Creature::getName() const { return m_name; }

char Creature::getSymbol() const { return m_symbol; }

int Creature::getDamage() const { return m_damage; }
void Creature::addDamage(int damage) { m_damage += damage; }

int Creature::getHealth() const { return m_health; }
void Creature::addHealth(int health) { m_health += health; }
void Creature::restoreFullHealth() { m_health = m_maxHealth; }

int Creature::getMaxHealth() { return m_maxHealth; }
int Creature::addMaxHealth(int maxHealth) { return m_maxHealth += maxHealth; }

int Creature::getGold() const { return m_gold; }

void Creature::reduceHealth(int health) { m_health -= health; }
bool Creature::isDead() const { return (m_health <= 0); }

Monster.h

#pragma once
#include "Creature.h"
#include "DialogueType.h"

class Player;

class Monster : public Creature
{
public:

    struct MonsterData
    {
        //no string needed because it's constant data
        const char * name;
        const char symbol;
        int health;
        int maxHealth;
        int damage;
        int gold;
    };

    static const MonsterData monsterData[MAX_MONSTER_TYPES];

    Monster(MonsterType type);

    MonsterType getMonsterType() const;

    //random monster generated based on player level
    static Monster getRandomMonster(const Player &p);

    //takes parameter of INTRO, HIT, or DEFEAT for creature
    void printDialogue(CombatDialogue dialogueType) const;

private:
    MonsterType m_monsterType;
};

Monster.cpp

#include "stdafx.h"
#include "Monster.h"
#include "Player.h"
#include "CombatDialogue.h"
#include "Utility.h"
#include <cstdlib>
#include <iostream>

//refers to combat dialogue of monster between it and player
void Monster::printDialogue(CombatDialogue dialogueType) const
{
    std::cout << std::endl << monsterPlayerInteraction[MONSTER][getMonsterType()][dialogueType] << std::endl;
}

//Look-up table for predefined values
//Initalization of an array of monsters
//monsters are listed from easiest to hardest
const Monster::MonsterData Monster::monsterData[MAX_MONSTER_TYPES]
{
    { "slime", 's', 1, 1, 1, 15 },
    { "imp", 'i', 2, 2, 2, 15 },
    { "leprechaun", 'l', 4, 4, 2, 45},
    { "golem", 'm', 4, 4, 2, 20 },
    { "ghost", 'G', 14, 14, 1, 25 },
    { "demon", 'd', 14, 14, 3, 25 },
    { "goblin", 'g', 10, 10, 2, 35 },
    { "pixie", 'p', 10, 10, 2, 25 },
    { "mutant", 'M', 16, 16, 2, 30 },
    {"vampire", 'v', 16, 16, 3, 30},
    {"werewolf", 'w', 16, 16, 4, 35},
    {"zombie", 'z', 22, 22, 1, 35},
    {"cyclops", 'c', 24, 24, 3, 40 },
    {"sasquatch", 'S', 24, 24, 3, 40},
    {"phoenix", 'P', 30, 30, 4, 100},
    { "dragon", 'D', 30, 30, 4, 100 }
};

Monster::Monster(MonsterType type)
    :
    Creature
    (
        monsterData[type].name,
        monsterData[type].symbol,
        monsterData[type].health,
        monsterData[type].maxHealth,
        monsterData[type].damage,
        monsterData[type].gold
    ),
    m_monsterType(type)
{
}

MonsterType Monster::getMonsterType() const { return m_monsterType; }

Monster Monster::getRandomMonster(const Player &p)
{
    int num; 
    //monsters scale accordingly with the player
    /*Level 1-5
        Slime
        Imp
        Leprechaun (extra gold)
        Golem
    */
    if (p.getLevel() >= 0 && p.getLevel() <= 5)
    {
        num = getRandomNumber(SLIME, GOLEM);
    }
    /*Level 6-10
        Ghost
        Demon
        Goblin
        Pixie (extra gold)
    */
    else if (p.getLevel() >= 6 && p.getLevel() <= 10)
    {
        num = getRandomNumber(GHOST, PIXIE);
    }

    /*Level 11-19
        Mutant
        Vampire
        Werewolf
        Zombie
        Cyclops
        Sasquatch
    */

    else if (p.getLevel() >= 11 && p.getLevel() <= 18)
    {
        num = getRandomNumber(MUTANT, SASQUATCH);
    }

    /*Final boss
        Pheonix
        Dragon
    */
    else if (p.getLevel() == 19)
    {
        num = getRandomNumber(PHOENIX, MAX_MONSTER_TYPES-1);
    }

    getRandomNumber(0, MAX_MONSTER_TYPES - 1);
    return Monster(static_cast<MonsterType>(num));
}

Player.h

#pragma once

#include "stdafx.h"
#include "Creature.h"
#include "Monster.h"
#include "Inventory.h"
#include "DialogueType.h"


class Player : public Creature
{
public:
    Player(const std::string &name);

    void levelUp();
    int getLevel() const;

    bool hasFullHealth();

    void addGold(int gold);
    void reduceGold(int gold);
    bool hasGold() const;

    void addEscChance(int escapeChance);
    int getEscChance() const;

    bool hasWon();

    Inventory inventory;

    //player is taking to monster, so it needs to know correct monster type
    void printDialogue(const Monster &m, CombatDialogue dialogueType) const;

    void printInvent();

    void applyEffect(const Item &item);

    bool hasStatus();
    void resetStatus();

private:
    int m_originalEscChance;
    int m_escChance;
    int m_level;
};

Player.cpp

#include "stdafx.h"
#include "Player.h"
#include "Item.h"
#include "CombatDialogue.h"
#include <algorithm>
#include <cassert>
#include <iostream>

Player::Player(const std::string &name)
    :
    Creature(name, '@', 10, 10, 1, 0),
    m_level(0),
    m_escChance(33),
    inventory(0)
{
    m_originalEscChance = m_escChance;
}

void Player::levelUp()
{
    ++m_level;
    if (m_level % 2 == 0)
    {
        ++m_damage;
    }
    ++m_maxHealth;
}

int Player::getLevel() const { return m_level; }

bool Player::hasFullHealth() { return (m_health == m_maxHealth); }

void Player::addGold(int gold) { m_gold += gold; }
void Player::reduceGold(int gold) { m_gold -= gold; }
bool Player::hasGold() const { return  (m_gold > 0); }

void Player::addEscChance(int escapeChance) { m_escChance += escapeChance; }
int Player::getEscChance() const { return m_escChance; }

bool Player::hasWon() { return (m_level >= 20); }

void Player::printInvent()
{
    for (auto it = inventory.vec.begin(); it != inventory.vec.end(); ++it)
    {
        int index = std::distance(inventory.vec.begin(), it);
        if (inventory.getItem(index).isCollectable())
        {
            std::cout << std::endl << inventory.getItem(index).getName() << ": " <<
                inventory.getItem(index).getAmount() << std::endl;
        }
    }
}

//refers to certain combat dialogue from player when it encounters a certain monster
void Player::printDialogue(const Monster &m, CombatDialogue dialogueType) const
{
    std::cout << std::endl << monsterPlayerInteraction[PLAYER][m.getMonsterType()][dialogueType] << std::endl;

}

void Player::applyEffect(const Item &item)
{
     if (item.getEffect() == Item::ADD_DAMAGE)
    {
        addDamage(3);
        return;
    }

    else if (item.getEffect() == Item::ADD_MAX_HEALTH)
    {
        addMaxHealth(3);
        return;
    }

    else if (item.getEffect() == Item::RESTORE_HEALTH)
    {
        //if current health is >= 75% of max health
        //then restore the rest (25%)
        if (hasFullHealth())
        {
            std::cout << std::endl << "Health is already full." << std::endl;
            return;
        }

        else if (static_cast<float>(m_health)/m_maxHealth > 0.75)
        {
            restoreFullHealth();
            std::cout << std::endl << "Health maxed out!" << std::endl;
            return;
        }

        else if (static_cast<float>(m_health) / m_maxHealth < 0.75)
        {
            int lostHealth = m_maxHealth * 0.25;
            addHealth(lostHealth);
            std::cout << std::endl << "Health increased by " << lostHealth << " !" << std::endl;
        }
    }

    else if (item.getEffect() == Item::RESTORE_FULL_HEALTH)
    {
        if (m_health <= static_cast<int>(0.25*m_maxHealth))
        {
            std::cout << std::endl << "Health fully restored!" << std::endl;
            restoreFullHealth();
            return;
        }

        else
        {
            std::cout << std::endl << "Nothing happens..." << std::endl;
            return;
        }
    }

    else if (item.getEffect() == Item::REPEL)
    {
        //to prevent item stacking
        if (m_escChance >= m_originalEscChance + 33)
        {
            std::cout << std::endl << "This item is not stackable" << std::endl;
            return;
        }

        std::cout << std::endl << "You're more likely to escape!" << std::endl;
            addEscChance(33);
    }
}

bool Player::hasStatus()
{
    //when any status is not equal to its original
    return (m_escChance != m_originalEscChance);
}

void Player::resetStatus()
{
    m_escChance = m_originalEscChance;
}

EntityType.h

enum CreatureType
{
    MONSTER,
    PLAYER,
    MAX_CREATURE_TYPES
};

enum MonsterType
{
    SLIME, 
    IMP,
    LEPRECHAUN,
    GOLEM,
    GHOST,
    DEMON,
    GOBLIN,
    PIXIE,
    MUTANT,
    VAMPIRE,
    WEREWOLF,
    ZOMBIE,
    CYCLOPS,
    SASQUATCH,
    PHOENIX,
    DRAGON,
    MAX_MONSTER_TYPES
};

DialogueType.h

enum CombatDialogue
{
    INTRO,
    HIT,
    DEATH,
    MAX_DIALOGUE_TYPES
};

Combat Dialogue.h (dialogue data in a 3D array)

#pragma once
#include "EntityType.h"
#include "DialogueType.h"

static const char * monsterPlayerInteraction[MAX_CREATURE_TYPES][MAX_MONSTER_TYPES][MAX_DIALOGUE_TYPES]
{
    {
    //Monsters:

    //Slime
        {   "A slime jiggles around.",
            "The slime smacks you as hard as it can.",
            "The slime squishes apart."
        },

    //Imp
        {   "A mischievous imp is caught red-handed.",
            "The imp tries to outmaneuver you, getting hits where it can.",
            "The imp is outmatched by your strength."
        },

    //Leprechaun
        {
            "A leprechaun comes out with a fierce fighting stance. It won't give up its gold easily.",
            "The leprechaun brawls like a true irishman.",
            "The leprechaun runs away, cursing you out for taking his gold."
        },

    //Golem
        {   "A golem stands strong, like a boulder." ,
            "The golem swings his fist at you.",
            "The golem tumbles and breaks into pieces."
        },

    //Ghost
        {
            "A ghost stares beyond into space.",
            "The ghost scares you, and you take some damage from stress.",
            "The ghost disappears before your eyes."
        },

    //Demon
        {
            "A red-horned demon appears with a smirk.",
            "The demon summons a tridet and throws it at you.",
            "The demon slowly sinks back to the pit from where it came."
        },

    //Goblin
        {
            "A goblin tells you to give him your money.",
            "The goblin wildly swings his pocket knife at you.",
            "The goblin retreats quickly out of sight. "
        },

    //Pixie
        {
            "A rabid pixie flies towards you.",
            "The pixie picks you up with its magic dust and drops you.",
            "The pixie weakly flutters to the ground."
        },

     //Mutant
        {
            "A menacing mutant stares intently at you.",
            "The mutant smacks you, pushing you with all his weight.",
            "The mutant disintegrates into a glowing green puddle."
        },

    //Vampire
        {
            "A sly vampire appears out of thin air.",
            "The vampire pierces your skin with its fangs.",
            "The vampire parishes as its heart has been pierced."
        },

    //Werewolf
        {
            "A werewolf stands in front of you, gnarling its teeth.",
            "The hairy beast thrashes you with its bone claws.",
            "The werewolf is knocked out, transforming into a human."
        },

    //Zombie
        {
            "A discolored, bloodied zombie straggles to you.",
            "The zombie bites your flesh thrashes his arms at you. ",
            "The zombie stops moving when as it get beheaded."
        },

    //Cyclops
        {
            "A cyclops give you the dirty eye.",
            "The cyclops swings his 10 foot club at you.",
            "The cyclops loses its sight and dies as a sword pierces its eye."
        },

    //Sasquatch
        {
            "A 10 foot ape with enormous feet appears before you.",
            "The sasquatch has incredible strength and speed in hand to hand combat.",
            "The sasquatch admits defeat as he is pinned and lets you go."
        },

    //Phoenix
        {
            "A firey phoenix appears before you.",
            "The phoenix scorches you with its flaming wings.",
            "The phoenix falls to the ground as it takes too much physical damage."
        },

    //Dragon
        {   "A dragon flies in with a gust of wind.",
            "The dragon spits fire in your direction and claws at you.",
            "The dragon collapses to the floor as you lay a fatal blow."
        }
    },

    {
    //Player reaction to:

    //Slime
        {
            "You hear a squishing noise.",
            "You continually jab at the slime's insides.",
            "You managed to be defeated by the slime."
        },

    //Imp
        {
            "You feel something coming out of your pocket...",
            "You land swift strikes against the imp.",
            "As you lay on the ground, you see the imp scavenging your things."
        },

    //Leprechaun
        {
            "You see a rainbow and a green figure approaching you.",
            "You commit to hand-to-hand combat with the leprechaun.",
            "The leprechaun does an irish jig around you as you lay on the ground."
        },

    //Golem
        {
            "You hear a clattering of stones. Something is stomping your way.",
            "You clash with the golem's hard exterior. You wear it down.",
            "K.O.! The heavy-handed golem defeats you with his strength."
        },

    //Ghost
        {
            "You feel something moving through you.",
            "You chant, telling the ghost to move on from this life.",
            "You fell to the same fate as the ghost. "
        },

    //Demon
        {
            "You feel a sinister force nearby.",
            "You scar the demon's body with a quick stab.",
            "You go down with the demon into the fiery pit from where it came."
        },

    //Goblin
        {
            "You see a green big-nosed figure hiding in a bush.",
            "You take advantage of your size and pummel the goblin.",
            "The stab wounds takes its toll, and you lay incapacitated."
        },

    //Pixie
        {
            "You feel some sort of your dust falling on your face.",
            "You cripple the pixie's wing a little bit.",
            "The impact of the ground kills you."
        },

    //Mutant
        {
            "You see a bright green glow nearby.",
            "You slice off the mutant's limb, as they grow back.",
            "The mutant's relentless attacks kills you."
        },

    //Vampire
        {
            "You see a dead man laying on the floor with his blood drained.",
            "You aim for the vampire's heart and continually jab at it.",
            "Your blood gets drained and you die as the vampire overwhelms you."
        },

    //Werewolf
        {
            "You notice the moonlight shining and hear a loud howl.",
            "You wrestle with the grizzly beast.",
            "The werewolf sprints away as you bleed to death."
        },

    //Zombie
        {
            "You hear moaning and smell rotting flesh.",
            "You try to mutilate the zombie, aiming for its head.",
            "The zombie looks over your tasty carcass."
        },

    //Cyclops
        {
            "You feel the earth quake as something stomps towards you.",
            "You aim at the cyclops feet, hoping to get him to the ground.",
            "You become a splat on the ground as the cyclops crushes you."
        },

    //Sasquatch
        {
            "Out of the corner of your eye, you see a tall figure in the woods.",
            "You try to grapple the sasquatch, and pin him.",
            "You are outmatched, and you are too exhausted to continue your journey."
        },

    //Phoenix
        {
            "You feel an immense heat and see something shining bright.",
            "You try to stab at the center of the phoenix, its only physical body part.",
            "You catch on fire, and are consumed by the phoenix's flames."
        },

    //Dragon
        {
            "You hear a thunderous roar and a powerful beat of wings.",
            "You jab at the dragon, trying to pierce its scales.",
            "You are consumed by the dragon's flames."
        }
    }
};

FightLogic.h

#pragma once

class Player;

class Monster;

void printActionScreen(Player &p);

void playerAttacks(Player &p, Monster &m);

void monsterAttacks(Player &p, const Monster &m);

void fightMonster(Player &p);

FightLogic.cpp

#include "stdafx.h"
#include "FightLogic.h"
#include "Player.h"
#include "Monster.h"
#include "Item.h"
#include "DialogueType.h"
#include "Utility.h"
#include <chrono>
#include <thread>
#include <algorithm>
#include <iostream>

void printActionScreen(Player &p)
{
    //if the player inventory is empty or the player is dead, then don't show this
    if (p.inventory.vec.empty() || p.isDead())
    {
        return;
    }

        //while inventory still has usable items
        while (!p.inventory.vec.empty() && std::all_of(p.inventory.vec.begin(), p.inventory.vec.end(), [](Item i1) {return (i1.isCollectable()); }))
        {
            std::cout << std::endl << "Do you want to use an item from your inventory? (Y/N)" << std::endl;
            char choice;
            std::cin >> choice;

            if (isChoice(choice, 'Y'))
            {
                p.printInvent();
                std::cout << std::endl << "Get an item by entering its first letter:";
                char symbol;
                std::cin >> symbol;
                p.inventory.findItem(symbol);

                if (p.inventory.hasFoundItem())
                {
                    p.applyEffect(p.inventory.getReqItem());

                    //reduce item amount by 1 (item amounts are handled appropriately)
                    p.inventory.removeItem(p.inventory.getReqItem());
                }
                else
                {
                    continue;
                }
            }

            else if (isChoice(choice, 'N'))
            {
                return;
            }

            else
            {
                continue;
            }
    }
    std::cout << std::endl << "You have run out of usable items." << std::endl;
}

void playerAttacks(Player &p, Monster &m)
{
    if (p.isDead())
    {
        p.printDialogue(m, DEATH);

        std::this_thread::sleep_for(std::chrono::milliseconds(400));

        return;
    }

    p.printDialogue(m, HIT);

    std::this_thread::sleep_for(std::chrono::milliseconds(400));

    m.reduceHealth(p.getDamage());

    if (!m.isDead())
    {
        std::cout << std::endl << "The " << m.getName() << "'s health: " <<
            m.getHealth() << std::endl;

        std::this_thread::sleep_for(std::chrono::milliseconds(400));
    }

    if (m.isDead())
    {
        m.printDialogue(DEATH);

        std::this_thread::sleep_for(std::chrono::milliseconds(400));

        p.levelUp();
        std::cout << std::endl << "You are now level " << p.getLevel() << "." << std::endl;
        std::cout << "You found " << m.getGold() << " gold." << std::endl;
        p.addGold(m.getGold());

        std::this_thread::sleep_for(std::chrono::milliseconds(400));
    }
}

void monsterAttacks(Player &p, const Monster &m)
{
    if (m.isDead())
    {
        return;
    }

    m.printDialogue(HIT);

    std::this_thread::sleep_for(std::chrono::milliseconds(400));

    p.reduceHealth(m.getDamage());

}

void fightMonster(Player &p) 
{

    Monster m = Monster::getRandomMonster(p);

    p.printDialogue(m, INTRO);

    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    m.printDialogue(INTRO);

    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    while (!m.isDead() && !p.isDead())
    {
        std::cout << std::endl << "Your health: " << p.getHealth() << std::endl;
        std::cout << "Your gold: " << p.getGold() << std::endl;
        std::cout << "(R)un or (F)ight: ";

        char input;
        std::cin >> input;
        if (isChoice(input, 'R'))
        {
            if (doProbability(p.getEscChance()))
            {
                std::cout << std::endl << "You successfully flee the battle." << std::endl;
                return;
            }

            else
            {
                if (!p.isDead())
                {
                    std::cout << std::endl << "You failed to flee. Time for another round." << std::endl;
                }

                monsterAttacks(p, m);
                continue;
            }
        }

        if (isChoice(input, 'F'))
        {
            playerAttacks(p, m);
            monsterAttacks(p, m);
        }

    }

}

Shop.h

 #pragma once
    #include "stdafx.h"
    #include "Inventory.h"
    #include "Item.h"
    #include <vector>

    class Player;

    class Shop
    {
    public:
        Shop();

        void printInventory();

        void setUpInventory();

        void haveTransaction(Item &item, Player &p);

        bool hasConfirmedPurchase() const;

        int getTotalCost() const;


        Inventory inventory;
    private:
        int totalCost = 0;
        bool confirmedPurchase;
    };

    void enterShop(Player &p);

Shop.cpp

#include "stdafx.h"
#include "Shop.h"
#include "Player.h"
#include "Utility.h"
#include <algorithm>
#include <chrono>
#include <thread>
#include <iostream>

Shop::Shop() :
    inventory(Item::MAX_ITEMS)
{
}

//prints inventory of the shop
//format = item name: description -- cost
void Shop::printInventory()
{
    std::cout << std::endl;

    for (auto it = inventory.vec.begin(); it != inventory.vec.end(); ++it)
    {
        int index = std::distance(inventory.vec.begin(), it);
        std::cout << inventory.getItem(index).getName() << " " << 
            inventory.getItem(index).getDescript() << " -- "
            << inventory.getItem(index).getCost() << " gold" << std::endl;
    }
}

void Shop::setUpInventory()
{
    inventory.vec =
    {
        Item('W', "Weapon Upgrade", 50, false, Item::ADD_DAMAGE, "(+3 damage)"),
        Item('A', "Armor Upgrade", 40, false, Item::ADD_MAX_HEALTH, "(+3 max health)"),
        Item('P', "Potion", 50, true, Item::RESTORE_HEALTH, "(+25% health)"),
        Item('U', "Ultimate Elixir", 120, true, Item::RESTORE_FULL_HEALTH, "(max health if <= 25% health)"),
        Item('R', "Repellent", 30, true, Item::REPEL, "(+33% flee chance)")
    };
}

void Shop :: haveTransaction(Item &item, Player &p)
{
    if (p.getGold() < item.getCost())
    {
        std::cout << std::endl << "You cannot afford this upgrade." << std::endl;
        confirmedPurchase = false;
        return;
    }

    p.reduceGold(item.getCost());
    totalCost += item.getCost();

    confirmedPurchase = true;
}

bool Shop::hasConfirmedPurchase() const { return (confirmedPurchase); }

int Shop::getTotalCost() const { return totalCost; }

void enterShop(Player &p)
{
    std::cout << std::endl << "Shop Keeper available! Open every 5 level ups." << std::endl;

    if (!p.hasGold())
    {
        std::cout << std::endl << "Come back next time when you have money!" << std::endl;
        return;
    }
        std::cout << std::endl << "(L)eave whe(N)ever you want." << std::endl;

        std::cout << std::endl << "(E)nter the shop?:";
        char entry;
        std::cin >> entry;


        if (isChoice(entry, 'E'))
        {
            Shop shop;

            shop.setUpInventory();

            //current health not being taken acount for
            int prevAttribute[MAX_ATTRIBUTES]{ p.getDamage(), p.getHealth(), p.getMaxHealth() };

            auto cheapest_it = std::min_element
            (
                shop.inventory.vec.begin(), shop.inventory.vec.end(), 
            [](Item element1, Item element2)
            {
                return (element1.getCost() < element2.getCost());
            }
            );


            int cheapest_in = std::distance(shop.inventory.vec.begin(), cheapest_it);


            while (p.getGold() >= shop.inventory.vec[cheapest_in].getCost())
            {
                std::cout << std::endl << "You have " << p.getGold() << " gold." << std::endl;

                std::cout << std::endl << "Upgrades available: " << std::endl;

                shop.printInventory();

                std::cout << std::endl << "Choice (first letter for each):";

                char symbol;
                std::cin >> symbol;

                if (isChoice(symbol, 'L') || isChoice(symbol, 'N'))
                {
                    break;
                }

                shop.inventory.findItem(symbol);

                if (shop.inventory.hasFoundItem())
                {
                    shop.haveTransaction(shop.inventory.getReqItem(), p);

                    //if the requested item from the shop inventory is just an upgrade
                    // then apply effect immediately

                    if (shop.hasConfirmedPurchase() && !shop.inventory.getReqItem().isCollectable())
                    {
                        p.applyEffect(shop.inventory.getReqItem());
                    }

                    //if the requested item from the shop inventory is just a collectable
                    //then just add the item to the player's inventory
                    //purchase counter excludes upgrades (as inventory prints collectables based on item amounts)

                    else if (shop.hasConfirmedPurchase() && shop.inventory.getReqItem().isCollectable())
                    {
                        p.inventory.addItem(shop.inventory.getReqItem());
                    }

                    //Important note: amount increment to item is done after item
                    //is added to player inventory. otherwise the item would get passed and 
                    //and already have 1 more in amount than usual in player inventory.
                    if (shop.inventory.getReqItem().isCollectable())
                    {
                        shop.inventory.getReqItem().addAmount(1);
                    }

                }

                else if (isChoice(symbol, 'L') || isChoice(symbol, 'N'))
                {
                    break;
                }

                else
                {
                    continue;
                }

                    std::cout << std::endl << "(M)ore purchases?:";
                    char more;
                    std::cin >> more;

                    if (isChoice(more, 'M'))
                    {
                        continue;
                    }

                    else if (isChoice(more, 'L') || isChoice(more, 'N'))
                    {
                        break;
                    }

                    else
                    {
                        continue;
                    }
            }

            if (prevAttribute[DAMAGE] < p.getDamage())
            {
                std::cout << std::endl << "You can hit harder!" << std::endl;
                std::cout << "Damage increased from " << prevAttribute[DAMAGE] <<
                    " to " << p.getDamage() << std::endl;
            }

            if (prevAttribute[MAX_HEALTH] < p.getMaxHealth())
            {
                std::cout << std::endl << "You've become tougher!" << std::endl;
                std::cout << "Max health increased from " << prevAttribute[MAX_HEALTH] <<
                    " to " << p.getMaxHealth() << std::endl;
            }

            std::cout << std::endl << "Collectables purchased: " << std::endl;

            //if there were no items purchased from the shop
            if (std::all_of(shop.inventory.vec.begin(), shop.inventory.vec.end(), [](Item i1) {return (i1.getAmount() == 0); }))
            {
                std::cout << std::endl << "None." << std::endl;
            }

            //otherwise print out sold items
            else
            {
                for (auto it = shop.inventory.vec.begin(); it != shop.inventory.vec.end(); ++it)
                {
                    int index = std::distance(shop.inventory.vec.begin(), it);

                    //if they alread had the item before entering the shop and bought more
                    if (shop.inventory.getItem(index).getAmount() > 0)
                    {
                        //print out item name and how many were bought
                        std::cout << std::endl << shop.inventory.getItem(index).getName() << ": " <<
                            shop.inventory.getItem(index).getAmount() << std::endl;
                        shop.inventory.getItem(index).resetAmount();
                    }
                }
            }


            std::cout << std::endl << "You spent " << shop.getTotalCost() << " gold." << std::endl;
            std::cout << std::endl <<"Come again (5 more levels needed)." << std::endl;

            std::this_thread::sleep_for(std::chrono::milliseconds(1500));
        }

        else
        {   
            std::cout << std::endl << "You have exited the shop." << std::endl;
            std::cout << "5 more levels needed." << std::endl;

            std::this_thread::sleep_for(std::chrono::milliseconds(1200));

            return;
        }
}

Item.h

#pragma once

class Item
{
public:
    enum StatusEffect
    {
        ADD_DAMAGE,
        ADD_MAX_HEALTH,
        RESTORE_HEALTH,
        RESTORE_FULL_HEALTH,
        REPEL,
        MAX_STATUS_EFFECTS
    };

    enum AllItems
    {
        WEAPON_UPGRADE,
        ARMOR_UPGRADE,
        POTION,
        ULTIMATE_ELIXIR,
        REPELLENT,
        MAX_ITEMS
    };

    Item();

    Item
    (
        char symbol,
        const char * name,
        int cost,
        //to differentiate from collectable items from upgrades
        bool collectable,
        StatusEffect effect,
        const char *description
    );

    char getSymbol() const;

    const char * getName() const;

    int getCost() const;

    bool isCollectable() const;

    StatusEffect getEffect() const;

    const char * getDescript() const;

    int getAmount() const;
    void addAmount(int amount);
    void reduceAmount(int amount);

    //for tracking purposes
    void resetAmount();

    friend bool operator==(const Item &i1, const Item &i2);

protected:
    char m_symbol;
    const char * m_name;
    int m_cost;
    bool m_collectable;
    StatusEffect m_effect;
    const char * m_description;
    int m_amount;
};

Item.cpp

#include "stdafx.h"
#include "Item.h"

Item::Item () {}

Item::Item
    (
        char symbol,
        const char * name,
        int cost,
        bool collectable,
        StatusEffect effect,
        const char * description
    )
:
    m_symbol(symbol),
    m_name(name),
    m_cost(cost),
    m_collectable(collectable),
    m_effect(effect),
    m_description(description),
    m_amount(0)

    {

    }

char Item::getSymbol() const { return m_symbol; }

const char * Item::getName() const { return m_name; }

int Item::getCost() const { return m_cost; }

bool Item::isCollectable() const { return (m_collectable); }

Item::StatusEffect Item::getEffect() const { return m_effect; }

const char * Item::getDescript() const { return m_description; }

int  Item::getAmount() const { return m_amount; }
void Item::addAmount(int amount) { m_amount += amount; }
void Item::reduceAmount(int amount) { m_amount -= amount; }

void Item::resetAmount()
{
    int m_amount = 0;
}

bool operator==(const Item &i1, const Item &i2) 
{
    return (i1.getSymbol() == i2.getSymbol());
}

Inventory.h

(I tried to make the use of vectors simpler with the inventory class. I wanted to know if some of my design choices were the best choices in implementation. I'm a beginner at STL, and I don't know if I should have used other data structures besides vectors. I also don't know if how the Inventory class interacts with the Player class and the Shop class is good.)

#pragma once
#include "stdafx.h"
#include <vector>

class Item;

class Inventory 
{
public:
    Inventory(int size);

    //if vector operation is wanted, use vector vec of object inventory
    //e.g. player.inventory.vec.begin(), etc
    std::vector<Item> vec;

    // findItem should be user for when a user requests something
    //changes m_RequestedIndex accordingly
    void findItem(const char symbol);

    //to be used in conjuction with findItem
    bool hasFoundItem();

    Item& getReqItem();

    //to be used with getRequestedItem
    void addItem(Item &item);

    //to be used with getRequestedItem
    void removeItem(Item &item);

    //instead of being based on the requested index
    //this function just finds an item for any index
    Item& getItem(int index);

private:

    //indicates the location of an item in the inventory
    int m_RequestedIndex;

    bool foundItem;
};

Inventory.cpp

#include "stdafx.h"
#include "Inventory.h"
#include "Item.h"
#include "Utility.h"
#include <iostream>
#include <algorithm>

//for reserving how many items you want for the vector
Inventory::Inventory(int size) : m_RequestedIndex(0)
{
    vec.reserve(size);
}

void Inventory::findItem(char symbol)
{
    //finds item in vector, according to letter entered
    auto itemIterator = std::find_if
    (
        vec.begin(), vec.end(),
        [symbol](Item element)
    {
        return (isChoice(symbol, element.getSymbol()));
    }
    );
    //if there is a symbol match, set the index from the beginning of the vector to the iterator
    if (itemIterator != vec.end())
    {
        m_RequestedIndex = std::distance(vec.begin(), itemIterator);
        foundItem = true;
    }

    else
    {
        std::cout << std::endl << "Requested item not found." << std::endl;
        foundItem = false;
    }
}

bool Inventory::hasFoundItem() { return (foundItem); }

//to be used in conjuction with foundItem
//not const because it will be used with addItem
Item& Inventory::getReqItem() 
{

    return vec[m_RequestedIndex];

}

//to be used in conjuction with getRequestedItem
//adds item from another inventory to the player inventory,
//so m_RequestedIndex won't work.
void Inventory::addItem(Item &item1)
{
    //checks if item is already in the inventory
    auto it = std::find_if
    (

        vec.begin(), vec.end(),
        [item1](Item item2)
    {
        return (item1 == item2);
    }
    );

    int index = std::distance(vec.begin(), it);

    //if item is already there, then just add 1 to the item amount
    if (it != vec.end())
    {
        vec[index].addAmount(1);
        return;
    }

    //otherwise push back this item to the end of the inventory
    vec.push_back(item1);
    vec[index].addAmount(1);
}

//to be used when player uses something from their inventory
void Inventory::removeItem(Item &item1)
{
    if (item1.getAmount() == 1)
    {
        //erase based on where the requested index is

        vec.erase(vec.begin() + m_RequestedIndex);

        std::cout << std::endl << "Out of these items." << std::endl;
        return;
    }

    else if (item1.getAmount() > 1)
    {
        vec[m_RequestedIndex].reduceAmount(1);
        return;
    }


}


Item& Inventory::getItem(int index)
{
    return vec[index];
}
\$\endgroup\$
  • 1
    \$\begingroup\$ In main, I'd do the call to srand only once, outside of the loop, no need to seed it more that once. Also, you might look into the new C++11 ways of doing random number generation (en.cppreference.com/w/cpp/numeric/random), rand isn't the best these days. \$\endgroup\$ – Bwmat Sep 9 '17 at 23:58
  • \$\begingroup\$ Ok, I wasn't aware of other methods of random number generation. If I call srand out of the loop, wouldn't that mean the program would have the same seed, so the player would fight the same monsters the second time? \$\endgroup\$ – Jimmy Bob Thorton Sep 10 '17 at 0:11
  • \$\begingroup\$ And could you elaborate on why rand isn't the best method for my program? \$\endgroup\$ – Jimmy Bob Thorton Sep 10 '17 at 0:22
  • 1
    \$\begingroup\$ Haven't dealt with prng much lately myself, so I'm a bit rusty, but this video should give a good overview: channel9.msdn.com/Events/GoingNative/2013/… \$\endgroup\$ – Bwmat Sep 10 '17 at 0:27
  • 1
    \$\begingroup\$ For why you only need to seed once, seeding chooses which sequence rand is going to traverse. That sequence, while it will repeat, because rand/srand's internal state is limited, won't do so for a LONG time. There may actually be some point to reseeding multiple times, as given enough knowledge about rand's return values, you can infer something about its internal state, and use that to predict future values, but if you care about something like that, you really should be using a more sophisticated PRNG anyway (and possibly a cryptographic one). \$\endgroup\$ – Bwmat Sep 10 '17 at 0:31
1
\$\begingroup\$

Item 1. Instead of

return (input == uppercaseChoice || input == static_cast<char>(tolower(uppercaseChoice)));

You can change the value, which case you do not know, instead of variable which case you do know

return uppercaseChoice == static_cast<char>(toupper(input));

And if you pass uppercaseChoice as int you do not need the cast at all

Item 2. Minimize header dependencies. For example I do not see a need to include

#include "EntityType.h"

in the Creature.h

Item 3. I would change monsterPlayerInteraction array to getter function Move body of the function to cpp and instead of 3d array - use map

static std::map<CreatureType, std::map<MonsterType, std::map<CombatDialogue, std::string>>> Interactions =
{
    {MONSTER, {
        {SLIME,{
                {INTRO, "A slime jiggles around."},
                {HIT,   "The slime smacks you as hard as it can."},
                {DEATH, "The slime squishes apart."}
            }
        },
        {IMP, {...}}
    },
    },
    {PLAYER, {...}}
};

//access
{
    std::cout << Interactions[MONSTER][SLIME][INTRO] << '\n';
}

This way you eliminate need of commenting which entry is related to which enum entry And have no problems extending the enum - there is no order dependency between map and enum definition which could broken your code in the future

Item 4. Optionally I would change the enums to class enums to avoid global namespace pollution. Regular enum members are visible in the global namespace - so it could potentially create conflicts with other global symbols. class enum solves this problem - since it's members are removed from global namespace and accessible only as CombatDialogue::INTRO

\$\endgroup\$
  • \$\begingroup\$ Regarding item 4, could you elaborate on global namespace pollution? \$\endgroup\$ – Jimmy Bob Thorton Sep 10 '17 at 14:47
  • \$\begingroup\$ Regarding item 2, the header file was basically to stop circular dependencies. The enums would cause circular dependencies, so I decided to put them into a separate header file in order to stop the dependencies. I am not sure of a better solution. \$\endgroup\$ – Jimmy Bob Thorton Sep 10 '17 at 14:49
  • 1
    \$\begingroup\$ Item2 - I'm not talking about combining header files. Different header file for enums is in most cases a good solution. I am talking about including header in other header when nothing is this included header is actually used. Creature header uses nothing from EntityType.h So it should not be included \$\endgroup\$ – Artemy Vysotsky Sep 10 '17 at 16:06
  • 1
    \$\begingroup\$ Item3. If you add new monster into enum of MosterType and put it between Slime and Imp - which your approach you will be forced to put the initialization strings between Slime and Imp initialization or all your strings will be misplaced. With map approach order of entries in the emum and order of entries in the initialization are independent since you write the key for new entry explicitly. \$\endgroup\$ – Artemy Vysotsky Sep 10 '17 at 16:10
  • 1
    \$\begingroup\$ Item4 - updated \$\endgroup\$ – Artemy Vysotsky Sep 10 '17 at 16:11
1
\$\begingroup\$

It is a good attempt to take it as a challenge. A few pointers may help you while you make progress.

  1. Best practice is to limit number of characters in a line to 80 characters. A few lines of code in your program is crossing this limit, so need to adjust the editor while reading the code. 80 character limit helps to read the code more conveniently

  2. Function rand() may deprecate on the way, so better to use mechanisms available in header file as part of C++11. Refer here to find an easy way to replace the rand() call

  3. Do we really need the modulo operation to check the level as follows p.getLevel() % 5 == 0 What is the harm if we take a simple approach of p.getLevel() > 5

  4. Why do we need std::endl in beginning and ending of a line . I do recommend to replace std::endl by adding '\n' at end of the string. The reasoning behind this recommendation is available here.

    std::cout << std::endl << "You have an inventory system." << std::endl;

The above line can be replaced with:

 `std::cout << "You have an inventory system.\n";` 

5. You can refactor your main function further and replace if else chain with a switch statement.

if (isChoice(choice, 'Y'))
        {
            firstTime = false;
            continue;
        }

        else if (isChoice(choice, 'N'))
        {
            break;
        }

        else
        {
            firstTime = false;
            continue;
        }

I will replace above snippet by introducing a small function isFirstTime() with an if condition as follows:

    if(isFirstTime())
         break;
   }//outer while loop ends;

bool isFirstTime(){
    bool firstTime = true;
    int choice;
    std::cin >> choice;

    Switch(choice){
        case 0: 
              firstTime = false;
              break;
         case 1: 
               break;
         default: 
               firstTime = false;
    }
    return firstTime;
}
\$\endgroup\$
  • \$\begingroup\$ Regarding 4, the std::endl on the end and beginning is just a matter of formatting in the console. Could you elaborate on when I should use '\n' instead of std::endl? The link states something about performance, and flushing the output buffer. I don't understand what flushing the output buffer means. I will take other advice for consideration in my program. \$\endgroup\$ – Jimmy Bob Thorton Sep 10 '17 at 14:44
  • \$\begingroup\$ Regarding 3, the use of the modulus operator is actually a game mechanic. I didn't want the player to be able to enter the shop after every level-up after level 5. I only wanted them to enter every 5 levels as there's no point in asking them after every battle because they probably do not have enough gold to buy anything. \$\endgroup\$ – Jimmy Bob Thorton Sep 10 '17 at 14:45
  • \$\begingroup\$ My comment on 3 in the program was kind of misleading, so I updated it to reflect this mechanic. \$\endgroup\$ – Jimmy Bob Thorton Sep 10 '17 at 15:11
  • 1
    \$\begingroup\$ About \n and endl read stackoverflow.com/a/213977/8491726 and about flushing stackoverflow.com/a/15042890/8491726 \$\endgroup\$ – Artemy Vysotsky Sep 10 '17 at 16:17

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.