From 3d98afa00a8befa0d8d94c1df2002ea3880bcca2 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Mon, 19 Apr 2010 01:15:59 -0700 Subject: [PATCH] Major refactor. Since Python is duck-typed the parser/impl split in the Java BulletML implementation only makes the code slow and awkward. Instead, have the 'parser' change the Action attributes, which results in no real loss of generality, and much more straightforward and faster code. --- bulletml/impl.py | 182 +++------------------------------------------ bulletml/parser.py | 165 +++++++++++++++++++++++++++++++++++----- 2 files changed, 158 insertions(+), 189 deletions(-) diff --git a/bulletml/impl.py b/bulletml/impl.py index 262b7dc..ca800be 100644 --- a/bulletml/impl.py +++ b/bulletml/impl.py @@ -2,33 +2,26 @@ from __future__ import division -from math import atan2, sin, cos, pi as PI +from math import atan2, sin, cos from bulletml import parser -PI_2 = PI * 2 - __all__ = ["Action", "Bullet"] class Action(object): """Running action implementation. - To implement new actions, + To implement new actions, add a new element/class pair to + parser.ActionDef.CONSTRUCTORS. It should support FromXML, + __getstate__, and __setstate__, and 5-ary __call__: - - 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 __call__(self, owner, action, params, rank, created) - Or, for very simple actions, add it to the Action.CUSTOM - dictionary. The key should be the type of the action and the - value a 3-ary callable that recieves the Action instance, the - owner, and a list to append created bullets to. - """ + Which will be called to execute it. This function should modify + owner, action, and created in-place, and return true if action + execution should stop for this bullet this frame. - CUSTOM = {} + """ def __init__(self, owner, parent, actions, params, rank, repeat=1): self.actions = actions @@ -122,168 +115,16 @@ class Action(object): self.pc = 0 action = self.actions[self.pc] - if isinstance(action, parser.Repeat): - repeat, (actions, params) = action(s_params, rank) - child = self.__class__( - owner, self, actions, params, rank, repeat) - owner.replace(self, child) - child.step(owner, created) - break - - elif isinstance(action, (parser.ActionDef, parser.ActionRef)): + if isinstance(action, (parser.ActionDef, parser.ActionRef)): actions, params = action(s_params, rank) child = self.__class__(owner, self, actions, params, rank) owner.replace(self, child) child.step(owner, created) break - elif isinstance(action, (parser.FireDef, parser.FireRef)): - direction, speed, offset, tags, appearance, actions = action( - s_params, rank) - if direction: - direction, type = direction - if type == "aim" or type is None: - direction += owner.aim - elif type == "sequence": - direction += self.previous_fire_direction - elif type == "relative": - direction += owner.direction - else: - direction = owner.aim - self.previous_fire_direction = direction - - if speed: - speed, type = speed - if type == "sequence": - speed += self.previous_fire_speed - elif type == "relative": - # The reference Noiz implementation uses - # prvFireSpeed here, but the standard is - # 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 - self.previous_fire_speed = speed - - x, y = owner.x, owner.y - if offset: - off_x, off_y = offset(s_params, rank) - if offset.type == "relative": - 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 - - if appearance is None: - appearance = owner.appearance - bullet = owner.__class__( - x=x, y=y, direction=direction, speed=speed, - target=owner.target, actions=actions, rank=rank, - appearance=appearance, tags=tags, Action=self.__class__) - created.append(bullet) - - elif isinstance(action, parser.ChangeSpeed): - frames, (speed, type) = action(s_params, rank) - self.speed_frames = frames - 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 - else: - self.speed = (speed - owner.speed) / frames - - elif isinstance(action, parser.ChangeDirection): - frames, (direction, type) = action(s_params, rank) - self.direction_frames = frames - self.aiming = False - if type == "sequence": - self.direction = direction - else: - if type == "absolute": - direction -= owner.direction - elif type != "relative": # aim or default - self.aiming = True - direction += owner.aim - owner.direction - - # Normalize to [-pi, pi). - direction = (direction + PI) % PI_2 - PI - if frames <= 0: - owner.direction += direction - else: - self.direction = direction / frames - - elif isinstance(action, parser.Accel): - frames, horizontal, vertical = action(s_params, rank) - self.accel_frames = frames - if horizontal: - mx, type = horizontal - 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 - elif type == "relative": - self.mx = mx / frames - if vertical: - my, type = vertical - 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(s_params, rank) - break - - elif isinstance(action, parser.Vanish): - owner.vanish() + elif action(owner, self, s_params, rank, created): break - elif isinstance(action, parser.Appearance): - owner.appearance = action.appearance - - else: - self.handle(action, owner, created) - - def handle(self, action, owner, created): - """Override in subclasses for new action types.""" - try: - handler = self.CUSTOM[type(action)] - except KeyError: - raise NotImplementedError(action.__class__.__name__) - else: - handler(self, owner, created) - class Bullet(object): """Simple bullet implementation. @@ -305,7 +146,6 @@ class Bullet(object): actions - internal action list Action - custom Action constructor - """ def __init__(self, x=0, y=0, direction=0, speed=0, target=None, diff --git a/bulletml/parser.py b/bulletml/parser.py index c4b3e04..653b90f 100644 --- a/bulletml/parser.py +++ b/bulletml/parser.py @@ -3,13 +3,13 @@ This is based on the format described at http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/bulletml_ref_e.html. -Unless you are adding support for new tags, the only class you should -care about in here is BulletML. +Unless you are adding support for new actions, the only class you +should care about in here is BulletML. """ from __future__ import division -import math +from math import sin, cos, radians, pi as PI from xml.etree.ElementTree import ElementTree @@ -33,6 +33,8 @@ from bulletml.expr import NumberDef, INumberDef __all__ = ["ParseError", "BulletML"] +PI_2 = PI * 2 + class ParseError(Error): """Raised when an error occurs parsing the XML structure.""" pass @@ -86,7 +88,7 @@ class Direction(object): return cls(element.get("type", default), NumberDef(element.text)) def __call__(self, params, rank): - return (math.radians(self.value(params, rank)), self.type) + return (radians(self.value(params, rank)), self.type) def __repr__(self): return "%s(%r, type=%r)" % ( @@ -123,8 +125,26 @@ class ChangeDirection(object): except UnboundLocalError as exc: raise ParseError(str(exc)) - def __call__(self, params, rank): - return self.term(params, rank), self.direction(params, rank) + def __call__(self, owner, action, params, rank, created): + frames = self.term(params, rank) + direction, type = self.direction(params, rank) + action.direction_frames = frames + action.aiming = False + if type == "sequence": + action.direction = direction + else: + if type == "absolute": + direction -= owner.direction + elif type != "relative": # aim or default + action.aiming = True + direction += owner.aim - owner.direction + + # Normalize to [-pi, pi). + direction = (direction + PI) % PI_2 - PI + if frames <= 0: + owner.direction += direction + else: + action.direction = direction / frames def __repr__(self): return "%s(term=%r, direction=%r)" % ( @@ -190,8 +210,21 @@ class ChangeSpeed(object): except UnboundLocalError as exc: raise ParseError(str(exc)) - def __call__(self, params, rank): - return self.term(params, rank), self.speed(params, rank) + def __call__(self, owner, action, params, rank, created): + frames = self.term(params, rank) + speed, type = self.speed(params, rank) + action.speed_frames = frames + if frames <= 0: + if type == "absolute": + owner.speed = speed + elif type == "relative": + owner.speed += speed + elif type == "sequence": + action.speed = speed + elif type == "relative": + action.speed = speed / frames + else: + action.speed = (speed - owner.speed) / frames def __repr__(self): return "%s(term=%r, speed=%r)" % ( @@ -214,8 +247,9 @@ class Wait(object): """Construct using an ElementTree-style element.""" return cls(INumberDef(element.text)) - def __call__(self, params, rank): - return self.frames(params, rank) + def __call__(self, owner, action, params, rank, created): + action.wait_frames = self.frames(params, rank) + return True def __repr__(self): return "%s(%r)" % (type(self).__name__, self.frames) @@ -237,6 +271,9 @@ class Tag(object): """Construct using an ElementTree-style element.""" return cls(element.text) + def __call__(self, owner, action, params, rank, created): + owner.tags.add(self.tag) + class Untag(object): """Unset a bullet tag.""" @@ -254,6 +291,12 @@ class Untag(object): """Construct using an ElementTree-style element.""" return cls(element.text) + def __call__(self, owner, action, params, rank, created): + try: + owner.tags.remove(self.tag) + except KeyError: + pass + class Appearance(object): """Set a bullet appearance.""" @@ -271,6 +314,9 @@ class Appearance(object): """Construct using an ElementTree-style element.""" return cls(element.text) + def __call__(self, owner, action, params, rank, created): + owner.apearance = self.appearance + class Vanish(object): """Make the owner disappear.""" @@ -285,6 +331,10 @@ class Vanish(object): def __repr__(self): return "%s()" % (type(self).__name__) + def __call__(self, owner, action, params, rank, created): + owner.vanish() + return True + class Repeat(object): """Repeat an action definition.""" @@ -315,8 +365,14 @@ class Repeat(object): except UnboundLocalError as exc: raise ParseError(str(exc)) - def __call__(self, params, rank): - return self.times(params, rank), self.action(params, rank) + def __call__(self, owner, action, params, rank, created): + repeat = self.times(params, rank) + actions, params = self.action(params, rank) + child = action.__class__( + owner, action, actions, params, rank, repeat) + owner.replace(action, child) + child.step(owner, created) + return True def __repr__(self): return "%s(%r, %r)" % (type(self).__name__, self.times, self.action) @@ -365,11 +421,37 @@ class Accel(object): except AttributeError: raise ParseError - def __call__(self, params, rank): + def __call__(self, owner, action, params, rank, created): frames = self.term(params, rank) horizontal = self.horizontal and self.horizontal(params, rank) vertical = self.vertical and self.vertical(params, rank) - return frames, horizontal, vertical + action.accel_frames = frames + if horizontal: + mx, type = horizontal + if frames <= 0: + if type == "absolute": + owner.mx = mx + elif type == "relative": + owner.mx += mx + elif type == "sequence": + action.mx = mx + elif type == "absolute": + action.mx = (mx - owner.mx) / frames + elif type == "relative": + action.mx = mx / frames + if vertical: + my, type = vertical + if frames <= 0: + if type == "absolute": + owner.my = my + elif type == "relative": + owner.my += my + elif type == "sequence": + action.my = my + elif type == "absolute": + action.my = (my - owner.my) / frames + elif type == "relative": + action.my = my / frames def __repr__(self): return "%s(%r, horizontal=%r, vertical=%r)" % ( @@ -671,7 +753,7 @@ class FireDef(object): doc._fires[element.get("label")] = fire return fire - def __call__(self, params, rank): + def __call__(self, owner, action, params, rank, created): direction, speed, tags, appearance, actions = self.bullet(params, rank) if self.direction: direction = self.direction(params, rank) @@ -680,7 +762,53 @@ class FireDef(object): tags = tags.union(self.tags) if self.appearance: appearance = self.appearance - return direction, speed, self.offset, tags, appearance, actions + + if direction: + direction, type = direction + if type == "aim" or type is None: + direction += owner.aim + elif type == "sequence": + direction += action.previous_fire_direction + elif type == "relative": + direction += owner.direction + else: + direction = owner.aim + action.previous_fire_direction = direction + + if speed: + speed, type = speed + if type == "sequence": + speed += action.previous_fire_speed + elif type == "relative": + # The reference Noiz implementation uses + # prvFireSpeed here, but the standard is + # 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 + action.previous_fire_speed = speed + + x, y = owner.x, owner.y + if self.offset: + off_x, off_y = self.offset(params, rank) + if self.offset.type == "relative": + 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 + + if appearance is None: + appearance = owner.appearance + bullet = owner.__class__( + x=x, y=y, direction=direction, speed=speed, + target=owner.target, actions=actions, rank=rank, + appearance=appearance, tags=tags, Action=action.__class__) + created.append(bullet) def __repr__(self): return "%s(direction=%r, speed=%r, bullet=%r)" % ( @@ -714,8 +842,9 @@ class FireRef(object): doc._fire_refs.append(fired) return fired - def __call__(self, params, rank): - return self.fire(self.params(params, rank), rank) + def __call__(self, owner, action, params, rank, created): + params = self.params(params, rank) + return self.fire(owner, action, params, rank, created) def __repr__(self): return "%s(params=%r, fire=%r)" % ( -- 2.20.1