rsnaker/graphics/
playing_render.rs1use crate::game_logic::fruits_manager::FruitsManager;
2use crate::game_logic::state::{GameState, GameStatus};
3use crate::graphics::menus;
4use crate::graphics::sprites::map::Map;
5use crate::graphics::sprites::snake_body::SnakeBody;
6use ratatui::layout::Rect;
7use ratatui::widgets::Paragraph;
8use ratatui::{DefaultTerminal, Frame};
9use std::sync::{Arc, RwLock};
10use std::thread::sleep;
11use std::time::{Duration, Instant};
12use tracing::{trace, trace_span};
13
14const BOTTOM_SPEED_FPS_SCORE_RECT: Rect = Rect::new(1, 9999, 70, 1);
17const LIFE_RECT: Rect = Rect::new(1, 0, 60, 1);
18const NB_OF_FRAMES_WINDOW: f64 = 1_000.0;
19const TOO_MUCH_LIVES_TO_DISPLAY: &str = " life: ❤️❤️❤️❤️❤️... ";
20pub fn playing_render_loop<'a: 'b, 'b>(
23 carte: &Arc<RwLock<Map>>,
24 fruits_manager: &Arc<RwLock<FruitsManager<'a, 'b>>>,
25 state: &Arc<RwLock<GameState>>,
26 serpent: &Arc<RwLock<SnakeBody>>,
27 caps_fps: bool,
28 speed_effect: (u16, &str),
29 terminal: &mut DefaultTerminal,
30) {
31 let speed_text = format!("Speed: x{}{}", speed_effect.0, speed_effect.1);
33
34 let mut rendering_break = false;
36 let mut need_carte_resize = false;
37 let mut frame_count = 0f64;
38 let mut start_windows_time = Instant::now();
39 let mut start_frame_time: Instant;
40 let target_frame_time = Duration::from_secs_f64(1.0 / 60.0); 'render_loop: loop {
45 let _span = trace_span!("render_iteration", frame = frame_count).entered();
46 start_frame_time = Instant::now();
48 frame_count += 1.0;
49 if frame_count >= NB_OF_FRAMES_WINDOW {
51 frame_count = 1.0;
52 start_windows_time = Instant::now();
53 }
54 terminal
56 .draw(|frame| {
57 let area = frame.area();
58 {
60 let map_guard = carte.read().unwrap();
62 let area_map = map_guard.area();
63 frame.render_widget(map_guard.get_widget(), *area_map);
64 if area.height != area_map.height || area.width != area_map.width {
65 need_carte_resize = true;
66 }
67 }
68 if need_carte_resize {
71 carte.write().unwrap().resize_to_terminal(area);
72 fruits_manager.write().unwrap().reset_to_terminal_size();
73 need_carte_resize = false;
74 }
75 {
77 frame.render_widget(
79 Paragraph::new(format!(
80 "{speed_text} | FPS: {} | Score: {} ",
81 (frame_count / start_windows_time.elapsed().as_secs_f64()).floor(),
82 state.read().unwrap().score
83 )),
84 BOTTOM_SPEED_FPS_SCORE_RECT.clamp(frame.area()),
85 );
86 }
87 {
89 let state_guard = state.read().unwrap();
90 let life = state_guard.life as usize;
92 frame.render_widget(
93 Paragraph::new(if life > 5 {
94 TOO_MUCH_LIVES_TO_DISPLAY.to_string()
95 } else {
96 format!(" life: {} ", "❤️ ".repeat(life))
97 }),
98 LIFE_RECT.clamp(frame.area()),
99 );
100 }
101 {
104 let snake_read = serpent.read().unwrap(); frame.render_widget(&*snake_read, frame.area());
108 }
109 {
110 let fruits_manager_read = fruits_manager.read().unwrap(); frame.render_widget(&*fruits_manager_read, frame.area());
114 }
115
116 let state_guard = state.read().unwrap();
118 rendering_break = game_state_render(
119 &state_guard.status,
120 state_guard.score,
121 state_guard.rank,
122 frame,
123 );
124 })
125 .expect("bad rendering, check sprites position");
126 if rendering_break {
127 sleep(Duration::from_secs(1));
129 break 'render_loop;
131 }
132 let frame_time = start_frame_time.elapsed();
134 if caps_fps && frame_time < target_frame_time {
137 sleep(target_frame_time.saturating_sub(frame_time));
138 }
139 trace!(?frame_time, "Frame rendered");
140 }
141}
142fn game_state_render(
144 state: &GameStatus,
145 score: u32,
146 rank: Option<usize>,
147 frame: &mut Frame,
148) -> bool {
149 let mut rendering_break = false;
150 match state {
151 GameStatus::Paused => {
152 menus::messages::pause_paragraph(frame);
153 }
154 GameStatus::GameOver(selection) => {
155 menus::messages::game_over_paragraph(frame, selection, score, rank);
156 }
157 GameStatus::ByeBye => {
158 menus::messages::byebye_paragraph(frame);
159 rendering_break = true;
160 }
161 GameStatus::Playing => (),
162 GameStatus::Restarting => {
163 menus::messages::restart_paragraph(frame);
164 }
165 GameStatus::Menu => {
166 menus::messages::menu_paragraph(frame);
167 rendering_break = true;
168 }
169 }
170 rendering_break
171}