Skip to main content

rsnaker/controls/
playing_input.rs

1use crate::controls::direction::Direction;
2use crate::controls::main_menu::{ENTER_KEYS, NEXT_KEYS, PREVIOUS_KEYS};
3use crate::game_logic::state::{GameOverMenu, GameState, GameStatus};
4use crossterm::event;
5use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
6use std::sync::{Arc, RwLock};
7use tracing::{error, info, warn};
8
9pub const QUIT_KEYS: [KeyCode; 3] = [KeyCode::Char('q'), KeyCode::Char('Q'), KeyCode::Tab];
10pub const START_KEYS: [KeyCode; 2] = [KeyCode::Char('r'), KeyCode::Char('R')];
11pub const MAIN_MENU_KEYS: [KeyCode; 3] = [KeyCode::Char('m'), KeyCode::Char('M'), KeyCode::Home];
12//const DIRECTIONAL_KEYS: [KeyCode; 4] = [KeyCode::Down, KeyCode::Up, KeyCode::Left, KeyCode::Right];
13pub const PAUSE_KEYS: [KeyCode; 4] = [
14    KeyCode::Char('p'),
15    KeyCode::Char('P'),
16    KeyCode::Char(' '),
17    KeyCode::Char('-'),
18];
19pub const RESET_KEYS: [KeyCode; 2] = [KeyCode::Char('r'), KeyCode::Char('R')];
20/// Returns whether a key event has no active modifier keys.
21///
22/// Game controls intentionally only react to bare key presses so terminal and
23/// operating-system shortcuts such as Alt+Tab or Ctrl+C are never interpreted
24/// as game actions.
25#[must_use]
26pub fn has_no_modifiers(key: &KeyEvent) -> bool {
27    key.modifiers == KeyModifiers::NONE
28}
29
30/// You cannot block middle-click paste/scroll behavior from inside your Rust TUI app.
31/// If you really want to disable it, you would have to modify user system settings or terminal emulator config
32/// (e.g., in alacrity, kitty, gnome-terminal, etc.)
33/// That is outside the app's control
34/// # Panics
35/// if Arc panics while holding the resources (poisoning), no recovery mechanism implemented better crash
36pub fn playing_input_loop(direction: &Arc<RwLock<Direction>>, gs: &Arc<RwLock<GameState>>) {
37    loop {
38        if let Ok(event::Event::Key(key)) = event::read()
39            && key.kind == KeyEventKind::Press
40            && has_no_modifiers(&key)
41        {
42            let mut gs_guard = gs.write().unwrap();
43            // Handle GameOver navigation separately
44            if let GameStatus::GameOver(selection) = gs_guard.status {
45                if NEXT_KEYS.contains(&key.code) {
46                    gs_guard.status = GameStatus::GameOver(selection.next());
47                } else if PREVIOUS_KEYS.contains(&key.code) {
48                    gs_guard.status = GameStatus::GameOver(selection.previous());
49                } else if ENTER_KEYS.contains(&key.code) {
50                    match selection {
51                        GameOverMenu::Restart => {
52                            info!("Restarting the game ! ");
53                            gs_guard.status = GameStatus::Restarting;
54                        }
55                        GameOverMenu::Menu => {
56                            info!("Coming back to menu ! ");
57                            gs_guard.status = GameStatus::Menu;
58                            break;
59                        }
60                        GameOverMenu::Quit => {
61                            error!("You choose to quit the game 😶‍🌫️  ");
62                            gs_guard.status = GameStatus::ByeBye;
63                            break;
64                        }
65                    }
66                }
67            }
68            //We keep an if and not an else if, to keep keyboard shortcut working on game over screen
69            //PAUSE
70            if PAUSE_KEYS.contains(&key.code) {
71                //in others state does nothing to not break game_logic logic
72                if gs_guard.status == GameStatus::Playing {
73                    info!("Game paused ! ⏸️ ");
74                    gs_guard.status = GameStatus::Paused;
75                } else if gs_guard.status == GameStatus::Paused {
76                    info!("Game restart after pausing ! ⏯️ ");
77                    gs_guard.status = GameStatus::Playing;
78                }
79            //QUIT
80            } else if QUIT_KEYS.contains(&key.code) {
81                error!("You choose to quit the game 😶‍🌫️ ");
82                gs_guard.status = GameStatus::ByeBye;
83                break;
84            //MENU
85            } else if MAIN_MENU_KEYS.contains(&key.code) {
86                warn!("Going back to menu ! 🗺️");
87                gs_guard.status = GameStatus::Menu;
88                break;
89            //RESTART
90            } else if RESET_KEYS.contains(&key.code) {
91                warn!("Game hot restart ! 🤖");
92                gs_guard.status = GameStatus::Restarting;
93            //Arrow input
94            } else {
95                let direction_input = match key.code {
96                    KeyCode::Left => Some(Direction::Left),
97                    KeyCode::Right => Some(Direction::Right),
98                    KeyCode::Down => Some(Direction::Down),
99                    KeyCode::Up => Some(Direction::Up),
100                    _ => None,
101                };
102                if let Some(dir) = direction_input {
103                    *direction.write().unwrap() = dir;
104                }
105            }
106        }
107    }
108}