3 http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html
6 from __future__
import division
10 from xml
.etree
.ElementTree
import ElementTree
13 from cStringIO
import StringIO
15 from StringIO
import StringIO
17 from bulletml
.errors
import Error
18 from bulletml
.expr
import NumberDef
, INumberDef
20 class ParseError(Error
):
21 """Raised when an error occurs parsing the XML structure."""
25 """Strip namespace poop off the front of a tag."""
27 return element
.tag
.rsplit('}', 1)[1]
31 class ParamList(object):
32 """List of parameter definitions."""
34 def __init__(self
, params
=[]):
35 self
.params
= list(params
)
38 def FromElement(cls
, doc
, element
):
39 """Construct using an ElementTree-style element."""
40 return cls([NumberDef(subelem
.text
) for subelem
in element
41 if realtag(subelem
) == "param"])
43 def __call__(self
, params
, rank
):
44 return [param(params
, rank
) for param
in self
.params
]
47 return "%s(%r)" % (type(self
).__name
__, self
.params
)
49 class Direction(object):
50 """Raw direction value."""
52 VALID_TYPES
= ["relative", "absolute", "aim", "sequence"]
54 def __init__(self
, type, value
):
55 if type not in self
.VALID_TYPES
:
56 raise ValueError("invalid type %r" % type)
61 def FromElement(cls
, doc
, element
, default
="absolute"):
62 """Construct using an ElementTree-style element."""
63 return cls(element
.get("type", default
), NumberDef(element
.text
))
65 def __call__(self
, params
, rank
):
66 return (math
.radians(self
.value(params
, rank
)), self
.type)
69 return "%s(%r, type=%r)" % (
70 type(self
).__name
__, self
.value
, self
.type)
72 class ChangeDirection(object):
73 """Direction change over time."""
75 def __init__(self
, term
, direction
):
77 self
.direction
= direction
80 def FromElement(cls
, doc
, element
):
81 """Construct using an ElementTree-style element."""
82 for subelem
in element
.getchildren():
83 tag
= realtag(subelem
)
84 if tag
== "direction":
85 direction
= Direction
.FromElement(doc
, subelem
)
87 term
= INumberDef(subelem
.text
)
89 return cls(term
, direction
)
90 except UnboundLocalError as exc
:
91 raise ParseError(str(exc
))
93 def __call__(self
, params
, rank
):
94 return self
.term(params
, rank
), self
.direction(params
, rank
)
97 return "%s(term=%r, direction=%r)" % (
98 type(self
).__name
__, self
.term
, self
.direction
)
101 """Raw speed value."""
103 VALID_TYPES
= ["relative", "absolute", "sequence"]
105 def __init__(self
, type, value
):
106 if type not in self
.VALID_TYPES
:
107 raise ValueError("invalid type %r" % type)
112 def FromElement(cls
, doc
, element
):
113 """Construct using an ElementTree-style element."""
114 return cls(element
.get("type", "absolute"), NumberDef(element
.text
))
116 def __call__(self
, params
, rank
):
117 return (self
.value(params
, rank
), self
.type)
120 return "%s(%r, type=%r)" % (type(self
).__name
__, self
.value
, self
.type)
122 class ChangeSpeed(object):
123 """Speed change over time."""
125 def __init__(self
, term
, speed
):
130 def FromElement(cls
, doc
, element
):
131 """Construct using an ElementTree-style element."""
132 for subelem
in element
.getchildren():
133 tag
= realtag(subelem
)
135 speed
= Speed
.FromElement(doc
, subelem
)
137 term
= INumberDef(subelem
.text
)
139 return cls(term
, speed
)
140 except UnboundLocalError as exc
:
141 raise ParseError(str(exc
))
143 def __call__(self
, params
, rank
):
144 return self
.term(params
, rank
), self
.speed(params
, rank
)
147 return "%s(term=%r, speed=%r)" % (
148 type(self
).__name
__, self
.term
, self
.speed
)
151 """Wait for some frames."""
153 def __init__(self
, frames
):
157 def FromElement(cls
, doc
, element
):
158 """Construct using an ElementTree-style element."""
159 return cls(INumberDef(element
.text
))
161 def __call__(self
, params
, rank
):
162 return self
.frames(params
, rank
)
165 return "%s(%r)" % (type(self
).__name
__, self
.frames
)
167 class Vanish(object):
168 """Make the owner disappear."""
174 def FromElement(cls
, doc
, element
):
175 """Construct using an ElementTree-style element."""
179 return "%s()" % (type(self
).__name
__)
181 class Repeat(object):
182 """Repeat an action definition."""
184 def __init__(self
, times
, action
):
189 def FromElement(cls
, doc
, element
):
190 """Construct using an ElementTree-style element."""
191 for subelem
in element
.getchildren():
192 tag
= realtag(subelem
)
194 times
= INumberDef(subelem
.text
)
195 elif tag
== "action":
196 action
= ActionDef
.FromElement(doc
, subelem
)
197 elif tag
== "actionRef":
198 action
= ActionRef
.FromElement(doc
, subelem
)
200 return cls(times
, action
)
201 except UnboundLocalError as exc
:
202 raise ParseError(str(exc
))
204 def __call__(self
, params
, rank
):
205 return self
.times(params
, rank
), self
.action(params
, rank
)
208 return "%s(%r, %r)" % (type(self
).__name
__, self
.times
, self
.action
)
211 """Accelerate over some time."""
216 def __init__(self
, term
, horizontal
=None, vertical
=None):
218 self
.horizontal
= horizontal
219 self
.vertical
= vertical
222 def FromElement(cls
, doc
, element
):
223 """Construct using an ElementTree-style element."""
227 for subelem
in element
.getchildren():
228 tag
= realtag(subelem
)
230 term
= INumberDef(subelem
.text
)
231 elif tag
== "horizontal":
232 horizontal
= Speed
.FromElement(doc
, subelem
)
233 elif tag
== "vertical":
234 vertical
= Speed
.FromElement(doc
, subelem
)
237 return cls(term
, horizontal
, vertical
)
238 except AttributeError:
241 def __call__(self
, params
, rank
):
242 frames
= self
.term(params
, rank
)
243 horizontal
= self
.horizontal
and self
.horizontal(params
, rank
)
244 vertical
= self
.vertical
and self
.vertical(params
, rank
)
245 return frames
, horizontal
, vertical
248 return "%s(%r, horizontal=%r, vertical=%r)" % (
249 type(self
).__name
__, self
.term
, self
.horizontal
, self
.vertical
)
251 class BulletDef(object):
252 """Bullet definition."""
257 def __init__(self
, actions
=[], direction
=None, speed
=None):
258 self
.direction
= direction
260 self
.actions
= list(actions
)
263 def FromElement(cls
, doc
, element
):
264 """Construct using an ElementTree-style element."""
268 for subelem
in element
.getchildren():
269 tag
= realtag(subelem
)
270 if tag
== "direction":
271 direction
= Direction
.FromElement(doc
, subelem
)
273 speed
= Speed
.FromElement(doc
, subelem
)
274 elif tag
== "action":
275 actions
.append(ActionDef
.FromElement(doc
, subelem
))
276 elif tag
== "actionRef":
277 actions
.append(ActionRef
.FromElement(doc
, subelem
))
278 dfn
= cls(actions
, direction
, speed
)
279 doc
.bullets
[element
.get("label")] = dfn
282 def __call__(self
, params
, rank
):
283 actions
= [action(params
, rank
) for action
in self
.actions
]
285 self
.direction
and self
.direction(params
, rank
),
286 self
.speed
and self
.speed(params
, rank
),
290 return "%s(direction=%r, speed=%r, actions=%r)" % (
291 type(self
).__name
__, self
.direction
, self
.speed
, self
.actions
)
293 class BulletRef(object):
294 """Create a bullet by name with parameters."""
296 def __init__(self
, bullet
, params
=None):
298 self
.params
= params
or ParamList()
301 def FromElement(cls
, doc
, element
):
302 """Construct using an ElementTree-style element."""
303 bullet
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
304 doc
._bullet
_refs
.append(bullet
)
307 def __call__(self
, params
, rank
):
308 return self
.bullet(self
.params(params
, rank
), rank
)
311 return "%s(params=%r, bullet=%r)" % (
312 type(self
).__name
__, self
.params
, self
.bullet
)
314 class ActionDef(object):
315 """Action definition."""
317 # This is self-referential, so it's filled in later.
318 CONSTRUCTORS
= dict()
320 def __init__(self
, actions
):
321 self
.actions
= list(actions
)
324 def FromElement(cls
, doc
, element
):
325 """Construct using an ElementTree-style element."""
327 for subelem
in element
.getchildren():
328 tag
= realtag(subelem
)
330 ctr
= cls
.CONSTRUCTORS
[tag
]
334 actions
.append(ctr
.FromElement(doc
, subelem
))
336 doc
.actions
[element
.get("label")] = dfn
339 def __call__(self
, params
, rank
):
340 return self
.actions
, params
343 return "%s(%r)" % (type(self
).__name
__, self
.actions
)
345 class ActionRef(object):
346 """Run an action by name with parameters."""
348 def __init__(self
, action
, params
=None):
350 self
.params
= params
or ParamList()
353 def FromElement(cls
, doc
, element
):
354 """Construct using an ElementTree-style element."""
355 action
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
356 doc
._action
_refs
.append(action
)
359 def __call__(self
, params
, rank
):
360 return self
.action(self
.params(params
, rank
), rank
)
363 return "%s(params=%r, action=%r)" % (
364 type(self
).__name
__, self
.params
, self
.action
)
366 class FireDef(object):
367 """Fire definition (creates a bullet)."""
369 def __init__(self
, bullet
, direction
=None, speed
=None):
371 self
.direction
= direction
375 def FromElement(cls
, doc
, element
):
376 """Construct using an ElementTree-style element."""
380 for subelem
in element
.getchildren():
381 tag
= realtag(subelem
)
382 if tag
== "direction":
383 direction
= Direction
.FromElement(doc
, subelem
, "aim")
385 speed
= Speed
.FromElement(doc
, subelem
)
386 elif tag
== "bullet":
387 bullet
= BulletDef
.FromElement(doc
, subelem
)
388 elif tag
== "bulletRef":
389 bullet
= BulletRef
.FromElement(doc
, subelem
)
392 fire
= cls(bullet
, direction
, speed
)
393 except UnboundLocalError as exc
:
394 raise ParseError(str(exc
))
396 doc
.fires
[element
.get("label")] = fire
399 def __call__(self
, params
, rank
):
400 direction
, speed
, actions
= self
.bullet(params
, rank
)
402 direction
= self
.direction(params
, rank
)
404 speed
= self
.speed(params
, rank
)
405 return direction
, speed
, actions
408 return "%s(direction=%r, speed=%r, bullet=%r)" % (
409 type(self
).__name
__, self
.direction
, self
.speed
, self
.bullet
)
411 class FireRef(object):
412 """Fire a bullet by name with parameters."""
414 def __init__(self
, fire
, params
=None):
416 self
.params
= params
or ParamList()
419 def FromElement(cls
, doc
, element
):
420 """Construct using an ElementTree-style element."""
421 fired
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
422 doc
._fire
_refs
.append(fired
)
425 def __call__(self
, params
, rank
):
426 """Generate a Bullet from the FireDef and params."""
427 return self
.fire(self
.params(params
, rank
), rank
)
430 return "%s(params=%r, fire=%r)" % (
431 type(self
).__name
__, self
.params
, self
.fire
)
433 class BulletML(object):
434 """BulletML document.
436 A BulletML document is a collection of bullets, actions, and
437 firings, as well as a base game type.
446 def __init__(self
, source
):
451 self
._bullet
_refs
= []
452 self
._action
_refs
= []
455 if isinstance(source
, (str, unicode)):
456 source
= StringIO(source
)
459 root
= tree
.parse(source
)
461 self
.type = root
.get("type", "none")
463 for element
in root
.getchildren():
464 tag
= realtag(element
)
465 if tag
in self
.CONSTRUCTORS
:
466 self
.CONSTRUCTORS
[tag
].FromElement(self
, element
)
469 for ref
in self
._bullet
_refs
:
470 ref
.bullet
= self
.bullets
[ref
.bullet
]
471 for ref
in self
._fire
_refs
:
472 ref
.fire
= self
.fires
[ref
.fire
]
473 for ref
in self
._action
_refs
:
474 ref
.action
= self
.actions
[ref
.action
]
475 except KeyError as exc
:
476 raise ParseError("unknown reference %s" % exc
)
478 del(self
._bullet
_refs
)
479 del(self
._action
_refs
)
482 self
.bullets
.pop(None, None)
483 self
.actions
.pop(None, None)
484 self
.fires
.pop(None, None)
488 """Get a list of all top-level actions."""
489 return [dfn
for name
, dfn
in self
.actions
.iteritems()
490 if name
and name
.startswith("top")]
493 return "%s(type=%r, bullets=%r, actions=%r, fires=%r)" % (
494 type(self
).__name
__, self
.type, self
.bullets
, self
.actions
,
497 ActionDef
.CONSTRUCTORS
= dict(
501 changeSpeed
=ChangeSpeed
,
502 changeDirection
=ChangeDirection
,