1use crate::graphics::graphic_block::Position;
35use crate::graphics::sprites::fruit::{Fruit, FRUITS_SCORES_PROBABILITIES};
36use crate::graphics::sprites::map::Map;
37use rand::{rng, RngExt};
38use ratatui::buffer::Buffer;
39use ratatui::layout::Rect;
40use ratatui::prelude::Widget;
41use ratatui::widgets::WidgetRef;
42use std::sync::{Arc, RwLock};
43use std::time::Instant;
44
45pub struct FruitsManager<'a, 'b: 'a> {
48 fruits: Vec<Fruit<'a>>, carte: Arc<RwLock<Map<'b>>>, fruit_timer_enabled: bool,
51 fruit_base_lifetime: f32,
52}
53
54impl<'a, 'b> FruitsManager<'a, 'b> {
55 #[must_use]
69 pub fn new(
70 nb: u16,
71 carte: Arc<RwLock<Map<'b>>>,
72 fruit_timer_enabled: bool,
73 fruit_base_lifetime: f32,
74 ) -> Self {
75 let mut fm = Self {
76 fruits: Vec::with_capacity(nb as usize),
77 carte,
78 fruit_timer_enabled,
79 fruit_base_lifetime,
80 };
81 fm.init(nb);
82 fm
83 }
84
85 fn spawn_random(carte: &Map, fruit_timer_enabled: bool, fruit_base_lifetime: f32) -> Fruit<'a> {
87 let position = Self::generate_position_rounded_by_cs(carte);
88 let random_value: u16 = rng().random_range(1..=100);
89 let mut cumulative_probability = 0;
90 for &(image, score, probability, size_effect) in FRUITS_SCORES_PROBABILITIES {
91 cumulative_probability += probability;
92 if random_value <= cumulative_probability {
93 return Fruit::new(
94 score,
95 size_effect,
96 position,
97 image,
98 fruit_base_lifetime,
99 fruit_timer_enabled,
100 );
101 }
102 }
103 let (image, score, _, size_effect) = FRUITS_SCORES_PROBABILITIES[0];
105 Fruit::new(
106 score,
107 size_effect,
108 position,
109 image,
110 fruit_base_lifetime,
111 fruit_timer_enabled,
112 )
113 }
114
115 pub fn replace_fruits(&mut self, fruits_to_remove: &[Fruit<'a>]) {
119 if fruits_to_remove.is_empty() {
120 return;
121 }
122 let initial_count = self.fruits.len();
123 self.fruits
124 .retain(|fruit| !fruits_to_remove.contains(fruit));
125
126 self.spawn_missing_fruits(initial_count - self.fruits.len());
127 }
128
129 pub fn update(&mut self) {
131 if !self.fruit_timer_enabled {
132 return;
133 }
134 let now = Instant::now();
135 let initial_count = self.fruits.len();
136 self.fruits.retain(|fruit| !fruit.is_expired(now));
138
139 self.spawn_missing_fruits(initial_count - self.fruits.len());
140 }
141
142 fn spawn_missing_fruits(&mut self, count: usize) {
144 if count == 0 {
145 return;
146 }
147 let carte_guard = self.carte.read().unwrap();
149 self.fruits.reserve(count);
151
152 for _ in 0..count {
153 self.fruits.push(Self::spawn_random(
154 &carte_guard,
155 self.fruit_timer_enabled,
156 self.fruit_base_lifetime,
157 ));
158 }
159 }
160
161 pub fn set_timer_paused(&mut self, paused: bool) {
163 if !self.fruit_timer_enabled {
164 return;
165 }
166
167 let now = Instant::now();
168 for fruit in &mut self.fruits {
169 fruit.set_timer_paused(paused, now);
170 }
171 }
172
173 #[must_use]
175 pub fn eat_some_fruits(&self, position: &Position) -> Option<Vec<Fruit<'a>>> {
176 let now = Instant::now();
177 let eaten: Vec<Fruit<'a>> = self
178 .fruits
179 .iter()
180 .filter(|x| x.is_at_position(position) && !x.is_expired(now))
181 .cloned()
182 .collect();
183 if eaten.is_empty() { None } else { Some(eaten) }
184 }
185
186 fn generate_position_rounded_by_cs(carte: &Map) -> Position {
188 let mut rng = rng();
189 let cs = carte.get_case_size();
190 let csy = 1;
191 let width = carte.area().width;
192 let height = carte.area().height;
193 let mut max_index_x = (width / cs).saturating_sub(cs);
195 let mut max_index_y = (height / csy).saturating_sub(csy);
196 if max_index_x <= 1 {
198 max_index_x = 2;
199 }
200 if max_index_y <= 1 {
201 max_index_y = 2;
202 }
203 Position {
204 x: rng.random_range(1..max_index_x) * cs,
205 y: rng.random_range(1..max_index_y) * csy,
206 }
207 }
208
209 pub(crate) fn reset_to_terminal_size(&mut self) {
210 for f in &mut self.fruits {
212 f.set_position(Self::generate_position_rounded_by_cs(
213 &self.carte.read().unwrap(),
214 ));
215 }
216 }
217
218 pub(crate) fn reset(&mut self) {
219 let len = u16::try_from(self.fruits.len()).unwrap();
220 self.fruits.clear();
221 self.init(len);
222 }
223
224 #[must_use]
226 pub fn get_fruits(&self) -> &Vec<Fruit<'a>> {
227 &self.fruits
228 }
229
230 fn init(&mut self, nb: u16) {
231 for _ in 0..nb {
232 self.fruits.push(Self::spawn_random(
233 &self.carte.read().unwrap(),
234 self.fruit_timer_enabled,
235 self.fruit_base_lifetime,
236 ));
237 }
238 }
239}
240
241impl<'a> WidgetRef for FruitsManager<'a, 'a> {
243 fn render_ref(&self, area: Rect, buf: &mut Buffer) {
244 for fruit in &self.fruits {
245 fruit.render_ref(area, buf);
246 }
247 }
248}
249
250impl<'a> Widget for FruitsManager<'a, 'a> {
252 fn render(self, area: Rect, buf: &mut Buffer) {
253 self.render_ref(area, buf);
254 }
255}
256
257impl<'a> Widget for &FruitsManager<'a, 'a> {
258 fn render(self, area: Rect, buf: &mut Buffer) {
259 self.render_ref(area, buf);
260 }
261}
262
263#[cfg(test)]
265mod tests {
266 use super::*;
267 use std::sync::Arc;
268
269 fn mock_map() -> Arc<RwLock<Map<'static>>> {
272 Arc::new(RwLock::new(Map::new(2, Rect::new(0, 0, 160, 12))))
273 }
274
275 fn dummy_position() -> Position {
276 Position { x: 10, y: 10 }
277 }
278
279 #[test]
280 fn test_new_creates_correct_number_of_fruits() {
281 let map = mock_map();
282 let manager = FruitsManager::new(5, map, true, 5f32);
283 assert_eq!(manager.fruits.len(), 5);
284 }
285
286 #[test]
287 fn test_replace_fruits_removes_and_adds_new() {
288 let map = mock_map();
289 let mut manager = FruitsManager::new(3, Arc::clone(&map), true, 5f32);
290 let fruits_to_remove = vec![manager.fruits[0].clone()];
291 manager.replace_fruits(&fruits_to_remove);
292 assert_eq!(manager.fruits.len(), 3);
293 assert!(!manager.fruits.contains(&fruits_to_remove[0]));
294 }
295
296 #[test]
297 fn test_eat_some_fruits_returns_correct_fruit() {
298 let map = mock_map();
299 let mut manager = FruitsManager::new(3, Arc::clone(&map), true, 5f32);
300 let fruit = Fruit::new(10, 1, dummy_position(), "🍎", 5f32, true);
301 manager.fruits[0] = fruit.clone();
302 let result = manager.eat_some_fruits(&dummy_position());
303 assert!(result.is_some());
304 assert!(result.unwrap().contains(&fruit));
305 }
306
307 #[test]
308 fn test_eat_some_fruits_returns_none_if_no_fruit() {
309 let map = mock_map();
310 let manager = FruitsManager::new(3, Arc::clone(&map), true, 5f32);
311 let result = manager.eat_some_fruits(&Position { x: 999, y: 999 });
312 assert!(result.is_none());
313 }
314
315 #[test]
316 fn test_update_replaces_expired_fruit() {
317 let map = mock_map();
318 let mut manager = FruitsManager::new(1, Arc::clone(&map), true, 5f32);
319 manager.fruits[0] = Fruit::new(10, 1, dummy_position(), "🍎", 0f32, true);
320
321 manager.update();
322
323 assert_eq!(manager.fruits.len(), 1);
324 assert!(!manager.fruits[0].is_expired(Instant::now()));
325 }
326}