diff --git a/OPEN-GAME b/OPEN-GAME index ddb3e9b..ba7c793 100755 --- a/OPEN-GAME +++ b/OPEN-GAME @@ -1,42 +1,38 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -from os import environ, execvp, chdir, getcwd -from os.path import exists, join, dirname -from sys import version_info, argv +import sys, os -def can_import(module_name): - try: - __import__(module_name) - except ImportError: - return False - else: - return True +def ignore_sighup(): + """ + Ignore hangup signal (that is thrown when launching from systemd?). + Taken from https://stackoverflow.com/questions/57205271/how-to-display-pygame-framebuffer-using-systemd-service + """ + import signal + def handler(signum, frame): + pass + signal.signal(signal.SIGHUP, handler) -def is_python_3(): - return version_info[0] >= 3 +# Change directory to the directory of the program launching the script (usually this script "OPEN-GAME"). +if "--go-to-dir" in sys.argv: + os.chdir(os.path.dirname(sys.argv[0])) -def is_current_version(file_name): - version = map(int, file_name.replace("python", "").split(".")) - return version == list(version_info)[:2] +# Use the KMS video driver. This works for newer versions of Raspberry Pi with the KMS overlay +# enabled, SDL 2, and Pygame 2. +if "--kms" in sys.argv: + os.putenv("SDL_VIDEODRIVER", "kmsdrm") -def launch_alternative(alternatives): - for alternative in alternatives: - if not is_current_version(alternative): - for root in environ["PATH"].split(":"): - if exists(join(root, alternative)): - execvp(alternative, [alternative] + argv) +# Use the framebuffer display. This only works with Pygame 1.9.6 (and SDL 1.2). +if "--fb" in sys.argv: + os.putenv("SDL_VIDEODRIVER", "fbcon") + os.putenv("SDL_FBDEV", "/dev/fb0") -def move_to_executable(): - chdir(dirname(argv[0])) +# Ignore hangup signal. This may be necessary when launching from systemd. +if "--ignore-hangup" in sys.argv: + ignore_sighup() -if is_python_3(): - launch_alternative(["python2", "python2.7", "python2.6"]) - -if not can_import("pygame"): - launch_alternative(["python2.7", "python2.6"]) - -if "--go-to-dir" in argv: - move_to_executable() +# Import GPIO library if requested +if "--gpio" in sys.argv: + import RPi.GPIO as GPIO from electric_sieve.ElectricSieve import ElectricSieve diff --git a/OPEN-GAME-FRAMEBUFFER b/OPEN-GAME-FRAMEBUFFER deleted file mode 100755 index 88b1dd3..0000000 --- a/OPEN-GAME-FRAMEBUFFER +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python - -import os -from os import environ, execvp, chdir, getcwd -from os.path import exists, join, dirname -from sys import version_info, argv - -def can_import(module_name): - try: - __import__(module_name) - except ImportError: - return False - else: - return True - -def is_python_3(): - return version_info[0] >= 3 - -def is_current_version(file_name): - version = map(int, file_name.replace("python", "").split(".")) - return version == list(version_info)[:2] - -def launch_alternative(alternatives): - for alternative in alternatives: - if not is_current_version(alternative): - for root in environ["PATH"].split(":"): - if exists(join(root, alternative)): - execvp(alternative, [alternative] + argv) - -def move_to_executable(): - chdir(dirname(argv[0])) - -if is_python_3(): - launch_alternative(["python2", "python2.7", "python2.6"]) - -if not can_import("pygame"): - launch_alternative(["python2.7", "python2.6"]) - -if "--go-to-dir" in argv: - move_to_executable() - -from electric_sieve.ElectricSieve import ElectricSieve - -os.putenv("SDL_FBDEV", "/dev/fb0") -os.putenv("SDL_VIDEODRIVER", "fbcon") -os.putenv("SDL_NOMOUSE", "1") -ElectricSieve().run() diff --git a/OPEN-GAME-KMS b/OPEN-GAME-KMS deleted file mode 100755 index e9a4f5a..0000000 --- a/OPEN-GAME-KMS +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 - -import sys, os - -def ignore_sighup(): - """ - Ignore hangup signal (that is thrown when launching from systemd?). - Taken from https://stackoverflow.com/questions/57205271/how-to-display-pygame-framebuffer-using-systemd-service - """ - import signal - def handler(signum, frame): - pass - signal.signal(signal.SIGHUP, handler) - -# Change directory to the directory of the program launching the script (usually this script "OPEN-GAME"). -if "--go-to-dir" in sys.argv: - os.chdir(os.path.dirname(sys.argv[0])) - -# 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") -# ignore_sighup() - -from electric_sieve.ElectricSieve import ElectricSieve - -ElectricSieve().run() diff --git a/README.md b/README.md index 74c8662..802e0c5 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ Electric Sieve ============== -Avoid touching the triangles with the rods. Chain successes to send acid deeper into the Earth. +Avoid touching the triangles with the rods Controls -------- -* L/R - Scroll sieve +* Left/Right - Scroll sieve * Down - Increase fall speed * F11 - Full screen @@ -20,4 +20,17 @@ Running ------- * Windows - double-click "scale-sieve" EXE -* Linux/Mac - run "./scale-sieve" on the command line +* Linux/Mac - run `./OPEN-GAME` on the command line + +Raspberry Pi +------------ + +Add `--gpio` to the command line to read input from buttons wired to the Raspberry Pi (the buttons should be wired to GPIO 17 and 27). + +Headless +-------- + +If running on Linux without X-Windows (for example, Raspberry Pi Lite OS), there are two options for launching + +* add `--kms` to the command line if the newer KMS driver and Pygame 2 are being used +* add `--fb` to use the framebuffer if using Pygame 1 diff --git a/electric_sieve/ElectricSieve.py b/electric_sieve/ElectricSieve.py index c64b393..9d3f2dc 100644 --- a/electric_sieve/ElectricSieve.py +++ b/electric_sieve/ElectricSieve.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- +import sys from random import randint, randrange, choice from time import time from operator import itemgetter -import RPi.GPIO as GPIO from pygame import Surface, PixelArray, Rect from pygame.draw import aalines, polygon @@ -25,12 +25,19 @@ class ElectricSieve(Game): PIN_LED_DOWN = 23 def __init__(self): - self.initialize_gpio() + if self.gpio_enabled(): + self.initialize_gpio() Game.__init__(self) self.background = Surface(self.display.screen.get_size()) self.background.fill((255, 80, 190)) self.title.activate() + def gpio_enabled(self): + """ + @return True if GPIO mode was requested at launch, False otherwise + """ + return "--gpio" in sys.argv + def initialize_gpio(self): """ Set pin numbering mode to GPIO, initialize all buttons to input pullup. @@ -38,9 +45,25 @@ class ElectricSieve(Game): # Use GPIO numbering GPIO.setmode(GPIO.BCM) - # Set all button pins to pullup + # Set button pins to pullup and attach to each a callback that runs on press or release for pin in self.PIN_BUTTON_UP, self.PIN_BUTTON_DOWN: GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) + GPIO.add_event_detect(pin, GPIO.BOTH, self.gpio_input) + + def gpio_input(self, pin): + """ + Translate GPIO input into PGFW commands, so Raspberry Pi wired controllers be used for input. The pin will be checked for a low + signal, meaning the pin has been connected to ground, which is translated to a button press. If the low signal is not detected, it + is translated to a button release. Usually called as a callback by `GPIO.add_event_detect` but can be called directly. + + @param pin Raspberry Pi pin number as read by the RPi.GPIO library + """ + cancel = not (GPIO.input(pin) == GPIO.LOW) + if pin == ElectricSieve.PIN_BUTTON_UP: + self.input.post_command("left", cancel=cancel) + elif pin == ElectricSieve.PIN_BUTTON_DOWN: + self.input.post_command("right", cancel=cancel) + self.input.post_any_command(id=pin, cancel=cancel) def set_children(self): Game.set_children(self) @@ -131,8 +154,6 @@ class Strip(Sprite): def __init__(self, parent): Sprite.__init__(self, parent) - GPIO.add_event_detect(ElectricSieve.PIN_BUTTON_UP, GPIO.BOTH, self.respond) - GPIO.add_event_detect(ElectricSieve.PIN_BUTTON_DOWN, GPIO.BOTH, self.respond) self.deactivate() self.display_surface = self.get_display_surface() self.delegate = self.get_game().delegate @@ -151,30 +172,17 @@ class Strip(Sprite): pass def respond(self, event): - if type(event) == int: - pressed = "pressed" if GPIO.input(event) == GPIO.LOW else "released" - print(f"pin {event} {pressed}") + """ + Translate input events into movement of the sieve object. This function is usually set as a callback, but it can be called directly. + + @param event `pygame.event.Event` with input + """ if self.active: - move = False - if type(event) == int: - if event == ElectricSieve.PIN_BUTTON_UP: - direction = self.LEFT - move = True - elif event == ElectricSieve.PIN_BUTTON_DOWN: - direction = self.RIGHT - move = True - active = GPIO.input(event) == GPIO.LOW - else: - compare = self.delegate.compare - if compare(event, "left") or compare(event, "left", True): - direction = self.LEFT - move = True - elif compare(event, "right") or compare(event, "right", True): - direction = self.RIGHT - move = True - active = not event.cancel - if move: - self.hshifts[direction].active = active + compare = self.delegate.compare + if compare(event, "left") or compare(event, "left", True): + self.hshifts[self.LEFT].active = not event.cancel + elif compare(event, "right") or compare(event, "right", True): + self.hshifts[self.RIGHT].active = not event.cancel def activate(self): self.active = True