From 09564da198824f3707923019ccceb9fdb2f6d213 Mon Sep 17 00:00:00 2001 From: frank Date: Sat, 4 Feb 2023 15:34:12 -0500 Subject: [PATCH] initial commit --- .gitignore | 2 + .gitmodules | 3 + Playzing.py | 167 ++++++++++++++++++++++++++++++++++++++++++++++ config | 16 +++++ fire.png | Bin 0 -> 2229 bytes games/__init__.py | 0 lib/pgfw | 1 + text.png | Bin 0 -> 16650 bytes 8 files changed, 189 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100755 Playzing.py create mode 100644 config create mode 100644 fire.png create mode 100644 games/__init__.py create mode 160000 lib/pgfw create mode 100644 text.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57bcd4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +games/* +!games/__init__.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5fed10a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/pgfw"] + path = lib/pgfw + url = https://open.shampoo.ooo/shampoo/pgfw diff --git a/Playzing.py b/Playzing.py new file mode 100755 index 0000000..f56e0c3 --- /dev/null +++ b/Playzing.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 + +# Launch PGFW games in the `games/` folder + +import argparse, os, pathlib, pygame, sys +import lib.pgfw.pgfw as pgfw +from games.ibitfit.electric_sieve.ElectricSieve import ElectricSieve + +class Playzing(pgfw.Game): + + def __init__(self): + """ + Create logo sprite, clear screen, and subscribe to events. + """ + pgfw.Game.__init__(self) + + # Assign types to configuration values + self.get_configuration().type_declarations.add_chart({ + "display": + { + "int-list": "clear" + }, + "logo": + { + "path": ["fire", "text"], + "int": ["margin", "restart", "delay"], + "bool": "trail" + } + }) + + # Clear screen to black + self.get_display_surface().fill(self.configuration.get("display", "clear")) + + # Subscribe to PGFW command events + self.subscribe(self.respond) + + # Load sprites for logo parts + self.logo_text = pgfw.Sprite(self) + self.logo_text.load_from_path(self.get_resource(self.configuration.get("logo", "text")), True) + self.logo_fire = pgfw.Sprite(self) + self.logo_fire.load_from_path(self.get_resource(self.configuration.get("logo", "fire")), True) + + # Register logo animation functions and begin animation + self.register(self.animate_logo) + self.register(self.reveal_text, interval=40) + self.play(self.animate_logo, play_once=True) + + def animate_logo(self): + """ + Reset the logo animation to its beginning and start animating. + """ + # Place the logo + screen_rect = self.get_display_surface().get_rect() + margin = self.configuration.get("logo", "margin") + self.logo_text.location.center = screen_rect.center + self.logo_text.move(-self.logo_fire.location.w + margin / 2) + + # Clear the screen + self.get_display_surface().fill(self.configuration.get("display", "clear")) + + # Place the fire at the left side of the logo text + self.logo_fire.location.midleft = self.logo_text.location.midleft + + # Close the draw clip for the logo completely + self.logo_text_clip = self.logo_text.location.copy() + self.logo_text_clip.width = 0 + + # Queue the text reveal to start + self.play(self.reveal_text, delay=self.configuration.get("logo", "delay")) + + def reveal_text(self): + """ + Move the fire right, opening the logo text clip rect until it's big enough to show all the text. Queue the animation to restart once finished. + """ + self.logo_fire.move(10) + self.logo_text_clip.width = self.logo_fire.location.left - self.logo_text.location.left + limit = self.logo_text.location.right + self.configuration.get("logo", "margin") / 2 + if self.logo_fire.location.left > limit: + self.logo_fire.location.left = limit + self.halt(self.reveal_text) + self.play(self.animate_logo, delay=self.configuration.get("logo", "restart"), play_once=True) + + def respond(self, event): + """ + Respond to all PGFW commands. If an any button is pressed, launch the iBitFit game. + """ + if self.delegate.compare(event, "any"): + os.chdir(pathlib.Path("games/ibitfit")) + print(f"current working dir is {os.getcwd()}") + ibitfit = ElectricSieve() + ibitfit.display.set_screen(dimensions=ibitfit.configuration.get("display", "dimensions")) + ibitfit.run() + os.chdir(pathlib.Path("../..")) + self.display.set_screen(dimensions=self.configuration.get("display", "dimensions")) + print(f"current working dir is {os.getcwd()}") + + def update(self): + pgfw.Animation.update(self) + + # Erase the center of the screen if enabled + if not self.configuration.get("logo", "trail"): + erase_rect = self.logo_text.location.copy() + erase_rect.width = self.logo_fire.location.right - self.logo_text.location.left + self.get_display_surface().fill(self.configuration.get("display", "clear"), erase_rect) + + # Draw the logo text clipped to the current state of the animation, then remove the clip + self.get_display_surface().set_clip(self.logo_text_clip) + self.logo_text.update() + self.get_display_surface().set_clip(None) + + # Draw the fire + self.logo_fire.update() + + +if __name__ == "__main__": + # Parse command line arguments + parser = argparse.ArgumentParser() + parser.add_argument( + "--keep-working-directory", action="store_true", help=""" + Keep using the current directory as the working directory. The script expects the games to be in the games/ folder, so they can be imported + as Python modules. Changing the directory to anything other than the launcher project's root folder will break it under normal circumstances, + so use with care. + """) + parser.add_argument( + "--kms", action="store_true", help=""" + Use SDL 2's KMS video driver. For use on systems without a windowing system (like Linux in only console mode). See + https://wiki.libsdl.org/SDL2/FAQUsingSDL#how_do_i_choose_a_specific_video_driver + """) + parser.add_argument( + "--framebuffer", action="store_true", help=""" + Use SDL 1.2's framebuffer video driver. For use on older systems without a windowing system that aren't using KMS. This won't work with + SDL 2 or Pygame 2. See https://wiki.libsdl.org/SDL2/FAQUsingSDL#how_do_i_choose_a_specific_video_driver + """) + parser.add_argument( + "--ignore-hangup", action="store_true", help=""" + Ignore hangup signals. Enabling this may be necessary for running the launcher as a systemd service. See + https://stackoverflow.com/questions/57205271/how-to-display-pygame-framebuffer-using-systemd-service + """) + arguments = parser.parse_args() + + # If not keeping the working directory, try to move into the same directory as where this program is stored. Use the location of the program + # that launched this process to determine the path. + if not arguments.keep_working_directory: + target = os.path.dirname(sys.argv[0]) + if not pathlib.Path(os.getcwd()).samefile(target): + try: + os.chdir(target) + except: + print(f""" + Warning: detected that the working directory is not the same as the launcher project's root directory and could not change to + to detected directory {target} + """) + + if arguments.kms: + + # Use the KMS video driver. This works for newer versions of Raspberry Pi with the KMS overlay enabled, SDL 2, and Pygame 2. + os.putenv("SDL_VIDEODRIVER", "kmsdrm") + + elif arguments.framebuffer: + + # Use the framebuffer display. This only works with Pygame 1.9.6 (and SDL 1.2). + os.putenv("SDL_VIDEODRIVER", "fbcon") + os.putenv("SDL_FBDEV", "/dev/fb0") + + # Run the launcher + playzing = Playzing() + playzing.run() diff --git a/config b/config new file mode 100644 index 0000000..ea1f559 --- /dev/null +++ b/config @@ -0,0 +1,16 @@ +[display] +clear = 0, 0, 0 +caption = Playzing +dimensions = 480, 720 +#dimensions = 720, 480 + +[logo] +fire = fire.png +text = text.png +margin = 18 +trail = no +restart = 22000 +delay = 3000 + +[input] +confirm-quit = yes diff --git a/fire.png b/fire.png new file mode 100644 index 0000000000000000000000000000000000000000..2a9c139a49b3375f95d8399e84d18b52ba4f41d0 GIT binary patch literal 2229 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 literal 0 HcmV?d00001