8
\$\begingroup\$

Started learning C++ a few weeks ago, tried to accurately simulate a Pokemon battle. Not too optimistic how it turned out, please tell me how I could improve it.

Only the bare minimum(damage, accuracy, type effectiveness) are implemented, items weather etc are not.

typechart.h:

#pragma once
constexpr float typeChart[18][18]
{
    {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f, 0.0f, 1.0f, 1.0f, 0.5f, 1.0f}, //normal
    {1.0f, 0.5f, 0.5f, 1.0f, 2.0f, 2.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 0.5f, 1.0f, 0.5f, 1.0f, 2.0f, 1.0f}, //fire
    {1.0f, 2.0f, 0.5f, 1.0f, 0.5f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.5f, 1.0f, 1.0f, 1.0f}, //water
    {1.0f, 1.0f, 2.0f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 2.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f, 1.0f, 1.0f, 1.0f}, //elec
    {1.0f, 0.5f, 2.0f, 1.0f, 0.5f, 1.0f, 1.0f, 0.5f, 2.0f, 0.5f, 1.0f, 0.5f, 2.0f, 1.0f, 0.5f, 1.0f, 0.5f, 1.0f}, //grass
    {1.0f, 0.5f, 0.5f, 1.0f, 2.0f, 0.5f, 1.0f, 1.0f, 2.0f, 2.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.5f, 1.0f}, //ice
    {2.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 2.0f, 0.0f, 1.0f, 2.0f, 2.0f, 0.5f}, //fight
    {1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 2.0f}, //poison
    {1.0f, 2.0f, 1.0f, 2.0f, 0.5f, 1.0f, 1.0f, 2.0f, 1.0f, 0.0f, 1.0f, 0.5f, 2.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f}, //ground
    {1.0f, 1.0f, 1.0f, 0.5f, 2.0f, 1.0f, 2.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.5f, 1.0f}, //flying
    {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.5f, 1.0f}, //psychic
    {1.0f, 0.5f, 1.0f, 1.0f, 2.0f, 1.0f, 0.5f, 0.5f, 1.0f, 0.5f, 2.0f, 1.0f, 1.0f, 0.5f, 1.0f, 2.0f, 0.5f, 0.5f}, //bug
    {1.0f, 2.0f, 1.0f, 1.0f, 1.0f, 2.0f, 0.5f, 1.0f, 0.5f, 2.0f, 1.0f, 2.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f, 1.0f}, //rock
    {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.5f, 1.0f, 1.0f}, //ghost
    {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.5f, 0.0f}, //dragon
    {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.5f, 1.0f, 0.5f}, //dark
    {1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 2.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 1.0f, 1.0f, 0.5f, 2.0f}, //steel
    {1.0f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f, 0.5f, 1.0f}  //fairy
};

pokemon.h:

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

struct Move
{
    std::string name;
    int basePower{};
    int accuracy{};
    int type{};
    bool dmgType{}; //true for physical, false for special
};

class Pokemon
{
    std::string name;
    int level{};
    int hp{};
    int hpMax{};
    int attack{};
    int defense{};
    int spAttack{};
    int spDefense{};
    int speed{};
    int typePrimary{};
    int typeSecondary{};
    Move slotOne;
    Move slotTwo;
    Move slotThree;
    Move slotFour;
public:
    std::string getName();
    void printBattleText();
    void printFainted();
    void setName(std::string iname);
    void setStats(int ilevel, int ihpMax, int iattack, int idefense, int ispAttack, int ispDefense, int ispeed);
    void setType(int one, int two);
    void setMoves(Move one, Move two, Move three, Move four);
    int getPhysAtkValue(Pokemon pokemon, Move move);
    int getSpAtkValue(Pokemon pokemon, Move move);
    bool isFasterThan(Pokemon pokemon);
    void displayMoves();
    Move moveSelect(int input);
    void takeDamage(int damage);
    double stabCheck(Move move);
    float getTypeEff(Move move);
    bool hasFainted();
};

pokemon.cpp:

#include "pokemon.h"
#include "typechart.h"

std::string Pokemon::getName()
{
    return name;
}

void Pokemon::printBattleText()
{
    std::cout << name << '\n' << "Level " << level << '\n' << hp << "/" << hpMax << "\n \n";
}

