diff options
| author | Zoltan Szabatin <[email protected]> | 2025-03-02 19:01:12 -0800 |
|---|---|---|
| committer | Zoltan Szabatin <[email protected]> | 2025-03-02 19:01:12 -0800 |
| commit | 3ba60ffec522cd35c137573f666aaea3ee899c7c (patch) | |
| tree | c7e44a62f4dd701669aee1f318ed17aff28ae858 /src | |
| parent | feat: Add breakout game (diff) | |
| download | splitscreen-duo-3ba60ffec522cd35c137573f666aaea3ee899c7c.tar.xz splitscreen-duo-3ba60ffec522cd35c137573f666aaea3ee899c7c.zip | |
refactor: Create base game class for games
Diffstat (limited to 'src')
| -rw-r--r-- | src/splitscreen_duo/__init__.py | 16 | ||||
| -rw-r--r-- | src/splitscreen_duo/games/breakout.py | 193 | ||||
| -rw-r--r-- | src/splitscreen_duo/games/game_base.py | 140 | ||||
| -rw-r--r-- | src/splitscreen_duo/games/snake.py | 188 |
4 files changed, 260 insertions, 277 deletions
diff --git a/src/splitscreen_duo/__init__.py b/src/splitscreen_duo/__init__.py index 581fe81..9fef2eb 100644 --- a/src/splitscreen_duo/__init__.py +++ b/src/splitscreen_duo/__init__.py @@ -6,7 +6,9 @@ import logging import pygame from .serial import Serial import json -from .games import benchmark, snake, breakout +from .games import benchmark +from .games.snake import Snake +from .games.breakout import Breakout def main() -> int: @@ -57,7 +59,8 @@ def main() -> int: elif message.get("value") == Game.SNAKE.value: logger.info("received snake game selection from primary") - game_result = snake.main_loop(menu.screen, serial, INSTANCE) + snake_game = Snake(menu.screen, serial, INSTANCE) + game_result = snake_game.main_loop() if ( game_result @@ -69,7 +72,8 @@ def main() -> int: elif message.get("value") == Game.BREAKOUT.value: logger.info("received breakout game selection from primary") - game_result = breakout.main_loop(menu.screen, serial, INSTANCE) + breakout_game = Breakout(menu.screen, serial, INSTANCE) + game_result = breakout_game.main_loop() if ( game_result @@ -108,7 +112,8 @@ def main() -> int: elif serial_command.get("value") == Game.SNAKE.value: logger.info("starting snake game") - game_result = snake.main_loop(menu.screen, serial, INSTANCE) + snake_game = Snake(menu.screen, serial, INSTANCE) + game_result = snake_game.main_loop() if game_result and game_result.get("command") == Command.QUIT.value: pygame.quit() @@ -117,7 +122,8 @@ def main() -> int: elif serial_command.get("value") == Game.BREAKOUT.value: logger.info("starting breakout game") - game_result = breakout.main_loop(menu.screen, serial, INSTANCE) + breakout_game = Breakout(menu.screen, serial, INSTANCE) + game_result = breakout_game.main_loop() if game_result and game_result.get("command") == Command.QUIT.value: pygame.quit() diff --git a/src/splitscreen_duo/games/breakout.py b/src/splitscreen_duo/games/breakout.py index bb42fa8..950adf5 100644 --- a/src/splitscreen_duo/games/breakout.py +++ b/src/splitscreen_duo/games/breakout.py @@ -1,8 +1,8 @@ import pygame import random from ..command import Command +from .game_base import GameBase import logging -import json BLACK = (0, 0, 0) WHITE = (255, 255, 255) @@ -20,10 +20,11 @@ SPEED = 5 logger = logging.getLogger(__name__) -class Breakout: - def __init__(self, screen_width, screen_height): - self.screen_width = screen_width - self.screen_height = screen_height +class Breakout(GameBase): + def __init__(self, screen, serial, instance): + super().__init__(screen, serial, instance) + self.screen_width = screen.get_width() + self.screen_height = screen.get_height() self.reset() @@ -98,146 +99,66 @@ class Breakout: def check_game_over(self): if self.ball[1] >= self.screen_height: return "lose" + if not self.bricks: return "win" return None + def main_loop(self): + clock = pygame.time.Clock() -def main_loop(screen, serial, instance): - clock = pygame.time.Clock() - breakout = Breakout(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_LEFT: - breakout.move_paddle(-20) - elif event.key == pygame.K_RIGHT: - breakout.move_paddle(20) - elif event.key == pygame.K_ESCAPE: - return None - - breakout.paddle[0] = pygame.mouse.get_pos()[0] - PADDLE_WIDTH // 2 - - breakout.move_paddle(0) - - 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) - - breakout.move_ball() - - game_over = breakout.check_game_over() - - if game_over: - my_score = breakout.score - - serial.write( - json.dumps( - { - "command": Command.SCORE.value, - "action": None, - "value": my_score, - } - ).encode("utf-8") - ) + while self.is_running: + result = self.update() + + if result is not None: + return result + + if not (self.waiting or self.score_display_time): + for event in pygame.event.get(): + result = self.handle_common_events(event) + + if result is not None: + return result + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_LEFT: + self.move_paddle(-20) + elif event.key == pygame.K_RIGHT: + self.move_paddle(20) + + self.paddle[0] = pygame.mouse.get_pos()[0] - PADDLE_WIDTH // 2 - if opponent_dead: - score_display_time = pygame.time.get_ticks() - else: - waiting = True - continue + self.move_paddle(0) + self.check_serial() + self.move_ball() - screen.fill(BLACK) - pygame.draw.rect( - screen, - BLUE, - [breakout.paddle[0], breakout.paddle[1], PADDLE_WIDTH, PADDLE_HEIGHT], - ) - pygame.draw.circle( - screen, WHITE, [int(breakout.ball[0]), int(breakout.ball[1])], BALL_SIZE - ) + game_over = self.check_game_over() - for brick in breakout.bricks: - pygame.draw.rect(screen, RED, brick) + if game_over: + self.end_game(self.score) - score_text = font.render(f"Score: {breakout.score}", True, WHITE) + continue - screen.blit(score_text, (10, 10)) - pygame.display.flip() - clock.tick(60) + self.screen.fill(BLACK) + pygame.draw.rect( + self.screen, + BLUE, + [self.paddle[0], self.paddle[1], PADDLE_WIDTH, PADDLE_HEIGHT], + ) + pygame.draw.circle( + self.screen, + WHITE, + [int(self.ball[0]), int(self.ball[1])], + BALL_SIZE, + ) + + for brick in self.bricks: + pygame.draw.rect(self.screen, RED, brick) - return None + score_text = self.font.render(f"Score: {self.score}", True, WHITE) + + self.screen.blit(score_text, (10, 10)) + pygame.display.flip() + clock.tick(60) + + return None diff --git a/src/splitscreen_duo/games/game_base.py b/src/splitscreen_duo/games/game_base.py new file mode 100644 index 0000000..ff45686 --- /dev/null +++ b/src/splitscreen_duo/games/game_base.py @@ -0,0 +1,140 @@ +import pygame +import json +from ..command import Command +import logging + +logger = logging.getLogger(__name__) + + +class GameBase: + def __init__(self, screen, serial, instance): + self.screen = screen + self.serial = serial + self.instance = instance + self.font = pygame.font.Font(None, 36) + self.is_running = True + self.opponent_dead = False + self.my_score = 0 + self.opponent_score = 0 + self.waiting = False + self.score_display_time = 0 + self.BLACK = (0, 0, 0) + self.WHITE = (255, 255, 255) + + def handle_common_events(self, event): + if event.type == pygame.QUIT: + return {"command": Command.QUIT.value, "action": None, "value": None} + elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: + return None + + return None + + def check_serial(self): + if self.serial.in_waiting() > 0: + data = self.serial.readline().decode("utf-8").strip() + + logger.debug(f"received serial data during check_serial: {data}") + + message = json.loads(data) + + if message.get("command") == Command.SCORE.value: + self.opponent_dead = True + self.opponent_score = message.get("value", 0) + + return True + elif message.get("command") == Command.QUIT.value: + return {"command": Command.QUIT.value, "action": None, "value": None} + return False + + def end_game(self, score): + self.my_score = score + + logger.debug(f"ending game, sending score: {self.my_score}") + + self.serial.write( + json.dumps( + {"command": Command.SCORE.value, "action": None, "value": self.my_score} + ).encode("utf-8") + ) + + if self.opponent_dead: + self.score_display_time = pygame.time.get_ticks() + + logger.debug("opponent already dead, starting score display") + else: + self.waiting = True + + logger.debug("waiting for opponent to finish") + + def update(self): + if self.waiting: + self.screen.fill(self.BLACK) + + text = self.font.render("waiting for opponent ...", True, self.WHITE) + + self.screen.blit( + text, + ( + self.screen.get_width() // 2 - text.get_width() // 2, + self.screen.get_height() // 2, + ), + ) + pygame.display.flip() + + if self.serial.in_waiting() > 0: + data = self.serial.readline().decode("utf-8").strip() + + logger.debug(f"received serial data while waiting: {data}") + + message = json.loads(data) + + if message.get("command") == Command.SCORE.value: + self.opponent_score = message.get("value", 0) + self.waiting = False + self.score_display_time = pygame.time.get_ticks() + + logger.debug("received opponent's score, starting score display") + elif message.get("command") == Command.QUIT.value: + logger.info("received quit command while waiting") + + return { + "command": Command.QUIT.value, + "action": None, + "value": None, + } + + elif self.score_display_time: + self.screen.fill(self.BLACK) + + my_text = self.font.render(f"Your Score: {self.my_score}", True, self.WHITE) + opp_text = self.font.render( + f"Your Opponent's Score: {self.opponent_score}", True, self.WHITE + ) + + self.screen.blit( + my_text, + ( + self.screen.get_width() // 2 - my_text.get_width() // 2, + self.screen.get_height() // 2 - 20, + ), + ) + self.screen.blit( + opp_text, + ( + self.screen.get_width() // 2 - opp_text.get_width() // 2, + self.screen.get_height() // 2 + 20, + ), + ) + pygame.display.flip() + + if pygame.time.get_ticks() - self.score_display_time > 3000: + logger.debug("score display timeout reached, exiting game") + + self.is_running = False + + return None + + return None + + def main_loop(self): + raise NotImplementedError diff --git a/src/splitscreen_duo/games/snake.py b/src/splitscreen_duo/games/snake.py index 64c6edd..ac624e0 100644 --- a/src/splitscreen_duo/games/snake.py +++ b/src/splitscreen_duo/games/snake.py @@ -1,8 +1,8 @@ import pygame import random from ..command import Command +from .game_base import GameBase import logging -import json BLACK = (0, 0, 0) WHITE = (255, 255, 255) @@ -14,10 +14,11 @@ 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 +class Snake(GameBase): + def __init__(self, screen, serial, instance): + super().__init__(screen, serial, instance) + self.screen_width = screen.get_width() + self.screen_height = screen.get_height() self.reset() @@ -79,151 +80,66 @@ class Snake: def check_collision(self): head = self.body[0] - if ( + return ( 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() + def main_loop(self): + clock = pygame.time.Clock() - if pygame.time.get_ticks() - score_display_time > 3000: - return None + while self.is_running: + result = self.update() - 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 result is not None: + return result - if opponent_dead: - score_display_time = pygame.time.get_ticks() - else: - waiting = True + if not (self.waiting or self.score_display_time): + for event in pygame.event.get(): + result = self.handle_common_events(event) - continue + if result is not None: + return result + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_UP: + self.change_to = "UP" + elif event.key == pygame.K_DOWN: + self.change_to = "DOWN" + elif event.key == pygame.K_LEFT: + self.change_to = "LEFT" + elif event.key == pygame.K_RIGHT: + self.change_to = "RIGHT" - screen.fill(BLACK) + self.check_serial() + self.move() + + if self.check_collision(): + self.end_game(self.score) + + continue + + self.screen.fill(BLACK) + + for segment in self.body: + pygame.draw.rect( + self.screen, + GREEN, + [segment[0], segment[1], BLOCK_SIZE, BLOCK_SIZE], + ) - for segment in snake.body: pygame.draw.rect( - screen, GREEN, [segment[0], segment[1], BLOCK_SIZE, BLOCK_SIZE] + self.screen, + RED, + [self.food[0], self.food[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) + score_text = self.font.render(f"Score: {self.score}", True, WHITE) - screen.blit(score_text, (10, 10)) - pygame.display.flip() - clock.tick(SPEED) + self.screen.blit(score_text, (10, 10)) + pygame.display.flip() + clock.tick(SPEED) - return None + return None |