# 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"}}