import os
delattr(os, "link")

from seam.pgfw.Setup import Setup

if __name__ == "__main__":
    Setup().setup()
from seam.pgfw.SetupWin import SetupWin

if __name__ == "__main__":
    SetupWin().setup()
# CCND
# Certified Crime Network Database

from math import sqrt, pi, sin, asin, radians, cos, acos, atan2, tan, hypot
from random import (triangular, randint, choice, random, randrange, uniform,
                    shuffle)
from collections import deque
from glob import glob, iglob
from re import match
from os import listdir
from os.path import join, basename, exists
from sys import argv
from copy import copy, deepcopy
from itertools import product, chain

from pygame import Surface, PixelArray, Color, Rect
from pygame.font import Font
from pygame.mixer import Sound, set_num_channels
from pygame.draw import line, polygon, circle, rect as drect
from pygame.time import get_ticks
from pygame.image import load, save
from pygame.event import clear
from pygame.transform import rotate, scale, flip
from pygame.gfxdraw import aacircle, aapolygon
from pygame.locals import *

from seam.pgfw.Game import Game
from seam.pgfw.GameChild import GameChild
from seam.pgfw.Sprite import Sprite
from seam.pgfw.Animation import Animation
from seam.pgfw.Note import Note
from seam.pgfw.Configuration import TypeDeclarations
from seam.pgfw.Vector import Vector
from seam.pgfw.motion import get_step, get_points_on_circle, get_endpoint

class SoundEffect(GameChild, Sound):

    def __init__(self, parent, path, volume):
        GameChild.__init__(self, parent)
        Sound.__init__(self, path)
        self.display_surface = self.get_display_surface()
        self.set_volume(volume)

    def play(self, loops=0, maxtime=0, fade_ms=0, position=None, x=None):
        channel = Sound.play(self, loops, maxtime, fade_ms)
        if x is not None:
            position = float(x) / self.display_surface.get_width()
	if position is not None and channel is not None:
            channel.set_volume(*self.get_panning(position))
        return channel

    def get_panning(self, position):
        return 1 - max(0, ((position - .5) * 2)), \
               1 + min(0, ((position - .5) * 2))


class Seam(Game, Animation):

    METER_BEVEL = 15
    METER_SIZE = 300, 22
    METER_ALPHA = 255
    METER_TICK_WIDTH = 3
    METER_TICK_MARGIN = 3
    METER_SHADOW_ALPHA = 120

    def __init__(self):
        Game.__init__(self, type_declarations=Types())
        Animation.__init__(self, self)
        set_num_channels(16)
        self.delegate = self.get_game().delegate
        self.capturing = False
        self.speed_nodeset = self.interpolator.get_nodeset("vroom-1")
        self.introduction = Introduction(self)
        self.ground = Ground(self)
        self.map = Map(self)
        self.digit_glyphs = DigitGlyphs(self)
        self.timer = Timer(self)
        self.wires = Wires(self)
        self.ships = Ships(self)
        self.net = Net(self)
        self.enemies = Enemies(self)
        self.life_meter = LifeMeter(self)
        self.grid = Grid(self)
        self.train = Train(self)
        self.power_ups = PowerUps(self)
        self.city = City(self)
        self.success_message = EndMessage(self, "SUCCESS", 0)
        self.failure_message = EndMessage(self, "GAME OVER", 0)
        self.instructions = Instructions(self)
        self.high_scores = HighScores(self)
        self.next_sound_effect = SoundEffect(self,
                                             self.get_resource("next.wav"), .9)
        self.initial_audio = Sound(self.get_resource("Beam.ogg"))
        self.game_audio = Sound(self.get_resource("Mouth.ogg"))
        self.end_audio = Sound(self.get_resource("Tode.ogg"))
        self.register(self.release_game_over_wait_period,
                      self.release_instructions_display_wait_period,
                      self.activate_introduction)
        self.reset()
        self.subscribe(self.respond)
        clear()

    def reset(self):
        self.game_over = False
        self.success = False
        self.game_over_wait_period_over = True
        self.displaying_instructions = True
        self.instructions_display_wait_period_over = False
        self.play(self.release_instructions_display_wait_period, delay=800,
                  play_once=True)
        self.queue_introduction(True)
        self.introduction.reset()
        self.map.reset()
        self.end_audio.fadeout(1000)
        self.game_audio.fadeout(1000)
        self.initial_audio.play(-1, 0, 1000)
        self.ground.reset()
        self.timer.reset()
        self.ships.reset()
        self.wires.reset()
        self.net.reset()
        self.enemies.reset()
        self.life_meter.reset()
        self.grid.reset()
        self.train.reset()
        self.power_ups.reset()
        self.city.reset()
        self.success_message.reset()
        self.failure_message.reset()
        self.instructions.reset()
        self.high_scores.reset()

    def release_instructions_display_wait_period(self):
        self.instructions_display_wait_period_over = True

    def queue_introduction(self, check_argv=False):
        delay = 90000
        if check_argv and "-ti" in argv:
            delay = 0
        # self.play(self.activate_introduction, delay=delay, play_once=True)

    def activate_introduction(self):
        self.introduction.activate()
        self.initial_audio.fadeout(1000)

    def respond(self, event):
        compare = self.delegate.compare
        if not self.game_over and not self.displaying_instructions:
            if compare(event, "act"):
                self.net.start()
            elif compare(event, "act", True) and self.net.active:
                self.net.capture()
        if compare(event, "reset-game"):
            self.reset()
        elif self.game_over and self.game_over_wait_period_over and \
                 compare(event, "any"):
            self.reset()
            self.next_sound_effect.play()
            self.end_audio.fadeout(1000)
        elif self.displaying_instructions and \
             self.instructions_display_wait_period_over and compare(event,
                                                                    "any"):
            if self.introduction.active:
                self.introduction.deactivate()
                self.queue_introduction()
            else:
                self.displaying_instructions = False
                self.halt(self.activate_introduction)
                self.instructions.deactivate()
                self.high_scores.deactivate()
                self.timer.start()
                self.enemies.spawn_initial()
                self.enemies.activate_spawning()
                self.next_sound_effect.play()
                self.initial_audio.fadeout(1000)
                self.game_audio.play(-1, 0, 0)
                self.life_meter.show()

    def end_game(self, success):
        self.enemies.halt_spawning()
        self.ships.cancel_motion()
        self.net.deactivate()
        self.game_audio.fadeout(1000)
        self.end_audio.play(-1, 0, 1000)
        self.timer.running = False
        self.game_over = True
        self.game_over_wait_period_over = False
        self.play(self.release_game_over_wait_period, delay=1200,
                  play_once=True)
        if success:
            scores = open(self.get_resource("scores"), "a")
            string = str(self.grid.ratio)
            scores.write(string + "\n")
            scores.close()
            self.success_message.activate()
            self.high_scores.refresh(string)
            self.next_sound_effect.play()
        else:
            self.failure_message.activate()
            self.high_scores.cancel_blink()
            self.enemies.hit_sound_effect.play()
        self.success = success

    def release_game_over_wait_period(self):
        self.game_over_wait_period_over = True

    def apply_speed_curve(self, low, high, inverse=False):
        ratio = self.speed_nodeset.get_y(self.timer.get_position())
        if inverse:
            ratio = 1 - ratio
        return low + ratio * (high - low)

    def update(self):
        if self.introduction.active:
            self.introduction.update()
        else:
            self.map.update()
            self.train.update()
            self.ground.update()
            self.train.draw_hidden()
            self.power_ups.update()
            self.map.update_expressions()
            self.wires.update()
            self.ships.shift()
            self.net.update()
            self.ships.update()
            self.enemies.update()
            if not self.game_over and self.life_meter.lives == 0:
                self.end_game(False)
            self.city.update()
            self.map.update_going_neurons()
            self.life_meter.update()
            self.digit_glyphs.reset()
            self.timer.update()
            self.grid.update()
            self.digit_glyphs.update()
            self.failure_message.update()
            self.success_message.update()
            self.instructions.update()
            self.high_scores.update()
        Animation.update(self)


class Introduction(Animation):

    def __init__(self, parent):
        Animation.__init__(self, parent)
        self.delegate = self.get_game().delegate
        self.display_surface = self.get_display_surface()
        self.set_photos()
        self.set_plates()
        self.set_border()
        self.audio = Sound(self.get_resource(join("intro", "audio.ogg")))
        self.audio.set_volume(.3)
        self.register(self.advance, interval=10000)

    def set_photos(self):
        photos = self.photos = []
        for path in sorted(glob(join(self.get_resource("intro"), "*.png"))):
            photos.append(Photo(self, load(path).convert()))

    def set_plates(self):
        font = Font(self.get_resource("display", "introduction-font"), 38)
        background = self.plate_background = Surface((693, 194))
        rect = self.plate_rect = background.get_rect()
        background.fill((0, 0, 0))
        background.set_alpha(int(255 * .9))
        rect.topleft = 53, 375
        plates = self.plates = []
        x_offset = 3
        min_sw = 16
        limit = rect.inflate(-40, 0).w - x_offset
        for blurb in file(self.get_resource(join("intro",
                                                 "lines"))).read().\
                                                 decode("utf_8").\
                                                 split("\n\n"):
            x = x_offset
            total_w = 0
            lines = [[]]
            cur_ii = 0
            for word in blurb.split():
                w = font.size(word)[0]
                x += w
                if x > limit:
                    lines[-1].append(limit - total_w)
                    x = 0
                    total_w = 0
                    lines.append([])
                lines[-1].append(word)
                total_w += w
                x += min_sw
            lines[-1].append(limit - total_w)
            ls = self.line_spacing = 19
            lh = font.get_linesize() + ls
            base = Surface((limit + x_offset, lh * len(lines)), SRCALPHA)
            color = Color(0, 0, 0)
            plate = Sprite(self, 220)
            for ho in xrange(0, 360, 60):
                frame = base.copy()
                y = 0
                for ii, line in enumerate(lines):
                    if ii == len(lines) - 1:
                        sw = min_sw
                        extra = 0
                    else:
                        sw = line[-1] / (len(line) - 1)
                        extra = line[-1] % (len(line) - 1)
                    x = x_offset
                    for word in line[:-1]:
                        color.hsla = ho, 70, 30, 100
                        text = font.render(word, True, color)
                        # self.reduce_alpha(text, 10)
                        frame.blit(text, (x - x_offset, y))
                        color.hsla = (240 + ho) % 360, 70, 30, 100
                        text = font.render(word, True, color)
                        # self.reduce_alpha(text, 10)
                        frame.blit(text, (x + x_offset, y))
                        text = font.render(word, True, (255, 255, 255))
                        # self.reduce_alpha(text, 25)
                        frame.blit(text, (x, y))
                        x += text.get_width() + sw + (extra >= 1)
                        extra -= 1
                    y += lh
                plate.add_frame(frame)
                plate.location.center = rect.center
                plate.location.top += ls - 2
            plates.append(plate)

    def reduce_alpha(self, surface, amount):
        pixels = PixelArray(surface)
        color = Color(0, 0, 0)
        for x in xrange(len(pixels)):
            for y in xrange(len(pixels[0])):
                h, s, l, a = Color(*surface.unmap_rgb(pixels[x][y])).hsla
                color.hsla = h, s, l, max(a - amount, 0)
                pixels[x][y] = color
        del pixels

    def set_border(self):
        border = self.border = Sprite(self)
        color = Color(0, 0, 0)
        for hue in xrange(0, 360, 10):
            rect = self.plate_rect.inflate(-8, -8)
            frame = Surface(rect.size)
            frame.set_colorkey((0, 0, 0))
            for ii in xrange(2):
                color.hsla = hue, 100, 50 + ii * 24, 100
                offset = 4 * ii
                drect(frame, color, (offset, offset, rect.w - offset * 2,
                                     rect.h - offset * 2), 1)
            border.add_frame(frame)
        border.location.topleft = rect.topleft

    def advance(self):
        self.active_index += 1
        if self.active_index == len(self.photos):
            self.active_index = 0
            self.audio.fadeout(3000)
            self.deactivate()
            self.parent.queue_introduction()

    def reset(self):
        self.deactivate()
        self.halt(self.advance)

    def deactivate(self):
        self.active = False
        self.audio.fadeout(3000)
        self.parent.initial_audio.play(-1, 0, 3000)
        self.parent.ground.reset()

    def activate(self):
        self.active = True
        self.active_index = 0
        self.reset_timer(self.advance)
        self.play(self.advance)
        self.audio.play(0, 0, 1000)

    def update(self):
        Animation.update(self)
        if self.active:
            self.photos[1].update()
            ds = self.display_surface
            ds.blit(self.plate_background, self.plate_rect)
            self.plates[self.active_index].update()
            self.border.update()


