1062 lines
42 KiB
Python
1062 lines
42 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import sys, random, subprocess
|
|
from random import randint, randrange, choice
|
|
from time import time
|
|
from operator import itemgetter
|
|
|
|
import pygame
|
|
from pygame import Surface, PixelArray, Rect
|
|
from pygame.draw import aalines, polygon
|
|
from pygame.font import Font
|
|
from pygame.mixer import Sound
|
|
from pygame.locals import *
|
|
|
|
from lib.pgfw.pgfw.Game import Game
|
|
from lib.pgfw.pgfw.GameChild import GameChild
|
|
from lib.pgfw.pgfw.Sprite import Sprite, BlinkingSprite
|
|
from lib.pgfw.pgfw.Vector import Vector
|
|
from lib.pgfw.pgfw.extension import render_box
|
|
from lib.pgfw.pgfw.Input import Joystick
|
|
|
|
from .land.Land import Land
|
|
|
|
# Import GPIO library if available
|
|
try:
|
|
import RPi.GPIO as GPIO
|
|
except ImportError:
|
|
pass
|
|
|
|
class ElectricSieve(Game):
|
|
|
|
# The GPIO pins corresponding to the buttons and LED indicators
|
|
PIN_BUTTON_LEFT = 24
|
|
PIN_BUTTON_RIGHT = 23
|
|
PIN_LED_UP = 22
|
|
PIN_LED_DOWN = 23
|
|
|
|
def __init__(self, suppress_gpio_init=False, rotate=False):
|
|
"""
|
|
Initialize super class, GPIO input, background, and activate the title screen.
|
|
"""
|
|
# Initialize super
|
|
Game.__init__(self)
|
|
|
|
# Add type declarations for custom config entries
|
|
self.get_configuration().type_declarations.add_chart({
|
|
"display":
|
|
{
|
|
"bool": "rotate",
|
|
"path": "prompt-image"
|
|
},
|
|
"input":
|
|
{
|
|
"int": ["title-hold", "initials-hold", "initials-idle"]
|
|
},
|
|
"audio":
|
|
{
|
|
"int": "title-fade"
|
|
},
|
|
"land":
|
|
{
|
|
"int": ["gradient", "height", "x-step"],
|
|
"float": ["altitude-ratio", "spacing-factor", "velocity-ratio", "fade-speed"]
|
|
}})
|
|
|
|
# Member dict for tracking pin state changes. Start at 1 because high means unpressed.
|
|
self.pin_states = {
|
|
self.PIN_BUTTON_LEFT: 1,
|
|
self.PIN_BUTTON_RIGHT: 1
|
|
}
|
|
|
|
# Rotate the display if requested
|
|
if rotate:
|
|
self.configuration.set("display", "rotate", True)
|
|
self.rotated = False
|
|
if self.get_configuration("display", "rotate"):
|
|
self.display.rotate()
|
|
self.rotated = True
|
|
|
|
# Initialize GPIO input and callbacks if GPIO library is loaded
|
|
if "RPi.GPIO" in sys.modules and not suppress_gpio_init:
|
|
self.initialize_gpio()
|
|
|
|
self.velocity = Vector(0, 0)
|
|
|
|
# Create an intermediate surface to draw the triangles to and create a trail effect
|
|
self.trail_effect = Surface(self.get_display_surface().get_size(), pygame.SRCALPHA)
|
|
|
|
# Create a black background
|
|
self.background = Surface(self.get_display_surface().get_size())
|
|
self.background.fill((0, 0, 0))
|
|
|
|
# Alpha filter
|
|
self.alpha_filter = Surface(self.get_display_surface().get_size(), pygame.SRCALPHA)
|
|
self.alpha_filter.fill(Color(0, 0, 0, 80))
|
|
|
|
# Create game objects
|
|
self.title = Title(self)
|
|
self.sieve = Sieve(self)
|
|
self.triangles = Triangles(self)
|
|
self.acid = Acid(self)
|
|
self.static = Static(self)
|
|
self.land = Land(self)
|
|
|
|
# Start the title screen
|
|
self.title.activate()
|
|
|
|
def initialize_gpio(self):
|
|
"""
|
|
Set pin numbering mode to GPIO, initialize all buttons to input pullup.
|
|
"""
|
|
# Use GPIO numbering
|
|
GPIO.setmode(GPIO.BCM)
|
|
|
|
# Set button pins to pullup and attach to each a callback that runs on press or release
|
|
for pin in self.PIN_BUTTON_LEFT, self.PIN_BUTTON_RIGHT:
|
|
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
|
GPIO.add_event_detect(pin, GPIO.BOTH, self.gpio_input)
|
|
|
|
def gpio_input(self, pin):
|
|
"""
|
|
Translate GPIO input into PGFW commands, so Raspberry Pi wired controllers be used for input.
|
|
|
|
Compare the pin state to what is stored in memory. Only fire an event if there has been a change in state. A change
|
|
from high to low triggers a press event. A change from low to high triggers a press cancel event.
|
|
|
|
@param pin Raspberry Pi pin number as read by the RPi.GPIO library
|
|
"""
|
|
# Print the input state of each pin if debug is requested on the command line
|
|
if "--debug" in sys.argv:
|
|
pin_name = "left" if pin == ElectricSieve.PIN_BUTTON_LEFT else "right"
|
|
left_pin_state = GPIO.input(ElectricSieve.PIN_BUTTON_LEFT)
|
|
right_pin_state = GPIO.input(ElectricSieve.PIN_BUTTON_RIGHT)
|
|
print(f"Received {pin} ({pin_name}) input. Left state is {left_pin_state}. Right state is {right_pin_state}")
|
|
|
|
# If the saved state of the pin is the same, there hasn't been a real button press or release, so don't continue
|
|
if self.pin_states[pin] != GPIO.input(pin):
|
|
self.pin_states[pin] = GPIO.input(pin)
|
|
# A high signal means the button is released, and a low signal means it is pressed
|
|
cancel = not (GPIO.input(pin) == GPIO.LOW)
|
|
if pin == ElectricSieve.PIN_BUTTON_LEFT:
|
|
self.input.post_command("left", cancel=cancel)
|
|
elif pin == ElectricSieve.PIN_BUTTON_RIGHT:
|
|
self.input.post_command("right", cancel=cancel)
|
|
self.input.post_any_command(id=pin, cancel=cancel)
|
|
|
|
def orient(self, geometry):
|
|
"""
|
|
Orient the passed pgfw.Vector, pygame.Rect, or pygame.Surface so it is rotated if necessary.
|
|
|
|
@param geometry A pgfw.Vector or pygame.Rect to rotate
|
|
@return Either a new pgfw.Vector or pygame.Rect depending on which was passed
|
|
"""
|
|
if not self.rotated:
|
|
return geometry
|
|
else:
|
|
if isinstance(geometry, Vector):
|
|
return self.rotated_point(geometry)
|
|
elif isinstance(geometry, pygame.Rect):
|
|
return self.rotated_rect(geometry)
|
|
elif isinstance(geometry, pygame.Surface):
|
|
return pygame.transform.rotate(geometry, 90)
|
|
|
|
def rotated_point(self, point):
|
|
"""
|
|
Return a new pgfw.Vector with the X and Y values of a pgfw.Vector rotated 90 degrees.
|
|
|
|
@param point pgfw.Vector to rotate
|
|
@return rotated pgfw.Vector
|
|
"""
|
|
return Vector(self.get_display_surface().get_height() - point.x, point.y)
|
|
|
|
def rotated_rect(self, rect):
|
|
"""
|
|
Return a new pygame.Rect rotated 90 degrees.
|
|
|
|
@param rect pygame.Rect to rotate
|
|
@return rotated pygame.Rect
|
|
"""
|
|
rotated = pygame.Rect(0, 0, 0, 0)
|
|
rotated.x = rect.y
|
|
rotated.y = self.get_display_surface().get_height() - rect.x + rect.w
|
|
rotated.w = rect.h
|
|
rotated.h = rect.w
|
|
return rotated
|
|
|
|
def end(self, event):
|
|
"""
|
|
Extend the parent end method to try adding a permanent quit feature in case there is a Raspbian Lite systemd autostart service running
|
|
"""
|
|
if event.type == QUIT or self.delegate.compare(event, "quit"):
|
|
if self.confirming_quit or not self.get_configuration("input", "confirm-quit"):
|
|
# If SHIFT is pressed, try permanently stopping the systemd service to get a console back in case this is running on
|
|
# Raspbian Lite
|
|
if pygame.key.get_mods() & pygame.KMOD_SHIFT:
|
|
try:
|
|
subprocess.run(["sudo", "systemctl", "stop", "ibitfit"])
|
|
print("Killing with permanent stop sent to systemd ibitfit.service")
|
|
except:
|
|
print("No iBitFit system service detected, so permanent quit either failed or was unnecessary")
|
|
super().end(event)
|
|
|
|
def update(self):
|
|
# Test if the level is being played
|
|
if self.triangles.active:
|
|
|
|
# Draw grid effect
|
|
self.land.update()
|
|
|
|
# Draw triangles onto the trail effect surface, update position
|
|
self.triangles.update()
|
|
|
|
if not self.title.active:
|
|
# Draw bottom layer background
|
|
self.get_display_surface().blit(self.background, (0, 0))
|
|
|
|
# Draw static behind objects on title screen
|
|
if self.title.active:
|
|
self.static.update()
|
|
|
|
# Draw the triangles to the screen, using the intermediate trail effect surface
|
|
self.get_display_surface().blit(self.trail_effect, (0, 0))
|
|
|
|
self.title.update()
|
|
|
|
# Draw the sieve
|
|
self.sieve.update()
|
|
|
|
if not self.title.active:
|
|
|
|
# Draw the static
|
|
self.static.update()
|
|
|
|
|
|
class Title(GameChild):
|
|
|
|
def __init__(self, parent):
|
|
GameChild.__init__(self, parent)
|
|
self.display_surface = self.get_display_surface()
|
|
self.delegate = self.parent.delegate
|
|
bg_color = 200, 168, 122
|
|
self.background = surface = Surface(self.display_surface.get_size())
|
|
tile = Surface((2, 2))
|
|
tile.fill(bg_color)
|
|
for y in range(0, surface.get_height(), 2):
|
|
for x in range(0, surface.get_width(), 2):
|
|
surface.blit(self.get_game().orient(tile), (x, y))
|
|
self.scoreboard = Scoreboard(self)
|
|
self.music = Sound(self.get_resource("audio", "title"))
|
|
self.advance = Sound(self.get_resource("audio", "title-advance"))
|
|
self.prompt_surface = pygame.image.load(self.get_configuration("display", "prompt-image")).convert_alpha()
|
|
self.prompt_surface = pygame.transform.smoothscale(self.prompt_surface, (440, 440 * 0.6666))
|
|
self.subscribe(self.respond)
|
|
|
|
def respond(self, event):
|
|
if self.active:
|
|
self.idle_time = 0
|
|
if not self.music.get_num_channels():
|
|
self.music.play(-1, 0, 1000)
|
|
self.get_game().static.noise.fadeout(1000)
|
|
if self.delegate.compare(event, "advance"):
|
|
self.holding_button = True
|
|
|
|
def activate(self):
|
|
self.active = True
|
|
self.holding_button = False
|
|
self.holding_button_elapsed = 0
|
|
self.idle_time = 0
|
|
self.music.play(-1)
|
|
self.get_game().static.activate()
|
|
self.get_game().static.full()
|
|
self.get_game().static.noise.stop()
|
|
self.get_game().sieve.activate()
|
|
self.get_game().triangles.activate(music=False)
|
|
|
|
def deactivate(self):
|
|
self.active = False
|
|
self.music.fadeout(500)
|
|
|
|
def update(self):
|
|
if self.active:
|
|
if self.holding_button_elapsed > self.get_configuration("input", "title-hold"):
|
|
self.deactivate()
|
|
self.parent.triangles.reset()
|
|
while self.parent.triangles:
|
|
self.parent.triangles.pop()
|
|
self.parent.triangles.activate()
|
|
self.parent.sieve.activate()
|
|
self.parent.static.reset()
|
|
self.parent.static.activate()
|
|
self.advance.play()
|
|
self.get_game().trail_effect.fill(Color(0, 0, 0, 0))
|
|
self.get_display_surface().blit(self.get_game().background, (0, 0))
|
|
else:
|
|
if self.idle_time > self.get_configuration("audio", "title-fade"):
|
|
self.music.fadeout(5000)
|
|
if not self.get_game().static.noise.get_num_channels():
|
|
self.get_game().static.noise.set_volume(0.25)
|
|
self.get_game().static.noise.play(-1, 0, 5000)
|
|
else:
|
|
self.idle_time += self.get_game().time_filter.get_last_frame_duration()
|
|
if self.holding_button:
|
|
self.holding_button_elapsed += self.get_game().time_filter.get_last_frame_duration()
|
|
self.scoreboard.update()
|
|
logo = Sprite(self)
|
|
logo.clear_frames()
|
|
elapsed = self.holding_button_elapsed / self.get_configuration("input", "title-hold")
|
|
if elapsed:
|
|
logo.add_frame(self.get_game().orient(pygame.transform.scale(
|
|
self.prompt_surface, (max(0, 440 - int(elapsed * 440)), max(0, 440 * 0.6666 - int(elapsed * 440 * 0.6666))))))
|
|
else:
|
|
logo.add_frame(self.prompt_surface)
|
|
logo.location.center = self.get_display_surface().get_rect().center
|
|
logo.update()
|
|
|
|
|
|
class Strip(Sprite):
|
|
|
|
LEFT, RIGHT = range(2)
|
|
|
|
def __init__(self, parent, interval):
|
|
Sprite.__init__(self, parent, interval)
|
|
self.deactivate()
|
|
self.display_surface = self.get_display_surface()
|
|
self.delegate = self.get_game().delegate
|
|
if not self.get_game().rotated:
|
|
self.hshifts = Shift(self, 1, "shift-2"), Shift(self, -1, "shift-2")
|
|
else:
|
|
self.hshifts = Shift(self, -1, "shift-2"), Shift(self, 1, "shift-2")
|
|
self.add_frames()
|
|
|
|
def deactivate(self):
|
|
self.active = False
|
|
|
|
def reset(self):
|
|
for shift in self.hshifts:
|
|
shift.reset()
|
|
|
|
def add_frames(self):
|
|
pass
|
|
|
|
def activate(self):
|
|
self.active = True
|
|
|
|
def update(self):
|
|
if self.active:
|
|
self.hshifts[self.LEFT].active = self.get_game().input.is_command_active("left")
|
|
self.hshifts[self.RIGHT].active = self.get_game().input.is_command_active("right")
|
|
for shift in self.hshifts:
|
|
shift.update()
|
|
if shift.time:
|
|
if not self.get_game().rotated:
|
|
self.move(shift.get_change())
|
|
else:
|
|
self.move(dy=shift.get_change())
|
|
Sprite.update(self)
|
|
|
|
|
|
class Shift(GameChild):
|
|
|
|
def __init__(self, parent, direction, nodeset):
|
|
GameChild.__init__(self, parent)
|
|
self.direction = direction
|
|
self.reset()
|
|
self.timer = self.get_game().time_filter
|
|
self.nodeset = self.get_game().interpolator.get_nodeset(nodeset)
|
|
|
|
def reset(self):
|
|
self.active = False
|
|
self.time = 0
|
|
|
|
def update(self):
|
|
least, greatest = self.nodeset[0].x, self.nodeset[-1].x
|
|
if self.active and self.time < greatest:
|
|
self.time = min(self.time + self.timer.get_last_frame_duration(), greatest)
|
|
elif not self.active and self.time > least:
|
|
self.time = max(self.time - self.timer.get_last_frame_duration(), least)
|
|
|
|
def get_change(self):
|
|
return self.nodeset.get_y(self.time) * self.direction
|
|
|
|
|
|
class Scoreboard(GameChild):
|
|
|
|
BACKGROUND = 255, 255, 255
|
|
FOREGROUND = 27, 27, 27
|
|
NEW = 27, 27, 27
|
|
SPACING = 45
|
|
MARGIN = 0
|
|
BLINK_INTERVAL = 400
|
|
PADDING = 0
|
|
BORDER = 1
|
|
SCORE_COUNT = 11
|
|
SIZES = [32, 28, 24, 22, 22, 20, 20, 20, 18, 18, 18]
|
|
|
|
def __init__(self, parent):
|
|
GameChild.__init__(self, parent)
|
|
ds = self.display_surface = self.get_display_surface()
|
|
self.scores_path = self.get_resource("score", "path")
|
|
self.most_recent_score = None
|
|
self.set_scores()
|
|
self.load()
|
|
|
|
def set_scores(self):
|
|
self.scores = []
|
|
try:
|
|
with open(self.scores_path, "r") as fp:
|
|
for line in fp:
|
|
fields = line.split()
|
|
self.scores.append((float(fields[0]), int(fields[1]), fields[2]))
|
|
fp.close()
|
|
except:
|
|
print("Warning: error while reading scores file. Ignoring for now.")
|
|
self.scores = sorted(self.scores, key=itemgetter(0))
|
|
self.scores = sorted(self.scores, key=itemgetter(1), reverse=True)
|
|
|
|
def load(self):
|
|
self.sprites = sprites = []
|
|
font_path = self.get_resource("display", "scoreboard-font-path")
|
|
blink = False
|
|
for ii, score in enumerate(self.get_scores()[:len(self.SIZES)]):
|
|
font = Font(font_path, self.SIZES[ii])
|
|
sprites.append((Sprite(self, self.BLINK_INTERVAL), Sprite(self, self.BLINK_INTERVAL)))
|
|
score_text = str(score[1])
|
|
color = self.BACKGROUND if (self.most_recent_score and not blink and score[1:] == self.most_recent_score) else self.FOREGROUND
|
|
score_plate = font.render(score_text, False, color, self.BACKGROUND)
|
|
rect = score_plate.get_rect()
|
|
surface = Surface(rect.inflate((2, 2)).size)
|
|
surface.fill(self.FOREGROUND)
|
|
rect.center = surface.get_rect().center
|
|
surface.blit(score_plate, rect)
|
|
width = 80
|
|
sprites[ii][1].add_frame(self.get_game().orient(
|
|
render_box(font, score_text, True, color, self.BACKGROUND, (0, 0, 0), padding=self.PADDING, width=width)))
|
|
sprites[ii][0].add_frame(self.get_game().orient(
|
|
render_box(font, score[2], True, color, self.BACKGROUND, (0, 0, 0), padding=self.PADDING, width=width)))
|
|
if self.most_recent_score and not blink and score[1:] == self.most_recent_score:
|
|
sprites[ii][1].add_frame(self.get_game().orient(
|
|
render_box(font, score_text, True, self.NEW, self.BACKGROUND, (0, 0, 0), padding=self.PADDING, width=width)))
|
|
sprites[ii][0].add_frame(self.get_game().orient(
|
|
render_box(font, score[2], True, self.NEW, self.BACKGROUND, (0, 0, 0), padding=self.PADDING, width=width)))
|
|
blink = True
|
|
if not self.get_game().rotated:
|
|
sprites[ii][0].location.left = self.MARGIN
|
|
sprites[ii][1].location.right = self.get_display_surface().get_rect().right - self.MARGIN
|
|
y = self.get_display_surface().get_rect().centery + self.SPACING * (ii - len(self.SIZES) / 2)
|
|
if (ii < 5):
|
|
y -= 75
|
|
else:
|
|
y += 30
|
|
for sprite in sprites[ii]:
|
|
sprite.location.centery = y
|
|
else:
|
|
sprites[ii][0].location.bottom = self.get_display_surface().get_height() - self.MARGIN
|
|
sprites[ii][1].location.top = self.MARGIN
|
|
x = self.get_display_surface().get_rect().centerx + self.SPACING * (ii - len(self.SIZES) / 2)
|
|
if (ii < 5):
|
|
x -= 75
|
|
else:
|
|
x += 30
|
|
for sprite in sprites[ii]:
|
|
sprite.location.centerx = x
|
|
|
|
def get_scores(self):
|
|
return self.scores
|
|
|
|
def write(self, initials):
|
|
score = int(round(self.get_game().triangles.score))
|
|
fields = str(time()), str(score), initials
|
|
with open(self.scores_path, "a") as fp:
|
|
fp.write(fields[0] + " " + fields[1] + " " + fields[2] + "\n")
|
|
fp.close()
|
|
self.most_recent_score = score, initials
|
|
self.scores.append((float(fields[0]), int(fields[1]), fields[2]))
|
|
self.scores = sorted(self.scores, key=itemgetter(0))
|
|
self.scores = sorted(self.scores, key=itemgetter(1), reverse=True)
|
|
self.load()
|
|
|
|
def update(self):
|
|
for pair in self.sprites:
|
|
for sprite in pair:
|
|
sprite.update()
|
|
|
|
|
|
class Sieve(Strip):
|
|
|
|
UP, DOWN = range(2)
|
|
|
|
def __init__(self, parent):
|
|
Strip.__init__(self, parent, 400)
|
|
self.delegate = self.get_game().delegate
|
|
self.electric = Electric(self)
|
|
if not self.get_game().rotated:
|
|
self.location.left = 0
|
|
self.add_location(offset=(self.location.w, 0))
|
|
else:
|
|
self.location.bottom = self.get_display_surface().get_height()
|
|
self.add_location(offset=(0, -self.location.h))
|
|
|
|
def add_frames(self):
|
|
bar_locations = []
|
|
self.bar_rects = bar_rects = []
|
|
x = 0
|
|
sh = 30
|
|
nodeset = self.get_game().interpolator.get_nodeset("scale")
|
|
self.bar_w = bar_w = 3
|
|
self.gaps = gaps = []
|
|
while x < nodeset[-1].x:
|
|
bar_locations.append(x)
|
|
bar_rects.append(Rect(x, 0, bar_w, sh))
|
|
gaps.append(nodeset.get_y(x, natural=True))
|
|
x += gaps[-1]
|
|
surface = Surface((x, sh))
|
|
transparent_color = (255, 0, 255)
|
|
surface.fill(transparent_color)
|
|
surface.set_colorkey(transparent_color)
|
|
frames = surface, surface.copy()
|
|
# colors = (0, 255, 0), (153, 0, 204)
|
|
colors = (255, 255, 255), (255, 255, 255)
|
|
for x in bar_locations:
|
|
bar_rects.append(Rect(x + surface.get_width(), 0, bar_w, sh))
|
|
for ii, frame in enumerate(frames):
|
|
frame.fill(colors[ii], (x, 0, bar_w, sh))
|
|
frame.fill(colors[ii - 1], (x + 1, 1, 1, sh - 2))
|
|
if self.get_game().rotated:
|
|
for ii, rect in enumerate(bar_rects):
|
|
bar_rects[ii] = self.get_game().orient(rect)
|
|
bar_rects[ii].move_ip(0, -6)
|
|
for frame in frames:
|
|
self.add_frame(self.get_game().orient(frame))
|
|
|
|
def reset(self):
|
|
Strip.reset(self)
|
|
if not self.get_game().rotated:
|
|
self.location.centerx = self.display_surface.get_rect().centerx
|
|
self.locations[1].centerx = self.location.centerx + self.location.w
|
|
else:
|
|
self.location.centery = self.display_surface.get_rect().centery
|
|
self.locations[1].centery = self.location.centery - self.location.h
|
|
|
|
def update(self):
|
|
if self.active:
|
|
if not self.get_game().rotated:
|
|
if self.location.right < 0:
|
|
self.move(self.location.w)
|
|
if self.locations[1].left > self.display_surface.get_width():
|
|
self.move(-self.location.w)
|
|
for location in self.locations:
|
|
location.bottom = self.parent.acid.get_top()
|
|
self.electric.location.centery = self.location.centery + 13
|
|
else:
|
|
if self.location.top > self.display_surface.get_height():
|
|
self.move(dy=-self.location.h)
|
|
if self.locations[1].bottom < 0:
|
|
self.move(dy=self.location.h)
|
|
for location in self.locations:
|
|
location.right = self.parent.acid.get_top()
|
|
self.electric.location.centerx = self.location.centerx + 13
|
|
self.electric.update()
|
|
for rect in self.bar_rects:
|
|
if not self.get_game().rotated:
|
|
rect.centery = self.location.centery
|
|
else:
|
|
rect.centerx = self.location.centerx
|
|
Strip.update(self)
|
|
|
|
|
|
class Electric(Sprite):
|
|
|
|
def __init__(self, parent):
|
|
Sprite.__init__(self, parent)
|
|
self.display_surface = self.get_display_surface()
|
|
self.add_frames()
|
|
|
|
def add_frames(self):
|
|
if not self.get_game().rotated:
|
|
surface = Surface((self.display_surface.get_width(), self.parent.location.h - 10))
|
|
else:
|
|
surface = Surface((self.display_surface.get_height(), self.parent.location.w - 10))
|
|
frames = surface, surface.copy()
|
|
# colors = (255, 255, 0), (100, 89, 213)
|
|
# colors = (180, 152, 111), (180, 152, 111)
|
|
colors = (255, 255, 255), (255, 255, 255)
|
|
pixel_arrays = PixelArray(frames[0]), PixelArray(frames[1])
|
|
for x in range(len(pixel_arrays[0])):
|
|
for y in range( len(pixel_arrays[0][0])):
|
|
pixel_arrays[0][x][y] = colors[(y + x) // 5 % 2]
|
|
pixel_arrays[1][x][y] = colors[(y + x + 1) // 5 % 2]
|
|
for pixels in pixel_arrays:
|
|
del pixels
|
|
for frame in frames:
|
|
self.add_frame(self.get_game().orient(frame))
|
|
|
|
|
|
class Triangles(GameChild, list):
|
|
|
|
def __init__(self, parent):
|
|
GameChild.__init__(self, parent)
|
|
self.hue = 0
|
|
self.music = Sound(self.get_resource("audio", "triangles"))
|
|
self.deactivate()
|
|
self.display_surface = self.get_game().trail_effect
|
|
self.delegate = self.get_game().delegate
|
|
self.booster = Shift(self, 1, "boost")
|
|
self.hit = Sound(self.get_resource("audio", "hit"))
|
|
self.miss = Sound(self.get_resource("audio", "miss"))
|
|
self.reset()
|
|
self.subscribe(self.respond)
|
|
|
|
def deactivate(self):
|
|
self.active = False
|
|
self.music.fadeout(500)
|
|
|
|
def reset(self):
|
|
list.__init__(self, [])
|
|
self.streak = 0
|
|
self.score = 0
|
|
self.booster.reset()
|
|
|
|
def populate(self):
|
|
if not self:
|
|
self.append(Triangle(self))
|
|
if not self.get_game().rotated:
|
|
self[-1].location.bottom = 0
|
|
else:
|
|
self[-1].location.right = 0
|
|
self.set_next_gap()
|
|
if not self.get_game().rotated:
|
|
while self[-1].location.top > -self.display_surface.get_height():
|
|
self.append(Triangle(self))
|
|
self[-1].location.bottom = self[-2].location.top - self.next_gap
|
|
self.set_next_gap()
|
|
else:
|
|
while self[-1].location.left > -self.display_surface.get_width():
|
|
self.append(Triangle(self))
|
|
self[-1].location.right = self[-2].location.left - self.next_gap
|
|
self.set_next_gap()
|
|
|
|
def set_next_gap(self):
|
|
self.next_gap = randint(500, 800)
|
|
|
|
def respond(self, event):
|
|
if self.active:
|
|
compare = self.delegate.compare
|
|
if compare(event, "down") or compare(event, "down", True):
|
|
self.booster.active = not event.cancel
|
|
|
|
def get_boost(self):
|
|
return self.booster.get_change()
|
|
|
|
def activate(self, music=True):
|
|
self.active = True
|
|
if music:
|
|
self.music.play(-1, 0, 500)
|
|
|
|
def update(self):
|
|
if self.active:
|
|
self.populate()
|
|
self.booster.update()
|
|
if self[0].location.collidelist(self.parent.sieve.locations) != -1:
|
|
sieve = self.parent.sieve
|
|
removed = False
|
|
if self[0].location.colliderect(sieve.electric.location):
|
|
if not self.get_game().title.active:
|
|
self.parent.acid.increase()
|
|
self.streak += 1
|
|
self.score += self.streak ** .8 + self.parent.acid.get_volume() * 5 + self[0].count
|
|
self.remove(self[0])
|
|
self.hit.play()
|
|
removed = True
|
|
else:
|
|
for br in sieve.bar_rects:
|
|
for tr in self[0].collision_rects:
|
|
tr_offset = (self[0].location.left, 0) if not self.get_game().rotated else \
|
|
(0, self[0].location.bottom - self.get_display_surface().get_height())
|
|
br_offset = (sieve.location.left, 0) if not self.get_game().rotated else \
|
|
(0, sieve.location.bottom - self.get_display_surface().get_height())
|
|
if tr.move(tr_offset).colliderect(br.move(br_offset)):
|
|
if not self.get_game().title.active:
|
|
self.parent.static.increase()
|
|
self.streak = 0
|
|
self.remove(self[0])
|
|
self.miss.play()
|
|
removed = True
|
|
break
|
|
if removed:
|
|
self.get_display_surface().blit(self.get_game().alpha_filter, (0, 0), None, pygame.BLEND_RGBA_SUB)
|
|
for triangle in self:
|
|
triangle.update()
|
|
|
|
|
|
class Triangle(Sprite):
|
|
|
|
def __init__(self, parent):
|
|
Sprite.__init__(self, parent, 100)
|
|
mark = randint(112, 328)
|
|
sieve = self.parent.parent.sieve
|
|
gaps = sieve.gaps
|
|
start = randrange(0, len(gaps))
|
|
widths = [gaps[start]]
|
|
while sum(widths) < mark:
|
|
widths.append(gaps[(start + len(widths)) % len(gaps)])
|
|
surface = Surface((sum(widths), 20))
|
|
surface.set_colorkey((0, 0, 0))
|
|
height = surface.get_height()
|
|
margin = 26
|
|
self.collision_rects = collision_rects = []
|
|
for ii, lightness in enumerate(range(30, 110, 10)):
|
|
color = pygame.Color(0, 0, 0)
|
|
color.hsla = parent.hue, 100, lightness, 100
|
|
# opposite_color = pygame.Color(0, 0, 0)
|
|
# opposite_color.hsla = (parent.hue + 180) % 360, 100, lightness, 100
|
|
x = 0
|
|
surface = surface.copy()
|
|
for width in widths:
|
|
x += sieve.bar_w
|
|
points = ((x + margin // 2, height - 2),
|
|
(x + width - margin // 2 - 1, height - 2),
|
|
(x + width / 2.0, 1))
|
|
polygon(surface, color, points)
|
|
if ii == 0:
|
|
if not self.get_game().rotated:
|
|
collision_rects.append(Rect(points[0], (width - margin - 1, 1)))
|
|
else:
|
|
collision_rects.append(Rect(height - 2 - 1, self.get_display_surface().get_height() - x - width + margin // 2 + 1, 1, width - margin - 1))
|
|
# points = ((x + margin // 2 + (width * .1), height - 2 - 2),
|
|
# (x + width - margin // 2 - 1 - (width * .1), height - 2 - 2),
|
|
# (x + width / 2.0, 1 + 5))
|
|
# polygon(surface, opposite_color, points)
|
|
x += width - sieve.bar_w
|
|
self.add_frame(self.get_game().orient(surface))
|
|
next_hue = parent.hue
|
|
while abs(next_hue - parent.hue) < 60:
|
|
next_hue = random.randint(0, 359)
|
|
parent.hue = next_hue
|
|
if not self.get_game().rotated:
|
|
self.location.centerx = self.get_display_surface().get_rect().centerx
|
|
else:
|
|
self.location.centery = self.get_display_surface().get_rect().centery
|
|
self.count = len(widths)
|
|
|
|
def update(self):
|
|
step = 9.5 * self.get_game().acid.get_volume() + 3.8 + self.parent.get_boost()
|
|
if not self.get_game().rotated:
|
|
self.move(dy=step)
|
|
else:
|
|
self.move(dx=step)
|
|
for rect in self.collision_rects:
|
|
if not self.get_game().rotated:
|
|
rect.bottom = self.location.bottom
|
|
else:
|
|
rect.right = self.location.right
|
|
Sprite.update(self)
|
|
|
|
|
|
class Acid(GameChild):
|
|
|
|
def __init__(self, parent):
|
|
GameChild.__init__(self, parent)
|
|
self.display_surface = self.get_display_surface()
|
|
self.level_r = 80, 320
|
|
self.nodeset = self.get_game().interpolator.get_nodeset("volume")
|
|
self.reset()
|
|
|
|
def reset(self):
|
|
self.substance = 0
|
|
|
|
def get_top(self):
|
|
if not self.get_game().rotated:
|
|
return self.display_surface.get_height() - self.get_level()
|
|
else:
|
|
return self.display_surface.get_width() - self.get_level()
|
|
|
|
def get_level(self):
|
|
return self.get_volume() * (self.level_r[1] - self.level_r[0]) + self.level_r[0]
|
|
|
|
def get_volume(self):
|
|
return self.nodeset.get_y(self.substance)
|
|
|
|
def increase(self):
|
|
self.substance += 1
|
|
|
|
|
|
class Static(Sprite):
|
|
|
|
def __init__(self, parent):
|
|
Sprite.__init__(self, parent, 120)
|
|
self.advance_automatically = 0
|
|
self.noise = Sound(self.get_resource("audio", "noise"))
|
|
self.end = Sound(self.get_resource("audio", "end"))
|
|
self.deactivate()
|
|
self.delegate = self.get_game().delegate
|
|
self.increaser = Shift(self, 1, "intensity")
|
|
self.total = Total(self)
|
|
self.initials = Initials(self)
|
|
self.reset()
|
|
self.add_frames()
|
|
|
|
def deactivate(self):
|
|
self.active = False
|
|
self.end.fadeout(500)
|
|
|
|
def reset(self):
|
|
self.advance_automatically = 0
|
|
self.complete = False
|
|
self.intensity = 0
|
|
self.noise.set_volume(0)
|
|
self.increaser.reset()
|
|
|
|
def add_frames(self):
|
|
surface = Surface(self.get_display_surface().get_size())
|
|
frames = surface, surface.copy(), surface.copy(), surface.copy()
|
|
tiles = []
|
|
for _ in range(32):
|
|
tiles.append(Surface((16, 16)))
|
|
pixel_arrays = []
|
|
for tile in tiles:
|
|
pixel_arrays.append(PixelArray(tile))
|
|
colors = (0, 0, 0), (64, 64, 64), (128, 128, 128), (196, 196, 196), (255, 255, 255)
|
|
for x in range(len(pixel_arrays[0])):
|
|
for y in range(len(pixel_arrays[0][0])):
|
|
for pixels in pixel_arrays:
|
|
pixels[x][y] = choice(colors)
|
|
for pixels in pixel_arrays:
|
|
del pixels
|
|
del pixel_arrays
|
|
for frame in frames:
|
|
for y in range(0, frame.get_height(), tiles[0].get_height()):
|
|
for x in range(0, frame.get_width(), tiles[0].get_width()):
|
|
frame.blit(choice(tiles), (x, y))
|
|
self.add_frame(frame)
|
|
|
|
def finish(self, text="---", wipe=False):
|
|
if wipe:
|
|
self.parent.title.scoreboard.most_recent_score = None
|
|
self.parent.title.scoreboard.write(text)
|
|
self.total.deactivate()
|
|
self.deactivate()
|
|
self.reset()
|
|
self.parent.acid.reset()
|
|
self.parent.triangles.reset()
|
|
self.parent.sieve.reset()
|
|
self.parent.title.activate()
|
|
|
|
def increase(self):
|
|
self.intensity += self.increaser.get_change()
|
|
if self.intensity > 1:
|
|
self.intensity = 1
|
|
self.increaser.time += 12000
|
|
if self.increaser.time >= self.increaser.nodeset[-1].x + 5000:
|
|
self.increaser.time = self.increaser.nodeset[-1].x + 5000
|
|
|
|
def full(self):
|
|
self.intensity = 1
|
|
|
|
def activate(self):
|
|
self.active = True
|
|
self.noise.play(-1)
|
|
|
|
def update(self):
|
|
if self.active:
|
|
if not self.get_game().title.active:
|
|
if not self.complete and self.intensity >= .65:
|
|
self.complete = True
|
|
self.parent.sieve.deactivate()
|
|
self.parent.triangles.deactivate()
|
|
self.set_alpha(255)
|
|
self.noise.fadeout(6000)
|
|
self.end.play(-1, 0, 4000)
|
|
self.total.load()
|
|
elif not self.complete:
|
|
self.set_alpha(min(150, int(self.intensity * 1.15 * 255)))
|
|
if self.intensity > 0:
|
|
self.intensity *= .998
|
|
self.increaser.update()
|
|
self.noise.set_volume(self.intensity)
|
|
if self.total.active:
|
|
if self.advance_automatically > 3000:
|
|
self.advance_automatically = 0
|
|
if self.get_game().triangles.score > self.get_game().title.scoreboard.get_scores()[Scoreboard.SCORE_COUNT - 1][1]:
|
|
self.total.deactivate()
|
|
self.initials.activate()
|
|
self.get_game().suppress_input_temporarily()
|
|
else:
|
|
self.finish(wipe=True)
|
|
else:
|
|
self.advance_automatically += self.get_game().time_filter.get_last_frame_duration()
|
|
if self.intensity > .1:
|
|
Sprite.update(self)
|
|
self.total.update()
|
|
self.initials.update()
|
|
|
|
|
|
class Initials(GameChild):
|
|
|
|
LETTER_SIZE = 24
|
|
FOREGROUND = 27, 27, 27
|
|
BACKGROUND = 255, 255, 255
|
|
PADDING = 10
|
|
ARROW_MARGIN = 20
|
|
ARROW_HEIGHT = 10
|
|
|
|
def __init__(self, parent):
|
|
GameChild.__init__(self, parent)
|
|
self.left_last_pressed = 0
|
|
self.button_prompt = BlinkingSprite(self, 500)
|
|
font = pygame.font.Font(self.get_resource("terminus/Terminus.ttf"), 32)
|
|
self.button_prompt.add_frame(self.get_game().orient(font.render("HOLD RIGHT TO ENTER", True, pygame.Color(0, 0, 0), pygame.Color(255, 255, 255))))
|
|
if not self.get_game().rotated:
|
|
self.button_prompt.location.midbottom = self.get_display_surface().get_rect().midbottom
|
|
else:
|
|
self.button_prompt.location.midright = self.get_display_surface().get_rect().midright
|
|
self.reset()
|
|
self.deactivate()
|
|
self.font = Font(self.get_resource("display", "initials-font"), self.LETTER_SIZE)
|
|
self.subscribe(self.respond)
|
|
|
|
def reset(self):
|
|
self.idle_time = 0
|
|
self.text = "---"
|
|
self.index = 0
|
|
self.holding_button = False
|
|
self.holding_button_elapsed = 0
|
|
|
|
def deactivate(self):
|
|
self.active = False
|
|
|
|
def respond(self, event):
|
|
if self.active:
|
|
self.idle_time = 0
|
|
compare = self.get_game().delegate.compare
|
|
if compare(event, "left", cancel=False):
|
|
self.left_last_pressed = pygame.time.get_ticks()
|
|
if compare(event, "right", cancel=False):
|
|
self.holding_button = True
|
|
elif compare(event, "right", cancel=True):
|
|
self.holding_button = False
|
|
self.holding_button_elapsed = 0
|
|
if (compare(event, "right", cancel=True) and pygame.time.get_ticks() - self.left_last_pressed > 200) or compare(event, "left", cancel=True):
|
|
if compare(event, "left", cancel=True):
|
|
increment = -1
|
|
elif compare(event, "right", cancel=True):
|
|
increment = 1
|
|
letter = self.text[self.index]
|
|
if letter == '-':
|
|
letter = 'A' if increment == 1 else 'Z'
|
|
else:
|
|
letter = chr(ord(letter) + increment)
|
|
if ord(letter) == 91 or ord(letter) == 64:
|
|
letter = '-'
|
|
replacement = ""
|
|
for ii in range(len(self.text)):
|
|
if ii == self.index:
|
|
replacement += letter
|
|
else:
|
|
replacement += self.text[ii]
|
|
self.text = replacement
|
|
|
|
def activate(self):
|
|
self.active = True
|
|
self.idle_time = 0
|
|
|
|
def submit(self):
|
|
self.deactivate()
|
|
self.parent.finish(self.text)
|
|
self.reset()
|
|
|
|
def update(self):
|
|
if self.active:
|
|
if self.idle_time > self.get_configuration("input", "initials-idle"):
|
|
self.submit()
|
|
else:
|
|
self.idle_time += self.get_game().time_filter.get_last_frame_duration()
|
|
ds = self.get_display_surface()
|
|
self.button_prompt.update()
|
|
if self.holding_button:
|
|
self.holding_button_elapsed += self.get_game().time_filter.get_last_frame_duration()
|
|
if self.holding_button_elapsed > self.get_configuration("input", "initials-hold"):
|
|
self.index += 1
|
|
if self.index == len(self.text):
|
|
self.submit()
|
|
else:
|
|
self.holding_button = False
|
|
self.holding_button_elapsed = 0
|
|
self.get_game().suppress_input_temporarily()
|
|
for ii, letter in enumerate(self.text):
|
|
box = self.get_game().orient(render_box(
|
|
self.font, letter, False, self.FOREGROUND, self.BACKGROUND, self.FOREGROUND, padding=self.PADDING))
|
|
rect = box.get_rect()
|
|
if not self.get_game().rotated:
|
|
rect.centery = ds.get_rect().centery
|
|
rect.centerx = ii * ds.get_width() / 3 + ds.get_width() / 6
|
|
else:
|
|
rect.centerx = ds.get_rect().centerx
|
|
rect.centery = (len(self.text) - 1 - ii) * ds.get_height() / 3 + ds.get_height() / 6
|
|
ds.blit(box, rect)
|
|
if ii == self.index:
|
|
hold_offset = self.holding_button_elapsed / self.get_configuration("input", "initials-hold") * 10
|
|
if not self.get_game().rotated:
|
|
x = rect.left - self.ARROW_MARGIN
|
|
left_points = ((x, rect.top), (x, rect.bottom), (x - self.ARROW_HEIGHT, rect.centery))
|
|
x = rect.right + self.ARROW_MARGIN + hold_offset
|
|
right_points = ((x, rect.top), (x, rect.bottom), (x + self.ARROW_HEIGHT, rect.centery))
|
|
else:
|
|
y = rect.top - self.ARROW_MARGIN - hold_offset
|
|
left_points = ((rect.left, y), (rect.right, y), (rect.centerx, y - self.ARROW_HEIGHT))
|
|
y = rect.bottom + self.ARROW_MARGIN
|
|
right_points = ((rect.left, y), (rect.right, y), (rect.centerx, y + self.ARROW_HEIGHT))
|
|
pygame.draw.polygon(ds, pygame.Color(0, 0, 0), left_points)
|
|
pygame.draw.polygon(ds, pygame.Color(0, 0, 0), right_points)
|
|
|
|
|
|
class Total(Sprite):
|
|
|
|
def __init__(self, parent):
|
|
Sprite.__init__(self, parent, 68)
|
|
self.deactivate()
|
|
self.font = Font(self.get_resource("display", "score-font-path"), 72)
|
|
# self.font.set_italic(True)
|
|
|
|
def deactivate(self):
|
|
self.active = False
|
|
|
|
def load(self):
|
|
self.clear_frames()
|
|
score = ""
|
|
for ch in str(int(round(self.get_game().triangles.score))):
|
|
score += ch + " "
|
|
colors = (255, 255, 180), (180, 255, 255), (255, 180, 255), \
|
|
(255, 220, 160), (160, 255, 220), (220, 160, 255)
|
|
template = Surface((self.display_surface.get_width(), 100))
|
|
transparent_color = (255, 0, 255)
|
|
template.fill(transparent_color)
|
|
template.set_colorkey(transparent_color)
|
|
tr = template.get_rect()
|
|
template.fill((255, 0, 0), (0, 20, tr.w, 1))
|
|
template.fill((255, 128, 128), (0, 21, tr.w, 1))
|
|
for y in range(22, 78, 2):
|
|
template.fill((255, 255, 255), (0, y, tr.w, 1))
|
|
template.fill((255, 128, 128), (0, 78, tr.w, 1))
|
|
template.fill((255, 0, 0), (0, 79, tr.w, 1))
|
|
for _ in range(20):
|
|
# surface = template.copy()
|
|
surface = Surface(template.get_size(), SRCALPHA)
|
|
# polygon(surface, choice(colors), ((tr.centerx - 7, 19),
|
|
# (tr.centerx, 0),
|
|
# (tr.centerx + 7, 19)))
|
|
text = self.font.render(score, True, choice(colors))
|
|
rect = text.get_rect()
|
|
rect.center = tr.centerx, tr.centery + 2
|
|
surface.blit(text, rect)
|
|
# polygon(surface, choice(colors), ((tr.centerx - 7, 80),
|
|
# (tr.centerx, tr.h - 1),
|
|
# (tr.centerx + 7, 80)))
|
|
self.add_frame(self.get_game().orient(surface))
|
|
self.location.center = self.display_surface.get_rect().center
|
|
self.active = True
|
|
|
|
def update(self):
|
|
if self.active:
|
|
Sprite.update(self)
|