1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use crate::controls::direction::Direction;
use crate::controls::speed::Speed;
use crate::game_logic::fruits_manager::FruitsManager;
use crate::game_logic::high_score::{HighScore, HighScoreManager};
use crate::game_logic::state::{GameOverMenu, GameState, GameStatus};
use crate::graphics::sprites::fruit::Fruit;
use crate::graphics::sprites::map::Map;
use crate::graphics::sprites::snake_body::SnakeBody;
use std::sync::{Arc, RwLock};
use std::thread::sleep;
use std::time::{Duration, Instant};
use tracing::{debug, error, info, trace, trace_span, warn};
/// # Panics
/// if Arc panics while holding the resources (poisoning),
/// no recovery mechanism implemented better crash
pub fn playing_logic_loop(
direction: &Arc<RwLock<Direction>>,
snake: &Arc<RwLock<SnakeBody>>,
gs: &Arc<RwLock<GameState>>,
carte: &Arc<RwLock<Map>>,
fruits_manager: &Arc<RwLock<FruitsManager>>,
(game_speed, snake_symbols, negative_size_fruits, snake_growth_factor): (
Speed,
String,
bool,
u16,
),
) {
let mut gsc;
debug!(fruits = ?fruits_manager.read().unwrap().get_fruits(), "Initial fruits on map");
loop {
let _span = trace_span!("logic_iteration").entered();
let loop_start = Instant::now();
//do not want to keep the lock for too long + cannot hold in the same thread 2 times the same hold
// so match a clone or use a let
gsc = gs.read().unwrap().status.clone();
//dead snakes tell no tales, nor move :p
match gsc {
GameStatus::Playing => {
let mut write_guard = snake.write().unwrap();
//Move the snake!
let position = write_guard.ramp(&direction.read().unwrap(), &carte.read().unwrap());
debug!(?position, "Snake moved");
// Check if we eat a fruit
let fruits = fruits_manager.read().unwrap().eat_some_fruits(position);
// fruits effects
if let Some(fruits) = fruits {
debug!(count = fruits.len(), "Fruits eaten by snake");
let score_fruits = fruits.iter().map(Fruit::get_score).sum::<i32>();
let base_size_effect = fruits
.iter()
.map(Fruit::get_grow_snake)
.map(i32::from)
.sum::<i32>();
let size_effect = i16::try_from(
base_size_effect
.saturating_mul(i32::from(snake_growth_factor))
.clamp(i32::from(i16::MIN), i32::from(i16::MAX)),
)
.unwrap();
info!(?fruits, "Fruit characteristics");
// In classic-like mode, negative fruits are disabled.
if negative_size_fruits || size_effect > 0i16 {
write_guard.relative_size_change(size_effect);
}
//NB:Converting an u16 to an i32 is always safe in Rust because the range of u16 (0 to 65,535)
// fits entirely within the range of i32 (−2,147,483,648 to 2,147,483,647).
//So no need to do: speed_score_modifier.try_into().expect("too much")/match for conversion
let fruit_impact = score_fruits * i32::from(game_speed.score_modifier());
info!(
fruit_impact = fruit_impact,
"Fruit impact on score because of speed score modifier"
);
let mut state_guard_gs = gs.write().unwrap();
// to always keep the score positive and between bounds
if fruit_impact < 0 {
state_guard_gs.score = state_guard_gs
.score
.saturating_sub(fruit_impact.unsigned_abs());
} else {
state_guard_gs.score = state_guard_gs
.score
.saturating_add(fruit_impact.unsigned_abs());
}
info!(
score = state_guard_gs.score,
snake_size = write_guard.body.len(),
"Current game metrics"
);
fruits_manager.write().unwrap().replace_fruits(&fruits);
debug!(fruits = ?fruits_manager.read().unwrap().get_fruits(), "Current fruits on map");
}
// Check if the gamer will lose one life
if write_guard.is_snake_eating_itself() {
//Ouch. You bite yourself
let mut state_guard = gs.write().unwrap();
if (state_guard.life) >= 1 {
state_guard.life -= 1;
warn!(
remaining_life = state_guard.life,
"Ouch! Did that taste good? You bit yourself!",
);
}
if state_guard.life == 0 {
error!(
score = state_guard.score,
"FATAL: Snake succumbed to its injuries. Game Over!"
);
//The GameOverMenu option will be used to store the user selection of what to do
state_guard.status = GameStatus::GameOver(GameOverMenu::default());
// Save High Score
if let Ok(manager) = HighScoreManager::new() {
let score_value: u32 = state_guard.score;
let rank = manager.save_score(&HighScore::new(
snake_symbols.clone(),
score_value,
//using Display implementation
game_speed.to_string(),
snake_growth_factor,
));
state_guard.rank = rank.unwrap_or(None);
}
}
}
}
GameStatus::Restarting => {
//let some time for the restarting screen to appear
sleep(Duration::from_secs(1));
gs.write().unwrap().reset();
snake.write().unwrap().reset();
*direction.write().unwrap() = Direction::Right;
fruits_manager.write().unwrap().reset();
//graphical resize on rendering part (not really a game_logic constant)
}
GameStatus::ByeBye | GameStatus::Menu => break,
GameStatus::Paused | GameStatus::GameOver(_) => {}
}
let elapsed = loop_start.elapsed();
trace!(?elapsed, "Logic loop iteration finished");
sleep(Duration::from_millis(game_speed.ms_value()));
}
}