class Photo(Sprite):

    def __init__(self, parent, base):
        Sprite.__init__(self, parent, 240)
        self.set_frames(base)

    def set_frames(self, base):
        color = Color(0, 0, 0)
        fc = 2
        self.add_frame(base)
        for ii in xrange(1, fc):
            frame = base.copy()
            pixels = PixelArray(frame)
            hue_offset = 360.0 / fc * ii
            color = Color(0, 0, 0)
            for rgb in product((0, 255), repeat=3):
                h, s, l, a = Color(*rgb).hsla
                color.hsla = int(h + hue_offset) % 360, int(round(s)), \
                             int(round(l)), a
                pixels.replace(rgb, color)
            # for x in xrange(len(pixels)):
            #     for y in xrange(len(pixels[0])):
            #         h, s, l, a = Color(*frame.unmap_rgb(pixels[x][y])).hsla
            #         color.hsla = int(h + hue_offset) % 360, int(round(s)), \
            #                      int(round(l)), a
            #         pixels[x][y] = color
            del pixels
            self.add_frame(frame)


class Ground(GameChild):

    BASE_COLOR = Color(225, 192, 87)
    TRANSPARENT_COLOR = 255, 255, 255
    FRAME_COUNT = 12
    SIGN_FOREGROUND = 255, 0, 255

    def __init__(self, parent):
        GameChild.__init__(self, parent)
        ds = self.display_surface = self.get_display_surface()
        self.background = Surface(ds.get_size())
        self.background.set_colorkey(self.TRANSPARENT_COLOR)
        self.power = Power(self)

    def set_frames(self):
        ds = self.display_surface
        background = Surface(ds.get_size())
        root = self.get_resource("sign")
        index = None
        for arg in argv:
            result = match("^-([\-0-9][0-9]*)$", arg)
            if result:
                index = int(result.group(1))
        paths = listdir(root)
        if index is None:
            # index = randrange(0, len(paths))
            index = 66
        file_name = basename(sorted(paths)[index])
        image = load(join(root, file_name)).convert()
        image.set_colorkey(self.TRANSPARENT_COLOR)
        for x in xrange(image.get_width()):
            for y in xrange(image.get_height()):
                if (x + y) % 2:
                    image.set_at((x, y), self.TRANSPARENT_COLOR)
        area_r = Rect(0, 0, 200, 300)
        area_r.center = ds.get_rect().center
        image_r = image.get_rect()
        dx = (area_r.w - image_r.w) / self.FRAME_COUNT
        dy = -(area_r.h - image_r.h) / self.FRAME_COUNT
        image_r.bottomleft = area_r.bottomleft
        if dx % 2 != dy % 2:
            dy += 1
            image_r.left -= self.FRAME_COUNT / 2
        dx = 0
        start_x, start_y = image_r.topleft
        fc = self.FRAME_COUNT
        # first = randint(325, 347)
        first = randint(348, 352)
        hues = deque(first + 25 * abs(i - fc / 2) for i in xrange(fc))
        color = Color(0, 0, 0)
        for ii in xrange(fc):
            x, y = start_x, start_y
            frame = background.copy()
            frame.set_colorkey(self.TRANSPARENT_COLOR)
            for ii, hue in enumerate(hues):
                sign = image.copy()
                sign.set_alpha(int(31 + (255 - 31) * float(ii) / (fc - 1)))
                color.hsla = hue % 360, 100, 50, 100
                pixels = PixelArray(sign)
                pixels.replace(self.SIGN_FOREGROUND, color)
                del pixels
                frame.blit(sign, (int(x), int(y)))
                x += dx
                y += dy
            hues.rotate(-1)
            self.add_frame(frame)

    def reset(self):
        self.power.reset()

    def update(self):
        # opened = self.get_current_frame().copy()
        # for rect in self.parent.map.revealed:
        #     opened.fill(self.TRANSPARENT_COLOR, rect)
        # Sprite.update(self, substitute=opened)
        ground = self.background.copy()
        self.power.display_surface = ground
        self.power.update()
        for rect in self.parent.map.revealed:
            ground.fill(self.TRANSPARENT_COLOR, rect)
        self.display_surface.blit(ground, (0, 0))


class Power(Sprite):

    SIZE = 250
    HUE_START_RANGE = 348, 352
    HUE_STEP = 25
    FRAME_COUNT = 12
    FRAME_Y_OFFSET = 10
    INTERVAL = 100

    def __init__(self, parent):
        Sprite.__init__(self, parent, self.INTERVAL)
        self.display_surface = self.get_display_surface()

    def reset(self):
        self.set_frames()

    def set_frames(self):
        fc = self.FRAME_COUNT
        hues = deque(randint(*self.HUE_START_RANGE) + self.HUE_STEP * abs(fi - fc / 2) for fi in xrange(fc))
        color = Color(0, 0, 0)
        offset = self.FRAME_Y_OFFSET
        for fi in xrange(fc):
            frame = Surface((self.SIZE, self.SIZE + offset * (fc - 1)), SRCALPHA)
            fr = frame.get_rect()
            for bi in xrange(fc):
                triangle = Surface((fr.w, fr.w), SRCALPHA)
                tr = triangle.get_rect()
                y = fr.h - (bi * offset) - 1
                color.hsla = hues[bi] % 360, 100, 50, 0
                color.a = int(21 + (255 - 21) * (float(bi) / (fc - 1)))
                polygon(triangle, color, get_points_on_circle(tr.center, tr.w / 2, 3))
                polygon(triangle, (0, 0, 0, 0), get_points_on_circle(tr.center, tr.w / 2 * .5, 3))
                frame.blit(triangle, (0, y - tr.h - 1))
            # for x in xrange(fr.w):
            #     for y in xrange(fr.h):
            #         if (x + y) % 2:
            #             frame.set_at((x, y), (0, 0, 0, 0))
            hues.rotate(-1)
            # write(frame, "%i.png" % fi)
            self.add_frame(frame)
        self.location.center = self.display_surface.get_rect().center
        self.location.top += offset * 5

    def get_equilateral_triangle(self, rect):
        x0, y0, x1, x2, y1 = rect.left, rect.bottom - 1, rect.right - 1, rect.w / 2 - 1 + rect.left,  rect.top
        return (x0, y0), (x1, y0), (x2, y1)

    def shrink_range(self, limits, ratio):
        start, end = limits
        offset = (end - start) * ratio / 2
        return map(int, (start + offset, end - offset))
                

class Map(Sprite):

    SHADOW_THICKNESS = 8
    SHADOW_ALPHA = 178
    HORIZONTAL, VERTICAL = range(2)
    PADDING = 120

    def __init__(self, parent):
        Sprite.__init__(self, parent, 125)
        self.set_frames()
        self.set_shadow_strips()
        self.neurons = Neurons(self)

    def set_frames(self):
        for path in sorted(iglob(join(self.get_resource("display",
                                                        "map-frame-path"),
                                     "*.png"))):
            self.add_frame(load(path).convert())

    def set_shadow_strips(self):
        tile = Surface([self.SHADOW_THICKNESS] * 2, SRCALPHA)
        tw, th = tile.get_size()
        for y in xrange(th):
            tile.fill((0, 0, 0, self.SHADOW_ALPHA * (1 - float(y) / (th - 1))),
                      (0, y, tw, 1))
        ds = self.get_display_surface()
        horizontal, vertical = self.shadow_strips = (Surface((ds.get_width() + \
                                                              self.PADDING,
                                                              th), SRCALPHA),
                                                     Surface((tw,
                                                              ds.get_height() \
                                                              + self.PADDING),
                                                             SRCALPHA))
        for x in xrange(0, horizontal.get_width(), tw):
            horizontal.blit(tile, (x, 0))
        for y in xrange(0, vertical.get_height(), tw):
            vertical.blit(rotate(tile, 90), (0, y))
        diagonal = self.diagonal = tile.copy()
        for w in xrange(tw, 0, -1):
            diagonal.fill((0, 0, 0, self.SHADOW_ALPHA * (1 - float(w) / tw)),
                          (0, 0, w, w))

    # we had a good dog sperm
    # see you in the afterlife
    # psychotic thoughts of blowing people away one by one
    # BLAT BLAT BLAT
    # everybody goes down
    # bananas I'm losing it
    # I'm going bananas
    # there's nothing but a hole
    # ...and 12 packs of crayons
    # snuffed out at 187 years old

    def reset(self):
        self.union_mode = True
        self.revealed = []
        self.shadow_markers = []
        self.neurons.reset()

    def reveal(self, initial):
        if not self.union_mode:
            bites = [initial]
            outgoing = []
            incoming = []
            clear = False
            while not clear:
                clear = True
                for rect in self.revealed:
                    for bite in bites:
                        if rect.colliderect(bite):
                            clear = False
                            self.revealed.remove(rect)
                            bites.remove(bite)
                            if rect != bite:
                                self.revealed.extend(self.split_rect(rect,
                                                                     bite))
                                bites.extend(self.split_rect(bite,
                                                             bite.clip(rect)))
                            break
            self.revealed.extend(bites)
        else:
            self.revealed.append(initial)
        self.set_shadow_markers()
        self.neurons.check_visibility()

    def split_rect(self, rect, other):
        cross = rect.clip(other)
        pieces = []
        if cross.top > rect.top:
            left = max(cross.left, rect.left)
            pieces.append(Rect(left, rect.top,
                               min(cross.right, rect.right) - left,
                               cross.top - rect.top))
            if cross.left > rect.left:
                pieces.append(Rect(rect.left, rect.top, cross.left - rect.left,
                                   cross.top - rect.top))
            if cross.right < rect.right:
                pieces.append(Rect(cross.right, rect.top,
                                   rect.right - cross.right,
                                   cross.top - rect.top))
        if cross.left > rect.left:
            pieces.append(Rect(rect.left, max(cross.top, rect.top),
                               cross.left - rect.left, min(rect.h, cross.h)))
        if cross.bottom < rect.bottom:
            left = max(cross.left, rect.left)
            pieces.append(Rect(left, cross.bottom,
                               min(cross.right, rect.right) - left,
                               rect.bottom - cross.bottom))
            if cross.left > rect.left:
                pieces.append(Rect(rect.left, cross.bottom,
                                   cross.left - rect.left,
                                   rect.bottom - cross.bottom))
            if cross.right < rect.right:
                pieces.append(Rect(cross.right, cross.bottom,
                                   rect.right - cross.right,
                                   rect.bottom - cross.bottom))
        if cross.right < rect.right:
            pieces.append(Rect(cross.right, max(cross.top, rect.top),
                               rect.right - cross.right, min(rect.h, cross.h)))
        return pieces

    def set_shadow_markers(self):
        shadow_markers = self.shadow_markers = [], []
        for rect in self.revealed:
            for orientation in self.HORIZONTAL, self.VERTICAL:
                if orientation == self.HORIZONTAL:
                    keep = (rect.left, rect.right),
                else:
                    keep = (rect.top, rect.bottom),
                unset = []
                for other in self.revealed:
                    if rect.colliderect(other) or rect.right == other.left or \
                           rect.left == other.right or \
                           rect.top == other.bottom or rect.bottom == other.top:
                        if orientation == self.HORIZONTAL:
                            if other.top < rect.top <= other.bottom:
                                unset.append((other.left, other.right))
                        else:
                            if other.left < rect.left <= other.right:
                                unset.append((other.top, other.bottom))
                for other in unset:
                    r_keep = []
                    for low, high in keep:
                        if other[0] > high or other[1] < low:
                            r_keep.append((low, high))
                        else:
                            under = low, other[0]
                            over = other[1], high
                            if under[0] < under[1]:
                                r_keep.append(under)
                            if over[0] < over[1]:
                                r_keep.append(over)
                    keep = r_keep
                shadow_markers[orientation].append((rect, keep))

    def update(self):
        Sprite.update(self, self.revealed)
        self.neurons.update()
        ds = self.display_surface
        for ii, markers in enumerate(self.shadow_markers):
            strip = self.shadow_strips[ii]
            for rect, keep in markers:
                for low, high in keep:
                    if ii == self.HORIZONTAL:
                        position = low, rect.top
                        w, h = high - low, strip.get_height()
                    else:
                        position = rect.left, low
                        w, h = strip.get_width(), high - low
                    ds.blit(strip, position, (0, 0, w, h))
                    # ds.blit(strip, position)
                    if ii == self.HORIZONTAL:
                        ds.blit(self.diagonal, (high, rect.top))

    def update_expressions(self):
        self.neurons.update_expressions()

    def update_going_neurons(self):
        self.neurons.update_going()


