rsnaker/game_logic/
fruits_manager.rs1use crate::graphics::graphic_block::Position;
30use crate::graphics::sprites::fruit::{FRUITS_SCORES_PROBABILITIES, Fruit};
31use crate::graphics::sprites::map::Map;
32use rand::{Rng, rng};
33use ratatui::buffer::Buffer;
34use ratatui::layout::Rect;
35use ratatui::prelude::Widget;
36use ratatui::widgets::WidgetRef;
37use std::sync::{Arc, RwLock};
38
39pub struct FruitsManager<'a, 'b: 'a> {
42 fruits: Vec<Fruit<'a>>, carte: Arc<RwLock<Map<'b>>>, }
45
46impl<'a, 'b> FruitsManager<'a, 'b> {
47 #[must_use]
61 pub fn new(nb: u16, carte: Arc<RwLock<Map<'b>>>) -> Self {
62 let mut fm = Self {
63 fruits: Vec::with_capacity(nb as usize),
64 carte,
65 };
66 fm.init(nb);
67 fm
68 }
69
70 fn spawn_random(carte: &Map) -> Fruit<'a> {
72 let position = Self::generate_position_rounded_by_cs(carte);
73 let mut rng = rng();
74 let random_value: u16 = rng.random_range(1..100);
75 let mut cumulative_probability = 0;
76 for &(image, score, probability, size_effect) in FRUITS_SCORES_PROBABILITIES {
77 cumulative_probability += probability;
78 if random_value <= cumulative_probability {
79 return Fruit::new(score, size_effect, position, image);
80 }
81 }
82 let (image, score, _, size_effect) = FRUITS_SCORES_PROBABILITIES[0];
84 Fruit::new(score, size_effect, position, image)
85 }
86
87 pub fn replace_fruits(&mut self, fruits_to_remove: &[Fruit<'a>]) {
91 let nb = fruits_to_remove.len();
92 self.fruits
93 .retain(|fruit| !fruits_to_remove.contains(fruit));
94 {
95 let carte_guard = self.carte.read().unwrap();
96 for _ in 0..nb {
97 self.fruits.push(Self::spawn_random(&carte_guard));
98 }
99 }
100 }
101
102 #[must_use]
104 pub fn eat_some_fruits(&self, position: &Position) -> Option<Vec<Fruit<'a>>> {
105 let eaten: Vec<Fruit<'a>> = self
106 .fruits
107 .iter()
108 .filter(|x| x.is_at_position(position))
109 .cloned()
110 .collect();
111 if eaten.is_empty() { None } else { Some(eaten) }
112 }
113
114 fn generate_position_rounded_by_cs(carte: &Map) -> Position {
116 let mut rng = rng();
117 let cs = carte.get_case_size();
118 let csy = 1;
119 let width = carte.area().width;
120 let height = carte.area().height;
121 let mut max_index_x = (width / cs).saturating_sub(cs);
122 let mut max_index_y = (height / csy).saturating_sub(csy);
123 if max_index_x <= 1 {
125 max_index_x = 2;
126 }
127 if max_index_y <= 1 {
128 max_index_y = 2;
129 }
130 Position {
131 x: rng.random_range(1..max_index_x) * cs,
132 y: rng.random_range(1..max_index_y) * csy,
133 }
134 }
135 pub(crate) fn reset_to_terminal_size(&mut self) {
136 for f in &mut self.fruits {
138 f.set_position(Self::generate_position_rounded_by_cs(
139 &self.carte.read().unwrap(),
140 ));
141 }
142 }
143 pub(crate) fn reset(&mut self) {
144 let len = u16::try_from(self.fruits.len()).unwrap();
145 self.fruits.clear();
146 self.init(len);
147 }
148 fn init(&mut self, nb: u16) {
149 for _ in 0..nb {
150 self.fruits
151 .push(Self::spawn_random(&self.carte.read().unwrap()));
152 }
153 }
154}
155
156impl<'a> WidgetRef for FruitsManager<'a, 'a> {
158 fn render_ref(&self, area: Rect, buf: &mut Buffer) {
159 for fruit in &self.fruits {
160 fruit.render_ref(area, buf);
161 }
162 }
163}
164
165impl<'a> Widget for FruitsManager<'a, 'a> {
167 fn render(self, area: Rect, buf: &mut Buffer) {
168 self.render_ref(area, buf);
169 }
170}
171
172impl<'a> Widget for &FruitsManager<'a, 'a> {
173 fn render(self, area: Rect, buf: &mut Buffer) {
174 self.render_ref(area, buf);
175 }
176}
177
178#[cfg(test)]
180mod tests {
181 use super::*;
182 use std::sync::Arc;
183
184 fn mock_map() -> Arc<RwLock<Map<'static>>> {
187 Arc::new(RwLock::new(Map::new(2, Rect::new(0, 0, 160, 12))))
188 }
189
190 fn dummy_position() -> Position {
191 Position { x: 10, y: 10 }
192 }
193
194 #[test]
195 fn test_new_creates_correct_number_of_fruits() {
196 let map = mock_map();
197 let manager = FruitsManager::new(5, map);
198 assert_eq!(manager.fruits.len(), 5);
199 }
200
201 #[test]
202 fn test_replace_fruits_removes_and_adds_new() {
203 let map = mock_map();
204 let mut manager = FruitsManager::new(3, Arc::clone(&map));
205 let fruits_to_remove = vec![manager.fruits[0].clone()];
206 manager.replace_fruits(&fruits_to_remove);
207 assert_eq!(manager.fruits.len(), 3);
208 assert!(!manager.fruits.contains(&fruits_to_remove[0]));
209 }
210
211 #[test]
212 fn test_eat_some_fruits_returns_correct_fruit() {
213 let map = mock_map();
214 let mut manager = FruitsManager::new(3, Arc::clone(&map));
215 let fruit = Fruit::new(10, 1, dummy_position(), "🍎");
216 manager.fruits[0] = fruit.clone();
217 let result = manager.eat_some_fruits(&dummy_position());
218 assert!(result.is_some());
219 assert!(result.unwrap().contains(&fruit));
220 }
221
222 #[test]
223 fn test_eat_some_fruits_returns_none_if_no_fruit() {
224 let map = mock_map();
225 let manager = FruitsManager::new(3, Arc::clone(&map));
226 let result = manager.eat_some_fruits(&Position { x: 999, y: 999 });
227 assert!(result.is_none());
228 }
229}