feat(snake): implement snake game with pure game logic, renderer, and IPC-based main loop
This commit is contained in:
82
crates/apps/snake/src/tests/game.rs
Normal file
82
crates/apps/snake/src/tests/game.rs
Normal 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);
|
||||
}
|
||||
2
crates/apps/snake/src/tests/mod.rs
Normal file
2
crates/apps/snake/src/tests/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod game;
|
||||
mod renderer;
|
||||
32
crates/apps/snake/src/tests/renderer.rs
Normal file
32
crates/apps/snake/src/tests/renderer.rs
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user