rsnaker/controls/
input.rs

1use crate::controls::direction::Direction;
2use crate::game_logic::state::{GameState, GameStatus};
3use crossterm::event;
4use crossterm::event::{KeyCode, KeyEventKind};
5use std::sync::{Arc, RwLock};
6
7const QUIT_KEYS: [KeyCode; 2] = [KeyCode::Char('q'), KeyCode::Char('Q')];
8const PARAMETERS_KEYS: [KeyCode; 2] = [KeyCode::Char('e'), KeyCode::Char('E')];
9const FRUITS_KEYS: [KeyCode; 2] = [KeyCode::Char('f'), KeyCode::Char('F')];
10const VELOCITY_KEYS: [KeyCode; 2] = [KeyCode::Char('s'), KeyCode::Char('P')];
11const HELP_KEYS: [KeyCode; 2] = [KeyCode::Char('h'), KeyCode::Char('H')];
12const MAIN_MENU_KEYS: [KeyCode; 2] = [KeyCode::Char('m'), KeyCode::Char('M')];
13//const DIRECTIONAL_KEYS: [KeyCode; 4] = [KeyCode::Down, KeyCode::Up, KeyCode::Left, KeyCode::Right];
14const START_KEYS: [KeyCode; 2] = [KeyCode::Char('r'), KeyCode::Char('R')];
15pub(crate) const PAUSE_KEYS: [KeyCode; 4] = [
16    KeyCode::Char('p'),
17    KeyCode::Char('P'),
18    KeyCode::Char(' '),
19    KeyCode::Home,
20];
21const RESET_KEYS: [KeyCode; 2] = [KeyCode::Char('r'), KeyCode::Char('R')];
22const NEXT_KEYS: [KeyCode; 2] = [KeyCode::Right, KeyCode::Up];
23const PREVIOUS_KEYS: [KeyCode; 3] = [KeyCode::Left, KeyCode::Backspace, KeyCode::Down];
24const ENTER_KEYS: [KeyCode; 2] = [KeyCode::Enter, KeyCode::End];
25
26/// You cannot block middle-click paste/scroll behavior from inside your Rust TUI app.
27/// If you really want to disable it, you would have to modify user system settings or terminal emulator config
28/// (e.g., in alacritty, kitty, gnome-terminal, etc.)
29/// That is outside the app's control
30/// # Panics
31/// if Arc panic while holding the resources (poisoning), no recovery mechanism implemented better crash
32pub fn playing_input_loop(direction: &Arc<RwLock<Direction>>, gs: &Arc<RwLock<GameState>>) {
33    loop {
34        if let Ok(event::Event::Key(key)) = event::read() {
35            if key.kind == KeyEventKind::Press {
36                //PAUSE
37                if PAUSE_KEYS.contains(&key.code) {
38                    let mut gs_guard = gs.write().unwrap();
39                    //in others state does nothing to not break game_logic logic
40                    if gs_guard.status == GameStatus::Playing {
41                        gs_guard.status = GameStatus::Paused;
42                    } else if gs_guard.status == GameStatus::Paused {
43                        gs_guard.status = GameStatus::Playing;
44                    }
45                //QUIT
46                } else if QUIT_KEYS.contains(&key.code) {
47                    gs.write().unwrap().status = GameStatus::ByeBye;
48                    break;
49                //MENU
50                } else if MAIN_MENU_KEYS.contains(&key.code) {
51                    gs.write().unwrap().status = GameStatus::Menu;
52                    break;
53                //RESTART
54                } else if RESET_KEYS.contains(&key.code) {
55                    gs.write().unwrap().status = GameStatus::Restarting;
56                //Arrow input
57                } else {
58                    let direction_input = match key.code {
59                        KeyCode::Left => Some(Direction::Left),
60                        KeyCode::Right => Some(Direction::Right),
61                        KeyCode::Down => Some(Direction::Down),
62                        KeyCode::Up => Some(Direction::Up),
63                        _ => None,
64                    };
65                    if let Some(dir) = direction_input {
66                        *direction.write().unwrap() = dir;
67                    }
68                }
69            }
70        }
71    }
72}
73
74#[derive(PartialEq, Clone, Debug)]
75pub enum GreetingOption {
76    StartGame,
77    Parameters,
78    Fruits,
79    Velocity,
80    Help,
81    MainMenu,
82    QuitGame,
83    Next,
84    Previous,
85    Enter,
86}
87/// Check input on greeting screen
88/// Return Some(GreetingOption) if input is valid, with the chosen Greeting Option, None otherwise
89/// # Panics                                                                                              
90/// if impossible to get key event, better crash as game will be unplayable  
91#[must_use]
92pub fn greeting_screen_manage_input() -> Option<GreetingOption> {
93    // Read keyboard key event
94    if let event::Event::Key(key) = event::read().expect("Error reading key event") {
95        match key.kind {
96            //If a key is pressed
97            KeyEventKind::Press => {
98                flush_input_buffer();
99                // If it is a directional key
100                if START_KEYS.contains(&key.code) {
101                    Some(GreetingOption::StartGame)
102                    // if it is a quit keys
103                } else if QUIT_KEYS.contains(&key.code) {
104                    Some(GreetingOption::QuitGame)
105                } else if PARAMETERS_KEYS.contains(&key.code) {
106                    Some(GreetingOption::Parameters)
107                } else if FRUITS_KEYS.contains(&key.code) {
108                    Some(GreetingOption::Fruits)
109                } else if VELOCITY_KEYS.contains(&key.code) {
110                    Some(GreetingOption::Velocity)
111                } else if HELP_KEYS.contains(&key.code) {
112                    Some(GreetingOption::Help)
113                } else if MAIN_MENU_KEYS.contains(&key.code) {
114                    Some(GreetingOption::MainMenu)
115                } else if NEXT_KEYS.contains(&key.code) {
116                    Some(GreetingOption::Next)
117                } else if PREVIOUS_KEYS.contains(&key.code) {
118                    Some(GreetingOption::Previous)
119                } else if ENTER_KEYS.contains(&key.code) {
120                    Some(GreetingOption::Enter)
121                } else {
122                    None
123                }
124            }
125            _ => None,
126        }
127    } else {
128        None
129    }
130}
131fn flush_input_buffer() {
132    while crossterm::event::poll(std::time::Duration::from_secs(0)).unwrap_or(false) {
133        let _ = crossterm::event::read(); // Discard any buffered events
134    }
135}