Merge remote-tracking branch 'origin/new_dev_menu'

This commit is contained in:
Nicholas Dyer 2023-05-14 22:38:08 -04:00
commit f3751def40
10 changed files with 123 additions and 317 deletions

View File

@ -1,50 +0,0 @@
"""
Module to test the GPIO input on the Raspberry Pi.
"""
from collections import deque
from pocket_friends.game_files.io.gpio_handler import Constants, GPIOHandler
def button_test():
"""
GPIO button test. Checks for a GPIO input and prints it out, simple as that.
"""
running = True
# Exit code used to quit the button test.
exit_code = deque()
for button in ['j_d', 'j_d', 'j_u', 'j_u', 'j_d', 'j_d', 'j_u', 'j_u', 'a', 'a', 'b']:
exit_code.append(button)
# Input log to check for quitting out of the button test.
input_log = deque()
GPIOHandler.setup()
def log(pressed_button):
"""
Logs the pressed button into the input log.
:param pressed_button:
"""
input_log.append(pressed_button)
if len(input_log) > len(exit_code): # Don't let the input log exceed the length of the exit code.
input_log.popleft()
def check_exit():
"""
Check if the input log and the exit code are the same. If they are, quit the button test.
"""
nonlocal running
if exit_code == input_log:
running = False
while running:
for button in Constants.buttons:
code = Constants.buttons.get(button)
if GPIOHandler.get_press(code): # If a button is pressed, print it out and do a quit check.
print('event: {0}'.format(button))
log(button)
check_exit()
GPIOHandler.teardown()

View File

@ -0,0 +1,19 @@
import os
import sys
import pygame
def reboot_system():
os.system('sudo reboot')
def shutdown_system():
os.system('sudo shutdown now')
def update():
os.system('bash ~/update.sh')
sys.exit(1)
def restart_app():
sys.exit(1)

View File