class Neurons(GameChild):

    def __init__(self, parent):
        GameChild.__init__(self, parent)
        self.display_surface = self.get_display_surface()
        self.root_directory = self.get_resource("display", "neuron-path")
        self.set_neurons()
        self.expression_surface = load(join(self.root_directory,
                                            "joy.png")).convert_alpha()
        self.found_sound_effect = SoundEffect(self,
                                              self.get_resource("lick.wav"), .5)
        self.home_sound_effect = SoundEffect(self,
                                             self.get_resource("home.wav"), .45)

    def set_neurons(self):
        neurons = self.neurons = []
        shadow_surface = load(self.get_resource(join(self.root_directory,
                                                     "shadow.png"))).\
                                                     convert_alpha()
        for ii, line in enumerate(open(join(self.root_directory, "map"))):
            values = tuple(int(x) for x in line.split())
            neurons.append(Neuron(self, ii, values[0:2], shadow_surface,
                                  values[2:4]))

    def reset(self):
        self.active_expressions = []
        for neuron in self.neurons:
            neuron.reset()
        self.set_destinations()

    def set_destinations(self):
        available = list(self.parent.parent.city.DOORS)
        shuffle(available)
        for neuron in self.neurons:
            neuron.set_destination(available.pop())

    def check_visibility(self):
        for neuron in (n for n in self.neurons if not n.revealed):
            area = None
            for rect in self.parent.revealed:
                if rect.colliderect(neuron.location):
                    piece = rect.clip(neuron.location)
                    if not area:
                        area = piece
                    else:
                        area.union_ip(piece)
                    if area and area.w >= neuron.location.w and \
                           area.h >= neuron.location.h:
                        neuron.revealed = True
                        self.active_expressions.append(Expression(
                            self, neuron.location.topleft))
                        self.found_sound_effect.play(x=neuron.location.centerx)
                        neuron.play(neuron.go, delay=3000, play_once=True)
                        break

    def update(self):
        for neuron in self.neurons:
            if not neuron.going:
                neuron.update()

    def update_going(self):
        for neuron in self.neurons:
            if neuron.going:
                neuron.update()

    def update_expressions(self):
        for expression in self.active_expressions:
            expression.update()


class Neuron(Sprite):

    SPEED = 2
    SPOTLIGHT_RADIUS = 25
    SPOTLIGHT_COLOR = Color(255, 0, 255, 70)
    SPOTLIGHT_OFFSET = 0

    def __init__(self, parent, index, coordinates, shadow_surface,
                 shadow_coordinates):
        Sprite.__init__(self, parent, 240)
        self.load_from_path(join(self.parent.root_directory, str(index)),
                            True)
        self.starting_coordinates = coordinates
        self.register(self.go)
        self.set_shadow(shadow_surface, shadow_coordinates)
        self.set_spotlight()

    def set_shadow(self, surface, coordinates):
        shadow = self.shadow = Sprite(self)
        shadow.add_frame(surface)
        self.shadow_starting_coordinates = coordinates

    def set_spotlight(self):
        radius = self.SPOTLIGHT_RADIUS
        spotlight = self.spotlight = Surface([radius * 2] * 2, SRCALPHA)
        for ii in xrange(radius, 8, -1):
            alpha = int(160 * (1 - float(ii) / radius))
            color = Color(choice((220, 80)), choice((220, 80)),
                          choice((220, 80)), alpha)
            circle(spotlight, color, [radius] * 2, ii)
        self.spotlight_rect = spotlight.get_rect()

    def set_destination(self, destination):
        self.destination = destination, self.display_surface.get_height()

    def go(self):
        self.going = True
        self.delta = get_step(self.location.midbottom, self.destination,
                              self.SPEED)

    def reset(self):
        self.revealed = False
        self.going = False
        self.location.topleft = self.starting_coordinates
        self.shadow.location.topleft = self.shadow_starting_coordinates
        self.unhide()
        self.shadow.unhide()

    def update(self):
        if self.going:
            if self.location.collidepoint(self.destination):
                self.parent.home_sound_effect.play(x=self.location.centerx)
                self.going = False
                self.hide()
                self.shadow.hide()
                self.get_game().city.enter(self.destination[0])
            self.move(*self.delta)
            self.shadow.move(*self.delta)
            if not self.in_background():
                self.spotlight_rect.center = self.location.center
                self.spotlight_rect.top += self.SPOTLIGHT_OFFSET
                self.display_surface.blit(self.spotlight, self.spotlight_rect)
        self.shadow.update()
        Sprite.update(self)

    def in_background(self):
        for rect in self.get_game().map.revealed:
            if rect.colliderect(self.location):
                return True


class Expression(Sprite):

    def __init__(self, parent, base):
        Sprite.__init__(self, parent)
        self.add_frame(self.parent.expression_surface)
        self.location.topleft = base[0] + 15, base[1] - 13
        self.register(self.disappear, self.appear)
        self.hide()
        self.play(self.appear, play_once=True, delay=500)

    def appear(self):
        self.unhide()
        self.play(self.disappear, play_once=True, delay=1000)

    def disappear(self):
        self.parent.active_expressions.remove(self)


class DigitGlyphs(GameChild):

    SPACING = 7

    def __init__(self, parent):
        GameChild.__init__(self, parent)
        self.set_glyphs()

    def set_glyphs(self):
        glyphs = self.glyphs = []
        root = self.get_resource("digits")
        for path in sorted(glob(join(root, "[0-9].png"))) + [join(root,
                                                                  "-.png")]:
            glyph = Sprite(self)
            glyph.load_from_path(path, True, False)
            glyphs.append(glyph)

    def reset(self):
        for glyph in self.glyphs:
            glyph.remove_locations()
            glyph.hide()

    def render(self, message, start_position):
        if str(message) == "100":
            message = "-1"
        for ii, char in enumerate(str(message).zfill(2)):
            if char == "-":
                glyph = self.glyphs[-1]
            else:
                glyph = self.glyphs[int(char)]
            position = start_position[0] + ii * \
                       (glyph.location.w + self.SPACING), start_position[1]
            if glyph.is_hidden():
                glyph.unhide()
                glyph.location.topleft = position
            else:
                glyph.add_location(position)

    def update(self):
        for glyph in self.glyphs:
            glyph.update()


class Meter(GameChild):

    SIZE = 70, 15
    MARGIN = 1
    ALPHA = 127
    LEFT, RIGHT = range(2)
    GRADIENT = 0, 120

    def __init__(self, parent, alignment, limit, symbol_file_name):
        GameChild.__init__(self, parent)
        self.alignment = alignment
        self.limit = limit
        self.display_surface = self.get_display_surface()
        self.set_mask()
        self.set_rect()
        self.set_symbol(symbol_file_name)

    def set_mask(self):
        w, h = self.SIZE
        mask = self.mask = Surface((w, h), SRCALPHA)
        mask.fill((255, 255, 255, self.ALPHA))
        polygon(mask, (255, 255, 255, 0), ((0, 1), (0, h - 1), (h - 2, h - 1)))
        if self.alignment == self.LEFT:
            self.mask = flip(mask, True, False)

    def set_rect(self):
        rect = self.rect = self.mask.get_rect()
        if self.alignment == self.RIGHT:
            rect.topright = self.display_surface.get_rect().topright

    def set_symbol(self, file_name):
        symbol = self.symbol = Sprite(self)
        symbol.load_from_path(join(self.get_resource("meter"), file_name), True,
                              False)
        if self.alignment == self.LEFT:
            symbol.location.left = 1
        else:
            symbol.location.right = self.rect.right - 1

    def render(self, level):
        rect = self.rect
        if self.alignment == self.RIGHT:
            position = rect.left + 23, 2
        else:
            position = 24, 2
        self.get_game().digit_glyphs.render(level, position)

    def update(self, level):
        color = Color(0, 0, 0)
        low, high = self.GRADIENT
        hue = low + (high - low) * float(level) / self.limit
        color.hsla = hue, 100, 50, 100
        surface = Surface(self.rect.size, SRCALPHA)
        surface.fill(color)
        surface.blit(self.mask, (0, 0), None, BLEND_RGBA_MIN)
        self.display_surface.blit(surface, self.rect)
        self.symbol.update()
        self.render(level)


class Timer(Meter):

    LIMIT = 99999

    def __init__(self, parent):
        Meter.__init__(self, parent, Meter.LEFT, 99, "clock.png")
        self.display_surface = self.get_display_surface()

    def reset(self):
        self.elapsed = 0
        self.running = False
        self.last_ticks = None

    def start(self):
        self.running = True
        self.last_ticks = None

    def get_position(self):
        return float(self.elapsed) / self.LIMIT

    def update(self):
        ticks = get_ticks()
        if self.running and self.last_ticks is not None:
            self.elapsed += ticks - self.last_ticks
            if self.elapsed >= self.LIMIT:
                self.elapsed = self.LIMIT
                self.parent.end_game(True)
        self.last_ticks = ticks
        Meter.update(self, int(self.LIMIT - self.elapsed) / 1000)


class Wires(GameChild, list):

    AMPLITUDES = 8, 10, 13, 16, 18, 16, 13, 10
    WIDTH = 9592
    LEFT, RIGHT = -1, 1
    # SPEED_RANGE = 2, 10
    SPEED_RANGE = 2, 3.8
    # SPEED_EXTREMES = 1, 13
    SPEED_EXTREMES = 1, 10

    def __init__(self, parent):
        GameChild.__init__(self, parent)
        list.__init__(self, (Wire(self, self.LEFT), Wire(self, self.RIGHT)))

    def reset(self):
        self.disable_extreme_speed()
        for wire in self:
            wire.reset()

    def disable_extreme_speed(self):
        self.extreme_speed = None

    def set_speed(self):
        if self.extreme_speed is not None:
            self.speed = self.extreme_speed
        else:
            self.speed = self.get_game().apply_speed_curve(*self.SPEED_RANGE)

    def enable_extreme_speed(self, fast=True):
        self.extreme_speed = self.SPEED_EXTREMES[fast]

    def update(self):
        self.set_speed()
        for wire in self:
            wire.update()


class Wire(Sprite):

    TRANSPARENT_COLOR = 255, 0, 255
    # SWELL_INTERVAL_RANGE = 40, 140
    SWELL_INTERVAL_RANGE = 100, 140

    def __init__(self, parent, heading):
        Sprite.__init__(self, parent)
        self.heading = heading
        self.display_surface = self.get_display_surface()
        self.color = (191, 71, 128) if heading == parent.LEFT else \
                     (71, 100, 221)
        second = self.second_color = Color(*self.color)
        h, s, l, a = second.hsla
        second.hsla = h, s, l + 15, a
        third = self.third_color = Color(*self.color)
        third.hsla = h, s, l + 30, a
        self.add_location(offset=(self.location.w, 0))

    def reset(self):
        self.amplitude_index = 0
        self.set_points()
        self.set_swell_interval()
        self.play(self.swell)

    def set_points(self):
        points = self.points = []
        next_jump = 0
        x = randint(-90, 90)
        while x < self.parent.WIDTH:
            if next_jump == 0 and x < self.parent.WIDTH - 800:
                random_point = self.get_random_point(x)
                while len(points) > 0 and \
                          abs(random_point[1] - points[-1][1]) < 80:
                    random_point = self.get_random_point(x)
                points.append(random_point)
                next_jump = int(round(triangular(2, 5)))
            x += randint(218, 402)
            points.append((x, self.get_random_y(points[-1][1])))
            next_jump -= 1

    def get_random_point(self, x):
        return x, self.get_random_y()

    def get_random_y(self, previous=None):
        if previous is None:
            previous = self.display_surface.get_rect().centery
        deviation = 73
        y = previous + abs(deviation - triangular(-deviation, deviation)) * \
            choice((1, -1))
        upper, lower = 12, self.display_surface.get_height() - 12
        if y < upper:
            y += (upper - y) * 2
        elif y > lower:
            y -= (y - lower) * 2
        return y

    def get_y(self, x, amplitude=None):
        if amplitude is None:
            amplitude = self.get_current_amplitude()
        while x < 0:
            x += self.parent.WIDTH
        x %= self.parent.WIDTH
        index = 0
        points = self.points
        while x > points[index + 1][0]:
            index += 1
        px, py = points[index]
        nx, ny = points[index + 1]
        dx = nx - px
        distance = sqrt(dx ** 2 + (ny - py) ** 2)
        h = int(round(distance * float(x - px) / dx))
        cycles = 4 * pi / distance
        beta = radians(90) - asin((nx - px) / distance)
        if ny < py:
            beta *= -1
        sx = amplitude * sin(cycles * h) + py
        return sin(beta) * h - cos(beta) * (sx - py) + py

    def get_current_amplitude(self):
        return self.parent.AMPLITUDES[self.amplitude_index]

    def swell(self):
        self.amplitude_index += 1
        if self.amplitude_index >= len(self.parent.AMPLITUDES):
            self.amplitude_index = 0

    def update(self):
        self.set_swell_interval()
        self.move(self.heading * self.parent.speed)
        if self.location.right < 0:
            self.move(self.location.width)
        if self.location.left > 0:
            self.move(-self.location.width)
        step = 10
        offset = -self.location.left
        px, py = offset, self.get_y(offset)
        ds = self.display_surface
        for x in xrange(offset, offset + ds.get_width() + step, step):
            y = self.get_y(x)
            if abs(y - py) < 16:
                line(ds, self.color, (px - offset, py), (x - offset, y))
                line(ds, self.second_color, (px - offset, py + 1),
                     (x - offset, y + 1))
                line(ds, self.third_color, (px - offset, py + 2),
                     (x - offset, y + 2))
            px, py = x, y
        Sprite.update(self)

    def set_swell_interval(self):
        low, high = self.SWELL_INTERVAL_RANGE
        self.register(self.swell, interval=int(self.get_game().\
                      apply_speed_curve(low, high, True)))


