1#![no_std]
2
3use embedded_graphics::{
4 pixelcolor::*,
5 prelude::{DrawTarget, OriginDimensions, Point, Size},
6 primitives::{Primitive, PrimitiveStyle, Rectangle},
7 Drawable, Pixel,
8};
9
10struct Snake<T: PixelColor, const MAX_SIZE: usize> {
11 parts: [Pixel<T>; MAX_SIZE],
12 len: usize,
13 direction: Direction,
14 size_x: u8,
15 size_y: u8,
16}
17
18struct SnakeIntoIterator<'a, T: PixelColor, const MAX_SIZE: usize> {
19 snake: &'a Snake<T, MAX_SIZE>,
20 index: usize,
21}
22
23impl<'a, T: PixelColor, const MAX_SIZE: usize> IntoIterator for &'a Snake<T, MAX_SIZE> {
24 type Item = Pixel<T>;
25 type IntoIter = SnakeIntoIterator<'a, T, MAX_SIZE>;
26
27 fn into_iter(self) -> Self::IntoIter {
28 SnakeIntoIterator {
29 snake: self,
30 index: 0,
31 }
32 }
33}
34
35impl<'a, T: PixelColor, const MAX_SIZE: usize> Iterator for SnakeIntoIterator<'a, T, MAX_SIZE> {
36 type Item = Pixel<T>;
37
38 fn next(&mut self) -> Option<Self::Item> {
39 let cur = self.snake.parts[self.index];
40 if self.index < self.snake.len {
41 self.index += 1;
42 return Some(cur);
43 }
44 None
45 }
46}
47
48impl<T: PixelColor, const MAX_SIZE: usize> Snake<T, MAX_SIZE> {
49 fn new(color: T, size_x: u8, size_y: u8) -> Snake<T, MAX_SIZE> {
50 Snake {
51 parts: [Pixel::<T>(Point { x: 0, y: 0 }, color); MAX_SIZE],
52 len: 5,
53 direction: Direction::None,
54 size_x,
55 size_y,
56 }
57 }
58 fn set_direction(&mut self, direction: Direction) {
59 self.direction = direction;
60 }
61 fn contains(&self, this: Point) -> bool {
62 for part in self.into_iter() {
63 if part.0 == this {
64 return true;
65 };
66 }
67 false
68 }
69 fn grow(&mut self) {
70 if self.len < MAX_SIZE - 1 {
71 self.len += 1;
72 }
73 }
74 fn make_step(&mut self) {
75 let mut i = self.len;
76 while i > 0 {
77 self.parts[i] = self.parts[i - 1];
78 i -= 1;
79 }
80 match self.direction {
81 Direction::Left => {
82 if self.parts[0].0.x == 0 {
83 self.parts[0].0.x = (self.size_x - 1) as i32;
84 } else {
85 self.parts[0].0.x -= 1;
86 }
87 }
88 Direction::Right => {
89 if self.parts[0].0.x == (self.size_x - 1) as i32 {
90 self.parts[0].0.x = 0;
91 } else {
92 self.parts[0].0.x += 1;
93 }
94 }
95 Direction::Up => {
96 if self.parts[0].0.y == 0 {
97 self.parts[0].0.y = (self.size_y - 1) as i32;
98 } else {
99 self.parts[0].0.y -= 1;
100 }
101 }
102 Direction::Down => {
103 if self.parts[0].0.y == (self.size_y - 1) as i32 {
104 self.parts[0].0.y = 0;
105 } else {
106 self.parts[0].0.y += 1;
107 }
108 }
109 Direction::None => {}
110 }
111 }
112}
113
114struct Food<T: PixelColor, RNG: rand_core::RngCore> {
115 size_x: u8,
116 size_y: u8,
117 place: Pixel<T>,
118 rng: RNG,
119}
120
121impl<T: PixelColor, RNG: rand_core::RngCore> Food<T, RNG> {
122 pub fn new(color: T, rand_source: RNG, size_x: u8, size_y: u8) -> Self {
123 Food {
124 size_x,
125 size_y,
126 place: Pixel(Point { x: 0, y: 0 }, color),
127 rng: rand_source,
128 }
129 }
130 fn replace<'a, const MAX_SIZE: usize>(&mut self, iter_source: &Snake<T, MAX_SIZE>) {
131 let mut p: Point;
132 'outer: loop {
133 let random_number = self.rng.next_u32();
134 let blocked_positions = iter_source.into_iter();
135 p = Point {
136 x: ((random_number >> 24) as u8 % self.size_x).into(),
137 y: ((random_number >> 16) as u8 % self.size_y).into(),
138 };
139 for blocked_position in blocked_positions {
140 if p == blocked_position.0 {
141 continue 'outer;
142 }
143 }
144 break;
145 }
146 self.place = Pixel::<T> {
147 0: p,
148 1: self.place.1,
149 }
150 }
151 fn get_pixel(&self) -> Pixel<T> {
152 self.place
153 }
154}
155
156#[derive(PartialEq, Debug, Clone, Copy)]
157pub enum Direction {
158 Left,
159 Right,
160 Up,
161 Down,
162 None,
163}
164
165pub struct SnakeGame<const MAX_SNAKE_SIZE: usize, T: PixelColor, RNG: rand_core::RngCore> {
166 snake: Snake<T, MAX_SNAKE_SIZE>,
167 food: Food<T, RNG>,
168 food_age: u8,
169 food_lifetime: u8,
170 size_x: u8,
171 size_y: u8,
172 scale_x: u8,
173 scale_y: u8,
174}
175
176impl<const MAX_SIZE: usize, T: PixelColor, RNG: rand_core::RngCore> SnakeGame<MAX_SIZE, T, RNG> {
177 pub fn new(
178 size_x: u8,
179 size_y: u8,
180 scale_x: u8,
181 scale_y: u8,
182 rand_source: RNG,
183 snake_color: T,
184 food_color: T,
185 food_lifetime: u8,
186 ) -> Self {
187 let snake = Snake::<T, MAX_SIZE>::new(snake_color, size_x / scale_x, size_y / scale_y);
188 let mut food =
189 Food::<T, RNG>::new(food_color, rand_source, size_x / scale_x, size_y / scale_y);
190 food.replace(&snake);
191 SnakeGame {
192 snake,
193 food,
194 food_age: 0,
195 food_lifetime,
196 size_x,
197 size_y,
198 scale_x,
199 scale_y,
200 }
201 }
202 pub fn set_direction(&mut self, direction: Direction) {
203 self.snake.set_direction(direction);
204 }
205 pub fn draw<D>(&mut self, target: &mut D) -> ()
206 where
207 D: DrawTarget<Color = T>,
208 {
209 self.snake.make_step();
210 let hit = self.snake.contains(self.food.get_pixel().0);
211 if hit {
212 self.snake.grow();
213 }
214 self.food_age += 1;
215 if self.food_age >= self.food_lifetime || hit {
216 self.food.replace(&self.snake);
217 self.food_age = 0;
218 }
219
220 let mut scaled_display = ScaledDisplay::<D> {
221 real_display: target,
222 size_x: self.size_x / self.scale_x,
223 size_y: self.size_y / self.scale_y,
224 scale_x: self.scale_x,
225 scale_y: self.scale_y,
226 };
227
228 for part in self.snake.into_iter() {
229 _ = part.draw(&mut scaled_display);
230 }
231 _ = self.food.get_pixel().draw(&mut scaled_display);
232 }
233}
234
235struct ScaledDisplay<'a, T: DrawTarget> {
237 real_display: &'a mut T,
238 size_x: u8,
239 size_y: u8,
240 scale_x: u8,
241 scale_y: u8,
242}
243
244impl<'a, T: DrawTarget> DrawTarget for ScaledDisplay<'a, T> {
245 type Color = T::Color;
246 type Error = T::Error;
247
248 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
249 where
250 I: IntoIterator<Item = Pixel<Self::Color>>,
251 {
252 for pixel in pixels {
253 let style = PrimitiveStyle::with_fill(pixel.1);
254 Rectangle::new(
255 Point::new(
256 pixel.0.x * self.scale_x as i32,
257 pixel.0.y * self.scale_y as i32,
258 ),
259 Size::new(self.scale_x as u32, self.scale_y as u32),
260 )
261 .into_styled(style)
262 .draw(self.real_display)?;
263 }
264 Ok(())
265 }
266}
267
268impl<'a, T: DrawTarget> OriginDimensions for ScaledDisplay<'a, T> {
269 fn size(&self) -> Size {
270 Size::new(self.size_x as u32, self.size_y as u32)
271 }
272}
273
274#[cfg(test)]
275mod tests {
276
277 use crate::Snake;
278 use embedded_graphics::pixelcolor::*;
279 use embedded_graphics::prelude::*;
280
281 #[test]
282 fn snake_basic() {
283 let mut snake = Snake::<Rgb888, 20>::new(Rgb888::RED, 8, 8);
284 snake.set_direction(crate::Direction::Right);
285 assert_eq!(
286 Pixel::<Rgb888>(Point { x: 0, y: 0 }, Rgb888::RED),
287 snake.into_iter().next().unwrap()
288 );
289 snake.make_step();
290 assert_eq!(
291 Pixel::<Rgb888>(Point { x: 1, y: 0 }, Rgb888::RED),
292 snake.into_iter().nth(0).unwrap()
293 );
294 assert_eq!(
295 Pixel::<Rgb888>(Point { x: 0, y: 0 }, Rgb888::RED),
296 snake.into_iter().nth(1).unwrap()
297 );
298 snake.set_direction(crate::Direction::Down);
299 snake.make_step();
300 assert_eq!(
301 Pixel::<Rgb888>(Point { x: 1, y: 1 }, Rgb888::RED),
302 snake.into_iter().nth(0).unwrap()
303 );
304 assert_eq!(
305 Pixel::<Rgb888>(Point { x: 1, y: 0 }, Rgb888::RED),
306 snake.into_iter().nth(1).unwrap()
307 );
308 assert_eq!(
309 Pixel::<Rgb888>(Point { x: 0, y: 0 }, Rgb888::RED),
310 snake.into_iter().nth(2).unwrap()
311 );
312 assert_eq!(true, snake.contains(Point { x: 0, y: 0 }));
313 assert_eq!(true, snake.contains(Point { x: 1, y: 0 }));
314 assert_eq!(true, snake.contains(Point { x: 1, y: 1 }));
315 }
316}