Pythonic way to have code-reuse in game Entity classes - python

I'm starting to define my Entity classes for a game I am writing. However, I want a lot of code re-use. I want to define classes for different functionality, and then have classes which 'have' some of these classes' functionality.
For example:
class Collidable:
def handle_collision(other, incident_vector):
pass
def __init__(self, shape):
self.shape = shape
class Movable:
def update_position(self):
self.velocity += self.acceleration
self.position += self.velocity
def __init__(self, velocity, acceleration):
self.velocity, self.acceleration = velocity, acceleration
class Drawable:
def draw(self):
pass
def __init__(self, image):
self.image = image
class Controllable:
def key_down(self, key):
pass
def __init__(self):
pass
Then have a Player class which is Collidable, Movable, Drawable, Controllable, an Invisible Barrier which is only Collidable, a Background which is only Drawable, etc. I've heard of many different ways of connecting multiple classes, (such as via Composition, (Multiple) Inheritance, Interfaces, etc), but I don't know which is most appropriate and/or pythonic for this situation.
Mix-ins (special case of Multiple Inheritance) looks to be what I'm looking for (since a Player should BE a Collidable, a Movable, a Drawable, and a Controllable), but in trying this out, I'm finding difficulty in using super to pass the right arguments to the right init functions.
Edit:
I'm using python 3.2.

Mixins are the way to go, but you don't want to call __init__ on them:
class CollidableMixin(object):
#...
def init_collidable(self, shape):
self.shape = shape
class MovableMixin(object):
#...
def init_movable(self, velocity, acceleration):
self.velocity, self.acceleration = velocity, acceleration
class DrawableMixin(object):
#...
def init_drawable(self, image):
self.image = image
As I see it, you don't need a separate class for Controllable because it just defines an interface which the inheriting class should have. While you do that a lot in statically typed languages like Java, you don't need that in Python. Instead, you just define a key_down method and be done with it. This is called duck typing.
In an example implementation, this will then look like this:
class Player(CollidableMixin, DrawableMixin, MovableMixin):
def __init__(self):
self.init_collidable(...)
self.init_drawable(...)
self.init_movable(...)
def key_down(self, key):
# ...
objects = []
objects.append(Player())
# ... add some more objects. Later we iterate through that collection,
# not knowing which of them is a player:
for o in objects:
try:
o.key_down(...)
except AttributeError:
pass

Here is a simple way to implement the inheritance using super(). For this to work you will always need to create instances of Player (and other classes that inherit from your ***able classes) with keyword arguments. Each base class will strip whatever keyword arguments it is using from kwargs and pass the rest on to the next __init__() in the mro, for example:
class Collidable(object):
def handle_collision(other, incident_vector):
pass
def __init__(self, shape, **kwargs):
self.shape = shape
super(Collidable, self).__init__(**kwargs)
class Movable(object):
def update_position(self):
self.velocity += self.acceleration
self.position += self.velocity
def __init__(self, velocity, acceleration, **kwargs):
self.velocity, self.acceleration = velocity, acceleration
super(Movable, self).__init__(**kwargs)
class Drawable(object):
def draw(self):
pass
def __init__(self, image, **kwargs):
self.image = image
super(Drawable, self).__init__(**kwargs)
class Controllable(object):
def key_down(self, key):
pass
def __init__(self, **kwargs):
super(Controllable, self).__init__(**kwargs)
Then you could define your Player class:
class Player(Collidable, Movable, Drawable, Controllable):
pass
And use it like this:
>>> p = Player(shape='circle', velocity=0.0, acceleration=1.0, image='player.png')
>>> p.shape
'circle'
>>> p.velocity
0.0
>>> p.acceleration
1.0
If you need additional instance variables for the Player class you would define an __init__() similar to the other classes, for example:
class Player(Collidable, Movable, Drawable, Controllable):
def __init__(name, **kwargs):
self.name = name
super(Player, self).__init__(**kwargs)

