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