@ -1,152 +1,53 @@
"""
Development menu for the hardware on Raspberry Pi. NOTE: THIS DOES NOTHING ON A COMPUTER!
"""
import pocket_friends.game_files.game
import importlib.util
import os
import pygame import pygame
import time from pocket_friends.elements import surface
from .button_test import button_test from pocket_friends.development.sprites import FunctionSelector
from .menus import Menu import pocket_friends.development.dev as dev
from pocket_friends.game_files.io.gpio_handler import GPIOHandler, Constants
try: dev_functions = {
importlib.util.find_spec('RPi.GPIO') 'Restart': 'reboot_system',
import RPi.GPIO as GPIO 'Shutdown': 'shutdown_system',
except ImportError: 'Update': 'update',
import pocket_friends.game_files.io.fake_gpio as GPIO 'Re-open App': 'restart_app'
}
# Global variable to keep track of the current menu.
menu = 'main'
def run_button_test(): class Surface(surface.GameSurface):
""" def __init__(self, game_res, resources_dir, game_fps, **kwargs):
Runs the GPIO button test. super().__init__(game_res, resources_dir, game_fps)
""" self.frames = 1
GPIOHandler.teardown() self.game_fps = game_fps
button_test() self.delay = 1
GPIOHandler.setup() self.font = pygame.font.Font(resources_dir + '/fonts/5Pts5.ttf', 10)
self.bg = pygame.image.load(self.resource_dir + '/images/dev_menu/dev_bg.png').convert_alpha()
functions = []
for key in dev_functions.keys():
functions.append(key)
def clear_screen(): self.function_selector = FunctionSelector(resources_dir, game_res, functions)
"""
Clears the screen.
"""
print("\n" * 20)
def execute(self):
def start_game(): executing_function = getattr(dev, dev_functions.get(self.function_selector.get_function()))
""" executing_function()
Cleans the GPIO and starts the hardware.
"""
GPIOHandler.teardown()
pocket_friends.game_files.game.main()
pygame.quit()
GPIOHandler.setup()
def update(self):
self.preprocess()
def quit_menu(): text = self.font.render('f: {0}'.format(self.frames), False, (128, 128, 128))
""" self.blit(text, (3, 68))
Quits the menu. self.function_selector.draw(self)
"""
exit(0)
self.frames += 1
self.frames %= self.game_fps
def quit_with_error(): for event in pygame.event.get():
""" if event.type == pygame.KEYDOWN:
Quits the menu with error code 3. if event.key == pygame.K_UP:
""" self.function_selector.scroll_up()
exit(3) if event.key == pygame.K_DOWN:
self.function_selector.scroll_down()
if event.key == pygame.K_a:
def change_menu(new_menu): self.execute()
""" if event.key == pygame.K_b:
Changes the global menu variable for the dev menu self.running = False
:param new_menu: the menu to change to
"""
global menu
menu = new_menu
clear_screen()
print('...')
time.sleep(0.75)
def shutdown():
"""
Shuts down the linux system.
"""
os.system('sudo shutdown now')
def restart():
"""
Restarts the linux system.
"""
os.system('sudo reboot')
def main():
"""
Starts the dev menu.
"""
# The following defines all of the options in the various different menus.
main_menu = Menu('Pocket Friends Dev Menu')
main_menu.add_option(Menu.Option('Start Game', start_game))
main_menu.add_option(Menu.Option('Button Test', run_button_test))
main_menu.add_option(Menu.Option('Restart Dev Menu', quit_with_error))
main_menu.add_option(Menu.Option('Shutdown Pi', change_menu, 'shutdown'))
main_menu.add_option(Menu.Option('Restart Pi', change_menu, 'restart'))
main_menu.add_option(Menu.Option('Quit Dev Menu', change_menu, 'quit'))
shutdown_confirm = Menu('Are you sure you want to shutdown?')
shutdown_confirm.add_option(Menu.Option('No', change_menu, 'main'))
shutdown_confirm.add_option(Menu.Option('Yes', shutdown))
restart_confirm = Menu('Are you sure you want to restart?')
restart_confirm.add_option(Menu.Option('No', change_menu, 'main'))
restart_confirm.add_option(Menu.Option('Yes', restart))
quit_confirm = Menu('Are you sure you want to exit?')
quit_confirm.add_option(Menu.Option('No', change_menu, 'main'))
quit_confirm.add_option(Menu.Option('Yes', quit_menu))
GPIOHandler.setup()
def menu_handler(current_menu):
"""
Draws the menu and handles the GPIO inputs
:param current_menu: the current menu being drawn on the screen
"""
current_menu.draw_menu()
while True: # Main GPIO input loop
# Limits how often the program checks for a GPIO input. Eases CPU usage.
time.sleep(0.125)
if GPIOHandler.get_press(Constants.buttons.get('j_d')):
current_menu.select_next()
break
if GPIOHandler.get_press(Constants.buttons.get('j_u')):
current_menu.select_prev()
break
if GPIOHandler.get_press(Constants.buttons.get('a')):
current_menu.run_selection()
break
while True: # Loop for drawing the menus.
while menu == 'main':
menu_handler(main_menu)
while menu == 'shutdown':
menu_handler(shutdown_confirm)
while menu == 'restart':
menu_handler(restart_confirm)
while menu == 'quit':
menu_handler(quit_confirm)

View File

