Home » Basic Pokedex in C++

Basic Pokedex in C++

Solutons:


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;
    type = type2;
    weight = weight2;
    height = height2;
    Gender = Gender2;
    evoLevel = evoLevel2;
    finalEvo = finalEvo2;
    dexNum = dexNum2;
}

Aha. First of all, if you have an argument that has the same name as a member, you can use this->[membername] to distinguish, e.g.

this->name = name2;

However, we’re in a constructor. Therefore, we can initialize our values. Note that this isn’t necessary with a POD, but some of your members are classes, for example Name (it’s a std::string). This saves a copy. We end up with:

Pokemon(Name name, Type type, Weight weight, Height height, Gender gender, 
        EvoLevel level, bool isFinalEvolution, PokedexID pid) 
 : name(std::move(name)), type(type), weight(weight), height(height), gender(gender),
   evolutionLevel(evolutionLevel), isFinalEvolution(isFinalEvolution), pid(pid) {
}

Note that this indicates that we didn’t really need have defined a constructor, we could just have used our usual struct initialisation syntax

Pokemon bulbasaur = { "Bulbasaur", ... };

But depending on your types, that’s slightly more error prone.

Output and DRY

You don’t follow the Don’t-Repeat-Yourself principle. Your code contains the facilities to print a Pokemon twice. This is bad, since changes on one part should probably also be done on the other, but get lost. Instead, provide a operator, if you want to use std::cout:

std::ostream & operator<<(std::ostream & out, const Pokemon & pokemon) {
    out << pokemon.name << " is a " << pokemon.type << " type.n"
        << pokemon.name << "'s weight and height are: " 
        << pokemon.weight << " lbs and " << pokemon.height << " inches.n";
        << pokemon.name << "'s gender is " << pokemon.gender << "n";

    if (pokemon.isFinalEvolution) {
        out << pokemon.name << " is the final evolution in its line.n";
    }
    ...
    return out;
}

Note that this also needs operator<< on all types used in Pokemon, e.g. Gender and Type.

The database

So here’s my greatest pet peeve with your code. Your main:

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);
...

There are two things I’m not fond of here. First of all, you only use the names “bulbasaur” or “ivysaur” once later, in std::vector<Pokemon> pokemon, so you’re polluting the local namespace. Instead, fill your vector directly:

std::vector<Pokemon> pokedex = { 
    Pokemon("Bulbasaur", Type::Grass | Type::Poison, 15.2, 28, Gender::Both, 1, false, 1),
    Pokemon("Ivysaur", Type::Grass | Type::Poison, 28.7, 39, Gender::Both, 2, false, 2),
    ...
};

But that’s not how you should do it.

So far I have 120 or so of them in there and it works PERFECTLY!

You’re entering all of them by hand. That’s insane. Let’s say you’ve messed the lbs up of Pikachu, or you want to add more Pokemon later. You will always have to edit your program and compile it. That’s bad. It’s error prone. And the errors are hard (compiling) to fix.

Instead, have all your Pokemon in an external file. You can then read from that file into your vector easily (if you write the streaming operator):

while(pokeFile >> pokemon){
    pokedex.push_back(pokemon);
}

That’s all that’s necessary, apart from getting the Pokemon data into the external file and writing istream& operator>>(istream&, Pokemon&). Instead of a main that gets larger with every other Pokemon generation, you only have to change your Pokemon.txt file. You can even translate it to other languages (e.g. “Bisasam”, “Bisaknosp”, “Bisaflor”,…), but that’s a little bit far at this point.

Other stuff

If you don’t need the index, use a range-based for loop instead of operator[]. Also, you’ve shadowed the variable input several times. Don’t. Furthermore strive for smaller functions, your main was too large.

If you’re using C++11, use the range-based for loop more often.

Also if you don’t want to modify a variable, use const. Your std::vector<Pokemon> is a perfect candidate for this, since you don’t want to change it afterwards.

Concerning ostream& operator<<(ostream&,const Pokemon&), we’ve lost the ability to print the previous/next evolution, but that’s by design, since you need access on the whole Pokedex for that (and there are possibly several evoltions for a single Pokemon, looking at you, Eevee). That’s something you should probably think about a little bit more. You could store the ID of the previous Pokemon instead, or use an invalid ID if it does not exist. But that’s left as an exercise.

Last but not least, if you were to write a productive Pokedex, use libraries that enable you to use database(-like) functions, for example SQLite, and/or abstract the queries behind a class, like

class Pokedex {
public:
    template<class F>
    Result find_one(F && f){
        return from_it(std::find(internal_list.begin(), internal_list.end(), f));
    }
    ResultList find_all(F && f){
        ResultList results;
        std::copy_if(internal_list.begin(), internal_list.end(), ...);
        return results;
    }
};

