diff options
| -rw-r--r-- | src/splitscreen_duo/__init__.py | 51 | ||||
| -rw-r--r-- | src/splitscreen_duo/command.py | 1 | ||||
| -rw-r--r-- | src/splitscreen_duo/game.py | 16 | ||||
| -rw-r--r-- | src/splitscreen_duo/games/benchmark.py | 65 | ||||
| -rw-r--r-- | src/splitscreen_duo/games/snake.py | 229 | ||||
| -rw-r--r-- | src/splitscreen_duo/menu.py | 2 |
6 files changed, 361 insertions, 3 deletions
diff --git a/src/splitscreen_duo/__init__.py b/src/splitscreen_duo/__init__.py index e47c7cb..8233caf 100644 --- a/src/splitscreen_duo/__init__.py +++ b/src/splitscreen_duo/__init__.py @@ -1,3 +1,4 @@ +from .game import Game from . import menu from .command import Command import os @@ -5,6 +6,8 @@ import logging import pygame from .serial import Serial import json +from .games import benchmark, snake + def main() -> int: logger = logging.getLogger(__name__) @@ -19,9 +22,9 @@ def main() -> int: level=(logging.DEBUG if os.getenv("DEVELOPMENT", "").lower() else logging.INFO) ) print("The Dual Screen Console") - logger.info(f"running as {os.getenv("INSTANCE", "unknown")}") + logger.info(f"running as {os.getenv('INSTANCE', 'unknown')}") menu.init_display() - + is_running = True while is_running: @@ -38,6 +41,31 @@ def main() -> int: pygame.quit() return 0 + elif message.get("command") == Command.SELECT_GAME.value: + if message.get("value") == Game.BENCHMARK.value: + logger.info("received benchmark game selection from primary") + + game_result = benchmark.main_loop(menu.screen, serial) + + if ( + game_result + and game_result.get("command") == Command.QUIT.value + ): + pygame.quit() + + return 0 + elif message.get("value") == Game.SNAKE.value: + logger.info("received snake game selection from primary") + + game_result = snake.main_loop(menu.screen, serial, INSTANCE) + + if ( + game_result + and game_result.get("command") == Command.QUIT.value + ): + pygame.quit() + + return 0 except json.JSONDecodeError: logger.error("failed to decode serial message") @@ -55,6 +83,25 @@ def main() -> int: pygame.quit() return 0 + elif serial_command.get("command") == Command.SELECT_GAME.value: + if serial_command.get("value") == Game.BENCHMARK.value: + logger.info("starting benchmark game") + + game_result = benchmark.main_loop(menu.screen, serial) + + if game_result and game_result.get("command") == Command.QUIT.value: + pygame.quit() + + return 0 + elif serial_command.get("value") == Game.SNAKE.value: + logger.info("starting snake game") + + game_result = snake.main_loop(menu.screen, serial, INSTANCE) + + if game_result and game_result.get("command") == Command.QUIT.value: + pygame.quit() + + return 0 menu.draw_menu() diff --git a/src/splitscreen_duo/command.py b/src/splitscreen_duo/command.py index df42a5a..df70b93 100644 --- a/src/splitscreen_duo/command.py +++ b/src/splitscreen_duo/command.py @@ -4,3 +4,4 @@ from enum import Enum class Command(Enum): QUIT = 0 SELECT_GAME = 1 + SCORE = 2 diff --git a/src/splitscreen_duo/game.py b/src/splitscreen_duo/game.py new file mode 100644 index 0000000..4c5ea45 --- /dev/null +++ b/src/splitscreen_duo/game.py @@ -0,0 +1,16 @@ +from enum import Enum + + +class Game(Enum): + BREAKOUT = "Breakout" + PONG = "Pong v.s. Computer" + SNAKE = "Snake" + BENCHMARK = "Benchmark" + + def all(): + return [ + Game.BREAKOUT.value, + Game.PONG.value, + Game.SNAKE.value, + Game.BENCHMARK.value, + ] diff --git a/src/splitscreen_duo/games/benchmark.py b/src/splitscreen_duo/games/benchmark.py new file mode 100644 index 0000000..3c8ea81 --- /dev/null +++ b/src/splitscreen_duo/games/benchmark.py @@ -0,0 +1,65 @@ +import pygame +import random + +from splitscreen_duo.command import Command + +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) +BALL_SIZE = 25 + + +class Ball: + def __init__(self, screen_width, screen_height): + self.x = random.randrange(BALL_SIZE, screen_width - BALL_SIZE) + self.y = random.randrange(BALL_SIZE, screen_height - BALL_SIZE) + self.change_x = random.randrange(-2, 3) + self.change_y = random.randrange(-2, 3) + + +def main_loop(screen, serial): + clock = pygame.time.Clock() + ball_list = [] + balls = 1 + ball = Ball(screen.get_width(), screen.get_height()) + is_running = True + + ball_list.append(ball) + + while is_running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return {"command": Command.QUIT.value, "action": None, "value": None} + + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_SPACE: + balls = balls * 2 + + for _ in range(balls - 1): + ball = Ball(screen.get_width(), screen.get_height()) + + ball_list.append(ball) + + elif event.key == pygame.K_ESCAPE: + return None + + for ball in ball_list: + ball.x += ball.change_x + ball.y += ball.change_y + + if ball.y > screen.get_height() - BALL_SIZE or ball.y < BALL_SIZE: + ball.change_y *= -1 + + if ball.x > screen.get_width() - BALL_SIZE or ball.x < BALL_SIZE: + ball.change_x *= -1 + + screen.fill(BLACK) + + for ball in ball_list: + pygame.draw.circle(screen, WHITE, [int(ball.x), int(ball.y)], BALL_SIZE) + + pygame.display.flip() + # clock.tick(60) + clock.tick() + # print(f"fps: {clock.get_fps():.2f}, balls: {balls}") + + return None diff --git a/src/splitscreen_duo/games/snake.py b/src/splitscreen_duo/games/snake.py new file mode 100644 index 0000000..64c6edd --- /dev/null +++ b/src/splitscreen_duo/games/snake.py @@ -0,0 +1,229 @@ +import pygame +import random +from ..command import Command +import logging +import json + +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) +RED = (255, 0, 0) +GREEN = (0, 255, 0) +BLOCK_SIZE = 20 +SPEED = 15 + +logger = logging.getLogger(__name__) + + +class Snake: + def __init__(self, screen_width, screen_height): + self.screen_width = screen_width + self.screen_height = screen_height + + self.reset() + + def reset(self): + self.body = [ + [ + self.screen_width // 2 - self.screen_width // 2 % BLOCK_SIZE, + self.screen_height // 2 - self.screen_height // 2 % BLOCK_SIZE, + ] + ] + self.direction = "RIGHT" + self.change_to = self.direction + self.score = 0 + self.food = self.spawn_food() + + def spawn_food(self): + while True: + food = [ + random.randrange(0, self.screen_width // BLOCK_SIZE) * BLOCK_SIZE, + random.randrange(0, self.screen_height // BLOCK_SIZE) * BLOCK_SIZE, + ] + + if food not in self.body: + return food + + def move(self): + if self.change_to == "UP" and self.direction != "DOWN": + self.direction = "UP" + if self.change_to == "DOWN" and self.direction != "UP": + self.direction = "DOWN" + if self.change_to == "LEFT" and self.direction != "RIGHT": + self.direction = "LEFT" + if self.change_to == "RIGHT" and self.direction != "LEFT": + self.direction = "RIGHT" + + head = [self.body[0][0], self.body[0][1]] + + if self.direction == "UP": + head[1] -= BLOCK_SIZE + if self.direction == "DOWN": + head[1] += BLOCK_SIZE + if self.direction == "LEFT": + head[0] -= BLOCK_SIZE + if self.direction == "RIGHT": + head[0] += BLOCK_SIZE + + self.body.insert(0, head) + + if head[0] == self.food[0] and head[1] == self.food[1]: + logger.debug( + f"snake ate food at {self.food}, score increased to {self.score + 1}" + ) + + self.score += 1 + self.food = self.spawn_food() + else: + self.body.pop() + + def check_collision(self): + head = self.body[0] + + if ( + head[0] < 0 + or head[0] >= self.screen_width + or head[1] < 0 + or head[1] >= self.screen_height + or head in self.body[1:] + ): + return True + + return False + + +def main_loop(screen, serial, instance): + clock = pygame.time.Clock() + snake = Snake(screen.get_width(), screen.get_height()) + font = pygame.font.Font(None, 36) + is_running = True + opponent_dead = False + my_score = 0 + opponent_score = 0 + waiting = False + score_display_time = 0 + + while is_running: + if waiting: + screen.fill(BLACK) + + text = font.render("Waiting for opponent...", True, WHITE) + + screen.blit( + text, + ( + screen.get_width() // 2 - text.get_width() // 2, + screen.get_height() // 2, + ), + ) + pygame.display.flip() + + if serial.in_waiting() > 0: + data = serial.readline().decode("utf-8").strip() + message = json.loads(data) + + if message.get("command") == Command.SCORE.value: + opponent_score = message.get("value", 0) + waiting = False + score_display_time = pygame.time.get_ticks() + elif message.get("command") == Command.QUIT.value: + return { + "command": Command.QUIT.value, + "action": None, + "value": None, + } + + elif score_display_time: + screen.fill(BLACK) + + my_text = font.render(f"Your Score: {my_score}", True, WHITE) + opp_text = font.render( + f"Your Opponent's Score: {opponent_score}", True, WHITE + ) + + screen.blit( + my_text, + ( + screen.get_width() // 2 - my_text.get_width() // 2, + screen.get_height() // 2 - 20, + ), + ) + screen.blit( + opp_text, + ( + screen.get_width() // 2 - opp_text.get_width() // 2, + screen.get_height() // 2 + 20, + ), + ) + pygame.display.flip() + + if pygame.time.get_ticks() - score_display_time > 3000: + return None + + else: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return { + "command": Command.QUIT.value, + "action": None, + "value": None, + } + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_UP: + snake.change_to = "UP" + elif event.key == pygame.K_DOWN: + snake.change_to = "DOWN" + elif event.key == pygame.K_LEFT: + snake.change_to = "LEFT" + elif event.key == pygame.K_RIGHT: + snake.change_to = "RIGHT" + elif event.key == pygame.K_ESCAPE: + return None + + if serial.in_waiting() > 0: + data = serial.readline().decode("utf-8").strip() + message = json.loads(data) + + if message.get("command") == Command.SCORE.value: + opponent_dead = True + opponent_score = message.get("value", 0) + + snake.move() + + if snake.check_collision(): + my_score = snake.score + + serial.write( + json.dumps( + { + "command": Command.SCORE.value, + "action": None, + "value": my_score, + } + ).encode("utf-8") + ) + + if opponent_dead: + score_display_time = pygame.time.get_ticks() + else: + waiting = True + + continue + + screen.fill(BLACK) + + for segment in snake.body: + pygame.draw.rect( + screen, GREEN, [segment[0], segment[1], BLOCK_SIZE, BLOCK_SIZE] + ) + + pygame.draw.rect( + screen, RED, [snake.food[0], snake.food[1], BLOCK_SIZE, BLOCK_SIZE] + ) + + score_text = font.render(f"Score: {snake.score}", True, WHITE) + + screen.blit(score_text, (10, 10)) + pygame.display.flip() + clock.tick(SPEED) + + return None diff --git a/src/splitscreen_duo/menu.py b/src/splitscreen_duo/menu.py index e1d97f5..a7bda82 100644 --- a/src/splitscreen_duo/menu.py +++ b/src/splitscreen_duo/menu.py @@ -3,9 +3,9 @@ import pygame import os from .input import Input from .command import Command +from .game import Game import logging import json -from .game import Game WHITE = (255, 255, 255) BLACK = (0, 0, 0) |