X-Git-Url: https://git.yukkurigames.com/?p=python-bulletml.git;a=blobdiff_plain;f=bulletml%2Fimpl.py;h=938e9e23ad85567a7a38c5a55d385af6cf5ba70f;hp=26b95804230b9f76877f5d41bfdf5ed841b1443f;hb=5da55ac7eeae21ee0ca3149bed99c95383397f05;hpb=16023be19df43f8b8acc4671e0fabdeffb0e720a diff --git a/bulletml/impl.py b/bulletml/impl.py index 26b9580..938e9e2 100644 --- a/bulletml/impl.py +++ b/bulletml/impl.py @@ -6,15 +6,22 @@ import math from bulletml import parser -# TODO(jfw): This is very non-Pythonic, it's pretty much just the -# BulletML reference ActionImpl translated to Python. - PI_2 = math.pi * 2 __all__ = ["Action", "Bullet"] class Action(object): - """Running action implementation.""" + """Running action implementation. + + To implement new actions, + + - Add a new element/class pair to parser.ActionDef.CONSTRUCTORS. + It should support FromXML, __getstate__, and __setstate__. + - Subclass impl.Action and override the 'handle' method to handle + your custom action type. + - Pass the impl.Bullet constructor your Action subclass when + creating your root Bullet. + """ def __init__(self, owner, parent, actions, params, rank, repeat=1): self.actions = actions @@ -61,9 +68,10 @@ class Action(object): self.previous_fire_direction = other.previous_fire_direction self.previous_fire_speed = other.previous_fire_speed - def step(self, owner, rank): + def step(self, owner, created, sin=math.sin, cos=math.cos): """Advance by one frame.""" - created = [] + s_params = self.params + rank = owner.rank if self.speed_frames > 0: self.speed_frames -= 1 @@ -86,11 +94,11 @@ class Action(object): owner.my += self.my if self.pc is None: - return created + return if self.wait_frames > 0: self.wait_frames -= 1 - return created + return while True: self.pc += 1 @@ -111,21 +119,22 @@ class Action(object): action = self.actions[self.pc] if isinstance(action, parser.Repeat): - repeat, (actions, params) = action(self.params, rank) - child = Action(owner, self, actions, params, rank, repeat) + repeat, (actions, params) = action(s_params, rank) + child = self.__class__( + owner, self, actions, params, rank, repeat) owner.replace(self, child) - created.extend(child.step(owner, rank)) + child.step(owner, created, sin, cos) break elif isinstance(action, (parser.ActionDef, parser.ActionRef)): - actions, params = action(self.params, rank) - child = Action(owner, self, actions, params, rank) + actions, params = action(s_params, rank) + child = self.__class__(owner, self, actions, params, rank) owner.replace(self, child) - created.extend(child.step(owner, rank)) + child.step(owner, created, sin, cos) break elif isinstance(action, (parser.FireDef, parser.FireRef)): - direction, speed, actions, offset = action(self.params, rank) + direction, speed, actions, offset = action(s_params, rank) if direction: direction, type = direction if type == "aim" or type is None: @@ -145,9 +154,9 @@ class Action(object): elif type == "relative": # The reference Noiz implementation uses # prvFireSpeed here, but the standard is - # pretty clear -- "0 means that the direction - # of this fire and the direction of the bullet - # are the same". + # pretty clear -- "In case of the type is + # "relative", ... the speed is relative to the + # speed of this bullet." speed += owner.speed else: speed = 1 @@ -155,24 +164,29 @@ class Action(object): x, y = owner.x, owner.y if offset: - off_x, off_y = offset(self.params, rank) + off_x, off_y = offset(s_params, rank) if offset.type == "relative": - sin = math.sin(direction) - cos = math.cos(direction) - x += cos * off_x + sin * off_y - y += sin * off_x - cos * off_y + s = sin(direction) + c = cos(direction) + x += c * off_x + s * off_y + y += s * off_x - c * off_y else: x += off_x y += off_y - bullet = Bullet( - x, y, direction, speed, owner.target, actions, self, rank) + bullet = owner.__class__( + x, y, direction, speed, owner.target, actions, rank) created.append(bullet) elif isinstance(action, parser.ChangeSpeed): - frames, (speed, type) = action(self.params, rank) + frames, (speed, type) = action(s_params, rank) self.speed_frames = frames - if type == "sequence": + if frames <= 0: + if type == "absolute": + owner.speed = speed + elif type == "relative": + owner.speed += speed + elif type == "sequence": self.speed = speed elif type == "relative": self.speed = speed / frames @@ -180,36 +194,36 @@ class Action(object): self.speed = (speed - owner.speed) / frames elif isinstance(action, parser.ChangeDirection): - frames, (direction, type) = action(self.params, rank) + frames, (direction, type) = action(s_params, rank) self.direction_frames = frames self.aiming = False if type == "sequence": self.direction = direction else: if type == "absolute": - self.direction = ( - direction - owner.direction) % PI_2 - elif type == "relative": - self.direction = direction - else: + direction -= owner.direction + elif type != "relative": # aim or default self.aiming = True - self.direction = ( - direction - + owner.aim - - owner.direction) % PI_2 + direction += owner.aim - owner.direction - if self.direction > math.pi: - self.direction -= PI_2 - if self.direction < -math.pi: - self.direction += PI_2 - self.direction /= self.direction_frames + # Normalize to [-pi, pi). + direction = (direction + math.pi) % PI_2 - math.pi + if frames <= 0: + owner.direction += direction + else: + self.direction = direction / frames elif isinstance(action, parser.Accel): - frames, horizontal, vertical = action(self.params, rank) + frames, horizontal, vertical = action(s_params, rank) self.accel_frames = frames if horizontal: mx, type = horizontal - if type == "sequence": + if frames <= 0: + if type == "absolute": + owner.mx = mx + elif type == "relative": + owner.mx += mx + elif type == "sequence": self.mx = mx elif type == "absolute": self.mx = (mx - owner.mx) / frames @@ -217,22 +231,41 @@ class Action(object): self.mx = mx / frames if vertical: my, type = vertical - if type == "sequence": + if frames <= 0: + if type == "absolute": + owner.my = my + elif type == "relative": + owner.my += my + elif type == "sequence": self.my = my elif type == "absolute": self.my = (my - owner.my) / frames elif type == "relative": self.my = my / frames + elif isinstance(action, parser.Tag): + owner.tags.add(action.tag) + + elif isinstance(action, parser.Untag): + try: + owner.tags.remove(action.tag) + except KeyError: + pass + elif isinstance(action, parser.Wait): - self.wait_frames = action(self.params, rank) + self.wait_frames = action(s_params, rank) break elif isinstance(action, parser.Vanish): owner.vanish() break - return created + else: + self.handle(action, owner, created, sin, cos) + + def handle(self, action, owner, created, sin, cos): + """Override in subclasses for new action types.""" + raise NotImplementedError(action.__class__.__name__) class Bullet(object): """Simple bullet implementation. @@ -246,6 +279,7 @@ class Bullet(object): target - object with .x and .y fields for "aim" directions vanished - set to true by a action rank - game difficulty, 0 to 1, default 0.5 + tags - string tags set by the running actions Contructor Arguments: x, y, direction, speed, target, rank - same as the attributes @@ -256,7 +290,7 @@ class Bullet(object): """ def __init__(self, x=0, y=0, direction=0, speed=0, target=None, - actions=(), parent=None, rank=0.5): + actions=(), rank=0.5, Action=Action): self.x = self.px = x self.y = self.py = y self.mx = 0 @@ -266,6 +300,7 @@ class Bullet(object): self.vanished = False self.target = target self.rank = rank + self.tags = set() # New bullets reset the parent hierarchy. self._actions = [Action(self, None, action, params, rank) for action, params in actions] @@ -322,7 +357,7 @@ class Bullet(object): else: self._actions[idx] = new - def step(self): + def step(self, sin=math.sin, cos=math.cos): """Advance by one frame. This updates the position and velocity, and may also set the @@ -333,11 +368,11 @@ class Bullet(object): created = [] for action in self._actions: - created.extend(action.step(self, self.rank)) + action.step(self, created, sin, cos) self.px = self.x self.py = self.y - self.x += self.mx + math.sin(self.direction) * self.speed - self.y += self.my - math.cos(self.direction) * self.speed + self.x += self.mx + sin(self.direction) * self.speed + self.y += self.my - cos(self.direction) * self.speed return created