depending how complex your queries should be.

Your “new” main

So, following the remarks above, I’ve somewhat rewritten your main. The Pokemon are gone from the cage of your code, instead they roam in some other Pokeball file.

#include <iostream>
#include <string>
#include <vector>
#include "Pokemon.h"

typedef std::vector<Pokemon> Pokedex;

std::string to_pokecase(const std::string & str){
    // excercise, should make "Bulbasaur" from "buLbaSAUR".
}

bool print_pokemon_by_name(const Pokedex & pokedex){
    //Ask for name
    std::cout << "Enter the name of the pokemon" << std::endl;
    Name input_name;
    std::cin >> input_name;

    input_name = to_pokecase(input_name);

    for (auto & thePokemon : pokedex) {
        if (input_name == thePokemon.name) {
            std::cout << "The pokemon " << thePokemon.name << " has been found!n" 
                      << thePokemon<< std::endl;
            return true;
        }
    }

    std::cerr << "Pokemon not found!" << std::endl;
    return false;
}

bool print_pokemons_by_type(const Pokedex & pokedex){
    // exercise
}

bool print_pokemon_by_number(const Pokedex & pokedex){
    // exercise
}

std::vector<Pokemon> read_pokemons_from_file(const std::string & fileName){
    // exercise
}

int main() {
    const auto pokedex = read_pokemons_from_file("Pokemon.txt");

    //Ask the user which way they would like to search
    std::cout << "Welcome to the Pokedex! We have the first " << pokedex.size() + 4 << " pokemon indexed!" << std::endl;
    std::cout << "Would you like to search by name, number, or type?" << std::endl;
    std::string input = "";
    std::cin >> input;

    //make input uppercase
    for (auto & p : input) {
        p = toupper(p);
    }

    if (input == "NAME") {
        print_pokemon_by_name(pokedex);
    }
    else if (input == "TYPE") {
        print_pokemons_by_type(pokedex);
    }
    else if (input == "NUMBER") {
        print_pokemon_by_number(pokedex);    
    } else {
        // handle invalid input
    }    
    return 0;
}

As you can see, the new main is much smaller. It can be read on a screen without scrolling at all. All functionalities that differ have been extracted into other functions, which themselves are rather small again.

There are several things that can be further refactored and rewritten, but that’s left as an exercise.

Addendum: The specification

Thanks to all comments on this review and all the Pokemon specification I’ve missed in several revisions of this review, I guess this is a good place to add two things.

First of all, before you implement your class, ask yourself more questions: Can you easily test for (pokemon) types? Do you often test for them? Should that be in its own function? Do you test for names? Do you need to normalize names? More than once? Do the Pokemon’s types need to be ordered?

Secondly, reflect the answers to those questions in your code, or, if the answer was negative, in your comments/documentation. Also, try to use higher-level code. The main above is already pretty high-level, with the effect that I can change Pokedex and Pokemon completely and never need to touch main at all.

Either way, good luck catching ’em all! May your Pokedex be complete.

Just a few things that seem like they might be open to improvement.

Evolution

Rather than having a Boolean to indicate whether a particular type is the final step in its evolution, I think I’d have an “evolvesTo” field to give the dexNum of the type to which this evolves, and (probably) use a special value such as -1 to indicate that it doesn’t evolve into anything else. I’d probably also add a similar field for “evolvesFrom”. At least in my opinion, encoding the information directly is much cleaner than depending on the order of records.

Database

Rather than having all that raw data embedded into the program, I’d at least consider moving it into an external file, which you’d then read in when you start the program. Doesn’t have to be anything complex or special–for example, I’d consider something like one Pokemon per line, with a tab between each field.

Constructor

It’s generally preferable to initialize members with a member initializer list rather than assign to them in the body of a constructor. When you do this, the compiler can sort out which is the parameter and which is the member, so you don’t need to add a 2 to each parameter name to keep them straight:

Pokemon::Pokemon(std::string name, std::string type, double weight,  double height, std::string Gender, int evoLevel, bool finalEvo, int dexNum) 
    : name(name),
      type(type),
      weight(weight),
      height(height),
      Gender(Gender),
      evoLevel(evoLevel),
      finalEvo(finalEvo),
      dexNum(dexNum),
{}

Searching

You have quite a few instances of searching through your database to find Pokemon based on various attributes. Nearly all these can be implemented with std::find_if to do the actual searching.

auto p = std::find_if(pokemon.begin(), pokemon.end(),
    [&](int dexNum) { return dexNum == input; });