class Ships(GameChild, list):

    INVINCIBILITY_LENGTH = 3000

    def __init__(self, parent):
        GameChild.__init__(self, parent)
        list.__init__(self, [Ship(self, Ship.OUTER), Ship(self, Ship.INNER)])

    def reset(self):
        for ship in self:
            ship.reset()

    def shift(self):
        for ship in self:
            ship.shift()

    def cancel_motion(self):
        for ship in self:
            ship.cancel_motion()

    def set_invincible(self, length=INVINCIBILITY_LENGTH):
        for ship in self:
            ship.set_invincible(length)

    def are_invincible(self):
        return self[0].is_playing(self[0].blink)

    def collides(self, other):
        for ship in self:
            if ship.location.colliderect(other):
                return True

    def update(self):
        for ship in self:
            ship.update()


class Ship(Sprite):

    OUTER, INNER = range(2)
    LEFT, RIGHT = range(2)
    BLINK_INTERVAL = 150

    def __init__(self, parent, position):
        Sprite.__init__(self, parent, 240)
        self.position = position
        self.wire = parent.parent.wires[0] if \
                    position == self.OUTER else parent.parent.wires[1]
        self.display_surface = self.get_display_surface()
        self.delegate = self.get_delegate()
        self.set_frames()
        self.register(self.blink, interval=self.BLINK_INTERVAL)
        self.register(self.cancel_blink)
        self.subscribe(self.respond)
        self.cancel_motion()

    def blink(self):
        self.toggle_hidden()

    def cancel_blink(self):
        self.halt(self.blink)
        self.unhide()

    def set_invincible(self, length):
        self.play(self.blink)
        self.play(self.cancel_blink, delay=length, play_once=True)

    def cancel_motion(self):
        self.moving = [False, False]

    def reset(self):
        self.remove_locations()
        self.location.centerx = self.display_surface.get_rect().centerx
        self.add_location(offset=(self.display_surface.get_width(), 0))
        self.cancel_blink()

    def set_frames(self):
        max_width = 18
        hue = 200 if self.position == self.INNER else 320
        foreground = Color(0, 0, 0)
        background = Color(0, 0, 0)
        for ii, width in enumerate(xrange(max_width, 10, -2)):
            frame = Surface((max_width, max_width))
            frame.set_colorkey((0, 0, 0))
            fr = frame.get_rect()
            rect = Rect((0, 0, width - 2, width - 2))
            rect.center = fr.center
            foreground.hsla = hue, 100, 85, 100
            background.hsla = hue, 100, 30, 100
            polygon(frame, background, ((rect.left, rect.top),
                                        (rect.right, rect.top),
                                        (rect.right, rect.bottom),
                                        (rect.left, rect.bottom)), 1)
            rect.move_ip(-1, -1)
            polygon(frame, foreground, ((rect.left, rect.top),
                                        (rect.right, rect.top),
                                        (rect.right, rect.bottom),
                                        (rect.left, rect.bottom)), 1)
            reticule = (255, 0, 0) if self.position == self.INNER else \
                       (255, 255, 0)
            if ii % 2 == 1:
                polygon(frame, reticule, ((fr.centerx - 2, fr.centery - 2),
                                          (fr.centerx + 1, fr.centery - 2),
                                          (fr.centerx + 1, fr.centery + 1),
                                          (fr.centerx - 2, fr.centery + 1)))
            hue = (hue - 16) % 360
            self.add_frame(frame)

    def respond(self, event):
        if not self.parent.parent.game_over and not \
               self.parent.parent.displaying_instructions:
            compare = self.delegate.compare
            if compare(event, "right") or compare(event, "right", True):
                self.moving[self.RIGHT] = not event.cancel
            elif compare(event, "left") or compare(event, "left", True):
                self.moving[self.LEFT] = not event.cancel

    def shift(self):
        speed = 6
        if self.moving[self.LEFT]:
            self.move(-speed)
        if self.moving[self.RIGHT]:
            self.move(speed)
        bound = self.display_surface.get_width()
        if self.location.right < 0:
            self.move(bound)
        if self.location.left > bound:
            self.move(-bound)
        for location in self.locations:
            location.centery = self.wire.get_y(self.location.centerx - \
                                               self.wire.location.left)


class Net(GameChild):

    GROWTH_RATES = .375, .8

    def __init__(self, parent):
        GameChild.__init__(self, parent)
        ds = self.display_surface = self.get_display_surface()
        self.drawn_rects = []
        self.hues = deque(xrange(0, 360, 40))
        self.growing_sound_effect = SoundEffect(self,
                                                self.get_resource("grow.wav"),
                                                1)
        self.capture_sound_effect = SoundEffect(self, self.\
                                    get_resource("capture.wav"), 1)

    def reset(self):
        self.deactivate()
        self.init_rect()
        self.set_growth_rate()

    def deactivate(self):
        self.active = False
        self.growing_sound_effect.stop()

    def init_rect(self):
        rect = self.rect = Rect(0, 0, 0, 0)
        return rect

    def set_growth_rate(self, quick=False):
        self.growth_rate = self.GROWTH_RATES[quick]

    def start(self):
        self.ox, self.oy = 19, 19
        self.activate()

    def activate(self):
        self.active = True
        self.growing_sound_effect_channel = self.growing_sound_effect.play(-1)

    def capture(self):
        self.deactivate()
        for rect in self.drawn_rects:
            territory = rect.clip(self.display_surface.get_rect())
            self.parent.map.reveal(rect)
            self.parent.grid.add(territory)
        if self.drawn_rects:
            self.capture_sound_effect.play(x=rect.centerx)

    def collide(self, other):
        for rect in self.drawn_rects:
            if rect.colliderect(other):
                return True

    def update(self):
        if self.active:
            self.ox += self.growth_rate
            self.oy += self.growth_rate
            rect = self.init_rect()
            ships = self.parent.ships
            rect.width = int(self.ox) * 2
            distance = abs(ships[0].location.centery - \
                           ships[1].location.centery)
            rect.height = distance + int(self.oy) * 2
            rect.centerx = ships[0].location.centerx
            if ships[0].location.centery > ships[1].location.centery:
                rect.centery = ships[1].location.centery + (distance / 2)
            else:
                rect.centery = ships[0].location.centery + (distance / 2)
            color = Color(0, 0, 0)
            color.hsla = self.hues[0], 90, 70, 100
            self.draw_outlines(rect, color)
            self.hues.rotate(-1)
            color.hsla = self.hues[0], 90, 90, 100
            self.draw_outlines(rect.inflate(-4, -4), color)
            if self.growing_sound_effect.get_num_channels():
                position = float(rect.centerx) / \
                           self.display_surface.get_width()
                self.growing_sound_effect_channel.set_volume(\
                    *self.growing_sound_effect.get_panning(position))

    def draw_outlines(self, rect, color):
        self.drawn_rects = []
        self.draw_outline(rect, color)
        offsets = []
        dsr = self.display_surface.get_rect()
        if rect.left < 0:
            self.draw_outline(rect.move(dsr.w, 0), color)
            if rect.top < 0:
                self.draw_outline(rect.move(dsr.w, dsr.h), color)
            elif rect.bottom > dsr.h:
                self.draw_outline(rect.move(dsr.w, -dsr.h), color)
        elif rect.right > dsr.w:
            self.draw_outline(rect.move(-dsr.w, 0), color)
            if rect.top < 0:
                self.draw_outline(rect.move(-dsr.w, dsr.h), color)
            elif rect.bottom > dsr.h:
                self.draw_outline(rect.move(-dsr.w, -dsr.h), color)
        if rect.top < 0:
            self.draw_outline(rect.move(0, dsr.h), color)
        elif rect.bottom > dsr.h:
            self.draw_outline(rect.move(0, -dsr.h), color)

    def draw_outline(self, rect, color):
        self.drawn_rects.append(rect)
        polygon(self.display_surface, color,
                (rect.topleft, rect.topright, rect.bottomright,
                 rect.bottomleft), 1)


