rsnaker/game_logic/
playing_logic.rs

1use crate::controls::direction::Direction;
2use crate::controls::input::GreetingOption::StartGame;
3use crate::controls::input::{greeting_screen_manage_input, GreetingOption};
4use crate::game_logic::fruits_manager::FruitsManager;
5use crate::game_logic::state::{GameState, GameStatus};
6use crate::graphics::menus::greeting_menu::main_greeting_menu;
7use crate::graphics::sprites::fruit::Fruit;
8use crate::graphics::sprites::map::Map;
9use crate::graphics::sprites::snake_body::SnakeBody;
10use ratatui::DefaultTerminal;
11use std::sync::{Arc, RwLock};
12use std::thread::sleep;
13use std::time::Duration;
14
15/// # Panics
16/// if Arc panic while holding the resources (poisoning), no recovery mechanism implemented better crash  
17pub fn playing_logic_loop(
18    direction: &Arc<RwLock<Direction>>,
19    snake: &Arc<RwLock<SnakeBody>>,
20    gs: &Arc<RwLock<GameState>>,
21    carte: &Arc<RwLock<Map>>,
22    fruits_manager: &Arc<RwLock<FruitsManager>>,
23    (game_speed, speed_score_modifier, classic_mode): (u64, u16, bool),
24) {
25    let mut gsc;
26    loop {
27        //do not want to keep the lock for too long + cannot hold in the same thread 2 times the same hold
28        // so match a clone, or use a let
29        gsc = gs.read().unwrap().status.clone();
30        //dead snakes tell no tales, nor move :p
31        match gsc {
32            GameStatus::Playing => {
33                let mut write_guard = snake.write().unwrap();
34                //Move the snake!
35                let position = write_guard.ramp(&direction.read().unwrap(), &carte.read().unwrap());
36                // Check if we eat a fruit
37                let fruits = fruits_manager.read().unwrap().eat_some_fruits(position);
38                // fruits effects
39                if let Some(fruits) = fruits {
40                    let score_fruits = fruits.iter().map(Fruit::get_score).sum::<i32>();
41                    let size_effect = fruits.iter().map(Fruit::get_grow_snake).sum::<i16>();
42                    // in all cases except classic mode with negative size, we always apply size modifiers
43                    if !(classic_mode && size_effect <= 0) {
44                        write_guard.relative_size_change(size_effect);
45                    }
46                    //NB:Converting an u16 to an i32 is always safe in Rust because the range of u16 (0 to 65,535)
47                    // fits entirely within the range of i32 (−2,147,483,648 to 2,147,483,647).
48                    //So no need to do: speed_score_modifier.try_into().expect("too much")/match for conversion
49                    gs.write().unwrap().score += score_fruits * i32::from(speed_score_modifier);
50                    fruits_manager.write().unwrap().replace_fruits(&fruits);
51                }
52                // Check if the gamer will lose one life
53                if write_guard.is_snake_eating_itself() {
54                    //Ouch. You bite yourself
55                    let mut state_guard = gs.write().unwrap();
56                    if (state_guard.life) >= 1 {
57                        state_guard.life -= 1;
58                    }
59                    if state_guard.life == 0 {
60                        state_guard.status = GameStatus::GameOver;
61                    }
62                }
63            }
64            GameStatus::Restarting => {
65                //let some time for the restarting screen to appear
66                sleep(Duration::from_millis(1000));
67                gs.write().unwrap().reset();
68                snake.write().unwrap().reset();
69                *direction.write().unwrap() = Direction::Right;
70                //graphical resize on rendering part (not really a game_logic constant)
71            }
72            GameStatus::ByeBye | GameStatus::Menu => break,
73            GameStatus::Paused | GameStatus::GameOver => {}
74        }
75        sleep(Duration::from_millis(game_speed));
76    }
77}
78/// Control part of the main menu
79/// allows to switch to sub menu (Fruits, Speed, Parameters etc.)
80///
81/// # Return true if the player want to play, false otherwise
82///
83/// # Panics                                                                                              
84/// if Terminal writing is not possible
85pub fn controls_greeting_screen(terminal: &mut DefaultTerminal) -> bool {
86    let mut stay = true;
87    let mut action: Option<GreetingOption> = None;
88    main_greeting_menu(terminal, &None, &StartGame);
89    // To manage keys to switch selected item
90    //begin with start
91    let mut selected = 3;
92    let selection: [GreetingOption; 6] = [
93        GreetingOption::MainMenu,
94        GreetingOption::Fruits,
95        GreetingOption::Velocity,
96        GreetingOption::StartGame,
97        GreetingOption::Parameters,
98        GreetingOption::Help,
99    ];
100    while stay {
101        let current = greeting_screen_manage_input();
102        //to keep refreshing old menu otherwise and not come backing
103        if current.is_some() {
104            // if it is a key, manage the selected menu button change
105            if current == Some(GreetingOption::Next) {
106                selected = (selected + 1) % selection.len();
107            } else if current == Some(GreetingOption::Previous) {
108                //smart trick to loop over
109                selected = (selected + selection.len() - 1) % selection.len();
110            }
111            // if enter set the selected option
112            else if current == Some(GreetingOption::Enter) {
113                action = Some(selection[selected].clone());
114            } else {
115                action = current;
116            }
117        }
118        //display the menu
119        main_greeting_menu(terminal, &action, &selection[selected]);
120        if let Some(GreetingOption::StartGame | GreetingOption::QuitGame) = action {
121            stay = false;
122        }
123    }
124    action == Some(GreetingOption::StartGame)
125}