Related

Attempting to connect classes, where one class has a list the other class

I'm trying to have two classes where one class has a list of the other class. Like a Bag class that has a list of what Balls are in it, where Ball is a class.
class Bag:
Inside = []
def CreateBag(self):
self.Inside.append(Ball("Blue", "Small"))
class Ball:
def __init__(self, Color, Size):
self.Color = Color
self.Size = Size
B = Bag()
B.CreateBag()
print(B.Inside)
When I do print(B.Inside) I get [<__main__.Ball object at 0x000002B7EFF8BA20>]. I've tried B.Inside.Color, but that gives me an error, making me think I have set this up wrong.
You have the basics, although try to follow PEP 8 (Style Guide for Python Code). To display objects beyond the default "<object # address>" you have to define a __repr__ (debug representation) of the object, and perhaps a __str__ (print representation):
class Bag:
def __init__(self):
self.inside = []
def add(self, item):
self.inside.append(item)
def __repr__(self):
return f'Bag({self.inside})'
def __getitem__(self,index):
return self.inside[index]
def __setitem__(self,index,value):
self.inside[index] = value
class Ball:
def __init__(self, color, size):
self.color = color
self.size = size
def __repr__(self):
return f'Ball(color={self.color!r}, size={self.size!r})'
def __str__(self):
return f'{self.size} {self.color} Ball'
bag = Bag()
bag.add(Ball('Blue','Small'))
bag.add(Ball('Red','Big'))
print(bag) # uses Bag.__repr__ since Bag.__str__ isn't defined
print(bag[0]) # uses Bag.__getitem__ and Ball.__str__
bag[0] = Ball('Green','Medium') # uses Bag.__setitem__
print(bag)
print(bag[0].color)
Bag([Ball(color='Blue', size='Small'), Ball(color='Red', size='Big')])
Small Blue Ball
Bag([Ball(color='Green', size='Medium'), Ball(color='Red', size='Big')])
Green

Correct way of passing a self variable as argument to a mixin parent method

I have to model a warrior and the different kinds of attacks he can perform. The idea is to use mixins to contain the attack logic. I have my classes defined in the following way:
class Warrior:
def __init__(self, energy):
self.energy = energy
class TemplarKnight(Warrior, HandToHandCombatMixin):
pass
class CombatMixin:
def __init__(self):
self.attacks_cost = {}
def attack(self, attacker, attack_cost):
if attacker.energy < attack_cost:
print('Not enough energy to attack')
else:
attacker.energy -= attack_cost
print('Attack!')
class HandToHandCombatMixin(CombatMixin):
def __init__(self):
super().__init__()
self.attacks_cost['sword_spin'] = 10
def sword_spin(self, attacker):
return self.attack(attacker, self.attacks_cost['sword_spin'])
But the problem comes when I try to test this setup. When I do
class TestTemplarKnight(unittest.TestCase):
def setUp(self):
self.templar = TemplarKnight(energy=100)
def test_templar_knight_can_sword_spin(self):
self.templar.sword_spin(self.warrior)
self.assertEquals(self.templar.energy, 90)
I get
def sword_spin(self, attacker):
return self.attack(
> attacker, self.attacks_cost['sword_spin'])
E AttributeError: 'TemplarKnight' object has no attribute 'attacks_cost'
It seems that Python thinks that the parameter self.attacks_cost (when calling self.attack() inside the sword_spin() method of the HandToHandCombatMixin class) belongs to the TemplarKnight class instead of the HandToHandCombatMixin.
How should I have written this code to make Python look for self.attacks_cost inside HandToHandCombatMixin?
To use super correctly, all the classes involved need to use it. Right now, Warrior.__init__ is called first, but it doesn't use super, so HandToHandCombatMixin.__init__ is never called.
Make the following additions:
class Warrior:
def __init__(self, energy, **kwargs):
super().__init__(**kwargs)
self.energy = energy
class TemplarKnight(Warrior, HandToHandCombatMixin):
pass
class CombatMixin:
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.attacks_cost = {}
def attack(self, attacker, attack_cost):
if attacker.energy < attack_cost:
print('Not enough energy to attack')
else:
attacker.energy -= attack_cost
print('Attack!')
class HandToHandCombatMixin(CombatMixin):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.attacks_cost['sword_spin'] = 10
def sword_spin(self, attacker):
return self.attack(attacker, self.attacks_cost['sword_spin'])
Now when you instantiate TemplarKnight, you'll guarantee that all the __init__ methods are called, and in the correct order. Eventually, once of the calls to super() will cause object.__init__ to be called, at which point the chain finally ends. If you are correctly handling the keyword arguments, **kwargs will be empty by the time that happens.

