use crate::graphics::graphic_block::Position;
use crate::graphics::sprites::fruit::{Fruit, FRUITS_SCORES_PROBABILITIES};
use crate::graphics::sprites::map::Map;
use rand::{rng, RngExt};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::prelude::Widget;
use ratatui::widgets::WidgetRef;
use std::sync::{Arc, RwLock};
pub struct FruitsManager<'a, 'b: 'a> {
fruits: Vec<Fruit<'a>>, carte: Arc<RwLock<Map<'b>>>, }
impl<'a, 'b> FruitsManager<'a, 'b> {
#[must_use]
pub fn new(nb: u16, carte: Arc<RwLock<Map<'b>>>) -> Self {
let mut fm = Self {
fruits: Vec::with_capacity(nb as usize),
carte,
};
fm.init(nb);
fm
}
fn spawn_random(carte: &Map) -> Fruit<'a> {
let position = Self::generate_position_rounded_by_cs(carte);
let random_value: u16 = rng().random_range(1..100);
let mut cumulative_probability = 0;
for &(image, score, probability, size_effect) in FRUITS_SCORES_PROBABILITIES {
cumulative_probability += probability;
if random_value <= cumulative_probability {
return Fruit::new(score, size_effect, position, image);
}
}
let (image, score, _, size_effect) = FRUITS_SCORES_PROBABILITIES[0];
Fruit::new(score, size_effect, position, image)
}
pub fn replace_fruits(&mut self, fruits_to_remove: &[Fruit<'a>]) {
let nb = fruits_to_remove.len();
self.fruits
.retain(|fruit| !fruits_to_remove.contains(fruit));
{
let carte_guard = self.carte.read().unwrap();
for _ in 0..nb {
self.fruits.push(Self::spawn_random(&carte_guard));
}
}
}
#[must_use]
pub fn eat_some_fruits(&self, position: &Position) -> Option<Vec<Fruit<'a>>> {
let eaten: Vec<Fruit<'a>> = self
.fruits
.iter()
.filter(|x| x.is_at_position(position))
.cloned()
.collect();
if eaten.is_empty() { None } else { Some(eaten) }
}
fn generate_position_rounded_by_cs(carte: &Map) -> Position {
let mut rng = rng();
let cs = carte.get_case_size();
let csy = 1;
let width = carte.area().width;
let height = carte.area().height;
let mut max_index_x = (width / cs).saturating_sub(cs);
let mut max_index_y = (height / csy).saturating_sub(csy);
if max_index_x <= 1 {
max_index_x = 2;
}
if max_index_y <= 1 {
max_index_y = 2;
}
Position {
x: rng.random_range(1..max_index_x) * cs,
y: rng.random_range(1..max_index_y) * csy,
}
}
pub(crate) fn reset_to_terminal_size(&mut self) {
for f in &mut self.fruits {
f.set_position(Self::generate_position_rounded_by_cs(
&self.carte.read().unwrap(),
));
}
}
pub(crate) fn reset(&mut self) {
let len = u16::try_from(self.fruits.len()).unwrap();
self.fruits.clear();
self.init(len);
}
fn init(&mut self, nb: u16) {
for _ in 0..nb {
self.fruits
.push(Self::spawn_random(&self.carte.read().unwrap()));
}
}
}
impl<'a> WidgetRef for FruitsManager<'a, 'a> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
for fruit in &self.fruits {
fruit.render_ref(area, buf);
}
}
}
impl<'a> Widget for FruitsManager<'a, 'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
impl<'a> Widget for &FruitsManager<'a, 'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn mock_map() -> Arc<RwLock<Map<'static>>> {
Arc::new(RwLock::new(Map::new(2, Rect::new(0, 0, 160, 12))))
}
fn dummy_position() -> Position {
Position { x: 10, y: 10 }
}
#[test]
fn test_new_creates_correct_number_of_fruits() {
let map = mock_map();
let manager = FruitsManager::new(5, map);
assert_eq!(manager.fruits.len(), 5);
}
#[test]
fn test_replace_fruits_removes_and_adds_new() {
let map = mock_map();
let mut manager = FruitsManager::new(3, Arc::clone(&map));
let fruits_to_remove = vec![manager.fruits[0].clone()];
manager.replace_fruits(&fruits_to_remove);
assert_eq!(manager.fruits.len(), 3);
assert!(!manager.fruits.contains(&fruits_to_remove[0]));
}
#[test]
fn test_eat_some_fruits_returns_correct_fruit() {
let map = mock_map();
let mut manager = FruitsManager::new(3, Arc::clone(&map));
let fruit = Fruit::new(10, 1, dummy_position(), "🍎");
manager.fruits[0] = fruit.clone();
let result = manager.eat_some_fruits(&dummy_position());
assert!(result.is_some());
assert!(result.unwrap().contains(&fruit));
}
#[test]
fn test_eat_some_fruits_returns_none_if_no_fruit() {
let map = mock_map();
let manager = FruitsManager::new(3, Arc::clone(&map));
let result = manager.eat_some_fruits(&Position { x: 999, y: 999 });
assert!(result.is_none());
}
}