gate
[hoa] / hair_on_arm / HairOnArm.py
1 # -*- coding: utf-8 -*-
2
3 from random import randint, randrange, random, choice
4 from math import pi, sin, cos, atan, copysign, sqrt
5 from os import listdir
6 from os.path import join
7
8 from pygame import Rect, Surface
9 from pygame.draw import line, circle, polygon, aaline
10 from pygame.mixer import find_channel, Sound, set_num_channels
11 from pygame.font import Font
12 from pygame.image import load, save
13 from pygame.locals import *
14
15 from hair_on_arm.pgfw.Game import Game
16 from hair_on_arm.pgfw.GameChild import GameChild
17 from hair_on_arm.pgfw.Sprite import Sprite
18 from hair_on_arm.pgfw.Animation import Animation
19 from hair_on_arm.Samples import Samples
20
21 class HairOnArm(Game):
22
23 def __init__(self):
24 Game.__init__(self)
25 set_num_channels(16)
26 self.title.activate()
27
28 def set_children(self):
29 Game.set_children(self)
30 self.gate = Gate(self)
31 self.title = Title(self)
32 self.tour = Tour(self)
33
34 def update(self):
35 self.title.update()
36 self.tour.update()
37
38
39 class Title(GameChild):
40
41 def __init__(self, parent):
42 GameChild.__init__(self, parent)
43 self.delegate = self.get_game().delegate
44 self.menu = Menu(self, (500, 400), 18,
45 ("level", self.start, ["all"] + range(1, 11), True,
46 self.set_gate_index),
47 ("invert", self.set_invert, ("off", "on"), True),
48 ("records", self.show_records))
49 self.deactivate()
50 self.subscribe(self.respond)
51
52 def start(self, index):
53 self.parent.tour.activate()
54 self.deactivate()
55
56 def set_gate_index(self, index):
57 if index == "all":
58 index = 0
59 self.parent.gate.set_image_index(index - 1)
60
61 def set_invert(self, invert):
62 self.parent.invert = False if invert == "off" else True
63
64 def show_records(self):
65 print "show records"
66
67 def deactivate(self):
68 self.active = False
69 self.menu.deactivate()
70
71 def respond(self, event):
72 compare = self.delegate.compare
73 if compare(event, "reset-game"):
74 self.menu.reset()
75 self.activate()
76
77 def activate(self):
78 self.active = True
79 self.menu.activate()
80
81 def update(self):
82 self.get_display_surface().fill((255, 255, 0))
83 self.parent.gate.update()
84 self.menu.update()
85
86
87 class Menu(Animation):
88
89 def __init__(self, parent, position, font_size, *args):
90 Animation.__init__(self, parent, self.highlight)
91 self.delegate = self.get_game().delegate
92 self.arrange(position, font_size, args)
93 self.subscribe(self.respond)
94 self.reset()
95 self.deactivate()
96 self.play()
97
98 def highlight(self):
99 self.highlit = not self.highlit
100
101 def arrange(self, position, font_size, options):
102 self.elements = elements = []
103 font = Font(self.get_resource("display", "font"), font_size)
104 height = 0
105 width = 0
106 margin = 5
107 for option in options:
108 values = [None] if len(option) < 3 else option[2]
109 value_pairs = []
110 for value in values:
111 text = str(option[0])
112 if value is not None:
113 text += " ‡ " + str(value).upper() + " ‡"
114 value_pairs.append((value, font.render(text, True,
115 (90, 90, 140))))
116 ew, eh = value_pairs[0][1].get_size()
117 height += eh + margin
118 if ew > width:
119 width = ew
120 respond_on_rotate = False if len(option) < 4 else option[3]
121 increment_callback = None if len(option) < 5 else option[4]
122 elements.append(Element(self, option[1], value_pairs,
123 respond_on_rotate, increment_callback))
124 x = position[0] - width / 2
125 y = position[1] - height / 2
126 step = elements[0].values[0][1].get_height() + margin
127 for element in elements:
128 element.set_position(x, y)
129 y += step
130
131 def respond(self, event):
132 if self.active:
133 compare = self.delegate.compare
134 if compare(event, "up"):
135 self.increment_active_element(-1)
136 elif compare(event, "down"):
137 self.increment_active_element(1)
138 elif compare(event, "right"):
139 self.elements[self.active_element].increment_active_value(1)
140 elif compare(event, "left"):
141 self.elements[self.active_element].increment_active_value(-1)
142 elif compare(event, "advance"):
143 self.elements[self.active_element].call()
144
145 def increment_active_element(self, increment):
146 self.active_element += increment
147 if self.active_element < 0:
148 self.active_element = len(self.elements) - 1
149 elif self.active_element >= len(self.elements):
150 self.active_element = 0
151
152 def reset(self):
153 self.active_element = 0
154 self.highlit = True
155 for element in self.elements:
156 element.reset()
157
158 def deactivate(self):
159 self.active = False
160
161 def activate(self):
162 self.active = True
163
164 def update(self):
165 if self.active:
166 Animation.update(self)
167 for element in self.elements:
168 element.update(element == self.elements[self.active_element] \
169 and self.highlit)
170
171
172 class Element(GameChild):
173
174 def __init__(self, parent, callback, values, respond_on_increment,
175 increment_callback):
176 GameChild.__init__(self, parent)
177 self.display_surface = self.get_display_surface()
178 self.callback = callback
179 self.values = values
180 self.respond_on_increment = respond_on_increment
181 self.increment_callback = increment_callback
182 self.mask = mask = Surface(self.values[0][1].get_size())
183 mask.fill((255, 255, 255))
184 self.reset()
185
186 def reset(self):
187 self.active_value = 0
188 if self.respond_on_increment:
189 self.call(True)
190
191 def increment_active_value(self, increment):
192 self.active_value += increment
193 if self.active_value < 0:
194 self.active_value = len(self.values) - 1
195 elif self.active_value >= len(self.values):
196 self.active_value = 0
197 if self.respond_on_increment:
198 self.call(True)
199
200 def call(self, increment=False):
201 callback = self.callback
202 if increment and self.increment_callback:
203 callback = self.increment_callback
204 if callback:
205 value = self.values[self.active_value][0]
206 if value is not None:
207 callback(value)
208 else:
209 callback()
210
211 def set_position(self, x, y):
212 self.position = x, y
213
214 def update(self, highlight=False):
215 surface = self.values[self.active_value][1]
216 if highlight:
217 surface = surface.copy()
218 surface.blit(self.mask, (0, 0), None, BLEND_RGB_MAX)
219 self.display_surface.blit(surface, self.position)
220
221
222 class Tour(GameChild):
223
224 RADIUS = 220
225
226 def __init__(self, parent):
227 GameChild.__init__(self, parent)
228 self.delegate = self.get_game().delegate
229 self.directions = {}
230 self.waiting = []
231 self.music_elapsed = 0
232 self.music_elapsed_next = 0
233 for ii, name in enumerate(("up", "right", "down", "left")):
234 self.directions[name] = Direction(self, pi / 2 * ii)
235 self.nodes = [Node(self) for _ in xrange(12)]
236 self.loops = [Loop(self, choice(range(8, 12))) for _ in xrange(16)]
237 self.drums = Sound(self.get_resource("audio", "plastic"))
238 self.drums.set_volume(.85)
239 self.subscribe(self.respond)
240 self.deactivate()
241
242 def respond(self, event):
243 compare = self.delegate.compare
244 if compare(event, "reset-game"):
245 self.deactivate()
246 else:
247 names = ["up", "right", "down", "left"]
248 for direction in names:
249 if compare(event, direction) or compare(event, direction, True):
250 opposite = names[(names.index(direction) + 2) % len(names)]
251 if event.cancel:
252 self.directions[direction].on = False
253 if direction in self.waiting:
254 self.waiting.remove(direction)
255 if opposite in self.waiting:
256 self.directions[opposite].on = True
257 else:
258 if not self.directions[opposite].on:
259 self.directions[direction].on = True
260 elif direction not in self.waiting:
261 self.waiting.append(direction)
262
263 def deactivate(self):
264 self.active = False
265 fade = 500
266 for loop in self.loops:
267 loop.fadeout(fade)
268 self.drums.fadeout(fade)
269
270 def activate(self):
271 self.active = True
272 self.drums.play(-1)
273
274 def update(self):
275 if self.active:
276 ds = self.get_display_surface()
277 ds.fill((0, 0, 0))
278 cx, cy = ds.get_rect().center
279 tx, ty = 0, 0
280 max_magnitude = 0
281 invert = (-1, 1)[self.parent.invert]
282 for direction in self.directions.itervalues():
283 magnitude = direction.get_magnitude()
284 direction.update()
285 dx = sin(direction.angle) * magnitude * self.RADIUS * invert
286 dy = -cos(direction.angle) * magnitude * self.RADIUS * invert
287 line(ds, (0, 0, 255), (cx, cy), (cx + dx * invert,
288 cy + dy * invert), 1)
289 tx += dx
290 ty += dy
291 if magnitude > max_magnitude:
292 max_magnitude = magnitude
293 angle = atan(float(tx) / ty) if ty else 0
294 x = copysign(sin(angle) * max_magnitude * self.RADIUS, tx * invert)
295 y = copysign(cos(angle) * max_magnitude * self.RADIUS, ty * invert)
296 line(ds, (255, 0, 0), (cx, cy), (cx + x, cy + y), 5)
297 angle = atan(float(ty) / tx) if tx else pi / 2
298 x = copysign(sin(angle), ty)
299 y = copysign(cos(angle), -tx)
300 line(ds, (255, 255, 0), (cx, cy), (cx + x * 25, cy + y * 25), 1)
301 for node in self.nodes:
302 node.rotate(x, y, max_magnitude)
303 drawn = False
304 for node in sorted(self.nodes, key=lambda n: n.z):
305 if node.z >= 0 and not drawn:
306 self.parent.gate.update()
307 drawn = True
308 node.update()
309 self.music_elapsed += self.get_game().time_filter. \
310 get_last_frame_duration()
311 if self.music_elapsed > self.music_elapsed_next:
312 for loop in self.loops:
313 loop.stop()
314 # for ii in xrange(3):
315 # loop = choice(self.loops)
316 # loop.set_volume(choice([x * .02 for x in xrange(2, 20)]))
317 # channel = loop.play(-1)
318 # channel.set_volume(ii == 0 or ii == 1, ii == 1 or ii == 2)
319 self.music_elapsed = 0
320 self.music_elapsed_next = choice([x * 40 for x in xrange(0, 4)])
321
322
323 class Direction(GameChild):
324
325 def __init__(self, parent, angle):
326 GameChild.__init__(self, parent)
327 self.angle = angle
328 self.on = False
329 self.attack = Transition(self, "roll-start")
330 self.release = Transition(self, "roll-end")
331
332 def get_magnitude(self):
333 return self.attack.get() if self.on else self.release.get()
334
335 def update(self):
336 self.attack.update(self.on)
337 self.release.update(not self.on)
338
339
340 class Transition(GameChild):
341
342 def __init__(self, parent, name):
343 GameChild.__init__(self, parent)
344 self.nodeset = self.get_game().interpolator.get_nodeset(name)
345 self.elapsed = 0
346 self.time_filter = self.get_game().time_filter
347
348 def get(self):
349 return self.nodeset.get_y(self.elapsed)
350
351 def update(self, active):
352 if active and self.elapsed < self.nodeset[-1].x:
353 self.elapsed += self.time_filter.get_last_frame_duration()
354 if self.elapsed > self.nodeset[-1].x:
355 self.elapsed = self.nodeset[-1].x
356 elif not active and self.elapsed > 0:
357 self.elapsed -= self.time_filter.get_last_frame_duration()
358 if self.elapsed < 0:
359 self.elapsed = 0
360
361
362 class Node(Sprite):
363
364 def __init__(self, parent):
365 Sprite.__init__(self, parent)
366 magnitude = random() * .8 + .2
367 self.x = sqrt(random() * magnitude) * choice((1, -1))
368 self.y = sqrt(random() * (magnitude - self.x ** 2)) * choice((1, -1))
369 self.z = sqrt(magnitude - self.x ** 2 - self.y ** 2) * choice((1, -1))
370
371 def rotate(self, x, y, magnitude):
372 k = x, y, 0
373 v = self.x, self.y, self.z
374 th = magnitude * .125
375 c = cos(th)
376 vs = [cc * c for cc in v]
377 s = sin(th)
378 vc = [(k[1] * v[2] - k[2] * v[1]) * s,
379 (k[2] * v[0] - k[0] * v[2]) * s,
380 (k[0] * v[1] - k[1] * v[0]) * s]
381 dp = sum(k[ii] * v[ii] for ii in xrange(len(k)))
382 kd = [cc * dp * (1 - c) for cc in k]
383 self.x, self.y, self.z = (vs[ii] + vc[ii] + kd[ii] for ii in \
384 xrange(len(v)))
385
386 def update(self):
387 ds = self.get_display_surface()
388 cx, cy = ds.get_rect().center
389 circle(ds, (randint(0, 255), randint(0, 255), randint(0, 255)),
390 (int(round(cx + self.x * self.parent.RADIUS)),
391 int(round(cy + self.y * self.parent.RADIUS))),
392 int(round(self.z * 12 + 16)))
393
394
395 class Loop(Samples):
396
397 def __init__(self, parent, frequency):
398 self.frequency = frequency
399 Samples.__init__(self)
400 self.set_volume(.25)
401
402 def build(self):
403 samples = self.get_empty_array(2 ** self.frequency)
404 t = 0
405 while t < len(samples):
406 ii = 0
407 if t % 3 == 0:
408 while ii < [13, 21, 34][t % 3]:
409 samples[t] = int(choice((sin, cos))(1.0 / (ii + 1) * \
410 (pi / 2)) * \
411 self.amplitude)
412 t += 1
413 if t == len(samples):
414 break
415 ii += 1
416 elif t % 3 == 1:
417 while ii < choice([2 ** x for x in xrange(3, 6)]):
418 samples[t] = int(self.amplitude * 2 / ((ii % 16) + 1) - \
419 self.amplitude)
420 t += 1
421 if t == len(samples):
422 break
423 ii += 1
424 else:
425 while ii < [x ** 2 for x in xrange(5, 9)][t % 4]:
426 samples[t] = int(choice((1, -1)) * \
427 choice([self.amplitude / float(x) for \
428 x in xrange(2, 10)]))
429 t += 1
430 if t == len(samples):
431 break
432 ii += 1
433 return samples
434
435
436 class Gate(Animation):
437
438 def __init__(self, parent):
439 Animation.__init__(self, parent, self.show_next_image, 200)
440 self.display_surface = ds = self.get_display_surface()
441 root = self.get_resource("image", "gate")
442 self.images = images = []
443 for name in sorted(listdir(root)):
444 images.append(load(join(root, name)))
445 self.image_index = 0
446 self.image_rect = image_rect = images[0].get_rect()
447 image_rect.center = ds.get_rect().center
448 bevel_width = 2
449 frame_width = 3
450 border_width = 1
451 self.border = border = Sprite(self, 200)
452 for ii in xrange(3):
453 inflation = bevel_width * 2 + frame_width * 2 + border_width * 2
454 surface = Surface(image_rect.inflate(inflation, inflation).size)
455 surface.fill(((255, 255, 0), (255, 0, 0), (34, 139, 34))[ii])
456 border.add_frame(surface)
457 border.location.center = image_rect.center
458 self.frame_background = load(self.get_resource("image", "hand"))
459 inflation = bevel_width * 2 + frame_width * 2
460 self.frame_surface = Surface(image_rect.inflate(inflation, inflation). \
461 size)
462 self.frame_rect = self.frame_surface.get_rect()
463 self.frame_rect.center = image_rect.center
464 self.frame_background_offset = 0
465 self.bevel_surface = Surface(image_rect.inflate(bevel_width * 2,
466 bevel_width * 2).size)
467 self.bevel_rect = self.bevel_surface.get_rect()
468 self.bevel_rect.center = image_rect.center
469 self.bevel_surface.fill((150, 212, 150))
470 points = (0, 0), (self.bevel_rect.w - 1, 0), (0, self.bevel_rect.h - 1)
471 polygon(self.bevel_surface, (40, 120, 40), points)
472 aaline(self.bevel_surface, (150, 212, 150), (0, self.bevel_rect.h - 1),
473 (self.bevel_rect.w - 1, 0))
474 save(self.bevel_surface, "/tmp/bevel_surface.png")
475 self.screen = screen = Sprite(self, 100)
476 screen.set_alpha(40)
477 components = []
478 for ii in xrange(3):
479 surface = Surface((4, 2))
480 surface.fill(((255, 0, 0), (0, 255, 0), (0, 0, 255))[ii])
481 components.append(surface)
482 component_rect = components[0].get_rect()
483 for frame_ii in xrange(3):
484 frame = Surface(image_rect.size)
485 for yi, y in enumerate(xrange(0, image_rect.h, component_rect.h)):
486 offset = (0, -2)[y % 2]
487 for xi, x in enumerate(xrange(offset, image_rect.w,
488 component_rect.w)):
489 frame.blit(components[(frame_ii + yi + xi) % 3], (x, y))
490 screen.add_frame(frame)
491 screen.location.center = image_rect.center
492
493 def show_next_image(self):
494 self.image_index += 1
495 if self.image_index == len(self.images):
496 self.image_index = 0
497
498 def set_image_index(self, index):
499 if index == -1:
500 self.play()
501 else:
502 self.halt()
503 self.image_index = index
504
505 def update(self):
506 Animation.update(self)
507 self.border.update()
508 self.frame_background_offset -= 2
509 if self.frame_background_offset < -self.frame_background.get_width():
510 self.frame_background_offset += self.frame_background.get_width()
511 self.frame_surface.blit(self.frame_background,
512 (self.frame_background_offset, 0))
513 self.frame_surface.blit(self.frame_background,
514 (self.frame_background_offset + \
515 self.frame_background.get_width(), 0))
516 self.display_surface.blit(self.frame_surface, self.frame_rect)
517 self.display_surface.blit(self.bevel_surface, self.bevel_rect)
518 self.display_surface.blit(self.images[self.image_index],
519 self.image_rect)
520 self.screen.update()