How to Create Instance Attributes By Lopping Over __init__ Arguments?

I was wondering if there was a way to generate class attributes by looping over the arguments of the init method without explicitly referring to a list containing all the arguments of the init method?
In the example below could I loop over hp, image, speed, x, y to create the self arguments ?
class Character(pygame.sprite.Sprite):
def __init__(self, hp, image, speed, x, y):
# Call the parent class (Sprite) constructor
super(Character, self).__init__()
self.image = image
self.rect = self.image.get_rect().move(x, y) #initial placement
self.speed = speed
self.hp = hp
For example with a loop that would look like that:
class Character(pygame.sprite.Sprite):
def __init__(self, hp, image, speed, x, y):
# Call the parent class (Sprite) constructor
super(Character, self).__init__()
for arg in arguments:
self.arg = arg
I am not quite sure how to get the "arguments" to refer to hp, image, speed, x and y ? Or am I stuck with using a list like below ?
class Character(pygame.sprite.Sprite):
def __init__(self, hp, image, speed, x, y):
# Call the parent class (Sprite) constructor
super(Character, self).__init__()
for arg in [self, hp, image, speed, x, y]:
self.arg = arg
You can use keyword arguments (kwargs) and define a list of attributes your instances require and you therefore expect in your __init__(). Then you can loop over them and assign your attributes via setattr:
class Character(pygame.sprite.Sprite):
ATTRS = ('hp', 'image', 'speed', 'x', 'y')
def __init__(self, **kwargs):
# Call the parent class (Sprite) constructor
super(Character, self).__init__()
for attr in self.ATTRS:
setattr(self, attr, kwargs.get(attr)) # sets to None if missing
set_rect(...) # do your processing of x, y
Or, even simpler, just turning all kwargs into instance attributes:
class Character(pygame.sprite.Sprite):
def __init__(self, **kwargs):
super(Character, self).__init__()
for key, value in kwargs.items():
setattr(self, key, value)
I would, however, advise you against such trickery. It might make your __init__ shorter, but it will hurt your productivity later, as most IDE's (Eclipse-PyDev, PyCharm, etc.) code completion/resolution features will not detect such dynamically set attributes on existing instances and also not suggest the required arguments when calling the constructor, which is especially annoying for other coders using your class.
It also does not make your code more readable. Imagine inheriting a code base that uses lots of such constructions. You will learn to like a clean explicit version like the first one you are suggesting in your question. A compromise to shorten your constructor would be using multiple assignment
self.image, self.speed, self.hp = image, speed, hp
self.rect = self.image.get_rect().move(x, y)
You can use a argument list, but I'm not sure if this is what you want...
class Test(object):
def __init__(self, *args):
for arg in args:
print(arg)
t = Test("one", 2, "three")

Python: Instance class mutation