void Pokemon::printFainted()
{
    std::cout << name << " fainted!\n";
}

void Pokemon::setName(std::string iname)
{
    name = iname;
}

void Pokemon::setStats(int ilevel, int ihpMax, int iattack, int idefense, int ispAttack, int ispDefense, int ispeed)
{
    level = ilevel;
    hp = ihpMax;
    hpMax = ihpMax;
    attack = iattack;
    defense = idefense;
    spAttack = ispAttack;
    spDefense = ispDefense;
    speed = ispeed;
}

void Pokemon::setType(int one, int two)
{
    typePrimary = one;
    typeSecondary = two;
}

void Pokemon::setMoves(Move one, Move two, Move three, Move four)
{
    slotOne = one;
    slotTwo = two;
    slotThree = three;
    slotFour = four;
}

int Pokemon::getPhysAtkValue(Pokemon pokemon, Move move)
{
    int levelCalc{ (2 * level / 5) + 2 };
    int finalCalc{ levelCalc * attack * move.basePower / pokemon.defense };
    return finalCalc;
}

int Pokemon::getSpAtkValue(Pokemon pokemon, Move move)
{
    int levelCalc{ (2 * level / 5) + 2 };
    int finalCalc{ levelCalc * spAttack * move.basePower / pokemon.spDefense };
    return finalCalc;
}

bool Pokemon::isFasterThan(Pokemon pokemon)
{
    return (speed > pokemon.speed);
}

void Pokemon::displayMoves()
{
    std::cout << "1. " << slotOne.name << std::endl;
    std::cout << "2. " << slotTwo.name << std::endl;
    std::cout << "3. " << slotThree.name << std::endl;
    std::cout << "4. " << slotFour.name << std::endl;
}

Move Pokemon::moveSelect(int input)
{
    switch (input)
    {
    case 1:
        return slotOne;
    case 2:
        return slotTwo;
    case 3:
        return slotThree;
    case 4:
        return slotFour;
    default:
        return slotOne;
    }
}

void Pokemon::takeDamage(int damage)
{
    if (damage == -1)
        std::cout << name << " avoided the attack!\n";
    else if (damage >= hp)
    {
        std::cout << name << " took " << damage - (damage - hp) << " damage!\n";
        hp = 0;
    }
    else
    {
        hp = hp - damage;
        std::cout << name << " took " << damage << " damage!\n \n";
    }
}

double Pokemon::stabCheck(Move move)
{
    if (typePrimary == move.type || typeSecondary == move.type)
        return 1.5;
    else
        return 1.0;
}

float Pokemon::getTypeEff(Move move)
{
    if (typeSecondary == -1)
        return typeChart[move.type][typePrimary];
    else
        return typeChart[move.type][typePrimary] * typeChart[move.type][typeSecondary];
}

bool Pokemon::hasFainted()
{
    return (hp <= 0);
}

battlefunctions.cpp:

#include "pokemon.h"
#include <array>

std::random_device rd{};
std::mt19937 mersenne{ rd() };
std::uniform_int_distribution hit{ 1, 100 };
std::uniform_int_distribution dmgRoll{ 85, 100 };

void battleStart(std::array<Pokemon, 2>&, std::array<Pokemon, 2>&);

int getInput(int lower, int upper)
{
    int input{};
    std::cin >> input;
    while (input < lower || input > upper)
    {
        std::cout << "Select a valid option:\n";
        std::cin >> input;
    }
    return input;
}

bool moveHit(int accuracy)
{
    int x = hit(mersenne);
    if (x <= accuracy)
        return true;
    else
        return false;
}

int getDamage(Pokemon& attacker, Pokemon& defender, Move move)
{
    int damageOne{}, damageFinal{};
    std::cout << attacker.getName() << " used " << move.name << "!\n";
    if (moveHit(move.accuracy))
    {
        if (move.dmgType)
        {
            damageOne = attacker.getPhysAtkValue(defender, move);
            damageFinal = (damageOne / 50) + 2;
        }
        else
        {
            damageOne = attacker.getSpAtkValue(defender, move);
            damageFinal = (damageOne / 50) + 2;
        }
        damageFinal = static_cast<int>(damageFinal * attacker.stabCheck(move) * defender.getTypeEff(move));
        damageFinal = damageFinal * dmgRoll(mersenne) / 100;
        if (defender.getTypeEff(move) >= 2)
            std::cout << "It's super effective!\n";
        else if (defender.getTypeEff(move) == 0)
            std::cout << "It doesn't affect " << defender.getName() << "...\n";
        else if (defender.getTypeEff(move) <= 0.5)
            std::cout << "It's not very effective...\n";
        return damageFinal;
    }
    else
    {
        return -1; //attack missed
    }
}

