clock
[hoa] / hair_on_arm / HairOnArm.py
1 # -*- coding: utf-8 -*-
2
3 from random import randint, randrange, random, choice, shuffle
4 from math import pi, sin, cos, atan, copysign, sqrt, tan
5 from os import listdir
6 from os.path import join
7 from glob import glob
8 from copy import copy
9 from array import array
10
11 from pygame import Rect, Surface, mixer
12 from pygame.draw import line, circle, polygon, aaline, aalines, ellipse
13 from pygame.mixer import find_channel, Sound, set_num_channels
14 from pygame.font import Font
15 from pygame.image import load, save
16 from pygame.locals import *
17
18 from hair_on_arm.pgfw.Game import Game
19 from hair_on_arm.pgfw.GameChild import GameChild
20 from hair_on_arm.pgfw.Sprite import Sprite
21 from hair_on_arm.pgfw.Animation import Animation
22 from hair_on_arm.Samples import Samples
23 from hair_on_arm.SoundEffect import SoundEffect
24
25 class HairOnArm(Game):
26
27 def __init__(self):
28 Game.__init__(self)
29 set_num_channels(16)
30 self.title.activate()
31
32 def set_children(self):
33 Game.set_children(self)
34 self.floor = Floor(self)
35 self.sky = Sky(self)
36 self.gate = Gate(self)
37 self.title = Title(self)
38 self.tour = Tour(self)
39
40 def update(self):
41 self.title.update()
42 self.tour.update()
43
44
45 class Floor(Animation, Rect):
46
47 BUFFER = 4
48
49 def __init__(self, parent):
50 Animation.__init__(self, parent, self.move)
51 self.display_surface = ds = self.get_display_surface()
52 Rect.__init__(self, (0, 0, ds.get_width(), 100))
53 self.bottom = ds.get_height()
54 self.delegate = self.get_game().delegate
55 self.dy_nodeset = self.get_game().interpolator.get_nodeset("floor")
56 self.reset()
57 self.subscribe(self.respond)
58 self.play()
59
60 def move(self):
61 self.y_offset += .5
62 if self.y_offset > self.BUFFER:
63 self.y_offset = 1
64 self.yi_switch = not self.yi_switch
65
66 def reset(self):
67 self.y_offset = 0
68 self.yi_switch = False
69
70 def respond(self, event):
71 if self.delegate.compare(event, "reset-game"):
72 self.reset()
73
74 def update(self):
75 Animation.update(self)
76 ds = self.display_surface
77 ds.set_clip(self)
78 ds.fill((225, 200, 225))
79 buf = self.BUFFER
80 y = self.y_offset
81 step = 58
82 x = range(0, ds.get_width() + step, step)
83 yi = self.yi_switch
84 cx = ds.get_rect().centerx
85 max_angle = pi / 2.1
86 while y < self.h + buf:
87 yw = int(round(self.top + y - buf))
88 dy = self.dy_nodeset.get_y(float(y) / (self.h + buf))
89 for xi in xrange(yi % 2, len(x) - 1, 2):
90 x1_a = tan(abs(x[xi] - cx) / float(cx) * max_angle)
91 x2_a = tan(abs(x[xi + 1] - cx) / float(cx) * max_angle)
92 points = (x[xi] + 1 + copysign(x1_a * y, x[xi] - cx), yw), \
93 (x[xi + 1] - 1 + copysign(x2_a * y, x[xi + 1] - cx),
94 yw), \
95 (x[xi + 1] + copysign(x2_a * (y + dy), x[xi + 1] - cx),
96 yw + dy), \
97 (x[xi] + copysign(x1_a * (y + dy), x[xi] - cx),
98 yw + dy)
99 polygon(ds, (200, 255, 225), points)
100 aalines(ds, (200, 255, 255), True, points)
101 y += dy
102 yi += 1
103 ds.set_clip(None)
104
105
106 class Sky(Animation):
107
108 def __init__(self, parent):
109 Animation.__init__(self, parent, self.swap, 400000)
110 self.display_surface = self.get_display_surface()
111 self.background = load(join(self.get_resource("image",
112 "sky"), "Splat.png")). \
113 convert_alpha()
114 self.mountains = load(self.get_resource("image", "mountains")). \
115 convert_alpha()
116 self.mountains_rect = self.mountains.get_rect()
117 self.mountains_rect.bottom = self.parent.floor.top
118 self.set_bodies()
119 self.set_gradient()
120 self.play()
121
122 def swap(self):
123 self.set_gradient()
124 self.set_bodies()
125
126 def set_bodies(self):
127 self.bodies = bodies = []
128 paths = glob(join(self.get_resource("image", "sky"), "[0-9]*"))
129 shuffle(paths)
130 start = -16
131 for path in paths:
132 bodies.append(Sprite(self, 120))
133 image = load(path).convert_alpha()
134 tiles = []
135 tw = 32
136 for x in xrange(0, image.get_width(), tw):
137 for y in xrange(0, image.get_height(), tw):
138 tile = Surface((tw, tw), SRCALPHA)
139 tile.blit(image, (-x, -y))
140 tiles.append(tile)
141 for ii in xrange(8):
142 shuffle(tiles)
143 frame = Surface(image.get_size(), SRCALPHA)
144 ii = 0
145 for x in xrange(0, image.get_width(), tw):
146 for y in xrange(0, image.get_height(), tw):
147 frame.blit(tiles[ii], (x, y))
148 ii += 1
149 bodies[-1].add_frame(frame)
150 bodies[-1].location.midleft = start, 100
151 start += bodies[-1].location.w + 32
152 if start > self.display_surface.get_width():
153 break
154
155 def set_gradient(self):
156 ds = self.display_surface
157 self.gradient = gradient = Surface((ds.get_width(),
158 self.parent.floor.top), SRCALPHA)
159 color = Color(0, 0, 0)
160 resolution = 40
161 hue, hs = self.get_hue_range(resolution)
162 y = gradient.get_height() - 1
163 for yi, y in enumerate(xrange(y, y - resolution, -1)):
164 color.hsla = hue % 360, \
165 80 + float(resolution - yi) / resolution * 20, \
166 60 + float(resolution - yi) / resolution * 20, 100
167 gradient.fill(color, (0, y, ds.get_width(), 1))
168 hue += hs
169 resolution = 75
170 hue, hs = self.get_hue_range(resolution, hue)
171 for yi, y in enumerate(xrange(y, y - resolution, -1)):
172 ratio = float(resolution - yi) / resolution
173 color.hsla = hue % 360, 60 + ratio * 20, 50 + ratio * 10, 100
174 gradient.fill(color, (0, y, ds.get_width(), 1))
175 hue += hs
176 resolution = 190
177 hue, hs = self.get_hue_range(resolution, hue)
178 for yi, y in enumerate(xrange(y, y - resolution, -1)):
179 ratio = float(resolution - yi) / resolution
180 color.hsla = hue % 360, 30 + ratio * 30, 30 + ratio * 20, \
181 20 + ratio * 80
182 gradient.fill(color, (0, y, ds.get_width(), 1))
183 hue += hs
184 resolution = y
185 hue, hs = self.get_hue_range(y, hue)
186 for yi, y in enumerate(xrange(y, 0, -1)):
187 ratio = float(resolution - yi) / resolution
188 color.hsla = hue % 360, ratio * 30, ratio * 30, ratio * 20
189 gradient.fill(color, (0, y, ds.get_width(), 1))
190 hue += hs
191
192 def get_hue_range(self, resolution, start=None):
193 if start is None:
194 start = randrange(0, 360)
195 return start, float(randrange(30, 90)) / resolution
196
197 def update(self):
198 Animation.update(self)
199 self.display_surface.blit(self.background, (0, 0))
200 for body in self.bodies:
201 body.update()
202 self.display_surface.blit(self.gradient, (0, 0))
203 self.display_surface.blit(self.mountains, self.mountains_rect)
204
205
206 class Title(GameChild):
207
208 def __init__(self, parent):
209 GameChild.__init__(self, parent)
210 self.delegate = self.get_game().delegate
211 self.menu = Menu(self, (500, 400), 18,
212 ("level", self.start, range(1, 11), True,
213 self.set_gate_index),
214 ("invert", self.set_invert, ("off", "on"), True),
215 ("records", self.show_records))
216 self.caster = Caster(self)
217 self.deactivate()
218 self.subscribe(self.respond)
219
220 def start(self, index):
221 self.parent.tour.activate(index)
222 self.deactivate()
223
224 def set_gate_index(self, index):
225 if index == "all":
226 index = 0
227 self.parent.gate.set_image_index(index - 1)
228
229 def set_invert(self, invert):
230 self.parent.invert = False if invert == "off" else True
231
232 def show_records(self):
233 pass
234
235 def deactivate(self):
236 self.active = False
237 self.menu.deactivate()
238
239 def respond(self, event):
240 compare = self.delegate.compare
241 if compare(event, "reset-game"):
242 self.menu.reset()
243 self.activate()
244
245 def activate(self, level_ii=None):
246 self.active = True
247 self.menu.activate()
248 if level_ii is not None:
249 self.menu.elements[0].active_value = level_ii
250
251 def update(self):
252 if self.active:
253 self.caster.update()
254 self.parent.sky.update()
255 self.parent.floor.update()
256 self.parent.gate.update()
257 self.menu.update()
258
259
260 class Caster(GameChild):
261
262 def __init__(self, parent):
263 GameChild.__init__(self, parent)
264 self.time_filter = self.get_game().time_filter
265 volume = .46
266 self.sounds = sounds = [SoundEffect(self, "caster", volume)]
267 for _ in xrange(2):
268 sound = array("h", sounds[0].get_buffer().raw)
269 start = randrange(0, len(sound) / 2 - 40000)
270 for ii in xrange(start, start + 40000, 3):
271 sound.insert(ii, sound[ii])
272 start = randrange(len(sound) / 2, len(sound) - 40000)
273 for ii in xrange(start, start + 40000, 3):
274 sound.insert(ii, sound[ii])
275 sounds.append(SoundEffect(self, sound, volume))
276 self.reset()
277
278 def reset(self):
279 self.playing = False
280 self.elapsed = 0
281 self.set_gap()
282
283 def set_gap(self):
284 self.gap = randint(2000, 5000)
285
286 def update(self):
287 self.elapsed += self.time_filter.get_last_frame_duration()
288 if not self.playing:
289 if self.elapsed >= self.gap:
290 self.elapsed = 0
291 sound = choice(self.sounds)
292 sound.play(random())
293 self.length = sound.get_length() * 1000
294 self.playing = True
295 elif self.elapsed >= self.length:
296 self.reset()
297
298
299 class Menu(Animation):
300
301 def __init__(self, parent, position, font_size, *args):
302 Animation.__init__(self, parent, self.highlight)
303 self.delegate = self.get_game().delegate
304 self.vertical_sfx = SoundEffect(self, "vertical", .2)
305 self.horizontal_sfx = SoundEffect(self, "horizontal", .56)
306 self.arrange(position, font_size, args)
307 self.subscribe(self.respond)
308 self.reset()
309 self.deactivate()
310 self.play()
311
312 def highlight(self):
313 self.highlit = not self.highlit
314
315 def arrange(self, position, font_size, options):
316 self.elements = elements = []
317 font = Font(self.get_resource("display", "font"), font_size)
318 height = 0
319 width = 0
320 margin = 5
321 for option in options:
322 values = [None] if len(option) < 3 else option[2]
323 value_pairs = []
324 for value in values:
325 text = str(option[0])
326 if value is not None:
327 text += " ‡ " + str(value).upper() + " ‡"
328 value_pairs.append((value, font.render(text, True,
329 (90, 90, 140))))
330 ew, eh = value_pairs[0][1].get_size()
331 height += eh + margin
332 if ew > width:
333 width = ew
334 respond_on_rotate = False if len(option) < 4 else option[3]
335 increment_callback = None if len(option) < 5 else option[4]
336 elements.append(Element(self, option[1], value_pairs,
337 respond_on_rotate, increment_callback))
338 x = position[0] - width / 2
339 y = position[1] - height / 2
340 step = elements[0].values[0][1].get_height() + margin
341 for element in elements:
342 element.set_position(x, y)
343 y += step
344
345 def respond(self, event):
346 if self.active:
347 compare = self.delegate.compare
348 if compare(event, ("up", "down")):
349 self.vertical_sfx.play()
350 self.increment_active_element((-1, 1)[compare(event, "down")])
351 elif compare(event, ("right", "left")):
352 active = self.elements[self.active_element]
353 if len(active.values) > 1:
354 self.horizontal_sfx.play()
355 active.increment_active_value((-1, 1)[compare(event, "right")])
356 elif compare(event, "advance"):
357 self.elements[self.active_element].call()
358
359 def increment_active_element(self, increment):
360 self.active_element += increment
361 if self.active_element < 0:
362 self.active_element = len(self.elements) - 1
363 elif self.active_element >= len(self.elements):
364 self.active_element = 0
365
366 def reset(self):
367 self.active_element = 0
368 self.highlit = True
369 for element in self.elements:
370 element.reset()
371
372 def deactivate(self):
373 self.active = False
374
375 def activate(self):
376 self.active = True
377
378 def update(self):
379 if self.active:
380 Animation.update(self)
381 for element in self.elements:
382 element.update(element == self.elements[self.active_element] \
383 and self.highlit)
384
385
386 class Element(GameChild):
387
388 def __init__(self, parent, callback, values, respond_on_increment,
389 increment_callback):
390 GameChild.__init__(self, parent)
391 self.display_surface = self.get_display_surface()
392 self.callback = callback
393 self.values = values
394 self.respond_on_increment = respond_on_increment
395 self.increment_callback = increment_callback
396 self.mask = mask = Surface(self.values[0][1].get_size())
397 mask.fill((255, 255, 255))
398 self.reset()
399
400 def reset(self):
401 self.active_value = 0
402 if self.respond_on_increment:
403 self.call(True)
404
405 def increment_active_value(self, increment):
406 self.active_value += increment
407 if self.active_value < 0:
408 self.active_value = len(self.values) - 1
409 elif self.active_value >= len(self.values):
410 self.active_value = 0
411 if self.respond_on_increment:
412 self.call(True)
413
414 def call(self, increment=False):
415 callback = self.callback
416 if increment and self.increment_callback:
417 callback = self.increment_callback
418 if callback:
419 value = self.values[self.active_value][0]
420 if value is not None:
421 callback(value)
422 else:
423 callback()
424
425 def set_position(self, x, y):
426 self.position = x, y
427
428 def update(self, highlight=False):
429 surface = self.values[self.active_value][1]
430 if highlight:
431 surface = surface.copy()
432 surface.blit(self.mask, (0, 0), None, BLEND_RGB_MAX)
433 self.display_surface.blit(surface, self.position)
434
435
436 class Tour(Animation):
437
438 RADIUS = 220
439
440 def __init__(self, parent):
441 Animation.__init__(self, parent)
442 self.display_surface = ds = self.get_display_surface()
443 self.delegate = self.get_game().delegate
444 self.countdown_beep = SoundEffect(self, "countdown", .7)
445 self.go_beep = SoundEffect(self, "go", .7)
446 self.clock = Clock(self)
447 self.ship = Ship(self)
448 self.axes = Axes(self)
449 self.static = Static(self)
450 self.playing_drums = Sound(self.get_resource("audio", "playing"))
451 self.playing_drums.set_volume(.85)
452 self.in_drums = Sound(self.get_resource("audio", "in"))
453 self.in_drums.set_volume(.85)
454 self.game_over_surface = Surface(ds.get_size(), SRCALPHA)
455 self.game_over_surface.fill((0, 0, 0, 127))
456 font = Font(self.get_resource("display", "font"), 12)
457 self.game_over_text = font.render("G A M E O V E R", True,
458 (255, 255, 0))
459 self.game_over_text_rect = self.game_over_text.get_rect()
460 self.game_over_text_rect.center = ds.get_rect().center
461 self.subscribe(self.respond)
462 self.register(self.fly_in, interval=100)
463 self.register(self.countdown, interval=1000)
464 self.deactivate()
465
466 def respond(self, event):
467 compare = self.delegate.compare
468 if compare(event, "reset-game"):
469 self.deactivate()
470 elif self.active and self.game_over and compare(event, "advance"):
471 self.deactivate()
472 self.parent.title.activate(self.level_ii)
473
474 def fly_in(self):
475 if self.ship.move():
476 self.halt(self.fly_in)
477 self.play(self.countdown)
478 self.in_drums.fadeout(1000)
479 self.clock.activate()
480
481 def countdown(self):
482 if self.countdown_ii < 3:
483 self.clock.set_message_index(self.countdown_ii)
484 self.countdown_beep.play()
485 self.countdown_ii += 1
486 else:
487 self.halt(self.countdown)
488 self.go_beep.play()
489 self.axes.unsuppress()
490 self.clock.ticking = True
491 self.static.activate()
492 self.playing_drums.play(-1, 0, 5000)
493
494 def deactivate(self):
495 self.halt()
496 self.active = False
497 fade = 1000
498 self.static.fade_out(fade)
499 self.playing_drums.fadeout(fade)
500 self.in_drums.fadeout(fade)
501 self.ship.deactivate()
502 self.clock.deactivate()
503
504 def activate(self, index):
505 self.active = True
506 self.game_over = False
507 self.start_level(index)
508
509 def start_level(self, index):
510 self.level_ii = level_ii = index - 1
511 self.nodes = nodes = []
512 new = Node(self)
513 while new.is_hidden():
514 new = Node(self)
515 nodes.append(new)
516 while len(nodes) < (3, 4, 5, 6, 8, 10, 12, 15, 18, 21)[level_ii]:
517 new = Node(self)
518 add = True
519 for node in nodes:
520 if abs(new.x - node.x) < .05 and abs(new.y - node.y) < .05 and \
521 abs(new.z - node.z) < .05:
522 add = False
523 break
524 if add:
525 nodes.append(new)
526 self.axes.suppress()
527 self.in_drums.play(-1, 0, 3000)
528 self.ship.activate()
529 self.countdown_ii = 0
530 self.clock.deactivate()
531 self.play(self.fly_in)
532
533 def end(self):
534 self.axes.suppress()
535 self.game_over = True
536
537 def update(self):
538 if self.active:
539 Animation.update(self)
540 self.static.update()
541 self.parent.sky.update()
542 self.parent.floor.update()
543 x, y, max_magnitude = self.axes.update()
544 for node in self.nodes:
545 node.rotate(x, y, max_magnitude)
546 gate_drawn = False
547 ship_drawn = False
548 for node in sorted(self.nodes, key=lambda n: n.z):
549 if node.z >= 0 and not gate_drawn:
550 self.parent.gate.update()
551 self.clock.update()
552 gate_drawn = True
553 node.update()
554 if self.ship.location[2] < node.z and not ship_drawn:
555 self.ship.update()
556 ship_drawn = True
557 if not gate_drawn:
558 self.parent.gate.update()
559 self.clock.update()
560 if not ship_drawn:
561 self.ship.update()
562 if self.game_over:
563 ds = self.display_surface
564 ds.blit(self.game_over_surface, (0, 0))
565 ds.blit(self.game_over_text, self.game_over_text_rect)
566
567
568 class Clock(GameChild):
569
570 def __init__(self, parent):
571 GameChild.__init__(self, parent)
572 self.display_surface = self.get_display_surface()
573 self.time_filter = self.get_game().time_filter
574 self.messages = [ShadowedText(self, word, 12) for word in \
575 ("three", "two", "one", "zero")]
576 self.rect = self.parent.parent.gate.frame_rect. \
577 inflate(0, -self.messages[0].rect.h). \
578 move(self.parent.parent.gate.frame_rect.w, 0)
579 self.deactivate()
580
581 def deactivate(self):
582 self.active = False
583
584 def activate(self):
585 self.active = True
586 self.elapsed = 0
587 self.limit = 30000 + self.parent.level_ii * 10000
588 self.ticking = False
589
590 def set_message_index(self, index):
591 self.message_ii = index
592
593 def finish(self):
594 self.set_message_index(4)
595 message = ShadowedText("%.2f" % self.elapsed)
596 message.play(message.blink)
597 if len(self.messages) < 5:
598 self.messages.append(message)
599 else:
600 self.messages[4] = message
601 self.ticking = False
602
603 def update(self):
604 if self.active:
605 if self.ticking:
606 self.elapsed += self.time_filter.get_last_frame_duration()
607 if self.elapsed >= self.limit:
608 self.elapsed = self.limit
609 self.set_message_index(3)
610 self.parent.end()
611 self.ticking = False
612 ratio = float(self.elapsed) / self.limit
613 mi = self.message_ii
614 if mi == 0:
615 color = (255, 31, 31)
616 elif mi == 1:
617 color = (255, 255, 31)
618 elif mi == 3:
619 color = (128, 128, 128)
620 else:
621 color = Color(0, 0, 0)
622 color.hsla = int(120 - ratio * 120), 100, 56, 100
623 self.messages[self.message_ii].update(color, self.rect.left + 4,
624 int(round(self.rect.top + \
625 self.rect.h * \
626 ratio)))
627
628
629 class ShadowedText(Animation):
630
631 def __init__(self, parent, text, size):
632 Animation.__init__(self, parent, self.blink, 250)
633 self.display_surface = self.get_display_surface()
634 self.visible = True
635 font = Font(self.get_resource("display", "font"), size)
636 font.set_bold(True)
637 self.surface = surface = font.render(text.upper(), True,
638 (255, 255, 255))
639 self.rect = surface.get_rect()
640
641 def blink(self):
642 self.visible = not self.visible
643
644 def update(self, color, x, y):
645 Animation.update(self)
646 if self.visible:
647 shadow_color = Color(*color)
648 h, s, l, a = shadow_color.hsla
649 shadow_color.hsla = h, abs(s - 24), abs(l - 40), 100
650 plate = Surface(self.rect.size, SRCALPHA)
651 plate.fill(shadow_color)
652 background = self.surface.copy()
653 background.blit(plate, (0, 0), None, BLEND_RGBA_MIN)
654 ds = self.display_surface
655 self.rect.midleft = x, y
656 ds.blit(background, self.rect)
657 plate.fill(color)
658 foreground = self.surface.copy()
659 foreground.blit(plate, (0, 0), None, BLEND_RGBA_MIN)
660 ds.blit(foreground, self.rect.move(1, 0))
661
662
663 class Ship(Animation):
664
665 def __init__(self, parent):
666 Animation.__init__(self, parent, self.spin)
667 self.display_surface = self.get_display_surface()
668 self.max_length = max_length = 4
669 self.play()
670 self.deactivate()
671
672 def deactivate(self):
673 self.active = False
674
675 def activate(self):
676 self.active = True
677 self.lines = [[self.max_length, -1,
678 choice(((0, 255, 128), (255, 255, 0), (200, 90, 255)))] \
679 for ii in xrange(12)]
680 self.location = location = [-self.max_length / 2 - 1,
681 self.display_surface.get_rect().centery, 0]
682 node = choice(self.parent.nodes)
683 while node.is_hidden():
684 node = choice(self.parent.nodes)
685 self.destination = destination = list(node.get_screen_coordinates()) + \
686 [node.z]
687 frame_count = 3000 / self.parent.accounts[self.parent.fly_in]. \
688 interval[0]
689 self.step = [float(destination[0] - location[0]) / frame_count,
690 float(destination[1] - location[1]) / frame_count,
691 float(destination[2] - location[2]) / frame_count]
692
693 def spin(self):
694 step = .5
695 for line in self.lines:
696 line[0] += step * line[1]
697 if line[0] < 0 or line[0] > self.max_length:
698 line[1] = -line[1]
699 line[0] += step * line[1]
700
701 def move(self):
702 location, step, destination = self.location, self.step, self.destination
703 location[0] += step[0]
704 location[1] += step[1]
705 location[2] += step[2]
706 arrived = all(abs(destination[ii] - location[ii]) < abs(step[ii]) \
707 for ii in xrange(len(location)))
708 if arrived:
709 self.deactivate()
710 return arrived
711
712 def update(self):
713 if self.active:
714 Animation.update(self)
715 cx, cy, z = self.location
716 lines = self.lines
717 w = float((z + 1) * 8)
718 step = w / len(lines)
719 x = cx
720 rect = Rect(x - w, cy - self.max_length / 2, w,
721 self.max_length).inflate(10, 14)
722 ds = self.display_surface
723 ds.fill(lines[0][2], rect)
724 ds.fill((0, 0, 0), (rect.topleft, (rect.w, 1)))
725 ds.fill((0, 0, 0), (rect.topleft, (1, rect.h)))
726 ds.fill((255, 255, 255), (rect.right - 1, rect.top, 1, rect.h))
727 ds.fill((255, 255, 255), (rect.left, rect.bottom - 1, rect.w, 1))
728 for member in lines:
729 dy = (z + 1) * member[0]
730 y = cy
731 line(ds, member[2], (x, y - dy), (x, y + dy), 3)
732 x -= step
733
734
735 class Axes(GameChild):
736
737 def __init__(self, parent):
738 GameChild.__init__(self, parent)
739 self.display_surface = self.get_display_surface()
740 self.delegate = self.get_game().delegate
741 self.directions = {}
742 for ii, name in enumerate(("up", "right", "down", "left")):
743 self.directions[name] = Direction(self, pi / 2 * ii)
744 self.waiting = []
745 self.rect = rect = Rect(0, 0, 80, 80)
746 rect.center = 597, 436
747 self.background = background = Sprite(self, 500)
748 color = Color(0, 0, 0)
749 frame_count = 16
750 gradient_count = 8
751 for ii in xrange(frame_count):
752 surface = Surface((rect.w * 2 + 65, rect.h * 2 + 65), SRCALPHA)
753 for jj in xrange(gradient_count):
754 ratio = float(jj + 1) / gradient_count
755 color.hsla = float(ii) / frame_count * 360, 80, 60, ratio * 100
756 circle(surface, color, surface.get_rect().center, \
757 int(round((1 - ratio) * (surface.get_width() / 2))))
758 background.add_frame(surface)
759 background.location.center = self.display_surface.get_rect().bottomright
760 self.subscribe(self.respond)
761
762 def respond(self, event):
763 if self.parent.active and not self.suppressed:
764 compare = self.delegate.compare
765 names = ["up", "right", "down", "left"]
766 for direction in names:
767 if compare(event, direction) or compare(event, direction, True):
768 opposite = names[(names.index(direction) + 2) % len(names)]
769 if event.cancel:
770 self.directions[direction].on = False
771 if direction in self.waiting:
772 self.waiting.remove(direction)
773 if opposite in self.waiting:
774 self.directions[opposite].on = True
775 else:
776 if not self.directions[opposite].on:
777 self.directions[direction].on = True
778 elif direction not in self.waiting:
779 self.waiting.append(direction)
780
781 def suppress(self):
782 self.suppressed = True
783 for direction in self.directions.itervalues():
784 direction.on = False
785
786 def unsuppress(self):
787 self.suppressed = False
788
789 def update(self):
790 self.background.update()
791 tx, ty, max_magnitude = 0, 0, 0
792 invert = (-1, 1)[self.parent.parent.invert]
793 ds = self.display_surface
794 cx, cy = self.rect.center
795 r = self.rect.w / 2
796 for direction in self.directions.itervalues():
797 magnitude = direction.get_magnitude()
798 direction.update()
799 dx = sin(direction.angle) * magnitude * invert
800 dy = -cos(direction.angle) * magnitude * invert
801 line(ds, (0, 0, 255), (cx, cy), (cx + dx * r * invert,
802 cy + dy * r * invert), 1)
803 tx += dx
804 ty += dy
805 if magnitude > max_magnitude:
806 max_magnitude = magnitude
807 angle = atan(float(tx) / ty) if ty else 0
808 x = copysign(sin(angle) * max_magnitude, tx * invert)
809 y = copysign(cos(angle) * max_magnitude, ty * invert)
810 line(ds, (255, 0, 0), (cx, cy), (cx + x * r, cy + y * r), 3)
811 angle = atan(float(ty) / tx) if tx else pi / 2
812 x = copysign(sin(angle), ty)
813 y = copysign(cos(angle), -tx)
814 length = 10
815 line(ds, (255, 255, 0), (cx, cy), (cx + x * length, cy + y * length), 1)
816 return x, y, max_magnitude
817
818
819 class Direction(GameChild):
820
821 def __init__(self, parent, angle):
822 GameChild.__init__(self, parent)
823 self.angle = angle
824 self.on = False
825 self.attack = Transition(self, "roll-start", 0)
826 self.release = Transition(self, "roll-end", -1)
827
828 def get_magnitude(self):
829 return self.attack.get() if self.on else self.release.get()
830
831 def update(self):
832 self.attack.update(self.on)
833 self.release.update(not self.on)
834
835
836 class Transition(GameChild):
837
838 def __init__(self, parent, name, elapsed_index):
839 GameChild.__init__(self, parent)
840 self.nodeset = self.get_game().interpolator.get_nodeset(name)
841 self.elapsed = self.nodeset[elapsed_index].x
842 self.time_filter = self.get_game().time_filter
843
844 def get(self):
845 return self.nodeset.get_y(self.elapsed)
846
847 def update(self, active):
848 if active and self.elapsed < self.nodeset[-1].x:
849 self.elapsed += self.time_filter.get_last_frame_duration()
850 if self.elapsed > self.nodeset[-1].x:
851 self.elapsed = self.nodeset[-1].x
852 elif not active and self.elapsed > 0:
853 self.elapsed -= self.time_filter.get_last_frame_duration()
854 if self.elapsed < 0:
855 self.elapsed = 0
856
857
858 class Static(Animation):
859
860 def __init__(self, parent):
861 Animation.__init__(self, parent)
862 self.loops = [Loop(self, choice(range(8, 12))) for _ in xrange(16)]
863 self.register(self.fade_in)
864 self.register(self.swap, interval=160)
865
866 def fade_in(self):
867 self.volume += .01
868 if self.volume >= 1:
869 self.volume = 1
870 self.play(self.swap)
871 else:
872 self.swap()
873
874 def swap(self):
875 for loop in self.loops:
876 loop.stop()
877 for ii in xrange(3):
878 loop = choice(self.loops)
879 loop.set_volume(choice([x * .02 * self.volume for x in \
880 xrange(2, 20)]))
881 channel = loop.play(-1)
882 channel.set_volume(ii == 0 or ii == 1, ii == 1 or ii == 2)
883 self.register(self.swap,
884 interval=choice([x * 40 for x in xrange(0, 4)]))
885
886 def fade_out(self, fade):
887 self.halt()
888 for loop in self.loops:
889 loop.fadeout(fade)
890
891 def activate(self):
892 self.volume = 0
893 self.play(self.fade_in)
894
895 def update(self):
896 Animation.update(self)
897
898
899 class Loop(Samples):
900
901 def __init__(self, parent, frequency):
902 self.frequency = frequency
903 Samples.__init__(self)
904 self.set_volume(.25)
905
906 def build(self):
907 samples = self.get_empty_array(2 ** self.frequency)
908 t = 0
909 while t < len(samples):
910 ii = 0
911 if t % 3 == 0:
912 while ii < [13, 21, 34][t % 3]:
913 samples[t] = int(choice((sin, cos))(1.0 / (ii + 1) * \
914 (pi / 2)) * \
915 self.amplitude)
916 t += 1
917 if t == len(samples):
918 break
919 ii += 1
920 elif t % 3 == 1:
921 while ii < choice([2 ** x for x in xrange(3, 6)]):
922 samples[t] = int(self.amplitude * 2 / ((ii % 16) + 1) - \
923 self.amplitude)
924 t += 1
925 if t == len(samples):
926 break
927 ii += 1
928 else:
929 while ii < [x ** 2 for x in xrange(5, 9)][t % 4]:
930 samples[t] = int(choice((1, -1)) * \
931 choice([self.amplitude / float(x) for \
932 x in xrange(2, 10)]))
933 t += 1
934 if t == len(samples):
935 break
936 ii += 1
937 return samples
938
939
940 class Node(Sprite):
941
942 def __init__(self, parent):
943 Sprite.__init__(self, parent)
944 self.display_surface = self.get_display_surface()
945 magnitude = random() * .8 + .2
946 self.x = sqrt(random() * magnitude) * choice((1, -1))
947 self.y = sqrt(random() * (magnitude - self.x ** 2)) * choice((1, -1))
948 self.z = sqrt(magnitude - self.x ** 2 - self.y ** 2) * choice((1, -1))
949
950 def rotate(self, x, y, magnitude):
951 k = x, y, 0
952 v = self.x, self.y, self.z
953 th = magnitude * .125
954 c = cos(th)
955 vs = [cc * c for cc in v]
956 s = sin(th)
957 vc = [(k[1] * v[2] - k[2] * v[1]) * s,
958 (k[2] * v[0] - k[0] * v[2]) * s,
959 (k[0] * v[1] - k[1] * v[0]) * s]
960 dp = sum(k[ii] * v[ii] for ii in xrange(len(k)))
961 kd = [cc * dp * (1 - c) for cc in k]
962 self.x, self.y, self.z = (vs[ii] + vc[ii] + kd[ii] for ii in \
963 xrange(len(v)))
964
965 def get_screen_coordinates(self):
966 cx, cy = self.display_surface.get_rect().center
967 x = int(round(cx + self.x * self.parent.RADIUS))
968 y = int(round(cy + self.y * self.parent.RADIUS))
969 return x, y
970
971 def is_hidden(self):
972 return self.z <= 0 and self.parent.parent.gate.frame_rect. \
973 collidepoint(self.get_screen_coordinates())
974
975 def update(self):
976 x, y = self.get_screen_coordinates()
977 circle(self.display_surface,
978 (randint(0, 255), randint(0, 255), randint(0, 255)),
979 self.get_screen_coordinates(), int(round(self.z * 12 + 16)))
980
981
982 class Gate(Animation):
983
984 def __init__(self, parent):
985 Animation.__init__(self, parent, self.show_next_image, 200)
986 self.display_surface = ds = self.get_display_surface()
987 root = self.get_resource("image", "gate")
988 self.images = images = []
989 for name in sorted(listdir(root)):
990 images.append(load(join(root, name)))
991 self.image_index = 0
992 self.image_rect = image_rect = images[0].get_rect()
993 image_rect.center = ds.get_rect().center
994 bevel_width = 2
995 frame_width = 3
996 border_width = 1
997 self.border = border = Sprite(self, 200)
998 for ii in xrange(3):
999 inflation = bevel_width * 2 + frame_width * 2 + border_width * 2
1000 surface = Surface(image_rect.inflate(inflation, inflation).size)
1001 surface.fill(((255, 255, 0), (255, 0, 0), (34, 139, 34))[ii])
1002 border.add_frame(surface)
1003 border.location.center = image_rect.center
1004 self.frame_background = load(self.get_resource("image", "hand"))
1005 inflation = bevel_width * 2 + frame_width * 2
1006 self.frame_surface = Surface(image_rect.inflate(inflation, inflation). \
1007 size)
1008 self.frame_rect = self.frame_surface.get_rect()
1009 self.frame_rect.center = image_rect.center
1010 self.frame_background_offset = 0
1011 self.bevel_surface = Surface(image_rect.inflate(bevel_width * 2,
1012 bevel_width * 2).size)
1013 self.bevel_rect = self.bevel_surface.get_rect()
1014 self.bevel_rect.center = image_rect.center
1015 self.bevel_surface.fill((150, 212, 150))
1016 points = (0, 0), (self.bevel_rect.w - 1, 0), (0, self.bevel_rect.h - 1)
1017 polygon(self.bevel_surface, (40, 120, 40), points)
1018 aaline(self.bevel_surface, (150, 212, 150), (0, self.bevel_rect.h - 1),
1019 (self.bevel_rect.w - 1, 0))
1020 self.screen = screen = Sprite(self, 100)
1021 screen.set_alpha(40)
1022 components = []
1023 for ii in xrange(3):
1024 surface = Surface((4, 2))
1025 surface.fill(((255, 0, 0), (0, 255, 0), (0, 0, 255))[ii])
1026 components.append(surface)
1027 component_rect = components[0].get_rect()
1028 for frame_ii in xrange(3):
1029 frame = Surface(image_rect.size)
1030 for yi, y in enumerate(xrange(0, image_rect.h, component_rect.h)):
1031 offset = (0, -2)[y % 2]
1032 for xi, x in enumerate(xrange(offset, image_rect.w,
1033 component_rect.w)):
1034 frame.blit(components[(frame_ii + yi + xi) % 3], (x, y))
1035 screen.add_frame(frame)
1036 screen.location.center = image_rect.center
1037
1038 def show_next_image(self):
1039 self.image_index += 1
1040 if self.image_index == len(self.images):
1041 self.image_index = 0
1042
1043 def set_image_index(self, index):
1044 if index == -1:
1045 self.play()
1046 else:
1047 self.halt()
1048 self.image_index = index
1049
1050 def update(self):
1051 Animation.update(self)
1052 self.border.update()
1053 self.frame_background_offset -= 2
1054 if self.frame_background_offset < -self.frame_background.get_width():
1055 self.frame_background_offset += self.frame_background.get_width()
1056 self.frame_surface.blit(self.frame_background,
1057 (self.frame_background_offset, 0))
1058 self.frame_surface.blit(self.frame_background,
1059 (self.frame_background_offset + \
1060 self.frame_background.get_width(), 0))
1061 self.display_surface.blit(self.frame_surface, self.frame_rect)
1062 self.display_surface.blit(self.bevel_surface, self.bevel_rect)
1063 self.display_surface.blit(self.images[self.image_index],
1064 self.image_rect)
1065 self.screen.update()