if (p == pokemon.end())
    std::cout << "Sorry, I can't find that Pokemon.n";
else {
    std::cout << "The Pokemon: " << p->name << " has been found.n";
    std::cout << p->name << " is a " << p->type << " type Pokemonn";
    // ...
}

If you were dealing with a lot more data (tens of thousands or tens of millions of items) there are more efficient ways of searching data, such as building an index for each field you want to search by. If you want to experiment with one implementation of this, the Boost Multi Index library provides this type of functionality.

On the other hand, if you have to deal with that amount of data, you probably want to use some sort of database anyway (e.g., SQLite).

The biggest problem I see with your code is Eevee.

Eevee has a branched evolution.

My fix is to have a vector with the dex number of the evolutions (And if you wanted to get fancy it would be a struct with the details of the evolution. (Type,Level and dex number )

Related Solutions

Recursive to iterative using a systematic method [closed]

So, to restate the question. We have a function f, in our case fac. def fac(n): if n==0: return 1 else: return n*fac(n-1) It is implemented recursively. We want to implement a function facOpt that does the same thing but iteratively. fac is written almost in...

How can I match values in one file to ranges from another?

if the data file sizes are not huge, there is a simpler way $ join input1 input2 | awk '$5<$4 && $3<$5 {print $2, $5-$3+1}' B100002 32 B100043 15 B123465 3 This Perl code seems to solve your problem It is a common idiom: to load the entire...

Javascript difference between “=” and “===” [duplicate]

You need to use == or === for equality checking. = is the assignment operator. You can read about assignment operators here on MDN. As a quick reference as you are learning JS: = assignment operator == equal to === equal value and equal type != not equal !==...

Compiler complains about misplaced else [closed]

Your compiler complains about an misplaced else because, well, there is an else without a preceding if: // ... for (j=1; j<n-i; j++) { if(a[j]<=a[j+1]) { // ... } // END OF IF } // END OF FOR else { continue; } // ... The else in your code does not follow...

Bootstrap – custom alerts with progress bar

/* !important are just used to overide the bootstrap css in the snippet */ .alertContainer { border-radius: 0 !important; border-width: 0 !important; padding: 0 !important; height: auto !important; position: absolute !important; bottom: 15px !important; left:...

How to Garbage Collect an external Javascript load?

Yes, s.onload = null is useful and will garbage collect! As of 2019, it is not possible to explicitly or programmatically trigger garbage collection in JavaScript. That means it collects when it wants. Although there is cases where setting to null may do a GC...

Math programming with python

At first, what you are looking for is the modulo operator and the function math.floor() Modulo from wikipedia: In computing, the modulo operation finds the remainder after division of one number by another (sometimes called modulus). for example: 12%12=0...

Android slide over letters to create a word [closed]

Here some advice you can use: First for each cell you can create an object that represents the state of that cell: class Cell { char mChar; int row,column; boolean isSelected; } then you can create a 2D array of your cells Cell[][] mTable = ... For views you...

Sum two integers in Java

You reused the x and y variable names (hence the variable x is already defined in method main error), and forgot to assign the ints read from the Scanner to the x and y variables. Besides, there's no need to create two Scanner objects. public static void...

Extend three classes that implements an interface in Java

Using this simplified implementation of the library, using method() instead of M(): interface IFC { void method(); } class A implements IFC { public void method() { System.out.println("method in A"); }; } As akuzminykh mentions in their comment You'd write a...

How to set the stream content in PHPExcel? [closed]

Okey, First thing first PHPExcel_Worksheet_MemoryDrawing() can't solve your problem if you insist to use stream content and pass that to your worksheet your PDF will not render your image. But you can use `PHPExcel_Worksheet_Drawing()' if you want to render...

How to remove all files from a directory?

Linux does not use extensions. It is up to the creator of the file to decide whether the name should have an extension. Linux looks at the first few bytes to figure out what kind of file it is dealing with. To remove all non-hidden files* in a directory use: rm...

Hacker used picture upload to get PHP code into my site

Client side validation The validation code you have provided is in JavaScript. That suggests it is code that you use to do the validation on the client. Rule number one of securing webapps is to never trust the client. The client is under the full control of...

First Time HTML5/CSS Site

Semantically, I would suggest using HTML5 elements more. For example, instead of... <div id="header"> <div id="logo"></div> </div> Use instead: (the ID can stay if you want it to) <header> <div id="logo"></div>...

How classes work in a CSS file? [closed]

In your first example: .container.inner_content.text_section Match any element that has all three classes .container.inner_content.text_section { color: red; } <div class="container inner_content">one class</div> <div class="container...