January 28, 2014♦
☀
E
F
F
L
U
E
N
C
E
☀
import os
delattr(os, "link")
from hair_on_arm.pgfw.Setup import Setup
if __name__ == "__main__":
Setup().setup()
from hair_on_arm.pgfw.SetupWin import SetupWin
if __name__ == "__main__":
SetupWin().setup()
from pygame.mixer import Sound
from pgfw.GameChild import GameChild
class SoundEffect(GameChild, Sound):
def __init__(self, parent, identifier, volume=1.0):
GameChild.__init__(self, parent)
if isinstance(identifier, str):
identifier = self.get_resource("audio", identifier)
Sound.__init__(self, identifier)
self.set_volume(volume)
def play(self, position=.5):
channel = Sound.play(self)
right = 1 + min(0, ((position - .5) * 2))
left = 1 - max(0, ((position - .5) * 2))
if channel is not None:
channel.set_volume(left, right)
return channel
# -*- coding: utf-8 -*-
from random import randint, randrange, random, choice, shuffle
from math import pi, sin, cos, atan, copysign, sqrt, tan, radians
from os import listdir
from os.path import join, isfile
from sys import argv
from glob import glob
from copy import copy
from array import array
from pygame import Rect, Surface, mixer, PixelArray
from pygame.draw import line, circle, polygon, aaline, aalines, ellipse
from pygame.mixer import find_channel, Sound, set_num_channels
from pygame.font import Font
from pygame.image import load, save
from pygame.transform import chop
from pygame.event import clear
from pygame.locals import *
from hair_on_arm.pgfw.Game import Game
from hair_on_arm.pgfw.GameChild import GameChild
from hair_on_arm.pgfw.Sprite import Sprite
from hair_on_arm.pgfw.Animation import Animation
from hair_on_arm.Samples import Samples
from hair_on_arm.SoundEffect import SoundEffect
class HairOnArm(Game):
def __init__(self):
Game.__init__(self)
set_num_channels(16)
self.title.activate()
clear()
def set_children(self):
Game.set_children(self)
self.floor = Floor(self)
self.sky = Sky(self)
self.gate = Gate(self)
self.record = Record(self)
self.title = Title(self)
self.tour = Tour(self)
def update(self):
self.title.update()
self.tour.update()
class Floor(Animation, Rect):
BUFFER = 4
def __init__(self, parent):
Animation.__init__(self, parent, self.move)
self.display_surface = ds = self.get_display_surface()
Rect.__init__(self, (0, 0, ds.get_width(), 100))
self.bottom = ds.get_height()
self.delegate = self.get_game().delegate
self.dy_nodeset = self.get_game().interpolator.get_nodeset("floor")
self.reset()
self.subscribe(self.respond)
self.play()
def move(self):
self.y_offset += .5
if self.y_offset > self.BUFFER:
self.y_offset = 1
self.yi_switch = not self.yi_switch
def reset(self):
self.y_offset = 0
self.yi_switch = False
def respond(self, event):
if self.delegate.compare(event, "reset-game"):
self.reset()
def update(self):
Animation.update(self)
ds = self.display_surface
ds.set_clip(self)
ds.fill((225, 200, 225))
buf = self.BUFFER
y = self.y_offset
step = 58
x = range(0, ds.get_width() + step, step)
yi = self.yi_switch
cx = ds.get_rect().centerx
max_angle = pi / 2.1
while y < self.h + buf:
yw = int(round(self.top + y - buf))
dy = self.dy_nodeset.get_y(float(y) / (self.h + buf))
for xi in xrange(yi % 2, len(x) - 1, 2):
x1_a = tan(abs(x[xi] - cx) / float(cx) * max_angle)
x2_a = tan(abs(x[xi + 1] - cx) / float(cx) * max_angle)
points = (x[xi] + 1 + copysign(x1_a * y, x[xi] - cx), yw), \
(x[xi + 1] - 1 + copysign(x2_a * y, x[xi + 1] - cx),
yw), \
(x[xi + 1] + copysign(x2_a * (y + dy), x[xi + 1] - cx),
yw + dy), \
(x[xi] + copysign(x1_a * (y + dy), x[xi] - cx),
yw + dy)
polygon(ds, (200, 255, 225), points)
aalines(ds, (200, 255, 255), True, points)
y += dy
yi += 1
ds.set_clip(None)
class Sky(Animation):
def __init__(self, parent):
Animation.__init__(self, parent)
self.display_surface = self.get_display_surface()
self.background = load(join(self.get_resource("image",
"sky"), "Splat.png")). \
convert_alpha()
self.mountains = load(self.get_resource("image", "mountains")). \
convert_alpha()
self.mountains_rect = self.mountains.get_rect()
self.mountains_rect.bottom = self.parent.floor.top
self.set_bodies()
self.set_gradient()
self.play()
def swap(self):
self.set_gradient()
self.set_bodies()
def set_bodies(self):
self.bodies = bodies = []
paths = glob(join(self.get_resource("image", "sky"), "[0-9]*"))
shuffle(paths)
start = -16
tw = choice([2 ** x for x in xrange(3, 6)])
for path in paths:
bodies.append(Sprite(self, 120))
image = load(path).convert_alpha()
tiles = []
for x in xrange(0, image.get_width(), tw):
for y in xrange(0, image.get_height(), tw):
tile = Surface((tw, tw), SRCALPHA)
tile.blit(image, (-x, -y))
tiles.append(tile)
for ii in xrange(8):
shuffle(tiles)
frame = Surface(image.get_size(), SRCALPHA)
ii = 0
for x in xrange(0, image.get_width(), tw):
for y in xrange(0, image.get_height(), tw):
frame.blit(tiles[ii], (x, y))
ii += 1
bodies[-1].add_frame(frame)
bodies[-1].location.midleft = start, 100
start += bodies[-1].location.w + 32
if start > self.display_surface.get_width():
break
def set_gradient(self):
ds = self.display_surface
self.gradient = gradient = Surface((ds.get_width(),
self.parent.floor.top), SRCALPHA)
color = Color(0, 0, 0)
resolution = 40
hue, hs = self.get_hue_range(resolution)
y = gradient.get_height() - 1
for yi, y in enumerate(xrange(y, y - resolution, -1)):
color.hsla = hue % 360, \
80 + float(resolution - yi) / resolution * 20, \
60 + float(resolution - yi) / resolution * 20, 100
gradient.fill(color, (0, y, ds.get_width(), 1))
hue += hs
resolution = 75
hue, hs = self.get_hue_range(resolution, hue)
for yi, y in enumerate(xrange(y, y - resolution, -1)):
ratio = float(resolution - yi) / resolution
color.hsla = hue % 360, 60 + ratio * 20, 50 + ratio * 10, 100
gradient.fill(color, (0, y, ds.get_width(), 1))
hue += hs
resolution = 190
hue, hs = self.get_hue_range(resolution, hue)
for yi, y in enumerate(xrange(y, y - resolution, -1)):
ratio = float(resolution - yi) / resolution
color.hsla = hue % 360, 30 + ratio * 30, 30 + ratio * 20, \
20 + ratio * 80
gradient.fill(color, (0, y, ds.get_width(), 1))
hue += hs
resolution = y
hue, hs = self.get_hue_range(y, hue)
for yi, y in enumerate(xrange(y, 0, -1)):
ratio = float(resolution - yi) / resolution
color.hsla = hue % 360, ratio * 30, ratio * 30, ratio * 20
gradient.fill(color, (0, y, ds.get_width(), 1))
hue += hs
def get_hue_range(self, resolution, start=None):
if start is None:
start = randrange(0, 360)
return start, float(randrange(30, 90)) / resolution
def update(self):
Animation.update(self)
self.display_surface.blit(self.background, (0, 0))
for body in self.bodies:
body.update()
self.display_surface.blit(self.gradient, (0, 0))
self.display_surface.blit(self.mountains, self.mountains_rect)
class Title(GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
self.delegate = self.get_game().delegate
self.menu = Menu(self, (500, 400), 18,
("level", self.start, range(1, 11), True,
self.set_gate_index),
("invert", self.set_invert, ("off", "on"), True),
("records", None))
self.caster = Caster(self)
self.deactivate()
self.subscribe(self.respond)
def start(self, index):
self.parent.tour.activate(index)
self.deactivate()
def set_gate_index(self, index):
if index == "all":
index = 0
self.parent.gate.set_image_index(index - 1)
def set_invert(self, invert):
self.parent.invert = False if invert == "off" else True
def deactivate(self):
self.active = False
self.menu.deactivate()
def respond(self, event):
compare = self.delegate.compare
if compare(event, "reset-game"):
self.menu.reset()
self.activate()
def activate(self, level_ii=None):
self.active = True
self.menu.activate()
if level_ii is not None:
self.menu.elements[0].active_value = level_ii
def update(self):
if self.active:
self.caster.update()
self.parent.sky.update()
self.parent.floor.update()
self.parent.gate.update()
self.menu.update()
class Caster(GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
self.time_filter = self.get_game().time_filter
volume = .46
self.sounds = sounds = [SoundEffect(self, "caster", volume)]
for _ in xrange(2):
sound = array("h", sounds[0].get_buffer().raw)
start = randrange(0, len(sound) / 2 - 40000)
for ii in xrange(start, start + 40000, 3):
sound.insert(ii, sound[ii])
start = randrange(len(sound) / 2, len(sound) - 40000)
for ii in xrange(start, start + 40000, 3):
sound.insert(ii, sound[ii])
sounds.append(SoundEffect(self, sound, volume))
self.reset()
def reset(self):
self.playing = False
self.elapsed = 0
self.set_gap()
def set_gap(self):
self.gap = randint(2000, 5000)
def update(self):
last = self.time_filter.get_last_frame_duration()
if last < 500:
self.elapsed += last
if not self.playing:
if self.elapsed >= self.gap:
self.elapsed = 0
sound = choice(self.sounds)
sound.play(random())
self.length = sound.get_length() * 1000
self.playing = True
elif self.elapsed >= self.length:
self.reset()
class Menu(Animation):
def __init__(self, parent, position, font_size, *args):
Animation.__init__(self, parent, self.highlight)
self.delegate = self.get_game().delegate
self.vertical_sfx = SoundEffect(self, "vertical", .2)
self.horizontal_sfx = SoundEffect(self, "horizontal", .56)
self.arrange(position, font_size, args)
self.subscribe(self.respond)
self.reset()
self.deactivate()
self.play()
def highlight(self):
self.highlit = not self.highlit
def arrange(self, position, font_size, options):
self.elements = elements = []
font = Font(self.get_resource("display", "font"), font_size)
height = 0
width = 0
margin = 5
for option in options:
values = [None] if len(option) < 3 else option[2]
value_pairs = []
for value in values:
text = str(option[0])
if value is not None:
text += " ‡ " + str(value).upper() + " ‡"
value_pairs.append((value, font.render(text, True,
(90, 90, 140))))
ew, eh = value_pairs[0][1].get_size()
height += eh + margin
if ew > width:
width = ew
respond_on_rotate = False if len(option) < 4 else option[3]
increment_callback = None if len(option) < 5 else option[4]
elements.append(Element(self, option[1], value_pairs,
respond_on_rotate, increment_callback))
x = position[0] - width / 2
y = position[1] - height / 2
step = elements[0].values[0][1].get_height() + margin
for element in elements:
element.set_position(x, y)
y += step
def respond(self, event):
if self.active:
compare = self.delegate.compare
if compare(event, ("up", "down")):
self.vertical_sfx.play()
self.increment_active_element((-1, 1)[compare(event, "down")])
if self.active_element == 2:
self.parent.parent.record.activate()
else:
self.parent.parent.record.deactivate()
elif compare(event, ("right", "left")):
active = self.elements[self.active_element]
if len(active.values) > 1:
self.horizontal_sfx.play()
active.increment_active_value((-1, 1)[compare(event, "right")])
elif compare(event, "advance"):
self.elements[self.active_element].call()
def increment_active_element(self, increment):
self.active_element += increment
if self.active_element < 0:
self.active_element = len(self.elements) - 1
elif self.active_element >= len(self.elements):
self.active_element = 0
def reset(self):
self.active_element = 0
self.highlit = True
for element in self.elements:
element.reset()
def deactivate(self):
self.active = False
def activate(self):
self.active = True
def update(self):
if self.active:
Animation.update(self)
for element in self.elements:
element.update(element == self.elements[self.active_element] \
and self.highlit)
class Element(GameChild):
def __init__(self, parent, callback, values, respond_on_increment,
increment_callback):
GameChild.__init__(self, parent)
self.display_surface = self.get_display_surface()
self.callback = callback
self.values = values
self.respond_on_increment = respond_on_increment
self.increment_callback = increment_callback
self.mask = mask = Surface(self.values[0][1].get_size())
mask.fill((255, 255, 255))
self.reset()
def reset(self):
self.active_value = 0
if self.respond_on_increment:
self.call(True)
def increment_active_value(self, increment):
self.active_value += increment
if self.active_value < 0:
self.active_value = len(self.values) - 1
elif self.active_value >= len(self.values):
self.active_value = 0
if self.respond_on_increment:
self.call(True)
def call(self, increment=False):
callback = self.callback
if increment and self.increment_callback:
callback = self.increment_callback
if callback:
value = self.values[self.active_value][0]
if value is not None:
callback(value)
else:
callback()
def set_position(self, x, y):
self.position = x, y
def update(self, highlight=False):
surface = self.values[self.active_value][1]
if highlight:
surface = surface.copy()
surface.blit(self.mask, (0, 0), None, BLEND_RGB_MAX)
self.display_surface.blit(surface, self.position)
class Tour(Animation):
RADIUS = 220
def __init__(self, parent):
Animation.__init__(self, parent)
self.display_surface = ds = self.get_display_surface()
self.delegate = self.get_game().delegate
self.countdown_beep = SoundEffect(self, "countdown", .7)
self.go_beep = SoundEffect(self, "go", .7)
self.lens = Lens(self)
self.clock = Clock(self)
self.ship = Ship(self)
self.axes = Axes(self)
self.static = Static(self)
self.playing_drums = Sound(self.get_resource("audio", "playing"))
self.playing_drums.set_volume(.85)
self.in_drums = Sound(self.get_resource("audio", "in"))
self.in_drums.set_volume(.85)
self.game_over_surface = Surface(ds.get_size(), SRCALPHA)
self.game_over_surface.fill((0, 0, 0, 127))
self.game_over_sfx = SoundEffect(self, "game-over", .7)
self.landing_sfx = SoundEffect(self, "landing", .7)
self.restart_sfx = SoundEffect(self, "restart", .6)
self.miss_sfx = SoundEffect(self, "lens-miss", .6)
self.next_level_sfx = SoundEffect(self, "next-level", .7)
self.subscribe(self.respond)
self.register(self.fly_in, interval=100)
self.register(self.countdown, interval=1000)
self.deactivate()
def respond(self, event):
compare = self.delegate.compare
if compare(event, "reset-game"):
self.deactivate()
self.lens.reset()
self.ship.reset()
elif self.active and self.game_over and compare(event, "advance"):
self.restart_sfx.play()
self.deactivate()
self.parent.title.activate(self.level_ii)
elif self.active and self.level_complete and compare(event, "advance"):
self.start_level(self.level_ii + 2)
self.playing_drums.fadeout(1000)
self.parent.gate.show_next_image()
def fly_in(self):
if self.ship.move():
self.halt(self.fly_in)
self.play(self.countdown)
self.in_drums.fadeout(1000)
self.clock.activate()
def countdown(self):
if self.countdown_ii < 3:
self.clock.set_message_index(self.countdown_ii)
self.countdown_beep.play()
self.countdown_ii += 1
else:
self.halt(self.countdown)
self.go_beep.play()
self.axes.unsuppress()
self.clock.ticking = True
self.static.activate()
self.playing_drums.play(-1, 0, 5000)
def deactivate(self):
self.halt()
self.active = False
self.fade_out()
self.ship.deactivate()
self.clock.deactivate()
def fade_out(self, include_static=True):
fade = 1000
if include_static:
self.static.fade_out(fade)
self.playing_drums.fadeout(fade)
self.in_drums.fadeout(fade)
def activate(self, index):
self.active = True
self.game_over = False
self.start_level(index)
def start_level(self, index):
self.level_complete = False
self.level_ii = level_ii = index - 1
self.nodes = nodes = []
new = Node(self)
while new.is_hidden() or self.lens.collide_node(new):
new = Node(self)
nodes.append(new)
while len(nodes) < (4, 6, 7, 8, 10, 12, 14, 17, 19, 21)[level_ii]:
new = Node(self)
if self.lens.collide_node(new):
continue
add = True
for node in nodes:
if abs(new.x - node.x) < .05 and abs(new.y - node.y) < .05 and \
abs(new.z - node.z) < .05:
add = False
break
if add:
nodes.append(new)
self.axes.suppress()
self.in_drums.play(-1, 0, 3000)
self.ship.activate()
self.countdown_ii = 0
self.clock.deactivate()
self.parent.sky.swap()
self.lens.set_colors()
self.play(self.fly_in)
self.landing_sfx.play()
def end(self, win=False):
font = Font(self.get_resource("display", "font"), 12)
message = "Y O U W O N !" if win else "G A M E O V E R"
self.game_over_text = font.render(message, True, (255, 255, 0))
self.game_over_text_rect = self.game_over_text.get_rect()
self.game_over_text_rect.center = self.display_surface.get_rect().center
self.axes.suppress()
self.game_over = True
if not win:
self.game_over_sfx.play(0)
self.game_over_sfx.fadeout(1500)
self.fade_out(False)
self.static.volume = .5
self.static.halt(self.static.fade_in)
self.static.play(self.static.swap)
self.clock.ticking = False
self.lens.reset()
def complete_level(self):
self.axes.suppress()
self.static.fade_out(1000)
self.clock.ticking = False
self.level_complete = True
self.clock.finish()
self.next_level_sfx.play()
def update(self):
if self.active:
Animation.update(self)
if not self.game_over:
identified = None
for node in self.nodes:
if self.lens.collide_node(node):
if not node.dephasing:
self.miss_sfx.play()
self.end()
break
else:
identified = node
if not self.game_over:
self.lens.scan(identified)
self.static.update()
self.parent.sky.update()
self.parent.floor.update()
self.lens.update()
x, y, max_magnitude = self.axes.update()
for node in self.nodes:
node.rotate(max_magnitude * .125, x, y)
self.lens.draw_background()
gate_drawn = False
ship_drawn = False
for node in sorted(self.nodes, key=lambda n: n.z):
if node.z >= 0 and not gate_drawn:
self.parent.gate.update()
self.clock.update()
self.lens.draw_foreground()
gate_drawn = True
node.update()
if self.ship.location[2] < node.z and not ship_drawn:
self.ship.update()
ship_drawn = True
if not gate_drawn:
self.parent.gate.update()
self.clock.update()
self.lens.draw_foreground()
if not ship_drawn:
self.ship.update()
if self.game_over:
ds = self.display_surface
ds.blit(self.game_over_surface, (0, 0))
ds.blit(self.game_over_text, self.game_over_text_rect)
class Lens(Animation):
def __init__(self, parent):
Animation.__init__(self, parent, self.blip, 800)
self.display_surface = ds = self.get_display_surface()
self.rim_mask = load(self.get_resource("image",
"lens-rim")).convert_alpha()
self.rect = rect = self.rim_mask.get_rect()
rect.center = ds.get_rect().centerx, \
self.parent.parent.gate.frame_rect.top - 60
self.shadow_mask = self.rim_mask.subsurface((0, rect.h / 2, rect.w,
rect.h - rect.h / 2))
self.shadow_rect = self.shadow_mask.get_rect()
self.shadow_rect.midbottom = rect.centerx, rect.bottom + 3
self.gradient_surfaces = gradient_surfaces = [ \
load(self.get_resource("image", "lens-gradient")).convert_alpha()]
self.gradient_rect = gradient_surfaces[0].get_rect()
self.gradient_rect.center = rect.center
for offset in xrange(30, 360, 30):
surface = gradient_surfaces[0].copy()
for x in xrange(surface.get_width()):
for y in xrange(surface.get_height()):
color = Color(*surface.get_at((x, y)))
h, s, l, a = color.hsla
color.hsla = (h + offset) % 360, s, l, a
surface.set_at((x, y), color)
gradient_surfaces.append(surface)
surface = load(self.get_resource("image", "lens-cloud")).convert_alpha()
self.clouds_background_surface = surface.copy()
pixels = PixelArray(self.clouds_background_surface)
for x in xrange(self.clouds_background_surface.get_width()):
for y in xrange(self.clouds_background_surface.get_height()):
color = Color(*self.clouds_background_surface.get_at((x, y)))
h, s, l, a = color.hsla
color.hsla = h, s, l, max(0, a - 51)
self.clouds_background_surface.set_at((x, y), color)
self.clouds_foreground_surface = surface.copy()
for x in xrange(self.clouds_foreground_surface.get_width()):
for y in xrange(self.clouds_foreground_surface.get_height()):
color = Color(*self.clouds_foreground_surface.get_at((x, y)))
h, s, l, a = color.hsla
color.hsla = h, s, l, max(0, a - 34)
self.clouds_foreground_surface.set_at((x, y), color)
self.clouds_background_offset = randint(0, surface.get_width() - 1)
self.clouds_foreground_offset = randint(0, surface.get_width() - 1)
self.clouds_mask = mask = self.gradient_surfaces[0].copy()
plate = Surface(mask.get_size(), SRCALPHA)
plate.fill((255, 255, 255))
mask.blit(plate, (0, 0), None, BLEND_RGB_MAX)
self.glass_surface = load(self.get_resource("image", "lens-glass")). \
convert_alpha()
self.glass_surface_rect = self.glass_surface.get_rect()
self.glass_surface_rect.center = self.rect.center
self.glass_edge_surface = load(self.get_resource("image",
"lens-glass-edge")). \
convert_alpha()
self.grain_surface = surface = Surface(self.clouds_mask.get_size(),
SRCALPHA)
self.grain_surface_offset = 0
for ii, y in enumerate(xrange(0, surface.get_height(), 8)):
surface.fill(((128, 128, 128, 95), (128, 128, 128, 15))[ii % 2],
(0, y, surface.get_width(), 8))
self.scan_sfx = SoundEffect(self, "lens-scan", .7)
self.complete_sfx = SoundEffect(self, "lens-complete", .7)
self.register(self.blink, interval=80)
self.reset()
self.set_colors()
def blip(self):
self.blip_ii += 1
if self.blip_ii == 3:
self.complete_sfx.play()
self.parent.nodes.remove(self.previously_identified)
if self.parent.nodes:
self.parent.ship.indicate_next_destination( \
self.previously_identified)
else:
self.parent.complete_level()
if self.parent.level_ii == 9:
self.parent.end(True)
self.reset()
self.set_colors(False)
else:
self.scan_sfx.play()
def reset(self):
self.halt()
self.blip_ii = 0
self.previously_identified = None
self.blink_on = False
def scan(self, node):
if not node and self.previously_identified:
self.reset()
self.set_colors(False)
elif node and (not self.previously_identified or \
self.previously_identified != node):
self.previously_identified = node
self.play(self.blip)
self.play(self.blink)
def blink(self):
self.blink_on = not self.blink_on
self.set_colors(False)
def set_colors(self, change_hue=True):
if change_hue:
self.gradient_surface = choice(self.gradient_surfaces)
self.hue = hue = randrange(0, 360) if change_hue else self.hue
color = Color(141, 82, 79)
h, s, l, a = color.hsla
color.hsla = hue, s, l + self.blink_on * 18, a
self.rim_plate = Surface(self.rect.size, SRCALPHA)
self.rim_plate.fill(color)
color = Color(68, 40, 39)
h, s, l, a = color.hsla
color.hsla = hue, s, l + self.blink_on * 18, a
self.shadow_plate = Surface(self.shadow_mask.get_size(), SRCALPHA)
self.shadow_plate.fill(color)
def collide_node(self, node):
nx, ny = node.get_screen_coordinates()
if node.z < 0 and self.gradient_rect.collidepoint((nx, ny)):
rect = self.gradient_rect
cx, cy = rect.center
return sqrt((nx - cx) ** 2 + (ny - cy) ** 2) <= rect.w / 2.0
def update(self):
Animation.update(self)
self.clouds_background_offset += 1
if self.clouds_background_offset >= self.clouds_background_surface. \
get_width():
self.clouds_background_offset -= self.clouds_background_surface. \
get_width()
self.clouds_foreground_offset += 2
if self.clouds_foreground_offset >= self.clouds_foreground_surface. \
get_width():
self.clouds_foreground_offset -= self.clouds_foreground_surface. \
get_width()
self.grain_surface_offset -= 1
if self.grain_surface_offset < -self.grain_surface.get_width():
self.grain_surface_offset += self.grain_surface.get_width()
def draw_background(self):
ds = self.display_surface
ds.blit(self.glass_surface, self.glass_surface_rect)
ds.blit(self.gradient_surface, self.gradient_rect)
def draw_foreground(self):
ds = self.display_surface
mask = self.clouds_mask.copy()
for offset in (0, self.grain_surface.get_height()):
mask.blit(self.grain_surface,
(0, self.grain_surface_offset + offset), None,
BLEND_RGBA_MIN)
ds.blit(mask, self.gradient_rect)
mask = self.clouds_mask.copy()
for offset in (0, -self.clouds_background_surface.get_width()):
mask.blit(self.clouds_background_surface,
(self.clouds_background_offset + offset, -17), None,
BLEND_RGBA_MIN)
ds.blit(mask, self.gradient_rect)
mask = self.clouds_mask.copy()
for offset in (0, -self.clouds_foreground_surface.get_width()):
mask.blit(self.clouds_foreground_surface,
(self.clouds_foreground_offset + offset, -12), None,
BLEND_RGBA_MIN)
ds.blit(mask, self.gradient_rect)
shadow = self.shadow_mask.copy()
shadow.blit(self.shadow_plate, (0, 0), None, BLEND_RGBA_MIN)
ds.blit(shadow, self.shadow_rect)
rim = self.rim_mask.copy()
rim.blit(self.rim_plate, (0, 0), None, BLEND_RGBA_MIN)
ds.blit(rim, self.rect)
ds.blit(self.glass_edge_surface, self.glass_surface_rect)
class Clock(GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
self.display_surface = self.get_display_surface()
self.time_filter = self.get_game().time_filter
self.messages = [ShadowedText(self, word, 12) for word in \
("three", "two", "one", "zero")]
self.rect = self.parent.parent.gate.frame_rect. \
inflate(0, -self.messages[0].rect.h). \
move(self.parent.parent.gate.frame_rect.w, 0)
self.set_message_index(0)
self.deactivate()
def deactivate(self):
self.active = False
def activate(self):
self.active = True
self.elapsed = 0
self.limit = 60000 + self.parent.level_ii * 30000
self.ticking = False
def set_message_index(self, index):
self.message_ii = index
def finish(self):
rank = self.parent.parent.record.add(self.parent.level_ii,
self.elapsed) + 1
suffix = "TSNRHTDD"[(rank / 10 % 10 != 1) * (rank % 10 < 4) * \
rank % 10::4]
self.rank_message = rm = ShadowedText(self, "%d%s" % (rank, suffix),
11)
rm.play(rm.blink)
self.set_message_index(4)
message = ShadowedText(self, "%.2f" % (self.elapsed / 1000.0), 16)
message.play(message.blink)
if len(self.messages) < 5:
self.messages.append(message)
else:
self.messages[4] = message
self.ticking = False
def update(self):
if self.active:
if self.ticking:
self.elapsed += self.time_filter.get_last_frame_duration()
if self.elapsed >= self.limit:
self.elapsed = self.limit
self.set_message_index(3)
self.parent.end()
ratio = float(self.elapsed) / self.limit
mi = self.message_ii
if mi == 0:
color = (255, 31, 31)
elif mi == 1:
color = (255, 255, 31)
elif mi == 3:
color = (128, 128, 128)
else:
color = Color(0, 0, 0)
color.hsla = int(120 - ratio * 120), 100, 56, 100
margin = 4
y = int(round(self.rect.top + self.rect.h * ratio))
self.messages[mi].update(color, self.rect.left + margin, y)
if mi == 4:
tr = self.messages[mi].rect
self.rank_message.update((255, 255, 255), self.rect.left + \
margin * 2 + tr.w, y)
class ShadowedText(Animation):
def __init__(self, parent, text, size):
Animation.__init__(self, parent, self.blink, 250)
self.display_surface = self.get_display_surface()
self.visible = True
font = Font(self.get_resource("display", "font"), size)
font.set_bold(True)
self.surface = surface = font.render(text.upper(), True,
(255, 255, 255))
self.rect = surface.get_rect()
def blink(self):
self.visible = not self.visible
def update(self, color, x, y):
Animation.update(self)
if self.visible:
shadow_color = Color(*color)
h, s, l, a = shadow_color.hsla
shadow_color.hsla = h, abs(s - 24), abs(l - 40), 100
plate = Surface(self.rect.size, SRCALPHA)
plate.fill(shadow_color)
background = self.surface.copy()
background.blit(plate, (0, 0), None, BLEND_RGBA_MIN)
ds = self.display_surface
self.rect.midleft = x, y
ds.blit(background, self.rect)
plate.fill(color)
foreground = self.surface.copy()
foreground.blit(plate, (0, 0), None, BLEND_RGBA_MIN)
ds.blit(foreground, self.rect.move(1, 0))
class Ship(Animation):
def __init__(self, parent):
Animation.__init__(self, parent, self.spin)
self.display_surface = self.get_display_surface()
self.max_length = max_length = 4
self.text = ShadowedText(self, "the", 12)
self.play()
self.register(self.travel_to_next_destination, interval=100)
self.deactivate()
def deactivate(self):
self.active = False
def activate(self):
self.active = True
self.lines = [[self.max_length, -1,
choice(((0, 255, 128), (255, 255, 0), (200, 90, 255)))] \
for ii in xrange(8)]
self.location = location = [-self.max_length / 2 - 1,
self.display_surface.get_rect().centery, 0]
node = choice(self.parent.nodes)
while node.is_hidden():
node = choice(self.parent.nodes)
self.destination_node = node
self.destination = destination = list(node.get_screen_coordinates()) + \
[node.z]
frame_count = 3000 / self.parent.accounts[self.parent.fly_in]. \
interval[0]
self.step = [float(destination[0] - location[0]) / frame_count,
float(destination[1] - location[1]) / frame_count,
float(destination[2] - location[2]) / frame_count]
def spin(self):
step = .5
for line in self.lines:
line[0] += step * line[1]
if line[0] < 0 or line[0] > self.max_length:
line[1] = -line[1]
line[0] += step * line[1]
def move(self):
location, step, destination = self.location, self.step, self.destination
location[0] += step[0]
location[1] += step[1]
location[2] += step[2]
arrived = all(abs(destination[ii] - location[ii]) <= abs(step[ii]) \
for ii in xrange(len(location)))
if arrived:
if self.is_playing(self.travel_to_next_destination):
self.halt(self.travel_to_next_destination)
self.parent.axes.unsuppress()
self.deactivate()
self.destination_node.dephasing = True
return arrived
def indicate_next_destination(self, previous):
self.active = True
self.parent.axes.suppress()
self.location = location = list(previous.get_screen_coordinates()) + \
[previous.z]
self.destination_node = choice(self.parent.nodes)
self.destination = destination = list(self.destination_node. \
get_screen_coordinates()) + \
[self.destination_node.z]
frame_count = 1000.0 / self.accounts[self.travel_to_next_destination]. \
interval[0]
self.step = [float(destination[0] - location[0]) / frame_count,
float(destination[1] - location[1]) / frame_count,
float(destination[2] - location[2]) / frame_count]
self.play(self.travel_to_next_destination)
def travel_to_next_destination(self):
self.move()
def reset(self):
self.halt(self.travel_to_next_destination)
def update(self):
if self.active:
Animation.update(self)
cx, cy, z = self.location
lines = self.lines
w = float((z + 1) * 8)
step = w / len(lines)
x = cx
rect = Rect(x - w, cy - self.max_length / 2, w,
self.max_length).inflate(10, 14)
ds = self.display_surface
ds.fill(lines[0][2], rect)
ds.fill((0, 0, 0), (rect.topleft, (rect.w, 1)))
ds.fill((0, 0, 0), (rect.topleft, (1, rect.h)))
ds.fill((255, 255, 255), (rect.right - 1, rect.top, 1, rect.h))
ds.fill((255, 255, 255), (rect.left, rect.bottom - 1, rect.w, 1))
for member in lines:
dy = (z + 1) * member[0]
line(ds, member[2], (x, cy - dy), (x, cy + dy), 3)
x -= step
self.text.update((127, 127, 255),
rect.left - self.text.rect.w - 7, cy)
class Axes(GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
self.display_surface = self.get_display_surface()
self.delegate = self.get_game().delegate
self.directions = {}
for ii, name in enumerate(("up", "right", "down", "left")):
self.directions[name] = Direction(self, pi / 2 * ii)
self.waiting = []
self.rect = rect = Rect(0, 0, 80, 80)
rect.center = 597, 436
self.background = background = Sprite(self, 500)
color = Color(0, 0, 0)
frame_count = 16
gradient_count = 8
for ii in xrange(frame_count):
surface = Surface((rect.w * 2 + 65, rect.h * 2 + 65), SRCALPHA)
for jj in xrange(gradient_count):
ratio = float(jj + 1) / gradient_count
color.hsla = float(ii) / frame_count * 360, 80, 60, ratio * 100
circle(surface, color, surface.get_rect().center, \
int(round((1 - ratio) * (surface.get_width() / 2))))
background.add_frame(surface)
background.location.center = self.display_surface.get_rect().bottomright
self.subscribe(self.respond)
def respond(self, event):
if self.parent.active and not self.suppressed:
compare = self.delegate.compare
names = ["up", "right", "down", "left"]
for direction in names:
if compare(event, direction) or compare(event, direction, True):
opposite = names[(names.index(direction) + 2) % len(names)]
if event.cancel:
self.directions[direction].on = False
if direction in self.waiting:
self.waiting.remove(direction)
if opposite in self.waiting:
self.directions[opposite].on = True
else:
if not self.directions[opposite].on:
self.directions[direction].on = True
elif direction not in self.waiting:
self.waiting.append(direction)
def suppress(self):
self.suppressed = True
for direction in self.directions.itervalues():
direction.on = False
def unsuppress(self):
self.suppressed = False
def update(self):
self.background.update()
tx, ty, max_magnitude = 0, 0, 0
invert = (-1, 1)[self.parent.parent.invert]
ds = self.display_surface
cx, cy = self.rect.center
r = self.rect.w / 2
for direction in self.directions.itervalues():
magnitude = direction.get_magnitude()
direction.update()
dx = sin(direction.angle) * magnitude * invert
dy = -cos(direction.angle) * magnitude * invert
line(ds, (0, 0, 255), (cx, cy), (cx + dx * r * invert,
cy + dy * r * invert), 1)
tx += dx
ty += dy
if magnitude > max_magnitude:
max_magnitude = magnitude
angle = atan(float(tx) / ty) if ty else 0
x = copysign(sin(angle) * max_magnitude, tx * invert)
y = copysign(cos(angle) * max_magnitude, ty * invert)
line(ds, (255, 0, 0), (cx, cy), (cx + x * r, cy + y * r), 3)
angle = atan(float(ty) / tx) if tx else pi / 2
x = copysign(sin(angle), ty)
y = copysign(cos(angle), -tx)
length = 10
line(ds, (255, 255, 0), (cx, cy), (cx + x * length, cy + y * length), 1)
return x, y, max_magnitude
class Direction(GameChild):
def __init__(self, parent, angle):
GameChild.__init__(self, parent)
self.angle = angle
self.on = False
self.attack = Transition(self, "roll-start", 0)
self.release = Transition(self, "roll-end", -1)
def get_magnitude(self):
return self.attack.get() if self.on else self.release.get()
def update(self):
self.attack.update(self.on)
self.release.update(not self.on)
class Transition(GameChild):
def __init__(self, parent, name, elapsed_index):
GameChild.__init__(self, parent)
self.nodeset = self.get_game().interpolator.get_nodeset(name)
self.elapsed = self.nodeset[elapsed_index].x
self.time_filter = self.get_game().time_filter
def get(self):
return self.nodeset.get_y(self.elapsed)
def update(self, active):
if active and self.elapsed < self.nodeset[-1].x:
self.elapsed += self.time_filter.get_last_frame_duration()
if self.elapsed > self.nodeset[-1].x:
self.elapsed = self.nodeset[-1].x
elif not active and self.elapsed > 0:
self.elapsed -= self.time_filter.get_last_frame_duration()
if self.elapsed < 0:
self.elapsed = 0
class Static(Animation):
def __init__(self, parent):
Animation.__init__(self, parent)
self.loops = [Loop(self, choice(range(8, 12))) for _ in xrange(16)]
self.register(self.fade_in)
self.register(self.swap, interval=160)
def fade_in(self):
self.volume += .01
if self.volume >= 1:
self.volume = 1
self.play(self.swap)
self.halt(self.fade_in)
else:
self.swap()
def swap(self):
for loop in self.loops:
loop.stop()
for ii in xrange(3):
loop = choice(self.loops)
loop.set_volume(choice([x * .02 * self.volume for x in \
xrange(2, 20)]))
channel = loop.play(-1)
channel.set_volume(ii == 0 or ii == 1, ii == 1 or ii == 2)
self.register(self.swap,
interval=choice([x * 40 for x in xrange(0, 4)]))
def fade_out(self, fade):
self.halt()
for loop in self.loops:
loop.fadeout(fade)
def activate(self):
self.volume = 0
self.play(self.fade_in)
def update(self):
Animation.update(self)
class Loop(Samples):
def __init__(self, parent, frequency):
self.frequency = frequency
Samples.__init__(self)
self.set_volume(.25)
def build(self):
samples = self.get_empty_array(2 ** self.frequency)
t = 0
amplitude = 16000
while t < len(samples):
ii = 0
if t % 3 == 0:
while ii < [13, 21, 34][t % 3]:
samples[t] = int(choice((sin, cos))(1.0 / (ii + 1) * \
(pi / 2)) * \
amplitude)
t += 1
if t == len(samples):
break
ii += 1
elif t % 3 == 1:
while ii < choice([2 ** x for x in xrange(3, 6)]):
samples[t] = int(amplitude * 2 / ((ii % 16) + 1) - \
amplitude)
t += 1
if t == len(samples):
break
ii += 1
else:
while ii < [x ** 2 for x in xrange(5, 9)][t % 4]:
samples[t] = int(choice((1, -1)) * \
choice([amplitude / float(x) for \
x in xrange(2, 10)]))
t += 1
if t == len(samples):
break
ii += 1
return samples
class Vector(GameChild):
def __init__(self, parent, x, y, z):
GameChild.__init__(self, parent)
self.x, self.y, self.z = x, y, z
def __repr__(self):
return "<%.3f %.3f %.3f>" % (self.x, self.y, self.z)
def rotate(self, th, x=0, y=0, z=0):
k = x, y, z
v = self.x, self.y, self.z
c = cos(th)
vs = [cc * c for cc in v]
s = sin(th)
vc = [(k[1] * v[2] - k[2] * v[1]) * s,
(k[2] * v[0] - k[0] * v[2]) * s,
(k[0] * v[1] - k[1] * v[0]) * s]
dp = sum(k[ii] * v[ii] for ii in xrange(len(k)))
kd = [cc * dp * (1 - c) for cc in k]
self.x, self.y, self.z = (vs[ii] + vc[ii] + kd[ii] for ii in \
xrange(len(v)))
def get_screen_coordinates(self, center=None, radius=None):
if center is None:
center = self.display_surface.get_rect().center
if radius is None:
radius = self.parent.RADIUS
cx, cy = center
x = int(round(cx + self.x * radius))
y = int(round(cy + self.y * radius))
return x, y
class Node(Vector):
def __init__(self, parent):
self.display_surface = parent.get_display_surface()
magnitude = random() * .7 + .3
x = sqrt(random() * magnitude) * choice((1, -1))
y = sqrt(random() * (magnitude - x ** 2)) * choice((1, -1))
z = sqrt(magnitude - x ** 2 - y ** 2) * choice((1, -1))
Vector.__init__(self, parent, x, y, z)
self.cube = Cube(self)
self.dephasing = False
def is_hidden(self):
return self.z <= 0 and self.parent.parent.gate.frame_rect. \
collidepoint(self.get_screen_coordinates())
def update(self):
self.cube.update()
class Cube(GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
self.display_surface = self.get_display_surface()
self.set_vectors()
self.set_edges()
self.set_axis()
self.set_colors()
self.radius_range = 4, 50
self.rotation_speed = random() * .1 + .075
def set_vectors(self):
w = .5
self.vectors = [Vector(self, w, w, w), Vector(self, w, w, -w),
Vector(self, w, -w, w), Vector(self, w, -w, -w),
Vector(self, -w, w, w), Vector(self, -w, w, -w),
Vector(self, -w, -w, w), Vector(self, -w, -w, -w)]
def set_edges(self):
vectors = self.vectors
self.edges = (vectors[0], vectors[1], vectors[3], vectors[2]), \
(vectors[0], vectors[4], vectors[5], vectors[1]), \
(vectors[0], vectors[2], vectors[6], vectors[4]), \
(vectors[4], vectors[5], vectors[7], vectors[6]), \
(vectors[2], vectors[6], vectors[7], vectors[3]), \
(vectors[1], vectors[3], vectors[7], vectors[5])
def set_axis(self):
self.axis = axis = []
x2 = random()
axis.append(sqrt(x2) * choice((1, -1)))
y2 = random() * (1 - x2)
axis.append(sqrt(y2) * choice((1, -1)))
axis.append(sqrt(1 - x2 - y2) * choice((1, -1)))
def set_colors(self):
self.surface_colors = surface_colors = []
self.line_colors = line_colors = []
for _ in xrange(len(self.edges)):
surface_color = Color(0, 0, 0)
hue = randrange(0, 360)
surface_color.hsla = hue, randint(40, 100), randint(40, 100), 100
surface_colors.append(surface_color)
line_color = Color(0, 0, 0)
line_color.hsla = (hue + 180) % 360, randint(0, 20), \
randint(10, 30), 100
line_colors.append(line_color)
def update(self):
self.rotate()
if not self.parent.is_hidden():
center = self.parent.get_screen_coordinates()
radius = self.get_radius()
surface = self.get_surface(radius)
rect = surface.get_rect()
if self.parent.dephasing:
self.set_colors()
for ii, face in enumerate(self.edges):
points = []
for vector in face:
points.append(vector.get_screen_coordinates(rect.center,
radius))
polygon(surface, self.surface_colors[ii], points)
rect.center = center
self.display_surface.blit(surface, rect)
def rotate(self):
for vector in self.vectors:
vector.rotate(self.rotation_speed, *self.axis)
def get_radius(self):
low, high = self.radius_range
return self.parent.z * ((high - low) / 2) + ((high - low) / 2) + low
def get_surface(self, radius):
surface = Surface([int(radius * 1.7)] * 2)
surface.set_colorkey((0, 0, 0))
return surface
class Gate(Animation):
def __init__(self, parent):
Animation.__init__(self, parent, self.show_next_image, 200)
self.display_surface = ds = self.get_display_surface()
root = self.get_resource("image", "gate")
self.images = images = []
for name in sorted(listdir(root)):
images.append(load(join(root, name)))
self.image_index = 0
self.image_rect = image_rect = images[0].get_rect()
image_rect.center = ds.get_rect().center
bevel_width = 2
frame_width = 3
border_width = 1
self.border = border = Sprite(self, 200)
for ii in xrange(3):
inflation = bevel_width * 2 + frame_width * 2 + border_width * 2
surface = Surface(image_rect.inflate(inflation, inflation).size)
surface.fill(((255, 255, 0), (255, 0, 0), (34, 139, 34))[ii])
border.add_frame(surface)
border.location.center = image_rect.center
self.frame_background = load(self.get_resource("image", "hand"))
inflation = bevel_width * 2 + frame_width * 2
self.frame_surface = Surface(image_rect.inflate(inflation, inflation). \
size)
self.frame_rect = self.frame_surface.get_rect()
self.frame_rect.center = image_rect.center
self.frame_background_offset = 0
self.bevel_surface = Surface(image_rect.inflate(bevel_width * 2,
bevel_width * 2).size)
self.bevel_rect = self.bevel_surface.get_rect()
self.bevel_rect.center = image_rect.center
self.bevel_surface.fill((150, 212, 150))
points = (0, 0), (self.bevel_rect.w - 1, 0), (0, self.bevel_rect.h - 1)
polygon(self.bevel_surface, (40, 120, 40), points)
aaline(self.bevel_surface, (150, 212, 150), (0, self.bevel_rect.h - 1),
(self.bevel_rect.w - 1, 0))
self.screen = screen = Sprite(self, 100)
screen.set_alpha(40)
components = []
for ii in xrange(3):
surface = Surface((4, 2))
surface.fill(((255, 0, 0), (0, 255, 0), (0, 0, 255))[ii])
components.append(surface)
component_rect = components[0].get_rect()
for frame_ii in xrange(3):
frame = Surface(image_rect.size)
for yi, y in enumerate(xrange(0, image_rect.h, component_rect.h)):
offset = (0, -2)[y % 2]
for xi, x in enumerate(xrange(offset, image_rect.w,
component_rect.w)):
frame.blit(components[(frame_ii + yi + xi) % 3], (x, y))
screen.add_frame(frame)
screen.location.center = image_rect.center
def show_next_image(self):
self.image_index += 1
if self.image_index == len(self.images):
self.image_index = 0
def set_image_index(self, index):
if index == -1:
self.play()
else:
self.halt()
self.image_index = index
def update(self):
Animation.update(self)
self.border.update()
self.frame_background_offset -= 2
if self.frame_background_offset < -self.frame_background.get_width():
self.frame_background_offset += self.frame_background.get_width()
self.frame_surface.blit(self.frame_background,
(self.frame_background_offset, 0))
self.frame_surface.blit(self.frame_background,
(self.frame_background_offset + \
self.frame_background.get_width(), 0))
self.display_surface.blit(self.frame_surface, self.frame_rect)
self.display_surface.blit(self.bevel_surface, self.bevel_rect)
self.display_surface.blit(self.images[self.image_index],
self.image_rect)
self.parent.record.update()
self.screen.update()
class Record(GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
self.display_surface = self.get_display_surface()
self.path = self.get_resource("text", "record")
self.deactivate()
self.set_view()
def deactivate(self):
self.active = False
def set_view(self):
record = self.get_record()
self.surface = surface = Surface(self.parent.gate.image_rect.size)
tile = Surface((2, 2))
for x in xrange(tile.get_width()):
for y in xrange(tile.get_height()):
tile.set_at((x, y), ((210, 210, 210),
(110, 110, 110))[(x + y) % 2])
for x in xrange(0, surface.get_width(), tile.get_width()):
for y in xrange(0, surface.get_height(), tile.get_height()):
surface.blit(tile, (x, y))
w, h = surface.get_size()
rect = Rect(0, 0, w / 5, h / 2)
x, y = 0, 0
font_path = self.get_resource("display", "font")
color = Color(0, 0, 0)
hue = 0
ii = 0
while y < h - 1:
rect.topleft = x, y
title = ShadowedText(self, "level " + str(ii + 1), 10)
title.display_surface = surface
ry = 3
title.update((240, 200, 169), rect.centerx - 2 - title.rect.w / 2,
rect.top + ry + title.rect.h / 2)
ry += title.rect.h
line(surface, (150, 150, 150), (rect.left + 28, ry + rect.top),
(rect.right - 28, ry + rect.top))
ry += 4
for jj, result in enumerate((record[ii][:2] + [None, None,
None])[:2]):
font = Font(font_path, 11 + (2 - jj) * 2)
color = Color(0, 0, 0)
color.hsla = hue, 100, 90 - jj * 70, 100
if result is None:
message = "-:--.--"
else:
message = "%.2f" % (result / 1000.0)
text = font.render(message, True, color)
text_rect = text.get_rect()
text_rect.midtop = rect.centerx, ry + rect.top
surface.blit(text, text_rect)
ry += text_rect.h - 2
ii += 1
hue += 36
x += w / 5.0
if x >= w:
x = 0
y += h / 2.0
def get_record(self):
record = [[] for _ in xrange(10)]
for l in file(self.path):
entries = l.split()
record[int(entries[0])].append(float(entries[1]))
for level in record:
level.sort()
return record
def add(self, level, result):
file(self.path, "a").write("%i %i\n" % (level, result))
self.set_view()
return self.get_record()[level].index(result)
def activate(self):
self.active = True
def update(self):
if self.active:
self.display_surface.blit(self.surface, self.parent.gate.image_rect)