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};
12
13const BOTTOM_SPEED_FPS_SCORE_RECT: Rect = Rect::new(1, 9999, 70, 1);
16const LIFE_RECT: Rect = Rect::new(1, 0, 60, 1);
17const NB_OF_FRAMES_WINDOW: f64 = 1_000.0;
18const TOO_MUCH_LIVES_TO_DISPLAY: &str = " life: ❤️❤️❤️❤️❤️... ";
19pub fn playing_render_loop<'a: 'b, 'b>(
22 carte: &Arc<RwLock<Map>>,
23 fruits_manager: &Arc<RwLock<FruitsManager<'a, 'b>>>,
24 state: &Arc<RwLock<GameState>>,
25 serpent: &Arc<RwLock<SnakeBody>>,
26 uncaps_fps: bool,
27 speed_symbol: &str,
28 terminal: &mut DefaultTerminal,
29) {
30 let speed_text = format!("Speed: {speed_symbol}");
32
33 let mut rendering_break = false;
35 let mut need_carte_resize = false;
36 let mut frame_count = 0f64;
37 let mut start_windows_time = Instant::now();
38 let mut start_frame_time: Instant;
39 let target_frame_time = Duration::from_secs_f64(1.0 / 60.0); 'render_loop: loop {
44 start_frame_time = Instant::now();
46 frame_count += 1.0;
47 if frame_count >= NB_OF_FRAMES_WINDOW {
49 frame_count = 1.0;
50 start_windows_time = Instant::now();
51 }
52 terminal
54 .draw(|frame| {
55 let area = frame.area();
56 {
58 let map_guard = carte.read().unwrap();
60 let area_map = map_guard.area();
61 frame.render_widget(map_guard.get_widget(), *area_map);
62 if area.height != area_map.height || area.width != area_map.width {
63 need_carte_resize = true;
64 }
65 }
66 if need_carte_resize {
69 carte.write().unwrap().resize_to_terminal(area);
70 fruits_manager.write().unwrap().resize_to_terminal();
71 need_carte_resize = false;
72 }
73 {
75 frame.render_widget(
77 Paragraph::new(format!(
78 "{speed_text} | FPS: {} | Score: {} ",
79 (frame_count / start_windows_time.elapsed().as_secs_f64()).floor(),
80 state.read().unwrap().score
81 )),
82 BOTTOM_SPEED_FPS_SCORE_RECT.clamp(frame.area()),
83 );
84 }
85 {
87 let state_guard = state.read().unwrap();
88 let life = state_guard.life as usize;
90 frame.render_widget(
91 Paragraph::new(if life > 5 {
92 TOO_MUCH_LIVES_TO_DISPLAY.to_string()
93 } else {
94 format!(" life: {} ", "❤️ ".repeat(life))
95 }),
96 LIFE_RECT.clamp(frame.area()),
97 );
98 }
99 {
102 let snake_read = serpent.read().unwrap(); frame.render_widget(&*snake_read, frame.area());
106 }
107 {
108 let fruits_manager_read = fruits_manager.read().unwrap(); frame.render_widget(&*fruits_manager_read, frame.area());
112 }
113
114 rendering_break = game_state_render(&state.read().unwrap().status, frame);
116 })
117 .expect("bad rendering, check sprites position");
118 if rendering_break {
119 sleep(Duration::from_millis(1000));
121 break 'render_loop;
123 }
124 let frame_time = start_frame_time.elapsed();
126 if !uncaps_fps && frame_time < target_frame_time {
129 sleep(target_frame_time.saturating_sub(frame_time));
130 }
131 }
132}
133fn game_state_render(state: &GameStatus, frame: &mut Frame) -> bool {
135 let mut rendering_break = false;
136 match state {
137 GameStatus::Paused => {
138 menus::status::pause_paragraph(frame);
139 }
140 GameStatus::GameOver => {
141 menus::status::game_over_paragraph(frame);
142 }
143 GameStatus::ByeBye => {
144 menus::status::byebye_paragraph(frame);
145 rendering_break = true;
146 }
147 GameStatus::Playing => (),
148 GameStatus::Restarting => {
149 menus::status::restart_paragraph(frame);
150 }
151 GameStatus::Menu => {
152 menus::status::menu_paragraph(frame);
153 rendering_break = true;
154 }
155 }
156 rendering_break
157}