1 """BulletML implementation.
3 http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html
6 from __future__
import division
10 from bulletml
import parser
12 # TODO(jfw): This is very non-Pythonic, it's pretty much just the
13 # BulletML reference ActionImpl translated to Python.
17 __all__
= ["Action", "Bullet"]
20 """Running action implementation."""
22 def __init__(self
, owner
, parent
, actions
, params
, rank
, repeat
=1):
23 self
.actions
= actions
30 self
.direction_frames
= 0
36 self
.previous_fire_direction
= 0
37 self
.previous_fire_speed
= 0
43 self
.copy_state(parent
)
46 return "%s(pc=%r, actions=%r)" % (
47 type(self
).__name
__, self
.pc
, self
.actions
)
50 """End this action and its parents."""
56 def copy_state(self
, other
):
57 """Copy fire/movement state from other to self."""
58 self
.direction_frames
= other
.direction_frames
59 self
.direction
= other
.direction
60 self
.aiming
= other
.aiming
61 self
.speed_frames
= other
.speed_frames
62 self
.speed
= other
.speed
63 self
.accel_frames
= other
.accel_frames
66 self
.previous_fire_direction
= other
.previous_fire_direction
67 self
.previous_fire_speed
= other
.previous_fire_speed
70 """Advance by one frame."""
73 if self
.speed_frames
> 0:
74 self
.speed_frames
-= 1
75 self
.owner
.speed
+= self
.speed
77 if self
.direction_frames
> 0:
78 # The Noiz implementation was a little weird here, I think
79 # there was a bug in it that prevented it from working if
80 # the frame count was 1. I'm still not sure what the aim
81 # check is supposed to do, exactly.
82 self
.direction_frames
-= 1
83 if self
.aiming
and self
.direction_frames
<= 0:
84 self
.owner
.direction
+= self
.owner
.aim
86 self
.owner
.direction
+= self
.direction
88 if self
.accel_frames
> 0:
89 self
.accel_frames
-= 1
90 self
.owner
.mx
+= self
.mx
91 self
.owner
.my
+= self
.my
96 if self
.wait_frames
> 0:
104 action
= self
.actions
[self
.pc
]
110 if self
.parent
is not None:
111 self
.parent
.copy_state(self
)
112 self
.owner
.replace(self
, self
.parent
)
116 action
= self
.actions
[self
.pc
]
118 if isinstance(action
, parser
.Repeat
):
119 repeat
, (actions
, params
) = action(self
.params
, self
.rank
)
121 self
.owner
, self
, actions
, params
, self
.rank
, repeat
)
122 self
.owner
.replace(self
, child
)
123 created
.extend(child
.step())
126 elif isinstance(action
, (parser
.ActionDef
, parser
.ActionRef
)):
127 actions
, params
= action(self
.params
, self
.rank
)
128 child
= Action(self
.owner
, self
, actions
, params
, self
.rank
)
129 self
.owner
.replace(self
, child
)
130 created
.extend(child
.step())
133 elif isinstance(action
, (parser
.FireDef
, parser
.FireRef
)):
134 direction
, speed
, actions
= action(self
.params
, self
.rank
)
136 direction
, type = direction
137 if type == "aim" or type is None:
138 direction
+= self
.owner
.aim
139 elif type == "sequence":
140 direction
+= self
.previous_fire_direction
141 elif type == "relative":
142 direction
+= self
.owner
.direction
144 direction
= self
.owner
.aim
145 self
.previous_fire_direction
= direction
149 if type == "sequence":
150 speed
+= self
.previous_fire_speed
151 elif type == "relative":
152 # The reference Noiz implementation uses
153 # prvFireSpeed here, but the standard is
154 # pretty clear -- "0 means that the direction
155 # of this fire and the direction of the bullet
157 speed
+= self
.owner
.speed
160 self
.previous_fire_speed
= speed
162 bullet
= Bullet(self
.owner
.x
, self
.owner
.y
, direction
, speed
,
163 self
.owner
.target
, actions
, self
)
164 created
.append(bullet
)
166 elif isinstance(action
, parser
.ChangeSpeed
):
167 frames
, (speed
, type) = action(self
.params
, self
.rank
)
168 self
.speed_frames
= frames
169 if type == "sequence":
171 elif type == "relative":
172 self
.speed
= speed
/ frames
174 self
.speed
= (speed
- self
.owner
.speed
) / frames
176 elif isinstance(action
, parser
.ChangeDirection
):
177 frames
, (direction
, type) = action(self
.params
, self
.rank
)
178 self
.direction_frames
= frames
180 if type == "sequence":
181 self
.direction
= direction
183 if type == "absolute":
185 direction
- self
.owner
.direction
) % PI_2
186 elif type == "relative":
187 self
.direction
= direction
193 - self
.owner
.direction
) % PI_2
195 if self
.direction
> math
.pi
:
196 self
.direction
-= PI_2
197 if self
.direction
< -math
.pi
:
198 self
.direction
+= PI_2
199 self
.direction
/= self
.direction_frames
201 elif isinstance(action
, parser
.Accel
):
202 frames
, horizontal
, vertical
= action(self
.params
, self
.rank
)
203 self
.accel_frames
= frames
205 mx
, type = horizontal
206 if type == "sequence":
208 elif type == "absolute":
209 self
.mx
= (mx
- self
.owner
.mx
) / frames
210 elif type == "relative":
211 self
.mx
= mx
/ frames
214 if type == "sequence":
216 elif type == "absolute":
217 self
.my
= (my
- self
.owner
.my
) / frames
218 elif type == "relative":
219 self
.my
= my
/ frames
221 elif isinstance(action
, parser
.Wait
):
222 self
.wait_frames
= action(self
.params
, self
.rank
)
225 elif isinstance(action
, parser
.Vanish
):
231 class Bullet(object):
232 """Simple bullet implementation.
235 x, y - current X/Y position
236 px, py - X/Y position prior to the last step
237 mx, my - X/Y axis-oriented speed modifier ("acceleration")
238 direction - direction of movement, in radians
239 speed - speed of movement, in units per frame
240 target - object with .x and .y fields for "aim" directions
241 vanished - set to true by a <vanish> action
243 Contructor Arguments:
244 x, y, direction, speed, target - same as the attributes
245 actions - internal action list
246 parent - parent of actions, None for manually-created bullets
247 rank - game difficulty, 0 to 1
251 def __init__(self
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
252 actions
=(), parent
=None, rank
=None):
257 self
.direction
= direction
259 self
.vanished
= False
262 rank
= parent
.rank
if parent
else 0.5
263 # New bullets reset the parent hierarchy.
264 self
._actions
= [Action(self
, None, action
, params
, rank
)
265 for action
, params
in actions
]
268 return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, "
269 "actions=%r, target=%r, vanished=%r)") % (
270 type(self
).__name
__, self
.x
, self
.y
, (self
.mx
, self
.my
),
271 self
.direction
, self
.speed
, self
._actions
, self
.target
,
276 """Angle to the target, in radians."""
277 if self
.target
is None:
278 return self
.direction
280 return math
.atan2(self
.target
.x
- self
.x
, self
.y
- self
.target
.y
)
284 """Check if this bullet is finished running.
286 A bullet is finished when it has vanished, and all its
287 actions have finished.
289 If this is true, the bullet should be removed from the screen.
290 (You will probably want to cull it under other circumstances
293 if not self
.vanished
:
295 for action
in self
._actions
:
296 if not action
.finished
:
301 """Vanish this bullet and stop all actions."""
303 for action
in self
._actions
:
307 def replace(self
, old
, new
):
308 """Replace an active action with another.
310 This is mostly used by actions internally to queue children.
313 idx
= self
._actions
.index(old
)
317 self
._actions
[idx
] = new
320 """Advance by one frame.
322 This updates the position and velocity, and may also set the
325 It returns any new bullets this bullet spawned during this step.
329 for action
in self
._actions
:
330 created
.extend(action
.step())
334 self
.x
+= self
.mx
+ math
.sin(self
.direction
) * self
.speed
335 self
.y
+= self
.my
- math
.cos(self
.direction
) * self
.speed