5
\$\begingroup\$

I set the stats (.ie. name, attack, defense, HP) in the __init__ method. However, I have chosen to build the self.attacks dictionary with a separate method.

class Pokemon(object):

     def __init__(self, name, attack, defense, HP):
                self.name = name
                self.attack = attack
                self.defense = defense
                self.HP = HP
                self.attacks = {}

     def Moves(self,move_1,damage_1,move_2,damage_2):
                self.attacks[move_1] = damage_1
                self.attacks[move_2] = damage_2

pikachu = Pokemon('Pikachu',40,10,50)
pidgey = Pokemon('Pidgey',50,30,60)

pikachu.Moves('tackle',50,'thunderbolt',70)
pidgey.Moves('gust',80,'tackle',50)

The code works as expected but I'm not sure if this proper use of classes. I believe it is and have seen some examples do similar things. I just wanted to check if I am headed in the right direction.

Edit: The reason I would like to know is because the Pokemon will be getting many more attributes and I need a way to assign those attributes in groups to avoid assigning fifty or so variables every time I create a new instance of Pokemon.

\$\endgroup\$
9
  • 1
    \$\begingroup\$ Will the Pokemon eventually have multiple attacks and defenses? If so, the class structure would be a little more complicated, with each attack/defense being added dynamically as its own callable method. \$\endgroup\$
    – Ryan Mills
    Feb 20, 2017 at 7:44
  • 3
    \$\begingroup\$ Your indentation is misleading. Could you please fix it ? As it is, your code is broken hence is off-topic here. Thanks \$\endgroup\$ Feb 20, 2017 at 7:45
  • 2
    \$\begingroup\$ Was there a particular reason to avoid the more straightforward pikachu = Pokemon('Pikachu', 40, 10, 50, {'tackle': 50, 'thunderbolt': 70})? Without knowing why you did it it's hard to say whether it's a good idea, but if you didn't have a reason it's probably not. \$\endgroup\$
    – jonrsharpe
    Feb 20, 2017 at 7:50
  • 2
    \$\begingroup\$ In that case your "add two moves by passing individual attributes of each" is going to scale really poorly. The OOP way to do it might be to pass in ..., {'tackle': Move(40), 'thunderbolt': Move(70)} and allow for extension and specialisation of the move objects later. But please edit to provide more context. \$\endgroup\$
    – jonrsharpe
    Feb 20, 2017 at 13:17
  • 1
    \$\begingroup\$ Yes, or Move('tackle', 40) to keep the name together with it. \$\endgroup\$
    – jonrsharpe
    Feb 20, 2017 at 17:43

1 Answer 1

3
\$\begingroup\$

You need to step away from programming and design your classes. Pokemon stats aren't just one number. And so you should design for this.

First things first, Pokemon have multiple stats, the main ones are:

  • Each Pokemon of the same breed have the same base stats. Say you have two Pikachu, their base stats are exactly the same. And so you should define this on a Pokemon's class.

  • They have 'genes', better know as IVs - individual values. These change for each and every Pokemon, two Pikachu are unlikely to have the same IVs. They also don't change, ever. And so it'd be a good idea to make these immutable.

  • They learn things by fighting, and gain EVs - effort values. These aren't immutable all the time, but have a limit, and become immutable at that point.

  • Pokemon have a nature, which also change a Pokemon's stats.

And so, if you want the program to be correct, you need to correctly handle the above. But you need to handle all of them differently:

And so you don't need to take 50 arguments in the creation of a Pokemon. You need 4 arguments, and if you want to pass the moves, 5. To start you off you'd need something like:

from collections import namedtuple

Stats = namedtuple('Stats', 'hp attack defense sp_atk sp_def speed')


# TODO: add limits on input
class EV(object):
    def __init__(self, hp, attack, defense, sp_atk, sp_def, speed):
        self.hp = hp
        self.attack = attack
        self.defense = defense
        self.sp_atk = sp_atk
        self.sp_def = sp_def
        self.speed = speed


class Pokemon(object):
    _base_stats = Stats(0, 0, 0, 0, 0, 0)
    def __init__(self, iv, ev, nature, level):
        # TODO: check input is within correct bounds
        self._iv = Stats(*iv)
        self._ev = EV(*ev)
        self._nature = nature
        self.level = level
        # TODO: take more data, such as the pokemon's name.

    @property
    def max_hp(self):
        base = self._base_stats.hp
        iv = self._iv.hp
        ev = self._ev.hp
        level = self.level
        return int(((2 * base + iv + int(ev/4)) * level) / 100) + level + 10

    def _calculate_stat(self, stat):
        base = getattr(self._base_stats, stat)
        iv = getattr(self._iv, stat)
        ev = getattr(self._ev, stat)
        level = self.level
        # TODO: correct nature
        nature = 1
        return int((int(((2 * base + iv + int(ev/4)) * level) / 100) + 5) * nature)

    @property
    def attack(self):
        return self._calculate_stat('attack')

    @property
    def defense(self):
        return self._calculate_stat('defense')


class Pikachu(object):
    _base_stats = Stats(35, 55, 40, 50, 50, 90)


def pokemon(base, *args, **kwargs):
    return type('Pokemon', (base, Pokemon), dict())(*args, **kwargs)


pikachu = pokemon(Pikachu, [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], None, 100)
print(pikachu.max_hp)
print(pikachu.attack)

Even if you don't use the previous, then I'd still recommend that you move the data into smaller more logical datatypes. But either way you should want to then change the way you're storing your moves. Each move should be an object, it has a name, damage, type, miss chance, special animation. You need to contain all of this in each move. But you also need to have an instance of each move, so that every move in the game doesn't share the same PP. And so to create each move, you may want to do the same as above with the Pikachu class. After this, it'd make more sense to store these instances in a list rather than a dictionary.

And so you should instead have something like:

class Tackle(object):
    name = 'tackle'
    damage = 70

pikachu = pokemon(..., [Tackle()])

Going further, if this is too static, then you can use type to define these classes. And allow you to off-load storing the data in a database.

You should want to take the moves in the constructor of the class as it normally makes saving and loading game state easier. If you did want to keep your Moves function then you should change it to add_move and only take one move, not two.

\$\endgroup\$
1
  • \$\begingroup\$ @Peulonrayz Thank you so much!! This answers my question perfectly! I think I was trying to start with small stuff and work my way up and should have been doing the opposite. Thanks a million!!!! Also, thank you for such a detailed response. \$\endgroup\$
    – Cody Pace
    Feb 21, 2017 at 12:51

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.