class Enemies(Animation):

    SPAWN_RATE = .0008
    INITIAL_POPULATION = 6
    # INITIAL_POPULATION = 5
    # SPEED_RANGE = 3, 9
    SPEED_RANGE = 4, 4
    # SPEED_OFFSETS = -2, 4
    SPEED_OFFSETS = -3, 6
    TAB_INDENT = 8
    TAB_POINT_OFFSET = 4, 7

    def __init__(self, parent):
        Animation.__init__(self, parent)
        self.display_surface = self.get_display_surface()
        self.set_alphabet()
        self.set_speech_bubble_tab()
        self.hit_sound_effect = SoundEffect(self, self.get_resource("hit.wav"),
                                            1)
        self.spawn_sound_effect = SoundEffect(self,
                                              self.get_resource("spawn.wav"),
                                              .9)
        self.eat_sound_effect = SoundEffect(self,
                                            self.get_resource("eat-enemy.wav"),
                                            .9)
        self.register(self.spawn)

    def set_alphabet(self):
        surface = load(self.get_resource(join("attack",
                                              "alphabet.png"))).convert()
        transparent_color = surface.get_at((0, 0))
        surface.set_colorkey(transparent_color)
        rect = Rect(1, 1, 1, surface.get_height() - 2)
        alphabet = self.alphabet = {}
        while len(alphabet) < 27:
            is_space = True
            for y in xrange(rect.top, rect.bottom + 1):
                if surface.get_at((rect.right, y)) != transparent_color:
                    is_space = False
                    break
            if is_space:
                glyph = surface.subsurface(rect).copy()
                if len(alphabet) < 26:
                    alphabet[chr(len(alphabet) + 97)] = glyph
                else:
                    alphabet["!"] = glyph
                rect.left = rect.right + 1
                rect.w = 1
            else:
                rect.w += 1

    def set_speech_bubble_tab(self):
        surface = self.speech_bubble_tab = load(self.get_resource(\
            join("attack", "speech.png"))).convert()
        w, h = surface.get_size()
        surface.set_colorkey(surface.get_at((w - 1, h - 1)))

    def reset(self):
        self.enemies = []
        self.halt_spawning()
        self.halt(self.spawn)
        self.disable_speed_offset()
        self.attack = None
        self.sync = False
        self.command_history = []

    def halt_spawning(self):
        self.spawning = False

    def disable_speed_offset(self):
        self.speed_offset = 0

    def set_speed(self):
        for enemy in self.enemies:
            enemy.speed = self.get_game().apply_speed_curve(\
                *self.SPEED_RANGE) + self.speed_offset

    def enable_speed_offset(self, fast=True):
        self.speed_offset = self.SPEED_OFFSETS[fast]

    def spawn_initial(self):
        self.play(self.spawn, play_once=True, initial=True)

    def activate_spawning(self):
        self.spawning = True

    def spawn(self, initial=False):
        enemy = Enemy(self)
        if enemy.place():
            self.spawn_sound_effect.play(x=enemy.location.centerx)
            enemy.materialize()
            self.enemies.append(enemy)
            if initial and len(self.enemies) < self.INITIAL_POPULATION:
                self.play(self.spawn, delay=400, play_once=True, initial=True)

    def organize(self, instigator):
        roll = random()
        history = self.command_history
        elapsed = self.parent.timer.elapsed
        if not self.history_contains((Attack.SYNC, Attack.DISPERSE), 3) or \
           (not self.history_contains((Attack.SYNC, Attack.DISPERSE), 2) and \
            roll < .5) or (not self.history_contains((Attack.SYNC,
                                                      Attack.DISPERSE), 1) and \
                           roll < .25) or roll < .1:
            command = Attack.DISPERSE if self.sync else Attack.SYNC
        elif (self.check_spawn_increase(0) and elapsed > 20000) or \
             (self.check_spawn_increase(1) and elapsed > 50000) or \
             (self.check_spawn_increase(2) and elapsed > 70000):
            command = Attack.SPAWN
        else:
            command = choice([command for command in \
                              (Attack.RAIN, Attack.ROLL, Attack.PULSE,
                               Attack.DILATION, Attack.TRAP, Attack.FALL) if \
                              not self.history_contains(command, 5)])
        history.append(command)
        self.attack = Attack(self, instigator, command)

    def history_contains(self, commands, depth=0):
        if isinstance(commands, int):
            commands = [commands]
        return any(command in self.command_history[-depth:] for command in \
                   commands)

    def check_spawn_increase(self, excess):
        return len(self.enemies) <= self.INITIAL_POPULATION + excess

    def synchronize_movement(self):
        self.sync = True

    def desynchronize_movement(self):
        self.sync = False

    def get_motion_vector(self):
        return randrange(0, 360), randint(21, 395)

    def update(self):
        self.set_speed()
        Animation.update(self)
        # if not self.attack and self.spawning and random() < self.SPAWN_RATE:
        #     self.spawn()
        if self.attack:
            self.attack.update()
            if not self.attack.is_playing(check_all=True):
                self.attack = None
        ships = self.parent.ships
        if not ships.are_invincible() and not self.parent.game_over:
            if self.parent.net.active:
                for enemy in self.enemies:
                    if not enemy.is_materializing():
                        for location in enemy.locations:
                            if self.parent.net.collide(location):
                                if not enemy.is_edible():
                                    self.strike_ships(enemy)
                                else:
                                    self.eat_enemy(enemy)
                                break
                    if ships.are_invincible():
                        self.parent.net.deactivate()
                        break
            if not ships.are_invincible():
                for enemy in self.enemies:
                    if not enemy.is_materializing():
                        for location in enemy.locations:
                            if location.collidelist([ship.location for ship in \
                                                     ships]) != -1:
                                if not enemy.is_edible():
                                    self.strike_ships(enemy)
                                else:
                                    self.eat_enemy(enemy)
                                break
                    if ships.are_invincible():
                        break
        self.move()
        for enemy in self.enemies:
            enemy.update()

    def strike_ships(self, enemy):
        # self.enemies.remove(enemy)
        self.parent.life_meter.remove()
        self.hit_sound_effect.play(x=enemy.location.centerx)
        self.parent.ships.set_invincible()

    def eat_enemy(self, enemy):
        self.parent.power_ups.power_ups["eat enemy"].cancel_eat_enemy()
        self.enemies.remove(enemy)
        self.eat_sound_effect.play(x=enemy.location.centerx)

    def move(self):
        if not self.attack or self.attack.command in \
           (Attack.SYNC, Attack.DISPERSE, Attack.SPAWN):
            if self.sync and random() < .05:
                idle = [enemy.is_idle() for enemy in self.enemies]
                if all(idle):
                    vector = self.get_motion_vector()
                    for enemy in self.enemies:
                        enemy.set_course(*vector)
            elif not self.sync:
                for enemy in self.enemies:
                    if enemy.is_idle() and random() < .01:
                        enemy.set_course()


class Enemy(Sprite):

    # MOVE_CHANCE_RANGE = .01, .05
    MOVE_CHANCE_RANGE = .01, .01
    BLINK_INTERVAL = 350
    MATERIALIZATION_LENGTH = 3000
    SPACING = 150
    SPAWN_ATTEMPT_LIMIT = 20
    MOVE_ATTEMPT_LIMIT = 8

    def __init__(self, parent):
        Sprite.__init__(self, parent)
        self.display_surface = self.get_display_surface()
        self.delta = None
        self.set_frames()
        self.register(self.blink, interval=self.BLINK_INTERVAL)
        self.register(self.cancel_blink)

    def set_frames(self):
        w = 21
        so = 2
        color = Color(0, 0, 0)
        hues = 0, 10, 20, 30, 40, 50, 60, 50, 40, 30, 20, 10
        for ii, hue in enumerate(hues):
            frame = Surface((w + so, w + so), SRCALPHA)
            color.hsla = 0, 0, 10, 100
            points = ((0, w - 1), (w / 2, 0), (w - 1, w - 1))
            frame.blit(self.get_grated_polygon(w, color, points), (so, so))
            color.hsla = 0, 0, 60, 75
            frame.blit(self.get_grated_polygon(w, color, points, False),
                       (so, so))
            color.hsla = 0, 100, 90, 90
            frame.blit(self.get_grated_polygon(w, color, points, False), (0, 0))
            color.hsla = hue, 100, 50, 100
            frame.blit(self.get_grated_polygon(w, color, points), (0, 0))
            color.hsla = hues[(ii + len(hues) / 2) % len(hues)], 100, 60, 100
            sw = 4
            points = ((sw, w - sw + 1), (w / 2, sw + 2), (w - sw - 1,
                                                          w - sw + 1))
            frame.blit(self.get_grated_polygon(w, color, points), (0, 0))
            self.add_frame(frame)
        self.add_frameset(name="edible", switch=True)
        hues = 240, 230, 220, 210, 200, 190, 180, 190, 200, 210, 220, 230
        for ii, hue in enumerate(hues):
            frame = Surface((w + so, w + so), SRCALPHA)
            color.hsla = 0, 0, 70, 100
            points = ((0, 0), (w, 0), (w / 2 - 1, w - 1))
            frame.blit(self.get_grated_polygon(w, color, points), (so, so))
            color.hsla = 0, 0, 60, 75
            frame.blit(self.get_grated_polygon(w, color, points, False),
                       (so, so))
            color.hsla = 240, 100, 90, 90
            frame.blit(self.get_grated_polygon(w, color, points, False), (0, 0))
            color.hsla = hue, 100, 50, 100
            frame.blit(self.get_grated_polygon(w, color, points), (0, 0))
            color.hsla = hues[(ii + len(hues) / 2) % len(hues)], 100, 60, 100
            sw = 4
            points = (sw, sw - 1), (w - sw - 1, sw - 1), (w / 2, w - sw - 2)
            frame.blit(self.get_grated_polygon(w, color, points), (0, 0))
            self.add_frame(frame)
        self.set_frameset(0)

    def get_grated_polygon(self, w, color, points, odd=True):
        surface = Surface((w, w), SRCALPHA)
        polygon(surface, color, points)
        for x in xrange(odd, w, 2):
            surface.fill((0, 0, 0, 0), (x, 0, 1, w))
        return surface

    def place(self):
        self.location.center = self.get_random_location()
        tries = 0
        while self.location.collidelist(\
            [ship.location for ship in self.parent.parent.ships]) != -1 or \
            not self.is_apart():
            self.location.center = self.get_random_location()
            tries += 1
            if tries == self.SPAWN_ATTEMPT_LIMIT:
                print "don't spawn"
                return False
        ds = self.display_surface
        self.add_location(offset=(ds.get_width(), 0))
        self.add_location(offset=(0, ds.get_height()))
        return True

    def get_random_location(self):
        return randint(0, self.display_surface.get_width()), \
               randint(0, self.display_surface.get_height())

    def is_apart(self):
        for enemy in self.parent.enemies:
            if enemy != self:
                if not self.check_spacing(self.location.center,
                                          enemy.location.center):
                    return False
        return True

    def check_spacing(self, location, other):
        x, y = location
        ox, oy = other
        lx, ly = self.display_surface.get_size()
        dx = abs(x - ox)
        dy = abs(y - oy)
        return hypot(min(dx, abs(lx - dx)), min(dy, abs(ly - dy))) > \
                   self.SPACING

    def blink(self):
        self.toggle_hidden()

    def cancel_blink(self):
        self.halt(self.blink)
        self.unhide()

    def materialize(self):
        self.play(self.blink)
        self.play(self.cancel_blink, delay=self.MATERIALIZATION_LENGTH,
                  play_once=True)

    def set_course(self, angle=None, magnitude=None):
        start = self.location.center
        lw, lh = self.display_surface.get_size()
        tries = 0
        override = angle is not None
        while True:
            if not override:
                angle, magnitude = self.parent.get_motion_vector()
            end = get_endpoint(start, angle, magnitude)
            normal_destination = end[0] % lw, end[1] % lh
            if override:
                break
            collide = False
            for enemy in self.parent.enemies:
                if enemy != self:
                    if enemy.delta:
                        other = enemy.normal_destination
                    else:
                        other = enemy.location.center
                    if not self.check_spacing(normal_destination, other):
                        collide = True
                        break
            if not collide:
                break
            tries += 1
            if tries == self.MOVE_ATTEMPT_LIMIT:
                print "don't set course"
                return
        self.normal_destination = normal_destination
        self.delta = get_step(start, end, self.speed)
        self.left = Vector(abs(start[0] - end[0]), abs(start[1] - end[1]))

    def is_materializing(self):
        return self.is_playing(self.blink)

    def is_edible(self):
        return self.get_current_frameset().name == "edible"

    def is_idle(self):
        attack = self.parent.attack
        return self.delta is None and not self.is_materializing() and \
               (not attack or (attack.command in (Attack.SYNC, Attack.DISPERSE,
                                                  Attack.SPAWN) and \
                               (attack.instigator != self or not \
                                attack.is_playing(attack.end_speak,
                                                  include_delay=True))))

    def update(self):
        if self.delta:
            if self.reached_destination():
                self.delta = None
            else:
                self.move(*self.delta)
                self.left -= map(abs, self.delta)
            rect = self.display_surface.get_rect()
            if self.location.right < 0:
                self.move(rect.w)
            if self.location.left > rect.w:
                self.move(-rect.w)
            if self.location.bottom < 0:
                self.move(dy=rect.h)
            if self.location.top > rect.h:
                self.move(dy=-rect.h)
        if self.is_idle():
            # if not self.parent.sync and random() < \
            #        self.get_game().apply_speed_curve(*self.MOVE_CHANCE_RANGE):
            #     self.set_course()
            if not self.parent.attack and random() < .025:
                self.parent.organize(self)
        Sprite.update(self)

    def reached_destination(self):
        return any(coordinate < 0 for coordinate in self.left)


