diff --git a/NS.py b/NS.py index 18842a2..edb43a9 100644 --- a/NS.py +++ b/NS.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from random import randint, choice +from random import randint, choice, random from math import pi from copy import copy from glob import iglob @@ -9,16 +9,17 @@ from threading import Thread from serial import Serial, SerialException from serial.tools import list_ports from time import sleep +from PIL import Image from pygame import Surface, Color, mixer from pygame.event import clear from pygame.mixer import Sound from pygame.image import load, fromstring -from pygame.transform import rotate, flip +from pygame.transform import rotate, flip, scale from pygame.time import get_ticks from pygame.font import Font from pygame.draw import aalines, lines -from pygame.gfxdraw import aapolygon, arc, polygon +from pygame.gfxdraw import aapolygon, arc, polygon, aaellipse, ellipse, filled_ellipse, filled_circle from pygame.locals import * from lib.pgfw.pgfw.Game import Game @@ -74,6 +75,7 @@ class NS(Game, Animation): self.dialogue = Dialogue(self) self.chemtrails = Chemtrails(self) self.boss = Boss(self) + self.tony = Tony(self) if self.serial_enabled(): self.serial_kill = False self.serial_data = 0 @@ -158,6 +160,7 @@ class NS(Game, Animation): self.platform.reset() self.dialogue.reset() self.no_reset_elapsed = 0 + self.title.activate() def set_most_recent_time(self, score): self.most_recent_time = score @@ -210,13 +213,12 @@ class NS(Game, Animation): print("auto arduino reset triggered") self.reset_arduino() self.no_reset_elapsed = 0 - self.title.update() self.introduction.update() self.ending.update() self.boss.update() - if not self.introduction.active: - self.platform.update() + self.platform.update() self.chemtrails.update() + self.title.update() self.boss.update_dialogue() self.wipe.update() self.idle_elapsed += self.time_filter.get_last_frame_duration() @@ -314,8 +316,67 @@ class Meter(GameChild): icon.update() +class Tony(Sprite): + + def __init__(self, parent): + Sprite.__init__(self, parent) + dsr = self.get_display_surface().get_rect() + for offset in range(12): + w, h = dsr.w + 40, int(dsr.h * .65) + glow = Surface((w, h), SRCALPHA) + for ii, y in enumerate(range(h, 0, -8)): + hue = range(200, 140, -5)[(ii - offset) % 12] + alpha = min(100, int(round(y / float(h - 10) * 100))) + color = get_hsla_color(hue, 100, 50, alpha) + if ii == 0: + aaellipse(glow, w // 2, y, w // 2 - 4, h // 20, color) + ellipse(glow, w // 2, y, w // 2 - 4, h // 20, color) + filled_ellipse(glow, w // 2, y, w // 2 - 4, h // 20, color) + frame = load(self.get_resource("Big_Tony.png")).convert_alpha() + frame.blit(glow, (-20, int(dsr.h * .35)), None, BLEND_RGBA_SUB) + self.add_frame(frame) + + +class Video(Sprite): + + def __init__(self, parent): + Sprite.__init__(self, parent, 100) + pattern = join(self.get_resource("gif"), "Boarding_*.gif") + self.gifs = [] + for path in iglob(pattern): + self.gifs.append(Image.open(path)) + print(self.gifs[-1].info) + self.gif = self.gifs[1] + self.mask = Surface((320, 320), SRCALPHA) + rect = self.mask.get_rect() + filled_circle(self.mask, rect.centerx, rect.centery, rect.centerx, (255, 255, 255)) + + def update(self): + if random() < .01: + while True: + selection = choice(self.gifs) + if selection != self.gif: + self.gif = selection + break + if random() < .005: + self.toggle_hidden() + self.gif.seek((self.gif.tell() + 1) % self.gif.n_frames) + frame = scale( + fromstring(self.gif.convert("RGBA").tobytes(), self.gif.size, "RGBA"), + (self.mask.get_width(), int(self.gif.width * self.gif.height / self.mask.get_width()))) + copy = self.mask.copy() + rect = frame.get_rect() + rect.bottom = copy.get_rect().bottom + copy.blit(frame, rect, None, BLEND_RGBA_MIN) + self.clear_frames() + self.add_frame(copy) + Sprite.update(self) + + class Title(GameChild): + UNLOCK_MOVES = NS.N, NS.NW, NS.E, NS.S + def __init__(self, parent): GameChild.__init__(self, parent) self.plank = Sprite(self) @@ -323,39 +384,43 @@ class Title(GameChild): 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.location.center = dsr.centerx, dsr.bottom - 100 - self.angle = choice((pi / 4, 3 * pi / 4, 5 * pi / 4, 7 * pi / 4)) - self.button_sound = self.get_audio().sfx["button"] - self.buttons = Button(self, NS.N, 10, 4), Button(self, NS.NW, 10, 4) - self.buttons[0].location.center = 277, 381 - self.buttons[1].location.center = 453, 381 + # self.angle = choice((pi / 4, 3 * pi / 4, 5 * pi / 4, 7 * pi / 4)) + self.angle = 7 * pi / 4 + self.background = Sprite(self) + self.background.load_from_path(self.get_resource("Title_tile.png"), True) + for y in range(0, dsr.h + self.background.location.h, self.background.location.h): + for x in range(0, dsr.w + self.background.location.w, self.background.location.w): + if x != 0 or y != 0: + self.background.add_location((x, y)) + self.effect = Sprite(self, 100) + palette = (255, 255, 255), (255, 255, 128), (255, 255, 0) + thickness = 8 + for offset in range(len(palette)): + frame = Surface(dsr.size) + for x in range(0, dsr.w, thickness): + frame.fill(palette[(offset + x) % len(palette)], (x, 0, thickness, dsr.h)) + self.effect.add_frame(frame) + self.video = Video(self) def reset(self): - self.activate() self.first_pressed = False self.first_pressed_elapsed = 0 - for button in self.buttons: - button.unhide() + self.unlock_index = 0 def activate(self): self.active = True + platform = self.get_game().platform + platform.activate() + platform.set_glowing(platform.get_buttons_from_edges([self.UNLOCK_MOVES[self.unlock_index]])) + self.get_game().chemtrails.activate() def deactivate(self): self.active = False - def activate_introduction(self): + def start_game(self): self.deactivate() - self.get_game().introduction.activate() self.get_game().set_most_recent_time(None) + self.get_game().boss.start_level(0) def draw_scores(self): step = 75 @@ -372,7 +437,7 @@ class Title(GameChild): text = entry message = render_box(font, text, True, Color(255, 255, 255), Color(128, 128, 128), Color(0, 0, 0), padding=2) - message.set_alpha(200) + message.set_alpha(230) rect = message.get_rect() rect.top = y if ii < 5: @@ -392,47 +457,59 @@ class Title(GameChild): return "%i:%02i.%i" % (minutes, seconds, fraction / 100) def update(self): + ''' + Move title, check button presses, and draw screen + ''' 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.effect.update() + # tiled background + self.background.move(-2, 2) + if self.background.location.right < 0: + self.background.move(self.background.location.w) + if self.background.location.top > 0: + self.background.move(dy=-self.background.location.h) + self.background.update(flags=BLEND_RGBA_MIN) + self.get_game().tony.update() + # advance unlock pattern + platform = self.get_game().platform + if not self.get_game().wipe.is_playing() and platform.get_edge_pressed() == self.UNLOCK_MOVES[self.unlock_index]: self.first_pressed = True self.first_pressed_elapsed = 0 - self.buttons[0].hide() - self.button_sound.play() - elif not wipe.is_playing() and self.first_pressed and \ - self.get_game().platform.get_edge_pressed() == NS.NW: - wipe.start(self.activate_introduction) - self.get_audio().play_sfx("confirm") - elif self.first_pressed: + if self.unlock_index == len(self.UNLOCK_MOVES) - 1: + platform.set_glowing([]) + self.get_game().wipe.start(self.start_game) + self.get_audio().play_sfx("confirm") + else: + self.unlock_index += 1 + platform.set_glowing(platform.get_buttons_from_edges([self.UNLOCK_MOVES[self.unlock_index]])) + self.get_audio().play_sfx("land_0") + # reset unlock pattern if idle + if 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.buttons[0].unhide() - self.border.update() - self.text.update() + if self.first_pressed_elapsed > 1000 * 60 * 1: + self.reset() + platform.update() + self.get_game().chemtrails.update() + # bounce the gif around the screen + if self.video.location.right > dsr.right or self.video.location.left < dsr.left: + self.angle = reflect_angle(self.angle, 0) + if self.video.location.right > dsr.right: + self.video.move(dsr.right - self.video.location.right) + else: + self.video.move(dsr.left - self.video.location.left) + if self.video.location.bottom > dsr.bottom or self.video.location.top < dsr.top: + self.angle = reflect_angle(self.angle, pi) + if self.video.location.bottom > dsr.bottom: + self.video.move(dy=dsr.bottom - self.video.location.bottom) + else: + self.video.move(dy=dsr.top - self.video.location.top) + dx, dy = get_delta(self.angle, 5, False) + self.video.move(dx, dy) + if not platform.get_pressed(): + self.video.update() self.draw_scores() - for button in self.buttons: - button.update() class Dialogue(Animation): @@ -477,7 +554,7 @@ class Dialogue(Animation): def stop_speech(self): if self.speech_channel is not None: self.speech_channel.stop() - self.speech_channel = None + self.speech_channel = None def deactivate(self): self.stop_speech() @@ -494,7 +571,7 @@ class Dialogue(Animation): def set_name(self, text): font = Font(self.get_resource(self.FONT_PATH), self.FONT_SIZE) self.name = Sprite(self) - self.name.add_frame(font.render(text, True, self.TEXT_COLOR)) + self.name.add_frame(font.render(text, True, self.TEXT_COLOR).convert_alpha()) self.name.location.midleft = self.name_box.location.left + 5, self.name_box.location.centery def show_text(self, text): @@ -526,11 +603,11 @@ class Dialogue(Animation): lines = self.full_text[:self.text_index].split("\n") frame = Surface((self.text_box.location.w - 10, 30 * len(lines)), SRCALPHA) for ii, line in enumerate(lines): - surface = font.render(line, True, self.TEXT_COLOR) + surface = font.render(line, True, self.TEXT_COLOR).convert_alpha() frame.blit(surface, (0, 30 * ii)) - message.add_frame(frame) - message.location.topleft = self.text_box.location.left + 9, self.text_box.location.top + 8 - message.update() + message.add_frame(frame) + message.location.topleft = self.text_box.location.left + 9, self.text_box.location.top + 8 + message.update() class Introduction(Animation): @@ -554,9 +631,8 @@ class Introduction(Animation): self.words = [] for word in "hey you lizard slime bag show me you can scrape".split(" "): font = Font(self.get_resource(Dialogue.FONT_PATH), 96) - sprite = RainbowSprite(self, font.render(word, True, (255, 0, 0)), 30) + sprite = RainbowSprite(self, font.render(word, True, (255, 0, 0)).convert_alpha(), 30) self.words.append(sprite) - self.tony = load(self.get_resource("Big_Tony.png")).convert() self.skateboard = Sprite(self) self.skateboard.load_from_path(self.get_resource("Introduction_skateboard.png"), True) self.slime_bag = Sprite(self) @@ -694,7 +770,7 @@ class Introduction(Animation): else: platform.set_glowing(platform.get_buttons_from_edges( [self.TUTORIAL_MOVES[self.tutorial_index]])) - self.get_display_surface().blit(self.tony, (0, 0)) + self.get_game().tony.update() self.slime_bag.update() self.skateboard.update() for word in self.words: @@ -730,7 +806,7 @@ class SkipPrompt(GameChild): left += self.buttons[-1].location.width + AdvancePrompt.BUTTON_SPACING self.text = Sprite(self) font = Font(self.get_resource(Dialogue.FONT_PATH), 18) - self.text.add_frame(font.render("TO SKIP", True, (0, 0, 0))) + self.text.add_frame(font.render("TO SKIP", True, (0, 0, 0)).convert_alpha()) self.text.location.midleft = ( self.buttons[2].location.right + 5, self.buttons[2].location.centery) @@ -1029,7 +1105,7 @@ class Platform(GameChild): class Light(Animation): MAX_GLOW_INDEX = 25 - INTRODUCTION_OFFSET = 80 + TITLE_OFFSET = 0 def __init__(self, parent, color, position): Animation.__init__(self, parent) @@ -1077,7 +1153,7 @@ class Light(Animation): def update(self): Animation.update(self) - if not self.get_game().introduction.active: + if not self.get_game().title.active: boss = self.get_game().boss chemtrails = self.get_game().chemtrails if boss.queue and boss.brandish_complete and not self.is_playing(self.glow) \ @@ -1091,10 +1167,10 @@ class Light(Animation): aa_filled_polygon(ds, self.get_points(), self.color) def get_points(self): - if self.get_game().introduction.active: + if self.get_game().title.active: points = [] for point in self.points: - points.append((point[0], point[1] - self.INTRODUCTION_OFFSET)) + points.append((point[0], point[1] - self.TITLE_OFFSET)) return points else: return self.points @@ -1168,17 +1244,17 @@ class Chemtrails(Sprite): if self.active: self.orient() Sprite.update(self) - if not self.get_game().introduction.active: + if not self.get_game().title.active: boss = self.get_game().boss if boss.queue: self.timer.tick() self.attack() if self.timer.amount < 0: self.life.decrease() - if not boss.is_playing(boss.show_end_dialogue): + if not boss.is_playing(boss.show_end_dialogue, include_delay=True): self.timer.reset() boss.combo() - if not boss.is_playing(boss.show_introduction_dialogue): + if not boss.is_playing(boss.show_introduction_dialogue, include_delay=True): self.timer.update() self.life.update() # self.boys.update() @@ -1199,7 +1275,7 @@ class Chemtrails(Sprite): boss.sword.block() if self.queue_index == len(queue): self.timer.reset() - if not boss.is_playing(boss.show_end_dialogue): + if not boss.is_playing(boss.show_end_dialogue, include_delay=True): boss.combo() self.get_audio().play_sfx("complete_pattern_3") else: @@ -1209,7 +1285,7 @@ class Chemtrails(Sprite): def orient(self): ds = self.get_display_surface() edge = self.get_game().platform.get_edge_pressed() - dy = -Light.INTRODUCTION_OFFSET if self.get_game().introduction.active else 0 + dy = -Light.TITLE_OFFSET if self.get_game().title.active else 0 if edge is not None: self.set_frameset(edge + 1) self.unhide() @@ -1681,17 +1757,17 @@ class Countdown(GameChild): dsr = self.get_display_surface().get_rect() font = Font(self.get_resource(Dialogue.FONT_PATH), 76) self.heading = Sprite(self) - self.heading.add_frame(font.render("CONTINUE?", True, (0, 0, 0), (255, 255, 255))) + self.heading.add_frame(font.render("CONTINUE?", True, (0, 0, 0), (255, 255, 255)).convert_alpha()) self.heading.location.midtop = dsr.centerx, 50 self.game_over = Sprite(self) - self.game_over.add_frame(font.render("GAME OVER", True, (0, 0, 0), (255, 255, 255))) + self.game_over.add_frame(font.render("GAME OVER", True, (0, 0, 0), (255, 255, 255)).convert_alpha()) self.game_over.location.center = dsr.centerx, dsr.centery - 40 self.glyphs = [] for ii in range(10): glyph = Sprite(self) frame = Surface((140, 140)) frame.fill((255, 255, 255)) - digits = font.render("%i" % ii, True, (0, 0, 0), (255, 255, 255)) + digits = font.render("%i" % ii, True, (0, 0, 0), (255, 255, 255)).convert_alpha() rect = digits.get_rect() rect.center = frame.get_rect().center frame.blit(digits, rect) @@ -1872,7 +1948,6 @@ class Ending(Animation): def __init__(self, parent): Animation.__init__(self, parent) - self.tony = load(self.get_resource("Big_Tony.png")).convert() self.slime_bag = Sprite(self) self.slime_bag.load_from_path(self.get_resource("Introduction_slime_bag.png"), True) self.slime_bag.location.center = self.get_display_surface().get_rect().centerx, 300 @@ -1934,7 +2009,7 @@ class Ending(Animation): else: self.start_wipe() self.advance_prompt.cancel_first_press() - self.get_display_surface().blit(self.tony, (0, 0)) + self.get_game().tony.update() self.slime_bag.update() dsr = self.get_display_surface().get_rect() if self.text.location.right > dsr.right or self.text.location.left < dsr.left: diff --git a/config b/config index 3f56497..04b1bcd 100644 --- a/config +++ b/config @@ -1,6 +1,6 @@ [setup] license = Public Domain -title = Electric Scrapeboard +title = Scrapeboard url = http://shampoo.ooo/games/esb version = 0.2.3 init-script = OPEN-GAME @@ -8,7 +8,7 @@ additional-packages = lib data-exclude = local/, *.pyc [display] -caption = Electric Scrapeboard +caption = Scrapeboard show-framerate = no dimensions = 640, 480 fullscreen = no @@ -26,7 +26,7 @@ sfx-volume = .8 [input] buffer = 0 arduino-port = /dev/ttyACM0 -serial = no +serial = yes [time] timer-max-time = 10000 diff --git a/record_test.py b/record_test.py new file mode 100644 index 0000000..ae66200 --- /dev/null +++ b/record_test.py @@ -0,0 +1,38 @@ +import pyaudio +import wave + +CHUNK = 1024 +FORMAT = pyaudio.paInt32 +CHANNELS = 2 +RATE = 44100 +RECORD_SECONDS = 5 +WAVE_OUTPUT_FILENAME = "output.wav" + +p = pyaudio.PyAudio() + +stream = p.open(format=FORMAT, + channels=CHANNELS, + rate=RATE, + input=True, + frames_per_buffer=CHUNK) + +print("* recording") + +frames = [] + +for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)): + data = stream.read(CHUNK) + frames.append(data) + +print("* done recording") + +stream.stop_stream() +stream.close() +p.terminate() + +wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb') +wf.setnchannels(CHANNELS) +wf.setsampwidth(p.get_sample_size(FORMAT)) +wf.setframerate(RATE) +wf.writeframes(b''.join(frames)) +wf.close() diff --git a/resource/Big_Tony.png b/resource/Big_Tony.png new file mode 100644 index 0000000..fe995b4 Binary files /dev/null and b/resource/Big_Tony.png differ diff --git a/resource/Title_plank.png b/resource/Title_plank.png index 646c97b..7457704 100644 Binary files a/resource/Title_plank.png and b/resource/Title_plank.png differ diff --git a/resource/Title_tile.png b/resource/Title_tile.png new file mode 100644 index 0000000..e06d706 Binary files /dev/null and b/resource/Title_tile.png differ diff --git a/resource/gif/Boarding_0.gif b/resource/gif/Boarding_0.gif new file mode 100644 index 0000000..f358415 Binary files /dev/null and b/resource/gif/Boarding_0.gif differ diff --git a/resource/gif/Boarding_1.gif b/resource/gif/Boarding_1.gif new file mode 100644 index 0000000..0139a2f Binary files /dev/null and b/resource/gif/Boarding_1.gif differ diff --git a/resource/gif/Boarding_2.gif b/resource/gif/Boarding_2.gif new file mode 100644 index 0000000..752c6a2 Binary files /dev/null and b/resource/gif/Boarding_2.gif differ diff --git a/resource/gif/Boarding_3.gif b/resource/gif/Boarding_3.gif new file mode 100644 index 0000000..a95ad5a Binary files /dev/null and b/resource/gif/Boarding_3.gif differ diff --git a/resource/gif/Boarding_4.gif b/resource/gif/Boarding_4.gif new file mode 100644 index 0000000..b6a1fc7 Binary files /dev/null and b/resource/gif/Boarding_4.gif differ diff --git a/resource/scores b/resource/scores index a92d9c1..7c9c0b8 100644 --- a/resource/scores +++ b/resource/scores @@ -17,3 +17,5 @@ 312561 173960 237842 +171298 +164711 diff --git a/www/Boarding_0.gif b/www/Boarding_0.gif deleted file mode 100644 index f358415..0000000 Binary files a/www/Boarding_0.gif and /dev/null differ diff --git a/www/Boarding_0.gif b/www/Boarding_0.gif new file mode 120000 index 0000000..9cdd11b --- /dev/null +++ b/www/Boarding_0.gif @@ -0,0 +1 @@ +../resource/gif/Boarding_0.gif \ No newline at end of file diff --git a/www/Boarding_1.gif b/www/Boarding_1.gif deleted file mode 100644 index 0139a2f..0000000 Binary files a/www/Boarding_1.gif and /dev/null differ diff --git a/www/Boarding_1.gif b/www/Boarding_1.gif new file mode 120000 index 0000000..ca44342 --- /dev/null +++ b/www/Boarding_1.gif @@ -0,0 +1 @@ +../resource/gif/Boarding_1.gif \ No newline at end of file diff --git a/www/Boarding_2.gif b/www/Boarding_2.gif deleted file mode 100644 index 752c6a2..0000000 Binary files a/www/Boarding_2.gif and /dev/null differ diff --git a/www/Boarding_2.gif b/www/Boarding_2.gif new file mode 120000 index 0000000..92b008e --- /dev/null +++ b/www/Boarding_2.gif @@ -0,0 +1 @@ +../resource/gif/Boarding_2.gif \ No newline at end of file diff --git a/www/Boarding_3.gif b/www/Boarding_3.gif deleted file mode 100644 index a95ad5a..0000000 Binary files a/www/Boarding_3.gif and /dev/null differ diff --git a/www/Boarding_3.gif b/www/Boarding_3.gif new file mode 120000 index 0000000..e4ddf9c --- /dev/null +++ b/www/Boarding_3.gif @@ -0,0 +1 @@ +../resource/gif/Boarding_3.gif \ No newline at end of file diff --git a/www/Multiplay_Closing-Flan.gif b/www/Multiplay_Closing-Flan.gif deleted file mode 100644 index b6a1fc7..0000000 Binary files a/www/Multiplay_Closing-Flan.gif and /dev/null differ diff --git a/www/Multiplay_Closing-Flan.gif b/www/Multiplay_Closing-Flan.gif new file mode 120000 index 0000000..7a57ce4 --- /dev/null +++ b/www/Multiplay_Closing-Flan.gif @@ -0,0 +1 @@ +../resource/gif/Boarding_4.gif \ No newline at end of file