feat(snake): implement snake game with pure game logic, renderer, and IPC-based main loop

This commit is contained in:
2026-10-18 04:00:09 +02:00
parent a7913ddeef
commit 0389c85108
8 changed files with 458 additions and 2 deletions

View File

@@ -0,0 +1,82 @@
use crate::game::*;
#[test]
fn new_game_is_running() {
let game = Snake::new();
assert_eq!(game.state(), GameState::Running);
}
#[test]
fn initial_snake_has_three_segments() {
let game = Snake::new();
assert_eq!(game.body().len(), 3);
}
#[test]
fn initial_score_is_zero() {
let game = Snake::new();
assert_eq!(game.score(), 0);
}
#[test]
fn tick_moves_head_right() {
let mut game = Snake::new();
let initial_head = game.head();
game.tick();
assert_eq!(game.head().x, initial_head.x + 1);
assert_eq!(game.head().y, initial_head.y);
}
#[test]
fn set_direction_changes_movement() {
let mut game = Snake::new();
game.set_direction(Direction::Down);
game.tick();
let head = game.head();
assert_eq!(head.y, GRID_HEIGHT / 2 + 1);
}
#[test]
fn cannot_reverse_direction() {
let mut game = Snake::new();
// moving right, try to go left — should be ignored
game.set_direction(Direction::Left);
game.tick();
assert_eq!(game.direction(), Direction::Right);
}
#[test]
fn wall_collision_ends_game() {
let mut game = Snake::new();
// Move right until hitting the wall
for _ in 0..GRID_WIDTH {
if game.state() != GameState::Running { break; }
game.tick();
}
assert_eq!(game.state(), GameState::GameOver);
}
#[test]
fn food_exists_on_grid() {
let game = Snake::new();
let food = game.food();
assert!(food.x < GRID_WIDTH);
assert!(food.y < GRID_HEIGHT);
}
#[test]
fn body_length_preserved_without_food() {
let mut game = Snake::new();
let initial_len = game.body().len();
game.tick();
assert_eq!(game.body().len(), initial_len);
}
#[test]
fn direction_up_moves_correctly() {
let mut game = Snake::new();
game.set_direction(Direction::Up);
let y_before = game.head().y;
game.tick();
assert_eq!(game.head().y, y_before - 1);
}

View File

@@ -0,0 +1,2 @@
mod game;
mod renderer;

View File

@@ -0,0 +1,32 @@
use crate::game::Snake;
use crate::renderer::{render, RenderCommand};
#[test]
fn render_produces_commands() {
let snake = Snake::new();
let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; 64];
let count = render(&snake, &mut commands);
// background + border + food + 3 body segments = 6
assert!(count >= 6);
}
#[test]
fn first_command_is_background() {
let snake = Snake::new();
let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; 64];
render(&snake, &mut commands);
assert_eq!(commands[0].x, 0);
assert_eq!(commands[0].y, 0);
assert!(commands[0].w > 0);
assert!(commands[0].h > 0);
}
#[test]
fn food_is_rendered() {
let snake = Snake::new();
let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; 64];
let count = render(&snake, &mut commands);
// Food is command index 2 (after bg and border)
assert!(count > 2);
assert_eq!(commands[2].color, 0xFF0040);
}