patterncppCritical
Basic Pokedex in C++
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:
Pokemon.cpp:
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
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)
Here are several things that spring to eye. First of all, all your fields are public. This indicates that you want a
Next, we should disable
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:
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.
Since Pokemon can have several types, we use another type
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
The operator enables us to use
Instead, if you use
you end up with a compiler error, instead of a frustrated user not able to find "Bulbasaur" by type "Grass". For testing, an
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
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
Again, this prevents you from writing
Putting it all together
For the other types, for the sake of simplicity, we say
Therefore, we end up with the following struct:
Note that contrary to your code, all variables here strictly follow camelCase, whereas your code contained
"But wait,", I here you say. "Now the arguments have the same name as my members!". And that's good. If someone uses
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;
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 usingPokemon("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.