use crate::graphics::graphic_block::{GraphicBlock, Position};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::prelude::Widget;
use ratatui::style::Style;
use ratatui::widgets::Paragraph;
use ratatui::widgets::WidgetRef;
use std::time::{Duration, Instant};
pub const FRUITS_SCORES_PROBABILITIES: &[(&str, i32, u16, i16)] = &[
("🦞", -50, 5, -150),
("🥥", -10, 5, -40),
("🍇", 5, 4, 0),
("🍐", 10, 10, 5),
("🥝", 20, 10, 8),
("🍋", 30, 15, 10),
("🍌", 40, 15, 15),
("🍉", 50, 15, 15),
("🍎", 75, 15, 15),
("🍓", 100, 5, 20),
("🍒", 200, 1, 25),
];
#[derive(PartialEq, Debug, Clone)]
pub struct Fruit<'a> {
score: i32,
grow_snake: i16,
graphic_block: GraphicBlock<'a>,
spawned_at: Instant,
lifetime: Duration,
timer_enabled: bool,
timer_paused_since: Option<Instant>,
accumulated_paused: Duration,
}
impl<'a> Fruit<'a> {
#[must_use]
pub fn duration_multiplier(size_effect: i16) -> f32 {
if size_effect >= 0 {
(1.0 + (f32::from(size_effect) / 50.0)).min(2.0)
} else {
(1.0 + (f32::from(size_effect) / 300.0)).max(0.5)
}
}
#[must_use]
pub fn duration_from_base(base: Duration, size_effect: i16) -> Duration {
Duration::from_secs_f32(base.as_secs_f32() * Self::duration_multiplier(size_effect))
}
#[must_use]
pub fn new(
score: i32,
grow_snake_by_relative_nb: i16,
position: Position,
image: &'a str,
base_lifetime: f32,
timer_enabled: bool,
) -> Fruit<'a> {
Self {
score,
grow_snake: grow_snake_by_relative_nb,
graphic_block: GraphicBlock::new(position, image, Style::default()),
spawned_at: Instant::now(),
lifetime: Duration::from_secs_f32(
base_lifetime * Self::duration_multiplier(grow_snake_by_relative_nb),
),
timer_enabled,
timer_paused_since: None,
accumulated_paused: Duration::ZERO,
}
}
#[must_use]
pub fn is_at_position(&self, position: &Position) -> bool {
self.graphic_block.get_position() == position
}
pub fn set_position(&mut self, position: Position) {
self.graphic_block.set_position(position);
}
pub fn set_timer_paused(&mut self, paused: bool, now: Instant) {
match (paused, self.timer_paused_since) {
(true, None) => {
self.timer_paused_since = Some(now);
}
(false, Some(paused_since)) => {
self.accumulated_paused += now.duration_since(paused_since);
self.timer_paused_since = None;
}
_ => {}
}
}
#[must_use]
pub fn is_expired(&self, now: Instant) -> bool {
self.timer_enabled
&& self.timer_paused_since.is_none()
&& self.time_elasped_with_breaks_sub(now) >= self.lifetime
}
#[must_use]
pub fn remaining_time(&self, now: Instant) -> Option<Duration> {
if self.timer_enabled {
self.lifetime
.checked_sub(self.time_elasped_with_breaks_sub(now))
} else {
None
}
}
#[must_use]
pub fn get_score(&self) -> i32 {
self.score
}
#[must_use]
pub fn get_grow_snake(&self) -> i16 {
self.grow_snake
}
fn time_elasped_with_breaks_sub(&self, now: Instant) -> Duration {
now.duration_since(self.spawned_at).saturating_sub(
self.accumulated_paused
+ if let Some(pause_time) = self.timer_paused_since {
now.duration_since(pause_time)
} else {
Duration::ZERO
},
)
}
}
impl Widget for Fruit<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.graphic_block.render(area, buf);
}
}
impl WidgetRef for Fruit<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
let now = Instant::now();
if self.is_expired(now) {
return;
}
self.graphic_block.render_ref(area, buf);
if !self.timer_enabled {
return;
}
if let Some(remaining) = self.remaining_time(now) {
let timer_text = format!("{:.1}", remaining.as_secs_f32());
let position = self.graphic_block.get_position();
let timer_area = Rect::new(position.x.saturating_add(2), position.y, 4, 1);
Paragraph::new(timer_text).render(timer_area, buf);
}
}
}