rsnaker/game_logic/
playing_thread_manager.rs

1pub use crate::controls::direction::Direction;
2use crate::controls::input::playing_input_loop;
3pub use crate::controls::speed::Speed;
4pub use crate::game_logic::fruits_manager::FruitsManager;
5use crate::game_logic::game_options::GameOptions;
6use crate::game_logic::playing_logic::{controls_greeting_screen, playing_logic_loop};
7pub use crate::game_logic::state::{GameState, GameStatus};
8use crate::graphics::playing_render::playing_render_loop;
9use crate::graphics::sprites::map::Map;
10use crate::graphics::sprites::snake_body::SnakeBody;
11use ratatui::DefaultTerminal;
12use std::sync::{Arc, RwLock};
13use std::thread;
14
15/// Our game engine
16///NB: 'c must outlive 'b as, 'c (fruits manager) uses in intern the map with lock on it.
17pub struct Game<'a, 'b, 'c: 'b> {
18    /// Game main parameters
19    options: GameOptions,
20    /// The game logic speed, linked to the snake movements
21    speed: Speed,
22    /// Represents the snake moving around
23    serpent: Arc<RwLock<SnakeBody<'a>>>,
24    /// The direction chosen by the player for the snake
25    direction: Arc<RwLock<Direction>>,
26    /// The game logic map where items/snake are displayed
27    /// NB: As we want a resizable map, ``RwLock``, otherwise use only Arc<Map> (immuable)
28    carte: Arc<RwLock<Map<'b>>>,
29    /// Game states and metrics (life etc.)
30    state: Arc<RwLock<GameState>>,
31    /// Manage fruits (popping, eaten etc.)
32    fruits_manager: Arc<RwLock<FruitsManager<'c, 'b>>>,
33    /// The current terminal
34    terminal: DefaultTerminal,
35}
36impl<'a, 'b, 'c> Game<'a, 'b, 'c> {
37    #[must_use]
38    pub fn new(
39        options: GameOptions,
40        serpent: SnakeBody<'a>,
41        carte: Map<'b>,
42        terminal: DefaultTerminal,
43    ) -> Game<'a, 'b, 'c> {
44        let arc_carte = Arc::new(RwLock::new(carte));
45        let life = options.life;
46        let fruits_nb = options.nb_of_fruit;
47        let speed = options.speed;
48        Game {
49            options,
50            speed,
51            serpent: Arc::new(RwLock::new(serpent)),
52            direction: Arc::new(RwLock::new(Direction::Right)),
53            carte: arc_carte.clone(),
54            state: Arc::new(RwLock::new(GameState::new(life))),
55            fruits_manager: Arc::new(RwLock::new(FruitsManager::new(
56                fruits_nb,
57                arc_carte.clone(),
58            ))),
59            terminal,
60        }
61    }
62    /// Displays the game menu and handles user navigation
63    ///
64    /// # Panics
65    ///
66    /// This function will panic if the internal `state` lock is poisoned
67    /// and cannot be read.
68    pub fn menu(&mut self) {
69        // In a loop to allow to replay and to go back to main menu
70        while self
71            .state
72            .read()
73            .expect("Panic in a previous thread, check previous error")
74            .status
75            != GameStatus::ByeBye
76        {
77            if controls_greeting_screen(&mut self.terminal) {
78                self.init();
79                self.start();
80            } else {
81                return;
82            }
83        }
84    }
85    fn init(&mut self) {
86        //init data for a new game
87        self.state.write().unwrap().reset();
88        self.serpent.write().unwrap().reset();
89        *self.direction.write().unwrap() = Direction::Right;
90    }
91    /// Start the main Game threads: input, rendering, logic
92    pub fn start(&mut self) {
93        // Be careful: not all threads on the same structure and do not keep them too much
94        // => performance issue otherwise
95        // Prepare thread use of variable
96
97        //For logical thread
98        let logic_snake = Arc::clone(&self.serpent);
99        let logic_gs = Arc::clone(&self.state);
100        let logic_dir = Arc::clone(&self.direction);
101        let carte = Arc::clone(&self.carte);
102        let fruits_manager = Arc::clone(&self.fruits_manager);
103
104        // For input management thread
105        let input_gs = Arc::clone(&self.state);
106        let input_dir = Arc::clone(&self.direction);
107
108        //if we want to have a variable speed put it under an Arc<Rw>, constant can directly be put under an Arc
109        // or share as a normal variable by copy
110        let game_speed = self.speed.ms_value();
111        let speed_score_modifier = self.speed.score_modifier();
112        let classic = self.options.classic_mode;
113
114        //In a scope to have auto cleaning by auto join at the end of the main thread
115        thread::scope(|s| {
116            // Game logic thread
117            s.spawn(move || {
118                playing_logic_loop(
119                    &logic_dir,
120                    &logic_snake,
121                    &logic_gs,
122                    &carte,
123                    &fruits_manager,
124                    (game_speed, speed_score_modifier, classic),
125                );
126            });
127            // input logic thread
128            s.spawn(move || {
129                playing_input_loop(&input_dir, &input_gs);
130            });
131
132            // Graphical thread (last one, reusing the main thread)
133            playing_render_loop(
134                &Arc::clone(&self.carte),
135                &Arc::clone(&self.fruits_manager),
136                &Arc::clone(&self.state),
137                &Arc::clone(&self.serpent),
138                self.options.uncaps_fps,
139                self.speed.symbol(),
140                &mut self.terminal,
141            );
142        });
143    }
144}