rsnaker/graphics/sprites/map.rs
1use crate::graphics::graphic_block::Position;
2use ratatui::layout::Rect;
3use ratatui::widgets::{Block, BorderType};
4
5/// A struct representing the game logic map.
6/// The map is resizable and represents the writeable area of the terminal, with a defined case size
7/// for each cell and a viewport for rendering the game logic world.
8///
9/// # Fields
10/// - `block`: A `Block` widget representing the map's visual border and title.
11/// - `CASE_SIZE`: The size of each cell (case) in pixels on the terminal screen.
12/// - `viewport`: The dimensions of the terminal viewport for rendering the map.
13#[derive(Clone)]
14pub struct Map<'a> {
15 block: Block<'a>,
16 case_size: u16,
17 viewport: Rect,
18}
19
20impl Map<'_> {
21 /// Creates a new `Map` instance with a given case size and viewport.
22 ///
23 /// # Parameters
24 /// - `case_size_in_px`: The size of each case (cell) on the map in pixels.
25 /// - `viewport`: The visible area of the terminal where the map will be rendered.
26 ///
27 /// # Returns
28 /// A new `Map` instance with the specified `case_size_in_px` and `viewport`.
29 ///
30 /// # Note
31 /// The map will have a double border and the title "Snake !".
32 #[must_use]
33 pub fn new<'a>(case_size_in_px: u16, viewport: Rect) -> Map<'a> {
34 Map {
35 block: Block::bordered().border_type(BorderType::Double),
36 //.title("Snake !"),
37 case_size: case_size_in_px,
38 viewport,
39 }
40 }
41 /// To resize the map viewport, not for in game logic use # unfair, but when after restarting it is OK
42 pub fn resize_to_terminal(&mut self, viewport: Rect) {
43 self.viewport = viewport;
44 }
45
46 /// Determines if a given position is outside the bounds of the map.
47 ///
48 /// # Parameters
49 /// - `Position { x, y }`: The position to check.
50 ///
51 /// # Returns
52 /// `true` if the position is outside the map's viewport, `false` otherwise.
53 ///
54 /// # Example
55 /// ```rust
56 /// use ratatui::layout::Rect;
57 /// use rsnaker::graphics::graphic_block::Position;
58 /// use rsnaker::graphics::sprites::map::Map;
59 /// let map = Map::new(10, Rect::new(0, 0, 100, 40));
60 /// let position = Position { x: 101, y: 20 };
61 /// assert!(map.out_of_map(&position));
62 /// ```
63 #[must_use]
64 pub fn out_of_map(&self, Position { x, y }: &Position) -> bool {
65 //+/- 2 to put out the bordered map (last case being ==== )
66 let x_max = self.viewport.width - (self.case_size + 2);
67 let y_max = self.viewport.height - 2;
68 let x_min = self.case_size;
69 let y_min = 1;
70 *x < x_min || *x > x_max || *y < y_min || *y > y_max
71 }
72
73 /// Reverses the position if it is outside the bounds of the map, effectively "wrapping" around
74 /// to the opposite edge of the map.
75 ///
76 /// # Parameters
77 /// - `Position { x, y }`: The position to check and possibly adjust.
78 ///
79 /// # Returns
80 /// A new `Position` where out-of-bounds coordinates are wrapped to the opposite edge of the map.
81 ///
82 /// # Example
83 /// ```rust
84 /// use ratatui::layout::Rect;
85 /// use rsnaker::graphics::graphic_block::Position;
86 /// use rsnaker::graphics::sprites::map::Map;
87 /// let map = Map::new(10, Rect::new(0, 0, 100, 40));
88 /// let position = Position { x: 101, y: 20 };
89 /// let new_position = map.out_of_map_reverse_position(&position);
90 /// assert_eq!(new_position.x, 10); // Wrapped to the opposite side
91 /// ```
92 #[must_use]
93 pub fn out_of_map_reverse_position(&self, Position { x, y }: &Position) -> Position {
94 //*2 to put out the bordered map (last case being ==== )
95 let x_max = self.viewport.width - (self.case_size + 2);
96 let y_max = self.viewport.height - 2;
97 let x_min = self.case_size;
98 let y_min = 1;
99
100 //to keep the grid coherent
101 let x_remainder = (x_max - x_min) % self.case_size;
102 let x_max = x_max - x_remainder; // adjusted
103 // y automatic as %1
104
105 if *y > y_max {
106 Position { x: *x, y: y_min }
107 } else if *y < y_min {
108 Position { x: *x, y: y_max }
109 } else if *x > x_max {
110 Position { x: x_min, y: *y }
111 } else if *x < x_min {
112 Position { x: x_max, y: *y }
113 } else {
114 Position { x: *x, y: *y }
115 }
116 }
117 /// Returns the current viewport (the visible area) of the map.
118 ///
119 /// # Returns
120 /// A reference to the `Rect` representing the map's viewport.
121 #[must_use]
122 pub fn area(&self) -> &Rect {
123 &self.viewport
124 }
125
126 /// Returns the `Block` widget representing the map's border and title.
127 ///
128 /// # Returns
129 /// A reference to the `Block` widget.
130 #[must_use]
131 pub fn get_widget(&self) -> &Block {
132 &self.block
133 }
134
135 /// Returns the size of each case (cell) on the map.
136 ///
137 /// # Returns
138 /// The size of each case in pixels.
139 #[must_use]
140 pub fn get_case_size(&self) -> u16 {
141 self.case_size
142 }
143}