class Attack(Animation):

    BUBBLE_OFFSET = 7, -18
    SYNC, DISPERSE, SPAWN, RAIN, ROLL, PULSE, DILATION, TRAP, FALL = range(9)
    NAMES = {SYNC: "sync", DISPERSE: "disperse", SPAWN: "spawn", RAIN: "rain",
             ROLL: "roll", PULSE: "pulse", DILATION: "dilation", TRAP: "trap",
             FALL: "fall"}

    def __init__(self, parent, instigator, command):
        Animation.__init__(self, parent)
        self.display_surface = self.get_display_surface()
        self.instigator = instigator
        self.command = command
        self.message = None
        self.set_speech_bubble()
        self.register(self.end_speak, self.end_attack)
        self.play(self.end_speak, delay=2500, play_once=True)

    def set_speech_bubble(self):
        offset = 15, -5
        tab = self.parent.speech_bubble_tab
        ir = self.instigator.location
        dr = self.display_surface.get_rect()
        fx, fy = ir.centerx > dr.centerx, ir.centery < dr.centery
        offset = [offset[ii] * -fl for ii, fl in enumerate((fx, fy))]
        tab = flip(tab, fx, fy)
        alphabet = self.parent.alphabet
        glyphs = []
        w = 0
        for ch in self.NAMES[self.command].lower() + "!":
            glyphs.append(alphabet[ch])
            w += glyphs[-1].get_width() + 1
            if ch == "!":
                w += 1
        gr = glyphs[0].get_rect()
        bubble = Surface((w + 7, gr.h + 6))
        transparent_color = (255, 0, 255)
        bubble.fill(transparent_color)
        bubble.set_colorkey(transparent_color)
        br = bubble.get_rect()
        bubble.fill((255, 255, 255), (1, 1, br.w - 2, gr.h + 4))
        line(bubble, (0, 0, 0), (2, 0), (br.w - 3, 0))
        bubble.set_at((1, 1), (0, 0, 0))
        line(bubble, (0, 0, 0), (0, 2), (0, gr.h + 3))
        bubble.set_at((1, gr.h + 4), (0, 0, 0))
        line(bubble, (0, 0, 0), (2, gr.h + 5), (br.w - 3, gr.h + 5))
        bubble.set_at((br.w - 2, gr.h + 4), (0, 0, 0))
        line(bubble, (0, 0, 0), (br.w - 1, 2), (br.w - 1, gr.h + 3))
        bubble.set_at((br.w - 2, 1), (0, 0, 0))
        x = 4
        for glyph in glyphs:
            if glyph.get_width() == 1:
                x += 1
            bubble.blit(glyph, (x, 3))
            x += glyph.get_width() + 1
        tr = tab.get_rect()
        surface = Surface((br.w, br.h + tr.h - 1))
        surface.fill(transparent_color)
        surface.set_colorkey(transparent_color)
        surface.blit(bubble, (0, fy * (tr.h - 1)))
        ti = self.parent.TAB_INDENT
        surface.blit(tab, (ti + (br.w - ti * 2 - tab.get_width()) * fx,
                           (not fy) * (br.h - 1)))
        sb = self.speech_bubble = Sprite(self)
        sb.add_frame(surface)
        box, boy = self.BUBBLE_OFFSET
        cx, cy = self.instigator.location.center
        if fy:
            boy *= -1
        sb.location.centery = cy + boy
        if not fx:
            sb.location.left = cx + box
        else:
            sb.location.right = cx - box

    def end_speak(self):
        self.speech_bubble.hide()
        self.act()

    def act(self):
        c = self.command
        enemies = self.parent
        if c == self.SYNC:
            self.set_message("sync")
            self.parent.synchronize_movement()
        elif c == self.DISPERSE:
            self.set_message("disperse")
            self.parent.desynchronize_movement()
        elif c == self.SPAWN:
            self.set_message("spawn")
            self.parent.spawn()
        elif c == self.ROLL:
            self.set_message("roll")
        elif c == self.RAIN:
            self.set_message("rain")
        elif c == self.TRAP:
            self.set_message("trap")
        elif c == self.FALL:
            self.set_message("fall")
        elif c == self.PULSE:
            self.set_message("pulse")
        elif c == self.DILATION:
            self.set_message("dilation")

    def set_message(self, text, delay=8000):
        font = Font(self.get_resource("display", "completion-font"), 13)
        self.message = font.render(text, False, (0, 0, 0), (255, 255, 255))
        self.play(self.end_attack, delay=delay, play_once=True)

    def end_attack(self):
        self.halt()

    def update(self):
        Animation.update(self)
        self.speech_bubble.update()
        if self.message:
            self.get_display_surface().blit(self.message, (0, 30))


class LifeMeter(Animation):

    INITIAL_LIVES = 3
    MARGIN = 48
    DISPLAY_LENGTH = 2000

    def __init__(self, parent):
        Animation.__init__(self, parent)
        self.display_surface = self.get_display_surface()
        self.heart = Heart(self)
        self.register(self.cancel_show)

    def reset(self):
        self.lives = self.INITIAL_LIVES
        self.rearrange()
        self.heart.hide()
        self.halt()

    def remove(self):
        self.lives -= 1
        if self.lives:
            self.rearrange()
            self.show()

    def add(self):
        self.lives += 1
        self.rearrange()
        self.show()

    def rearrange(self):
        sr = self.display_surface.get_rect()
        hr = self.heart.location
        count = self.lives
        self.heart.remove_locations()
        x = sr.w / 2 - (hr.w / 2) * (count - 1) - self.MARGIN / 2 * (count - 1)
        y = sr.h / 2
        hr.center = x, y
        for ii in xrange(1, count):
            self.heart.add_location(offset=((hr.w + self.MARGIN) * ii, 0))

    def show(self):
        self.heart.unhide()
        self.play(self.cancel_show, delay=self.DISPLAY_LENGTH, play_once=True)

    def blink(self):
        self.heart.toggle_hidden()

    def cancel_show(self):
        self.heart.hide()

    def update(self):
        Animation.update(self)
        self.heart.update()


class Heart(Sprite):

    def __init__(self, parent):
        Sprite.__init__(self, parent)
        self.set_frames()

    def set_frames(self):
        for ii, path in enumerate(sorted(glob(join(self.get_resource("heart"),
                                                   "[0-4]*.png")))):
            self.add_frame(load(path).convert_alpha())
        self.add_frameset([0, 1, 4, 2, 0, 3, 4],
                          [300, 0, 400, 0, 400, 0, 300], switch=True)


class Grid(Meter, list):

    def __init__(self, parent):
        Meter.__init__(self, parent, Meter.RIGHT, 100, "percent.png")
        self.display_surface = self.get_display_surface()

    def reset(self):
        ds = self.display_surface
        list.__init__(self, [[False for y in xrange(ds.get_height())] for x in \
                             xrange(ds.get_width())])
        self.ratio = 0.0

    def add(self, rect):
        for x in xrange(rect.left, rect.right):
            self[x][rect.top:rect.bottom] = [True] * (rect.bottom - rect.top)
        count = 0
        for x in self.parent.grid:
            for y in x:
                count += y
        self.ratio = float(count) / (len(self) * len(self[0]))

    def update(self):
        Meter.update(self, int(self.ratio * 100))


class Train(Animation):

    COUNT = 7
    SPACING = 42
    SPEED = 4
    PEEK_DEPTH = 2

    def __init__(self, parent):
        Animation.__init__(self, parent)
        self.display_surface = self.get_display_surface()
        self.hands = tuple(Hand(self) for _ in xrange(self.COUNT))
        self.pin = Pin(self)
        self.trap_sound_effect = SoundEffect(self, \
            self.get_resource(join("power-up", "hand", "wind-laser.wav")), .8)
        self.register(self.queue_exit)

    def reset(self):
        self.halt()
        self.escaping = False
        self.trapped = False
        self.gone = False
        hands = self.hands
        absolute = self.absolute_locations = [[0, 0]] * len(hands)
        for hand in hands:
            hand.reset()
        ds = self.display_surface
        absolute[0] = hands[0].location.center = [randrange(0, ds.get_width()),
                                                  randrange(0, ds.get_height())]
        self.direction = randrange(0, 360)
        backward = (self.direction + 180) % 360
        for ii, hand in enumerate(hands[1:]):
            backward += triangular(-40, 40)
            x = hands[ii].location.centerx + sin(radians(backward)) * \
                self.SPACING
            y = hands[ii].location.centery - cos(radians(backward)) * \
                self.SPACING
            absolute[ii + 1] = hand.location.center = [x, y]
        for hand in hands:
            hand.add_location(offset=(ds.get_width(), 0))
            hand.add_location(offset=(0, ds.get_height()))
        self.active_notes = []

    def queue_exit(self):
        self.gone = True

    def update(self):
        if not self.gone:
            Animation.update(self)
            hands = self.hands
            if not self.trapped:
                self.trapped = True
                escape = True
                for ii, hand in enumerate(self.hands):
                    contained = False
                    area = None
                    for rect in self.parent.map.revealed:
                        if rect.colliderect(hand.location):
                            piece = rect.clip(hand.location)
                            if not area:
                                area = piece
                            else:
                                area.union_ip(piece)
                            if area and area.w >= hand.location.w and \
                                   area.h >= hand.location.h:
                                contained = True
                                break
                    if not contained:
                        if ii < self.PEEK_DEPTH:
                            escape = False
                        if self.escaping and ii == 0:
                            self.escaping = False
                        self.trapped = False
                        break
                if escape and not self.escaping:
                    self.escaping = True
                    self.direction = (self.direction + 180 + \
                                      triangular(-20, 20)) % 360
                if self.trapped:
                    self.trap_sound_effect.play(x=self.hands[0].location.\
                                                centerx)
                    notes = self.active_notes = []
                    hue_offset = randrange(0, 360)
                    for ii, hand in enumerate(reversed(self.hands)):
                        notes.append(Eighth(self, self.hands.index(hand) == 0,
                                            hue_offset))
                        notes[-1].location.center = hand.location.center
                        notes[-1].play(notes[-1].start, delay=300 * ii,
                                       play_once=True)
                        hand.set_frameset("giving")
                        hand.fade(4000, True)
                        hue_offset += 25
                else:
                    absolute = self.absolute_locations
                    for ii in xrange(len(hands) - 1, 0, -1):
                        x2, y2, x1, y1 = absolute[ii - 1] + absolute[ii]
                        distance = sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
                        angle = atan2(x2 - x1, y2 - y1)
                        dx, dy = sin(angle) * distance * .1, \
                                 cos(angle) * distance * .1
                        hands[ii].move(dx, dy)
                        absolute[ii][0] += dx
                        absolute[ii][1] += dy
                    if random() < .0625:
                        self.direction += randint(-65, 65)
                    dx, dy = sin(radians(self.direction)) * self.SPEED, \
                             -cos(radians(self.direction)) * self.SPEED
                    hands[0].move(dx, dy)
                    absolute[0][0] += dx
                    absolute[0][1] += dy
            for hand in hands:
                hand.update()
            for note in self.active_notes:
                note.update()

    def draw_hidden(self):
        self.pin.remove_locations()
        draw = False
        first = True
        for hand in self.hands:
            place_pin = True
            for rect in self.parent.map.revealed:
                if rect.colliderect(hand):
                    place_pin = False
                    break
            if place_pin:
                draw = True
                if first:
                    location = self.pin.location
                    first = False
                else:
                    location = self.pin.add_location((0, 0))
                location.center = hand.location.center
        if draw:
            self.pin.update()


class Hand(Sprite):

    TRANSPARENT_COLOR = (255, 0, 255)
    FRAME_COUNT = 8

    def __init__(self, parent):
        Sprite.__init__(self, parent)
        ds = self.display_surface = self.get_display_surface()
        self.set_frames()

    def set_frames(self):
        count = self.FRAME_COUNT
        root = self.get_resource(join("power-up", "hand"))
        for state in ("open", "closed"):
            base = load(join(root, "%s.png" % state)).convert()
            base.set_colorkey(self.TRANSPARENT_COLOR)
            for _ in xrange(count):
                frame = base.copy()
                for x in xrange(frame.get_width()):
                    for y in xrange(frame.get_height()):
                        if frame.get_at((x, y)) == (0, 0, 255):
                            color = Color(0, 0, 0)
                            color.hsla = randrange(0, 360), 100, 36, 100
                            frame.set_at((x, y), color)
                self.add_frame(frame)
        self.add_frameset(range(count), 450, "open")
        self.add_frameset([ii + (ii % 2) * count for ii in range(count)], 450,
                          "giving")

    def reset(self):
        self.remove_locations()
        self.set_frameset("open")
        self.location.fader.reset()
        self.set_alpha(255)

    def update(self):
        rect = self.display_surface.get_rect()
        if self.location.right < 0:
            self.move(rect.w)
        if self.location.left > rect.w:
            self.move(-rect.w)
        if self.location.bottom < 0:
            self.move(dy=rect.h)
        if self.location.top > rect.h:
            self.move(dy=-rect.h)
        Sprite.update(self)


class Pin(Sprite):

    SIZE = 16, 16
    FRAME_COUNT = 10
    RING_COUNT = 8

    def __init__(self, parent):
        Sprite.__init__(self, parent)
        self.set_frames()

    def set_frames(self):
        w, h = self.SIZE
        for frame_ii in xrange(self.FRAME_COUNT):
            lightness = 50 + 27 * (float(frame_ii + 1) / self.FRAME_COUNT)
            frame = Surface((w, h), SRCALPHA)
            frame.fill((255, 255, 255, 0))
            for circle_ii in xrange(self.RING_COUNT):
                color = Color(0, 0, 0)
                hue = int(360 * (float(circle_ii + frame_ii) / \
                                 self.RING_COUNT)) % 360
                alpha = int(100 * (float(circle_ii + 1) / self.RING_COUNT))
                color.hsla = hue, 90, lightness, alpha
                radius = int(w * (1 - float(circle_ii) / self.RING_COUNT) / 2)
                circle(frame, color, (w / 2, h / 2), radius)
            # color.hsla = 60, 100, 20, 60
            # circle(frame, color, (w / 2, h / 2), w / 2 - 3, 1)
            color.hsla = 0, 0, 30, 68
            aacircle(frame, w / 2, h / 2, w / 2 - 3, color)
            self.add_frame(frame)


