rsnaker/game_logic/
playing_logic.rs

1use crate::controls::direction::Direction;
2use crate::controls::speed::Speed;
3use crate::game_logic::fruits_manager::FruitsManager;
4use crate::game_logic::high_score::{HighScore, HighScoreManager};
5use crate::game_logic::state::{GameOverMenu, GameState, GameStatus};
6use crate::graphics::sprites::fruit::Fruit;
7use crate::graphics::sprites::map::Map;
8use crate::graphics::sprites::snake_body::SnakeBody;
9use std::sync::{Arc, RwLock};
10use std::thread::sleep;
11use std::time::Duration;
12
13/// # Panics
14/// if Arc panics while holding the resources (poisoning),
15/// no recovery mechanism implemented better crash  
16pub fn playing_logic_loop(
17    direction: &Arc<RwLock<Direction>>,
18    snake: &Arc<RwLock<SnakeBody>>,
19    gs: &Arc<RwLock<GameState>>,
20    carte: &Arc<RwLock<Map>>,
21    fruits_manager: &Arc<RwLock<FruitsManager>>,
22    (game_speed, snake_symbols, classic_mode): (Speed, String, bool),
23) {
24    let mut gsc;
25    loop {
26        //do not want to keep the lock for too long + cannot hold in the same thread 2 times the same hold
27        // so match a clone or use a let
28        gsc = gs.read().unwrap().status.clone();
29        //dead snakes tell no tales, nor move :p
30        match gsc {
31            GameStatus::Playing => {
32                let mut write_guard = snake.write().unwrap();
33                //Move the snake!
34                let position = write_guard.ramp(&direction.read().unwrap(), &carte.read().unwrap());
35                // Check if we eat a fruit
36                let fruits = fruits_manager.read().unwrap().eat_some_fruits(position);
37                // fruits effects
38                if let Some(fruits) = fruits {
39                    let score_fruits = fruits.iter().map(Fruit::get_score).sum::<i32>();
40                    let size_effect = fruits.iter().map(Fruit::get_grow_snake).sum::<i16>();
41                    // in all cases except classic mode with negative size, we always apply size modifiers
42                    if !(classic_mode && size_effect <= 0) {
43                        write_guard.relative_size_change(size_effect);
44                    }
45                    //NB:Converting an u16 to an i32 is always safe in Rust because the range of u16 (0 to 65,535)
46                    // fits entirely within the range of i32 (−2,147,483,648 to 2,147,483,647).
47                    //So no need to do: speed_score_modifier.try_into().expect("too much")/match for conversion
48                    let fruit_impact = score_fruits * i32::from(game_speed.score_modifier());
49                    let mut state_guard_gs = gs.write().unwrap();
50                    // to always keep the score positive and between bounds
51                    if fruit_impact < 0 {
52                        state_guard_gs.score = state_guard_gs
53                            .score
54                            .saturating_sub(fruit_impact.unsigned_abs());
55                    } else {
56                        state_guard_gs.score = state_guard_gs
57                            .score
58                            .saturating_add(fruit_impact.unsigned_abs());
59                    }
60                    fruits_manager.write().unwrap().replace_fruits(&fruits);
61                }
62                // Check if the gamer will lose one life
63                if write_guard.is_snake_eating_itself() {
64                    //Ouch. You bite yourself
65                    let mut state_guard = gs.write().unwrap();
66                    if (state_guard.life) >= 1 {
67                        state_guard.life -= 1;
68                    }
69                    if state_guard.life == 0 {
70                        //The GameOverMenu option will be used to store the user selection of what to do
71                        state_guard.status = GameStatus::GameOver(GameOverMenu::default());
72
73                        // Save High Score
74                        if let Ok(manager) = HighScoreManager::new() {
75                            let score_value: u32 = state_guard.score;
76                            let _ = manager.save_score(&HighScore::new(
77                                snake_symbols.clone(),
78                                score_value,
79                                //using Display implementation
80                                game_speed.to_string(),
81                            ));
82                            state_guard.rank = manager.get_rank(score_value).unwrap_or(None);
83                        }
84                    }
85                }
86            }
87            GameStatus::Restarting => {
88                //let some time for the restarting screen to appear
89                sleep(Duration::from_millis(1000));
90                gs.write().unwrap().reset();
91                snake.write().unwrap().reset();
92                *direction.write().unwrap() = Direction::Right;
93                fruits_manager.write().unwrap().reset();
94                //graphical resize on rendering part (not really a game_logic constant)
95            }
96            GameStatus::ByeBye | GameStatus::Menu => break,
97            GameStatus::Paused | GameStatus::GameOver(_) => {}
98        }
99        sleep(Duration::from_millis(game_speed.ms_value()));
100    }
101}