92982db02afd4d128f8b475b7f42937b0baabd42
[python-bulletml.git] / bulletml / impl.py
1 """BulletML implementation.
2
3 http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html
4 """
5
6 from __future__ import division
7
8 import math
9
10 from bulletml import parser
11
12 # TODO(jfw): This is very non-Pythonic, it's pretty much just the
13 # BulletML reference ActionImpl translated to Python.
14
15 class Action(object):
16 """Running action implementation."""
17
18 def __init__(self, owner, parent, actions, params, rank):
19 self.actions = actions
20 self.parent = parent
21 self.repeat = 1
22 self.wait_frames = 0
23 self.speed = 0
24 self.speed_frames = 0
25 self.aim_direction = 0
26 self.direction = 0
27 self.direction_frames = 0
28 self.aiming = False
29 self.aim_mx = 0
30 self.aim_my = 0
31 self.mx = 0
32 self.my = 0
33 self.owner = owner
34 self.accel_frames = 0
35 self.previous_fire_direction = 0
36 self.previous_fire_speed = 0
37 self.params = params
38 self.rank = rank
39 self.pc = -1
40 if parent:
41 self.copy_state(parent)
42
43 def __repr__(self):
44 return "%s(pc=%r, actions=%r)" % (
45 type(self).__name__, self.pc, self.actions)
46
47 @property
48 def finished(self):
49 return self.pc is None
50
51 def vanish(self):
52 if self.parent:
53 self.parent.vanish()
54 self.pc = None
55
56 def copy_state(self, other):
57 self.direction_frames = other.direction_frames
58 self.direction = other.direction
59 self.aiming = other.aiming
60 self.speed_frames = other.speed_frames
61 self.speed = other.speed
62 self.accel_frames = other.accel_frames
63 self.mx = other.mx
64 self.my = other.my
65 self.previous_fire_direction = other.previous_fire_direction
66 self.previous_fire_speed = other.previous_fire_speed
67
68 def step(self):
69 created = []
70
71 if self.speed_frames > 0:
72 self.speed_frames -= 1
73 self.owner.speed += self.speed
74
75 if self.direction_frames > 0:
76 self.direction_frames -= 1
77 if self.direction_frames <= 0:
78 if self.aiming:
79 self.owner.direction += self.owner.aim
80 else:
81 self.owner.direction += self.direction
82
83 if self.accel_frames > 0:
84 self.accel_frames -= 1
85 self.owner.mx += self.mx
86 self.owner.my += self.my
87
88 if self.pc is None:
89 return created
90
91 if self.wait_frames > 0:
92 self.wait_frames -= 1
93 return created
94
95 while True:
96 self.pc += 1
97
98 if self.pc >= len(self.actions):
99 self.repeat -= 1
100 if self.repeat <= 0:
101 self.pc = None
102 if self.parent is not None:
103 self.parent.copy_state(self)
104 self.owner.replace(self, self.parent)
105 break
106 else:
107 self.pc = 0
108
109 action = self.actions[self.pc]
110
111 if isinstance(action, parser.Repeat):
112 repeat, (actions, params) = action(self.params, self.rank)
113 child = Action(self.owner, self, actions, params, self.rank)
114 child.repeat = repeat
115 self.owner.replace(self, child)
116 created.extend(child.step())
117 break
118
119 elif isinstance(action, (parser.ActionDef, parser.ActionRef)):
120 actions, params = action(self.params, self.rank)
121 child = Action(self.owner, self, actions, params, self.rank)
122 self.owner.replace(self, child)
123 created.extend(child.step())
124 break
125
126 elif isinstance(action, (parser.FireDef, parser.FireRef)):
127 direction, speed, actions = action(self.params, self.rank)
128 if direction:
129 direction, type = direction
130 if type == "aim" or type is None:
131 direction += self.owner.aim
132 elif type == "sequence":
133 direction += self.previous_fire_direction
134 elif type == "relative":
135 direction += self.owner.direction
136 else:
137 direction = self.owner.aim
138 self.previous_fire_direction = direction
139
140
141 if speed:
142 speed, type = speed
143 if type == "sequence":
144 speed += self.previous_fire_speed
145 elif type == "relative":
146 # FIXME(jfw): Reference impl uses prvFireSpeed
147 # here? That doesn't seem right at all.
148 speed += self.owner.speed
149 else:
150 speed = 1
151 self.previous_fire_speed = speed
152
153 bullet = Bullet(self.owner.x, self.owner.y, direction, speed,
154 self.owner.target, actions, self)
155 created.append(bullet)
156
157 elif isinstance(action, parser.ChangeSpeed):
158 frames, (speed, type) = action(self.params, self.rank)
159 self.speed_frames = frames
160 if type == "sequence":
161 self.speed = speed
162 elif type == "relative":
163 self.speed = speed / frames
164 else:
165 self.speed = (speed - self.owner.speed) / frames
166
167 elif isinstance(action, parser.ChangeDirection):
168 frames, (direction, type) = action(self.params, self.rank)
169 self.direction_frames = frames
170 self.aiming = False
171 if type == "sequence":
172 self.aiming = False
173 self.direction = direction
174 else:
175 if type == "absolute":
176 self.aiming = False
177 self.direction = (
178 direction - self.owner.direction) % 360
179 elif type == "relative":
180 self.aiming = False
181 self.direction = direction
182 else:
183 self.aiming = True
184 self.direction = (
185 direction
186 + self.owner.aim
187 - self.owner.direction) % 360
188
189 while self.direction > 180:
190 self.direction -= 360
191 while self.direction < -180:
192 self.direction += 360
193 self.direction /= self.direction_frames
194
195 elif isinstance(action, parser.Accel):
196 frames, horizontal, vertical = action(self.params, self.rank)
197 self.accel_frames = frames
198 if horizontal:
199 mx, type = horizontal
200 if type == "sequence":
201 self.mx = mx
202 elif type == "absolute":
203 self.mx = (mx - self.owner.mx) / frames
204 elif type == "relative":
205 self.mx = mx / frames
206 if vertical:
207 my, type = vertical
208 if type == "sequence":
209 self.my = my
210 elif type == "absolute":
211 self.my = (my - self.owner.my) / frames
212 elif type == "relative":
213 self.my = my / frames
214
215 elif isinstance(action, parser.Wait):
216 self.wait_frames = action(self.params, self.rank)
217 break
218
219 elif isinstance(action, parser.Vanish):
220 self.owner.vanish()
221 break
222
223 return created
224
225 class Bullet(object):
226 """Simple bullet implementation."""
227
228 def __init__(self, x=0, y=0, direction=0, speed=0, target=None,
229 actions=(), parent=None, rank=None):
230 self.x = self.px = x
231 self.y = self.py = y
232 self.mx = 0
233 self.my = 0
234 self.direction = direction
235 self.speed = speed
236 self.vanished = False
237 self.target = target
238 self.actions = []
239 if rank is None:
240 rank = parent.rank if parent else 0.5
241 # New bullets reset the parent hierarchy.
242 self.actions = [Action(self, None, action, params, rank)
243 for action, params in actions]
244
245 def __repr__(self):
246 return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, "
247 "actions=%r, target=%r, vanished=%r)") % (
248 type(self).__name__, self.x, self.y, (self.mx, self.my),
249 self.direction, self.speed, self.actions, self.target,
250 self.vanished)
251
252 @property
253 def aim(self):
254 """Angle to the target."""
255 if self.target is None:
256 return self.direction
257 else:
258 return math.degrees(
259 math.atan2(self.target.x - self.x, self.y - self.target.y))
260
261 @property
262 def finished(self):
263 for action in self.actions:
264 if not action.finished:
265 return False
266 return self.vanished
267
268 def vanish(self):
269 """Vanish this bullet and stop all actions."""
270 self.vanished = True
271 for action in self.actions:
272 action.vanish()
273 self.actions = []
274
275 def replace(self, old, new):
276 try:
277 idx = self.actions.index(old)
278 except ValueError:
279 pass
280 else:
281 self.actions[idx] = new
282
283 def step(self):
284 created = []
285
286 for action in self.actions:
287 created.extend(action.step())
288
289 direction = math.radians(self.direction)
290 self.px = self.x
291 self.py = self.y
292 self.x += self.mx + math.sin(direction) * self.speed
293 self.y += self.my - math.cos(direction) * self.speed
294
295 return created
296
297 @classmethod
298 def FromDoc(cls, doc, params=(), x=0, y=0, speed=0, direction=0,
299 target=None, rank=0.5):
300 """Construct a bullet from top-level actions in a document."""
301 actions = [act(params, rank) for act in doc.top]
302 return cls(x, y, direction, speed, target, actions, rank=rank)
303