2ca6962c53eaa08f061f5300ecca82a5f0c7bf7e
[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, PixelArray
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.lens = Lens(self)
447 self.clock = Clock(self)
448 self.ship = Ship(self)
449 self.axes = Axes(self)
450 self.static = Static(self)
451 self.playing_drums = Sound(self.get_resource("audio", "playing"))
452 self.playing_drums.set_volume(.85)
453 self.in_drums = Sound(self.get_resource("audio", "in"))
454 self.in_drums.set_volume(.85)
455 self.game_over_surface = Surface(ds.get_size(), SRCALPHA)
456 self.game_over_surface.fill((0, 0, 0, 127))
457 font = Font(self.get_resource("display", "font"), 12)
458 self.game_over_text = font.render("G A M E O V E R", True,
459 (255, 255, 0))
460 self.game_over_text_rect = self.game_over_text.get_rect()
461 self.game_over_text_rect.center = ds.get_rect().center
462 self.game_over_sfx = SoundEffect(self, "game-over", .7)
463 self.landing_sfx = SoundEffect(self, "landing", .7)
464 self.restart_sfx = SoundEffect(self, "restart", .6)
465 self.subscribe(self.respond)
466 self.register(self.fly_in, interval=100)
467 self.register(self.countdown, interval=1000)
468 self.deactivate()
469
470 def respond(self, event):
471 compare = self.delegate.compare
472 if compare(event, "reset-game"):
473 self.deactivate()
474 elif self.active and self.game_over and compare(event, "advance"):
475 self.restart_sfx.play()
476 self.deactivate()
477 self.parent.title.activate(self.level_ii)
478
479 def fly_in(self):
480 if self.ship.move():
481 self.halt(self.fly_in)
482 self.play(self.countdown)
483 self.in_drums.fadeout(1000)
484 self.clock.activate()
485
486 def countdown(self):
487 if self.countdown_ii < 3:
488 self.clock.set_message_index(self.countdown_ii)
489 self.countdown_beep.play()
490 self.countdown_ii += 1
491 else:
492 self.halt(self.countdown)
493 self.go_beep.play()
494 self.axes.unsuppress()
495 self.clock.ticking = True
496 self.static.activate()
497 self.playing_drums.play(-1, 0, 5000)
498
499 def deactivate(self):
500 self.halt()
501 self.active = False
502 self.fade_out()
503 self.ship.deactivate()
504 self.clock.deactivate()
505
506 def fade_out(self, include_static=True):
507 fade = 1000
508 if include_static:
509 self.static.fade_out(fade)
510 self.playing_drums.fadeout(fade)
511 self.in_drums.fadeout(fade)
512
513 def activate(self, index):
514 self.active = True
515 self.game_over = False
516 self.start_level(index)
517
518 def start_level(self, index):
519 self.level_ii = level_ii = index - 1
520 self.nodes = nodes = []
521 new = Node(self)
522 while new.is_hidden():
523 new = Node(self)
524 nodes.append(new)
525 while len(nodes) < (3, 4, 5, 6, 8, 10, 12, 15, 18, 21)[level_ii]:
526 new = Node(self)
527 add = True
528 for node in nodes:
529 if abs(new.x - node.x) < .05 and abs(new.y - node.y) < .05 and \
530 abs(new.z - node.z) < .05:
531 add = False
532 break
533 if add:
534 nodes.append(new)
535 self.axes.suppress()
536 self.in_drums.play(-1, 0, 3000)
537 self.ship.activate()
538 self.countdown_ii = 0
539 self.clock.deactivate()
540 self.play(self.fly_in)
541 self.landing_sfx.play()
542
543 def end(self):
544 self.axes.suppress()
545 self.game_over = True
546 self.game_over_sfx.play(0)
547 self.game_over_sfx.fadeout(1500)
548 self.fade_out(False)
549 self.static.volume = .5
550 self.static.swap()
551
552 def update(self):
553 if self.active:
554 Animation.update(self)
555 self.static.update()
556 self.parent.sky.update()
557 self.parent.floor.update()
558 self.lens.update()
559 x, y, max_magnitude = self.axes.update()
560 for node in self.nodes:
561 node.rotate(x, y, max_magnitude)
562 self.lens.draw_background()
563 gate_drawn = False
564 ship_drawn = False
565 for node in sorted(self.nodes, key=lambda n: n.z):
566 if node.z >= 0 and not gate_drawn:
567 self.parent.gate.update()
568 self.clock.update()
569 self.lens.draw_foreground()
570 gate_drawn = True
571 node.update()
572 if self.ship.location[2] < node.z and not ship_drawn:
573 self.ship.update()
574 ship_drawn = True
575 if not gate_drawn:
576 self.parent.gate.update()
577 self.clock.update()
578 self.lens.draw_foreground()
579 if not ship_drawn:
580 self.ship.update()
581 if self.game_over:
582 ds = self.display_surface
583 ds.blit(self.game_over_surface, (0, 0))
584 ds.blit(self.game_over_text, self.game_over_text_rect)
585
586
587 class Lens(Animation):
588
589 def __init__(self, parent):
590 Animation.__init__(self, parent, self.scan, 800)
591 self.display_surface = ds = self.get_display_surface()
592 self.rim_mask = load(self.get_resource("image",
593 "lens-rim")).convert_alpha()
594 self.rect = rect = self.rim_mask.get_rect()
595 rect.center = ds.get_rect().centerx, \
596 self.parent.parent.gate.frame_rect.top - 60
597 self.shadow_mask = self.rim_mask.subsurface((0, rect.h / 2, rect.w,
598 rect.h - rect.h / 2))
599 self.shadow_rect = self.shadow_mask.get_rect()
600 self.shadow_rect.midbottom = rect.centerx, rect.bottom + 3
601 self.gradient_surfaces = gradient_surfaces = [ \
602 load(self.get_resource("image", "lens-gradient")).convert_alpha()]
603 self.gradient_rect = gradient_surfaces[0].get_rect()
604 self.gradient_rect.center = rect.center
605 for offset in xrange(30, 360, 30):
606 surface = gradient_surfaces[0].copy()
607 for x in xrange(surface.get_width()):
608 for y in xrange(surface.get_height()):
609 color = Color(*surface.get_at((x, y)))
610 h, s, l, a = color.hsla
611 color.hsla = (h + offset) % 360, s, l, a
612 surface.set_at((x, y), color)
613 gradient_surfaces.append(surface)
614 surface = load(self.get_resource("image", "lens-cloud")).convert_alpha()
615 self.clouds_background_surface = surface.copy()
616 pixels = PixelArray(self.clouds_background_surface)
617 for x in xrange(self.clouds_background_surface.get_width()):
618 for y in xrange(self.clouds_background_surface.get_height()):
619 color = Color(*self.clouds_background_surface.get_at((x, y)))
620 h, s, l, a = color.hsla
621 color.hsla = h, s, l, max(0, a - 51)
622 self.clouds_background_surface.set_at((x, y), color)
623 self.clouds_foreground_surface = surface.copy()
624 for x in xrange(self.clouds_foreground_surface.get_width()):
625 for y in xrange(self.clouds_foreground_surface.get_height()):
626 color = Color(*self.clouds_foreground_surface.get_at((x, y)))
627 h, s, l, a = color.hsla
628 color.hsla = h, s, l, max(0, a - 34)
629 self.clouds_foreground_surface.set_at((x, y), color)
630 self.clouds_background_offset = randint(0, surface.get_width() - 1)
631 self.clouds_foreground_offset = randint(0, surface.get_width() - 1)
632 self.clouds_mask = mask = self.gradient_surfaces[0].copy()
633 plate = Surface(mask.get_size(), SRCALPHA)
634 plate.fill((255, 255, 255))
635 mask.blit(plate, (0, 0), None, BLEND_RGB_MAX)
636 self.glass_surface = load(self.get_resource("image", "lens-glass")). \
637 convert_alpha()
638 self.glass_surface_rect = self.glass_surface.get_rect()
639 self.glass_surface_rect.center = self.rect.center
640 self.set_colors()
641
642 def scan(self):
643 pass
644
645 def set_colors(self):
646 self.gradient_surface = choice(self.gradient_surfaces)
647 hue = randrange(0, 360)
648 color = Color(141, 82, 79)
649 h, s, l, a = color.hsla
650 color.hsla = hue, s, l, a
651 self.rim_plate = Surface(self.rect.size, SRCALPHA)
652 self.rim_plate.fill(color)
653 color = Color(68, 40, 39)
654 h, s, l, a = color.hsla
655 color.hsla = hue, s, l, a
656 self.shadow_plate = Surface(self.shadow_mask.get_size(), SRCALPHA)
657 self.shadow_plate.fill(color)
658
659 def update(self):
660 Animation.update(self)
661 self.clouds_background_offset += 1
662 if self.clouds_background_offset >= self.clouds_background_surface. \
663 get_width():
664 self.clouds_background_offset -= self.clouds_background_surface. \
665 get_width()
666 self.clouds_foreground_offset += 2
667 if self.clouds_foreground_offset >= self.clouds_foreground_surface. \
668 get_width():
669 self.clouds_foreground_offset -= self.clouds_foreground_surface. \
670 get_width()
671
672 def draw_background(self):
673 ds = self.display_surface
674 ds.blit(self.glass_surface, self.glass_surface_rect)
675 ds.blit(self.gradient_surface, self.gradient_rect)
676
677 def draw_foreground(self):
678 ds = self.display_surface
679 mask = self.clouds_mask.copy()
680 for offset in (0, -self.clouds_background_surface.get_width()):
681 mask.blit(self.clouds_background_surface,
682 (self.clouds_background_offset + offset, -17), None,
683 BLEND_RGBA_MIN)
684 ds.blit(mask, self.gradient_rect)
685 mask = self.clouds_mask.copy()
686 for offset in (0, -self.clouds_foreground_surface.get_width()):
687 mask.blit(self.clouds_foreground_surface,
688 (self.clouds_foreground_offset + offset, -12), None,
689 BLEND_RGBA_MIN)
690 ds.blit(mask, self.gradient_rect)
691 shadow = self.shadow_mask.copy()
692 shadow.blit(self.shadow_plate, (0, 0), None, BLEND_RGBA_MIN)
693 ds.blit(shadow, self.shadow_rect)
694 rim = self.rim_mask.copy()
695 rim.blit(self.rim_plate, (0, 0), None, BLEND_RGBA_MIN)
696 ds.blit(rim, self.rect)
697
698
699 class Clock(GameChild):
700
701 def __init__(self, parent):
702 GameChild.__init__(self, parent)
703 self.display_surface = self.get_display_surface()
704 self.time_filter = self.get_game().time_filter
705 self.messages = [ShadowedText(self, word, 12) for word in \
706 ("three", "two", "one", "zero")]
707 self.rect = self.parent.parent.gate.frame_rect. \
708 inflate(0, -self.messages[0].rect.h). \
709 move(self.parent.parent.gate.frame_rect.w, 0)
710 self.deactivate()
711
712 def deactivate(self):
713 self.active = False
714
715 def activate(self):
716 self.active = True
717 self.elapsed = 0
718 self.limit = 30000 + self.parent.level_ii * 10000
719 self.ticking = False
720
721 def set_message_index(self, index):
722 self.message_ii = index
723
724 def finish(self):
725 self.set_message_index(4)
726 message = ShadowedText("%.2f" % self.elapsed)
727 message.play(message.blink)
728 if len(self.messages) < 5:
729 self.messages.append(message)
730 else:
731 self.messages[4] = message
732 self.ticking = False
733
734 def update(self):
735 if self.active:
736 if self.ticking:
737 self.elapsed += self.time_filter.get_last_frame_duration()
738 if self.elapsed >= self.limit:
739 self.elapsed = self.limit
740 self.set_message_index(3)
741 self.parent.end()
742 self.ticking = False
743 ratio = float(self.elapsed) / self.limit
744 mi = self.message_ii
745 if mi == 0:
746 color = (255, 31, 31)
747 elif mi == 1:
748 color = (255, 255, 31)
749 elif mi == 3:
750 color = (128, 128, 128)
751 else:
752 color = Color(0, 0, 0)
753 color.hsla = int(120 - ratio * 120), 100, 56, 100
754 self.messages[self.message_ii].update(color, self.rect.left + 4,
755 int(round(self.rect.top + \
756 self.rect.h * \
757 ratio)))
758
759
760 class ShadowedText(Animation):
761
762 def __init__(self, parent, text, size):
763 Animation.__init__(self, parent, self.blink, 250)
764 self.display_surface = self.get_display_surface()
765 self.visible = True
766 font = Font(self.get_resource("display", "font"), size)
767 font.set_bold(True)
768 self.surface = surface = font.render(text.upper(), True,
769 (255, 255, 255))
770 self.rect = surface.get_rect()
771
772 def blink(self):
773 self.visible = not self.visible
774
775 def update(self, color, x, y):
776 Animation.update(self)
777 if self.visible:
778 shadow_color = Color(*color)
779 h, s, l, a = shadow_color.hsla
780 shadow_color.hsla = h, abs(s - 24), abs(l - 40), 100
781 plate = Surface(self.rect.size, SRCALPHA)
782 plate.fill(shadow_color)
783 background = self.surface.copy()
784 background.blit(plate, (0, 0), None, BLEND_RGBA_MIN)
785 ds = self.display_surface
786 self.rect.midleft = x, y
787 ds.blit(background, self.rect)
788 plate.fill(color)
789 foreground = self.surface.copy()
790 foreground.blit(plate, (0, 0), None, BLEND_RGBA_MIN)
791 ds.blit(foreground, self.rect.move(1, 0))
792
793
794 class Ship(Animation):
795
796 def __init__(self, parent):
797 Animation.__init__(self, parent, self.spin)
798 self.display_surface = self.get_display_surface()
799 self.max_length = max_length = 4
800 self.text = ShadowedText(self, "the", 12)
801 self.play()
802 self.deactivate()
803
804 def deactivate(self):
805 self.active = False
806
807 def activate(self):
808 self.active = True
809 self.lines = [[self.max_length, -1,
810 choice(((0, 255, 128), (255, 255, 0), (200, 90, 255)))] \
811 for ii in xrange(8)]
812 self.location = location = [-self.max_length / 2 - 1,
813 self.display_surface.get_rect().centery, 0]
814 node = choice(self.parent.nodes)
815 while node.is_hidden():
816 node = choice(self.parent.nodes)
817 self.destination = destination = list(node.get_screen_coordinates()) + \
818 [node.z]
819 frame_count = 3000 / self.parent.accounts[self.parent.fly_in]. \
820 interval[0]
821 self.step = [float(destination[0] - location[0]) / frame_count,
822 float(destination[1] - location[1]) / frame_count,
823 float(destination[2] - location[2]) / frame_count]
824
825 def spin(self):
826 step = .5
827 for line in self.lines:
828 line[0] += step * line[1]
829 if line[0] < 0 or line[0] > self.max_length:
830 line[1] = -line[1]
831 line[0] += step * line[1]
832
833 def move(self):
834 location, step, destination = self.location, self.step, self.destination
835 location[0] += step[0]
836 location[1] += step[1]
837 location[2] += step[2]
838 arrived = all(abs(destination[ii] - location[ii]) < abs(step[ii]) \
839 for ii in xrange(len(location)))
840 if arrived:
841 self.deactivate()
842 return arrived
843
844 def update(self):
845 if self.active:
846 Animation.update(self)
847 cx, cy, z = self.location
848 lines = self.lines
849 w = float((z + 1) * 8)
850 step = w / len(lines)
851 x = cx
852 rect = Rect(x - w, cy - self.max_length / 2, w,
853 self.max_length).inflate(10, 14)
854 ds = self.display_surface
855 ds.fill(lines[0][2], rect)
856 ds.fill((0, 0, 0), (rect.topleft, (rect.w, 1)))
857 ds.fill((0, 0, 0), (rect.topleft, (1, rect.h)))
858 ds.fill((255, 255, 255), (rect.right - 1, rect.top, 1, rect.h))
859 ds.fill((255, 255, 255), (rect.left, rect.bottom - 1, rect.w, 1))
860 for member in lines:
861 dy = (z + 1) * member[0]
862 line(ds, member[2], (x, cy - dy), (x, cy + dy), 3)
863 x -= step
864 self.text.update((127, 127, 255),
865 rect.left - self.text.rect.w - 7, cy)
866
867
868 class Axes(GameChild):
869
870 def __init__(self, parent):
871 GameChild.__init__(self, parent)
872 self.display_surface = self.get_display_surface()
873 self.delegate = self.get_game().delegate
874 self.directions = {}
875 for ii, name in enumerate(("up", "right", "down", "left")):
876 self.directions[name] = Direction(self, pi / 2 * ii)
877 self.waiting = []
878 self.rect = rect = Rect(0, 0, 80, 80)
879 rect.center = 597, 436
880 self.background = background = Sprite(self, 500)
881 color = Color(0, 0, 0)
882 frame_count = 16
883 gradient_count = 8
884 for ii in xrange(frame_count):
885 surface = Surface((rect.w * 2 + 65, rect.h * 2 + 65), SRCALPHA)
886 for jj in xrange(gradient_count):
887 ratio = float(jj + 1) / gradient_count
888 color.hsla = float(ii) / frame_count * 360, 80, 60, ratio * 100
889 circle(surface, color, surface.get_rect().center, \
890 int(round((1 - ratio) * (surface.get_width() / 2))))
891 background.add_frame(surface)
892 background.location.center = self.display_surface.get_rect().bottomright
893 self.subscribe(self.respond)
894
895 def respond(self, event):
896 if self.parent.active and not self.suppressed:
897 compare = self.delegate.compare
898 names = ["up", "right", "down", "left"]
899 for direction in names:
900 if compare(event, direction) or compare(event, direction, True):
901 opposite = names[(names.index(direction) + 2) % len(names)]
902 if event.cancel:
903 self.directions[direction].on = False
904 if direction in self.waiting:
905 self.waiting.remove(direction)
906 if opposite in self.waiting:
907 self.directions[opposite].on = True
908 else:
909 if not self.directions[opposite].on:
910 self.directions[direction].on = True
911 elif direction not in self.waiting:
912 self.waiting.append(direction)
913
914 def suppress(self):
915 self.suppressed = True
916 for direction in self.directions.itervalues():
917 direction.on = False
918
919 def unsuppress(self):
920 self.suppressed = False
921
922 def update(self):
923 self.background.update()
924 tx, ty, max_magnitude = 0, 0, 0
925 invert = (-1, 1)[self.parent.parent.invert]
926 ds = self.display_surface
927 cx, cy = self.rect.center
928 r = self.rect.w / 2
929 for direction in self.directions.itervalues():
930 magnitude = direction.get_magnitude()
931 direction.update()
932 dx = sin(direction.angle) * magnitude * invert
933 dy = -cos(direction.angle) * magnitude * invert
934 line(ds, (0, 0, 255), (cx, cy), (cx + dx * r * invert,
935 cy + dy * r * invert), 1)
936 tx += dx
937 ty += dy
938 if magnitude > max_magnitude:
939 max_magnitude = magnitude
940 angle = atan(float(tx) / ty) if ty else 0
941 x = copysign(sin(angle) * max_magnitude, tx * invert)
942 y = copysign(cos(angle) * max_magnitude, ty * invert)
943 line(ds, (255, 0, 0), (cx, cy), (cx + x * r, cy + y * r), 3)
944 angle = atan(float(ty) / tx) if tx else pi / 2
945 x = copysign(sin(angle), ty)
946 y = copysign(cos(angle), -tx)
947 length = 10
948 line(ds, (255, 255, 0), (cx, cy), (cx + x * length, cy + y * length), 1)
949 return x, y, max_magnitude
950
951
952 class Direction(GameChild):
953
954 def __init__(self, parent, angle):
955 GameChild.__init__(self, parent)
956 self.angle = angle
957 self.on = False
958 self.attack = Transition(self, "roll-start", 0)
959 self.release = Transition(self, "roll-end", -1)
960
961 def get_magnitude(self):
962 return self.attack.get() if self.on else self.release.get()
963
964 def update(self):
965 self.attack.update(self.on)
966 self.release.update(not self.on)
967
968
969 class Transition(GameChild):
970
971 def __init__(self, parent, name, elapsed_index):
972 GameChild.__init__(self, parent)
973 self.nodeset = self.get_game().interpolator.get_nodeset(name)
974 self.elapsed = self.nodeset[elapsed_index].x
975 self.time_filter = self.get_game().time_filter
976
977 def get(self):
978 return self.nodeset.get_y(self.elapsed)
979
980 def update(self, active):
981 if active and self.elapsed < self.nodeset[-1].x:
982 self.elapsed += self.time_filter.get_last_frame_duration()
983 if self.elapsed > self.nodeset[-1].x:
984 self.elapsed = self.nodeset[-1].x
985 elif not active and self.elapsed > 0:
986 self.elapsed -= self.time_filter.get_last_frame_duration()
987 if self.elapsed < 0:
988 self.elapsed = 0
989
990
991 class Static(Animation):
992
993 def __init__(self, parent):
994 Animation.__init__(self, parent)
995 self.loops = [Loop(self, choice(range(8, 12))) for _ in xrange(16)]
996 self.register(self.fade_in)
997 self.register(self.swap, interval=160)
998
999 def fade_in(self):
1000 self.volume += .01
1001 if self.volume >= 1:
1002 self.volume = 1
1003 self.play(self.swap)
1004 self.halt(self.fade_in)
1005 else:
1006 self.swap()
1007
1008 def swap(self):
1009 for loop in self.loops:
1010 loop.stop()
1011 for ii in xrange(3):
1012 loop = choice(self.loops)
1013 loop.set_volume(choice([x * .02 * self.volume for x in \
1014 xrange(2, 20)]))
1015 channel = loop.play(-1)
1016 channel.set_volume(ii == 0 or ii == 1, ii == 1 or ii == 2)
1017 self.register(self.swap,
1018 interval=choice([x * 40 for x in xrange(0, 4)]))
1019
1020 def fade_out(self, fade):
1021 self.halt()
1022 for loop in self.loops:
1023 loop.fadeout(fade)
1024
1025 def activate(self):
1026 self.volume = 0
1027 self.play(self.fade_in)
1028
1029 def update(self):
1030 Animation.update(self)
1031
1032
1033 class Loop(Samples):
1034
1035 def __init__(self, parent, frequency):
1036 self.frequency = frequency
1037 Samples.__init__(self)
1038 self.set_volume(.25)
1039
1040 def build(self):
1041 samples = self.get_empty_array(2 ** self.frequency)
1042 t = 0
1043 while t < len(samples):
1044 ii = 0
1045 if t % 3 == 0:
1046 while ii < [13, 21, 34][t % 3]:
1047 samples[t] = int(choice((sin, cos))(1.0 / (ii + 1) * \
1048 (pi / 2)) * \
1049 self.amplitude)
1050 t += 1
1051 if t == len(samples):
1052 break
1053 ii += 1
1054 elif t % 3 == 1:
1055 while ii < choice([2 ** x for x in xrange(3, 6)]):
1056 samples[t] = int(self.amplitude * 2 / ((ii % 16) + 1) - \
1057 self.amplitude)
1058 t += 1
1059 if t == len(samples):
1060 break
1061 ii += 1
1062 else:
1063 while ii < [x ** 2 for x in xrange(5, 9)][t % 4]:
1064 samples[t] = int(choice((1, -1)) * \
1065 choice([self.amplitude / float(x) for \
1066 x in xrange(2, 10)]))
1067 t += 1
1068 if t == len(samples):
1069 break
1070 ii += 1
1071 return samples
1072
1073
1074 class Node(Sprite):
1075
1076 def __init__(self, parent):
1077 Sprite.__init__(self, parent)
1078 self.display_surface = self.get_display_surface()
1079 magnitude = random() * .8 + .2
1080 self.x = sqrt(random() * magnitude) * choice((1, -1))
1081 self.y = sqrt(random() * (magnitude - self.x ** 2)) * choice((1, -1))
1082 self.z = sqrt(magnitude - self.x ** 2 - self.y ** 2) * choice((1, -1))
1083
1084 def rotate(self, x, y, magnitude):
1085 k = x, y, 0
1086 v = self.x, self.y, self.z
1087 th = magnitude * .125
1088 c = cos(th)
1089 vs = [cc * c for cc in v]
1090 s = sin(th)
1091 vc = [(k[1] * v[2] - k[2] * v[1]) * s,
1092 (k[2] * v[0] - k[0] * v[2]) * s,
1093 (k[0] * v[1] - k[1] * v[0]) * s]
1094 dp = sum(k[ii] * v[ii] for ii in xrange(len(k)))
1095 kd = [cc * dp * (1 - c) for cc in k]
1096 self.x, self.y, self.z = (vs[ii] + vc[ii] + kd[ii] for ii in \
1097 xrange(len(v)))
1098
1099 def get_screen_coordinates(self):
1100 cx, cy = self.display_surface.get_rect().center
1101 x = int(round(cx + self.x * self.parent.RADIUS))
1102 y = int(round(cy + self.y * self.parent.RADIUS))
1103 return x, y
1104
1105 def is_hidden(self):
1106 return self.z <= 0 and self.parent.parent.gate.frame_rect. \
1107 collidepoint(self.get_screen_coordinates())
1108
1109 def update(self):
1110 x, y = self.get_screen_coordinates()
1111 circle(self.display_surface,
1112 (randint(0, 255), randint(0, 255), randint(0, 255)),
1113 self.get_screen_coordinates(), int(round(self.z * 12 + 16)))
1114
1115
1116 class Gate(Animation):
1117
1118 def __init__(self, parent):
1119 Animation.__init__(self, parent, self.show_next_image, 200)
1120 self.display_surface = ds = self.get_display_surface()
1121 root = self.get_resource("image", "gate")
1122 self.images = images = []
1123 for name in sorted(listdir(root)):
1124 images.append(load(join(root, name)))
1125 self.image_index = 0
1126 self.image_rect = image_rect = images[0].get_rect()
1127 image_rect.center = ds.get_rect().center
1128 bevel_width = 2
1129 frame_width = 3
1130 border_width = 1
1131 self.border = border = Sprite(self, 200)
1132 for ii in xrange(3):
1133 inflation = bevel_width * 2 + frame_width * 2 + border_width * 2
1134 surface = Surface(image_rect.inflate(inflation, inflation).size)
1135 surface.fill(((255, 255, 0), (255, 0, 0), (34, 139, 34))[ii])
1136 border.add_frame(surface)
1137 border.location.center = image_rect.center
1138 self.frame_background = load(self.get_resource("image", "hand"))
1139 inflation = bevel_width * 2 + frame_width * 2
1140 self.frame_surface = Surface(image_rect.inflate(inflation, inflation). \
1141 size)
1142 self.frame_rect = self.frame_surface.get_rect()
1143 self.frame_rect.center = image_rect.center
1144 self.frame_background_offset = 0
1145 self.bevel_surface = Surface(image_rect.inflate(bevel_width * 2,
1146 bevel_width * 2).size)
1147 self.bevel_rect = self.bevel_surface.get_rect()
1148 self.bevel_rect.center = image_rect.center
1149 self.bevel_surface.fill((150, 212, 150))
1150 points = (0, 0), (self.bevel_rect.w - 1, 0), (0, self.bevel_rect.h - 1)
1151 polygon(self.bevel_surface, (40, 120, 40), points)
1152 aaline(self.bevel_surface, (150, 212, 150), (0, self.bevel_rect.h - 1),
1153 (self.bevel_rect.w - 1, 0))
1154 self.screen = screen = Sprite(self, 100)
1155 screen.set_alpha(40)
1156 components = []
1157 for ii in xrange(3):
1158 surface = Surface((4, 2))
1159 surface.fill(((255, 0, 0), (0, 255, 0), (0, 0, 255))[ii])
1160 components.append(surface)
1161 component_rect = components[0].get_rect()
1162 for frame_ii in xrange(3):
1163 frame = Surface(image_rect.size)
1164 for yi, y in enumerate(xrange(0, image_rect.h, component_rect.h)):
1165 offset = (0, -2)[y % 2]
1166 for xi, x in enumerate(xrange(offset, image_rect.w,
1167 component_rect.w)):
1168 frame.blit(components[(frame_ii + yi + xi) % 3], (x, y))
1169 screen.add_frame(frame)
1170 screen.location.center = image_rect.center
1171
1172 def show_next_image(self):
1173 self.image_index += 1
1174 if self.image_index == len(self.images):
1175 self.image_index = 0
1176
1177 def set_image_index(self, index):
1178 if index == -1:
1179 self.play()
1180 else:
1181 self.halt()
1182 self.image_index = index
1183
1184 def update(self):
1185 Animation.update(self)
1186 self.border.update()
1187 self.frame_background_offset -= 2
1188 if self.frame_background_offset < -self.frame_background.get_width():
1189 self.frame_background_offset += self.frame_background.get_width()
1190 self.frame_surface.blit(self.frame_background,
1191 (self.frame_background_offset, 0))
1192 self.frame_surface.blit(self.frame_background,
1193 (self.frame_background_offset + \
1194 self.frame_background.get_width(), 0))
1195 self.display_surface.blit(self.frame_surface, self.frame_rect)
1196 self.display_surface.blit(self.bevel_surface, self.bevel_rect)
1197 self.display_surface.blit(self.images[self.image_index],
1198 self.image_rect)
1199 self.screen.update()