HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppCritical

Basic Pokedex in C++

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
pokedexbasicstackoverflow

Problem

I recently started learning C++ about a week ago, and I made a bit of progress. In honor of Pokemon Go releasing, I decided to make a Pokedex with the original 151 Pokemon. So far I have 120 or so of them in there and it works PERFECTLY! (Thank goodness). I believe my code could be much more efficient and work better. I probably am using bad practices too. If I am, please feel free to tell me and if you find a problem, tell me!

Pokemon.h:

#pragma once
#include 

class Pokemon {
public:
    std::string type;
    double weight, height;
    std::string Gender;
    int evoLevel;
    bool finalEvo;
    int dexNum;
    std::string name;

    Pokemon(std::string name2, std::string type2, double weight2, double height2, std::string Gender2, int evoLevel2, bool finalEvo2, int dexNum2);
    Pokemon();
};


Pokemon.cpp:

#include "Pokemon.h"

Pokemon::Pokemon(std::string name2, std::string type2, double weight2,  double height2, std::string Gender2, int evoLevel2, bool finalEvo2, int dexNum2) {
    name = name2;
    type = type2;
    weight = weight2;
    height = height2;
    Gender = Gender2;
    evoLevel = evoLevel2;
    finalEvo = finalEvo2;
    dexNum = dexNum2;
}

//Default constructer
Pokemon::Pokemon() {
    name = "Pichario";
    type = "Death";
    weight = 10;
    height = 12;
    Gender = "Male and Female";
    evoLevel = 1;
    finalEvo = true;
    dexNum = 999;
}


main.cpp:

```
#include
#include
#include
#include "Pokemon.h"

int main() {
//Create Pokemon objects
Pokemon bulbasaur("Bulbasaur", "Grass and Poison", 15.2, 28, "Male and Female", 1, false, 1);
Pokemon ivysaur("Ivysaur", "Grass and Poison", 28.7, 39, "Male and Female", 2, false, 2);
Pokemon venusaur("Venusaur", "Grass and Poison", 220.5, 79, "Male and Female", 3, true, 3);

Pokemon charmander("Charmander", "Fire", 18.7, 24, "Male and Female", 1, false, 4);
Pokemon charmeleon("Charmeleon", "Fire", 41.9, 44, "Male and Female", 2, false, 5);
Pokemon chariza

Solution

Your Pokemon class (declaration)

class Pokemon {
public:
    std::string type;
    double weight, height;
    std::string Gender;
    int evoLevel;
    bool finalEvo;
    int dexNum;
    std::string name;

    Pokemon(std::string name2, std::string type2, double weight2, double height2, std::string Gender2, int evoLevel2, bool finalEvo2, int dexNum2);
    Pokemon();
};


Here are several things that spring to eye. First of all, all your fields are public. This indicates that you want a struct, not a class. In C++, they only differ by the default visibility. class is private by default, struct is public by default. So let's change that:

struct Pokemon {


Next, we should disable Pokemon(). That way, one cannot create a pokemon by accident:

Pokemon() = delete;


Note that this isn't necessary, since any constructor will prevent the compiler from providing a default one.

Better types

In your other constructor, we change the name of the arguments and their types slightly:

Pokemon(Name name, Type type, Weight weight, Height height, Gender gender, 
            EvoLevel level, bool finalEvolution, PokedexID pid);


Wait. What the hell are all those types? First of all, they are an overkill. Second of all, they follow the C++ core guidelines. You don't want to use a Pokemon's name for its gender by accident, do you?

Handling the Pokemon's type

Which brings us to enumerations. Type and Gender are perfect candidates for those:

enum class BasicType {
   Fire,
   Grass,
   Water,
   Electro,
   Poison,
   ...
};


Since Pokemon can have several types, we use another type Type to combine them:

class Type{ 
    Type(BasicType main);
    Type(BasicType main, BasicType sub);

    void addSubType(BasicType);
    bool hasType(BasicType) const;
};


I'm not sure whether Pokemon will ever have more than two types, but as @Eeevee noticed, they are ordered.

If you want, you can define syntactic sugar upon this with by overloading operator|:

Type operator|(BasicType main, BasicType sub) { return Type(main, sub); }
Type operator|(Type a,         BasicType sub) { return a.addSubtype(sub); }


The operator enables us to use Type::Fire | Type::Poison. Note that this is probably again an overkill. However, it will prevent you from using

Pokemon("Bulbasaur", "Gras and Poison", ...);


Instead, if you use

Pokemon("Bulbasaur", Type::Gras | Type::Poison, ...);


you end up with a compiler error, instead of a frustrated user not able to find "Bulbasaur" by type "Grass". For testing, an operator& might come in handy:

Type operator&(Type      a, BasicType b) { return a.hasType(b); }
Type operator&(BasicType a, Type      b) { return b.hasType(a); }


That's very basic, but it gets its job done. How you actually store the types is left as an exercise, but not too hard. Note that you can add arbitrary methods to Type or functions that take Type as an argument, so there is enough space for additional mad science experiments features.

A gender study

We can do the same for gender, although it's slightly easier here, since there are only some and therefore we don't need the operator| trick or addSubType:

enum class Gender {
   Male,
   Female,
   Both,
   Unknown
};


Again, this prevents you from writing

Pokemon("Bulbasaur", Type::Grass | Type::Poison, 15.2, 28, "Apache helicopter", ...);


Putting it all together

For the other types, for the sake of simplicity, we say

typedef std::string Name;
typedef double Weight;
typedef double Height;
typedef unsigned int EvoLevel;
typedef unsigned int PokedexID;


Therefore, we end up with the following struct:

struct Pokemon {
    Pokemon() = delete;
    Pokemon(Name name, Type type, Weight weight, Height height, Gender gender, 
            EvoLevel level, bool isFinalEvolution, PokedexID pid);

  Name name;
    Type type;
    Weight weight;
    Height height;
    Gender gender;
    EvoLevel evolutionLevel;
    bool isFinalEvolution;
    PokedexID pid;
};


Note that contrary to your code, all variables here strictly follow camelCase, whereas your code contained Gender. Also notice that in class objects the members are usually prefixed with m_, suffixed with _ or follow another naming convention. Since you want to use it without encapsulation and all public (e.g. pokemon.type) prefixes or suffixes would make your code harder to read here.

"But wait,", I here you say. "Now the arguments have the same name as my members!". And that's good. If someone uses Pokemon::Pokemon(...) in their program, they want their IDE to show the argument names in a nice way, without the 2 as suffix.

Which brings us to your constructor's implementation:

```
// your code
#include "Pokemon.h"

Pokemon::Pokemon(std::string name2, std::string type2, double weight2, double height2, std::string Gender2, int evoLevel2, bool finalEvo2, int dexNum2) {
name = name2;

Code Snippets

class Pokemon {
public:
    std::string type;
    double weight, height;
    std::string Gender;
    int evoLevel;
    bool finalEvo;
    int dexNum;
    std::string name;

    Pokemon(std::string name2, std::string type2, double weight2, double height2, std::string Gender2, int evoLevel2, bool finalEvo2, int dexNum2);
    Pokemon();
};
struct Pokemon {
Pokemon() = delete;
Pokemon(Name name, Type type, Weight weight, Height height, Gender gender, 
            EvoLevel level, bool finalEvolution, PokedexID pid);
enum class BasicType {
   Fire,
   Grass,
   Water,
   Electro,
   Poison,
   ...
};

Context

StackExchange Code Review Q#135293, answer score: 154

Revisions (0)

No revisions yet.