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 fn init(&mut self, nb: u16) {
148 for _ in 0..nb {
149 self.fruits
150 .push(Self::spawn_random(&self.carte.read().unwrap()));
151 }
152 }
153}
154
155impl<'a> WidgetRef for FruitsManager<'a, 'a> {
157 fn render_ref(&self, area: Rect, buf: &mut Buffer) {
158 for fruit in &self.fruits {
159 fruit.render_ref(area, buf);
160 }
161 }
162}
163
164impl<'a> Widget for FruitsManager<'a, 'a> {
166 fn render(self, area: Rect, buf: &mut Buffer) {
167 self.render_ref(area, buf);
168 }
169}
170
171impl<'a> Widget for &FruitsManager<'a, 'a> {
172 fn render(self, area: Rect, buf: &mut Buffer) {
173 self.render_ref(area, buf);
174 }
175}
176
177#[cfg(test)]
179mod tests {
180 use super::*;
181 use std::sync::Arc;
182
183 fn mock_map() -> Arc<RwLock<Map<'static>>> {
186 Arc::new(RwLock::new(Map::new(2, Rect::new(0, 0, 160, 12))))
187 }
188
189 fn dummy_position() -> Position {
190 Position { x: 10, y: 10 }
191 }
192
193 #[test]
194 fn test_new_creates_correct_number_of_fruits() {
195 let map = mock_map();
196 let manager = FruitsManager::new(5, map);
197 assert_eq!(manager.fruits.len(), 5);
198 }
199
200 #[test]
201 fn test_replace_fruits_removes_and_adds_new() {
202 let map = mock_map();
203 let mut manager = FruitsManager::new(3, Arc::clone(&map));
204 let fruits_to_remove = vec![manager.fruits[0].clone()];
205 manager.replace_fruits(&fruits_to_remove);
206 assert_eq!(manager.fruits.len(), 3);
207 assert!(!manager.fruits.contains(&fruits_to_remove[0]));
208 }
209
210 #[test]
211 fn test_eat_some_fruits_returns_correct_fruit() {
212 let map = mock_map();
213 let mut manager = FruitsManager::new(3, Arc::clone(&map));
214 let fruit = Fruit::new(10, 1, dummy_position(), "🍎");
215 manager.fruits[0] = fruit.clone();
216 let result = manager.eat_some_fruits(&dummy_position());
217 assert!(result.is_some());
218 assert!(result.unwrap().contains(&fruit));
219 }
220
221 #[test]
222 fn test_eat_some_fruits_returns_none_if_no_fruit() {
223 let map = mock_map();
224 let manager = FruitsManager::new(3, Arc::clone(&map));
225 let result = manager.eat_some_fruits(&Position { x: 999, y: 999 });
226 assert!(result.is_none());
227 }
228}