Compare commits

6 Commits

Author SHA1 Message Date
Nick Dyer
34d934fb4a default res now 240x240 2021-07-03 03:28:11 -04:00
Nick Dyer
7def89db02 added backlight test to dev menu 2021-07-02 01:51:52 -04:00
Nick Dyer
d753058352 added dev to launch, changed default resolution to 240x240 2021-07-02 01:48:36 -04:00
Nick Dyer
4aa8af306c added backlight control in gpio_handler.py 2021-07-02 01:47:51 -04:00
Nick Dyer
195abbc5eb added new fake functions and scrips to FakeGPIO.py 2021-07-02 01:47:19 -04:00
Nick Dyer
85557ebb1c made changes to launch script, added customizable resolution 2021-06-20 20:52:23 -04:00
23 changed files with 1115 additions and 755 deletions

View File

@@ -1,11 +1,11 @@
![Pocket Friends](https://gitea.citruxx.com/ndyer/pocket-friends/raw/branch/master/pocket_friends/game_files/resources/images/promotional.png)
![Pocket Friends](https://github.com/nickedyer/pocket-friends/blob/master/pocket_friends/game_files/resources/images/promotional.png?raw=true)
[![License: GNU GPL v3.0](https://img.shields.io/badge/license-GNU%20GPL%20v3.0-blue)](LICENSE)
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!
~~You can download the latest release of Pocket Friends on the [releases page.](https://gitea.citruxx.com/ndyer/pocket-friends/releases)~~
~~You can download the latest release of Pocket Friends on the [releases page.](https://github.com/nickedyer/pocket-friends/releases)~~
There are currently no releases of the game. To install the current version on GitHub, follow the instructions below.
---
@@ -13,13 +13,13 @@ 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.6 or greater
- Pip
- Git
All you need to do to install Pocket Friends is install it with pip and you're good to go!
`pip install git+https://gitea.citruxx.com/ndyer/pocket-friends.git`
`pip install git+https://github.com/nickedyer/pocket-friends.git`
Now that the game is installed, just run it like you would any other Python program.
@@ -33,7 +33,7 @@ If you wish to build this version of Pocket Friends for Windows, you will need t
requirements as to install it to your system from source. After you have done that,
issue the following:
```
git clone https://gitea.citruxx.com/ndyer/pocket-friends.git
git clone https://github.com/nickedyer/pocket-friends.git
cd pocket-friends
pip install -r requirements.txt
pip install pyinstaller

View File

@@ -1,4 +1 @@
"""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.3'

View File

@@ -1,23 +1,7 @@
"""
Launch script for Pocket Friends.
Wrapper that launches the launch script.
"""
import os
import pygame
import sys
from pathlib import Path
import pocket_friends.game_files.game as game
import pocket_friends.launch as launch
if __name__ == '__main__':
enable_dev = False
# enable dev mode if --dev argument is passed
if len(sys.argv) > 0:
for args in sys.argv:
if args == '--delete-save':
save_dir = os.path.join(Path.home(), '.pocket_friends')
os.remove(save_dir + '/save.json')
game.main()
pygame.quit()
sys.exit()
launch.main()

View File

@@ -1,13 +1,17 @@
"""
Module used to fake the RPi.GPIO module so that
the program can be run without the actual hardware.
the hardware can be run without the actual hardware.
"""
# Constants used by RPi.GPIO
BOARD = 0
IN = 0
OUT = 1
FALLING = 0
HIGH = 1
LOW = 0
def setmode(new_mode):
"""
@@ -28,6 +32,16 @@ def setup(channel, mode, initial=None, pull_up_down=None):
pass
def output(channel, set):
"""
Fake function to set a fake channel output.
:param channel:
:param set:
:return:
"""
pass
def add_event_detect(channel, edge_type, callback=None, bouncetime=0):
"""
Fake function to add a non-existent event detect.
@@ -43,7 +57,7 @@ def event_detected(channel):
"""
Fake function to detect an event. Always returns false.
:param channel:
:return:
:return: False
"""
return False

View File

@@ -2,7 +2,7 @@
Module to test the GPIO input on the Raspberry Pi.
"""
from collections import deque
from pocket_friends.game_files.io.gpio_handler import Constants, GPIOHandler
from ..hardware.gpio_handler import Constants, GPIOHandler
def button_test():

View File

@@ -8,13 +8,13 @@ import pygame
import time
from .button_test import button_test
from .menus import Menu
from pocket_friends.game_files.io.gpio_handler import GPIOHandler, Constants
from ..hardware.gpio_handler import GPIOHandler, Constants
try:
importlib.util.find_spec('RPi.GPIO')
import RPi.GPIO as GPIO
except ImportError:
import pocket_friends.game_files.io.fake_gpio as GPIO
import pocket_friends.development.FakeGPIO as GPIO
# Global variable to keep track of the current menu.
menu = 'main'
@@ -29,6 +29,15 @@ def run_button_test():
GPIOHandler.setup()
def backlight_test():
"""
Turns off the backlight for 5 seconds.
"""
GPIOHandler.turn_off_backlight()
time.sleep(5)
GPIOHandler.turn_on_backlight()
def clear_screen():
"""
Clears the screen.
@@ -55,9 +64,9 @@ def quit_menu():
def quit_with_error():
"""
Quits the menu with error code 3.
Quits the menu with error code 2.
"""
exit(3)
exit(2)
def change_menu(new_menu):
@@ -96,6 +105,7 @@ def main():
main_menu = Menu('Pocket Friends Dev Menu')
main_menu.add_option(Menu.Option('Start Game', start_game))
main_menu.add_option(Menu.Option('Button Test', run_button_test))
main_menu.add_option(Menu.Option('Backlight Test', backlight_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'))

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
"""Sub-package for handling all I/O operations including keyboard input (GPIO input when connected to a Raspberry Pi)
and save data reading and writing."""

View File

@@ -1,53 +0,0 @@
import pocket_friends
import json
class SaveData:
"""
Class that represents the save data of the game.
"""
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()

View File

@@ -1,62 +0,0 @@
"""
Handle 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
# If the RPi.GPIO module is not found (aka the program is not running on a Pi), import the fake
# GPIO module instead to prevent a crash.
try:
importlib.util.find_spec('RPi.GPIO')
import RPi.GPIO as GPIO
except ImportError:
import pocket_friends.game_files.io.fake_gpio as GPIO
# Dictionary of all the buttons used and what their corresponding GPIO codes are
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
}
def setup():
"""
Primes the GPIO pins for reading the inputs of the buttons.
"""
GPIO.setmode(GPIO.BOARD)
GPIO.setup(BUTTONS.get('a'), GPIO.IN)
GPIO.setup(BUTTONS.get('b'), GPIO.IN)
GPIO.setup(BUTTONS.get('j_i'), GPIO.IN)
GPIO.setup(BUTTONS.get('j_u'), GPIO.IN)
GPIO.setup(BUTTONS.get('j_d'), GPIO.IN)
GPIO.setup(BUTTONS.get('j_l'), GPIO.IN)
GPIO.setup(BUTTONS.get('j_r'), GPIO.IN)
GPIO.add_event_detect(BUTTONS.get('a'), GPIO.FALLING)
GPIO.add_event_detect(BUTTONS.get('b'), GPIO.FALLING)
GPIO.add_event_detect(BUTTONS.get('j_i'), GPIO.FALLING)
GPIO.add_event_detect(BUTTONS.get('j_u'), GPIO.FALLING)
GPIO.add_event_detect(BUTTONS.get('j_d'), GPIO.FALLING)
GPIO.add_event_detect(BUTTONS.get('j_l'), GPIO.FALLING)
GPIO.add_event_detect(BUTTONS.get('j_r'), GPIO.FALLING)
def teardown():
"""
Cleans up the GPIO handler.
"""
GPIO.cleanup()
def get_press(button):
"""
Returns true if a button has changed 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)

View File

@@ -1,82 +0,0 @@
import pygame
import importlib.util
import pocket_friends.game_files.io.gpio_handler as gpio_handler
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.
"""
def __init__(self, pygame_clock):
self.clock = pygame_clock
# If not on actual hardware, fake the GPIO in order to get it working correctly.
try:
importlib.util.find_spec('RPi.GPIO')
import RPi.GPIO as GPIO
self.on_hardware = True
except ImportError:
import pocket_friends.game_files.io.fake_gpio as GPIO
self.on_hardware = False
self.last_input_tick = 0
def create_event(self, pressed_button):
"""
Creates a pygame event with a given keyboard code
:param pressed_button:
"""
# Register a button click so long as the last button click happened no less than two frames ago
if pygame.time.get_ticks() - self.last_input_tick > self.clock.get_time() * 2 or not self.on_hardware:
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_gpio(self):
"""
Handles getting GPIO button presses and making a pygame event when a press is detected.
"""
for pressed_button in gpio_handler.BUTTONS:
code = gpio_handler.BUTTONS.get(pressed_button)
# Check if a button has been pressed. If it has, create a pygame event for it.
if gpio_handler.get_press(code):
self.create_event(code)
def keyboard_handler(self):
"""
Simulates key presses to GPIO button presses. Also handles quitting the game.
"""
# 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(gpio_handler.BUTTONS.get('a'))
if keyboard_event.key == pygame.K_b:
self.create_event(gpio_handler.BUTTONS.get('b'))
if keyboard_event.key == pygame.K_PERIOD:
self.create_event(gpio_handler.BUTTONS.get('j_i'))
if keyboard_event.key == pygame.K_RIGHT:
self.create_event(gpio_handler.BUTTONS.get('j_r'))
if keyboard_event.key == pygame.K_LEFT:
self.create_event(gpio_handler.BUTTONS.get('j_l'))
if keyboard_event.key == pygame.K_DOWN:
self.create_event(gpio_handler.BUTTONS.get('j_d'))
if keyboard_event.key == pygame.K_UP:
self.create_event(gpio_handler.BUTTONS.get('j_u'))
if keyboard_event.key == pygame.K_ESCAPE:
running = False
def update(self):
"""
Run the input handler and check for inputs.
"""
if self.on_hardware:
self.handle_gpio()
else:
self.keyboard_handler()

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -1,147 +0,0 @@
import pygame
from . import sprites
import pocket_friends.game_files.io.gpio_handler as gpio_handler
from ..io.input_handler import InputHandler
class Surface(pygame.Surface):
def __init__(self, window_size, resources_dir, game_fps, **kwargs):
super().__init__(window_size, pygame.SRCALPHA)
self.name = 'egg_select'
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()
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.clock.tick(self.game_fps)
self.blit(self.bg, (0, 0))
self.sprites.update()
self.sprites.draw(self)
self.input_handler.update()
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 == gpio_handler.BUTTONS.get('j_r'):
self.sel_right()
if event.key == gpio_handler.BUTTONS.get('j_l'):
self.sel_left()
if event.key == gpio_handler.BUTTONS.get('j_d'):
self.sel_down()
if event.key == gpio_handler.BUTTONS.get('j_u'):
self.sel_up()
if event.key == gpio_handler.BUTTONS.get('a'):
self.additional_args = {'selected_egg': self.selected_color}
self.next_surface = 'selection_info'
self.running = False

View File

@@ -1,60 +0,0 @@
import pygame
from . import sprites
import pocket_friends.game_files.io.gpio_handler as gpio_handler
from .sprites import SelectionEgg
from ..io.input_handler import InputHandler
class Surface(pygame.Surface):
def __init__(self, window_size, resources_dir, game_fps, **kwargs):
super().__init__(window_size, pygame.SRCALPHA)
self.name = 'selection_info'
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()
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, window_size[0], egg.description)
self.info_icons = sprites.EggInfo(resources_dir, egg.contentedness, egg.metabolism, (32, 4))
def update(self):
self.clock.tick(self.game_fps)
self.blit(self.bg, (0, 0))
self.sprites.update()
self.sprites.draw(self)
self.info_text.draw(self)
self.info_icons.draw(self)
self.input_handler.update()
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == gpio_handler.BUTTONS.get('j_d'):
# Scroll down on the info screen.
self.info_text.scroll_down()
if event.key == gpio_handler.BUTTONS.get('j_u'):
# Scroll up on the info screen.
self.info_text.scroll_up()
if event.key == gpio_handler.BUTTONS.get('a'):
pass
if event.key == gpio_handler.BUTTONS.get('b'):
self.running = False
self.additional_args = {'selected_color': self.selected_egg}
self.next_surface = 'egg_select'

View File

@@ -1,246 +0,0 @@
import pygame
import json
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, pygame.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 SelectionEgg(pygame.sprite.Sprite):
"""
Class for the eggs on the egg selection screen.
"""
def __init__(self, egg_color, resources_dir):
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):
"""
Updates the sprite object.
"""
# 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='Lorem ipsum dolor sit amet, consectetur adipiscing elit. '
'Nam commodo tempor aliquet. Suspendisse placerat accumsan'
' neque, nec volutpat nunc porta ut.'):
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):
"""
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, ((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.
:param surface: the surface to draw the icons on.
"""
# Blit the info onto the given surface.
surface.blit(self.surface, (self.x, self.y))

View File

@@ -1,28 +0,0 @@
import pygame
import time
class Surface(pygame.Surface):
def __init__(self, window_size, resources_dir, game_fps):
super().__init__(window_size, pygame.SRCALPHA)
self.name = 'title'
self.running = True
self.next_surface = None
self.clock = pygame.time.Clock()
self.additional_args = {}
self.bg = pygame.image.load(resources_dir + '/images/bg.png').convert_alpha()
self.title = pygame.image.load(resources_dir + '/images/title.png').convert_alpha()
self.frames = 1
self.game_fps = game_fps
self.delay = 1
def update(self):
self.clock.tick(self.game_fps)
self.blit(self.bg, (0, 0))
self.blit(self.title, (0, 0))
self.frames += 1
if self.frames > self.game_fps * self.delay:
self.next_surface = 'egg_select'
self.running = False

View File

View File

@@ -0,0 +1,90 @@
"""
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
}
hardware = {
'backlight': 37
}
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)
GPIO.setup(Constants.hardware.get('backlight'), GPIO.OUT, initial=GPIO.HIGH)
@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)
@staticmethod
def turn_on_backlight():
"""
Turns on the TFT backlight.
"""
GPIO.output(Constants.hardware.get('backlight'), GPIO.HIGH)
@staticmethod
def turn_off_backlight():
"""
Turns off the TFT backlight.
"""
GPIO.output(Constants.hardware.get('backlight'), GPIO.LOW)

46
pocket_friends/launch.py Normal file
View File

@@ -0,0 +1,46 @@
import os
from pathlib import Path
import argparse
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_main
def main():
"""
Launch script for Pocket Friends.
"""
# Creates the parser object.
parser = argparse.ArgumentParser()
# Adds parser arguments.
parser.add_argument('-D', '--delete-save', action='store_true', help='Deletes the save file if it exists.')
parser.add_argument('-s', '--size', type=int, default=240, help='Sets the size of the window.')
parser.add_argument('--dev', action='store_true', help='Deletes the save file if it exists.')
# Parse the arguments given
args = parser.parse_args()
# If given the delete-save argument, delete the safe file
if args.delete_save:
save_dir = os.path.join(Path.home(), '.pocket_friends')
# Remove the file if it exists
try:
os.remove(save_dir + '/save.json')
print('Save file deleted.')
except FileNotFoundError:
print('Save file does not exist, cannot delete.')
# Set the screen size
screen_size = int(args.size)
if not args.dev:
game_main(screen_size)
else:
dev_main()
# Cleanup
pygame.quit()
sys.exit()

View File

@@ -1 +1 @@
pygame~=2.1.2
pygame==1.9.4

View File

@@ -15,12 +15,12 @@ setuptools.setup(
license='GNU GPL-3.0',
long_description=long_description,
long_description_content_type='text/markdown',
url='https://gitea.citruxx.com/ndyer/pocket-friends',
url='https://github.com/nickedyer/pocket-friends',
packages=setuptools.find_packages(),
# https://pypi.org/classifiers/
classifiers=[
],
install_requires=required,
python_requires='>=3.10',
python_requires='>=3.6',
include_package_data=True,
)