int switchPokemon(std::array <Pokemon, 2>& party)
{
    std::cout << "Who will you switch to?\n";
    int partySize{ 2 }, count{}, input{ 0 };
    while (input <= 1 || input > partySize)
    {
        count = 1;
        for (auto& Pokemon : party)
        {
            std::cout << count << ". " << Pokemon.getName() << std::endl;
            count++;
        }
        std::cin >> input;
        if (input <= 1 || input > partySize)
            std::cout << "You can't select that!\n";
        else if (party[input - 1].hasFainted())
            std::cout << party[input - 1].getName() << "has fainted! You can't send them out!\n";
    }
    int result = input;
    return result;

}

bool loseBattle(std::array<Pokemon, 2>& party)
{
    for (auto& pokemon : party)
    {
        if (pokemon.hasFainted() == false)
            return false;
    }
    return true;
}

void twoFaint(std::array<Pokemon, 2> two, std::array<Pokemon, 2> one)
{
    two[0].printFainted();
    if (loseBattle(two))
    {
        std::cout << "Player Two has no Pokemon left!\n";
        throw "Player One wins!";
    }
    else
    {
        int switchOut{ switchPokemon(two) };
        std::swap(two[0], two[switchOut - 1]);
        battleStart(one, two);
    }
}

void oneFaint(std::array<Pokemon, 2> one, std::array<Pokemon, 2> two)
{
    one[0].printFainted();
    if (loseBattle(one))
    {
        std::cout << "Player One has no Pokemon left!\n";
        throw "Player Two wins!";
    }
    else
    {
        int switchOut{ switchPokemon(one) };
        std::swap(one[0], one[switchOut - 1]);
        battleStart(one, two);
    }
}

movedata.h:

#pragma once
#include "pokemon.h"

namespace Moves
{
    Move flamethrower{ "Flamethrower", 90, 100, 1, false };
    Move airSlash{ "Air Slash", 75, 95, 9, false };
    Move dragonClaw{ "Dragon Claw", 80, 100, 14, true };
    Move slash{ "Slash", 70, 100, 0, true };
    Move psychic{ "Psychic", 90, 100, 10, false };
    Move thunderPunch{ "Thunder Punch", 75, 100, 3, true };
    Move icePunch{ "Ice Punch", 75, 100, 5, true };
    Move energyBall{ "Energy Ball", 90, 100, 4, false };
    Move bubbleBeam{ "Bubblebeam", 65, 100, 2, false };
    Move shadowBall{ "Shadow Ball", 80, 100, 13, false };
    Move iceBeam{ "Ice Beam", 90, 100, 5, false };
    Move dazzlingGleam{ "Dazzling Gleam", 80, 100, 17, false };
    Move dragonRush{ "Dragon Rush", 100, 75, 14, true };
    Move earthquake{ "Earthquake", 100, 100, 8, true };
    Move brickBreak{ "Brick Break", 75, 100, 6, true };
    Move poisonJab{ "Poison Jab", 80, 100, 7, true };
}

main.cpp:

#include "pokemon.h"
#include "movedata.h"
#include <array>

int getInput(int, int);
bool moveHit(int);
int getDamage(Pokemon&, Pokemon&, Move);
int switchPokemon(std::array <Pokemon, 2>&);
bool loseBattle(std::array<Pokemon, 2>&);
void twoFaint(std::array<Pokemon, 2>, std::array<Pokemon, 2>);
void oneFaint(std::array<Pokemon, 2>, std::array<Pokemon, 2>);

