aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZoltan Szabatin <[email protected]>2025-03-02 22:39:48 -0800
committerZoltan Szabatin <[email protected]>2025-03-02 22:39:48 -0800
commit05061161e296ba2e54f3fc7d60a88adac9cd2761 (patch)
tree20c3c64cb254ff08ca183c880293d434d686fc32
parentfeat(games): Use input handler (diff)
downloadsplitscreen-duo-05061161e296ba2e54f3fc7d60a88adac9cd2761.tar.xz
splitscreen-duo-05061161e296ba2e54f3fc7d60a88adac9cd2761.zip
feat: Add split mode toggle for debugging
-rw-r--r--src/splitscreen_duo/__init__.py104
-rw-r--r--src/splitscreen_duo/command.py4
-rw-r--r--src/splitscreen_duo/games/breakout.py14
-rw-r--r--src/splitscreen_duo/games/game_base.py55
-rw-r--r--src/splitscreen_duo/games/pong.py54
-rw-r--r--src/splitscreen_duo/games/snake.py18
-rw-r--r--src/splitscreen_duo/menu.py21
7 files changed, 192 insertions, 78 deletions
diff --git a/src/splitscreen_duo/__init__.py b/src/splitscreen_duo/__init__.py
index 309135c..defba37 100644
--- a/src/splitscreen_duo/__init__.py
+++ b/src/splitscreen_duo/__init__.py
@@ -19,8 +19,9 @@ def main() -> int:
serial = Serial(os.getenv("SERIAL_DEVICE", "/dev/null"), 115200)
INSTANCE = os.getenv("INSTANCE", "Unknown")
+ is_joint_mode = [False]
- pygame.display.set_caption(f"SplitScreen Duo Menu ({INSTANCE})")
+ pygame.display.set_caption(f"SplitScreen Duo ({INSTANCE})")
logging.basicConfig(
level=(logging.DEBUG if os.getenv("DEVELOPMENT", "").lower() else logging.INFO)
)
@@ -28,6 +29,10 @@ def main() -> int:
logger.info(f"running as {os.getenv('INSTANCE', 'unknown')}")
menu.init_display()
+ font = pygame.font.Font(None, 36)
+ BLACK = (0, 0, 0)
+ WHITE = (255, 255, 255)
+ score = 0
game_handlers = {
Game.BENCHMARK.value: (
"benchmark",
@@ -35,15 +40,17 @@ def main() -> int:
),
Game.SNAKE.value: (
"snake",
- lambda: Snake(menu.screen, serial, INSTANCE).main_loop(),
+ lambda: Snake(menu.screen, serial, INSTANCE, is_joint_mode[0]).main_loop(),
),
Game.BREAKOUT.value: (
"breakout",
- lambda: Breakout(menu.screen, serial, INSTANCE).main_loop(),
+ lambda: Breakout(
+ menu.screen, serial, INSTANCE, is_joint_mode[0]
+ ).main_loop(),
),
Game.PONG.value: (
"pong",
- lambda: Pong(menu.screen, serial, INSTANCE).main_loop(),
+ lambda: Pong(menu.screen, serial, INSTANCE, is_joint_mode[0]).main_loop(),
),
}
@@ -59,6 +66,11 @@ def main() -> int:
game_result = game_func()
+ if is_joint_mode[0] and INSTANCE == "primary":
+ serial.write(
+ json.dumps({"command": Command.GAME_ENDED.value}).encode("utf-8")
+ )
+
if game_result and game_result.get("command") == Command.QUIT.value:
logger.info(f"{game_name} game returned QUIT, shutting down")
pygame.quit()
@@ -70,20 +82,49 @@ def main() -> int:
is_running = True
while is_running:
- if INSTANCE == "secondary" and serial.in_waiting() > 0:
+ if serial.in_waiting() > 0:
+ data = serial.readline().decode("utf-8").strip()
+
try:
- data = serial.readline().decode("utf-8").strip()
+ message = json.loads(data)
+ command = message.get("command")
- logger.debug(f"received serial data: {data}")
+ if command == Command.ENTER_JOINT_MODE.value:
+ is_joint_mode[0] = True
- message = json.loads(data)
+ logger.info("secondary entering joint mode")
+ elif command == Command.EXIT_JOINT_MODE.value:
+ is_joint_mode[0] = False
- if message.get("command") == Command.QUIT.value:
- logger.info("received quit command from primary, shutting down")
+ logger.info("secondary exiting joint mode")
+ elif command == Command.QUIT.value:
pygame.quit()
return 0
- elif message.get("command") == Command.SELECT_GAME.value:
+ elif INSTANCE == "secondary" and is_joint_mode[0]:
+ if command == Command.STATS.value:
+ score = message.get("value", 0)
+ elif command == Command.GAME_ENDED.value:
+ menu.screen.fill(BLACK)
+
+ text = font.render("Game Ended", True, WHITE)
+
+ menu.screen.blit(
+ text,
+ (
+ menu.screen.get_width() // 2 - text.get_width() // 2,
+ menu.screen.get_height() // 2,
+ ),
+ )
+ pygame.display.flip()
+ pygame.time.wait(2000)
+
+ score = 0
+ elif (
+ INSTANCE == "secondary"
+ and not is_joint_mode[0]
+ and command == Command.SELECT_GAME.value
+ ):
result = handle_game_selection(message, is_secondary=True)
if result == 0:
@@ -95,24 +136,43 @@ def main() -> int:
except Exception as e:
logger.error(f"error processing serial data: {e}")
- serial_command = menu.process_events(serial, INSTANCE)
+ if INSTANCE == "secondary" and is_joint_mode[0]:
+ menu.screen.fill(BLACK)
- if serial_command:
- serial.write(json.dumps(serial_command).encode("utf-8"))
+ text = font.render(f"Score: {score}", True, WHITE)
- if serial_command.get("command") == Command.QUIT.value:
- logger.info("primary sent quit, shutting down")
- pygame.quit()
+ menu.screen.blit(
+ text,
+ (
+ menu.screen.get_width() // 2 - text.get_width() // 2,
+ menu.screen.get_height() // 2,
+ ),
+ )
+ pygame.display.flip()
+ pygame.time.delay(100)
+ else:
+ serial_command = menu.process_events(serial, INSTANCE, is_joint_mode)
- return 0
- elif serial_command.get("command") == Command.SELECT_GAME.value:
- result = handle_game_selection(serial_command)
+ if serial_command:
+ if not is_joint_mode[0] or INSTANCE == "primary":
+ serial.write(json.dumps(serial_command).encode("utf-8"))
+
+ if serial_command.get("command") == Command.QUIT.value:
+ pygame.quit()
- if result == 0:
return 0
+ elif serial_command.get("command") == Command.SELECT_GAME.value:
+ result = handle_game_selection(serial_command)
- menu.draw_menu()
+ if result == 0:
+ return 0
+
+ menu.draw_menu()
pygame.quit()
return 0
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/splitscreen_duo/command.py b/src/splitscreen_duo/command.py
index df70b93..79cd716 100644
--- a/src/splitscreen_duo/command.py
+++ b/src/splitscreen_duo/command.py
@@ -5,3 +5,7 @@ class Command(Enum):
QUIT = 0
SELECT_GAME = 1
SCORE = 2
+ STATS = 3
+ GAME_ENDED = 4
+ ENTER_JOINT_MODE = 5
+ EXIT_JOINT_MODE = 6
diff --git a/src/splitscreen_duo/games/breakout.py b/src/splitscreen_duo/games/breakout.py
index 734b394..ef369de 100644
--- a/src/splitscreen_duo/games/breakout.py
+++ b/src/splitscreen_duo/games/breakout.py
@@ -21,8 +21,8 @@ logger = logging.getLogger(__name__)
class Breakout(GameBase):
- def __init__(self, screen, serial, instance):
- super().__init__(screen, serial, instance)
+ def __init__(self, screen, serial, instance, is_joint_mode=False):
+ super().__init__(screen, serial, instance, is_joint_mode)
self.screen_width = screen.get_width()
self.screen_height = screen.get_height()
@@ -65,6 +65,7 @@ class Breakout(GameBase):
if self.ball[0] <= BALL_SIZE or self.ball[0] >= self.screen_width - BALL_SIZE:
self.ball_dx *= -1
+
if self.ball[1] <= BALL_SIZE:
self.ball_dy *= -1
@@ -90,6 +91,9 @@ class Breakout(GameBase):
self.bricks.remove(brick)
self.score += 1
+
+ self.send_stats(self.score)
+
self.ball_dy *= -1
logger.debug(f"brick hit, score: {self.score}")
@@ -156,9 +160,11 @@ class Breakout(GameBase):
for brick in self.bricks:
pygame.draw.rect(self.screen, RED, brick)
- score_text = self.font.render(f"Score: {self.score}", True, WHITE)
+ if not self.is_joint_mode or self.instance != "primary":
+ score_text = self.font.render(f"Score: {self.score}", True, WHITE)
+
+ self.screen.blit(score_text, (10, 10))
- self.screen.blit(score_text, (10, 10))
pygame.display.flip()
clock.tick(60)
diff --git a/src/splitscreen_duo/games/game_base.py b/src/splitscreen_duo/games/game_base.py
index 547b27e..8ef02ec 100644
--- a/src/splitscreen_duo/games/game_base.py
+++ b/src/splitscreen_duo/games/game_base.py
@@ -9,10 +9,11 @@ logger = logging.getLogger(__name__)
class GameBase:
- def __init__(self, screen, serial, instance):
+ def __init__(self, screen, serial, instance, is_joint_mode=False):
self.screen = screen
self.serial = serial
self.instance = instance
+ self.is_joint_mode = is_joint_mode
self.font = pygame.font.Font(None, 36)
self.is_running = True
self.opponent_dead = False
@@ -37,7 +38,6 @@ class GameBase:
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)
@@ -53,26 +53,50 @@ class GameBase:
return False
def end_game(self, score):
- self.my_score = score
+ if self.is_joint_mode:
+ self.is_running = False
+
+ if self.instance == "primary":
+ self.serial.write(
+ json.dumps({"command": Command.GAME_ENDED.value}).encode("utf-8")
+ + b"\n"
+ )
+ else:
+ 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")
+ + b"\n"
+ )
- 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()
- 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("opponent already dead, starting score display")
- else:
- self.waiting = True
+ logger.debug("waiting for opponent to finish")
- logger.debug("waiting for opponent to finish")
+ def send_stats(self, value):
+ if self.is_joint_mode and self.instance == "primary":
+ self.serial.write(
+ json.dumps({"command": Command.STATS.value, "value": value}).encode(
+ "utf-8"
+ )
+ + b"\n"
+ )
def update(self):
+ if self.is_joint_mode:
+ return None
+
if self.waiting:
self.screen.fill(self.BLACK)
@@ -108,7 +132,6 @@ class GameBase:
"action": None,
"value": None,
}
-
elif self.score_display_time:
self.screen.fill(self.BLACK)
diff --git a/src/splitscreen_duo/games/pong.py b/src/splitscreen_duo/games/pong.py
index 7f0d4e9..464e529 100644
--- a/src/splitscreen_duo/games/pong.py
+++ b/src/splitscreen_duo/games/pong.py
@@ -17,9 +17,8 @@ logger = logging.getLogger(__name__)
class Pong(GameBase):
- def __init__(self, screen, serial, instance):
- super().__init__(screen, serial, instance)
-
+ def __init__(self, screen, serial, instance, is_joint_mode=False):
+ super().__init__(screen, serial, instance, is_joint_mode)
self.screen_width = screen.get_width()
self.screen_height = screen.get_height()
self.player_score = 0
@@ -33,14 +32,8 @@ class Pong(GameBase):
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.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
@@ -61,6 +54,7 @@ class Pong(GameBase):
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
@@ -72,16 +66,10 @@ class Pong(GameBase):
self.ball_dx *= -1
player_rect = pygame.Rect(
- self.player_paddle[0],
- self.player_paddle[1],
- PADDLE_WIDTH,
- PADDLE_HEIGHT,
+ 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,
+ self.ai_paddle[0], self.ai_paddle[1], PADDLE_WIDTH, PADDLE_HEIGHT
)
ball_rect = pygame.Rect(
self.ball[0] - BALL_SIZE,
@@ -102,17 +90,16 @@ class Pong(GameBase):
if self.ball[1] <= 0:
self.player_score += 1
+ self.send_stats(self.player_score)
self.reset_ball()
elif self.ball[1] >= self.screen_height:
self.ai_score += 1
+ self.send_stats(self.player_score)
self.reset_ball()
def reset_ball(self):
- self.ball = [
- self.screen_width // 2,
- self.screen_height // 2,
- ]
+ 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])
@@ -201,9 +188,10 @@ class Pong(GameBase):
game_over = self.check_game_over()
if game_over:
- self.score_display_time = pygame.time.get_ticks()
+ if self.is_joint_mode and self.instance == "primary":
+ self.send_stats(self.player_score)
- logger.debug(f"game over: {game_over}, starting score display")
+ self.end_game(self.player_score)
continue
@@ -230,13 +218,17 @@ class Pong(GameBase):
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)
+ if not self.is_joint_mode or self.instance != "primary":
+ 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))
- 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)
diff --git a/src/splitscreen_duo/games/snake.py b/src/splitscreen_duo/games/snake.py
index 51c0bcc..b593ea1 100644
--- a/src/splitscreen_duo/games/snake.py
+++ b/src/splitscreen_duo/games/snake.py
@@ -15,8 +15,8 @@ logger = logging.getLogger(__name__)
class Snake(GameBase):
- def __init__(self, screen, serial, instance):
- super().__init__(screen, serial, instance)
+ def __init__(self, screen, serial, instance, is_joint_mode=False):
+ super().__init__(screen, serial, instance, is_joint_mode)
self.screen_width = screen.get_width()
self.screen_height = screen.get_height()
@@ -73,6 +73,9 @@ class Snake(GameBase):
)
self.score += 1
+
+ self.send_stats(self.score)
+
self.food = self.spawn_food()
else:
self.body.pop()
@@ -122,6 +125,11 @@ class Snake(GameBase):
continue
+ if self.check_collision():
+ self.end_game(self.score)
+
+ continue
+
self.screen.fill(BLACK)
for segment in self.body:
@@ -137,9 +145,11 @@ class Snake(GameBase):
[self.food[0], self.food[1], BLOCK_SIZE, BLOCK_SIZE],
)
- score_text = self.font.render(f"Score: {self.score}", True, WHITE)
+ if not self.is_joint_mode or self.instance != "primary":
+ score_text = self.font.render(f"Score: {self.score}", True, WHITE)
+
+ self.screen.blit(score_text, (10, 10))
- self.screen.blit(score_text, (10, 10))
pygame.display.flip()
clock.tick(SPEED)
diff --git a/src/splitscreen_duo/menu.py b/src/splitscreen_duo/menu.py
index a7bda82..9517d3d 100644
--- a/src/splitscreen_duo/menu.py
+++ b/src/splitscreen_duo/menu.py
@@ -17,6 +17,7 @@ WIDTH = HEIGHT = FONT = screen = selected_index = None
def init_display():
global WIDTH, HEIGHT, FONT, screen, selected_index
+
WIDTH, HEIGHT = (
pygame.display.Info().current_w // 2,
pygame.display.Info().current_h // 2,
@@ -40,7 +41,7 @@ def draw_menu():
pygame.display.flip()
-def process_events(serial, instance):
+def process_events(serial, instance, is_joint_mode):
global selected_index
input_handler = Input(debug=IS_DEVELOPMENT_MODE)
@@ -75,4 +76,22 @@ def process_events(serial, instance):
elif action == "QUIT":
return {"command": Command.QUIT.value, "action": None, "value": None}
+ if (
+ event.type == pygame.KEYDOWN
+ and event.key == pygame.K_b
+ and instance == "primary"
+ and IS_DEVELOPMENT_MODE
+ ):
+ is_joint_mode[0] = not is_joint_mode[0]
+
+ logger.info(f"Toggled joint mode to {is_joint_mode[0]}")
+
+ command = (
+ Command.ENTER_JOINT_MODE.value
+ if is_joint_mode[0]
+ else Command.EXIT_JOINT_MODE.value
+ )
+
+ serial.write(json.dumps({"command": command}).encode("utf-8"))
+
return None