3 http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html
6 from __future__
import division
8 from xml
.etree
.ElementTree
import ElementTree
11 from cStringIO
import StringIO
13 from StringIO
import StringIO
15 from bulletml
.errors
import Error
16 from bulletml
.expr
import NumberDef
, INumberDef
18 class ParseError(Error
):
19 """Raised when an error occurs parsing the XML structure."""
23 """Strip namespace poop off the front of a tag."""
25 return element
.tag
.rsplit('}', 1)[1]
29 class ParamList(object):
30 """List of parameter definitions."""
32 def __init__(self
, params
=[]):
33 self
.params
= list(params
)
36 def FromElement(cls
, doc
, element
):
37 """Construct using an ElementTree-style element."""
38 return cls([NumberDef(subelem
.text
) for subelem
in element
39 if realtag(subelem
) == "param"])
41 def __call__(self
, params
, rank
):
42 return [param(params
, rank
) for param
in self
.params
]
45 return "%s(%r)" % (type(self
).__name
__, self
.params
)
47 class Direction(object):
48 """Raw direction value."""
50 VALID_TYPES
= ["relative", "absolute", "aim", "sequence"]
52 def __init__(self
, type, value
):
53 if type not in self
.VALID_TYPES
:
54 raise ValueError("invalid type %r" % type)
59 def FromElement(cls
, doc
, element
, default
="absolute"):
60 """Construct using an ElementTree-style element."""
61 return cls(element
.get("type", default
), NumberDef(element
.text
))
63 def __call__(self
, params
, rank
):
64 return (self
.value(params
, rank
), self
.type)
67 return "%s(%r, type=%r)" % (
68 type(self
).__name
__, self
.value
, self
.type)
70 class ChangeDirection(object):
71 """Direction change over time."""
73 def __init__(self
, term
, direction
):
75 self
.direction
= direction
78 def FromElement(cls
, doc
, element
):
79 """Construct using an ElementTree-style element."""
80 for subelem
in element
.getchildren():
81 tag
= realtag(subelem
)
82 if tag
== "direction":
83 direction
= Direction
.FromElement(doc
, subelem
)
85 term
= INumberDef(subelem
.text
)
87 return cls(term
, direction
)
88 except UnboundLocalError as exc
:
89 raise ParseError(str(exc
))
91 def __call__(self
, params
, rank
):
92 return self
.term(params
, rank
), self
.direction(params
, rank
)
95 return "%s(term=%r, direction=%r)" % (
96 type(self
).__name
__, self
.term
, self
.direction
)
99 """Raw speed value."""
101 VALID_TYPES
= ["relative", "absolute", "sequence"]
103 def __init__(self
, type, value
):
104 if type not in self
.VALID_TYPES
:
105 raise ValueError("invalid type %r" % type)
110 def FromElement(cls
, doc
, element
):
111 """Construct using an ElementTree-style element."""
112 return cls(element
.get("type", "absolute"), NumberDef(element
.text
))
114 def __call__(self
, params
, rank
):
115 return (self
.value(params
, rank
), self
.type)
118 return "%s(%r, type=%r)" % (type(self
).__name
__, self
.value
, self
.type)
120 class ChangeSpeed(object):
121 """Speed change over time."""
123 def __init__(self
, term
, speed
):
128 def FromElement(cls
, doc
, element
):
129 """Construct using an ElementTree-style element."""
130 for subelem
in element
.getchildren():
131 tag
= realtag(subelem
)
133 speed
= Speed
.FromElement(doc
, subelem
)
135 term
= INumberDef(subelem
.text
)
137 return cls(term
, speed
)
138 except UnboundLocalError as exc
:
139 raise ParseError(str(exc
))
141 def __call__(self
, params
, rank
):
142 return self
.term(params
, rank
), self
.speed(params
, rank
)
145 return "%s(term=%r, speed=%r)" % (
146 type(self
).__name
__, self
.term
, self
.speed
)
149 """Wait for some frames."""
151 def __init__(self
, frames
):
155 def FromElement(cls
, doc
, element
):
156 """Construct using an ElementTree-style element."""
157 return cls(INumberDef(element
.text
))
159 def __call__(self
, params
, rank
):
160 return self
.frames(params
, rank
)
163 return "%s(%r)" % (type(self
).__name
__, self
.frames
)
165 class Vanish(object):
166 """Make the owner disappear."""
172 def FromElement(cls
, doc
, element
):
173 """Construct using an ElementTree-style element."""
177 return "%s()" % (type(self
).__name
__)
179 class Repeat(object):
180 """Repeat an action definition."""
182 def __init__(self
, times
, action
):
187 def FromElement(cls
, doc
, element
):
188 for subelem
in element
.getchildren():
189 tag
= realtag(subelem
)
191 times
= INumberDef(subelem
.text
)
192 elif tag
== "action":
193 action
= ActionDef
.FromElement(doc
, subelem
)
194 elif tag
== "actionRef":
195 action
= ActionRef
.FromElement(doc
, subelem
)
197 return cls(times
, action
)
198 except UnboundLocalError as exc
:
199 raise ParseError(str(exc
))
201 def __call__(self
, params
, rank
):
202 return self
.times(params
, rank
), self
.action(params
, rank
)
205 return "%s(%r, %r)" % (type(self
).__name
__, self
.times
, self
.action
)
208 """Accelerate over some time."""
213 def __init__(self
, term
, horizontal
=None, vertical
=None):
215 self
.horizontal
= horizontal
216 self
.vertical
= vertical
219 def FromElement(cls
, doc
, element
):
220 """Construct using an ElementTree-style element."""
224 for subelem
in element
.getchildren():
225 tag
= realtag(subelem
)
227 term
= INumberDef(subelem
.text
)
228 elif tag
== "horizontal":
229 horizontal
= Speed
.FromElement(doc
, subelem
)
230 elif tag
== "vertical":
231 vertical
= Speed
.FromElement(doc
, subelem
)
234 return cls(term
, horizontal
, vertical
)
235 except AttributeError:
238 def __call__(self
, params
, rank
):
239 frames
= self
.term(params
, rank
)
240 horizontal
= self
.horizontal
and self
.horizontal(params
, rank
)
241 vertical
= self
.vertical
and self
.vertical(params
, rank
)
242 return frames
, horizontal
, vertical
245 return "%s(%r, horizontal=%r, vertical=%r)" % (
246 type(self
).__name
__, self
.term
, self
.horizontal
, self
.vertical
)
248 class BulletDef(object):
249 """Bullet definition."""
254 def __init__(self
, actions
=[], direction
=None, speed
=None):
255 self
.direction
= direction
257 self
.actions
= list(actions
)
260 def FromElement(cls
, doc
, element
):
261 """Construct using an ElementTree-style element."""
265 for subelem
in element
.getchildren():
266 tag
= realtag(subelem
)
267 if tag
== "direction":
268 direction
= Direction
.FromElement(doc
, subelem
)
270 speed
= Speed
.FromElement(doc
, subelem
)
271 elif tag
== "action":
272 actions
.append(ActionDef
.FromElement(doc
, subelem
))
273 elif tag
== "actionRef":
274 actions
.append(ActionRef
.FromElement(doc
, subelem
))
275 dfn
= cls(actions
, direction
, speed
)
276 doc
.bullets
[element
.get("label")] = dfn
279 def __call__(self
, params
, rank
):
280 actions
= [action(params
, rank
) for action
in self
.actions
]
282 self
.direction
and self
.direction(params
, rank
),
283 self
.speed
and self
.speed(params
, rank
),
287 return "%s(direction=%r, speed=%r, actions=%r)" % (
288 type(self
).__name
__, self
.direction
, self
.speed
, self
.actions
)
290 class BulletRef(object):
291 """Create a bullet by name with parameters."""
293 def __init__(self
, bullet
, params
=None):
295 self
.params
= params
or ParamList()
298 def FromElement(cls
, doc
, element
):
299 """Construct using an ElementTree-style element."""
300 bullet
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
301 doc
._bullet
_refs
.append(bullet
)
304 def __call__(self
, params
, rank
):
305 return self
.bullet(self
.params(params
, rank
), rank
)
308 return "%s(params=%r, bullet=%r)" % (
309 type(self
).__name
__, self
.params
, self
.bullet
)
311 class ActionDef(object):
312 """Action definition."""
314 # This is self-referential, so it's filled in later.
315 CONSTRUCTORS
= dict()
317 def __init__(self
, actions
):
318 self
.actions
= list(actions
)
321 def FromElement(cls
, doc
, element
):
322 """Construct using an ElementTree-style element."""
324 for subelem
in element
.getchildren():
325 tag
= realtag(subelem
)
327 ctr
= cls
.CONSTRUCTORS
[tag
]
331 actions
.append(ctr
.FromElement(doc
, subelem
))
333 doc
.actions
[element
.get("label")] = dfn
336 def __call__(self
, params
, rank
):
337 return self
.actions
, params
340 return "%s(%r)" % (type(self
).__name
__, self
.actions
)
342 class ActionRef(object):
343 """Run an action by name with parameters."""
345 def __init__(self
, action
, params
=None):
347 self
.params
= params
or ParamList()
350 def FromElement(cls
, doc
, element
):
351 """Construct using an ElementTree-style element."""
352 action
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
353 doc
._action
_refs
.append(action
)
356 def __call__(self
, params
, rank
):
357 return self
.action(self
.params(params
, rank
), rank
)
360 return "%s(params=%r, action=%r)" % (
361 type(self
).__name
__, self
.params
, self
.action
)
363 class FireDef(object):
364 """Fire definition (creates a bullet)."""
366 def __init__(self
, bullet
, direction
=None, speed
=None):
368 self
.direction
= direction
372 def FromElement(cls
, doc
, element
):
373 """Construct using an ElementTree-style element."""
377 for subelem
in element
.getchildren():
378 tag
= realtag(subelem
)
379 if tag
== "direction":
380 direction
= Direction
.FromElement(doc
, subelem
, "aim")
382 speed
= Speed
.FromElement(doc
, subelem
)
383 elif tag
== "bullet":
384 bullet
= BulletDef
.FromElement(doc
, subelem
)
385 elif tag
== "bulletRef":
386 bullet
= BulletRef
.FromElement(doc
, subelem
)
389 fire
= cls(bullet
, direction
, speed
)
390 except UnboundLocalError as exc
:
391 raise ParseError(str(exc
))
393 doc
.fires
[element
.get("label")] = fire
396 def __call__(self
, params
, rank
):
397 direction
, speed
, actions
= self
.bullet(params
, rank
)
399 direction
= self
.direction(params
, rank
)
401 speed
= self
.speed(params
, rank
)
402 return direction
, speed
, actions
405 return "%s(direction=%r, speed=%r, bullet=%r)" % (
406 type(self
).__name
__, self
.direction
, self
.speed
, self
.bullet
)
408 class FireRef(object):
409 """Fire a bullet by name with parameters."""
411 def __init__(self
, fire
, params
=None):
413 self
.params
= params
or ParamList()
416 def FromElement(cls
, doc
, element
):
417 """Construct using an ElementTree-style element."""
418 fired
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
419 doc
._fire
_refs
.append(fired
)
422 def __call__(self
, params
, rank
):
423 """Generate a Bullet from the FireDef and params."""
424 return self
.fire(self
.params(params
, rank
), rank
)
427 return "%s(params=%r, fire=%r)" % (
428 type(self
).__name
__, self
.params
, self
.fire
)
430 class BulletML(object):
431 """BulletML document.
433 A BulletML document is a collection of bullets, actions, and
434 firings, as well as a base game type.
443 def __init__(self
, source
):
448 self
._bullet
_refs
= []
449 self
._action
_refs
= []
452 if isinstance(source
, (str, unicode)):
453 source
= StringIO(source
)
456 root
= tree
.parse(source
)
458 self
.type = root
.get("type", "none")
460 for element
in root
.getchildren():
461 tag
= realtag(element
)
462 if tag
in self
.CONSTRUCTORS
:
463 self
.CONSTRUCTORS
[tag
].FromElement(self
, element
)
466 for ref
in self
._bullet
_refs
:
467 ref
.bullet
= self
.bullets
[ref
.bullet
]
468 for ref
in self
._fire
_refs
:
469 ref
.fire
= self
.fires
[ref
.fire
]
470 for ref
in self
._action
_refs
:
471 ref
.action
= self
.actions
[ref
.action
]
472 except KeyError as exc
:
473 raise ParseError("unknown reference %s" % exc
)
475 del(self
._bullet
_refs
)
476 del(self
._action
_refs
)
479 self
.bullets
.pop(None, None)
480 self
.actions
.pop(None, None)
481 self
.fires
.pop(None, None)
485 """Get a list of all top-level actions."""
486 return [dfn
for name
, dfn
in self
.actions
.iteritems()
487 if name
and name
.startswith("top")]
490 return "%s(type=%r, bullets=%r, actions=%r, fires=%r)" % (
491 type(self
).__name
__, self
.type, self
.bullets
, self
.actions
,
494 ActionDef
.CONSTRUCTORS
= dict(
498 changeSpeed
=ChangeSpeed
,
499 changeDirection
=ChangeDirection
,