glass edge
[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.glass_edge_surface = load(self.get_resource("image",
641 "lens-glass-edge")). \
642 convert_alpha()
643 self.set_colors()
644
645 def scan(self):
646 pass
647
648 def set_colors(self):
649 self.gradient_surface = choice(self.gradient_surfaces)
650 hue = randrange(0, 360)
651 color = Color(141, 82, 79)
652 h, s, l, a = color.hsla
653 color.hsla = hue, s, l, a
654 self.rim_plate = Surface(self.rect.size, SRCALPHA)
655 self.rim_plate.fill(color)
656 color = Color(68, 40, 39)
657 h, s, l, a = color.hsla
658 color.hsla = hue, s, l, a
659 self.shadow_plate = Surface(self.shadow_mask.get_size(), SRCALPHA)
660 self.shadow_plate.fill(color)
661
662 def update(self):
663 Animation.update(self)
664 self.clouds_background_offset += 1
665 if self.clouds_background_offset >= self.clouds_background_surface. \
666 get_width():
667 self.clouds_background_offset -= self.clouds_background_surface. \
668 get_width()
669 self.clouds_foreground_offset += 2
670 if self.clouds_foreground_offset >= self.clouds_foreground_surface. \
671 get_width():
672 self.clouds_foreground_offset -= self.clouds_foreground_surface. \
673 get_width()
674
675 def draw_background(self):
676 ds = self.display_surface
677 ds.blit(self.glass_surface, self.glass_surface_rect)
678 ds.blit(self.gradient_surface, self.gradient_rect)
679
680 def draw_foreground(self):
681 ds = self.display_surface
682 mask = self.clouds_mask.copy()
683 for offset in (0, -self.clouds_background_surface.get_width()):
684 mask.blit(self.clouds_background_surface,
685 (self.clouds_background_offset + offset, -17), None,
686 BLEND_RGBA_MIN)
687 ds.blit(mask, self.gradient_rect)
688 mask = self.clouds_mask.copy()
689 for offset in (0, -self.clouds_foreground_surface.get_width()):
690 mask.blit(self.clouds_foreground_surface,
691 (self.clouds_foreground_offset + offset, -12), None,
692 BLEND_RGBA_MIN)
693 ds.blit(mask, self.gradient_rect)
694 shadow = self.shadow_mask.copy()
695 shadow.blit(self.shadow_plate, (0, 0), None, BLEND_RGBA_MIN)
696 ds.blit(shadow, self.shadow_rect)
697 rim = self.rim_mask.copy()
698 rim.blit(self.rim_plate, (0, 0), None, BLEND_RGBA_MIN)
699 ds.blit(rim, self.rect)
700 ds.blit(self.glass_edge_surface, self.glass_surface_rect)
701
702
703 class Clock(GameChild):
704
705 def __init__(self, parent):
706 GameChild.__init__(self, parent)
707 self.display_surface = self.get_display_surface()
708 self.time_filter = self.get_game().time_filter
709 self.messages = [ShadowedText(self, word, 12) for word in \
710 ("three", "two", "one", "zero")]
711 self.rect = self.parent.parent.gate.frame_rect. \
712 inflate(0, -self.messages[0].rect.h). \
713 move(self.parent.parent.gate.frame_rect.w, 0)
714 self.deactivate()
715
716 def deactivate(self):
717 self.active = False
718
719 def activate(self):
720 self.active = True
721 self.elapsed = 0
722 self.limit = 30000 + self.parent.level_ii * 10000
723 self.ticking = False
724
725 def set_message_index(self, index):
726 self.message_ii = index
727
728 def finish(self):
729 self.set_message_index(4)
730 message = ShadowedText("%.2f" % self.elapsed)
731 message.play(message.blink)
732 if len(self.messages) < 5:
733 self.messages.append(message)
734 else:
735 self.messages[4] = message
736 self.ticking = False
737
738 def update(self):
739 if self.active:
740 if self.ticking:
741 self.elapsed += self.time_filter.get_last_frame_duration()
742 if self.elapsed >= self.limit:
743 self.elapsed = self.limit
744 self.set_message_index(3)
745 self.parent.end()
746 self.ticking = False
747 ratio = float(self.elapsed) / self.limit
748 mi = self.message_ii
749 if mi == 0:
750 color = (255, 31, 31)
751 elif mi == 1:
752 color = (255, 255, 31)
753 elif mi == 3:
754 color = (128, 128, 128)
755 else:
756 color = Color(0, 0, 0)
757 color.hsla = int(120 - ratio * 120), 100, 56, 100
758 self.messages[self.message_ii].update(color, self.rect.left + 4,
759 int(round(self.rect.top + \
760 self.rect.h * \
761 ratio)))
762
763
764 class ShadowedText(Animation):
765
766 def __init__(self, parent, text, size):
767 Animation.__init__(self, parent, self.blink, 250)
768 self.display_surface = self.get_display_surface()
769 self.visible = True
770 font = Font(self.get_resource("display", "font"), size)
771 font.set_bold(True)
772 self.surface = surface = font.render(text.upper(), True,
773 (255, 255, 255))
774 self.rect = surface.get_rect()
775
776 def blink(self):
777 self.visible = not self.visible
778
779 def update(self, color, x, y):
780 Animation.update(self)
781 if self.visible:
782 shadow_color = Color(*color)
783 h, s, l, a = shadow_color.hsla
784 shadow_color.hsla = h, abs(s - 24), abs(l - 40), 100
785 plate = Surface(self.rect.size, SRCALPHA)
786 plate.fill(shadow_color)
787 background = self.surface.copy()
788 background.blit(plate, (0, 0), None, BLEND_RGBA_MIN)
789 ds = self.display_surface
790 self.rect.midleft = x, y
791 ds.blit(background, self.rect)
792 plate.fill(color)
793 foreground = self.surface.copy()
794 foreground.blit(plate, (0, 0), None, BLEND_RGBA_MIN)
795 ds.blit(foreground, self.rect.move(1, 0))
796
797
798 class Ship(Animation):
799
800 def __init__(self, parent):
801 Animation.__init__(self, parent, self.spin)
802 self.display_surface = self.get_display_surface()
803 self.max_length = max_length = 4
804 self.text = ShadowedText(self, "the", 12)
805 self.play()
806 self.deactivate()
807
808 def deactivate(self):
809 self.active = False
810
811 def activate(self):
812 self.active = True
813 self.lines = [[self.max_length, -1,
814 choice(((0, 255, 128), (255, 255, 0), (200, 90, 255)))] \
815 for ii in xrange(8)]
816 self.location = location = [-self.max_length / 2 - 1,
817 self.display_surface.get_rect().centery, 0]
818 node = choice(self.parent.nodes)
819 while node.is_hidden():
820 node = choice(self.parent.nodes)
821 self.destination = destination = list(node.get_screen_coordinates()) + \
822 [node.z]
823 frame_count = 3000 / self.parent.accounts[self.parent.fly_in]. \
824 interval[0]
825 self.step = [float(destination[0] - location[0]) / frame_count,
826 float(destination[1] - location[1]) / frame_count,
827 float(destination[2] - location[2]) / frame_count]
828
829 def spin(self):
830 step = .5
831 for line in self.lines:
832 line[0] += step * line[1]
833 if line[0] < 0 or line[0] > self.max_length:
834 line[1] = -line[1]
835 line[0] += step * line[1]
836
837 def move(self):
838 location, step, destination = self.location, self.step, self.destination
839 location[0] += step[0]
840 location[1] += step[1]
841 location[2] += step[2]
842 arrived = all(abs(destination[ii] - location[ii]) < abs(step[ii]) \
843 for ii in xrange(len(location)))
844 if arrived:
845 self.deactivate()
846 return arrived
847
848 def update(self):
849 if self.active:
850 Animation.update(self)
851 cx, cy, z = self.location
852 lines = self.lines
853 w = float((z + 1) * 8)
854 step = w / len(lines)
855 x = cx
856 rect = Rect(x - w, cy - self.max_length / 2, w,
857 self.max_length).inflate(10, 14)
858 ds = self.display_surface
859 ds.fill(lines[0][2], rect)
860 ds.fill((0, 0, 0), (rect.topleft, (rect.w, 1)))
861 ds.fill((0, 0, 0), (rect.topleft, (1, rect.h)))
862 ds.fill((255, 255, 255), (rect.right - 1, rect.top, 1, rect.h))
863 ds.fill((255, 255, 255), (rect.left, rect.bottom - 1, rect.w, 1))
864 for member in lines:
865 dy = (z + 1) * member[0]
866 line(ds, member[2], (x, cy - dy), (x, cy + dy), 3)
867 x -= step
868 self.text.update((127, 127, 255),
869 rect.left - self.text.rect.w - 7, cy)
870
871
872 class Axes(GameChild):
873
874 def __init__(self, parent):
875 GameChild.__init__(self, parent)
876 self.display_surface = self.get_display_surface()
877 self.delegate = self.get_game().delegate
878 self.directions = {}
879 for ii, name in enumerate(("up", "right", "down", "left")):
880 self.directions[name] = Direction(self, pi / 2 * ii)
881 self.waiting = []
882 self.rect = rect = Rect(0, 0, 80, 80)
883 rect.center = 597, 436
884 self.background = background = Sprite(self, 500)
885 color = Color(0, 0, 0)
886 frame_count = 16
887 gradient_count = 8
888 for ii in xrange(frame_count):
889 surface = Surface((rect.w * 2 + 65, rect.h * 2 + 65), SRCALPHA)
890 for jj in xrange(gradient_count):
891 ratio = float(jj + 1) / gradient_count
892 color.hsla = float(ii) / frame_count * 360, 80, 60, ratio * 100
893 circle(surface, color, surface.get_rect().center, \
894 int(round((1 - ratio) * (surface.get_width() / 2))))
895 background.add_frame(surface)
896 background.location.center = self.display_surface.get_rect().bottomright
897 self.subscribe(self.respond)
898
899 def respond(self, event):
900 if self.parent.active and not self.suppressed:
901 compare = self.delegate.compare
902 names = ["up", "right", "down", "left"]
903 for direction in names:
904 if compare(event, direction) or compare(event, direction, True):
905 opposite = names[(names.index(direction) + 2) % len(names)]
906 if event.cancel:
907 self.directions[direction].on = False
908 if direction in self.waiting:
909 self.waiting.remove(direction)
910 if opposite in self.waiting:
911 self.directions[opposite].on = True
912 else:
913 if not self.directions[opposite].on:
914 self.directions[direction].on = True
915 elif direction not in self.waiting:
916 self.waiting.append(direction)
917
918 def suppress(self):
919 self.suppressed = True
920 for direction in self.directions.itervalues():
921 direction.on = False
922
923 def unsuppress(self):
924 self.suppressed = False
925
926 def update(self):
927 self.background.update()
928 tx, ty, max_magnitude = 0, 0, 0
929 invert = (-1, 1)[self.parent.parent.invert]
930 ds = self.display_surface
931 cx, cy = self.rect.center
932 r = self.rect.w / 2
933 for direction in self.directions.itervalues():
934 magnitude = direction.get_magnitude()
935 direction.update()
936 dx = sin(direction.angle) * magnitude * invert
937 dy = -cos(direction.angle) * magnitude * invert
938 line(ds, (0, 0, 255), (cx, cy), (cx + dx * r * invert,
939 cy + dy * r * invert), 1)
940 tx += dx
941 ty += dy
942 if magnitude > max_magnitude:
943 max_magnitude = magnitude
944 angle = atan(float(tx) / ty) if ty else 0
945 x = copysign(sin(angle) * max_magnitude, tx * invert)
946 y = copysign(cos(angle) * max_magnitude, ty * invert)
947 line(ds, (255, 0, 0), (cx, cy), (cx + x * r, cy + y * r), 3)
948 angle = atan(float(ty) / tx) if tx else pi / 2
949 x = copysign(sin(angle), ty)
950 y = copysign(cos(angle), -tx)
951 length = 10
952 line(ds, (255, 255, 0), (cx, cy), (cx + x * length, cy + y * length), 1)
953 return x, y, max_magnitude
954
955
956 class Direction(GameChild):
957
958 def __init__(self, parent, angle):
959 GameChild.__init__(self, parent)
960 self.angle = angle
961 self.on = False
962 self.attack = Transition(self, "roll-start", 0)
963 self.release = Transition(self, "roll-end", -1)
964
965 def get_magnitude(self):
966 return self.attack.get() if self.on else self.release.get()
967
968 def update(self):
969 self.attack.update(self.on)
970 self.release.update(not self.on)
971
972
973 class Transition(GameChild):
974
975 def __init__(self, parent, name, elapsed_index):
976 GameChild.__init__(self, parent)
977 self.nodeset = self.get_game().interpolator.get_nodeset(name)
978 self.elapsed = self.nodeset[elapsed_index].x
979 self.time_filter = self.get_game().time_filter
980
981 def get(self):
982 return self.nodeset.get_y(self.elapsed)
983
984 def update(self, active):
985 if active and self.elapsed < self.nodeset[-1].x:
986 self.elapsed += self.time_filter.get_last_frame_duration()
987 if self.elapsed > self.nodeset[-1].x:
988 self.elapsed = self.nodeset[-1].x
989 elif not active and self.elapsed > 0:
990 self.elapsed -= self.time_filter.get_last_frame_duration()
991 if self.elapsed < 0:
992 self.elapsed = 0
993
994
995 class Static(Animation):
996
997 def __init__(self, parent):
998 Animation.__init__(self, parent)
999 self.loops = [Loop(self, choice(range(8, 12))) for _ in xrange(16)]
1000 self.register(self.fade_in)
1001 self.register(self.swap, interval=160)
1002
1003 def fade_in(self):
1004 self.volume += .01
1005 if self.volume >= 1:
1006 self.volume = 1
1007 self.play(self.swap)
1008 self.halt(self.fade_in)
1009 else:
1010 self.swap()
1011
1012 def swap(self):
1013 for loop in self.loops:
1014 loop.stop()
1015 for ii in xrange(3):
1016 loop = choice(self.loops)
1017 loop.set_volume(choice([x * .02 * self.volume for x in \
1018 xrange(2, 20)]))
1019 channel = loop.play(-1)
1020 channel.set_volume(ii == 0 or ii == 1, ii == 1 or ii == 2)
1021 self.register(self.swap,
1022 interval=choice([x * 40 for x in xrange(0, 4)]))
1023
1024 def fade_out(self, fade):
1025 self.halt()
1026 for loop in self.loops:
1027 loop.fadeout(fade)
1028
1029 def activate(self):
1030 self.volume = 0
1031 self.play(self.fade_in)
1032
1033 def update(self):
1034 Animation.update(self)
1035
1036
1037 class Loop(Samples):
1038
1039 def __init__(self, parent, frequency):
1040 self.frequency = frequency
1041 Samples.__init__(self)
1042 self.set_volume(.25)
1043
1044 def build(self):
1045 samples = self.get_empty_array(2 ** self.frequency)
1046 t = 0
1047 while t < len(samples):
1048 ii = 0
1049 if t % 3 == 0:
1050 while ii < [13, 21, 34][t % 3]:
1051 samples[t] = int(choice((sin, cos))(1.0 / (ii + 1) * \
1052 (pi / 2)) * \
1053 self.amplitude)
1054 t += 1
1055 if t == len(samples):
1056 break
1057 ii += 1
1058 elif t % 3 == 1:
1059 while ii < choice([2 ** x for x in xrange(3, 6)]):
1060 samples[t] = int(self.amplitude * 2 / ((ii % 16) + 1) - \
1061 self.amplitude)
1062 t += 1
1063 if t == len(samples):
1064 break
1065 ii += 1
1066 else:
1067 while ii < [x ** 2 for x in xrange(5, 9)][t % 4]:
1068 samples[t] = int(choice((1, -1)) * \
1069 choice([self.amplitude / float(x) for \
1070 x in xrange(2, 10)]))
1071 t += 1
1072 if t == len(samples):
1073 break
1074 ii += 1
1075 return samples
1076
1077
1078 class Node(Sprite):
1079
1080 def __init__(self, parent):
1081 Sprite.__init__(self, parent)
1082 self.display_surface = self.get_display_surface()
1083 magnitude = random() * .8 + .2
1084 self.x = sqrt(random() * magnitude) * choice((1, -1))
1085 self.y = sqrt(random() * (magnitude - self.x ** 2)) * choice((1, -1))
1086 self.z = sqrt(magnitude - self.x ** 2 - self.y ** 2) * choice((1, -1))
1087
1088 def rotate(self, x, y, magnitude):
1089 k = x, y, 0
1090 v = self.x, self.y, self.z
1091 th = magnitude * .125
1092 c = cos(th)
1093 vs = [cc * c for cc in v]
1094 s = sin(th)
1095 vc = [(k[1] * v[2] - k[2] * v[1]) * s,
1096 (k[2] * v[0] - k[0] * v[2]) * s,
1097 (k[0] * v[1] - k[1] * v[0]) * s]
1098 dp = sum(k[ii] * v[ii] for ii in xrange(len(k)))
1099 kd = [cc * dp * (1 - c) for cc in k]
1100 self.x, self.y, self.z = (vs[ii] + vc[ii] + kd[ii] for ii in \
1101 xrange(len(v)))
1102
1103 def get_screen_coordinates(self):
1104 cx, cy = self.display_surface.get_rect().center
1105 x = int(round(cx + self.x * self.parent.RADIUS))
1106 y = int(round(cy + self.y * self.parent.RADIUS))
1107 return x, y
1108
1109 def is_hidden(self):
1110 return self.z <= 0 and self.parent.parent.gate.frame_rect. \
1111 collidepoint(self.get_screen_coordinates())
1112
1113 def update(self):
1114 x, y = self.get_screen_coordinates()
1115 circle(self.display_surface,
1116 (randint(0, 255), randint(0, 255), randint(0, 255)),
1117 self.get_screen_coordinates(), int(round(self.z * 12 + 16)))
1118
1119
1120 class Gate(Animation):
1121
1122 def __init__(self, parent):
1123 Animation.__init__(self, parent, self.show_next_image, 200)
1124 self.display_surface = ds = self.get_display_surface()
1125 root = self.get_resource("image", "gate")
1126 self.images = images = []
1127 for name in sorted(listdir(root)):
1128 images.append(load(join(root, name)))
1129 self.image_index = 0
1130 self.image_rect = image_rect = images[0].get_rect()
1131 image_rect.center = ds.get_rect().center
1132 bevel_width = 2
1133 frame_width = 3
1134 border_width = 1
1135 self.border = border = Sprite(self, 200)
1136 for ii in xrange(3):
1137 inflation = bevel_width * 2 + frame_width * 2 + border_width * 2
1138 surface = Surface(image_rect.inflate(inflation, inflation).size)
1139 surface.fill(((255, 255, 0), (255, 0, 0), (34, 139, 34))[ii])
1140 border.add_frame(surface)
1141 border.location.center = image_rect.center
1142 self.frame_background = load(self.get_resource("image", "hand"))
1143 inflation = bevel_width * 2 + frame_width * 2
1144 self.frame_surface = Surface(image_rect.inflate(inflation, inflation). \
1145 size)
1146 self.frame_rect = self.frame_surface.get_rect()
1147 self.frame_rect.center = image_rect.center
1148 self.frame_background_offset = 0
1149 self.bevel_surface = Surface(image_rect.inflate(bevel_width * 2,
1150 bevel_width * 2).size)
1151 self.bevel_rect = self.bevel_surface.get_rect()
1152 self.bevel_rect.center = image_rect.center
1153 self.bevel_surface.fill((150, 212, 150))
1154 points = (0, 0), (self.bevel_rect.w - 1, 0), (0, self.bevel_rect.h - 1)
1155 polygon(self.bevel_surface, (40, 120, 40), points)
1156 aaline(self.bevel_surface, (150, 212, 150), (0, self.bevel_rect.h - 1),
1157 (self.bevel_rect.w - 1, 0))
1158 self.screen = screen = Sprite(self, 100)
1159 screen.set_alpha(40)
1160 components = []
1161 for ii in xrange(3):
1162 surface = Surface((4, 2))
1163 surface.fill(((255, 0, 0), (0, 255, 0), (0, 0, 255))[ii])
1164 components.append(surface)
1165 component_rect = components[0].get_rect()
1166 for frame_ii in xrange(3):
1167 frame = Surface(image_rect.size)
1168 for yi, y in enumerate(xrange(0, image_rect.h, component_rect.h)):
1169 offset = (0, -2)[y % 2]
1170 for xi, x in enumerate(xrange(offset, image_rect.w,
1171 component_rect.w)):
1172 frame.blit(components[(frame_ii + yi + xi) % 3], (x, y))
1173 screen.add_frame(frame)
1174 screen.location.center = image_rect.center
1175
1176 def show_next_image(self):
1177 self.image_index += 1
1178 if self.image_index == len(self.images):
1179 self.image_index = 0
1180
1181 def set_image_index(self, index):
1182 if index == -1:
1183 self.play()
1184 else:
1185 self.halt()
1186 self.image_index = index
1187
1188 def update(self):
1189 Animation.update(self)
1190 self.border.update()
1191 self.frame_background_offset -= 2
1192 if self.frame_background_offset < -self.frame_background.get_width():
1193 self.frame_background_offset += self.frame_background.get_width()
1194 self.frame_surface.blit(self.frame_background,
1195 (self.frame_background_offset, 0))
1196 self.frame_surface.blit(self.frame_background,
1197 (self.frame_background_offset + \
1198 self.frame_background.get_width(), 0))
1199 self.display_surface.blit(self.frame_surface, self.frame_rect)
1200 self.display_surface.blit(self.bevel_surface, self.bevel_rect)
1201 self.display_surface.blit(self.images[self.image_index],
1202 self.image_rect)
1203 self.screen.update()