diff --git a/README.md b/README.md index 432bb01..ad0c579 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ There are currently no releases of the game. To install the current version on G ## Installing From Source Requirements: -- Python 3.10 or greater +- Python 3.7 - Pip - Git diff --git a/compile.py b/compile.py index c46b9b8..47c4954 100644 --- a/compile.py +++ b/compile.py @@ -4,7 +4,7 @@ import pocket_friends script_dir = os.path.dirname(os.path.abspath(__file__)) -PyInstaller.__main__.run([ +PyInstaller.__main__.preprocess([ '{0}/pocket_friends/__main__.py'.format(script_dir), '--clean', '--noconsole', diff --git a/pocket_friends/__init__.py b/pocket_friends/__init__.py index c8ad717..cb23a34 100644 --- a/pocket_friends/__init__.py +++ b/pocket_friends/__init__.py @@ -1 +1,4 @@ -__version__ = 'dev_0.0.3' +"""Pocket Friends is a game where you raise your own little pocket friend! These pocket friends, called bloops, +are great little companions to have! You can feed them, play with them, and watch them grow up!""" + +__version__ = 'dev_0.0.4' diff --git a/pocket_friends/__main__.py b/pocket_friends/__main__.py index 8bc143a..c742d8a 100644 --- a/pocket_friends/__main__.py +++ b/pocket_friends/__main__.py @@ -2,28 +2,25 @@ Launch script for Pocket Friends. """ import os -from pathlib import Path import pygame import sys -from pocket_friends.game_files.game import main as game_main -from pocket_friends.development.dev_menu import main as dev_menu_main +from pathlib import Path +import pocket_friends.game as game if __name__ == '__main__': enable_dev = False + resolution = 240 # 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 args == '--delete-save': + for arg in sys.argv: + if arg == '--delete-save': save_dir = os.path.join(Path.home(), '.pocket_friends') os.remove(save_dir + '/save.json') + if '--res=' in arg: + resolution = int(arg.split('=')[1]) - if not enable_dev: - game_main() - else: - dev_menu_main() + game.start_game(resolution) pygame.quit() sys.exit() diff --git a/pocket_friends/development/FakeGPIO.py b/pocket_friends/development/FakeGPIO.py deleted file mode 100644 index 23a82c6..0000000 --- a/pocket_friends/development/FakeGPIO.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Module used to fake the RPi.GPIO module so that -the hardware 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/pocket_friends/development/button_test.py b/pocket_friends/development/button_test.py index f9c7ee5..df5353c 100644 --- a/pocket_friends/development/button_test.py +++ b/pocket_friends/development/button_test.py @@ -2,7 +2,7 @@ Module to test the GPIO input on the Raspberry Pi. """ from collections import deque -from ..hardware.gpio_handler import Constants, GPIOHandler +from pocket_friends.game_files.io.gpio_handler import Constants, GPIOHandler def button_test(): diff --git a/pocket_friends/development/dev_menu.py b/pocket_friends/development/dev_menu.py index 24e2fe4..9dead9b 100644 --- a/pocket_friends/development/dev_menu.py +++ b/pocket_friends/development/dev_menu.py @@ -8,13 +8,13 @@ import pygame import time from .button_test import button_test from .menus import Menu -from ..hardware.gpio_handler import GPIOHandler, Constants +from pocket_friends.game_files.io.gpio_handler import GPIOHandler, Constants try: importlib.util.find_spec('RPi.GPIO') import RPi.GPIO as GPIO except ImportError: - import pocket_friends.development.FakeGPIO as GPIO + import pocket_friends.game_files.io.fake_gpio as GPIO # Global variable to keep track of the current menu. menu = 'main' diff --git a/pocket_friends/development/menus.py b/pocket_friends/development/menus.py index 45cd56b..6e8d62d 100644 --- a/pocket_friends/development/menus.py +++ b/pocket_friends/development/menus.py @@ -63,7 +63,7 @@ class Menu: :param kwargs: keyword arguments to be passed to the function """ try: - return self._options[self._selection].run(*args, **kwargs) + 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 diff --git a/pocket_friends/elements/__init__.py b/pocket_friends/elements/__init__.py new file mode 100644 index 0000000..7f96f12 --- /dev/null +++ b/pocket_friends/elements/__init__.py @@ -0,0 +1 @@ +"""Submodule for use in the game. Helps with the drawing of various objects such as sprites and text boxes.""" diff --git a/pocket_friends/elements/sprites.py b/pocket_friends/elements/sprites.py new file mode 100644 index 0000000..1752dab --- /dev/null +++ b/pocket_friends/elements/sprites.py @@ -0,0 +1,277 @@ +import pygame +import json + + +class SpriteSheet: + """ + Class to be used by sprites in order to give them a texture and an animation. + + Attributes: + images (list) List of all the sprites in the animation separated from the sprite sheet. + """ + + def __init__(self, sprite_sheet, texture_json): + """ + Creates a sprite sheet given a sprite image and its corresponding JSON file. + Args: + sprite_sheet (str): The path of the sprite sheet image component. + texture_json (str): The path of the sprite sheet JSON component, contains the number of frames in the + sprite sheet, and the width and height of an individual sprite from the sprite sheet. + """ + # Load in whole sprite sheet as one image. + sprite_sheet = pygame.image.load(sprite_sheet).convert_alpha() + self.images = [] + + # Get the sprite sheet json file. + with open(texture_json, 'r') as json_file: + img_attrib = json.load(json_file) + json_file.close() + + # Count for how many images have been added in the image list + image_count = 0 + + # Get the sprite size as a tuple + sprite_size = img_attrib['width'], img_attrib['height'] + + # Iterate through every image location on the sprite sheet given the sprite size + for i in range(sprite_sheet.get_size()[1] // sprite_size[1]): + i *= sprite_size[1] + for j in range(sprite_sheet.get_size()[0] // sprite_size[0]): + j *= sprite_size[0] + + # Create a new transparent surface + sprite = pygame.Surface(sprite_size, pygame.SRCALPHA) + # Blit the sprite onto the image + sprite.blit(sprite_sheet, (0, 0), (j, i, sprite_size[0], sprite_size[1])) + # Add the image to the list of images + self.images.append(sprite) + + image_count += 1 + + # Break the loop if the specified number of frames has been reached. + if image_count >= img_attrib['frames']: + break + if image_count >= img_attrib['frames']: + break + + +class SelectionEgg(pygame.sprite.Sprite): + """ + Sprite to render the egg on the egg selection screen. + + Attributes: + egg_color (str): The color of the egg (also its name). + description (str): The description of the egg to be displayed when selected. + contentedness (int): How likely the egg is to stay happy, ranges from 0-5. + metabolism (int): How quickly the egg will get hungry, ranges from 0-5. + rect (pygame.Rect): Pygame rectangle used to position the egg on screen. + + """ + + def __init__(self, egg_color, resources_dir): + """ + Creates a SelectionEgg object given an egg color and a resource location. + Args: + egg_color (str): The color egg that should be rendered. + resources_dir (str): The path of the resources directory. + """ + pygame.sprite.Sprite.__init__(self) + + self.egg_color = egg_color + + # Loads the JSON file of the egg to read in data. + with open(resources_dir + '/data/bloop_info/{0}.json'.format(egg_color), 'r') as save_file: + json_file = json.load(save_file) + save_file.close() + + # Gets the description off the egg from the JSON file. + self.description = json_file.get('description') + self.contentedness = json_file.get('contentedness') + self.metabolism = json_file.get('metabolism') + + # Load the egg from the given color and get the bounding rectangle for the image. + sprite_sheet = SpriteSheet(resources_dir + '/images/bloops/{0}/egg.png'.format(self.egg_color), + resources_dir + '/images/bloops/{0}/egg.json'.format(self.egg_color)) + self._images = sprite_sheet.images + + # Get the rectangle from the first image in the list + self.rect = self._images[0].get_rect() + self._index = 0 + self.image = self._images[self._index] + + def update(self): + """ + Update the sprite to the next animation frame. + """ + # Animate the sprite + self._index = (self._index + 1) % len(self._images) + self.image = self._images[self._index] + + +class InfoText: + """ + Class for drawing large amounts of text on the screen at a time + """ + + def __init__(self, resources_dir, game_res, text='Test text.'): + """ + Creates an InfoText object to be used on a surface. + Args: + resources_dir (str): The full path of the game's resources directory + game_res (int): The internal resolution of the game. Used for correct scaling. + text (:obj:`str`, optional): The given text to render. Defaults to "Test text."' + """ + + self.font = pygame.font.Font(resources_dir + '/fonts/5Pts5.ttf', 10) + self.text = [] # Text broken up into a list according to how it will fit on screen. + self.max_lines = 6 # Max number of lines to be shown on screen at a time. + self.offset = 0 + self.game_res = game_res + + # Arrow icons to indicate scrolling + self.up_arrow = pygame.image.load(resources_dir + '/images/gui/up_arrow.png').convert_alpha() + self.down_arrow = pygame.image.load(resources_dir + '/images/gui/down_arrow.png').convert_alpha() + + raw_text = text # Copy the text to a different variable to be cut up. + + margins = 4.5 + max_line_width = self.game_res - (margins * 2) # The maximum pixel width that drawn text can be. + cut_chars = '.,! ' # Characters that will be considered "cuts" aka when a line break can occur. + + # Prevents freezing if the end of the string does not end in a cut character + # Will fix eventually more elegantly + if raw_text[-1:] not in cut_chars: + raw_text += ' ' + + # Calculating line breaks. + while len(raw_text) > 0: + index = 0 + test_text = '' # Chunk of text to pseudo-render and test the width of. + + # Loops until the testing text has reached the size limit. + while True: + + # Break if the current index is larger than the remaining text. + if index + 1 > len(raw_text): + index -= 1 + break + + # Add one character to the testing text from the raw text. + test_text += raw_text[index] + # Get the width of the pseudo-rendered text. + text_width = self.font.size(test_text)[0] + + # Break if the text is larger than the defined max width. + if text_width > max_line_width: + break + index += 1 + pass + + # Gets the chunk of text to be added to the list. + text_chunk = raw_text[0:index + 1] + # Determines if the chunk of text has any break characters. + has_breaks = any(cut_chars in text_chunk for cut_chars in cut_chars) + + # If the text has break characters, start with the last character and go backwards until + # one has been found, decreasing the index each time. + if has_breaks: + while raw_text[index] not in cut_chars: + index -= 1 + text_chunk = raw_text[0:index + 1] + # If there are no break characters in the chunk, simply decrease the index by one and insert + # a dash at the end of the line to indicate the word continues. + else: + index -= 1 + text_chunk = raw_text[0:index + 1] + text_chunk += '-' + + # Append the text chunk to the list of text to draw. + self.text.append(text_chunk) + + # Cut the text to repeat the process with the new cut string. + raw_text = raw_text[index + 1:] + + def draw(self, surface): + """ + Draw the text on a given surface. + Args: + surface (:obj:`pygame.Surface`): The surface to draw the text on + """ + # Constants to help draw the text + line_separation = 7 + left_margin = 3 + top_margin = 25 + bottom_margin = 10 + + # Draw the lines on the screen + for i in range(min(len(self.text), self.max_lines)): + text = self.font.render(self.text[i + self.offset], False, (64, 64, 64)) + surface.blit(text, (left_margin, top_margin + (i * line_separation))) + + # Draw the arrows if there is more text than is on screen. + if self.offset != 0: + surface.blit(self.up_arrow, ((self.game_res / 2) - (self.up_arrow.get_rect().width / 2), top_margin - 3)) + if len(self.text) - (self.offset + 1) >= self.max_lines: + surface.blit(self.down_arrow, + ((self.game_res / 2) - (self.down_arrow.get_rect().width / 2), self.game_res - bottom_margin)) + + def scroll_down(self): + """ + Scrolls the text on the screen down. + """ + # Ensures that the offset cannot be too big as to try to render non-existent lines. + if len(self.text) - (self.offset + 1) >= self.max_lines: + self.offset += 1 + + def scroll_up(self): + """ + Scrolls the text on the screen up. + """ + if self.offset > 0: # Ensures a non-zero offset is not possible. + self.offset -= 1 + + +class EggInfo: + """ + Class to draw the contentedness and metabolism value off the egg on the info screen. + """ + + def __init__(self, resources_dir, contentedness, metabolism, location): + self.contentedness = contentedness + self.metabolism = metabolism + self.x = location[0] + self.y = location[1] + + # Create a new surface to blit onto the other surface + self.surface = pygame.Surface((44, 15), pygame.SRCALPHA) + + # Blit the two indicator icons on screen + smiley = pygame.image.load(resources_dir + '/images/gui/smiley.png').convert_alpha() + self.surface.blit(smiley, (0, 0)) + apple = pygame.image.load(resources_dir + '/images/gui/apple.png').convert_alpha() + self.surface.blit(apple, (1, 9)) + + # Draw 5 stars. If the value of the contentedness is less than the current star, make it a blank star. + for i in range(5): + if i < self.contentedness: + star = pygame.image.load(resources_dir + '/images/gui/star.png').convert_alpha() + else: + star = pygame.image.load(resources_dir + '/images/gui/blank_star.png').convert_alpha() + self.surface.blit(star, (11 + (i * 6), 1)) + + # Draw 5 stars. If the value of the metabolism is less than the current star, make it a blank star. + for i in range(5): + if i < self.metabolism: + star = pygame.image.load(resources_dir + '/images/gui/star.png').convert_alpha() + else: + star = pygame.image.load(resources_dir + '/images/gui/blank_star.png').convert_alpha() + self.surface.blit(star, (11 + (i * 6), 10)) + + def draw(self, surface): + """ + Draw the info icons on a given surface. + Args: + surface (:obj:`pygame.Surface`): The surface to draw the text on + """ + # Blit the info onto the given surface. + surface.blit(self.surface, (self.x, self.y)) diff --git a/pocket_friends/elements/surface.py b/pocket_friends/elements/surface.py new file mode 100644 index 0000000..663f499 --- /dev/null +++ b/pocket_friends/elements/surface.py @@ -0,0 +1,46 @@ +"""Module to aid in the drawing of surfaces to make switching from one screen to another easier.""" +import pygame +from ..io.input_handler import InputHandler + + +class GameSurface(pygame.Surface): + """ + Class to be used as the backbone of all game surfaces. + Attributes: + running (bool): Boolean to tell whether the surface is running or not. + next_surface (:obj:`str`): What the next surface should be after halting. + 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. + """ + def __init__(self, game_res, resources_dir, game_fps): + """ + Create a GameSurface object. + Args: + game_res (int): The internal resolution of the surface. + resources_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. + """ + super().__init__(game_res, pygame.SRCALPHA) + self.running = True + self.next_surface = None + self.resource_dir = resources_dir + self._clock = pygame.time.Clock() + self.game_fps = game_fps + self._input_handler = InputHandler(self._clock) + self.additional_args = {} + + self._bg = pygame.image.load(self.resource_dir + '/images/bg.png').convert_alpha() + self.sprites = pygame.sprite.Group() + + def preprocess(self): + """ + Advance the surface by one frame and draw the background. + """ + self._clock.tick(self.game_fps) + + self.blit(self._bg, (0, 0)) + self.sprites.update() + self.sprites.draw(self) + + self._input_handler.update() diff --git a/pocket_friends/game.py b/pocket_friends/game.py new file mode 100644 index 0000000..f4019ca --- /dev/null +++ b/pocket_friends/game.py @@ -0,0 +1,74 @@ +import pygame +import os +import pocket_friends +import importlib + + +valid_surfaces = [ + 'title', + 'egg_select', + 'selection_info', + 'error_screen' +] + +# Add all the surface modules to a dictionary for easy switching +surface_modules = {} +for module in valid_surfaces: + surface_modules[module] = importlib.import_module('pocket_friends.surfaces.{0}'.format(module)) +starting_surface = 'title' + +# FPS for the game to run at. +game_fps = 16 +# The internal resolution of the game +game_res = 80 + +# Get the path for where all game resources are (images, fonts, sounds, etc.) +script_dir = os.path.dirname(os.path.abspath(__file__)) +resources_dir = script_dir + '/resources' + +# Makes Pygame draw on the display of the RPi. +os.environ['SDL_FBDEV'] = '/dev/fb1' + + +def start_game(resolution=240): + """ + Starts the game. + + Args: + resolution (int, optional): Resolution to display the game at. Defaults to 240. + """ + + pygame.init() + + # Hide the cursor for the Pi display. + pygame.mouse.set_visible(False) + + window = pygame.display.set_mode((resolution, resolution)) + surface = surface_modules.get(starting_surface).Surface((game_res, game_res), resources_dir, game_fps) + + # Only really useful for PCs. Does nothing on the Raspberry Pi. + pygame.display.set_caption('Pocket Friends {0}'.format(pocket_friends.__version__)) + + # Add an icon to the pygame window. + icon = pygame.image.load(resources_dir + '/icon/icon.png').convert_alpha() + pygame.display.set_icon(icon) + + running = True + + while running: + surface.update() + + # The game is only 80x80px, however it is upscaled to whatever the running resolution is. + frame = pygame.transform.scale(surface, (resolution, resolution)) + window.blit(frame, frame.get_rect()) + + if not surface.running: + next_surface = surface.next_surface + additional_args = surface.additional_args + if next_surface not in valid_surfaces: + next_surface = 'error_screen' + surface = surface_modules.get(next_surface).Surface((game_res, game_res), resources_dir, + game_fps, **additional_args) + pygame.display.flip() + + pygame.quit() diff --git a/pocket_friends/game_files/__init__.py b/pocket_friends/game_files/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pocket_friends/game_files/game.py b/pocket_friends/game_files/game.py deleted file mode 100644 index 5d4a8f5..0000000 --- a/pocket_friends/game_files/game.py +++ /dev/null @@ -1,984 +0,0 @@ -""" -Main file for the entire game. Controls everything except for GPIO input. -""" -from collections import deque -import importlib.util -import json -import os -from pathlib import Path -import pocket_friends -import pygame -from pygame.locals import * -from ..hardware.gpio_handler import Constants, GPIOHandler - -# FPS for the entire game to run at. -game_fps = 16 -# The resolution the game is rendered at. -game_res = 80 - -# Gets the directory of the script for importing and the save directory -script_dir = os.path.dirname(os.path.abspath(__file__)) -save_dir = os.path.join(Path.home(), '.pocket_friends') - -# Tries to make the save directory. Does nothing if it already exists. -try: - os.mkdir(save_dir) -except FileExistsError: - pass - - -class SpriteSheet: - """ - Imports a sprite sheet as separate pygame images given an image file and a json file. - """ - - def __init__(self, sprite_sheet, texture_json): - # Load in whole sprite sheet as one image. - self.sprite_sheet = pygame.image.load(sprite_sheet).convert_alpha() - self.images = [] - - # Get the sprite sheet json file. - with open(texture_json, 'r') as json_file: - self.img_attrib = json.load(json_file) - json_file.close() - - # Count for how many images have been added in the image list - image_count = 0 - - # Get the sprite size as a tuple - sprite_size = self.img_attrib['width'], self.img_attrib['height'] - - # Iterate through every image location on the sprite sheet given the sprite size - for i in range(self.sprite_sheet.get_size()[1] // sprite_size[1]): - i *= sprite_size[1] - for j in range(self.sprite_sheet.get_size()[0] // sprite_size[0]): - j *= sprite_size[0] - - # Create a new transparent surface - sprite = pygame.Surface(sprite_size, SRCALPHA) - # Blit the sprite onto the image - sprite.blit(self.sprite_sheet, (0, 0), (j, i, sprite_size[0], sprite_size[1])) - # Add the image to the list of images - self.images.append(sprite) - - image_count += 1 - - # Break the loop if the specified number of frames has been reached. - if image_count >= self.img_attrib['frames']: - break - if image_count >= self.img_attrib['frames']: - break - - -class DataHandler: - """ - Class that handles the hardware attributes and save files. - """ - - def __init__(self): - # Attributes that are saved to a file to recover upon startup. - self.attributes = { - 'version': pocket_friends.__version__, - 'time_elapsed': 0, - 'bloop': '', - 'age': 0, - 'health': 0, - 'hunger': 0, - 'happiness': 0, - 'care_counter': 0, - 'missed_care': 0, - 'adult': 0, - 'evolution_stage': '', - } - - # Frame counter - self.frames_passed = 0 - - def write_save(self): - """ - Writes attributes of class to "save.json" file. - """ - with open(save_dir + '/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_dir + '/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() - - def update(self): - """ - Run the game logic. - """ - self.frames_passed += 1 - # Run logic of the game every second. - if self.frames_passed >= game_fps: - - # Add one to the age of the bloop. - self.attributes['age'] += 1 - - # Save the data when the age of the bloop is a multiple of 10. - if self.attributes['age'] % 10 == 0: - self.write_save() - - # Reset frame counter - self.frames_passed = 0 - - -class PlaygroundFriend(pygame.sprite.Sprite): - """ - Class for the sprite of the creature on the main playground. - """ - - def __init__(self, data_handler): - pygame.sprite.Sprite.__init__(self) - - # All attributes of the bloops - self.bloop = data_handler.attributes['bloop'] - self.adult = data_handler.attributes['adult'] - self.evolution_stage = data_handler.attributes['evolution_stage'] - self.direction = 0 - - if self.evolution_stage == 'adult': - image = self.evolution_stage + self.adult - else: - image = self.evolution_stage - - # Draw the correct bloop depending on the stage - sprite_sheet = SpriteSheet(script_dir + '/resources/images/bloops/{0}/{1}.png'.format(self.bloop, image), - script_dir + '/resources/images/bloops/{0}/{1}.json'.format(self.bloop, image)) - - # Load the images from the sprite sheet - self.images = sprite_sheet.images - - # Put the egg in the middle of the screen. - self.rect = self.images[0].get_rect() - self.rect.x = (game_res / 2) - (self.rect.width / 2) - self.rect.y = (game_res / 2) - (self.rect.height / 2) - - # Start animation at the beginning of the sprite sheet. - self.index = 0 - self.image = self.images[self.index] - - self.movement_frames = game_fps / 2 # How many frames pass before the bloop moves - self.current_frame = 0 - - def pet(self): - """ - Pet the bloop! - """ - pass - - def update(self): - """ - Takes the images loaded and animates it, spacing it out equally for the framerate. - """ - - margins = 9 # Margins for how far the bloop can move from the left and the right of the screen - movement_amount = 2 # Pixels that the bloop moves in one movement - - self.current_frame += 1 - - # Check to see if the number of movement frames has passed - if self.current_frame >= self.movement_frames: - self.current_frame = 0 - - # Move only if the bloop is not in the egg stage - if self.evolution_stage != 'egg': - - # Change direction if the bloop has reached either edge of the screen - if self.rect.x < margins: - self.direction = 1 - elif self.rect.x > game_res - margins - self.rect.width: - self.direction = 0 - - # Move according to the direction. - if self.direction == 0: - self.rect.x -= movement_amount - else: - self.rect.x += movement_amount - - # Animate the bloop - self.index = (self.index + 1) % len(self.images) - self.image = self.images[self.index] - - -class SelectionEgg(pygame.sprite.Sprite): - """ - Class for the eggs on the egg selection screen. - """ - - def __init__(self, egg_color): - pygame.sprite.Sprite.__init__(self) - - self.egg_color = egg_color - - # Loads the JSON file of the egg to read in data. - with open(script_dir + '/resources/data/bloop_info/{0}.json'.format(egg_color), 'r') as save_file: - json_file = json.load(save_file) - save_file.close() - - # Gets the description off the egg from the JSON file. - self.description = json_file.get('description') - self.contentedness = json_file.get('contentedness') - self.metabolism = json_file.get('metabolism') - - # Load the egg from the given color and get the bounding rectangle for the image. - sprite_sheet = SpriteSheet(script_dir + '/resources/images/bloops/{0}/egg.png'.format(self.egg_color), - script_dir + '/resources/images/bloops/{0}/egg.json'.format(self.egg_color)) - self.images = sprite_sheet.images - - # Get the rectangle from the first image in the list - self.rect = self.images[0].get_rect() - self.index = 0 - self.image = self.images[self.index] - - def update(self): - """ - Updates the sprite object. - """ - # Animate the sprite - self.index = (self.index + 1) % len(self.images) - self.image = self.images[self.index] - - -class EggInfo: - """ - Class to draw the contentedness and metabolism value off the egg on the info screen. - """ - - def __init__(self, contentedness, metabolism, location): - self.contentedness = contentedness - self.metabolism = metabolism - self.x = location[0] - self.y = location[1] - - # Create a new surface to blit onto the other surface - self.surface = pygame.Surface((44, 15), SRCALPHA) - - # Blit the two indicator icons on screen - smiley = pygame.image.load(script_dir + '/resources/images/gui/smiley.png').convert_alpha() - self.surface.blit(smiley, (0, 0)) - apple = pygame.image.load(script_dir + '/resources/images/gui/apple.png').convert_alpha() - self.surface.blit(apple, (1, 9)) - - # Draw 5 stars. If the value of the contentedness is less than the current star, make it a blank star. - for i in range(5): - if i < self.contentedness: - star = pygame.image.load(script_dir + '/resources/images/gui/star.png').convert_alpha() - else: - star = pygame.image.load(script_dir + '/resources/images/gui/blank_star.png').convert_alpha() - self.surface.blit(star, (11 + (i * 6), 1)) - - # Draw 5 stars. If the value of the metabolism is less than the current star, make it a blank star. - for i in range(5): - if i < self.metabolism: - star = pygame.image.load(script_dir + '/resources/images/gui/star.png').convert_alpha() - else: - star = pygame.image.load(script_dir + '/resources/images/gui/blank_star.png').convert_alpha() - self.surface.blit(star, (11 + (i * 6), 10)) - - def draw(self, surface): - """ - Draw the info icons on a given surface. - :param surface: the surface to draw the icons on. - """ - # Blit the info onto the given surface. - surface.blit(self.surface, (self.x, self.y)) - - -class InfoText: - """ - Class for drawing large amounts of text on the screen at a time - """ - - def __init__(self, font, text='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam commodo tempor ' - 'aliquet. Suspendisse placerat accumsan neque, nec volutpat nunc porta ut.'): - - self.font = font - self.text = [] # Text broken up into a list according to how it will fit on screen. - self.max_lines = 6 # Max number of lines to be shown on screen at a time. - self.offset = 0 - - # Arrow icons to indicate scrolling - self.up_arrow = pygame.image.load(script_dir + '/resources/images/gui/up_arrow.png').convert_alpha() - self.down_arrow = pygame.image.load(script_dir + '/resources/images/gui/down_arrow.png').convert_alpha() - - raw_text = text # Copy the text to a different variable to be cut up. - - margins = 4.5 - max_line_width = game_res - (margins * 2) # The maximum pixel width that drawn text can be. - cut_chars = '.,! ' # Characters that will be considered "cuts" aka when a line break can occur. - - # Prevents freezing if the end of the string does not end in a cut character - # Will fix eventually more elegantly - if raw_text[-1:] not in cut_chars: - raw_text += ' ' - - # Calculating line breaks. - while len(raw_text) > 0: - index = 0 - test_text = '' # Chunk of text to pseudo-render and test the width of. - - # Loops until the testing text has reached the size limit. - while True: - - # Break if the current index is larger than the remaining text. - if index + 1 > len(raw_text): - index -= 1 - break - - # Add one character to the testing text from the raw text. - test_text += raw_text[index] - # Get the width of the pseudo-rendered text. - text_width = font.size(test_text)[0] - - # Break if the text is larger than the defined max width. - if text_width > max_line_width: - break - index += 1 - pass - - # Gets the chunk of text to be added to the list. - text_chunk = raw_text[0:index + 1] - # Determines if the chunk of text has any break characters. - has_breaks = any(cut_chars in text_chunk for cut_chars in cut_chars) - - # If the text has break characters, start with the last character and go backwards until - # one has been found, decreasing the index each time. - if has_breaks: - while raw_text[index] not in cut_chars: - index -= 1 - text_chunk = raw_text[0:index + 1] - # If there are no break characters in the chunk, simply decrease the index by one and insert - # a dash at the end of the line to indicate the word continues. - else: - index -= 1 - text_chunk = raw_text[0:index + 1] - text_chunk += '-' - - # Append the text chunk to the list of text to draw. - self.text.append(text_chunk) - - # Cut the text to repeat the process with the new cut string. - raw_text = raw_text[index + 1:] - - def draw(self, surface): - """ - Draws the text on a given surface. - :param surface: The surface for the text to be drawn on. - """ - # Constants to help draw the text - line_separation = 7 - left_margin = 3 - top_margin = 25 - bottom_margin = 10 - - # Draw the lines on the screen - for i in range(min(len(self.text), self.max_lines)): - text = self.font.render(self.text[i + self.offset], False, (64, 64, 64)) - surface.blit(text, (left_margin, top_margin + (i * line_separation))) - - # Draw the arrows if there is more text than is on screen. - if self.offset != 0: - surface.blit(self.up_arrow, ((game_res / 2) - (self.up_arrow.get_rect().width / 2), top_margin - 3)) - if len(self.text) - (self.offset + 1) >= self.max_lines: - surface.blit(self.down_arrow, - ((game_res / 2) - (self.down_arrow.get_rect().width / 2), game_res - bottom_margin)) - - def scroll_down(self): - """ - Scrolls the text on the screen down. - """ - # Ensures that the offset cannot be too big as to try to render non-existent lines. - if len(self.text) - (self.offset + 1) >= self.max_lines: - self.offset += 1 - - def scroll_up(self): - """ - Scrolls the text on the screen up. - """ - if self.offset > 0: # Ensures a non-zero offset is not possible. - self.offset -= 1 - - -class MenuIcon(pygame.sprite.Sprite): - """ - Sprite for an icon on the main popup menu. - """ - - def __init__(self, icon): - pygame.sprite.Sprite.__init__(self) - self.icon = icon - - # Load the sprite sheet from the icon name - sprite_sheet = SpriteSheet(script_dir + '/resources/images/gui/popup_menu/{0}.png'.format(self.icon), - script_dir + '/resources/images/gui/popup_menu/{0}.json'.format(self.icon)) - self.images = sprite_sheet.images - - # Get the rectangle from the first image in the list - self.rect = self.images[0].get_rect() - self.image = self.images[0] - - def select(self): - """ - Change the icon sprite to the selected icon. - """ - self.image = self.images[1] - - def deselect(self): - """ - Change the icon sprite to the not selected icon. - """ - self.image = self.images[0] - - -class PopupMenu: - """ - Class to create a popup menu that can be hidden and shown at will - """ - - def __init__(self, position): - # Background frame of the popup menu - self.frame = pygame.image.load(script_dir + '/resources/images/gui/popup_menu/frame.png').convert_alpha() - - self.draw_menu = False # Whether or not to draw the popup menu - self.menu_sprites = pygame.sprite.Group() # Sprite group for the icons - self.selected = 0 # The currently selected icon - - # The names of the icons to be drawn - icon_names = ['apple', 'dumbbell', 'stats', 'controller', 'bed'] - - self.icons = [] - # Create an icon sprite for each name in the list and add it to the icon list - for i in icon_names: - self.icons.append(MenuIcon(i)) - - # Add each sprite in the icon list to the sprite group - for i in range(len(self.icons)): - icon = self.icons[i] - if i == self.selected: # Make the default selected icon glow - icon.select() - - # Calculate the position of the icon on screen - icon.rect.x = 10 + (i * 15) - (icon.image.get_width() / 2) - icon.rect.y = position[1] + self.frame.get_height() / 2 - icon.image.get_height() / 2 - - # Add the icon to the sprite group. - self.menu_sprites.add(icon) - - def toggle(self): - """ - Toggles the menu on or off. - """ - self.draw_menu = not self.draw_menu - - def next(self): - """ - Changes the selection to the next icon (to the right.) - """ - if self.draw_menu: # Only change if the menu is on screen - - self.icons[self.selected].deselect() # Deselect the current icon - self.selected += 1 # Change selection to the next icon - if self.selected >= len(self.icons): # Wrap around if new value is invalid - self.selected = 0 - self.icons[self.selected].select() # Select the newly selected icon - - def prev(self): - """ - Changes the selection to the previous icon (to the left.) - """ - if self.draw_menu: # Only change if the menu is on screen - - self.icons[self.selected].deselect() # Deselect the current icon - self.selected -= 1 # Change selection to the previous icon - if self.selected < 0: # Wrap around if new value is invalid - self.selected = len(self.icons) - 1 - self.icons[self.selected].select() # Select the newly selected icon - - def draw(self, surface): - """ - Draw the menu onto a given surface - :param surface: the surface to draw the menu on. - """ - # Draw the menu only if it is toggled on. - if self.draw_menu: - surface.blit(self.frame, (3, 3)) - self.menu_sprites.draw(surface) - - -# 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 - - on_hardware = True -except ImportError: - import pocket_friends.development.FakeGPIO as GPIO - - on_hardware = False - - -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. - screen_size = 240 - - window = pygame.display.set_mode((screen_size, screen_size)) - surface = pygame.Surface((game_res, game_res)) - - # Only really useful for PCs. Does nothing on the Raspberry Pi. - pygame.display.set_caption('Pocket Friends {0}'.format(pocket_friends.__version__)) - - # Add an icon to the pygame window. - icon = pygame.image.load(script_dir + '/resources/images/icon/icon.png').convert_alpha() - pygame.display.set_icon(icon) - - clock = pygame.time.Clock() - - # Font used for small text in the game. Bigger text is usually image files. - small_font = pygame.font.Font(script_dir + '/resources/fonts/5Pts5.ttf', 10) - - # Default game state when the game first starts. - game_state = 'title' - running = True - data_handler = DataHandler() - - # 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(script_dir + '/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 or not on_hardware: - 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(script_dir + '/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': - - # Submenu used within the playground. - submenu = 'main' - - while running and game_state == 'playground': - - all_sprites.empty() - - if submenu == 'main': - - # Create the bloop and the menu - bloop = PlaygroundFriend(data_handler) - all_sprites.add(bloop) - popup_menu = PopupMenu((3, 3)) - - while running and game_state == 'playground' and submenu == 'main': - pre_handler() - data_handler.update() - - for event in pygame.event.get(): - if event.type == pygame.KEYDOWN: - if event.key == Constants.buttons.get('j_r'): - # Move selection to the next item - popup_menu.next() - if event.key == Constants.buttons.get('j_l'): - # Move selection to the previous item - popup_menu.prev() - if event.key == Constants.buttons.get('a'): - # Change submenu to the menu the icon points to - if popup_menu.draw_menu: - submenu = popup_menu.icons[popup_menu.selected].icon - else: # Pet the bloop otherwise - bloop.pet() - if event.key == Constants.buttons.get('b'): - # Toggle the popup menu on or off - popup_menu.toggle() - - # Draw the popup menu if toggled on - popup_menu.draw(surface) - - draw() - - else: # Go to the error state if an invalid state is set. - game_state = None - - elif game_state == 'init': - all_sprites.empty() - pre_handler() - draw() - - # Read the save file. - data_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 data_handler.attributes['bloop'] == '': - game_state = 'egg_select' - else: - game_state = 'playground' - - elif game_state == 'egg_select': - - # Submenu used within the egg selection menu. - submenu = 'main' - - selected = 0 - selected_color = "" - - while running and game_state == 'egg_select': - - all_sprites.empty() - - if submenu == 'main': - - # Creates and holds the egg objects in a list. - eggs = [SelectionEgg('dev_egg'), SelectionEgg('blue'), 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) - - 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 = 'bloop_info' - - # Draws the cursor on screen. - cursor = pygame.image.load( - script_dir + '/resources/images/gui/egg_selector.png').convert_alpha() - surface.blit(cursor, get_cursor_coords(selected)) - - selected_color = eggs[selected].egg_color - - draw() - - elif submenu == 'bloop_info': - - # Draw the selected egg on screen - egg = SelectionEgg(selected_color) - egg.rect.x = 8 - egg.rect.y = 3 - all_sprites.add(egg) - - # Info screen for the eggs. - info_text = InfoText(small_font, egg.description) - info_icons = EggInfo(egg.contentedness, egg.metabolism, (32, 4)) - - while running and game_state == 'egg_select' and submenu == 'bloop_info': - - pre_handler() - - for event in pygame.event.get(): - if event.type == pygame.KEYDOWN: - if event.key == Constants.buttons.get('j_d'): - # Scroll down on the info screen. - info_text.scroll_down() - if event.key == Constants.buttons.get('j_u'): - # Scroll up on the info screen. - info_text.scroll_up() - if event.key == Constants.buttons.get('a'): - # Write save file with new attributes - data_handler.attributes['bloop'] = egg.egg_color - data_handler.attributes['health'] = 10 - data_handler.attributes['hunger'] = 10 - data_handler.attributes['happiness'] = 10 - data_handler.attributes['evolution_stage'] = 'egg' - data_handler.write_save() - - # Go to playground - game_state = 'playground' - if event.key == Constants.buttons.get('b'): - # Go back to the egg selection screen. - submenu = 'main' - - # Draw the info screen. - info_text.draw(surface) - info_icons.draw(surface) - - 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 isn't frozen. - - while running and game_state != 'title': - - pre_handler() - - # Draw the error screen - error_screen = pygame.image.load(script_dir + '/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, game_res - 10)) - - 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/pocket_friends/hardware/__init__.py b/pocket_friends/hardware/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pocket_friends/hardware/gpio_handler.py b/pocket_friends/hardware/gpio_handler.py deleted file mode 100644 index f8cd603..0000000 --- a/pocket_friends/hardware/gpio_handler.py +++ /dev/null @@ -1,71 +0,0 @@ -""" -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 pocket_friends.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/io/__init__.py b/pocket_friends/io/__init__.py new file mode 100644 index 0000000..8828bf5 --- /dev/null +++ b/pocket_friends/io/__init__.py @@ -0,0 +1,2 @@ +"""Subpackage for handling all I/O operations including keyboard input (GPIO input when connected to a Raspberry Pi) +and save data reading and writing.""" \ No newline at end of file diff --git a/pocket_friends/io/data.py b/pocket_friends/io/data.py new file mode 100644 index 0000000..e7dfeee --- /dev/null +++ b/pocket_friends/io/data.py @@ -0,0 +1,55 @@ +import pocket_friends +import json + + +class SaveData: + """ + Class used to read and write save data for the game + + Attributes: + attributes (dict): Dictionary containing all the attributes to read and write from a save file. + """ + def __init__(self): + """ + Constructs the object with all starting values. + """ + # Attributes that are saved to a file to recover upon startup. + self.attributes = { + 'version': pocket_friends.__version__, + 'time_elapsed': 0, + 'bloop': '', + 'age': 0, + 'health': 0, + 'hunger': 0, + 'happiness': 0, + 'care_counter': 0, + 'missed_care': 0, + 'adult': 0, + 'evolution_stage': '', + } + + # Frame counter + self.frames_passed = 0 + + def write_save(self): + """ + Writes attributes of the save object to "save.json" file. + """ + with open(save_dir + '/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 the save object's attributes dictionary. Creates file if it does not + exist. + """ + # Open up the save file and read it into self.attributes. + try: + with open(save_dir + '/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() diff --git a/pocket_friends/io/input_handler.py b/pocket_friends/io/input_handler.py new file mode 100644 index 0000000..e04f7f0 --- /dev/null +++ b/pocket_friends/io/input_handler.py @@ -0,0 +1,72 @@ +import pygame + + +class InputHandler: + """ + Class that is implemented into surfaces in order to control the + pressing of buttons on both the real hardware and on a keyboard. + + Attributes: + clock (pygame.time.Clock): Pygame clock used for input time calculations. + last_input_tick (int): The tick that the last input was registered on. + + """ + + def __init__(self, pygame_clock, tick_check=True): + """ + Create a InputHandler object using a given Pygame clock. + + Args: + pygame_clock (:obj:`pygame.time.Clock`): A pygame clock to use as the clock for input time calculations. + tick_check (bool, optional): Bool to ignore inputs that happen to quickly after another. Defaults to True. + + """ + self.clock = pygame_clock + self.tick_check = tick_check + self.last_input_tick = 0 + + def create_event(self, pressed_button): + """ + Create a pygame event given a GPIO code and post it to the pygame event handler. + Args: + pressed_button (int): The GPIO code to be registered and pressed. + + """ + # Register a button click so long as the last button click happened no less than two frames ago + if self.tick_check: + 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})) + else: + pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {'key': pressed_button})) + pygame.event.post(pygame.event.Event(pygame.KEYUP, {'key': pressed_button})) + self.last_input_tick = pygame.time.get_ticks() + + def handle_keyboard(self): + """Handle keyboard presses and generate corresponding GPIO codes to create events.""" + + # 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: + self.create_event(pygame.K_a) + if keyboard_event.key == pygame.K_b: + self.create_event(pygame.K_b) + if keyboard_event.key == pygame.K_KP_ENTER: + self.create_event(pygame.K_KP_ENTER) + if keyboard_event.key == pygame.K_RIGHT: + self.create_event(pygame.K_RIGHT) + if keyboard_event.key == pygame.K_LEFT: + self.create_event(pygame.K_LEFT) + if keyboard_event.key == pygame.K_DOWN: + self.create_event(pygame.K_DOWN) + if keyboard_event.key == pygame.K_UP: + self.create_event(pygame.K_UP) + if keyboard_event.key == pygame.K_ESCAPE: + running = False + + def update(self): + """Run either the GPIO handler or the keyboard handler to check for input and create events.""" + self.handle_keyboard() diff --git a/pocket_friends/game_files/resources/data/bloop_info/blue.json b/pocket_friends/resources/data/bloop_info/blue.json similarity index 100% rename from pocket_friends/game_files/resources/data/bloop_info/blue.json rename to pocket_friends/resources/data/bloop_info/blue.json diff --git a/pocket_friends/game_files/resources/data/bloop_info/dev_egg.json b/pocket_friends/resources/data/bloop_info/dev_egg.json similarity index 100% rename from pocket_friends/game_files/resources/data/bloop_info/dev_egg.json rename to pocket_friends/resources/data/bloop_info/dev_egg.json diff --git a/pocket_friends/game_files/resources/data/bloop_info/rainbow.json b/pocket_friends/resources/data/bloop_info/rainbow.json similarity index 100% rename from pocket_friends/game_files/resources/data/bloop_info/rainbow.json rename to pocket_friends/resources/data/bloop_info/rainbow.json diff --git a/pocket_friends/game_files/resources/data/bloop_info/red.json b/pocket_friends/resources/data/bloop_info/red.json similarity index 100% rename from pocket_friends/game_files/resources/data/bloop_info/red.json rename to pocket_friends/resources/data/bloop_info/red.json diff --git a/pocket_friends/game_files/resources/fonts/5Pts5.ttf b/pocket_friends/resources/fonts/5Pts5.ttf similarity index 100% rename from pocket_friends/game_files/resources/fonts/5Pts5.ttf rename to pocket_friends/resources/fonts/5Pts5.ttf diff --git a/pocket_friends/game_files/resources/images/icon/icon.ico b/pocket_friends/resources/icon/icon.ico similarity index 100% rename from pocket_friends/game_files/resources/images/icon/icon.ico rename to pocket_friends/resources/icon/icon.ico diff --git a/pocket_friends/game_files/resources/images/icon/icon.png b/pocket_friends/resources/icon/icon.png similarity index 100% rename from pocket_friends/game_files/resources/images/icon/icon.png rename to pocket_friends/resources/icon/icon.png diff --git a/pocket_friends/game_files/resources/images/bg.png b/pocket_friends/resources/images/bg.png similarity index 100% rename from pocket_friends/game_files/resources/images/bg.png rename to pocket_friends/resources/images/bg.png diff --git a/pocket_friends/game_files/resources/images/bloops/_postponed/green/green.png b/pocket_friends/resources/images/bloops/_postponed/green/green.png similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/_postponed/green/green.png rename to pocket_friends/resources/images/bloops/_postponed/green/green.png diff --git a/pocket_friends/game_files/resources/images/bloops/_postponed/indigo/indigo.png b/pocket_friends/resources/images/bloops/_postponed/indigo/indigo.png similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/_postponed/indigo/indigo.png rename to pocket_friends/resources/images/bloops/_postponed/indigo/indigo.png diff --git a/pocket_friends/game_files/resources/images/bloops/_postponed/orange/orange.png b/pocket_friends/resources/images/bloops/_postponed/orange/orange.png similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/_postponed/orange/orange.png rename to pocket_friends/resources/images/bloops/_postponed/orange/orange.png diff --git a/pocket_friends/game_files/resources/images/bloops/_postponed/violet/violet.png b/pocket_friends/resources/images/bloops/_postponed/violet/violet.png similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/_postponed/violet/violet.png rename to pocket_friends/resources/images/bloops/_postponed/violet/violet.png diff --git a/pocket_friends/game_files/resources/images/bloops/_postponed/white/white.png b/pocket_friends/resources/images/bloops/_postponed/white/white.png similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/_postponed/white/white.png rename to pocket_friends/resources/images/bloops/_postponed/white/white.png diff --git a/pocket_friends/game_files/resources/images/bloops/_postponed/yellow/yellow.png b/pocket_friends/resources/images/bloops/_postponed/yellow/yellow.png similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/_postponed/yellow/yellow.png rename to pocket_friends/resources/images/bloops/_postponed/yellow/yellow.png diff --git a/pocket_friends/game_files/resources/images/bloops/blue/egg.json b/pocket_friends/resources/images/bloops/blue/egg.json similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/blue/egg.json rename to pocket_friends/resources/images/bloops/blue/egg.json diff --git a/pocket_friends/game_files/resources/images/bloops/blue/egg.png b/pocket_friends/resources/images/bloops/blue/egg.png similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/blue/egg.png rename to pocket_friends/resources/images/bloops/blue/egg.png diff --git a/pocket_friends/game_files/resources/images/bloops/dev_egg/baby.json b/pocket_friends/resources/images/bloops/dev_egg/baby.json similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/dev_egg/baby.json rename to pocket_friends/resources/images/bloops/dev_egg/baby.json diff --git a/pocket_friends/game_files/resources/images/bloops/dev_egg/baby.png b/pocket_friends/resources/images/bloops/dev_egg/baby.png similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/dev_egg/baby.png rename to pocket_friends/resources/images/bloops/dev_egg/baby.png diff --git a/pocket_friends/game_files/resources/images/bloops/dev_egg/egg.json b/pocket_friends/resources/images/bloops/dev_egg/egg.json similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/dev_egg/egg.json rename to pocket_friends/resources/images/bloops/dev_egg/egg.json diff --git a/pocket_friends/game_files/resources/images/bloops/dev_egg/egg.png b/pocket_friends/resources/images/bloops/dev_egg/egg.png similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/dev_egg/egg.png rename to pocket_friends/resources/images/bloops/dev_egg/egg.png diff --git a/pocket_friends/game_files/resources/images/bloops/rainbow/egg.json b/pocket_friends/resources/images/bloops/rainbow/egg.json similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/rainbow/egg.json rename to pocket_friends/resources/images/bloops/rainbow/egg.json diff --git a/pocket_friends/game_files/resources/images/bloops/rainbow/egg.png b/pocket_friends/resources/images/bloops/rainbow/egg.png similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/rainbow/egg.png rename to pocket_friends/resources/images/bloops/rainbow/egg.png diff --git a/pocket_friends/game_files/resources/images/bloops/red/egg.json b/pocket_friends/resources/images/bloops/red/egg.json similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/red/egg.json rename to pocket_friends/resources/images/bloops/red/egg.json diff --git a/pocket_friends/game_files/resources/images/bloops/red/egg.png b/pocket_friends/resources/images/bloops/red/egg.png similarity index 100% rename from pocket_friends/game_files/resources/images/bloops/red/egg.png rename to pocket_friends/resources/images/bloops/red/egg.png diff --git a/pocket_friends/game_files/resources/images/debug/invalid.png b/pocket_friends/resources/images/debug/invalid.png similarity index 100% rename from pocket_friends/game_files/resources/images/debug/invalid.png rename to pocket_friends/resources/images/debug/invalid.png diff --git a/pocket_friends/game_files/resources/images/gui/apple.png b/pocket_friends/resources/images/gui/apple.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/apple.png rename to pocket_friends/resources/images/gui/apple.png diff --git a/pocket_friends/game_files/resources/images/gui/bar_graphic.png b/pocket_friends/resources/images/gui/bar_graphic.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/bar_graphic.png rename to pocket_friends/resources/images/gui/bar_graphic.png diff --git a/pocket_friends/game_files/resources/images/gui/bar_outline.png b/pocket_friends/resources/images/gui/bar_outline.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/bar_outline.png rename to pocket_friends/resources/images/gui/bar_outline.png diff --git a/pocket_friends/game_files/resources/images/gui/blank_star.png b/pocket_friends/resources/images/gui/blank_star.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/blank_star.png rename to pocket_friends/resources/images/gui/blank_star.png diff --git a/pocket_friends/game_files/resources/images/gui/down_arrow.png b/pocket_friends/resources/images/gui/down_arrow.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/down_arrow.png rename to pocket_friends/resources/images/gui/down_arrow.png diff --git a/pocket_friends/game_files/resources/images/gui/egg_selector.png b/pocket_friends/resources/images/gui/egg_selector.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/egg_selector.png rename to pocket_friends/resources/images/gui/egg_selector.png diff --git a/pocket_friends/game_files/resources/images/gui/popup_menu/apple.json b/pocket_friends/resources/images/gui/popup_menu/apple.json similarity index 100% rename from pocket_friends/game_files/resources/images/gui/popup_menu/apple.json rename to pocket_friends/resources/images/gui/popup_menu/apple.json diff --git a/pocket_friends/game_files/resources/images/gui/popup_menu/apple.png b/pocket_friends/resources/images/gui/popup_menu/apple.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/popup_menu/apple.png rename to pocket_friends/resources/images/gui/popup_menu/apple.png diff --git a/pocket_friends/game_files/resources/images/gui/popup_menu/bed.json b/pocket_friends/resources/images/gui/popup_menu/bed.json similarity index 100% rename from pocket_friends/game_files/resources/images/gui/popup_menu/bed.json rename to pocket_friends/resources/images/gui/popup_menu/bed.json diff --git a/pocket_friends/game_files/resources/images/gui/popup_menu/bed.png b/pocket_friends/resources/images/gui/popup_menu/bed.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/popup_menu/bed.png rename to pocket_friends/resources/images/gui/popup_menu/bed.png diff --git a/pocket_friends/game_files/resources/images/gui/popup_menu/controller.json b/pocket_friends/resources/images/gui/popup_menu/controller.json similarity index 100% rename from pocket_friends/game_files/resources/images/gui/popup_menu/controller.json rename to pocket_friends/resources/images/gui/popup_menu/controller.json diff --git a/pocket_friends/game_files/resources/images/gui/popup_menu/controller.png b/pocket_friends/resources/images/gui/popup_menu/controller.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/popup_menu/controller.png rename to pocket_friends/resources/images/gui/popup_menu/controller.png diff --git a/pocket_friends/game_files/resources/images/gui/popup_menu/dumbbell.json b/pocket_friends/resources/images/gui/popup_menu/dumbbell.json similarity index 100% rename from pocket_friends/game_files/resources/images/gui/popup_menu/dumbbell.json rename to pocket_friends/resources/images/gui/popup_menu/dumbbell.json diff --git a/pocket_friends/game_files/resources/images/gui/popup_menu/dumbbell.png b/pocket_friends/resources/images/gui/popup_menu/dumbbell.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/popup_menu/dumbbell.png rename to pocket_friends/resources/images/gui/popup_menu/dumbbell.png diff --git a/pocket_friends/game_files/resources/images/gui/popup_menu/frame.png b/pocket_friends/resources/images/gui/popup_menu/frame.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/popup_menu/frame.png rename to pocket_friends/resources/images/gui/popup_menu/frame.png diff --git a/pocket_friends/game_files/resources/images/gui/popup_menu/stats.json b/pocket_friends/resources/images/gui/popup_menu/stats.json similarity index 100% rename from pocket_friends/game_files/resources/images/gui/popup_menu/stats.json rename to pocket_friends/resources/images/gui/popup_menu/stats.json diff --git a/pocket_friends/game_files/resources/images/gui/popup_menu/stats.png b/pocket_friends/resources/images/gui/popup_menu/stats.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/popup_menu/stats.png rename to pocket_friends/resources/images/gui/popup_menu/stats.png diff --git a/pocket_friends/game_files/resources/images/gui/smiley.png b/pocket_friends/resources/images/gui/smiley.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/smiley.png rename to pocket_friends/resources/images/gui/smiley.png diff --git a/pocket_friends/game_files/resources/images/gui/star.png b/pocket_friends/resources/images/gui/star.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/star.png rename to pocket_friends/resources/images/gui/star.png diff --git a/pocket_friends/game_files/resources/images/gui/up_arrow.png b/pocket_friends/resources/images/gui/up_arrow.png similarity index 100% rename from pocket_friends/game_files/resources/images/gui/up_arrow.png rename to pocket_friends/resources/images/gui/up_arrow.png diff --git a/pocket_friends/game_files/resources/images/promotional.png b/pocket_friends/resources/images/promotional.png similarity index 100% rename from pocket_friends/game_files/resources/images/promotional.png rename to pocket_friends/resources/images/promotional.png diff --git a/pocket_friends/game_files/resources/images/title.png b/pocket_friends/resources/images/title.png similarity index 100% rename from pocket_friends/game_files/resources/images/title.png rename to pocket_friends/resources/images/title.png diff --git a/pocket_friends/surfaces/egg_select.py b/pocket_friends/surfaces/egg_select.py new file mode 100644 index 0000000..525dac0 --- /dev/null +++ b/pocket_friends/surfaces/egg_select.py @@ -0,0 +1,129 @@ +import pygame +from pocket_friends.elements import sprites +from pocket_friends.elements import surface + + +class Surface(surface.GameSurface): + def __init__(self, game_res, resources_dir, game_fps, **kwargs): + super().__init__(game_res, resources_dir, game_fps) + + preselected_color = None + for key in kwargs.keys(): + if key == 'selected_color': + preselected_color = kwargs.get(key) + + egg_list = [ + 'dev_egg', + 'blue', + 'rainbow' + ] + self.eggs = [] + for egg in egg_list: + self.eggs.append(sprites.SelectionEgg(egg, self.resource_dir)) + + self.eggs_per_row = 3 + distance_between_eggs = 36 / self.eggs_per_row + + # Count the total rows. + self.total_rows = -(-len(self.eggs) // self.eggs_per_row) + distance_between_rows = 32 / self.eggs_per_row + + # Determine the location of each egg. + for egg in self.eggs: + current_row = self.eggs.index(egg) // self.eggs_per_row + rows_after = self.total_rows - (current_row + 1) + egg_in_row = self.eggs.index(egg) % self.eggs_per_row + eggs_after = min(len(self.eggs) - (current_row * self.eggs_per_row), self.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. + self.sprites.add(egg) + + self.selected_egg = 0 + self.selected_color = '' + if preselected_color is not None: + self.selected_color = self.eggs[self.selected_egg].egg_color + for i in range(len(self.eggs)): + if self.eggs[i].egg_color == preselected_color: + self.selected_egg = i + + def get_cursor_coords(self): + """ + Gets the coordinates of an egg on the selection screen by index and returns it as a tuple + :return: tuple of the coordinates of the selected egg + """ + cursor_x_offset = -2 + cursor_y_offset = -2 + + return (self.eggs[self.selected_egg].rect.x + cursor_x_offset, + self.eggs[self.selected_egg].rect.y + cursor_y_offset) + + def sel_left(self): + """ + Select the egg to the left with constraints. + """ + + if self.selected_egg % self.eggs_per_row != 0: + self.selected_egg -= 1 + + def sel_right(self): + """ + Select the egg to the right with constraints. + """ + + row = self.selected_egg // self.eggs_per_row + eggs_in_row = min(len(self.eggs) - (row * self.eggs_per_row), self.eggs_per_row) + + if self.selected_egg % self.eggs_per_row != eggs_in_row - 1: + self.selected_egg += 1 + + def sel_up(self): + """ + Select the egg above with constraints. + """ + + if self.selected_egg // self.eggs_per_row != 0: + self.selected_egg -= self.eggs_per_row + + def sel_down(self): + """ + Select the egg below with constraints. + """ + + if self.selected_egg // self.eggs_per_row != self.total_rows - 1: + self.selected_egg += self.eggs_per_row + + def update(self): + self.preprocess() + + cursor = pygame.image.load( + self.resource_dir + '/images/gui/egg_selector.png').convert_alpha() + self.blit(cursor, self.get_cursor_coords()) + + self.selected_color = self.eggs[self.selected_egg].egg_color + + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_RIGHT: + self.sel_right() + if event.key == pygame.K_LEFT: + self.sel_left() + if event.key == pygame.K_DOWN: + self.sel_down() + if event.key == pygame.K_UP: + self.sel_up() + if event.key == pygame.K_a: + self.additional_args = {'selected_egg': self.selected_color} + self.next_surface = 'selection_info' + self.running = False diff --git a/pocket_friends/surfaces/error_screen.py b/pocket_friends/surfaces/error_screen.py new file mode 100644 index 0000000..1aa1800 --- /dev/null +++ b/pocket_friends/surfaces/error_screen.py @@ -0,0 +1,28 @@ +import pygame +from pocket_friends.elements import surface + + +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.title = pygame.image.load(resources_dir + '/images/debug/invalid.png').convert_alpha() + self.next_surface = 'title' + + def update(self): + self.preprocess() + + self.blit(self.title, (0, -4)) + text = self.font.render('Frames: {0}'.format(self.frames), False, (64, 64, 64)) + self.blit(text, (3, 68)) + + self.frames += 1 + self.frames %= self.game_fps + + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_b: + self.running = False diff --git a/pocket_friends/surfaces/selection_info.py b/pocket_friends/surfaces/selection_info.py new file mode 100644 index 0000000..3d9bb30 --- /dev/null +++ b/pocket_friends/surfaces/selection_info.py @@ -0,0 +1,49 @@ +import pygame +from pocket_friends.elements import sprites +from pocket_friends.elements import surface + + +class Surface(surface.GameSurface): + def __init__(self, game_res, resources_dir, game_fps, **kwargs): + super().__init__(game_res, resources_dir, game_fps) + + preselected_color = None + for key in kwargs.keys(): + if key == 'selected_color': + preselected_color = kwargs.get(key) + + self.selected_egg = None + for key in kwargs.keys(): + if key == 'selected_egg': + self.selected_egg = kwargs.get(key) + + egg = sprites.SelectionEgg(self.selected_egg, resources_dir) + egg.rect.x = 8 + egg.rect.y = 3 + self.sprites.add(egg) + + self.info_text = sprites.InfoText(resources_dir, game_res[0], egg.description) + self.info_icons = sprites.EggInfo(resources_dir, egg.contentedness, egg.metabolism, (32, 4)) + + def update(self): + self.preprocess() + + self.info_text.draw(self) + self.info_icons.draw(self) + + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_DOWN: + # Scroll down on the info screen. + self.info_text.scroll_down() + if event.key == pygame.K_UP: + # Scroll up on the info screen. + self.info_text.scroll_up() + if event.key == pygame.K_a: + self.running = False + self.additional_args = {'selected_color': self.selected_egg} + self.next_surface = 'playground' + if event.key == pygame.K_b: + self.running = False + self.additional_args = {'selected_color': self.selected_egg} + self.next_surface = 'egg_select' diff --git a/pocket_friends/surfaces/title.py b/pocket_friends/surfaces/title.py new file mode 100644 index 0000000..57087c6 --- /dev/null +++ b/pocket_friends/surfaces/title.py @@ -0,0 +1,25 @@ +import pygame +from pocket_friends.elements import surface + + +class Surface(surface.GameSurface): + def __init__(self, game_res, resources_dir, game_fps): + super().__init__(game_res, resources_dir, game_fps) + + self.frames = 0 + self.delay = 1 + + self.title = pygame.image.load(resources_dir + '/images/title.png').convert_alpha() + + def update(self): + """ + Advance the surface logic by one frame. + """ + self.preprocess() + self.blit(self.title, (0, 0)) + + print(self.frames) + self.frames += 1 + if self.frames > self.game_fps * self.delay: + self.next_surface = 'egg_select' + self.running = False diff --git a/requirements.txt b/requirements.txt index 1581372..454329a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -pygame~=2.1.2 \ No newline at end of file +pygame~=1.9.6 \ No newline at end of file diff --git a/setup.py b/setup.py index 57eb41d..6f91597 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,6 @@ setuptools.setup( classifiers=[ ], install_requires=required, - python_requires='>=3.10', + python_requires='>=3.7', include_package_data=True, )