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)
18.222.120.133
18.222.120.133
18.222.120.133
 
September 30, 2015


Edge of Life is a form I made with Babycastles and Mouth Arcade for an event in New York called Internet Yami-ichi, a flea market of internet-ish goods. We set up our table to look like a doctor's office and pharmacy and offered free examinations and medication prescriptions, a system described by one person as "a whole pharmacy and medical industrial complex".

Diagnoses were based on responses to the form and observations by our doctor during a short examination. The examination typically involved bizarre questions, toy torpedoes being thrown at people and a plastic bucket over the patient's head. The form combined ideas from Myers-Briggs Type Indicators, Codex Seraphinianus and chain-mail personality tests that tell you which TV show character you are. In our waiting room, we had Lake of Roaches installed in a stuffed bat (GIRP bat). It was really fun!

The icons for the food pyramid are from Maple Story and the gun icons are from the dingbat font Outgunned. I'm also using Outgunned to generate the items in Food Spring.