class Eighth(Sprite):

    TRANSPARENT_COLOR = 255, 0, 255
    SIXTEENTH_NOTE_LENGTH = 100

    def __init__(self, parent, is_head, hue_offset):
        Sprite.__init__(self, parent)
        self.is_head = is_head
        self.hue_offset = hue_offset
        self.set_frames()
        self.register(self.cancel_explosion, self.show_explosion, self.start, self.fade_notes)
        self.hide()

    def set_frames(self):
        base = load(self.get_resource(join("power-up", "hand",
                                           "note.png"))).convert()
        base.set_colorkey(self.TRANSPARENT_COLOR)
        for hue in ((self.hue_offset + h) % 360 for h in xrange(0, 50, 5)):
            frame = base.copy()
            color = Color(0, 0, 0)
            color.hsla = hue, 95, 55, 100
            pixels = PixelArray(frame)
            pixels.replace((0, 0, 0), color)
            pixels.replace((255, 255, 255), (63, 191, 127))
            del pixels
            self.add_frame(frame)
        self.add_frameset(name="explode", switch=True)
        base = Surface((32, 32), SRCALPHA)
        rect = base.get_rect()
        for ii in xrange(15):
            frame = base.copy()
            color = Color(0, 0, 0)
            color.hsla = ii * 2, 100, 50, 100 - (ii * 2)
            circle(frame, color, rect.center, ii + 1, 1)
            self.add_frame(frame)
        self.set_frameset(0)

    def start(self):
        self.unhide()
        self.play(self.show_explosion, delay=3000, play_once=True)

    def show_explosion(self):
        self.set_frameset("explode")
        self.play(self.cancel_explosion, delay=540, play_once=True)
        channels = self.channels = []
        for _ in xrange(3):
            shape = choice((Note.SQUARE, Note.TRIANGLE, Note.SINE, Note.SAW,
                            Note.DIRTY))
            note = Note(choice(Note.names), randint(2, 5), shape=shape, volume=.15)
            channels.append(note.play(panning=(random(), random())))
            # Note(choice(Note.names), randint(2, 5), shape=shape,
            #      volume=.15).play(fadeout=1000, panning=(random(), random()))
        self.play(self.fade_notes)

    def cancel_explosion(self):
        if self.is_head:
            self.parent.parent.power_ups.use_heart()

    def fade_notes(self):
        for channel in self.channels:
            if channel:
                channel.set_volume(channel.get_volume() - .1)
                if channel.get_volume() <= .1:
                    channel.stop()
                    train = self.parent
                    if not train.is_playing(train.queue_exit, include_delay=True):
                        train.play(train.queue_exit, delay=1000, play_once=True)
                    self.halt(self.fade_notes)


class PowerUps(GameChild):

    BORDER_FILE_NAME = "border.png"
    EFFECT_LENGTH = {"eat enemy": 18000, "grow faster": 14000,
                     "extra heart": 4000, "invincibility": 15000,
                     "slow down": 15000, "spawn enemy": 4000, "speed up": 15000,
                     "union": 4000}

    def __init__(self, parent):
        GameChild.__init__(self, parent)
        directory = self.get_resource("power-up")
        self.supply_sound_effects = (\
            SoundEffect(self, self.get_resource(join(directory, "spotted.wav")),
                        .6),
            SoundEffect(self, self.get_resource(join(directory, "poison.wav")),
                        .6))
        border_base_image = load(join(directory,
                                      self.BORDER_FILE_NAME)).convert_alpha()
        power_ups = self.power_ups = {}
        for path in iglob(join(directory, "*.png")):
            file_name = basename(path)
            effect = self.parse_effect_name(path)
            if file_name != self.BORDER_FILE_NAME:
                power_ups[effect] = PowerUp(self, load(path).convert_alpha(),
                                            effect, border_base_image.copy(),
                                            self.EFFECT_LENGTH[effect])
        music = self.music = {}
        for path in iglob(join(directory, "*.ogg")):
            name = self.parse_effect_name(path)
            music[name] = Sound(self.get_resource(path))
            music[name].set_volume(.6)
        self.dragon = Dragon(self)

    def parse_effect_name(self, path):
        return basename(path).replace("-", " ").split(".")[0]

    def reset(self):
        for audio in self.music.itervalues():
            audio.fadeout(1000)
        for power_up in self.power_ups.values():
            power_up.reset()
        self.dragon.reset()

    def use_heart(self):
        power_up = self.power_ups["extra heart"]
        power_up.activate(self.parent.parent.train.hands[0].location.center)
        power_up.use()

    def play_sound_effect(self, power_up):
        self.supply_sound_effects[\
            power_up.classification].play(x=power_up.location.centerx)

    def update(self):
        for power_up in self.power_ups.itervalues():
            if power_up.active:
                power_up.update()
        self.dragon.update()


class PowerUp(Sprite):

    BORDER_COLORS_HSV = ((62, 76, 100), (172, 76, 100)), ((135, 100, 46),
                                                          (246, 100, 46))
    BACKGROUND_BASE_COLORS = Color(0, 255, 0), Color(255, 0, 0)
    BLINK_INTERVAL = 300
    MARGIN = 200
    POSITIVE, NEGATIVE = range(2)
    FRAME_COUNT = 7
    # The cleansing of the blood has been done before and will continue being
    # done

    def __init__(self, parent, symbol, effect, border, length):
        Sprite.__init__(self, parent)
        self.symbol = symbol
        self.effect = effect
        self.border = border
        self.length = length
        self.display_surface = self.get_display_surface()
        self.classification = self.NEGATIVE if effect in \
                              ("spawn enemy", "speed up") else self.POSITIVE
        self.register(self.blink, interval=self.BLINK_INTERVAL)
        self.register(self.cancel_blink, self.highlight, self.restore_speed,
                      self.cancel_growth_increase, self.fade_music_out,
                      self.restore_game_audio, self.restore_enemy,
                      self.deactivate)
        self.set_frames()

    # financial reports rewound reveal
    # vast expenditures of priceless resource allocation
    # feed addiction at the source
    # the NSA's database
    # scan drones
    # and border patrol telecop corporations
    # they say peace, peace is gone

    def set_frames(self):
        self.add_frameset(name="large")
        borders = self.border, self.border.copy()
        pixels = PixelArray(borders[0])
        pixels.replace((0, 0, 0), (15, 15, 15))
        pixels.replace((255, 255, 255), (159, 159, 159))
        pixels = PixelArray(borders[1])
        pixels.replace((255, 255, 255), (255, 0, 0) if \
                       self.classification == self.NEGATIVE else (0, 255, 0))
        pixels.replace((0, 0, 0), (63, 0, 0) if \
                       self.classification == self.POSITIVE else \
                       (0, 63, 0))
        del pixels
        for ii, hue in enumerate(xrange(0, 360, 360 / self.FRAME_COUNT)):
            frame = Surface(borders[0].get_size(), SRCALPHA)
            ratio = float(ii) / (self.FRAME_COUNT - 1)
            color = Color(0, 0, 0)
            color.hsla = hue, 100, 50 , 100
            frame.fill(color)
            frame.blit(borders[bool(ii)], (0, 0))
            color = Color(0, 0, 0)
            color.hsla = (hue + 180) % 360, 100, 50, 100
            symbol = self.symbol.copy()
            pixels = PixelArray(symbol)
            pixels.replace((0, 0, 0), color)
            del pixels
            rect = symbol.get_rect()
            rect.center = frame.get_rect().center
            frame.blit(symbol, rect)
            self.add_frame(frame)

    def act(self):
        # the CIA cloned my sperm to make Obama's third testicle
        length = self.length
        effect = self.effect
        game = self.get_game()
        if effect == "eat enemy":
            if game.enemies.enemies:
                enemy = choice(game.enemies.enemies)
                enemy.set_frameset("edible")
                self.play(self.restore_enemy, delay=length, play_once=True,
                          enemy=enemy)
            else:
                self.halt(self.deactivate)
                self.play(self.deactivate, delay=2000, play_once=True)
        elif effect == "grow faster":
            game.net.set_growth_rate(True)
            self.play(self.cancel_growth_increase, delay=length, play_once=True)
        elif effect == "extra heart":
            game.life_meter.add()
        elif effect == "invincibility":
            game.ships.set_invincible(length)
        elif effect == "slow down":
            game.wires.enable_extreme_speed(False)
            game.enemies.enable_speed_offset(False)
            self.play(self.restore_speed, delay=length, play_once=True)
        elif effect == "spawn enemy":
            game.enemies.spawn()
        elif effect == "speed up":
            game.wires.enable_extreme_speed()
            game.enemies.enable_speed_offset()
            self.play(self.restore_speed, delay=length, play_once=True)
        elif effect == "union":
            self.parent.parent.map.union_mode = True
        if effect in self.parent.music:
            game.game_audio.fadeout(2000)
            song = self.parent.music[effect]
            song.play(-1, 0, 2000)
            self.play(self.fade_music_out, delay=length - 1000, play_once=True)
            self.play(self.restore_game_audio, delay=length - 1000,
                      play_once=True)

    def cancel_growth_increase(self):
        self.get_game().net.set_growth_rate()

    def restore_speed(self):
        game = self.get_game()
        game.wires.disable_extreme_speed()
        game.enemies.disable_speed_offset()

    def fade_music_out(self):
        self.parent.music[self.effect].fadeout(1000)

    def restore_game_audio(self):
        self.get_game().game_audio.play(-1, 0, 1000)

    def restore_enemy(self, enemy):
        enemy.set_frameset(0)

    def deactivate(self):
        self.active = False

    def cancel_eat_enemy(self):
        self.halt(self.restore_enemy)
        self.halt(self.fade_music_out)
        self.halt(self.restore_game_audio)
        self.halt(self.deactivate)
        self.fade_music_out()
        self.restore_game_audio()
        self.deactivate()

    def reset(self):
        self.buried = False
        self.halt(self.blink)
        self.halt(self.cancel_blink)
        self.halt(self.highlight)
        self.halt(self.deactivate)
        self.deactivate()
        self.unhide()
        self.set_alpha(255)

    def blink(self):
        self.toggle_hidden()

    def cancel_blink(self):
        self.halt(self.blink)
        self.hide()

    def highlight(self):
        self.location.midtop = self.display_surface.get_rect().centerx, 10
        for multiplier in -1, 1:
            self.add_location(offset=(multiplier * self.location.w * 2, 0),
                              count=1)
        self.unhide()

    def activate(self, position=None, center=True):
        self.remove_locations()
        self.active = True
        if position is not None:
            if center:
                self.location.center = position
            else:
                self.location.topleft = position

    def use(self):
        self.act()
        self.play(self.deactivate, delay=self.length, play_once=True)
        self.parent.play_sound_effect(self)
        if self.effect != "extra heart":
            if self.effect != "spawn enemy":
                self.play(self.blink, delay=self.length - 3000)
            self.play(self.highlight, delay=0, play_once=True)


