from random import randint, choice 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 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) 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.lights = [ Light(self, "cyan", self.NW), Light(self, "magenta", self.NE), Light(self, "yellow", self.SE), Light(self, "white", self.SW) ] self.chemtrails = Chemtrails(self) self.boss = Boss(self) self.last_press = get_ticks() self.register(self.unsuppress_restart) self.reset() def reset(self): self.suppress_restart = False self.game_over = False self.boss.reset() self.chemtrails.reset() for light in self.lights: light.reset() def respond(self, event): if 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 if (event.key in (K_UP, K_o)): self.lights[0].pressed = pressed if self.game_over and not self.suppress_restart: self.reset() elif (event.key in (K_RIGHT, K_p)): self.lights[1].pressed = pressed elif (event.key in (K_DOWN, K_SEMICOLON)): self.lights[2].pressed = pressed elif (event.key in (K_LEFT, K_l)): self.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.get_display_surface().blit(self.background, (0, 0)) self.boss.update() for light in self.lights: light.update() self.chemtrails.update() if self.game_over: self.message.update() if not self.suppress_restart: self.restart_message.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) 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 (Sword.N, Sword.NW, Sword.W) elif self.position == NS.NE: return orientation in (Sword.N, Sword.NE, Sword.E) elif self.position == NS.SE: return orientation in (Sword.NW, Sword.E, Sword.S) elif self.position == NS.SW: return orientation in (Sword.S, Sword.NE, Sword.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.life.reset() self.timer_remaining = self.TIME_LIMIT def challenge(self): self.timer_remaining = self.TIME_LIMIT self.queue_index = 0 def update(self): 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() for light in self.get_game().lights: light.reset() light.reset_timer() def orient(self): ds = self.get_display_surface() lights = self.parent.lights pressed = [light.position for light in self.parent.lights if light.pressed] if NS.NW in pressed and NS.NE in pressed: rect = self.image.get_rect() rect.center = ds.get_width() / 2, NS.FRONT - 30 ds.blit(self.image, rect.topleft) self.orientation = Sword.N elif NS.NE in pressed and NS.SE in pressed: 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 = Sword.E elif NS.SE in pressed and NS.SW in pressed: 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 = Sword.S elif NS.SW in pressed and NS.NW in pressed: 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 = Sword.W elif NS.NW in pressed and NS.SE in pressed: 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 = Sword.NW elif NS.NE in pressed and NS.SW in pressed: 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 = Sword.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.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 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): RainbowSprite.update(self) # self.get_display_surface().blit(self.image, (0, 0)) self.sword.update() self.health.update() class Sword(Sprite): N, E, S, W, NE, NW = range(6) 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 (self.W, self.E): self.set_frameset("vertical") self.location.centery = dsr.centery - 100 if position == self.W: self.location.centerx = dsr.centerx - 100 else: self.location.centerx = dsr.centerx + 100 elif position in (self.N, self.S): self.set_frameset("horizontal") self.location.centerx = dsr.centerx if position == self.N: self.location.centery = dsr.centery - 200 else: self.location.centery = dsr.centery else: if position == self.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))