I have two classes:
class Egg:
def __init__(self):
self._color = 'White'
class Larvae(Egg):
def __init__(self):
super().__init__()
self._color = 'Transparente'
To illustrate ...
So, in my code I have an Egg instance. When it's time I would like transform them into Larvae instances. I can create a Larvae instance and hand copy informations about a "previous" Egg instance. What does Python offer for something like that? For "mute" an instance in instance of subclass of its class?
Edit: As commented below, OOP in this question is not good way to do the wanted behavior. So, keep this in mind when reading answer
How about using a state-based approach?
class Ant:
def __init__(self, state='egg'):
self.state = state
#property
def color(self):
return {
'egg': 'Transparent',
'larvae': 'White,'
}[self.state]
def hatch(self):
if self.state == 'egg':
self.state = 'larvae'
else:
raise Exception('Cannot hatch if not an egg!')
This is possible, but not a great idea.
It is possible to change the type of an object after constructing it, but, well, it's just not robust. When you change the type of an object, its __init__() does not run. Any attributes left over from the old type are still there, and if they clash with attributes belonging to the new type, you probably have a nasty mess to deal with. If you're really sure this is the right approach, you can do it by assigning to the __class__ attribute (e.g. spam.__class__ = SomeClass). I strongly advise against this, however.
Instead, I would recommend factoring out the data you want to preserve into a "state" attribute, which you then transfer from one type to another. For example:
class Egg(object):
def __init__(self, state):
# Other egg-related stuff here...
self.state = state
def grow_up(self):
return Larva(self.state)
class Larva(object):
def __init__(self, state):
# Other larva-related stuff here...
self.state = state
def grow_up(self):
return Pupa(self.state)
class Pupa(object):
# and so on...
spam = Egg([1, 2, 3])
spam = spam.grow_up()
print(spam.state) # [1, 2, 3]
This answer has little to do with your request, but I find that it's worth to show that your OOP is a little bit counterintuitive. The whole point of OOP is that you create an intuitive inheritance hierarchy. There is little point in OO if your low level classes are supposed to be aware of higher level ones. In your use-case the Egg must be aware of the Larvae in order to transform, but that makes little sense. A parent class should not reference its child-classes.
class BaseInsect(object):
"""
Some stuff that all insects share at any stage
"""
def __init__(self, colour, *args, **kwargs):
self.colour = colour
...
class ImmatureAnt(BaseInsect):
...
class Egg(ImmatureAnt):
"""
Some egg-specific stuff
"""
def __init__(self, *args, **kwargs):
super(Egg, self).__init__("white")
...
class Larvae(ImmatureAnt):
def __init__(self, *args, **kwargs):
super(Larvae, self).__init__("transparent")
...
#staticmethod
def from_egg(egg, *args, **kwargs):
# make a larvae out of an egg
...
class BaseAdultAnt(BaseInsect):
"""
Some stuff all adult ants have
"""
...
class WorkerAnt(BaseAdultAnt):
...
class BaseReproducingAnt(BaseAdultAnt):
...
class Male(BaseReproducingAnt):
...
class Queen(BaseReproducingAnt):
...
Back to your question.
You've already been told that you can pass an Egg instance to the Larvae constructor. That will be a beautiful way. To make this answer a little bit less off-topic I'm giving an alternative solution. You might want to use several functions.
def egg_to_larvae(egg, *args, **kwargs):
"""
:param egg: an Egg instance
:type egg: Egg
"""
# do some stuff to get all the info needed to create a larvae...
return larvae
def larvae_to_pupae(larvae, *args, **kwargs):
...
return pupae
You get the idea
Just use a common base class instead of trying to inherit Larvae from an Egg:
class AntBase:
def __init__(self, color):
self._color = color
class Egg(AntBase):
def __init__(self, color='White'):
super().__init__(color)
class Larvae(AntBase):
def __init__(self, color='Transparente'):
super().__init__(color)
While Egg and Larvae are similar (both related to ants), Larvae is not an Egg, nor is an Egg a Larvae.
They are both "ant-things" though, thus we create a common AntBase class for all your ant things.
To convert an Egg into a Larvae, you can have a converter classmethod:
class AntBase:
def __init__(self, color):
self._color = color
#classmethod
def transform_from(cls, instance):
return cls(instance._color)
And now you can do:
egg0 = Egg()
larvae0 = Larvae.transform_from(egg0)
You can even have custom behaviour for each class:
class Larvae(AntBase):
#classmethod
def transform_from(cls, instance):
if isinstance(instance, AntMale):
raise ValueError("You can't have Larvae from AntMale")
return super().transform_from(instance)