class Dragon(Sprite):

    POWER_UP_OFFSET = 11, 16
    TIME_GAP_RANGE = 4500, 8900
    FIRST_TIME_GAP_RANGE = 18500, 23500
    VERTICAL_MARGIN = 50
    PATH_RADIUS = 50
    SPEED = 5

    def __init__(self, parent):
        Sprite.__init__(self, parent)
        self.set_frames()
        self.register(self.activate)

    def set_frames(self):
        self.load_from_path(self.get_resource("dragon"), True, False,
                            query="[0-9]*.png", omit=True)
        order = [0, 1, 2, 1] + range(25) + range(23, 2, -1) + range(25, 40) + \
                (range(40, 46) + range(44, 40, -1)) * 16 + \
                range(38, 24, -1) + range(3, -1, -1) + [1, 2, 1, 0]
        framerate = [250] * 6 + [400] * 2 + [40] * 41 + [300] + [40] * 14 + \
                    [40] * 161 + [40] * 14 + [400] + [250] * 7
        self.add_frameset(order, framerate, "appear", True)

    def reset(self):
        self.deactivate()
        self.union_grabbed = True
        self.active_power_up = None
        self.use_first_time_gap = True

    def cue_activation(self):
        if self.use_first_time_gap:
            gap_range = self.FIRST_TIME_GAP_RANGE
            self.use_first_time_gap = False
        else:
            gap_range = self.TIME_GAP_RANGE
        self.play(self.activate, delay=randint(*gap_range), play_once=True)

    def deactivate(self):
        self.hide()
        self.halt()
        self.moving = False

    def activate(self):
        self.set_location_and_path()
        self.unhide()
        self.get_current_frameset().reset()
        self.frame_shift_count = 0
        self.play()

    def shift_frame(self):
        if not self.is_hidden():
            Sprite.shift_frame(self)
            self.frame_shift_count += 1
            if self.frame_shift_count == len(self.get_current_frameset().order):
                self.deactivate()
            elif self.frame_shift_count == 74:
                self.activate_power_up()
            elif self.frame_shift_count == 84:
                self.moving = True
            elif self.frame_shift_count == 205:
                self.moving = False
            elif self.active_power_up and self.frame_shift_count == 215:
                self.active_power_up.deactivate()
                self.active_power_up = None

    def activate_power_up(self):
        if self.active_power_up:
            self.active_power_up.deactivate()
        if not self.union_grabbed:
            power_up = self.parent.power_ups["union"]
        else:
            while True:
                power_up = choice(self.parent.power_ups.values())
                if power_up.effect not in ("union", "extra heart"):
                    break
        power_up.activate()
        self.active_power_up = power_up

    def set_location_and_path(self):
        dr = self.get_display_surface().get_rect()
        radius = self.PATH_RADIUS
        area = dr.inflate(-radius - self.location.w / 2,
                          -self.VERTICAL_MARGIN - self.location.h / 2)
        self.location.center = randrange(area.left, area.right), \
                               randrange(area.top, area.bottom)
        points = deque(get_points_on_circle(self.location.center, radius, 5, 5))
        edges = self.edges = deque()
        for _ in xrange(len(points)):
            edges.append((points[0], points[1]))
            points.rotate(-1)
        edges.rotate(randrange(0, len(edges)))
        self.set_course()

    def set_course(self):
        edge = self.edges[0]
        self.location.center, self.destination = edge
        self.step = get_step(edge[0], edge[1], self.SPEED)

    def update(self):
        Sprite.update(self)
        if not self.is_hidden() and self.moving:
            px = self.location.center[0]
            cx = self.destination[0]
            self.move(*self.step)
            nx = self.location.center[0]
            if not nx - self.destination[0] or \
                   (px - self.destination[0]) / (nx - self.destination[0]) < 0:
                self.edges.rotate(-1)
                self.set_course()
        elif self.is_hidden() and \
                 not any(pu.active for pu in self.parent.power_ups.values()) \
                 and not self.is_playing(self.activate):
            self.cue_activation()
        if self.active_power_up:
            self.activate_power_up()
            seam = self.parent.parent
            power_up = self.active_power_up
            location = power_up.location
            location.topleft = self.location.move(*self.POWER_UP_OFFSET).topleft
            if seam.ships.collides(location) or (seam.net.active and \
                                                 seam.net.collide(location)):
                power_up.use()
                if power_up.effect == "union":
                    self.union_grabbed = True
                self.active_power_up = None
                self.moving = False
                if self.frame_shift_count < 205:
                    increment = 205 - self.frame_shift_count
                    self.get_current_frameset().increment_index(increment)
                    self.frame_shift_count = 205
                    self.accounts[self.get_default()].increment_interval_index(\
                        increment)


class City(GameChild):

    TRANSPARENT_COLOR = 255, 0, 255
    DOORS = 24, 84, 145, 202, 256, 300, 347, 400, 470, 539, 590, 637, 700, 765

    def __init__(self, parent):
        GameChild.__init__(self, parent)
        ds = self.display_surface = self.get_display_surface()
        self.boundaries = 0, 44, 124, 168, 236, 279, 320, 373, 425, 516, 565, \
                          615, 660, 736, ds.get_width()
        self.set_original()
        self.lights = Lights(self)

    def set_original(self):
        original = self.original = load(self.get_resource("skyline.png")).\
                   convert()
        original.set_colorkey(self.TRANSPARENT_COLOR)
        rect = self.skyline_rect = original.get_rect()
        rect.midbottom = self.display_surface.get_rect().midbottom

    def reset(self):
        self.vacancy = [True] * len(self.DOORS)
        self.set_skyline()

    def set_skyline(self):
        skyline = self.skyline = self.original.copy()
        pixels = PixelArray(skyline)
        for x in xrange(len(pixels)):
            for y in xrange(len(pixels[0])):
                color = Color(*skyline.unmap_rgb(pixels[x][y]))
                if color != self.TRANSPARENT_COLOR:
                    h, s, l, a = color.hsla
                    color.hsla = h, 0, l, a
                    pixels[x][y] = color
        del pixels

    def enter(self, x):
        index = self.DOORS.index(x)
        boundaries = self.boundaries
        self.vacancy[index] = False
        corner = boundaries[index], 0
        self.skyline.blit(self.original, corner,
                          (corner, (boundaries[index + 1] - boundaries[index],
                                    self.skyline_rect.h)))

    def update(self):
        if not self.parent.displaying_instructions:
            self.display_surface.blit(self.skyline, self.skyline_rect)
        else:
            self.display_surface.blit(self.original, self.skyline_rect)
        self.lights.update()


class Lights(Animation):

    HALO_MAP = (0,0,1,1,1,0,0),\
               (0,1,0,0,0,1,0),\
               (1,0,0,0,0,0,1),\
               (1,0,0,0,0,0,1),\
               (1,0,0,0,0,0,1),\
               (0,1,0,0,0,1,0)
    HALO_ALPHA = int(.43 * 255)
    HALO_OFFSET = -2, -2
    MARKERS = 0, 3, 7, 13, 14, 19, 21, 23, 29, 36, 43, 46, 53, 57, 60

    def __init__(self, parent):
        Animation.__init__(self, parent)
        ds = self.display_surface = self.get_display_surface()
        self.halos_visible = True
        self.set_lights()
        self.register(self.select, interval=400)
        self.register(self.blink_halos, interval=120)
        self.play(self.select)
        self.play(self.blink_halos)

    def set_lights(self):
        surface = load(self.get_resource("lights.png")).convert()
        surface.set_colorkey((0, 0, 0))
        lights = self.lights = []
        x = 0
        y_offset = self.display_surface.get_height() - surface.get_height()
        while x < surface.get_width() - 1:
            y = 0
            while y < surface.get_height():
                it, other = surface.get_at((x, y)), surface.get_at((x + 1, y))
                if it != (0, 0, 0):
                    if it != other and other != (0, 0, 0):
                        tl = x, y - 1
                        lights.append((surface.subsurface(Rect(tl,
                                                               (3, 4))).copy(),
                                       (tl[0], tl[1] + y_offset),
                                       self.get_halo_surface(other)))
                    y += 3
                y += 1
            x += 1

    def get_halo_surface(self, color):
        halo_map = self.HALO_MAP
        w, h = len(halo_map[0]), len(halo_map)
        surface = Surface((w, h))
        surface.set_colorkey((0, 0, 0))
        color = Color(*color)
        hue, s, v, a = color.hsva
        color.hsva = (hue + 20) % 360, 70, 77, 100
        for y in xrange(h):
            for x in xrange(w):
                if halo_map[y][x]:
                    surface.set_at((x, y), color)
        surface.set_alpha(self.HALO_ALPHA)
        return surface

    def select(self):
        active = self.active = []
        for ii, vacant in enumerate(self.parent.vacancy):
            if not vacant or self.get_game().displaying_instructions:
                for li in xrange(*self.MARKERS[ii:ii + 2]):
                    if random() < .25:
                        active.append(li)

    def blink_halos(self):
        self.halos_visible = not self.halos_visible

    def update(self):
        Animation.update(self)
        for index in self.active:
            self.display_surface.blit(*self.lights[index][:2])
            if self.halos_visible:
                lx, ly = self.lights[index][1]
                ox, oy = self.HALO_OFFSET
                self.display_surface.blit(self.lights[index][2],
                                          (lx + ox, ly + oy))


class EndMessage(Sprite):

    def __init__(self, parent, text, hue):
        Sprite.__init__(self, parent)
        self.font = Font(self.get_resource("display", "end-message-font"), 32)
        spaced = text[0]
        for char in text[1:]:
            spaced += "   " + char
        self.set_frames(spaced, hue)
        self.location.center = self.get_display_surface().get_rect().center

    def reset(self):
        self.deactivate()

    def deactivate(self):
        self.active = False

    def activate(self):
        self.active = True

    def set_frames(self, text, hue):
        color = Color(0, 0, 0)
        for lightness in xrange(50, 100, 5):
            color.hsla = hue, 100, lightness, 100
            self.add_frame(self.font.render(text, True, color))

    def update(self):
        if self.active:
            Sprite.update(self)


class Instructions(Sprite):

    def __init__(self, parent):
        Sprite.__init__(self, parent)
        ds = self.display_surface = self.get_display_surface()
        root = self.get_resource("instructions")
        foreground = load(join(root, "foreground.png")).convert_alpha()
        background = load(join(root, "background.png")).convert_alpha()
        for offset in xrange(0, 64, 4):
            frame = Surface(foreground.get_size())
            frame.blit(background, (-offset, 0))
            frame.blit(background, (frame.get_width() - offset, 0))
            frame.blit(foreground, (0, 0))
            self.add_frame(frame)
        self.location.midtop = ds.get_rect().midtop
        self.location.top += 480
        border = self.border = Surface(self.location.size)
        border.set_colorkey((0, 0, 0))
        border.fill((255, 0, 0))
        border.fill((0, 0, 0), border.get_rect().inflate(-2, -2))
        alpha = 180
        self.set_alpha(alpha)
        border.set_alpha(alpha)

    def reset(self):
        self.activate()

    def activate(self):
        self.active = True

    def deactivate(self):
        self.active = False

    def update(self):
        if self.active:
            Sprite.update(self)
            self.display_surface.blit(self.border, self.location.topleft)


class HighScores(Animation):

    def __init__(self, parent):
        Animation.__init__(self, parent)
        self.display_surface = self.get_display_surface()
        self.register(self.blink, interval=500)
        self.refresh()
        self.play(self.blink)

    def reset(self):
        self.activate()
        self.blink_on = True

    def activate(self):
        self.active = True

    def deactivate(self):
        self.active = False

    def refresh(self, submitted=None):
        scores_path = join(self.get_resource("resource"), "scores")
        if not exists(scores_path):
            scores = [0.4025, 0.265, 0.1875, 0.1325, 0.0825]
            open(scores_path, "w").write("\n".join(map(str, scores)) + "\n")
        else:
            scores = map(float, sorted(open(scores_path).readlines(),
                                       reverse=True)[:5])
        font_path = self.get_resource("display", "high-scores-font")
        font = Font(font_path, 14)
        font.set_italic(True)
        font.set_bold(True)
        foreground_color = (192, 127, 40)
        background_color = (254, 220, 178)
        plates = self.plates = [font.render(" TOP", True, foreground_color,
                                            background_color)]
        self.cancel_blink()
        for ii, score in enumerate(scores):
            size = range(22, 12, -2)[ii]
            if str(score) == submitted:
                self.blink_index = ii
            text = "%.2f" % (score * 100)
            font = Font(font_path, size)
            font.set_italic(True)
            main = font.render(text[:2], True, foreground_color)
            precision = Font(font_path, size - 6).render(text[-3:], True,
                                                    foreground_color)
            background = Surface((main.get_width() + precision.get_width() + 1,
                                  main.get_height()))
            background.fill(background_color)
            background.blit(main, (2, 1))
            rect = precision.get_rect()
            br = background.get_rect()
            rect.bottomright = br.right - 2, br.bottom
            background.blit(precision, rect)
            plates.append(background)

    def cancel_blink(self):
        self.blink_index = None

    def blink(self):
        self.blink_on = not self.blink_on

    def update(self):
        if self.active:
            step = self.display_surface.get_height() / (len(self.plates) + 1)
            for ii, y in enumerate(xrange(step,
                                          self.display_surface.get_height() - \
                                          step, step)):
                plate = self.plates[ii]
                rect = plate.get_rect()
                rect.midleft = 0, y
                if self.blink_on or ii - 1 != self.blink_index:
                    self.display_surface.blit(plate, rect)
            Animation.update(self)


class Types(TypeDeclarations):

    additional_defaults = {"keys": {"list": "act"}}
3.139.239.109
3.139.239.109
3.139.239.109
 
August 12, 2013

I've been researching tartan/plaid recently for decoration in my updated version of Ball & Cup, now called Send. I want to create the atmosphere of a sports event, so I plan on drawing tartan patterns at the vertical edges of the screen as backgrounds for areas where spectator ants generate based on player performance. I figured I would make my own patterns, but after browsing tartans available in the official register, I decided to use existing ones instead.

I made a list of the tartans that had what I thought were interesting titles and chose 30 to base the game's levels on. I sequenced them, using their titles to form a loose narrative related to the concept of sending. Here are three tartans in the sequence (levels 6, 7 and 8) generated by an algorithm I inferred by looking at examples that reads a tartan specification and draws its pattern using a simple dithering technique to blend the color stripes.


Acadia


Eve


Spice Apple

It would be wasting an opportunity if I didn't animate the tartans, so I'm thinking about animations for them. One effect I want to try is making them look like water washing over the area where the ants are spectating. I've also recorded some music for the game. Here are the loops for the game over and high scores screens.

Game Over

High Scores