@ -1,120 +0,0 @@
"""
Menu class to help with drawing menus on screen
"""
class Menu:
"""
Menu class. Creates a menu with text to display and options
"""
def __init__(self, menu_text=''):
self._menu_text = menu_text
self._options = []
self._selection = 0
def add_option(self, option):
"""
Adds an option to the menu. Only allows instances of Menu.Option
:param option:
"""
if not isinstance(option, Menu.Option):
raise TypeError('option must be an instance of Menu.Option')
else:
self._options.append(option)
def get_option(self, index):
"""
Gets an option object given the index of the option. Raises IndexError if given
index is out of bounds.
:param index: the index of the option
:return: the option object
"""
try:
return self._options[index]
except IndexError as ex:
raise IndexError('option index out of range') from ex
def select_next(self):
"""
Selects the next option in the list. Wraps around to the first option if
the last option is currently selected.
"""
self._selection += 1
if self._selection >= len(self._options):
self._selection = 0
def select_prev(self):
"""
Selects the previous option in the list. Wraps around to the last option if
the first option is currently selected.
"""
self._selection -= 1
if self._selection < 0:
if len(self._options) > 0:
self._selection = len(self._options) - 1
else:
self._selection = 0
def run_selection(self, *args, **kwargs):
"""
Runs the function that the currently selected option object points to.
:param args: arguments to be passed to the function
:param kwargs: keyword arguments to be passed to the function
"""
try:
return self._options[self._selection].preprocess(*args, **kwargs)
except IndexError as ex:
raise Exception('menu has no options, cannot run a non-existent option') from ex
def draw_menu(self):
"""
Draws the menu on screen with a leading 20 blank lines.
"""
print('\n' * 20)
print(self._menu_text + '\n')
for option in self._options:
selection_char = '>'
if self._options.index(option) != self._selection:
selection_char = ' '
print('{0} {1}'.format(selection_char, option.get_text()))
class Option:
"""
Class that defines options for the Menu class.
"""
def __init__(self, option_text='', function=None, *args, **kwargs):
self._option_text = option_text
self._function = function
self._default_args = args
self._default_kwargs = kwargs
def get_text(self):
"""
Returns the text to be displayed by the option
:return: the option text
"""
return self._option_text
def run(self, *args, **kwargs):
"""
Runs the function that the option object points to. Returns None if
there is no function or the given function is not valid.
:param args: arguments to be passed to the function
:param kwargs: keyword arguments to be passed to the function
:return: the return value of the function (if any)
"""
if len(args) == 0:
args = self._default_args
if len(kwargs) == 0:
kwargs = self._default_kwargs
try:
return self._function(*args, **kwargs)
except TypeError:
return None

View File

@ -0,0 +1,34 @@
import pygame
class FunctionSelector:
def __init__(self, resources_dir, game_res, functions):
self.font = pygame.font.Font(resources_dir + '/fonts/5Pts5.ttf', 10)
self.functions = functions
self.max_lines = 6 # Max number of lines to be shown on screen at a time.
self.offset = 0
self.game_res = game_res
self.selected = 0
self.max_index = len(self.functions) - 1
self.selector = pygame.image.load(resources_dir + '/images/dev_menu/selector.png').convert_alpha()
def draw(self, surface):
for i in range(self.max_index + 1):
text = self.font.render(self.functions[i], False, (0, 0, 0))
surface.blit(text, (8, (-3 + (i * 6))))
surface.blit(self.selector, (0, (self.selected * 6)))
def scroll_down(self):
self.selected += 1
if self.selected > self.max_index:
self.selected = self.max_index
def scroll_up(self):
self.selected -= 1
if self.selected < 0:
self.selected = 0
def get_function(self):
return self.functions[self.selected]

View File

@ -12,6 +12,7 @@ class GameSurface(pygame.Surface):
resource_dir (:obj:`str`): The path of the game's main resource directory. resource_dir (:obj:`str`): The path of the game's main resource directory.
game_fps (int): How many frames per second the game will run at. game_fps (int): How many frames per second the game will run at.
additional_args (dict): Additional arguments to send to the next surface after halting. additional_args (dict): Additional arguments to send to the next surface after halting.
bg (:obj:`pygame.Surface`): The background of the surface.
""" """
def __init__(self, game_res, resources_dir, game_fps): def __init__(self, game_res, resources_dir, game_fps):
""" """
@ -29,8 +30,9 @@ class GameSurface(pygame.Surface):
self.game_fps = game_fps self.game_fps = game_fps
self._input_handler = InputHandler(self._clock) self._input_handler = InputHandler(self._clock)
self.additional_args = {} self.additional_args = {}
self.dev_override = False
self._bg = pygame.image.load(self.resource_dir + '/images/bg.png').convert_alpha() self.bg = pygame.image.load(self.resource_dir + '/images/bg.png').convert_alpha()
self.sprites = pygame.sprite.Group() self.sprites = pygame.sprite.Group()
def preprocess(self): def preprocess(self):
@ -39,8 +41,12 @@ class GameSurface(pygame.Surface):
""" """
self._clock.tick(self.game_fps) self._clock.tick(self.game_fps)
self.blit(self._bg, (0, 0)) self.blit(self.bg, (0, 0))
self.sprites.update() self.sprites.update()
self.sprites.draw(self) self.sprites.draw(self)
self._input_handler.update() self._input_handler.update()
if self._input_handler.dev_found:
self.next_surface = 'dev_menu'
self.dev_override = True
self.running = False

