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