Python OOP - Class relationships

Assuming I have a system of three Classes.
The GameClass creates instances of both other classes upon initialization.
class FieldClass:
def __init__( self ):
return
def AnswerAQuestion( self ):
return 42
class PlayerClass:
def __init__( self ):
return
def DoMagicHere( self ):
# Access "AnswerAQuestion" located in the "FieldClass" instance in "GameClass"
pass
class GameClass:
def __init__( self ):
self.Field = FieldClass()
self.Player = PlayerClass()
What would be the best way of accessing AnswerAQuestion() located in FieldClass from within the instance of PlayerClass?
Do I have to pass a reference to the FieldClass instance to PlayerClass?
Is there another, better way of solving this? Doing the above would make me have to include an additional variable in PlayerClass to hold the FieldClass instance.
Is there a completely different way of managing class relationships in Python?
I would go with Dependency Injection: instantiate a GameClass with the required FieldClass and PlayerClass in the constructor call etc. (i.e. instead of creating the dependent objects from within GameClass as you are doing at the moment).
class GameClass:
def __init__( self, fc, pc ):
self.Field = fc
self.Player = pc
class PlayerClass:
def __init__( self, fc ):
self.fc = fc
def DoMagicHere( self ):
# use self.fc
pass
fc=FieldClass()
pc=PlayerClass(fc)
gc=GameClass(fc, pc)
With DI, you can easily have access to the members you require once the setup phase is completed.
To better understand your class relationships you have to tell us more about your project and what you are trying to accomplish.
One way to reference the instance of FieldClass from PlayerClass in your example is to pass the GameClass instance to the PlayerClass instance:
class PlayerClass:
def __init__(self, game):
self.game = game
def DoMagicHere(self):
self.game.Field.AnswerAQuestion()
# ...
class GameClass:
def __init__( self ):
self.Field = FieldClass()
self.Player = PlayerClass(self)
Another way is to pass the field variable
class PlayerClass:
def __init__(self, field):
self.field = field
def DoMagicHere(self):
self.field.AnswerAQuestion()
# ...
class GameClass:
def __init__( self ):
self.Field = FieldClass()
self.Player = PlayerClass(self.Field)
As a side note: you use a peculiar naming scheme. I suggest this:
class Game:
def __init__(self):
self.field = Field()
self.player = Player(...)
I suggest you read the Python Style Guide before acquiring bad habits.
You could provide the context to the child objects, i.e.:
class FieldClass:
def __init__(self,game):
self.game = game
class PlayerClass:
def __init__(self,game):
self.game = game
def DoMagicHere(self):
self.game.Field.AnswerAQuestion()
class GameClass:
def __init__(self):
self.Field = FieldClass(self)
self.Player = PlayerClass(self)
Since the objects know their context they can reach into it and access other objects in the same context.
I would do something like the following, where you pass Game in as a back-reference, allowing you access to the other associated classes. In this way, you could optionally give that player instance a direct relationship to the field.
class FieldClass(object):
def __init__(self, game):
self.Game = game
def AnswerAQuestion(self):
return 42
class PlayerClass(object):
def __init__(self, game):
self.Game = game
self.Field = game.Field # Optional
def DoMagicHere(self):
self.Game.Field.AnswerAQuestion()
self.Field.AnswerAQuestion() # Optional
class GameClass(object):
def __init__(self):
self.Field = FieldClass(self)
self.Player = PlayerClass(self)
One side note is that it's good practice now to have all your classes inherit from object rather than to have them standing alone.

Categories

Resources