#!/usr/bin/python
# Rocket Land Launch
# "A gracious spring, turned to blood-ravenous autumn" - Rihaku,
# Lament of the Frontier Guard
from os import environ
from os.path import join
from random import randint, randrange, choice, random, uniform, shuffle
from math import tan, radians, ceil
from pygame import init, Surface, transform, PixelArray
from pygame.time import get_ticks, wait
from pygame.event import get
from pygame.display import set_mode, flip, set_caption
from pygame.mouse import set_visible
from pygame.image import load
from pygame.draw import polygon, aaline, circle
from pygame.locals import *
class Between:
resolution = (640, 480)
target_frame_duration = 40
def __init__(self):
self.quit_queued = False
self.duration = 0
init()
set_visible(False)
set_caption("Divine Remains Holds Domain")
set_caption("Desert of Utility")
set_caption("Involution")
self.set_screen()
self.characters = Characters(self)
self.title = Title(self)
self.field = Field(self)
self.title.activate()
self.last_ticks = get_ticks()
self.reset()
def reset(self):
self.characters.reset()
self.title.reset()
self.field.reset()
def run(self):
while True:
self.maintain_framerate()
self.dispatch_events()
if self.quit_queued:
break
self.title.update()
self.field.update()
flip()
def maintain_framerate(self):
while self.duration < self.target_frame_duration:
wait(2)
ticks = get_ticks()
self.duration += ticks - self.last_ticks
self.last_ticks = ticks
self.duration -= self.target_frame_duration
def dispatch_events(self):
for event in get():
if event.type == KEYDOWN:
key = event.key
if key == K_F11:
self.set_screen(True)
elif key == K_F8:
self.reset()
elif key == K_ESCAPE:
self.quit()
elif self.title.active and key in (K_UP, K_DOWN):
self.title.change_character(key == K_UP)
elif self.title.active and key == K_RETURN:
self.title.deactivate()
self.field.activate()
self.field.start_level()
elif event.type == QUIT:
self.quit()
def set_screen(self, toggle_fullscreen=False):
flags = 0
if toggle_fullscreen:
flags = self.screen.get_flags() ^ FULLSCREEN
self.screen = set_mode(self.resolution, flags)
def quit(self):
self.quit_queued = True
class Child:
def __init__(self, parent):
self.parent = parent
self.set_root()
self.set_screen()
def set_root(self):
node = self.parent
while not isinstance(node, Between):
node = node.parent
self.root = node
def set_screen(self):
self.screen = self.root.screen
class Characters(Child, list):
folder = join("resource", "img", "character")
paths = "h-Hh", "6oF", "Bag"
def __init__(self, parent):
Child.__init__(self, parent)
list.__init__(self, (Character(self, join(self.folder, path)) for \
path in self.paths))
def reset(self):
self.current_index = 1
self.parent.field.jumper.set_surface()
def shift_index(self, decrease=False):
step = -1 if decrease else 1
self.current_index += step
if self.current_index == len(self):
self.current_index = 0
elif self.current_index < 0:
self.current_index = len(self) - 1
self.parent.field.jumper.set_surface()
def get_selected_character(self):
return self[self.current_index]
class Character(Child):
def __init__(self, parent, path):
Child.__init__(self, parent)
self.mono_surface = load(join(path, "mono.png")).convert_alpha()
self.large_surface = load(join(path, "large.png")).convert_alpha()
self.mini_surface = load(join(path, "mini.png")).convert_alpha()
def is_selected_character(self):
return self == self.parent.get_selected_character()
class Animation(Child):
def __init__(self, parent, interval):
Child.__init__(self, parent)
self.interval = interval
self.playing = False
def play(self):
self.playing = True
self.last_ticks = get_ticks()
self.frame_duration = 0
def stop(self):
self.playing = False
def update(self):
if self.playing:
self.frame_duration += get_ticks() - self.last_ticks
if self.frame_duration >= self.interval:
self.frame_duration -= self.interval
self.advance_frame()
self.last_ticks = get_ticks()
class Title(Animation):
color_components = (0, 80, 70), (0, 60, 60), (0, 90, 80)
interval_range = 0, 120
interval_change_rate = .005
indicator_colors = (Color(*components) for components in \
((255, 255, 0), (255, 0, 255), (0, 255, 255),
(255, 192, 87)))
def __init__(self, parent):
Animation.__init__(self, parent, self.interval_range[0])
self.background_index = 0
self.set_backgrounds()
rects = self.character_rects = []
characters = self.parent.characters
for ii, character in enumerate(characters):
rect = character.large_surface.get_rect()
rect.center = self.screen.get_width() / 2, \
int(float(ii + 1) / (len(characters) + 1) * \
self.screen.get_height())
rects.append(rect)
indicator_surfaces = self.indicator_surfaces = []
rect = self.indicator_rect = Rect(self.screen.get_width() / 3, 0, 22,
23)
for color in self.indicator_colors:
surface = Surface(rect.size)
surface.set_colorkey((0, 0, 0))
polygon(surface, color, ((0, 0), (rect.w - 1, rect.h / 2 - 1),
(0, rect.h - 1)))
indicator_surfaces.append(surface)
self.indicator_surfaces_index = 0
def set_backgrounds(self):
backgrounds = self.backgrounds = []
tiles = []
size = 4
colors = []
for h, s, l in self.color_components:
color = Color(0, 0, 0)
color.hsla = h, s, l, 100
colors.append(color)
for ii in xrange(len(colors)):
tile = Surface((size, size))
for x in xrange(size):
for y in xrange(size):
if not (x + y) % 2:
color = colors[ii]
elif (x + y) % 4 == 1:
color = colors[(ii + 1) % len(colors)]
else:
color = colors[(ii + 2) % len(colors)]
tile.set_at((x, y), color)
surface = Surface(self.screen.get_size())
for x in xrange(0, surface.get_width(), size):
for y in xrange(0, surface.get_height(), size):
surface.blit(tile, (x, y))
backgrounds.append(surface)
def reset(self):
self.place_indicator()
self.activate()
def place_indicator(self):
self.indicator_rect.centery = self.\
character_rects[self.parent.characters.\
current_index].centery
def activate(self):
self.active = True
self.play()
def deactivate(self):
self.active = False
def advance_frame(self):
self.background_index += 1
if self.background_index == len(self.backgrounds):
self.background_index = 0
def change_character(self, decrement=False):
self.parent.characters.shift_index(decrement)
self.place_indicator()
def update(self):
if self.active:
if random() < self.interval_change_rate:
self.interval = randint(*self.interval_range)
Animation.update(self)
self.screen.blit(self.backgrounds[self.background_index], (0, 0))
for ii, character in enumerate(self.parent.characters):
if character.is_selected_character():
surface = character.large_surface
else:
surface = character.mono_surface
self.screen.blit(surface, self.character_rects[ii])
self.indicator_surfaces_index += 1
if self.indicator_surfaces_index == len(self.indicator_surfaces):
self.indicator_surfaces_index = 0
self.screen.blit(self.\
indicator_surfaces[self.indicator_surfaces_index],
self.indicator_rect)
class Level:
def __init__(self, pad_width_range, pad_speed_range, pad_gap_range,
room_height):
self.pad_width_range = pad_width_range
self.pad_speed_range = pad_speed_range
self.pad_gap_range = pad_gap_range
self.room_height = room_height
def generate_pad_parameters(self):
return tuple((uniform(*limits) for limits in (self.pad_width_range,
self.pad_speed_range,
self.pad_gap_range)))
class Field(Child):
levels = Level((25, 40), (.75, 1), (52, 72), 16), \
Level((18, 28), (1.2, 1.5), (58, 80), 45), \
Level((4, 12), (8, 11), (100, 150), 360)
def __init__(self, parent):
Child.__init__(self, parent)
self.background = Background(self)
self.road = Road(self)
self.pit = Pit(self)
self.room = Room(self)
self.jumper = Jumper(self)
def reset(self):
self.level_index = 0
self.deactivate()
def deactivate(self):
self.active = False
def activate(self):
self.active = True
self.pit.play()
self.road.fire.play()
def get_current_level(self):
return self.levels[self.level_index]
def start_level(self):
self.background.paint()
pad_color = self.pad_color = Color(0, 0, 0)
pad_color.hsla = randrange(0, 360), 100, 32, 100
pad_border_color = self.pad_border_color = Color(0, 0, 0)
pad_border_color.hsla = randrange(0, 360), 60, 86, 100
self.road.populate()
self.room.place()
self.jumper.drop()
def update(self):
if self.active:
self.background.update()
self.pit.update()
self.road.update()
self.room.update()
self.jumper.update()
class Background(Child):
tile_size = 16
tile_count = 32
tile_color_range = 0, 120
blend = BLEND_RGB_ADD
segment_sizes = [.1, .15, .25, .33]
foreground_saturation_range = 80, 80
foreground_lightness_range = 70, 70
foreground_hue_offset_range = 4, 30
mask_speed = 1
def __init__(self, parent):
Child.__init__(self, parent)
self.mask_x = 0
self.mask = Surface(self.screen.get_size())
self.foreground = Surface(self.screen.get_size())
def paint(self):
self.fill_mask()
self.fill_foreground()
def fill_mask(self):
self.set_tiles()
mask = self.mask
for x in xrange(0, mask.get_width(), self.tile_size):
for y in xrange(0, mask.get_height(), self.tile_size):
mask.blit(choice(self.tiles), (x, y))
def set_tiles(self):
self.tiles = tiles = []
for _ in xrange(self.tile_count):
size = self.tile_size
tile = Surface((size, size))
palette = self.get_palette()
window = Rect(0, 0, size / 2, size / 2)
for x in xrange(0, size, window.w):
for y in xrange(0, size, window.h):
window.topleft = x, y
tile.fill(palette[(x + y) % 2], window)
tiles.append(tile)
def get_palette(self):
return self.get_tile_color(), self.get_tile_color()
def get_tile_color(self):
color = [0, 0, 0]
color[randint(0, 2)] = randint(*self.tile_color_range)
return color
def fill_foreground(self):
foreground = self.foreground
rect = foreground.get_rect()
x_intervals = [0]
total = 0
shuffle(self.segment_sizes)
for size in self.segment_sizes:
interval = int(size * rect.w)
x_intervals.append(interval + total)
total += interval
x_intervals.append(rect.w)
interval_index = 0
base_hue = randrange(0, 360)
saturation = randint(*self.foreground_saturation_range)
lightness = randint(*self.foreground_lightness_range)
next_base_color = self.get_foreground_color(base_hue, saturation,
lightness)
for x in xrange(rect.w):
if x >= x_intervals[interval_index]:
interval_index += 1
base_color = next_base_color
next_base_color = self.get_foreground_color(base_color.hsla[0],
saturation,
lightness)
bh = base_color.hsla[0]
nh = next_base_color.hsla[0]
if nh < bh:
difference = 360 - bh + nh
else:
difference = nh - bh
hue = int(bh + difference * \
((x - x_intervals[interval_index - 1]) / \
float(x_intervals[interval_index] - \
x_intervals[interval_index - 1]))) % 360
color = Color(0, 0, 0)
color.hsla = [hue] + map(int, base_color.hsla[1:])
foreground.fill(color, (x, 0, 1, rect.h))
def get_foreground_color(self, base, saturation, lightness):
color = Color(0, 0, 0)
hue = (base + randint(*self.foreground_hue_offset_range)) % 360
color.hsla = hue, saturation, lightness, 100
return color
def update(self):
self.mask_x -= self.mask_speed
if self.mask_x < -self.screen.get_width():
self.mask_x = 0
self.screen.blit(self.foreground, (0, 0))
self.screen.blit(self.mask, (self.mask_x, 0), None, self.blend)
self.screen.blit(self.mask, (self.mask_x + self.screen.get_width(), 0),
None, self.blend)
class Road(Child):
def __init__(self, parent):
Child.__init__(self, parent)
self.fire = Fire(self)
self.pads = Pads(self)
def populate(self):
self.pads.populate()
def update(self):
self.fire.update()
self.pads.update()
class Fire(Animation):
frame_count = 128
tile_path = join("resource", "img", "fire.png")
speed = 1
height = 20
def __init__(self, parent):
Animation.__init__(self, parent, 0)
self.frame_index = 0
base_tile = load(self.tile_path).convert()
self.tile_height = base_tile.get_height()
frames = self.frames = []
frame_count = self.frame_count
for ii in xrange(frame_count):
tile = base_tile.copy()
pixels = PixelArray(tile)
for x in xrange(len(pixels)):
for y in xrange(len(pixels[0])):
color = Color(*tile.unmap_rgb(pixels[x][y]))
h, s, l, a = color.hsla
color.hsla = int((h + ii * 360.0 / frame_count) % 360), \
max(0, s - 10), min(100, l + 10), a
pixels[x][y] = color
del pixels
tr = tile.get_rect()
frame = Surface((self.screen.get_width(),
tr.h * (self.height / tr.h + 2)), SRCALPHA)
for x in xrange(0, frame.get_width(), tr.w):
for y in xrange(0, frame.get_height(), tr.h):
frame.blit(tile, (x, y))
frames.append(frame)
window_rect = self.window_rect = Rect(0, self.screen.get_height() - \
self.height,
self.screen.get_width(),
self.height)
self.rect = self.frames[0].get_rect()
self.rect.bottom = window_rect.bottom
def advance_frame(self):
self.frame_index += 1
if self.frame_index == len(self.frames):
self.frame_index = 0
def get_current_frame(self):
return self.frames[self.frame_index]
def update(self):
Animation.update(self)
self.rect.bottom += self.speed
if self.rect.bottom == self.window_rect.bottom + self.tile_height:
self.rect.bottom = self.window_rect.bottom
self.screen.set_clip(self.window_rect)
self.screen.blit(self.get_current_frame(), self.rect)
self.screen.set_clip(None)
class Pads(Child, list):
def __init__(self, parent):
Child.__init__(self, parent)
def populate(self):
list.__init__(self, [])
x = -20
while x < self.screen.get_width():
width, speed, self.gap = self.parent.parent.get_current_level().\
generate_pad_parameters()
self.append(Pad(self, width, speed))
self[-1].x = x
x += self.gap + width
def update(self):
self.retire()
for pad in self:
pad.update()
if self.screen.get_width() - self[-1].rect.right >= self.gap:
width, speed, self.gap = self.parent.parent.get_current_level().\
generate_pad_parameters()
self.append(Pad(self, width, speed))
def retire(self):
while self[0].rect.right < 0:
self.pop(0)
class Pad(Child):
height = 6
def __init__(self, parent, width, speed):
Child.__init__(self, parent)
self.speed = speed
surface = self.surface = Surface((width, self.height))
rect = self.rect = surface.get_rect()
field = self.parent.parent.parent
rect.bottomleft = self.screen.get_width(), \
field.road.fire.window_rect.top
surface.fill(field.pad_color)
surface.fill(field.pad_border_color, (0, 0, rect.w, 2))
surface.fill(field.pad_border_color, (0, 0, 2, rect.h))
surface.fill(field.pad_border_color, (rect.w - 2, 0, 2, rect.h))
self.x = rect.left
def update(self):
self.x -= self.speed
self.rect.left = int(self.x)
self.screen.blit(self.surface, self.rect)
class Pit(Animation):
frame_count = 20
radius = 8
alpha = 220
def __init__(self, parent):
Animation.__init__(self, parent, 600)
self.frame_index = 0
background_frames = self.background_frames = []
foreground_frames = self.foreground_frames = []
radius = self.radius
for ii in xrange(self.frame_count):
background_frame = Surface((radius * 2, self.screen.get_height()))
background_frame.set_colorkey((0, 0, 0))
foreground_frame = background_frame.copy()
color = Color(0, 0, 0)
color.hsla = int(ii * 360.0 / self.frame_count), 100, 60, 100
for y in xrange(radius, background_frame.get_height(), radius * 2):
# circle(background_frame, color, (radius, y), radius)
color.hsla = [(color.hsla[0] + 30) % 360] + list(color.hsla[1:])
circle(foreground_frame, color, (radius, y), radius - 4)
background_frame.set_alpha(self.alpha)
foreground_frame.set_alpha(self.alpha)
background_frames.append(background_frame)
foreground_frames.append(foreground_frame)
rect = self.rect = background_frame.get_rect()
rect.right = self.screen.get_rect().right - 2
def advance_frame(self):
self.frame_index += 1
if self.frame_index == len(self.background_frames):
self.frame_index = 0
def update(self):
Animation.update(self)
self.screen.blit(self.background_frames[self.frame_index], self.rect)
self.screen.blit(self.foreground_frames[self.frame_index], self.rect)
class Room(Child):
image_path = join("resource", "img", "cliff")
def __init__(self, parent):
Child.__init__(self, parent)
self.set_surfaces()
self.close()
def set_surfaces(self):
self.closed_surface = load(join(self.image_path,
"closed.png")).convert_alpha()
self.open_surface = load(join(self.image_path,
"open.png")).convert_alpha()
rect = self.rect = self.closed_surface.get_rect()
rect.right = self.screen.get_rect().right
def close(self):
self.closed = True
self.set_active_surface()
def set_active_surface(self):
if self.closed:
self.active_surface = self.closed_surface
else:
self.active_surface = self.open_surface
def open(self):
self.closed = False
self.set_active_surface()
def place(self):
self.rect.bottom = self.screen.get_height() - \
self.parent.get_current_level().room_height
def update(self):
self.screen.blit(self.active_surface, self.rect)
class Jumper(Child):
hover_location = 38, 300
hover_length = 3000
def __init__(self, parent):
Child.__init__(self, parent)
self.blink = Blink(self)
def set_surface(self):
self.surface = self.parent.parent.characters.get_selected_character().\
mini_surface
self.rect = self.surface.get_rect()
def drop(self):
self.blink.play()
self.hover_remaining = self.hover_length
self.last_ticks = get_ticks()
self.velocity = [0, 0]
self.rect.center = self.hover_location
self.precise_location = list(self.rect.topleft)
def update(self):
if self.hover_remaining > 0:
self.hover_remaining -= get_ticks() - self.last_ticks
if self.hover_remaining <= 0:
self.blink.stop()
self.velocity = [0, -5]
else:
self.last_ticks = get_ticks()
self.blink.update()
self.precise_location[0] += self.velocity[0]
self.precise_location[1] -= self.velocity[1]
self.rect.topleft = map(int, self.precise_location)
if self.blink.visible:
self.screen.blit(self.surface, self.rect)
class Blink(Animation):
def __init__(self, parent):
Animation.__init__(self, parent, 300)
self.stop()
def advance_frame(self):
self.visible = not self.visible
def stop(self):
Animation.stop(self)
self.visible = True
if __name__ == "__main__":
environ["SDL_VIDEO_CENTERED"] = "1"
Between().run()