aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZoltan Szabatin <[email protected]>2025-03-02 19:56:28 -0800
committerZoltan Szabatin <[email protected]>2025-03-02 19:56:28 -0800
commit4ee5ce27a8309be7989e2126f39f9411c1178da2 (patch)
tree7c3cd8874065ad6ed236c6f1385e7300e5074a69
parentrefactor: Create base game class for games (diff)
downloadsplitscreen-duo-4ee5ce27a8309be7989e2126f39f9411c1178da2.tar.xz
splitscreen-duo-4ee5ce27a8309be7989e2126f39f9411c1178da2.zip
feat: Add pong game
-rw-r--r--src/splitscreen_duo/__init__.py24
-rw-r--r--src/splitscreen_duo/games/game_base.py2
-rw-r--r--src/splitscreen_duo/games/pong.py242
3 files changed, 267 insertions, 1 deletions
diff --git a/src/splitscreen_duo/__init__.py b/src/splitscreen_duo/__init__.py
index 9fef2eb..9689db4 100644
--- a/src/splitscreen_duo/__init__.py
+++ b/src/splitscreen_duo/__init__.py
@@ -9,6 +9,7 @@ import json
from .games import benchmark
from .games.snake import Snake
from .games.breakout import Breakout
+from .games.pong import Pong
def main() -> int:
@@ -82,6 +83,19 @@ def main() -> int:
pygame.quit()
return 0
+ elif message.get("value") == Game.PONG.value:
+ logger.info("received pong game selection from primary")
+
+ pong_game = Pong(menu.screen, serial, INSTANCE)
+ game_result = pong_game.main_loop()
+
+ 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")
@@ -129,6 +143,16 @@ def main() -> int:
pygame.quit()
return 0
+ elif serial_command.get("value") == Game.PONG.value:
+ logger.info("starting pong game")
+
+ pong_game = Pong(menu.screen, serial, INSTANCE)
+ game_result = pong_game.main_loop()
+
+ if game_result and game_result.get("command") == Command.QUIT.value:
+ pygame.quit()
+
+ return 0
menu.draw_menu()
diff --git a/src/splitscreen_duo/games/game_base.py b/src/splitscreen_duo/games/game_base.py
index ff45686..87cf65f 100644
--- a/src/splitscreen_duo/games/game_base.py
+++ b/src/splitscreen_duo/games/game_base.py
@@ -70,7 +70,7 @@ class GameBase:
if self.waiting:
self.screen.fill(self.BLACK)
- text = self.font.render("waiting for opponent ...", True, self.WHITE)
+ text = self.font.render("Waiting for opponent ...", True, self.WHITE)
self.screen.blit(
text,
diff --git a/src/splitscreen_duo/games/pong.py b/src/splitscreen_duo/games/pong.py
new file mode 100644
index 0000000..aa3d8a3
--- /dev/null
+++ b/src/splitscreen_duo/games/pong.py
@@ -0,0 +1,242 @@
+import pygame
+import random
+from ..command import Command
+from .game_base import GameBase
+import logging
+
+BLACK = (0, 0, 0)
+WHITE = (255, 255, 255)
+PADDLE_WIDTH = 100
+PADDLE_HEIGHT = 10
+BALL_SIZE = 10
+PADDLE_SPEED = 10
+AI_SPEED = 4
+BALL_SPEED = 5
+
+logger = logging.getLogger(__name__)
+
+
+class Pong(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.player_score = 0
+ self.ai_score = 0
+ self.score_display_time = 0
+
+ self.reset()
+
+ def reset(self):
+ self.player_paddle = [
+ self.screen_width // 2 - PADDLE_WIDTH // 2,
+ self.screen_height - 40,
+ ]
+ self.ai_paddle = [
+ self.screen_width // 2 - PADDLE_WIDTH // 2,
+ 40,
+ ]
+ self.ball = [
+ self.screen_width // 2,
+ self.screen_height // 2,
+ ]
+ self.ball_dx = random.choice([-BALL_SPEED, BALL_SPEED])
+ self.ball_dy = BALL_SPEED
+
+ def move_player_paddle(self, dx):
+ self.player_paddle[0] += dx
+
+ if self.player_paddle[0] < 0:
+ self.player_paddle[0] = 0
+ if self.player_paddle[0] > self.screen_width - PADDLE_WIDTH:
+ self.player_paddle[0] = self.screen_width - PADDLE_WIDTH
+
+ def move_ai_paddle(self):
+ if random.random() < 0.8:
+ if self.ai_paddle[0] + PADDLE_WIDTH // 2 < self.ball[0]:
+ self.ai_paddle[0] += AI_SPEED
+ elif self.ai_paddle[0] + PADDLE_WIDTH // 2 > self.ball[0]:
+ self.ai_paddle[0] -= AI_SPEED
+
+ if self.ai_paddle[0] < 0:
+ self.ai_paddle[0] = 0
+ if self.ai_paddle[0] > self.screen_width - PADDLE_WIDTH:
+ self.ai_paddle[0] = self.screen_width - PADDLE_WIDTH
+
+ def move_ball(self):
+ self.ball[0] += self.ball_dx
+ self.ball[1] += self.ball_dy
+
+ if self.ball[0] <= BALL_SIZE or self.ball[0] >= self.screen_width - BALL_SIZE:
+ self.ball_dx *= -1
+
+ player_rect = pygame.Rect(
+ self.player_paddle[0],
+ self.player_paddle[1],
+ PADDLE_WIDTH,
+ PADDLE_HEIGHT,
+ )
+ ai_rect = pygame.Rect(
+ self.ai_paddle[0],
+ self.ai_paddle[1],
+ PADDLE_WIDTH,
+ PADDLE_HEIGHT,
+ )
+ ball_rect = pygame.Rect(
+ self.ball[0] - BALL_SIZE,
+ self.ball[1] - BALL_SIZE,
+ BALL_SIZE * 2,
+ BALL_SIZE * 2,
+ )
+
+ if ball_rect.colliderect(player_rect) and self.ball_dy > 0:
+ self.ball_dy *= -1
+ hit_pos = (self.ball[0] - self.player_paddle[0]) / PADDLE_WIDTH
+ self.ball_dx = BALL_SPEED * (hit_pos - 0.5) * 2
+ elif ball_rect.colliderect(ai_rect) and self.ball_dy < 0:
+ self.ball_dy *= -1
+ hit_pos = (self.ball[0] - self.ai_paddle[0]) / PADDLE_WIDTH
+ self.ball_dx = BALL_SPEED * (hit_pos - 0.5) * 2
+
+ if self.ball[1] <= 0:
+ self.player_score += 1
+
+ self.reset_ball()
+ elif self.ball[1] >= self.screen_height:
+ self.ai_score += 1
+
+ self.reset_ball()
+
+ def reset_ball(self):
+ self.ball = [
+ self.screen_width // 2,
+ self.screen_height // 2,
+ ]
+ self.ball_dx = random.choice([-BALL_SPEED, BALL_SPEED])
+ self.ball_dy = random.choice([-BALL_SPEED, BALL_SPEED])
+
+ def check_game_over(self):
+ if self.player_score >= 5:
+ return "win"
+
+ if self.ai_score >= 5:
+ return "lose"
+
+ return None
+
+ def update(self):
+ if self.score_display_time:
+ self.screen.fill(BLACK)
+
+ result_text = "You Won!" if self.player_score >= 5 else "You Lost!"
+ result_display = self.font.render(result_text, True, WHITE)
+ player_score_text = self.font.render(
+ f"Your Score: {self.player_score}", True, WHITE
+ )
+ ai_score_text = self.font.render(f"AI Score: {self.ai_score}", True, WHITE)
+
+ self.screen.blit(
+ result_display,
+ (
+ self.screen_width // 2 - result_display.get_width() // 2,
+ self.screen_height // 2 - 40,
+ ),
+ )
+ self.screen.blit(
+ player_score_text,
+ (
+ self.screen_width // 2 - player_score_text.get_width() // 2,
+ self.screen_height // 2 - 20,
+ ),
+ )
+ self.screen.blit(
+ ai_score_text,
+ (
+ self.screen_width // 2 - ai_score_text.get_width() // 2,
+ self.screen_height // 2 + 20,
+ ),
+ )
+ pygame.display.flip()
+
+ if pygame.time.get_ticks() - self.score_display_time > 3000:
+ logger.debug("score display timeout reached, exiting pong")
+
+ self.is_running = False
+
+ return None
+
+ def main_loop(self):
+ clock = pygame.time.Clock()
+
+ while self.is_running:
+ result = self.update()
+
+ if result is not None:
+ return result
+
+ if not 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_player_paddle(-PADDLE_SPEED)
+ elif event.key == pygame.K_RIGHT:
+ self.move_player_paddle(PADDLE_SPEED)
+
+ keys = pygame.key.get_pressed()
+
+ if keys[pygame.K_LEFT]:
+ self.move_player_paddle(-PADDLE_SPEED)
+ elif keys[pygame.K_RIGHT]:
+ self.move_player_paddle(PADDLE_SPEED)
+
+ self.move_ai_paddle()
+ self.move_ball()
+
+ game_over = self.check_game_over()
+
+ if game_over:
+ self.score_display_time = pygame.time.get_ticks()
+
+ logger.debug(f"game over: {game_over}, starting score display")
+
+ continue
+
+ self.screen.fill(BLACK)
+ pygame.draw.rect(
+ self.screen,
+ WHITE,
+ [
+ self.player_paddle[0],
+ self.player_paddle[1],
+ PADDLE_WIDTH,
+ PADDLE_HEIGHT,
+ ],
+ )
+ pygame.draw.rect(
+ self.screen,
+ WHITE,
+ [self.ai_paddle[0], self.ai_paddle[1], PADDLE_WIDTH, PADDLE_HEIGHT],
+ )
+ pygame.draw.circle(
+ self.screen,
+ WHITE,
+ [int(self.ball[0]), int(self.ball[1])],
+ BALL_SIZE,
+ )
+
+ player_score_text = self.font.render(
+ f"Player: {self.player_score}", True, WHITE
+ )
+ ai_score_text = self.font.render(f"AI: {self.ai_score}", True, WHITE)
+
+ self.screen.blit(player_score_text, (10, self.screen_height - 40))
+ self.screen.blit(ai_score_text, (10, 10))
+ pygame.display.flip()
+ clock.tick(60)
+
+ return None