aboutsummaryrefslogtreecommitdiff
path: root/src/splitscreen_duo/games
diff options
context:
space:
mode:
authorZoltan Szabatin <[email protected]>2025-03-02 18:38:27 -0800
committerZoltan Szabatin <[email protected]>2025-03-02 18:38:29 -0800
commit225f4a227013443b09a205cabe73886facab4d2b (patch)
tree939ef33031bb2c31cfc3eec96fbc7982ce585784 /src/splitscreen_duo/games
parentrefactor: Move games to proper game enumeration (diff)
downloadsplitscreen-duo-225f4a227013443b09a205cabe73886facab4d2b.tar.xz
splitscreen-duo-225f4a227013443b09a205cabe73886facab4d2b.zip
feat: Add snake and benchmark games
Diffstat (limited to 'src/splitscreen_duo/games')
-rw-r--r--src/splitscreen_duo/games/benchmark.py65
-rw-r--r--src/splitscreen_duo/games/snake.py229
2 files changed, 294 insertions, 0 deletions
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