diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f21358b --- /dev/null +++ b/.gitignore @@ -0,0 +1,135 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Save file +/save.json + +# pocket-friends compile script +/compile.bat diff --git a/data/__init__.py b/data/__init__.py new file mode 100644 index 0000000..975cbfa --- /dev/null +++ b/data/__init__.py @@ -0,0 +1 @@ +"""Imports classes for running the game.""" diff --git a/data/development/FakeGPIO.py b/data/development/FakeGPIO.py new file mode 100644 index 0000000..c974005 --- /dev/null +++ b/data/development/FakeGPIO.py @@ -0,0 +1,56 @@ +""" +Module used to fake the RPi.GPIO module so that +the game can be run without the actual hardware. +""" + +# Constants used by RPi.GPIO +BOARD = 0 +IN = 0 +FALLING = 0 + + +def setmode(new_mode): + """ + Fake setmode function. + :param new_mode: + """ + pass + + +def setup(channel, mode, initial=None, pull_up_down=None): + """ + Fake setup function. + :param channel: + :param mode: + :param initial: + :param pull_up_down: + """ + pass + + +def add_event_detect(channel, edge_type, callback=None, bouncetime=0): + """ + Fake function to add a non-existent event detect. + :param channel: + :param edge_type: + :param callback: + :param bouncetime: + """ + pass + + +def event_detected(channel): + """ + Fake function to detect an event. Always returns false. + :param channel: + :return: + """ + return False + + +def cleanup(channel=None): + """ + Fake cleanup function. + :param channel: + """ + pass diff --git a/data/development/__init__.py b/data/development/__init__.py new file mode 100644 index 0000000..2b19ec2 --- /dev/null +++ b/data/development/__init__.py @@ -0,0 +1 @@ +"""Initializes all classes needed for the development environment, and faking GPIO inputs.""" diff --git a/data/development/button_test.py b/data/development/button_test.py new file mode 100644 index 0000000..b89813b --- /dev/null +++ b/data/development/button_test.py @@ -0,0 +1,50 @@ +""" +Module to test the GPIO input on the Raspberry Pi. +""" +from collections import deque +from ..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/data/development/dev_menu.py b/data/development/dev_menu.py new file mode 100644 index 0000000..916f663 --- /dev/null +++ b/data/development/dev_menu.py @@ -0,0 +1,154 @@ +""" +Development menu for the game on Raspberry Pi. NOTE: THIS DOES NOTHING ON A COMPUTER! +""" +import data.game +import importlib.util +import os +import pygame +import time +from .button_test import button_test +from .menus import Menu +from ..gpio_handler import GPIOHandler, Constants + +dev_version = '0.0.1' + +try: + importlib.util.find_spec('RPi.GPIO') + import RPi.GPIO as GPIO +except ImportError: + import data.development.FakeGPIO as GPIO + +# Global variable to keep track of the current menu. +menu = 'main' + + +def run_button_test(): + """ + Runs the GPIO button test. + """ + GPIOHandler.teardown() + button_test() + GPIOHandler.setup() + + +def clear_screen(): + """ + Clears the screen. + """ + print("\n" * 20) + + +def start_game(): + """ + Cleans the GPIO and starts the game. + """ + GPIOHandler.teardown() + data.game.main() + pygame.quit() + GPIOHandler.setup() + + +def quit_menu(): + """ + Quits the menu. + """ + exit(0) + + +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 {0}\nGame Version {1}'.format(dev_version, data.game.version)) + 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) diff --git a/data/development/menus.py b/data/development/menus.py new file mode 100644 index 0000000..45cd56b --- /dev/null +++ b/data/development/menus.py @@ -0,0 +1,120 @@ +""" +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].run(*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/data/game.py b/data/game.py new file mode 100644 index 0000000..b45bbba --- /dev/null +++ b/data/game.py @@ -0,0 +1,487 @@ +""" +Main file for the entire game. Controls everything except for GPIO input. +""" +from collections import deque +import importlib.util +import json +import os +import pygame +from pygame.locals import * +from .gpio_handler import Constants, GPIOHandler + +version = '0.0.1' +game_fps = 16 + + +class FileHandler: + """ + Class that handles the game attributes and save files. + """ + + def __init__(self): + # Attributes that are saved to a file to recover upon startup. + self.attributes = { + 'time_elapsed': 0, + 'age': 0, + 'health': 0, + 'hunger': 0, + 'happiness': 0, + 'evolution_stage': -1, + } + + def write_save(self): + """ + Writes attributes of class to "save.json" file. + """ + with open('save.json', 'w') as save_file: + json.dump(self.attributes, save_file) + save_file.close() + + def read_save(self): + """ + Reads from "save.json" and inserts into attributes dictionary. Creates file if it does not exist. + """ + # Open up the save file and read it into self.attributes. + try: + with open('save.json', 'r') as save_file: + self.attributes = json.load(save_file) + save_file.close() + + # If there is no save file, write one with the defaults. + except FileNotFoundError: + self.write_save() + + +class PlaygroundFriend(pygame.sprite.Sprite): + """ + Class for the sprite of the creature on the main playground. + """ + + def __init__(self): + pygame.sprite.Sprite.__init__(self) + + +class SelectionEgg(pygame.sprite.Sprite): + """ + Class for the eggs on the egg selection screen. + """ + + def __init__(self, egg_color): + pygame.sprite.Sprite.__init__(self) + + image_directory = 'resources/images/egg_images/{0}'.format(egg_color) + + # Load the egg from the given color and get the bounding rectangle for the image. + self.images = [] + for filename in os.listdir(image_directory): + self.images.append(pygame.image.load(image_directory + '/' + filename)) + + self.rect = self.images[0].get_rect() + self.index = 0 + self.image = self.images[self.index] + + self.animation_frames = game_fps / len(self.images) + self.current_frame = 0 + + def update_frame_dependent(self): + """ + Updates the image of Sprite every 6 frame (approximately every 0.1 second if frame rate is 60). + """ + + self.current_frame += 1 + if self.current_frame >= self.animation_frames: + self.current_frame = 0 + self.index = (self.index + 1) % len(self.images) + self.image = self.images[self.index] + + def update(self): + """This is the method that's being called when 'all_sprites.update(dt)' is called.""" + self.update_frame_dependent() + + +# Makes Pygame draw on the display of the RPi. +os.environ["SDL_FBDEV"] = "/dev/fb1" + +# Useful for debugging on the PC. Imports a fake RPi.GPIO library if one is not found (which it can't +# be on a PC, RPi.GPIO cannot be installed outside of a Raspberry Pi. +try: + importlib.util.find_spec('RPi.GPIO') + import RPi.GPIO as GPIO +except ImportError: + import data.development.FakeGPIO as GPIO + + +def game(): + """ + Starts the game. + """ + pygame.init() + + # Hide the cursor for the Pi display. + pygame.mouse.set_visible(False) + + # The game is normally rendered at 80 pixels and upscaled from there. If changing displays, change the + # screen_size to reflect what the resolution of the new display is. + rendered_size = 80 + screen_size = 800 + + window = pygame.display.set_mode((screen_size, screen_size)) + surface = pygame.Surface((rendered_size, rendered_size)) + + # Only really useful for PCs. Does nothing on the Raspberry Pi. + pygame.display.set_caption('Pocket Friends') + + clock = pygame.time.Clock() + + # Font used for small text in the game. Bigger text is usually image files. + small_font = pygame.font.Font('resources/fonts/5Pts5.ttf', 10) + + # Default game state when the game first starts. + game_state = 'title' + running = True + file_handler = FileHandler() + + # A group of all the sprites on screen. Used to update all sprites at onc + all_sprites = pygame.sprite.Group() + + # Start the GPIO handler to take in buttons from the RPi HAT. + GPIOHandler.setup() + + # Dev code used to exit the game. Default Down, Down, Up, Up, Down, Down, Up, Up, A, A, B + dev_code = deque() + for button in [Constants.buttons.get('j_d'), Constants.buttons.get('j_d'), Constants.buttons.get('j_u'), + Constants.buttons.get('j_u'), Constants.buttons.get('j_d'), Constants.buttons.get('j_d'), + Constants.buttons.get('j_u'), Constants.buttons.get('j_u'), Constants.buttons.get('a'), + Constants.buttons.get('a'), Constants.buttons.get('b')]: + dev_code.append(button) + + # Log of the inputs. + input_log = deque() + + # Time since last input. Used to help regulate double presses of buttons. + last_input_tick = 0 + + def draw(): + """ + Draws the main pygame display. + """ + + # Draws all the sprites on screen and scales the screen to the correct size from the rendered size. + all_sprites.update() + all_sprites.draw(surface) + frame = pygame.transform.scale(surface, (screen_size, screen_size)) + window.blit(frame, frame.get_rect()) + + # Update the entire display. + pygame.display.flip() + + def draw_bg(): + """ + Draws the main game background image onto a given surface. + """ + bg_image = pygame.image.load('resources/images/bg.png').convert() + surface.blit(bg_image, (0, 0)) + + def log_button(pressed_button): + """ + Logs the button presses to register the dev code. + :param pressed_button: The button code to be logged + """ + input_log.append(pressed_button) + if len(input_log) > len(dev_code): + input_log.popleft() + + def create_event(pressed_button): + """ + Creates a pygame event with a given keyboard code + :param pressed_button: + """ + nonlocal last_input_tick + # Register a button click so long as the last button click happened no less than two frames ago + if pygame.time.get_ticks() - last_input_tick > clock.get_time() * 2: + pygame.event.post(pygame.event.Event(KEYDOWN, {'key': pressed_button})) + pygame.event.post(pygame.event.Event(KEYUP, {'key': pressed_button})) + log_button(pressed_button) + last_input_tick = pygame.time.get_ticks() + + def check_dev_code(): + """ + Checks if the dev code has been entered. If it has, stop the program. + """ + nonlocal running + + if dev_code == input_log: + running = False + + def handle_gpio(): + """ + Handles getting GPIO button presses and making a pygame event when a press is detected. + """ + for pressed_button in Constants.buttons: + code = Constants.buttons.get(pressed_button) + + # Check if a button has been pressed. If it has, create a pygame event for it. + if GPIOHandler.get_press(code): + create_event(code) + + def keyboard_handler(): + """ + Simulates key presses to GPIO button presses. Also handles quitting the game. + """ + nonlocal running + + # Checks if a corresponding keyboard key has been pressed. If it has, emulate a button press. + for keyboard_event in pygame.event.get(): + if keyboard_event.type == pygame.QUIT: + running = False + if keyboard_event.type == pygame.KEYDOWN: + if keyboard_event.key == pygame.K_a: + create_event(Constants.buttons.get('a')) + if keyboard_event.key == pygame.K_b: + create_event(Constants.buttons.get('b')) + if keyboard_event.key == pygame.K_PERIOD: + create_event(Constants.buttons.get('j_i')) + if keyboard_event.key == pygame.K_RIGHT: + create_event(Constants.buttons.get('j_r')) + if keyboard_event.key == pygame.K_LEFT: + create_event(Constants.buttons.get('j_l')) + if keyboard_event.key == pygame.K_DOWN: + create_event(Constants.buttons.get('j_d')) + if keyboard_event.key == pygame.K_UP: + create_event(Constants.buttons.get('j_u')) + if keyboard_event.key == pygame.K_ESCAPE: + running = False + + def pre_handler(): + """ + Runs at the beginning of each loop, handles drawing the background, controlling game speed, and + controlling the GPIO button inputs and keyboard handler + """ + # Regulate the speed of the game. + clock.tick(game_fps) + + # Handle all inputs for both debugging and real GPIO button presses. + keyboard_handler() + handle_gpio() + check_dev_code() + + # Draw the background. + draw_bg() + + while running: + if game_state == 'title': + all_sprites.empty() + pre_handler() + + # Draw the title image in the middle of the screen. + title_image = pygame.image.load('resources/images/title.png').convert_alpha() + surface.blit(title_image, (0, 0)) + draw() + + # Show the title for 1 second then move on to the initialization phase of the game. + pygame.time.wait(1000) + game_state = 'init' + + elif game_state == 'playground': + all_sprites.empty() + game_state = None # Playground currently not implemented, send to error screen. + + elif game_state == 'init': + all_sprites.empty() + pre_handler() + draw() + + # Read the save file. + file_handler.read_save() + + # Determines if it is a new game or not by looking at the evolution stage. If it is -1, the egg has + # not been created yet, and the game sends you to the egg selection screen. If not, the game sends + # you to the playground. + if file_handler.attributes['evolution_stage'] == -1: + game_state = 'egg_select' + else: + game_state = 'playground' + + elif game_state == 'egg_select': + + # Submenu used within the egg selection menu. + submenu = 'main' + + selected = 0 + + while running and game_state == 'egg_select': + all_sprites.empty() + if submenu == 'main': + + # Creates and holds the egg objects in a list. + eggs = [SelectionEgg('red'), SelectionEgg('orange'), SelectionEgg('yellow'), + SelectionEgg('green'), + SelectionEgg('blue'), SelectionEgg('indigo'), SelectionEgg('violet'), SelectionEgg('white'), + SelectionEgg('rainbow')] + + # How many eggs per row should be displayed. + eggs_per_row = 3 + distance_between_eggs = 36 / eggs_per_row + + # Count the total rows. + total_rows = -(-len(eggs) // eggs_per_row) + distance_between_rows = 32 / eggs_per_row + + # Determine the location of each egg. + for egg in eggs: + current_row = eggs.index(egg) // eggs_per_row + rows_after = total_rows - (current_row + 1) + egg_in_row = eggs.index(egg) % eggs_per_row + eggs_after = min(len(eggs) - (current_row * eggs_per_row), eggs_per_row) - (egg_in_row + 1) + + x_offset = 32 + y_offset = 30 + + # The x coordinate of an egg is determined by which egg in the row it is, and how many eggs + # are in that row. If there is only 1 egg in a row, it is in the middle of the screen. If + # there are two, they're on equal halves and so on. + x = x_offset - (eggs_after * distance_between_eggs) + (egg_in_row * distance_between_eggs) + y = y_offset - (rows_after * distance_between_rows) + (current_row * distance_between_rows) + + egg.rect.x = x + egg.rect.y = y + + # Add the egg to the sprite list. + all_sprites.add(egg) + selected = 0 + + def get_cursor_coords(selection): + """ + Gets the coordinates of an egg on the selection screen by index and returns it as a tuple + :param selection: index of the egg to be selected + :return: tuple of the coordinates of the selected egg + """ + cursor_x_offset = -2 + cursor_y_offset = -2 + + return eggs[selection].rect.x + cursor_x_offset, eggs[selection].rect.y + cursor_y_offset + + def sel_left(): + """ + Select the egg to the left with constraints. + """ + nonlocal selected + + if selected % eggs_per_row != 0: + selected -= 1 + + def sel_right(): + """ + Select the egg to the right with constraints. + """ + nonlocal selected + + row = selected // eggs_per_row + eggs_in_row = min(len(eggs) - (row * eggs_per_row), eggs_per_row) + + if selected % eggs_per_row != eggs_in_row - 1: + selected += 1 + + def sel_up(): + """ + Select the egg above with constraints. + """ + nonlocal selected + + if selected // eggs_per_row != 0: + selected -= eggs_per_row + + def sel_down(): + """ + Select the egg below with constraints. + """ + nonlocal selected + + if selected // eggs_per_row != total_rows - 1: + selected += eggs_per_row + + while running and game_state == 'egg_select' and submenu == 'main': + + pre_handler() + + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + if event.key == Constants.buttons.get('j_r'): + sel_right() + if event.key == Constants.buttons.get('j_l'): + sel_left() + if event.key == Constants.buttons.get('j_d'): + sel_down() + if event.key == Constants.buttons.get('j_u'): + sel_up() + if event.key == Constants.buttons.get('a'): + # Advance to the egg info screen for the selected egg. + submenu = 'egg_info' + + # Draws the cursor on screen. + cursor = pygame.image.load('resources/images/clock_selector.png').convert_alpha() + surface.blit(cursor, get_cursor_coords(selected)) + + draw() + + elif submenu == 'egg_info': + while running and game_state == 'egg_select' and submenu == 'egg_info': + pre_handler() + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + if event.key == Constants.buttons.get('a'): + # Go to an invalid game state if continuing. + game_state = None + if event.key == Constants.buttons.get('b'): + # Go back to the egg selection screen. + submenu = 'main' + + # Quick debugging for which egg is selected. + selection_debug = small_font.render('Egg {0}'.format(selected), False, (64, 64, 64)) + surface.blit(selection_debug, (5, 35)) + + draw() + + else: # Go to the error state if an invalid state is set. + game_state = None + + else: + # Error screen. This appears when an invalid game state has been selected. + + all_sprites.empty() + frames_passed = 0 # Counter for frames, helps ensure the game isnt frozen. + + while running and game_state != 'title': + + pre_handler() + + # Draw the error screen + error_screen = pygame.image.load('resources/images/debug/invalid.png').convert_alpha() + surface.blit(error_screen, (0, -8)) + + # Counts the frames passed. Resets every second. + frames_passed += 1 + if frames_passed >= game_fps: + frames_passed = 0 + + # Draws the frame counter. + frame_counter = small_font.render('frames: {0}'.format(frames_passed), False, (64, 64, 64)) + surface.blit(frame_counter, (1, 70)) + + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + if event.key == Constants.buttons.get('b'): + # Reset back to the title screen. + game_state = 'title' + + draw() + + +def main(): + """ + Calls the game() function to start the game. + """ + game() + + GPIOHandler.teardown() + pygame.quit() diff --git a/data/gpio_handler.py b/data/gpio_handler.py new file mode 100644 index 0000000..185df5a --- /dev/null +++ b/data/gpio_handler.py @@ -0,0 +1,71 @@ +""" +Module that helps with the handling of taking inputs from the GPIO pins on the Raspberry +Pi and converting them to events to be used in other places (pygame, etc.) +""" +import importlib.util + +try: + importlib.util.find_spec('RPi.GPIO') + import RPi.GPIO as GPIO +except ImportError: + import data.development.FakeGPIO as GPIO + + +class Constants: + """ + Contains the constants used by the HAT to read in buttons + """ + buttons = { + 'a': 31, # A button + 'b': 29, # B button + 'j_i': 7, # Joystick in + 'j_u': 11, # Joystick up + 'j_d': 15, # Joystick down + 'j_l': 13, # Joystick left + 'j_r': 16 # Joystick right + } + + +class GPIOHandler: + """ + Class to handle the GPIO inputs from the buttons. + """ + + @staticmethod + def setup(): + """ + Primes the GPIO pins for reading the inputs of the buttons. + """ + GPIO.setmode(GPIO.BOARD) + + GPIO.setup(Constants.buttons.get('a'), GPIO.IN) + GPIO.setup(Constants.buttons.get('b'), GPIO.IN) + GPIO.setup(Constants.buttons.get('j_i'), GPIO.IN) + GPIO.setup(Constants.buttons.get('j_u'), GPIO.IN) + GPIO.setup(Constants.buttons.get('j_d'), GPIO.IN) + GPIO.setup(Constants.buttons.get('j_l'), GPIO.IN) + GPIO.setup(Constants.buttons.get('j_r'), GPIO.IN) + + GPIO.add_event_detect(Constants.buttons.get('a'), GPIO.FALLING) + GPIO.add_event_detect(Constants.buttons.get('b'), GPIO.FALLING) + GPIO.add_event_detect(Constants.buttons.get('j_i'), GPIO.FALLING) + GPIO.add_event_detect(Constants.buttons.get('j_u'), GPIO.FALLING) + GPIO.add_event_detect(Constants.buttons.get('j_d'), GPIO.FALLING) + GPIO.add_event_detect(Constants.buttons.get('j_l'), GPIO.FALLING) + GPIO.add_event_detect(Constants.buttons.get('j_r'), GPIO.FALLING) + + @staticmethod + def teardown(): + """ + Cleans up the GPIO handler. + """ + GPIO.cleanup() + + @staticmethod + def get_press(button): + """ + Returns true if a button has moved from not pressed to pressed. + :param button: button to be detected + :return: True if the button is has been pressed, False otherwise + """ + return GPIO.event_detected(button) diff --git a/pocket_friends.py b/pocket_friends.py new file mode 100644 index 0000000..3ee8203 --- /dev/null +++ b/pocket_friends.py @@ -0,0 +1,25 @@ +""" +Launch script for Pocket Friends. +""" +import pygame +import sys +from data.game import main as game_main +from data.development.dev_menu import main as dev_menu_main + +enable_dev = False + +if __name__ == '__main__': + + # enable dev mode if --dev argument is passed + if len(sys.argv) > 0: + for args in sys.argv: + if args == '--dev': + enable_dev = True + + if not enable_dev: + game_main() + else: + dev_menu_main() + + pygame.quit() + sys.exit() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..125fed6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pygame==1.9.4 \ No newline at end of file diff --git a/resources/fonts/5Pts5.ttf b/resources/fonts/5Pts5.ttf new file mode 100644 index 0000000..3ae64c5 Binary files /dev/null and b/resources/fonts/5Pts5.ttf differ diff --git a/resources/images/bg.png b/resources/images/bg.png new file mode 100644 index 0000000..bd437af Binary files /dev/null and b/resources/images/bg.png differ diff --git a/resources/images/clock_selector.png b/resources/images/clock_selector.png new file mode 100644 index 0000000..7cb48e2 Binary files /dev/null and b/resources/images/clock_selector.png differ diff --git a/resources/images/debug/invalid.png b/resources/images/debug/invalid.png new file mode 100644 index 0000000..6a2b0ec Binary files /dev/null and b/resources/images/debug/invalid.png differ diff --git a/resources/images/egg_images/blue/blue.png b/resources/images/egg_images/blue/blue.png new file mode 100644 index 0000000..9656daf Binary files /dev/null and b/resources/images/egg_images/blue/blue.png differ diff --git a/resources/images/egg_images/dev_egg/dev_egg_0.png b/resources/images/egg_images/dev_egg/dev_egg_0.png new file mode 100644 index 0000000..20679ad Binary files /dev/null and b/resources/images/egg_images/dev_egg/dev_egg_0.png differ diff --git a/resources/images/egg_images/dev_egg/dev_egg_1.png b/resources/images/egg_images/dev_egg/dev_egg_1.png new file mode 100644 index 0000000..29bcad4 Binary files /dev/null and b/resources/images/egg_images/dev_egg/dev_egg_1.png differ diff --git a/resources/images/egg_images/green/green.png b/resources/images/egg_images/green/green.png new file mode 100644 index 0000000..510b163 Binary files /dev/null and b/resources/images/egg_images/green/green.png differ diff --git a/resources/images/egg_images/indigo/indigo.png b/resources/images/egg_images/indigo/indigo.png new file mode 100644 index 0000000..f57734f Binary files /dev/null and b/resources/images/egg_images/indigo/indigo.png differ diff --git a/resources/images/egg_images/orange/orange.png b/resources/images/egg_images/orange/orange.png new file mode 100644 index 0000000..1a97b0e Binary files /dev/null and b/resources/images/egg_images/orange/orange.png differ diff --git a/resources/images/egg_images/rainbow/rainbow.png b/resources/images/egg_images/rainbow/rainbow.png new file mode 100644 index 0000000..92291ec Binary files /dev/null and b/resources/images/egg_images/rainbow/rainbow.png differ diff --git a/resources/images/egg_images/red/red.png b/resources/images/egg_images/red/red.png new file mode 100644 index 0000000..3a3026e Binary files /dev/null and b/resources/images/egg_images/red/red.png differ diff --git a/resources/images/egg_images/violet/violet.png b/resources/images/egg_images/violet/violet.png new file mode 100644 index 0000000..aea5d8c Binary files /dev/null and b/resources/images/egg_images/violet/violet.png differ diff --git a/resources/images/egg_images/white/white.png b/resources/images/egg_images/white/white.png new file mode 100644 index 0000000..6f33f7a Binary files /dev/null and b/resources/images/egg_images/white/white.png differ diff --git a/resources/images/egg_images/yellow/yellow.png b/resources/images/egg_images/yellow/yellow.png new file mode 100644 index 0000000..5dd7fbb Binary files /dev/null and b/resources/images/egg_images/yellow/yellow.png differ diff --git a/resources/images/icon.png b/resources/images/icon.png new file mode 100644 index 0000000..33f5bf3 Binary files /dev/null and b/resources/images/icon.png differ diff --git a/resources/images/title.png b/resources/images/title.png new file mode 100644 index 0000000..6f1d693 Binary files /dev/null and b/resources/images/title.png differ