aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorZoltan Szabatin <[email protected]>2025-03-02 19:01:12 -0800
committerZoltan Szabatin <[email protected]>2025-03-02 19:01:12 -0800
commit3ba60ffec522cd35c137573f666aaea3ee899c7c (patch)
treec7e44a62f4dd701669aee1f318ed17aff28ae858 /src
parentfeat: Add breakout game (diff)
downloadsplitscreen-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__.py16
-rw-r--r--src/splitscreen_duo/games/breakout.py193
-rw-r--r--src/splitscreen_duo/games/game_base.py140
-rw-r--r--src/splitscreen_duo/games/snake.py188
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