void battleStart(std::array<Pokemon, 2>& one, std::array<Pokemon, 2>& two)
{
    one[0].printBattleText();
    two[0].printBattleText();
    std::cout << "Player One, what will you do?\n1.Fight\n2.Pokemon\n";
    int playerOneInput{ getInput(1, 2) };
    int playerOneInputTwo{};
    switch (playerOneInput)
    {
    case 2:
        playerOneInputTwo = switchPokemon(one);
        break;
    default:
        one[0].displayMoves();
        playerOneInputTwo = getInput(1, 4);
    }
    std::cout << "Player Two, what will you do?\n1. Fight\n2. Pokemon\n";
    int playerTwoInput{ getInput(1, 2) };
    int playerTwoInputTwo{};
    switch (playerTwoInput)
    {
    case 2:
        playerTwoInputTwo = switchPokemon(two);
        break;
    default:
        two[0].displayMoves();
        playerTwoInputTwo = getInput(1, 4);
    }
    if (playerOneInput == 1 && playerTwoInput == 1)
    {
        if (one[0].isFasterThan(two[0]))
        {
            int damage{ getDamage(one[0], two[0], one[0].moveSelect(playerOneInputTwo)) };
            two[0].takeDamage(damage);
            if (two[0].hasFainted())
                twoFaint(two, one);
            int damageSecondRound{ getDamage(two[0], one[0], two[0].moveSelect(playerTwoInputTwo)) };
            one[0].takeDamage(damageSecondRound);
            if (one[0].hasFainted())
                oneFaint(one, two);
            battleStart(one, two);
        }
        else
        {
            int damage{ getDamage(two[0], one[0], two[0].moveSelect(playerTwoInputTwo)) };
            one[0].takeDamage(damage);
            if (one[0].hasFainted())
                oneFaint(one, two);
            int damageSecondRound{ getDamage(one[0], two[0], one[0].moveSelect(playerOneInputTwo)) };
            two[0].takeDamage(damageSecondRound);
            if (two[0].hasFainted())
                twoFaint(two, one);
            battleStart(one, two);
        }
    }
    else if (playerOneInput == 1 && playerTwoInput == 2)
    {
        std::cout << "Player Two withdrew " << two[0].getName() << "! They sent out " << two[playerTwoInputTwo - 1].getName() << "!\n";
        std::swap(two[0], two[playerTwoInputTwo - 1]);
        int damage{ getDamage(one[0], two[0], one[0].moveSelect(playerOneInputTwo)) };
        two[0].takeDamage(damage);
        if (two[0].hasFainted())
            twoFaint(two, one);
        battleStart(one, two);
    }
    else if (playerOneInput == 2 && playerTwoInput == 1)
    {
        std::cout << "Player One withdrew " << one[0].getName() << "! They sent out " << one[playerOneInputTwo - 1].getName() << "!\n";
        std::swap(one[0], one[playerOneInputTwo - 1]);
        int damage{ getDamage(two[0], one[0], two[0].moveSelect(playerTwoInputTwo)) };
        one[0].takeDamage(damage);
        if (one[0].hasFainted())
            oneFaint(one, two);
        battleStart(one, two);
    }
    else
    {
        std::cout << "Player One withdrew " << one[0].getName() << "! They sent out " << one[playerOneInputTwo - 1].getName() << "!\n";
        std::swap(one[0], one[playerOneInputTwo - 1]);
        std::cout << "Player Two withdrew " << two[0].getName() << "! They sent out " << two[playerTwoInputTwo - 1].getName() << "!\n";
        std::swap(two[0], two[playerTwoInputTwo - 1]);
        battleStart(one, two);
    }
}

int main()
{
    Pokemon Charizard;
    Charizard.setName("Charizard");
    Charizard.setStats(50, 150, 90, 130, 177, 140, 155);
    Charizard.setType(1, 9); //fire, flying
    Charizard.setMoves(Moves::flamethrower, Moves::airSlash, Moves::dragonClaw, Moves::slash);

    Pokemon Mesprit;
    Mesprit.setName("Mesprit");
    Mesprit.setStats(50, 155, 157, 157, 157, 157, 140);
    Mesprit.setType(10, -1); //psychic
    Mesprit.setMoves(Moves::psychic, Moves::icePunch, Moves::thunderPunch, Moves::energyBall);

    Pokemon Manaphy;
    Manaphy.setName("Manaphy");
    Manaphy.setStats(50, 195, 150, 150, 150, 150, 150);
    Manaphy.setType(2, -1); //water
    Manaphy.setMoves(Moves::bubbleBeam, Moves::dazzlingGleam, Moves::iceBeam, Moves::shadowBall);

    Pokemon Garchomp;
    Garchomp.setName("Garchomp");
    Garchomp.setStats(50, 180, 180, 145, 85, 110, 160);
    Garchomp.setType(14, 8); //dragon, ground
    Garchomp.setMoves(Moves::brickBreak, Moves::dragonRush, Moves::earthquake, Moves::poisonJab);

    std::array<Pokemon, 2> partyOne{ Charizard, Manaphy };
    std::array<Pokemon, 2> partyTwo{ Mesprit, Garchomp };

    try 
    {
        battleStart(partyOne, partyTwo);
    }
    catch (const char* winText)
    {
        std::cout << winText << std::endl;
        return EXIT_SUCCESS;
    }
    
}
\$\endgroup\$
0

