embedded_snake/
lib.rs

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
235/// A dummy DrawTarget implementation that can magnify each pixel so the user code does not need to adapt for scaling things
236struct 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}