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, 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 fruits: Vec<Fruit> = Vec::with_capacity(nb as usize);
63 {
64 let c = carte.read().unwrap();
65 for _ in 0..nb {
66 fruits.push(Self::spawn_random(&c));
67 }
68 }
69 Self { fruits, carte }
70 }
71
72 fn spawn_random(carte: &Map) -> Fruit<'a> {
74 let position = Self::generate_position_rounded_by_cs(carte);
75 let mut rng = rng();
76 let random_value: u16 = rng.random_range(1..100);
77 let mut cumulative_probability = 0;
78 for &(image, score, probability, size_effect) in FRUITS_SCORES_PROBABILITIES {
79 cumulative_probability += probability;
80 if random_value <= cumulative_probability {
81 return Fruit::new(score, size_effect, position, image);
82 }
83 }
84 let (image, score, _, size_effect) = FRUITS_SCORES_PROBABILITIES[0];
86 Fruit::new(score, size_effect, position, image)
87 }
88
89 pub fn replace_fruits(&mut self, fruits_to_remove: &[Fruit<'a>]) {
93 let nb = fruits_to_remove.len();
94 self.fruits
95 .retain(|fruit| !fruits_to_remove.contains(fruit));
96 {
97 let carte_guard = self.carte.read().unwrap();
98 for _ in 0..nb {
99 self.fruits.push(Self::spawn_random(&carte_guard));
100 }
101 }
102 }
103
104 #[must_use]
106 pub fn eat_some_fruits(&self, position: &Position) -> Option<Vec<Fruit<'a>>> {
107 let eaten: Vec<Fruit<'a>> = self
108 .fruits
109 .iter()
110 .filter(|x| x.is_at_position(position))
111 .cloned()
112 .collect();
113 if eaten.is_empty() { None } else { Some(eaten) }
114 }
115
116 fn generate_position_rounded_by_cs(carte: &Map) -> Position {
118 let mut rng = rng();
119 let cs = carte.get_case_size();
120 let csy = 1;
121 let width = carte.area().width;
122 let height = carte.area().height;
123 let mut max_index_x = (width / cs).saturating_sub(cs);
124 let mut max_index_y = (height / csy).saturating_sub(csy);
125 if max_index_x <= 1 {
127 max_index_x = 2;
128 }
129 if max_index_y <= 1 {
130 max_index_y = 2;
131 }
132 Position {
133 x: rng.random_range(1..max_index_x) * cs,
134 y: rng.random_range(1..max_index_y) * csy,
135 }
136 }
137 pub(crate) fn resize_to_terminal(&mut self) {
138 for f in &mut self.fruits {
140 f.set_position(Self::generate_position_rounded_by_cs(
141 &self.carte.read().unwrap(),
142 ));
143 }
144 }
145}
146
147impl<'a> WidgetRef for FruitsManager<'a, 'a> {
149 fn render_ref(&self, area: Rect, buf: &mut Buffer) {
150 for fruit in &self.fruits {
151 fruit.render_ref(area, buf);
152 }
153 }
154}
155
156impl<'a> Widget for FruitsManager<'a, 'a> {
158 fn render(self, area: Rect, buf: &mut Buffer) {
159 self.render_ref(area, buf);
160 }
161}
162
163impl<'a> Widget for &FruitsManager<'a, 'a> {
164 fn render(self, area: Rect, buf: &mut Buffer) {
165 self.render_ref(area, buf);
166 }
167}
168
169#[cfg(test)]
171mod tests {
172 use super::*;
173 use std::sync::Arc;
174
175 fn mock_map() -> Arc<RwLock<Map<'static>>> {
178 Arc::new(RwLock::new(Map::new(2, Rect::new(0, 0, 160, 12))))
179 }
180
181 fn dummy_position() -> Position {
182 Position { x: 10, y: 10 }
183 }
184
185 #[test]
186 fn test_new_creates_correct_number_of_fruits() {
187 let map = mock_map();
188 let manager = FruitsManager::new(5, map);
189 assert_eq!(manager.fruits.len(), 5);
190 }
191
192 #[test]
193 fn test_replace_fruits_removes_and_adds_new() {
194 let map = mock_map();
195 let mut manager = FruitsManager::new(3, Arc::clone(&map));
196 let fruits_to_remove = vec![manager.fruits[0].clone()];
197 manager.replace_fruits(&fruits_to_remove);
198 assert_eq!(manager.fruits.len(), 3);
199 assert!(!manager.fruits.contains(&fruits_to_remove[0]));
200 }
201
202 #[test]
203 fn test_eat_some_fruits_returns_correct_fruit() {
204 let map = mock_map();
205 let mut manager = FruitsManager::new(3, Arc::clone(&map));
206 let fruit = Fruit::new(10, 1, dummy_position(), "🍎");
207 manager.fruits[0] = fruit.clone();
208 let result = manager.eat_some_fruits(&dummy_position());
209 assert!(result.is_some());
210 assert!(result.unwrap().contains(&fruit));
211 }
212
213 #[test]
214 fn test_eat_some_fruits_returns_none_if_no_fruit() {
215 let map = mock_map();
216 let manager = FruitsManager::new(3, Arc::clone(&map));
217 let result = manager.eat_some_fruits(&Position { x: 999, y: 999 });
218 assert!(result.is_none());
219 }
220}