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

Setting Pokemon stats

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

Problem

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.

Solution

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:

  • For base stats, you want to use Composition over inheritance.



  • For IVs, you want to use a namedtuple, or just a tuple.



  • For EVs, you want to have a user defined type.



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.

Code Snippets

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)
class Tackle(object):
    name = 'tackle'
    damage = 70

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

Context

StackExchange Code Review Q#155817, answer score: 3

Revisions (0)

No revisions yet.