View File

@ -3,8 +3,8 @@ import os
import pocket_friends import pocket_friends
import importlib import importlib
valid_surfaces = [ valid_surfaces = [
'dev_menu',
'title', 'title',
'egg_select', 'egg_select',
'selection_info', 'selection_info',
@ -12,9 +12,10 @@ valid_surfaces = [
] ]
# Add all the surface modules to a dictionary for easy switching # Add all the surface modules to a dictionary for easy switching
surface_modules = {} surface_modules = {'dev_menu': importlib.import_module('pocket_friends.development.{0}'.format('dev_menu'))}
for module in valid_surfaces: for module in valid_surfaces:
surface_modules[module] = importlib.import_module('pocket_friends.surfaces.{0}'.format(module)) if module != 'dev_menu':
surface_modules[module] = importlib.import_module('pocket_friends.surfaces.{0}'.format(module))
starting_surface = 'title' starting_surface = 'title'
# FPS for the game to run at. # FPS for the game to run at.
@ -63,9 +64,13 @@ def start_game(resolution=240):
window.blit(frame, frame.get_rect()) window.blit(frame, frame.get_rect())
if not surface.running: if not surface.running:
next_surface = surface.next_surface if surface.dev_override:
next_surface = 'dev_menu'
else:
next_surface = surface.next_surface
additional_args = surface.additional_args additional_args = surface.additional_args
if next_surface not in valid_surfaces: if next_surface not in valid_surfaces:
print(next)
next_surface = 'error_screen' next_surface = 'error_screen'
surface = surface_modules.get(next_surface).Surface((game_res, game_res), resources_dir, surface = surface_modules.get(next_surface).Surface((game_res, game_res), resources_dir,
game_fps, **additional_args) game_fps, **additional_args)

View File

@ -1,5 +1,5 @@
import pygame import pygame
from collections import deque
class InputHandler: class InputHandler:
""" """
@ -24,6 +24,12 @@ class InputHandler:
self.clock = pygame_clock self.clock = pygame_clock
self.tick_check = tick_check self.tick_check = tick_check
self.last_input_tick = 0 self.last_input_tick = 0
self.dev_check = deque()
self.dev_code = deque()
for button in [pygame.K_DOWN, pygame.K_DOWN, pygame.K_UP, pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT,
pygame.K_RIGHT, pygame.K_LEFT, pygame.K_a]:
self.dev_code.append(button)
self.dev_found = False
def create_event(self, pressed_button): def create_event(self, pressed_button):
""" """
@ -37,6 +43,9 @@ class InputHandler:
if pygame.time.get_ticks() - self.last_input_tick > self.clock.get_time() * 2: if pygame.time.get_ticks() - self.last_input_tick > self.clock.get_time() * 2:
pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {'key': pressed_button})) pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {'key': pressed_button}))
pygame.event.post(pygame.event.Event(pygame.KEYUP, {'key': pressed_button})) pygame.event.post(pygame.event.Event(pygame.KEYUP, {'key': pressed_button}))
self.dev_check.append(pressed_button)
if len(self.dev_check) > len(self.dev_code):
self.dev_check.popleft()
else: else:
pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {'key': pressed_button})) pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {'key': pressed_button}))
pygame.event.post(pygame.event.Event(pygame.KEYUP, {'key': pressed_button})) pygame.event.post(pygame.event.Event(pygame.KEYUP, {'key': pressed_button}))
@ -70,3 +79,5 @@ class InputHandler:
def update(self): def update(self):
"""Run either the GPIO handler or the keyboard handler to check for input and create events.""" """Run either the GPIO handler or the keyboard handler to check for input and create events."""
self.handle_keyboard() self.handle_keyboard()
if self.dev_code == self.dev_check:
self.dev_found = True

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B