thrust_rl/env/games/snake/
snake.rs1use std::collections::VecDeque;
7
8use super::types::{Direction, Position};
9
10#[derive(Debug, Clone)]
12pub struct Snake {
13 pub id: usize,
15 pub head: Position,
17 pub body: VecDeque<Position>,
19 pub direction: Direction,
21 pub length: usize,
23 pub alive: bool,
25}
26
27impl Snake {
28 pub fn new(id: usize, start_pos: Position, start_direction: Direction) -> Self {
30 let mut body = VecDeque::new();
31 body.push_back(start_pos);
32
33 Self { id, head: start_pos, body, direction: start_direction, length: 1, alive: true }
34 }
35
36 pub fn move_forward(&mut self) {
38 let new_head = self.head.add_direction(self.direction);
39
40 self.body.push_front(new_head);
42 self.head = new_head;
43
44 if self.body.len() > self.length {
46 self.body.pop_back();
47 }
48 }
49
50 pub fn move_forward_wrap(&mut self, width: i32, height: i32) {
52 let new_head = self.head.add_direction(self.direction).wrap(width, height);
53
54 self.body.push_front(new_head);
56 self.head = new_head;
57
58 if self.body.len() > self.length {
60 self.body.pop_back();
61 }
62 }
63
64 pub fn change_direction(&mut self, new_direction: Direction) {
66 if new_direction != self.direction.opposite() {
68 self.direction = new_direction;
69 }
70 }
71
72 pub fn grow(&mut self) {
74 self.length += 1;
75 }
76
77 pub fn collides_with_wall(&self, width: i32, height: i32) -> bool {
79 !self.head.in_bounds(width, height)
80 }
81
82 pub fn collides_with_self(&self) -> bool {
84 self.body.iter().skip(1).any(|&pos| pos == self.head)
86 }
87
88 pub fn collides_with_other(&self, other: &Snake) -> bool {
90 other.body.iter().any(|&pos| pos == self.head)
92 }
93
94 pub fn eats_food(&self, food_pos: &Position) -> bool {
96 self.head == *food_pos
97 }
98
99 pub fn get_all_positions(&self) -> Vec<Position> {
101 self.body.iter().cloned().collect()
102 }
103
104 pub fn len(&self) -> usize {
106 self.length
107 }
108
109 pub fn is_empty(&self) -> bool {
114 self.length == 0
115 }
116
117 pub fn is_alive(&self) -> bool {
119 self.alive
120 }
121}
122
123#[derive(Debug, Clone)]
125pub struct Food {
126 pub position: Position,
130}
131
132impl Food {
133 pub fn new(position: Position) -> Self {
135 Self { position }
136 }
137
138 pub fn generate_random(
140 width: i32,
141 height: i32,
142 snakes: &[Snake],
143 rng: &mut impl rand::Rng,
144 ) -> Self {
145 loop {
146 let x = rng.random_range(0..width);
147 let y = rng.random_range(0..height);
148 let pos = Position::new(x, y);
149
150 let occupied = snakes.iter().any(|snake| snake.get_all_positions().contains(&pos));
152
153 if !occupied {
154 return Self::new(pos);
155 }
156 }
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_snake_creation() {
166 let pos = Position::new(5, 5);
167 let snake = Snake::new(0, pos, Direction::Right);
168
169 assert_eq!(snake.id, 0);
170 assert_eq!(snake.head, pos);
171 assert_eq!(snake.direction, Direction::Right);
172 assert_eq!(snake.length, 1);
173 assert_eq!(snake.body.len(), 1);
174 assert!(snake.is_alive());
175 }
176
177 #[test]
178 fn test_snake_movement() {
179 let start_pos = Position::new(5, 5);
180 let mut snake = Snake::new(0, start_pos, Direction::Right);
181
182 snake.move_forward();
183 assert_eq!(snake.head, Position::new(6, 5));
184 assert_eq!(snake.body.len(), 1); snake.grow();
187 snake.move_forward();
188 assert_eq!(snake.head, Position::new(7, 5));
189 assert_eq!(snake.body.len(), 2); assert_eq!(snake.length, 2);
191 }
192
193 #[test]
194 fn test_snake_direction_change() {
195 let mut snake = Snake::new(0, Position::new(5, 5), Direction::Right);
196
197 snake.change_direction(Direction::Down);
199 assert_eq!(snake.direction, Direction::Down);
200
201 snake.change_direction(Direction::Up);
203 assert_eq!(snake.direction, Direction::Down); }
205
206 #[test]
207 fn test_snake_collision_detection() {
208 let mut snake = Snake::new(0, Position::new(1, 1), Direction::Right);
209 snake.grow();
210 snake.move_forward(); assert!(!snake.collides_with_wall(3, 3), "(2, 1) is in-bounds on 3x3");
212
213 snake.move_forward(); assert!(snake.collides_with_wall(3, 3), "(3, 1) is out-of-bounds on 3x3");
216
217 let mut snake2 = Snake::new(0, Position::new(5, 5), Direction::Right);
219 snake2.grow();
220 snake2.grow();
221 snake2.grow();
222 snake2.grow(); snake2.move_forward(); snake2.change_direction(Direction::Down);
225 snake2.move_forward(); snake2.change_direction(Direction::Left);
227 snake2.move_forward(); snake2.change_direction(Direction::Up);
229 snake2.move_forward(); assert!(snake2.collides_with_self());
231 }
232
233 #[test]
234 fn test_snake_food_eating() {
235 let snake_pos = Position::new(5, 5);
236 let food_pos = Position::new(5, 5);
237 let snake = Snake::new(0, snake_pos, Direction::Right);
238
239 assert!(snake.eats_food(&food_pos));
240
241 let food_pos2 = Position::new(6, 5);
242 assert!(!snake.eats_food(&food_pos2));
243 }
244
245 #[test]
246 fn test_food_generation() {
247 let mut rng = rand::rng();
248 let snakes = vec![
249 Snake::new(0, Position::new(0, 0), Direction::Right),
250 Snake::new(1, Position::new(2, 2), Direction::Right),
251 ];
252
253 let food = Food::generate_random(5, 5, &snakes, &mut rng);
254 let food_pos = food.position;
255
256 assert!(food_pos.in_bounds(5, 5));
258
259 for snake in &snakes {
261 assert!(!snake.get_all_positions().contains(&food_pos));
262 }
263 }
264}