diff --git a/pocket_friends/development/button_test.py b/pocket_friends/development/button_test.py deleted file mode 100644 index df5353c..0000000 --- a/pocket_friends/development/button_test.py +++ /dev/null @@ -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() diff --git a/pocket_friends/development/dev.py b/pocket_friends/development/dev.py new file mode 100644 index 0000000..1fc799b --- /dev/null +++ b/pocket_friends/development/dev.py @@ -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) \ No newline at end of file diff --git a/pocket_friends/development/dev_menu.py b/pocket_friends/development/dev_menu.py index 9dead9b..1653954 100644 --- a/pocket_friends/development/dev_menu.py +++ b/pocket_friends/development/dev_menu.py @@ -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 time -from .button_test import button_test -from .menus import Menu -from pocket_friends.game_files.io.gpio_handler import GPIOHandler, Constants +from pocket_friends.elements import surface +from pocket_friends.development.sprites import FunctionSelector +import pocket_friends.development.dev as dev -try: - importlib.util.find_spec('RPi.GPIO') - import RPi.GPIO as GPIO -except ImportError: - import pocket_friends.game_files.io.fake_gpio as GPIO - -# Global variable to keep track of the current menu. -menu = 'main' +dev_functions = { + 'Restart': 'reboot_system', + 'Shutdown': 'shutdown_system', + 'Update': 'update', + 'Re-open App': 'restart_app' +} -def run_button_test(): - """ - Runs the GPIO button test. - """ - GPIOHandler.teardown() - button_test() - GPIOHandler.setup() +class Surface(surface.GameSurface): + def __init__(self, game_res, resources_dir, game_fps, **kwargs): + super().__init__(game_res, resources_dir, game_fps) + self.frames = 1 + self.game_fps = game_fps + self.delay = 1 + 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(): - """ - Clears the screen. - """ - print("\n" * 20) + self.function_selector = FunctionSelector(resources_dir, game_res, functions) + def execute(self): -def start_game(): - """ - Cleans the GPIO and starts the hardware. - """ - GPIOHandler.teardown() - pocket_friends.game_files.game.main() - pygame.quit() - GPIOHandler.setup() + executing_function = getattr(dev, dev_functions.get(self.function_selector.get_function())) + executing_function() + def update(self): + self.preprocess() -def quit_menu(): - """ - Quits the menu. - """ - exit(0) + text = self.font.render('f: {0}'.format(self.frames), False, (128, 128, 128)) + self.blit(text, (3, 68)) + self.function_selector.draw(self) + self.frames += 1 + self.frames %= self.game_fps -def quit_with_error(): - """ - Quits the menu with error code 3. - """ - exit(3) - - -def change_menu(new_menu): - """ - Changes the global menu variable for the dev menu - :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) + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_UP: + self.function_selector.scroll_up() + if event.key == pygame.K_DOWN: + self.function_selector.scroll_down() + if event.key == pygame.K_a: + self.execute() + if event.key == pygame.K_b: + self.running = False diff --git a/pocket_friends/development/menus.py b/pocket_friends/development/menus.py deleted file mode 100644 index 6e8d62d..0000000 --- a/pocket_friends/development/menus.py +++ /dev/null @@ -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 diff --git a/pocket_friends/development/sprites.py b/pocket_friends/development/sprites.py new file mode 100644 index 0000000..f172a57 --- /dev/null +++ b/pocket_friends/development/sprites.py @@ -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] diff --git a/pocket_friends/elements/surface.py b/pocket_friends/elements/surface.py index 663f499..cc3a3e0 100644 --- a/pocket_friends/elements/surface.py +++ b/pocket_friends/elements/surface.py @@ -12,6 +12,7 @@ class GameSurface(pygame.Surface): 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. 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): """ @@ -29,8 +30,9 @@ class GameSurface(pygame.Surface): self.game_fps = game_fps self._input_handler = InputHandler(self._clock) 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() def preprocess(self): @@ -39,8 +41,12 @@ class GameSurface(pygame.Surface): """ self._clock.tick(self.game_fps) - self.blit(self._bg, (0, 0)) + self.blit(self.bg, (0, 0)) self.sprites.update() self.sprites.draw(self) self._input_handler.update() + if self._input_handler.dev_found: + self.next_surface = 'dev_menu' + self.dev_override = True + self.running = False diff --git a/pocket_friends/game.py b/pocket_friends/game.py index f4019ca..5b1cd15 100644 --- a/pocket_friends/game.py +++ b/pocket_friends/game.py @@ -3,8 +3,8 @@ import os import pocket_friends import importlib - valid_surfaces = [ + 'dev_menu', 'title', 'egg_select', 'selection_info', @@ -12,9 +12,10 @@ valid_surfaces = [ ] # 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: - 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' # FPS for the game to run at. @@ -63,9 +64,13 @@ def start_game(resolution=240): window.blit(frame, frame.get_rect()) 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 if next_surface not in valid_surfaces: + print(next) next_surface = 'error_screen' surface = surface_modules.get(next_surface).Surface((game_res, game_res), resources_dir, game_fps, **additional_args) diff --git a/pocket_friends/io/input_handler.py b/pocket_friends/io/input_handler.py index e04f7f0..6d13e8c 100644 --- a/pocket_friends/io/input_handler.py +++ b/pocket_friends/io/input_handler.py @@ -1,5 +1,5 @@ import pygame - +from collections import deque class InputHandler: """ @@ -24,6 +24,12 @@ class InputHandler: self.clock = pygame_clock self.tick_check = tick_check 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): """ @@ -37,6 +43,9 @@ class InputHandler: 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.KEYUP, {'key': pressed_button})) + self.dev_check.append(pressed_button) + if len(self.dev_check) > len(self.dev_code): + self.dev_check.popleft() else: pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {'key': pressed_button})) pygame.event.post(pygame.event.Event(pygame.KEYUP, {'key': pressed_button})) @@ -70,3 +79,5 @@ class InputHandler: def update(self): """Run either the GPIO handler or the keyboard handler to check for input and create events.""" self.handle_keyboard() + if self.dev_code == self.dev_check: + self.dev_found = True diff --git a/pocket_friends/resources/images/dev_menu/dev_bg.png b/pocket_friends/resources/images/dev_menu/dev_bg.png new file mode 100644 index 0000000..838cde6 Binary files /dev/null and b/pocket_friends/resources/images/dev_menu/dev_bg.png differ diff --git a/pocket_friends/resources/images/dev_menu/selector.png b/pocket_friends/resources/images/dev_menu/selector.png new file mode 100644 index 0000000..f3f7a62 Binary files /dev/null and b/pocket_friends/resources/images/dev_menu/selector.png differ