Reputation: 21923
I am extending an existing PlayerClassFromGameEngine
class to allow custom effects to take effect only for a certain duration.
Example:
Using the original class, I would freeze a player by saying player.move_type = MoveTypes.Freeze
and then unfreeze him by saying player.move_type = MoveTypes.Normal
.
Now I'd like to extend the class so I can use a function call instead: player.freeze(5)
, to freeze the player for five seconds.
I obviously need two functions, the effect function and an undo function, f.e. freeze()
and unfreeze()
.
Here's my current class, that works fine:
class MyPlayer(PlayerClassFromGameEngine):
def __init__(self, index):
super().__init__(index) # Let the original game engine handle this
self.effects = defaultdict(set)
def freeze(self, duration):
self.move_type = MoveType.FREEZE # Move type comes from the super class
thread = threading.Thread(target=self._unfreeze)
thread.args = (duration, thread)
self.effects['freeze'].add(thread)
thread.start()
def _unfreeze(self, duration, thread):
time.sleep(duration)
self.effects['freeze'].remove(thread)
if not self.effects['freeze']: # No more freeze effects
self.move_type = MoveType.NORMAL
As you see, only one effect takes more than 10 lines of code, having 20 of these would be awful, since they all work the exact same way, just with different key ('freeze'
, burn
, etc.) and some call a function instead of accessing move_type
property.
I've got basically zero idea where to start, maybe descriptors and decorators somehow, but can somebody give me some advice, or better yet a working solution?
EDIT:
Here's what I came up with after Martijn's suggestion, but it doesn't work since I can't access the player inside the Effect
class
from collections import defaultdict
from threading import Thread
from time import sleep
class Effect(object):
def __init__(self, f, undo_f=None):
self.f = f
self.undo_f = undo_f
self._thread = None
def __call__(self, duration):
self._thread = Thread(target=self._execute, args=(duration, ))
self._thread.start()
def _execute(self, duration):
self.f()
sleep(duration)
self.undo_f()
def undo(self, undo_f):
return type(self)(self.f, undo_f)
class Player:
def __init__(self, index):
self.index = index
self._effects = defaultdict(set)
@Effect
def freeze(self):
print('FROZEN')
@freeze.undo
def freeze(self):
print('UNFROZEN')
p = Player(1)
p.freeze(3)
What I think I need is to somehow access the player inside of the Effect
class, since I can't call self.f(player)
or self.undo_f(player)
in the Effect._execute
method, nor can I access player's effects
dictionary.
I figured I won't be needing the key
parameter anywhere, since I can just generate a random number for every effect (an unique one ofc.), since it's not shown to anyone anyways.
Upvotes: 0
Views: 92
Reputation: 6395
This would be a way to go:
from functools import partial
import time
import threading
class aftereffect(object):
def __init__(self, func):
self._func = func
self._aftereffect = lambda instance: None
def aftereffect(self, func):
self._aftereffect = func
return self
def __get__(self, instance, cls):
# this is the descriptor protocol, and
# instance is the actual object
def delayed_effect(timeout):
time.sleep(timeout)
self._aftereffect(instance)
def caller(*args, **kwargs):
timeout = kwargs.pop("_timeout", 1.)
t = threading.Thread(target=partial(delayed_effect, timeout=timeout))
t.start()
return self._func(*args, **kwargs)
return caller.__get__(instance, cls)
class Thing(object):
@aftereffect
def something(self):
print "something", self
@something.aftereffect
def something(self):
print "after_something", self
t = Thing()
t.something()
t.something(_timeout=5)
Upvotes: 2