from random import randint, choice from math import pi from copy import copy from pygame import Surface, Color from pygame.mixer import Sound from pygame.image import load from pygame.transform import rotate from pygame.time import get_ticks from pygame.font import Font 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, RainbowSprite from lib.pgfw.pgfw.Animation import Animation from lib.pgfw.pgfw.extension import get_step_relative, get_delta, reflect_angle from lib.pgfw.pgfw.gfx_extension import aa_filled_polygon class SoundEffect(GameChild, Sound): def __init__(self, parent, path, volume=1.0): GameChild.__init__(self, parent) Sound.__init__(self, path) self.display_surface = self.get_display_surface() self.initial_volume = volume self.set_volume(volume) def play(self, loops=0, maxtime=0, fade_ms=0, position=None, x=None): self.set_volume(self.initial_volume * self.get_configuration("audio", "sfx-volume")) 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 NS(Game, Animation): NW, NE, SE, SW = range(4) N, E, S, W, NE, NW = range(6) FRONT_WIDTH = 230 BACK_WIDTH = 500 LENGTH = 150 FRONT = 300 STEP = .4 def __init__(self): Game.__init__(self) Animation.__init__(self, self) self.subscribe(self.respond, KEYDOWN) self.subscribe(self.respond, KEYUP) self.subscribe(self.respond) ds = self.get_display_surface() self.background = Surface(ds.get_size()) self.background.fill((0, 0, 0)) self.title = Title(self) self.introduction = Introduction(self) self.wipe = Wipe(self) self.platform = Platform(self) self.chemtrails = Chemtrails(self) self.boss = Boss(self) self.last_press = get_ticks() self.register(self.unsuppress_restart) self.reset() def reset(self): self.suppressing_input = False self.suppress_restart = False self.game_over = False self.title.reset() self.wipe.reset() self.introduction.reset() self.boss.reset() self.chemtrails.reset() self.platform.reset() def respond(self, event): if not self.suppressing_input and event.type in (KEYDOWN, KEYUP): # if self.last_press <= get_ticks() - int(self.get_configuration("input", "buffer")): pressed = True if event.type == KEYDOWN else False lights = self.platform.lights if event.key in (K_UP, K_o): lights[0].pressed = pressed if self.game_over and not self.suppress_restart: self.reset() elif event.key in (K_RIGHT, K_p): lights[1].pressed = pressed elif event.key in (K_DOWN, K_SEMICOLON): lights[2].pressed = pressed elif event.key in (K_LEFT, K_l): lights[3].pressed = pressed self.last_press = get_ticks() else: if self.get_delegate().compare(event, "reset-game"): self.reset() def finish_battle(self, win): self.game_over = True font = Font(self.get_resource("rounded-mplus-1m-bold.ttf"), 128) text = font.render("YOU WIN" if win else "GAME OVER", True, Color("white")) self.message = Sprite(self) self.message.add_frame(text) self.message.location.center = self.get_display_surface().get_rect().center self.boss.halt(self.boss.brandish) self.boss.sword.reset() self.boss.queue = [] self.boss.brandish_complete = True if win: self.boss.set_frameset(0) self.suppress_restart = True self.play(self.unsuppress_restart, delay=4000, play_once=True) def unsuppress_restart(self): self.suppress_restart = False font = Font(self.get_resource("rounded-mplus-1m-bold.ttf"), 48) text = font.render("(PRESS BLUE TO RESTART)", True, Color("white")) self.restart_message = Sprite(self) self.restart_message.add_frame(text) dsr = self.get_display_surface().get_rect() self.restart_message.location.center = dsr.centerx, dsr.centery + 80 def update(self): Animation.update(self) self.title.update() self.introduction.update() self.boss.update() self.platform.update() self.chemtrails.update() if self.game_over: self.message.update() if not self.suppress_restart: self.restart_message.update() self.wipe.update() class Title(GameChild): def __init__(self, parent): GameChild.__init__(self, parent) self.plank = Sprite(self) self.plank.load_from_path(self.get_resource("Title_plank.png"), True) ds = self.get_display_surface() dsr = ds.get_rect() self.plank.location.center = dsr.center self.slime_bag = Sprite(self) self.slime_bag.load_from_path(self.get_resource("Title_slime_bag.png"), True) self.slime_bag.location.bottomleft = dsr.bottomleft image = load(self.get_resource("Title_border.png")).convert() image.set_colorkey((0, 0, 0)) self.border = RainbowSprite(self, image, 30) self.border.location.center = dsr.centerx, dsr.bottom - 100 self.text = Sprite(self) self.text.load_from_path(self.get_resource("Title_text.png"), True, False, (255, 0, 0)) self.text.load_from_path(self.get_resource("Title_text_half.png"), True, False, (255, 0, 0)) self.text.add_frameset([0], name="full", switch=True) self.text.add_frameset([1], name="half") self.text.location.center = dsr.centerx, dsr.bottom - 100 self.angle = choice((pi / 4, 3 * pi / 4, 5 * pi / 4, 7 * pi / 4)) def reset(self): self.activate() self.first_pressed = False self.first_pressed_elapsed = 0 self.text.set_frameset("full") def activate(self): self.active = True def deactivate(self): self.active = False def activate_introduction(self): self.deactivate() self.get_game().introduction.activate() def update(self): if self.active: ds = self.get_display_surface() ds.fill((255, 255, 255)) dsr = ds.get_rect() if self.plank.location.right > dsr.right or self.plank.location.left < dsr.left: self.angle = reflect_angle(self.angle, 0) if self.plank.location.right > dsr.right: self.plank.move(dsr.right - self.plank.location.right) else: self.plank.move(dsr.left - self.plank.location.left) if self.plank.location.bottom > dsr.bottom or self.plank.location.top < dsr.top: self.angle = reflect_angle(self.angle, pi) if self.plank.location.bottom > dsr.bottom: self.plank.move(dy=dsr.bottom - self.plank.location.bottom) else: self.plank.move(dy=dsr.top - self.plank.location.top) dx, dy = get_delta(self.angle, 2, False) self.plank.move(dx, dy) self.plank.update() self.slime_bag.update() wipe = self.get_game().wipe if not self.first_pressed and self.get_game().platform.get_edge_pressed() == NS.N: self.first_pressed = True self.first_pressed_elapsed = 0 self.text.set_frameset("half") elif not wipe.is_playing() and self.first_pressed and \ self.get_game().platform.get_edge_pressed() == NS.NW: wipe.start(self.activate_introduction) elif self.first_pressed: self.first_pressed_elapsed += self.get_game().time_filter.get_last_frame_duration() if self.first_pressed_elapsed > 4000: self.first_pressed = False self.first_pressed_elapsed = 0 self.text.set_frameset("full") self.border.update() self.text.update() class Introduction(Animation): def __init__(self, parent): Animation.__init__(self, parent) self.tony = load(self.get_resource("Big_Tony.png")) def reset(self): self.deactivate() def deactivate(self): self.active = False def activate(self): self.active = True def update(self): if self.active: Animation.update(self) self.get_display_surface().blit(self.tony, (0, 0)) class Wipe(Animation): BLIND_COUNT = 4 SPEED = 6 TRANSPARENT_COLOR = 255, 0, 0 def __init__(self, parent): Animation.__init__(self, parent) self.image = load(self.get_resource("Ink.png")).convert() self.image.set_colorkey(self.TRANSPARENT_COLOR) def reset(self): self.deactivate() self.halt() def deactivate(self): self.active = False def activate(self): self.active = True def start(self, callback): self.activate() self.up = True self.get_game().suppressing_input = True self.blind_height = self.get_display_surface().get_height() / self.BLIND_COUNT self.callback = callback self.play() def build_frame(self): if self.up: self.blind_height -= self.SPEED if self.blind_height <= 0: self.up = False self.callback() else: self.blind_height += self.SPEED if self.blind_height >= self.get_display_surface().get_height() / self.BLIND_COUNT: self.halt() self.deactivate() self.get_game().suppressing_input = False def update(self): if self.active: Animation.update(self) ds = self.get_display_surface() dsr = ds.get_rect() frame = self.image.copy() for y in xrange(0, dsr.h, dsr.h / self.BLIND_COUNT): if self.up: frame.fill(self.TRANSPARENT_COLOR, (0, y, dsr.w, self.blind_height)) else: frame.fill(self.TRANSPARENT_COLOR, (0, y + dsr.h / self.BLIND_COUNT - self.blind_height, dsr.w, self.blind_height)) ds.blit(frame, (0, 0)) class Platform(GameChild): def __init__(self, parent): GameChild.__init__(self, parent) self.lights = [ Light(self, "cyan", NS.NW), Light(self, "magenta", NS.NE), Light(self, "yellow", NS.SE), Light(self, "white", NS.SW) ] def reset(self): self.deactivate() for light in self.lights: light.reset() def deactivate(self): self.active = False def activate(self): self.active = True def get_pressed(self): return [light.position for light in self.lights if light.pressed] def get_edge_pressed(self): pressed = self.get_pressed() if NS.NW in pressed and NS.NE in pressed: return NS.N elif NS.NE in pressed and NS.SW in pressed: return NS.NE elif NS.NE in pressed and NS.SE in pressed: return NS.E elif NS.NW in pressed and NS.SE in pressed: return NS.NW elif NS.SE in pressed and NS.SW in pressed: return NS.S elif NS.SW in pressed and NS.NW in pressed: return NS.W def update(self): if self.active: for light in self.lights: light.update() class Light(Animation): def __init__(self, parent, color, position): Animation.__init__(self, parent) self.color = Color(color) self.position = position self.pressed = False ds = self.get_display_surface() frontleft = ds.get_width() / 2 - NS.FRONT_WIDTH / 2, NS.FRONT backleft = ds.get_width() / 2 - NS.BACK_WIDTH / 2, NS.FRONT + NS.LENGTH left_step = get_step_relative(frontleft, backleft, NS.STEP) midleft = frontleft[0] + left_step[0], frontleft[1] + left_step[1] frontmid = ds.get_width() / 2, NS.FRONT mid = ds.get_width() / 2, NS.FRONT + NS.LENGTH * NS.STEP backmid = ds.get_width() / 2, NS.FRONT + NS.LENGTH frontright = ds.get_width() / 2 + NS.FRONT_WIDTH / 2, NS.FRONT backright = ds.get_width() / 2 + NS.BACK_WIDTH / 2, NS.FRONT + NS.LENGTH right_step = get_step_relative(frontright, backright, NS.STEP) midright = frontright[0] + right_step[0], frontright[1] + right_step[1] if self.position == NS.NW: self.points = frontleft, frontmid, mid, midleft elif self.position == NS.NE: self.points = frontmid, frontright, midright, mid elif self.position == NS.SE: self.points = mid, midright, backright, backmid elif self.position == NS.SW: self.points = midleft, mid, backmid, backleft self.register(self.blink, interval=300) def reset(self): self.hidden = False self.halt(self.blink) self.reset_timer() def blink(self): self.hidden = not self.hidden def update(self): Animation.update(self) boss = self.get_game().boss chemtrails = self.get_game().chemtrails if boss.queue and boss.brandish_complete and not self.is_playing(self.blink) \ and self.in_orientation(boss.queue[chemtrails.queue_index]): self.play(self.blink) elif self.is_playing(self.blink) and (not boss.queue or not self.in_orientation(boss.queue[chemtrails.queue_index])): self.reset() if not self.hidden: aa_filled_polygon(self.get_display_surface(), self.points, self.color) def in_orientation(self, orientation): if self.position == NS.NW: return orientation in (NS.N, NS.NW, NS.W) elif self.position == NS.NE: return orientation in (NS.N, NS.NE, NS.E) elif self.position == NS.SE: return orientation in (NS.NW, NS.E, NS.S) elif self.position == NS.SW: return orientation in (NS.S, NS.NE, NS.W) class Chemtrails(GameChild): TIME_LIMIT = 8000 TIME_ADDITION = 1000 def __init__(self, parent): GameChild.__init__(self, parent) self.image = load(self.get_resource("Chemtrails.png")).convert_alpha() self.life = Life(self) def reset(self): self.deactivate() self.life.reset() self.timer_remaining = self.TIME_LIMIT def deactivate(self): self.active = False def activate(self): self.active = True def challenge(self): self.timer_remaining = self.TIME_LIMIT self.queue_index = 0 def update(self): if self.active: self.orient() if self.get_game().boss.queue: self.timer_remaining -= self.get_game().time_filter.get_last_frame_duration() self.attack() if self.timer_remaining < 0: self.life.decrease() if not self.get_game().game_over: self.timer_remaining = self.TIME_LIMIT self.get_game().boss.combo() font = Font(self.get_resource("rounded-mplus-1m-bold.ttf"), 24) text = font.render("%.2f" % max(0, self.timer_remaining / 1000.0), True, Color("white")) rect = text.get_rect() ds = self.get_display_surface() rect.topright = ds.get_rect().topright ds.blit(text, rect) self.life.update() def attack(self): boss = self.get_game().boss queue = boss.queue if self.orientation == queue[self.queue_index]: self.timer_remaining += self.TIME_ADDITION boss.health.decrease(5) self.queue_index += 1 if self.queue_index == len(queue): self.timer_remaining = self.TIME_LIMIT self.get_game().boss.combo() self.get_game().platform.reset() def orient(self): ds = self.get_display_surface() edge = self.get_game().platform.get_edge_pressed() if edge == NS.N: rect = self.image.get_rect() rect.center = ds.get_width() / 2, NS.FRONT - 30 ds.blit(self.image, rect.topleft) self.orientation = NS.N elif edge == NS.E: image = rotate(self.image, 270) rect = image.get_rect() rect.center = ds.get_width() / 2 + NS.FRONT_WIDTH / 2, NS.FRONT + NS.LENGTH * NS.STEP + 10 ds.blit(image, rect.topleft) self.orientation = NS.E elif edge == NS.S: rect = self.image.get_rect() rect.center = ds.get_width() / 2, NS.FRONT + NS.LENGTH - NS.LENGTH * NS.STEP - 20 ds.blit(self.image, rect.topleft) self.orientation = NS.S elif edge == NS.W: image = rotate(self.image, 270) rect = image.get_rect() rect.center = ds.get_width() / 2 - NS.FRONT_WIDTH / 2 + 70, NS.FRONT + NS.LENGTH * NS.STEP + 10 ds.blit(image, rect.topleft) self.orientation = NS.W elif edge == NS.NW: image = rotate(self.image, 315) rect = image.get_rect() rect.center = ds.get_width() / 2 + 45, NS.FRONT + NS.LENGTH * NS.STEP - 40 ds.blit(image, rect.topleft) self.orientation = NS.NW elif edge == NS.NE: image = rotate(self.image, 45) rect = image.get_rect() rect.center = ds.get_width() / 2 - 30, NS.FRONT + NS.LENGTH * NS.STEP - 50 ds.blit(image, rect.topleft) self.orientation = NS.NE else: self.orientation = None class Life(GameChild): SPACING = 30 MARGIN = 0 def __init__(self, parent): GameChild.__init__(self, parent) self.heart = load(self.get_resource("Heart.png")).convert_alpha() def reset(self): self.count = 3 def decrease(self): if self.count > 0: self.count -= 1 if self.count <= 0: self.count = 0 self.get_game().finish_battle(False) def update(self): ds = self.get_display_surface() dsr = ds.get_rect() hr = self.heart.get_rect() rect = Rect(0, 0, hr.w * self.count + self.SPACING * (self.count - 1), hr.h) rect.midbottom = dsr.centerx, dsr.h - self.MARGIN for x in xrange(rect.left, rect.right, hr.w + self.SPACING): ds.blit(self.heart, (x, rect.top)) class Boss(RainbowSprite): def __init__(self, parent): RainbowSprite.__init__(self, parent, load("resource/Koolaid.png").convert_alpha(), 30) self.health = Health(self) self.sword = Sword(self) self.register(self.brandish, self.cancel_flash) self.add_frameset([0], name="normal") def cancel_flash(self): self.set_frameset("normal") def reset(self): self.deactivate() self.unhide() self.cancel_flash() self.halt(self.cancel_flash) self.health.reset() self.halt(self.brandish) self.sword.reset() self.combo() self.queue = None self.brandish_complete = True def deactivate(self): self.active = False def activate(self): self.active = True def combo(self): self.queue = None self.play(self.brandish, delay=2500, play_once=True) def brandish(self): self.queue = [] choices = range(6) if self.health.amount > 90: length = 1 elif self.health.amount > 70: length = 2 elif self.health.amount > 40: length = 3 else: length = 4 while len(self.queue) < length: while True: orientation = randint(0, 5) if not self.queue or orientation != self.queue[-1]: self.queue.append(orientation) break self.unbrandished = copy(self.queue) self.brandish_complete = False self.sword.play(self.sword.brandish, play_once=True) self.get_game().chemtrails.challenge() def update(self): if self.active: RainbowSprite.update(self) # self.get_display_surface().blit(self.image, (0, 0)) self.sword.update() self.health.update() class Sword(Sprite): def __init__(self, parent): Sprite.__init__(self, parent) image = load(self.get_resource("Sword.png")).convert_alpha() self.add_frame(image) for angle in 270, 315, 45: self.add_frame(rotate(image, angle)) self.add_frameset([0], name="vertical") self.add_frameset([1], name="horizontal") self.add_frameset([2], name="rdiagonal") self.add_frameset([3], name="ldiagonal") self.set_frameset("vertical") self.location.center = self.get_display_surface().get_rect().center self.register(self.brandish, self.lower) def reset(self): self.halt(self.brandish) self.halt(self.lower) self.hide() def brandish(self): self.unhide() position = self.parent.unbrandished.pop(0) dsr = self.get_display_surface().get_rect() if position in (NS.W, NS.E): self.set_frameset("vertical") self.location.centery = dsr.centery - 100 if position == NS.W: self.location.centerx = dsr.centerx - 100 else: self.location.centerx = dsr.centerx + 100 elif position in (NS.N, NS.S): self.set_frameset("horizontal") self.location.centerx = dsr.centerx if position == NS.N: self.location.centery = dsr.centery - 200 else: self.location.centery = dsr.centery else: if position == NS.NW: self.set_frameset("ldiagonal") else: self.set_frameset("rdiagonal") self.location.center = dsr.centerx, dsr.centery - 100 self.play(self.lower, delay=400, play_once=True) if len(self.parent.unbrandished) > 0: self.play(self.brandish, delay=600, play_once=True) def lower(self): self.hide() if len(self.parent.unbrandished) == 0: self.parent.brandish_complete = True def update(self): Sprite.update(self) class Health(GameChild): WIDTH = 200 HEIGHT = 32 COLOR = "yellow" def __init__(self, parent): GameChild.__init__(self, parent) def reset(self): self.amount = 100 def decrease(self, damage): self.amount -= damage if self.amount <= 0: self.amount = 0 self.get_game().finish_battle(True) else: self.parent.play(self.parent.cancel_flash, delay=1000, play_once=True) self.parent.set_frameset(0) def update(self): surface = Surface((int(self.WIDTH * (self.amount / 100.0)), self.HEIGHT)) surface.fill(Color(self.COLOR)) surface.set_alpha(255) font = Font(self.get_resource("rounded-mplus-1m-bold.ttf"), 14) text = font.render("HEALTH", True, Color("black")) # surface.blit(text, (8, 0)) self.get_display_surface().blit(surface, (0, 0))