rsnaker/graphics/sprites/
fruit.rs1use crate::graphics::graphic_block::{GraphicBlock, Position};
20use ratatui::buffer::Buffer;
21use ratatui::layout::Rect;
22use ratatui::prelude::Widget;
23use ratatui::style::Style;
24use ratatui::widgets::Paragraph;
25use ratatui::widgets::WidgetRef;
26use std::time::{Duration, Instant};
27
28pub const FRUITS_SCORES_PROBABILITIES: &[(&str, i32, u16, i16)] = &[
33 ("🦞", -50, 5, -150),
34 ("🥥", -10, 5, -40),
35 ("🍇", 5, 4, 0),
36 ("🍐", 10, 10, 5),
37 ("🥝", 20, 10, 8),
38 ("🍋", 30, 15, 10),
39 ("🍌", 40, 15, 15),
40 ("🍉", 50, 15, 15),
41 ("🍎", 75, 15, 15),
42 ("🍓", 100, 5, 20),
43 ("🍒", 200, 1, 25),
44];
45
46#[derive(PartialEq, Debug, Clone)]
49pub struct Fruit<'a> {
50 score: i32,
51 grow_snake: i16,
52 graphic_block: GraphicBlock<'a>,
53 spawned_at: Instant,
54 lifetime: Duration,
55 timer_enabled: bool,
56 timer_paused_since: Option<Instant>,
57 accumulated_paused: Duration,
58}
59
60impl<'a> Fruit<'a> {
61 #[must_use]
63 pub fn duration_multiplier(size_effect: i16) -> f32 {
64 if size_effect >= 0 {
65 (1.0 + (f32::from(size_effect) / 50.0)).min(2.0)
66 } else {
67 (1.0 + (f32::from(size_effect) / 300.0)).max(0.5)
68 }
69 }
70
71 #[must_use]
73 pub fn duration_from_base(base: Duration, size_effect: i16) -> Duration {
74 Duration::from_secs_f32(base.as_secs_f32() * Self::duration_multiplier(size_effect))
75 }
76
77 #[must_use]
79 pub fn new(
80 score: i32,
81 grow_snake_by_relative_nb: i16,
82 position: Position,
83 image: &'a str,
84 base_lifetime: f32,
85 timer_enabled: bool,
86 ) -> Fruit<'a> {
87 Self {
88 score,
89 grow_snake: grow_snake_by_relative_nb,
90 graphic_block: GraphicBlock::new(position, image, Style::default()),
91 spawned_at: Instant::now(),
92 lifetime: Duration::from_secs_f32(
93 base_lifetime * Self::duration_multiplier(grow_snake_by_relative_nb),
94 ),
95 timer_enabled,
96 timer_paused_since: None,
97 accumulated_paused: Duration::ZERO,
98 }
99 }
100
101 #[must_use]
103 pub fn is_at_position(&self, position: &Position) -> bool {
104 self.graphic_block.get_position() == position
105 }
106
107 pub fn set_position(&mut self, position: Position) {
109 self.graphic_block.set_position(position);
110 }
111
112 pub fn set_timer_paused(&mut self, paused: bool, now: Instant) {
114 match (paused, self.timer_paused_since) {
115 (true, None) => {
116 self.timer_paused_since = Some(now);
119 }
120 (false, Some(paused_since)) => {
121 self.accumulated_paused += now.duration_since(paused_since);
123 self.timer_paused_since = None;
124 }
125 _ => {}
126 }
127 }
128
129 #[must_use]
131 pub fn is_expired(&self, now: Instant) -> bool {
132 self.timer_enabled
133 && self.timer_paused_since.is_none()
134 && self.time_elasped_with_breaks_sub(now) >= self.lifetime
135 }
136
137 #[must_use]
139 pub fn remaining_time(&self, now: Instant) -> Option<Duration> {
140 if self.timer_enabled {
141 self.lifetime
142 .checked_sub(self.time_elasped_with_breaks_sub(now))
143 } else {
144 None
145 }
146 }
147
148 #[must_use]
150 pub fn get_score(&self) -> i32 {
151 self.score
152 }
153
154 #[must_use]
155 pub fn get_grow_snake(&self) -> i16 {
156 self.grow_snake
157 }
158
159 fn time_elasped_with_breaks_sub(&self, now: Instant) -> Duration {
160 now.duration_since(self.spawned_at).saturating_sub(
161 self.accumulated_paused
162 + if let Some(pause_time) = self.timer_paused_since {
163 now.duration_since(pause_time)
166 } else {
167 Duration::ZERO
168 },
169 )
170 }
171}
172
173impl Widget for Fruit<'_> {
175 fn render(self, area: Rect, buf: &mut Buffer) {
176 self.graphic_block.render(area, buf);
177 }
178}
179
180impl WidgetRef for Fruit<'_> {
182 fn render_ref(&self, area: Rect, buf: &mut Buffer) {
183 let now = Instant::now();
184 if self.is_expired(now) {
185 return;
186 }
187
188 self.graphic_block.render_ref(area, buf);
189 if !self.timer_enabled {
190 return;
191 }
192
193 if let Some(remaining) = self.remaining_time(now) {
194 let timer_text = format!("{:.1}", remaining.as_secs_f32());
195 let position = self.graphic_block.get_position();
196 let timer_area = Rect::new(position.x.saturating_add(2), position.y, 4, 1);
199 Paragraph::new(timer_text).render(timer_area, buf);
200 }
201 }
202}