Skip to main content

rsnaker/game_logic/
playing_thread_manager.rs

1pub use crate::controls::direction::Direction;
2use crate::controls::main_menu::controls_main_switch_menu;
3use crate::controls::playing_input::playing_input_loop;
4pub use crate::controls::speed::Speed;
5pub use crate::game_logic::fruits_manager::FruitsManager;
6use crate::game_logic::game_options::GameOptions;
7use crate::game_logic::playing_logic::playing_logic_loop;
8pub use crate::game_logic::state::{GameState, GameStatus};
9use crate::graphics::playing_render::playing_render_loop;
10use crate::graphics::sprites::map::Map;
11use crate::graphics::sprites::snake_body::SnakeBody;
12use ratatui::text::Span;
13use ratatui::DefaultTerminal;
14use std::cmp::max;
15use std::sync::{Arc, RwLock};
16use std::thread;
17use tracing::{debug, info, trace};
18
19/// our game engine
20/// NB: 'c must outlive 'b as, 'c (fruits manager) uses in intern the map with lock on it.
21/// NB: 't the terminal life must outlive all the other lifetimes
22pub struct Game<'a, 'b, 'c: 'b, 't: 'a + 'b + 'c> {
23    /// Game main parameters
24    options: &'t GameOptions,
25    /// The game logic speed, linked to the snake movements
26    speed: Speed,
27    /// Represents the snake moving around
28    serpent: Arc<RwLock<SnakeBody<'a>>>,
29    /// The direction chosen by the player for the snake
30    direction: Arc<RwLock<Direction>>,
31    /// The game logic map where items/snake are displayed
32    /// NB: As we want a resizable map, `RwLock`, otherwise use only Arc<Map> (immuable)
33    carte: Arc<RwLock<Map<'b>>>,
34    /// Game states and metrics (life etc.)
35    state: Arc<RwLock<GameState>>,
36    /// Manage fruits (popping, eaten, etc.)
37    fruits_manager: Arc<RwLock<FruitsManager<'c, 'b>>>,
38    /// The current terminal
39    terminal: &'t mut DefaultTerminal,
40}
41impl<'a, 'b, 'c, 't> Game<'a, 'b, 'c, 't> {
42    #[must_use]
43    fn new(
44        options: &'t GameOptions,
45        serpent: SnakeBody<'a>,
46        carte: Map<'b>,
47        terminal: &'t mut DefaultTerminal,
48    ) -> Game<'a, 'b, 'c, 't> {
49        let arc_carte = Arc::new(RwLock::new(carte));
50        let life = options.life;
51        let fruits_nb = options.nb_of_fruits;
52        let speed = options.speed;
53        Game {
54            options,
55            speed,
56            serpent: Arc::new(RwLock::new(serpent)),
57            direction: Arc::new(RwLock::new(Direction::Right)),
58            carte: arc_carte.clone(),
59            state: Arc::new(RwLock::new(GameState::new(life))),
60            fruits_manager: Arc::new(RwLock::new(FruitsManager::new(
61                fruits_nb,
62                arc_carte.clone(),
63                options.fruit_timer,
64                f32::from(options.fruit_duration_seconds),
65            ))),
66            terminal,
67        }
68    }
69    /// Displays the game menu and handles user navigation
70    ///
71    /// # Panics
72    ///
73    /// This function will panic if the internal `state` lock is poisoned
74    /// and cannot be read.
75    pub fn menu(mut options: GameOptions, mut terminal: DefaultTerminal) {
76        info!("Welcome dear player ! Make your choice on Main menu !");
77        //one loop means one game, hard reset of the game from the menu
78        // (as parameters can change in the parameter menu)
79        loop {
80            //Display the menu and get the user choice: play or not
81            // (as well as others menu options of course)
82            if controls_main_switch_menu(&mut terminal, &mut options) {
83                info!("Let's play! 🐍 (Run has been entered in the menu, starting the game...)");
84                // if the player wants to play, we need to initiate some game values
85                //  to get the correct case size for display
86                let case_size = u16::try_from(max(
87                    Span::raw(&options.body_symbol).width(),
88                    Span::raw(&options.head_symbol).width(),
89                    //ratatui using UnicodeWidthStr crates as dep
90                ))
91                .expect("Bad symbol size, use a real character");
92                let carte: Map = Map::new(case_size, terminal.get_frame().area());
93                let serpent: SnakeBody = SnakeBody::new(
94                    &options.body_symbol,
95                    &options.head_symbol,
96                    options.snake_length,
97                    GameOptions::initial_position(),
98                    case_size,
99                );
100                info!(Snake_lenght = ?options.snake_length, Snake_head = ?options.head_symbol, Snake_body = ?options.body_symbol, "Snake info for starting");
101                trace!(?serpent);
102                debug!(?options);
103                let mut game = Game::new(&options, serpent, carte, &mut terminal);
104                game.start();
105                if game
106                    .state
107                    .read()
108                    .expect("Panic in a previous thread, check previous error")
109                    .status
110                    == GameStatus::ByeBye
111                {
112                    break;
113                }
114            } else {
115                break;
116            }
117        }
118        info!("Good bye, come back soon, snaker is waiting for you 🐍");
119    }
120    /// Start the main Game threads: input, rendering, logic
121    pub fn start(&mut self) {
122        debug!("Starting game threads");
123        // Be careful: not all threads on the same structure and do not keep them too much
124        // => performance issue otherwise
125        // Prepare thread use of variable
126
127        //For logical thread
128        let logic_snake = Arc::clone(&self.serpent);
129        let logic_gs = Arc::clone(&self.state);
130        let logic_dir = Arc::clone(&self.direction);
131        let carte = Arc::clone(&self.carte);
132        let fruits_manager = Arc::clone(&self.fruits_manager);
133
134        // For input management thread
135        let input_gs = Arc::clone(&self.state);
136        let input_dir = Arc::clone(&self.direction);
137
138        //if we want to have a variable speed put it under an Arc<Rw>, constant can directly be put under an Arc
139        // or share as a normal variable by copy
140        //Game speed is a constant by game, so we can clone it normally
141        let current_game_speed = self.speed;
142        let negative_size_fruits = self.options.negative_size_fruits;
143        let snake_symbols = format!("{}{}", self.options.head_symbol, self.options.body_symbol);
144        //In a scope to have auto cleaning by auto join at the end of the main thread
145        thread::scope(|s| {
146            // Game logic thread
147            thread::Builder::new()
148                //For better log name the thread, otherwise just s.spawn()
149                .name("t_game_logic".to_string())
150                .spawn_scoped(s, move || {
151                    playing_logic_loop(
152                        &logic_dir,
153                        &logic_snake,
154                        &logic_gs,
155                        &carte,
156                        &fruits_manager,
157                        (current_game_speed, snake_symbols, negative_size_fruits),
158                    );
159                })
160                .expect("Unable to create the thread");
161            // input logic thread
162            thread::Builder::new()
163                //For better log name the thread, otherwise just s.spawn()
164                .name("t_input".to_string())
165                .spawn_scoped(s, move || {
166                    playing_input_loop(&input_dir, &input_gs);
167                })
168                .expect("Unable to create the thread");
169
170            // Graphical thread (last one, reusing the main thread)
171            playing_render_loop(
172                &Arc::clone(&self.carte),
173                &Arc::clone(&self.fruits_manager),
174                &Arc::clone(&self.state),
175                &Arc::clone(&self.serpent),
176                self.options.caps_fps,
177                (self.speed.score_modifier(), self.speed.symbol()),
178                self.terminal,
179            );
180        });
181    }
182}