Compare commits
89 Commits
Author | SHA1 | Date | |
---|---|---|---|
3fde1996ca | |||
eef3a8756d | |||
8675caaca6 | |||
9fd2bc03df | |||
680a2cab75 | |||
9669e6185d | |||
8c49c62f66 | |||
30e25999c4 | |||
f765bb3dcb | |||
86e8c51494 | |||
f3751def40 | |||
4ba12d5078 | |||
e5c17b60e7 | |||
d7a01340b7 | |||
ad97725634 | |||
2def4f846a | |||
d4ff6d0786 | |||
7c49424350 | |||
1c467371a8 | |||
8a34ed7c01 | |||
b6432e233a | |||
4249a7c3f1 | |||
bcf933992e | |||
187baf6f3e | |||
e974687da3 | |||
bd2931ebe8 | |||
60757a23ff | |||
6a0201da85 | |||
06755ee937 | |||
83a7e98a7f | |||
41ab0406f8 | |||
4904ed2184 | |||
63a87c12dd | |||
2b2c2d946a | |||
0f69120213 | |||
35ac1cff51 | |||
38a5812839 | |||
f6e9869282 | |||
afd228d81a | |||
6debf73267 | |||
1fef87e1c3 | |||
e9df84897a | |||
07be003a54 | |||
c547f76b35 | |||
aa7bcccada | |||
e6cb2bd4c8 | |||
d932538b41 | |||
526b51b754 | |||
b8776a6f7f | |||
9b752bde21 | |||
8e9e96d1bb | |||
5d3fc6ca49 | |||
3fa4e2190e | |||
b65f112b36 | |||
5b2c724882 | |||
d8775fb1a9 | |||
4045930bd9 | |||
d322949b5a | |||
b97d54f49d | |||
65365adc3d | |||
f56f775541 | |||
509efa7e23 | |||
327f6cadee | |||
a8674f073e | |||
eff7f2c893 | |||
adfece1a32 | |||
07aae2b075 | |||
bba0af4946 | |||
dd621dbe7a | |||
bbf549c024 | |||
ca9406aab8 | |||
535aa993a5 | |||
4e14204392 | |||
7b8898b77b | |||
da5bacf950 | |||
fd7e1b7492 | |||
bbe97f0de7 | |||
296b6df4a0 | |||
2ffd9bd929 | |||
6f6eaa3c6d | |||
d66d56630d | |||
7073bbb73c | |||
f1c83ade97 | |||
787c1cc252 | |||
36487cad9e | |||
2987bfa7ed | |||
44986e62c0 | |||
6d28f0827f | |||
c25b021501 |
10
README.md
@ -1,11 +1,11 @@
|
||||
|
||||
![Pocket Friends](https://github.com/nickedyer/pocket-friends/blob/master/pocket_friends/game_files/resources/images/promotional.png?raw=true)
|
||||
![Pocket Friends](https://gitea.citruxx.com/ndyer/pocket-friends/raw/branch/master/pocket_friends/resources/images/promotional.png)
|
||||
|
||||
[![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://github.com/nickedyer/pocket-friends/releases)~~
|
||||
~~You can download the latest release of Pocket Friends on the [releases page.](https://gitea.citruxx.com/ndyer/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.6 or greater
|
||||
- Python 3.7
|
||||
- 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://github.com/nickedyer/pocket-friends.git`
|
||||
`pip install git+https://gitea.citruxx.com/ndyer/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://github.com/nickedyer/pocket-friends.git
|
||||
git clone https://gitea.citruxx.com/ndyer/pocket-friends.git
|
||||
cd pocket-friends
|
||||
pip install -r requirements.txt
|
||||
pip install pyinstaller
|
||||
|
@ -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',
|
||||
|
@ -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.1.0'
|
||||
|
@ -2,28 +2,26 @@
|
||||
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 arg == '--dev':
|
||||
enable_dev = True
|
||||
|
||||
if not enable_dev:
|
||||
game_main()
|
||||
else:
|
||||
dev_menu_main()
|
||||
game.start_game(resolution=resolution, dev=enable_dev)
|
||||
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
|
30
pocket_friends/_development/_dev.py
Normal file
@ -0,0 +1,30 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pygame
|
||||
|
||||
|
||||
def reboot_system():
|
||||
os.system('sudo reboot')
|
||||
|
||||
|
||||
def shutdown_system():
|
||||
os.system('sudo shutdown now')
|
||||
|
||||
|
||||
def update():
|
||||
pygame.quit()
|
||||
os.system('bash ~/update.sh')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def restart_app():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def show_black():
|
||||
return 'show_black'
|
||||
|
||||
|
||||
def quit():
|
||||
sys.exit(0)
|
34
pocket_friends/_development/_sprites.py
Normal file
@ -0,0 +1,34 @@
|
||||
import pygame
|
||||
|
||||
|
||||
class FunctionSelector:
|
||||
|
||||
def __init__(self, resources_dir, game_res, functions):
|
||||
self.font = pygame.font.Font(resources_dir + '/fonts/5Pts5.ttf', 10)
|
||||
self.functions = functions
|
||||
self.max_lines = 6 # Max number of lines to be shown on screen at a time.
|
||||
self.offset = 0
|
||||
self.game_res = game_res
|
||||
|
||||
self.selected = 0
|
||||
self.max_index = len(self.functions) - 1
|
||||
self.selector = pygame.image.load(resources_dir + '/images/dev_menu/selector.png').convert_alpha()
|
||||
|
||||
def draw(self, surface):
|
||||
for i in range(self.max_index + 1):
|
||||
text = self.font.render(self.functions[i], False, (0, 0, 0))
|
||||
surface.blit(text, (8, (-3 + (i * 6))))
|
||||
surface.blit(self.selector, (0, (self.selected * 6)))
|
||||
|
||||
def scroll_down(self):
|
||||
self.selected += 1
|
||||
if self.selected > self.max_index:
|
||||
self.selected = self.max_index
|
||||
|
||||
def scroll_up(self):
|
||||
self.selected -= 1
|
||||
if self.selected < 0:
|
||||
self.selected = 0
|
||||
|
||||
def get_function(self):
|
||||
return self.functions[self.selected]
|
33
pocket_friends/_development/surfaces/_black_screen.py
Normal file
@ -0,0 +1,33 @@
|
||||
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.game_fps = game_fps
|
||||
self.frames = -3 * 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 = '_dev_menu'
|
||||
|
||||
def update(self):
|
||||
self.preprocess()
|
||||
self.fill((0, 0, 0))
|
||||
|
||||
text_1 = self.font.render('Press B', False, (64, 64, 64))
|
||||
text_2 = self.font.render('to return', False, (64, 64, 64))
|
||||
|
||||
if self.frames < 0:
|
||||
self.blit(text_1, (3, 20))
|
||||
self.blit(text_2, (3, 40))
|
||||
|
||||
self.frames += 1
|
||||
if self.frames > self.game_fps:
|
||||
self.frames = 0
|
||||
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_b:
|
||||
self.running = False
|
58
pocket_friends/_development/surfaces/_dev_menu.py
Normal file
@ -0,0 +1,58 @@
|
||||
import pygame
|
||||
from pocket_friends.elements import surface
|
||||
from pocket_friends._development._sprites import FunctionSelector
|
||||
import pocket_friends._development._dev as dev
|
||||
|
||||
dev_functions = {
|
||||
'Restart': 'reboot_system',
|
||||
'Shutdown': 'shutdown_system',
|
||||
'Update': 'update',
|
||||
'Re-open App': 'restart_app',
|
||||
'Black screen': 'show_black',
|
||||
'!!! Close App': 'quit'
|
||||
}
|
||||
|
||||
|
||||
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.background = pygame.image.load(self.resource_dir + '/images/dev_menu/dev_bg.png').convert_alpha()
|
||||
|
||||
functions = []
|
||||
for key in dev_functions.keys():
|
||||
functions.append(key)
|
||||
|
||||
self.function_selector = FunctionSelector(resources_dir, game_res, functions)
|
||||
|
||||
def execute(self):
|
||||
|
||||
executing_function = getattr(dev, dev_functions.get(self.function_selector.get_function()))
|
||||
output = executing_function()
|
||||
if output == 'show_black':
|
||||
self.next_surface = '_black_screen'
|
||||
self.running = False
|
||||
|
||||
def update(self):
|
||||
self.preprocess()
|
||||
|
||||
text = self.font.render('f: {0}'.format(self.frames), False, (128, 128, 128))
|
||||
self.blit(text, (3, 68))
|
||||
self.function_selector.draw(self)
|
||||
|
||||
self.frames += 1
|
||||
self.frames %= self.game_fps
|
||||
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_UP:
|
||||
self.function_selector.scroll_up()
|
||||
if event.key == pygame.K_DOWN:
|
||||
self.function_selector.scroll_down()
|
||||
if event.key == pygame.K_a:
|
||||
self.execute()
|
||||
if event.key == pygame.K_b:
|
||||
self.running = False
|
28
pocket_friends/_development/surfaces/_error_screen.py
Normal file
@ -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
|
@ -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
|
@ -1,50 +0,0 @@
|
||||
"""
|
||||
Module to test the GPIO input on the Raspberry Pi.
|
||||
"""
|
||||
from collections import deque
|
||||
from ..hardware.gpio_handler import Constants, GPIOHandler
|
||||
|
||||
|
||||
def button_test():
|
||||
"""
|
||||
GPIO button test. Checks for a GPIO input and prints it out, simple as that.
|
||||
"""
|
||||
running = True
|
||||
|
||||
# Exit code used to quit the button test.
|
||||
exit_code = deque()
|
||||
for button in ['j_d', 'j_d', 'j_u', 'j_u', 'j_d', 'j_d', 'j_u', 'j_u', 'a', 'a', 'b']:
|
||||
exit_code.append(button)
|
||||
|
||||
# Input log to check for quitting out of the button test.
|
||||
input_log = deque()
|
||||
|
||||
GPIOHandler.setup()
|
||||
|
||||
def log(pressed_button):
|
||||
"""
|
||||
Logs the pressed button into the input log.
|
||||
:param pressed_button:
|
||||
"""
|
||||
input_log.append(pressed_button)
|
||||
if len(input_log) > len(exit_code): # Don't let the input log exceed the length of the exit code.
|
||||
input_log.popleft()
|
||||
|
||||
def check_exit():
|
||||
"""
|
||||
Check if the input log and the exit code are the same. If they are, quit the button test.
|
||||
"""
|
||||
nonlocal running
|
||||
|
||||
if exit_code == input_log:
|
||||
running = False
|
||||
|
||||
while running:
|
||||
for button in Constants.buttons:
|
||||
code = Constants.buttons.get(button)
|
||||
if GPIOHandler.get_press(code): # If a button is pressed, print it out and do a quit check.
|
||||
print('event: {0}'.format(button))
|
||||
log(button)
|
||||
check_exit()
|
||||
|
||||
GPIOHandler.teardown()
|
@ -1,152 +0,0 @@
|
||||
"""
|
||||
Development menu for the hardware on Raspberry Pi. NOTE: THIS DOES NOTHING ON A COMPUTER!
|
||||
"""
|
||||
import pocket_friends.game_files.game
|
||||
import importlib.util
|
||||
import os
|
||||
import pygame
|
||||
import time
|
||||
from .button_test import button_test
|
||||
from .menus import Menu
|
||||
from ..hardware.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
|
||||
|
||||
# Global variable to keep track of the current menu.
|
||||
menu = 'main'
|
||||
|
||||
|
||||
def run_button_test():
|
||||
"""
|
||||
Runs the GPIO button test.
|
||||
"""
|
||||
GPIOHandler.teardown()
|
||||
button_test()
|
||||
GPIOHandler.setup()
|
||||
|
||||
|
||||
def clear_screen():
|
||||
"""
|
||||
Clears the screen.
|
||||
"""
|
||||
print("\n" * 20)
|
||||
|
||||
|
||||
def start_game():
|
||||
"""
|
||||
Cleans the GPIO and starts the hardware.
|
||||
"""
|
||||
GPIOHandler.teardown()
|
||||
pocket_friends.game_files.game.main()
|
||||
pygame.quit()
|
||||
GPIOHandler.setup()
|
||||
|
||||
|
||||
def quit_menu():
|
||||
"""
|
||||
Quits the menu.
|
||||
"""
|
||||
exit(0)
|
||||
|
||||
|
||||
def quit_with_error():
|
||||
"""
|
||||
Quits the menu with error code 3.
|
||||
"""
|
||||
exit(3)
|
||||
|
||||
|
||||
def change_menu(new_menu):
|
||||
"""
|
||||
Changes the global menu variable for the dev menu
|
||||
:param new_menu: the menu to change to
|
||||
"""
|
||||
global menu
|
||||
menu = new_menu
|
||||
clear_screen()
|
||||
print('...')
|
||||
time.sleep(0.75)
|
||||
|
||||
|
||||
def shutdown():
|
||||
"""
|
||||
Shuts down the linux system.
|
||||
"""
|
||||
os.system('sudo shutdown now')
|
||||
|
||||
|
||||
def restart():
|
||||
"""
|
||||
Restarts the linux system.
|
||||
"""
|
||||
os.system('sudo reboot')
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Starts the dev menu.
|
||||
"""
|
||||
|
||||
# The following defines all of the options in the various different menus.
|
||||
|
||||
main_menu = Menu('Pocket Friends Dev Menu')
|
||||
main_menu.add_option(Menu.Option('Start Game', start_game))
|
||||
main_menu.add_option(Menu.Option('Button Test', run_button_test))
|
||||
main_menu.add_option(Menu.Option('Restart Dev Menu', quit_with_error))
|
||||
main_menu.add_option(Menu.Option('Shutdown Pi', change_menu, 'shutdown'))
|
||||
main_menu.add_option(Menu.Option('Restart Pi', change_menu, 'restart'))
|
||||
main_menu.add_option(Menu.Option('Quit Dev Menu', change_menu, 'quit'))
|
||||
|
||||
shutdown_confirm = Menu('Are you sure you want to shutdown?')
|
||||
shutdown_confirm.add_option(Menu.Option('No', change_menu, 'main'))
|
||||
shutdown_confirm.add_option(Menu.Option('Yes', shutdown))
|
||||
|
||||
restart_confirm = Menu('Are you sure you want to restart?')
|
||||
restart_confirm.add_option(Menu.Option('No', change_menu, 'main'))
|
||||
restart_confirm.add_option(Menu.Option('Yes', restart))
|
||||
|
||||
quit_confirm = Menu('Are you sure you want to exit?')
|
||||
quit_confirm.add_option(Menu.Option('No', change_menu, 'main'))
|
||||
quit_confirm.add_option(Menu.Option('Yes', quit_menu))
|
||||
|
||||
GPIOHandler.setup()
|
||||
|
||||
def menu_handler(current_menu):
|
||||
"""
|
||||
Draws the menu and handles the GPIO inputs
|
||||
:param current_menu: the current menu being drawn on the screen
|
||||
"""
|
||||
current_menu.draw_menu()
|
||||
|
||||
while True: # Main GPIO input loop
|
||||
|
||||
# Limits how often the program checks for a GPIO input. Eases CPU usage.
|
||||
time.sleep(0.125)
|
||||
|
||||
if GPIOHandler.get_press(Constants.buttons.get('j_d')):
|
||||
current_menu.select_next()
|
||||
break
|
||||
if GPIOHandler.get_press(Constants.buttons.get('j_u')):
|
||||
current_menu.select_prev()
|
||||
break
|
||||
if GPIOHandler.get_press(Constants.buttons.get('a')):
|
||||
current_menu.run_selection()
|
||||
break
|
||||
|
||||
while True: # Loop for drawing the menus.
|
||||
|
||||
while menu == 'main':
|
||||
menu_handler(main_menu)
|
||||
|
||||
while menu == 'shutdown':
|
||||
menu_handler(shutdown_confirm)
|
||||
|
||||
while menu == 'restart':
|
||||
menu_handler(restart_confirm)
|
||||
|
||||
while menu == 'quit':
|
||||
menu_handler(quit_confirm)
|
@ -1,120 +0,0 @@
|
||||
"""
|
||||
Menu class to help with drawing menus on screen
|
||||
"""
|
||||
|
||||
|
||||
class Menu:
|
||||
"""
|
||||
Menu class. Creates a menu with text to display and options
|
||||
"""
|
||||
|
||||
def __init__(self, menu_text=''):
|
||||
self._menu_text = menu_text
|
||||
self._options = []
|
||||
self._selection = 0
|
||||
|
||||
def add_option(self, option):
|
||||
"""
|
||||
Adds an option to the menu. Only allows instances of Menu.Option
|
||||
:param option:
|
||||
"""
|
||||
if not isinstance(option, Menu.Option):
|
||||
raise TypeError('option must be an instance of Menu.Option')
|
||||
else:
|
||||
self._options.append(option)
|
||||
|
||||
def get_option(self, index):
|
||||
"""
|
||||
Gets an option object given the index of the option. Raises IndexError if given
|
||||
index is out of bounds.
|
||||
:param index: the index of the option
|
||||
:return: the option object
|
||||
"""
|
||||
try:
|
||||
return self._options[index]
|
||||
except IndexError as ex:
|
||||
raise IndexError('option index out of range') from ex
|
||||
|
||||
def select_next(self):
|
||||
"""
|
||||
Selects the next option in the list. Wraps around to the first option if
|
||||
the last option is currently selected.
|
||||
"""
|
||||
self._selection += 1
|
||||
if self._selection >= len(self._options):
|
||||
self._selection = 0
|
||||
|
||||
def select_prev(self):
|
||||
"""
|
||||
Selects the previous option in the list. Wraps around to the last option if
|
||||
the first option is currently selected.
|
||||
"""
|
||||
self._selection -= 1
|
||||
if self._selection < 0:
|
||||
if len(self._options) > 0:
|
||||
self._selection = len(self._options) - 1
|
||||
else:
|
||||
self._selection = 0
|
||||
|
||||
def run_selection(self, *args, **kwargs):
|
||||
"""
|
||||
Runs the function that the currently selected option object points to.
|
||||
:param args: arguments to be passed to the function
|
||||
:param kwargs: keyword arguments to be passed to the function
|
||||
"""
|
||||
try:
|
||||
return self._options[self._selection].run(*args, **kwargs)
|
||||
except IndexError as ex:
|
||||
raise Exception('menu has no options, cannot run a non-existent option') from ex
|
||||
|
||||
def draw_menu(self):
|
||||
"""
|
||||
Draws the menu on screen with a leading 20 blank lines.
|
||||
"""
|
||||
print('\n' * 20)
|
||||
|
||||
print(self._menu_text + '\n')
|
||||
|
||||
for option in self._options:
|
||||
selection_char = '>'
|
||||
|
||||
if self._options.index(option) != self._selection:
|
||||
selection_char = ' '
|
||||
|
||||
print('{0} {1}'.format(selection_char, option.get_text()))
|
||||
|
||||
class Option:
|
||||
"""
|
||||
Class that defines options for the Menu class.
|
||||
"""
|
||||
|
||||
def __init__(self, option_text='', function=None, *args, **kwargs):
|
||||
self._option_text = option_text
|
||||
self._function = function
|
||||
self._default_args = args
|
||||
self._default_kwargs = kwargs
|
||||
|
||||
def get_text(self):
|
||||
"""
|
||||
Returns the text to be displayed by the option
|
||||
:return: the option text
|
||||
"""
|
||||
return self._option_text
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
"""
|
||||
Runs the function that the option object points to. Returns None if
|
||||
there is no function or the given function is not valid.
|
||||
:param args: arguments to be passed to the function
|
||||
:param kwargs: keyword arguments to be passed to the function
|
||||
:return: the return value of the function (if any)
|
||||
"""
|
||||
if len(args) == 0:
|
||||
args = self._default_args
|
||||
if len(kwargs) == 0:
|
||||
kwargs = self._default_kwargs
|
||||
|
||||
try:
|
||||
return self._function(*args, **kwargs)
|
||||
except TypeError:
|
||||
return None
|
1
pocket_friends/elements/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Submodule for use in the game. Helps with the drawing of various objects such as sprites and text boxes."""
|
277
pocket_friends/elements/sprites.py
Normal file
@ -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))
|
58
pocket_friends/elements/surface.py
Normal file
@ -0,0 +1,58 @@
|
||||
"""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.
|
||||
|
||||
The GameSurface class is what the game uses to draw all other elements on top of. It controls a number of game
|
||||
operations, including input handling, frame rate limiting, and surface switching. It is a child class of Pygame's
|
||||
Surface class and utilizes Pygame's sprite handling along with the ability to blit certain things on the screen
|
||||
in order to draw elements easily.
|
||||
|
||||
Attributes:
|
||||
running: Boolean to tell whether the surface is running or not.
|
||||
next_surface: String that represents the next surface to be switched to after halting.
|
||||
resource_dir: String that contains the path of the game's main resource directory.
|
||||
game_fps: An integer value of how many frames per second the game will run at.
|
||||
additional_args: Dictionary of additional arguments to send to the next surface after halting.
|
||||
background: A Pygame surface that will be drawn behind all other elements.
|
||||
"""
|
||||
def __init__(self, game_res: tuple, resources_dir: str, game_fps: int):
|
||||
"""
|
||||
Create a GameSurface object.
|
||||
Args:
|
||||
game_res: The internal resolution of the surface.
|
||||
resources_dir: The path of the game's main resource directory.
|
||||
game_fps: How many frames per second the game will run at.
|
||||
"""
|
||||
super().__init__(game_res, pygame.SRCALPHA)
|
||||
self.running = True # Surfaces should be running by default
|
||||
self.next_surface = None
|
||||
self.resource_dir = resources_dir
|
||||
self.game_fps = game_fps
|
||||
self.additional_args = {}
|
||||
self.dev_override = False
|
||||
|
||||
self.background = pygame.image.load(self.resource_dir + '/images/bg.png').convert_alpha()
|
||||
|
||||
self._clock = pygame.time.Clock()
|
||||
self._input_handler = InputHandler(self._clock)
|
||||
self._sprites = pygame.sprite.Group()
|
||||
|
||||
def preprocess(self):
|
||||
"""
|
||||
Draw the surface background and advance all the surface's sprites by one frame; Run the input handler.
|
||||
"""
|
||||
self._clock.tick(self.game_fps)
|
||||
|
||||
self.blit(self.background, (0, 0))
|
||||
self._sprites.update()
|
||||
self._sprites.draw(self)
|
||||
|
||||
self._input_handler.update()
|
||||
if self._input_handler.dev_found:
|
||||
self.dev_override = True
|
||||
self.running = False
|
95
pocket_friends/game.py
Normal file
@ -0,0 +1,95 @@
|
||||
"""Main module for the game. Runs setting up the Pygame window and handles scene switching."""
|
||||
|
||||
import pygame
|
||||
import os
|
||||
import pocket_friends
|
||||
import importlib
|
||||
|
||||
valid_surfaces = [
|
||||
'_dev_menu',
|
||||
'_black_screen',
|
||||
'_error_screen',
|
||||
'title',
|
||||
'egg_select',
|
||||
'selection_info',
|
||||
]
|
||||
|
||||
# Add all the surface modules to a dictionary for easy switching
|
||||
surface_modules = {}
|
||||
for module in valid_surfaces:
|
||||
if module[0] == '_':
|
||||
surface_modules[module] = importlib.import_module('pocket_friends._development.surfaces.{0}'.format(module))
|
||||
else:
|
||||
surface_modules[module] = importlib.import_module('pocket_friends.surfaces.{0}'.format(module))
|
||||
|
||||
# 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: int, dev: bool):
|
||||
"""
|
||||
Starts the game.
|
||||
|
||||
Args:
|
||||
resolution: Resolution to display the game at.
|
||||
dev: Boolean to enable the developer menu at start or not
|
||||
"""
|
||||
|
||||
pygame.init()
|
||||
|
||||
# Hide the cursor for the Pi display.
|
||||
pygame.mouse.set_visible(False)
|
||||
|
||||
if dev:
|
||||
starting_surface = '_dev_menu'
|
||||
else:
|
||||
starting_surface = 'title'
|
||||
|
||||
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())
|
||||
|
||||
# When the current surface is not running, check to make sure that the next surface will be valid
|
||||
if not surface.running:
|
||||
# Force the dev menu to appear if the flag has been passed
|
||||
if surface.dev_override:
|
||||
next_surface = '_dev_menu'
|
||||
else:
|
||||
next_surface = surface.next_surface
|
||||
|
||||
# Send to the error screen if the given surface isn't a valid one
|
||||
if next_surface not in valid_surfaces:
|
||||
next_surface = '_error_screen'
|
||||
|
||||
# Get the additional args to pass on from the ending surface to the next one
|
||||
additional_args = surface.additional_args
|
||||
# Create the new surface and pass through the additional argss
|
||||
surface = surface_modules.get(next_surface).Surface((game_res, game_res), resources_dir,
|
||||
game_fps, **additional_args)
|
||||
pygame.display.flip()
|
||||
|
||||
pygame.quit()
|
@ -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 = 320
|
||||
|
||||
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()
|
@ -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)
|
2
pocket_friends/io/__init__.py
Normal file
@ -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."""
|
55
pocket_friends/io/data.py
Normal file
@ -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()
|
83
pocket_friends/io/input_handler.py
Normal file
@ -0,0 +1,83 @@
|
||||
import pygame
|
||||
from collections import deque
|
||||
|
||||
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
|
||||
self.dev_check = deque()
|
||||
self.dev_code = deque()
|
||||
for button in [pygame.K_DOWN, pygame.K_DOWN, pygame.K_UP, pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT,
|
||||
pygame.K_RIGHT, pygame.K_LEFT, pygame.K_a]:
|
||||
self.dev_code.append(button)
|
||||
self.dev_found = False
|
||||
|
||||
def create_event(self, pressed_button):
|
||||
"""
|
||||
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}))
|
||||
self.dev_check.append(pressed_button)
|
||||
if len(self.dev_check) > len(self.dev_code):
|
||||
self.dev_check.popleft()
|
||||
else:
|
||||
pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {'key': pressed_button}))
|
||||
pygame.event.post(pygame.event.Event(pygame.KEYUP, {'key': pressed_button}))
|
||||
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()
|
||||
if self.dev_code == self.dev_check:
|
||||
self.dev_found = True
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 807 B After Width: | Height: | Size: 807 B |
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 610 B |
Before Width: | Height: | Size: 820 B After Width: | Height: | Size: 820 B |
Before Width: | Height: | Size: 740 B After Width: | Height: | Size: 740 B |
Before Width: | Height: | Size: 490 B After Width: | Height: | Size: 490 B |
Before Width: | Height: | Size: 736 B After Width: | Height: | Size: 736 B |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 422 B After Width: | Height: | Size: 422 B |
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 565 B |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 864 B After Width: | Height: | Size: 864 B |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
BIN
pocket_friends/resources/images/dev_menu/dev_bg.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
pocket_friends/resources/images/dev_menu/selector.png
Normal file
After Width: | Height: | Size: 839 B |
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 246 B |
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 218 B |
Before Width: | Height: | Size: 188 B After Width: | Height: | Size: 188 B |
Before Width: | Height: | Size: 178 B After Width: | Height: | Size: 178 B |
Before Width: | Height: | Size: 217 B After Width: | Height: | Size: 217 B |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 419 B After Width: | Height: | Size: 419 B |
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 268 B |
Before Width: | Height: | Size: 350 B After Width: | Height: | Size: 350 B |
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 235 B |
Before Width: | Height: | Size: 302 B After Width: | Height: | Size: 302 B |
Before Width: | Height: | Size: 258 B After Width: | Height: | Size: 258 B |
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 213 B |
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 213 B |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
129
pocket_friends/surfaces/egg_select.py
Normal file
@ -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
|
63
pocket_friends/surfaces/selection_info.py
Normal file
@ -0,0 +1,63 @@
|
||||
"""Module for selection info. Shows egg stats after an egg has been selected from the starting
|
||||
screen. Contains the surface object to be rendered."""
|
||||
|
||||
import pygame
|
||||
from pocket_friends.elements import sprites
|
||||
from pocket_friends.elements import surface
|
||||
|
||||
|
||||
class Surface(surface.GameSurface):
|
||||
"""
|
||||
Surface object for the selection info screen. Contains the selected egg, egg properties, and description in a
|
||||
scrollable text box. Child class of surface.GameSurface.
|
||||
"""
|
||||
def __init__(self, game_res: tuple, resources_dir: str, game_fps: int, **kwargs: str):
|
||||
"""
|
||||
Create a selection info surface object.
|
||||
|
||||
Args:
|
||||
game_res: The internal resolution of the surface.
|
||||
resources_dir: The path of the game's main resource directory.
|
||||
game_fps: How many frames per second the game will run at.
|
||||
|
||||
Keyword Args:
|
||||
selected_egg: The egg that was selected by the previous screen.
|
||||
"""
|
||||
super().__init__(game_res, resources_dir, game_fps)
|
||||
|
||||
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):
|
||||
"""
|
||||
Draw objects on the screen and advance the surface logic by one frame; Handle user input.
|
||||
"""
|
||||
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:
|
||||
self._info_text.scroll_down()
|
||||
if event.key == pygame.K_UP:
|
||||
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'
|
37
pocket_friends/surfaces/title.py
Normal file
@ -0,0 +1,37 @@
|
||||
"""Title screen module. Contains the surface object to be rendered on the screen."""
|
||||
import pygame
|
||||
from pocket_friends.elements import surface
|
||||
|
||||
|
||||
class Surface(surface.GameSurface):
|
||||
"""
|
||||
Surface object for the title screen. Child class of surface.GameSurface.
|
||||
"""
|
||||
def __init__(self, game_res: tuple, resources_dir: str, game_fps: int):
|
||||
"""
|
||||
Create a title screen surface object.
|
||||
Args:
|
||||
game_res: The internal resolution of the surface.
|
||||
resources_dir: The path of the game's main resource directory.
|
||||
game_fps: How many frames per second the game will run at.
|
||||
"""
|
||||
|
||||
super().__init__(game_res, resources_dir, game_fps)
|
||||
|
||||
self._frames = 0 # Counter for the number of frames passed
|
||||
self._delay = 1 # Delay in seconds that the title should stay on screen
|
||||
|
||||
# Image of the title screen
|
||||
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))
|
||||
|
||||
self._frames += 1
|
||||
if self._frames > self.game_fps * self._delay:
|
||||
self.next_surface = 'egg_select'
|
||||
self.running = False
|
@ -1 +1 @@
|
||||
pygame==1.9.4
|
||||
pygame~=1.9.6
|
4
setup.py
@ -15,12 +15,12 @@ setuptools.setup(
|
||||
license='GNU GPL-3.0',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
url='https://github.com/nickedyer/pocket-friends',
|
||||
url='https://gitea.citruxx.com/ndyer/pocket-friends',
|
||||
packages=setuptools.find_packages(),
|
||||
# https://pypi.org/classifiers/
|
||||
classifiers=[
|
||||
],
|
||||
install_requires=required,
|
||||
python_requires='>=3.6',
|
||||
python_requires='>=3.7',
|
||||
include_package_data=True,
|
||||
)
|
||||
|