2 Answers 2

3
\$\begingroup\$

Overall quite nicely done - I wish I had been as good after just a few weeks of learning :-)

Here are some concrete points that I hope will be helpful:

  • As a major way of improving your code, learn about const correctness. In short, every function that doesn't modify the state of the object should be const; all kinds of getter function fall into this category for example. Using const variables helps you avoid making unintended mistakes and documents the intention of the code. Passing by const reference is often a good idea.

  • Learn about class constructors. Constructors are the appropriate way of initializing an object. They will document the code better and their usage will lead to performance gains and most importantly protect from misuse of classes when compared to setter functions that must be called after object initialization. Think about: isn't it nice to be able to say "if an object exists, it will be in some nice state" instead of "oh I hope the user has remembered to call this init function, because if not, things will break inside my class and I need to maybe add some checks".

  • Learn about enum types (slightly more modernly, about class enums). Use these instead of bare ints or bools to represent small predefined types. Again, this will self-document the code and protect from mistakes.

  • In Pokemon, I would use an std::array to represent the Move slots instead of hardcoding them; it will be easier to operate on them when you can naturally index the slots.

  • Your code is quite well-structured otherwise. For example, I wouldn't have expected a beginner to separate the battle functions into a separate piece of logic.

  • Often times, you write along the lines of if (blah) return true else return false, when in reality you can just write return blah. This leads to clearer code and is what an experienced programmer will expect.

  • Define variables as late as possible and as close to their site of usage. This will improve readability and potentially performance as well. For example, look at getDamage: the reader will go "damageOne and damageFinal, what? Why? Why are they declared here?" - they shouldn't be, they should appear later inside the if-branch because they can. Making them appear any earlier than they have to pollutes the scope and hurts readability when you need to keep track of unncessary things.

  • As a good guideline, remember that one function (or class), one responsibility. For example, getDamage seems like it might be doing too much. It should only get the damage - why is it also interpreting the damage somehow and printing out things? That's confusing and separating the logic would help maintainability.

  • In switchPokemon, a good example of a variable that should be const is partySize. If it is not const, an experienced reader will instinctly assume it can change and try to see where that happens. But in fact, if it would change, that's not what you want. It's bad and something would break but the compiler won't complain.

  • In loseBattle, don't compare against false. More idiomatic is to check if (!pokemon.hasFainted()) { ... }.

\$\endgroup\$
-2
\$\begingroup\$

Hello i think you should read data from a file instead of putting it in code so you can tweak balance without recompiling, if you fear the lack of readability in code, juste add a single comment where is appropriate.

You also could use Enum for pokemon type. I was a bit confused by Move, as i thought it was movement (It's really an attack, or ability).

Same for one and two, why not just players[i]?

some simplification:

    void Pokemon::takeDamage(int damage)
{
    if (damage == -1)
        std::cout << name << " avoided the attack!\n";
    
    else
    {
        hp -= damage;
        std::cout << name << " took " << damage << " damage!\n \n";
        if (hp<0) {hp = 0;}
    }
}

You probably should create a Player Class wich own pokemon (and object?)

A last thing :

try... catch exist to handle exceptions not really for a perfectly fine end of program.

I didn't test you program by running it!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ {} after a variable is default-initializing it. It is consider gold-standard today in C++ to initialize all variables, or - even better - assign to them in the declaration. \$\endgroup\$
    – Aganju
    Commented Apr 17, 2021 at 4:55
  • \$\begingroup\$ I didn't check c++ code in years, it's the first time i see that, was used to var = 0/NULL \$\endgroup\$
    – jolrael
    Commented Apr 17, 2021 at 5:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.