Skip to main content

rsnaker/controls/
main_menu.rs

1use crate::controls::playing_input::{QUIT_KEYS, START_KEYS};
2use crate::game_logic::game_options::GameOptions;
3use crate::game_logic::logger::log_configuration::update_log_level;
4use crate::graphics::menus::edge_snake::SPEED_MOVING_SNAKE_SLEEP_TIME_MS;
5use crate::graphics::menus::main_menu::SwitchMenu::{
6    Doc, Fruits, Highs, Main, Parameters, Run, Speed,
7};
8use crate::graphics::menus::main_menu::{display_main_menu, SwitchMenu};
9use crate::graphics::menus::retro_parameter_table::customized_with_doc::setup_and_run_doc_table_parameters;
10use crate::graphics::menus::retro_parameter_table::customized_with_edit::setup_and_run_cli_table_parameters;
11use crate::graphics::menus::retro_parameter_table::customized_with_fruits::setup_and_run_fruits_table_parameters;
12use crate::graphics::menus::retro_parameter_table::customized_with_highscore::setup_and_run_highs_table_parameters;
13use crate::graphics::menus::retro_parameter_table::customized_with_speed::setup_and_run_speed_table_parameters;
14use crossterm::event;
15use crossterm::event::{KeyCode, KeyEventKind};
16use ratatui::DefaultTerminal;
17use std::time::Duration;
18use tracing::{debug, error, info};
19
20const SWITCH_MENUS_OPTION: [SwitchMenu; 6] = [Highs, Fruits, Speed, Run, Parameters, Doc];
21const PARAMETERS_KEYS: [KeyCode; 2] = [KeyCode::Char('e'), KeyCode::Char('E')];
22const FRUITS_KEYS: [KeyCode; 2] = [KeyCode::Char('f'), KeyCode::Char('F')];
23const VELOCITY_KEYS: [KeyCode; 2] = [KeyCode::Char('s'), KeyCode::Char('P')];
24const HELP_KEYS: [KeyCode; 2] = [KeyCode::Char('d'), KeyCode::Char('D')];
25const HIGH_SCORE_KEYS: [KeyCode; 2] = [KeyCode::Char('h'), KeyCode::Char('H')];
26pub const NEXT_KEYS: [KeyCode; 2] = [KeyCode::Right, KeyCode::Up];
27pub const PREVIOUS_KEYS: [KeyCode; 3] = [KeyCode::Left, KeyCode::Backspace, KeyCode::Down];
28pub const ENTER_KEYS: [KeyCode; 2] = [KeyCode::Enter, KeyCode::End];
29/// The control part of the main menu
30/// allows switching to a submenu (Fruits, Speed, Parameters, etc.)
31/// Use `MainMenuInput` to known which keys have been used
32/// and `GreetingSimpleDisplay` to display an easy menu, without input control (all except run and parameters)
33/// Return true if the player wants to play, false otherwise
34///
35/// # Panics                                                                                              
36/// if Terminal writing is not possible
37enum MenuFlow {
38    StayOnMainScreen,
39    StartGame,
40    QuitGame,
41}
42
43fn enter_menu_screen(
44    input: &MainMenuInput,
45    selected: &mut usize,
46    terminal: &mut DefaultTerminal,
47    options: &mut GameOptions,
48) -> MenuFlow {
49    //get the screen menu to display or do the action associated with the input
50    let to_display = match input {
51        MainMenuInput::Enter => SWITCH_MENUS_OPTION[*selected].clone(),
52        MainMenuInput::Parameters => Parameters,
53        MainMenuInput::Fruits => Fruits,
54        MainMenuInput::Speed => Speed,
55        MainMenuInput::Doc => Doc,
56        MainMenuInput::Highs => Highs,
57        MainMenuInput::Next => {
58            *selected = (*selected + 1) % SWITCH_MENUS_OPTION.len();
59            Main
60        }
61        MainMenuInput::Previous => {
62            *selected = (*selected + SWITCH_MENUS_OPTION.len() - 1) % SWITCH_MENUS_OPTION.len();
63            Main
64        }
65        MainMenuInput::QuitGame => {
66            error!("You choose to quit the game πŸ˜Άβ€πŸŒ«οΈ ");
67            return MenuFlow::QuitGame;
68        }
69        MainMenuInput::Start => {
70            return MenuFlow::StartGame;
71        }
72        MainMenuInput::Main => Main,
73    };
74
75    match to_display {
76        Parameters => {
77            info!("You choose to edit game parameter, good luck engineer ! βš™οΈ");
78            run_submenu_and_reset(terminal, selected, |term| {
79                setup_and_run_cli_table_parameters(term, options);
80            });
81            //turn on log as soon as the user validates them, in case we have logs in a menu also
82            update_log_level(options.log_level);
83            debug!(options = ?options, "Game options after editing");
84        }
85        Fruits => {
86            info!("You choose to see the fruit parameter, good luck they are tasty ! 🍌");
87            run_submenu_and_reset(terminal, selected, setup_and_run_fruits_table_parameters);
88        }
89        Highs => {
90            info!("You choose to see the wall of fame, good luck beating them ! πŸ’―");
91            run_submenu_and_reset(terminal, selected, setup_and_run_highs_table_parameters);
92        }
93        Speed => {
94            info!("You choose to see the speeding information, good luck flash guy ! 🐒");
95            run_submenu_and_reset(terminal, selected, setup_and_run_speed_table_parameters);
96        }
97        Run => {
98            return MenuFlow::StartGame;
99        }
100        Doc => {
101            info!("You choose to see the doc information, good luck with books ! πŸ“–");
102            run_submenu_and_reset(terminal, selected, setup_and_run_doc_table_parameters);
103        }
104        Main => {
105            display_main_menu(terminal, &SWITCH_MENUS_OPTION[*selected]);
106        }
107    }
108    MenuFlow::StayOnMainScreen
109}
110
111pub fn controls_main_switch_menu(
112    terminal: &mut DefaultTerminal,
113    options: &mut GameOptions,
114) -> bool {
115    let mut selected = 3;
116
117    loop {
118        display_main_menu(terminal, &SWITCH_MENUS_OPTION[selected]);
119
120        //If there is a user input, the screen could change, otherwise keep the same screen and go for the snake
121        if event::poll(Duration::from_millis(SPEED_MOVING_SNAKE_SLEEP_TIME_MS / 2))
122            .expect("Error polling event")
123        {
124            let input = main_menu_event();
125
126            let flow = enter_menu_screen(&input, &mut selected, terminal, options);
127
128            match flow {
129                MenuFlow::StayOnMainScreen => {
130                    //for re-displaying after quiting a submenu without waiting for another input
131                    //display_main_menu(terminal, &SWITCH_MENUS_OPTION[selected], &edge_snake);
132                }
133                MenuFlow::StartGame => return true,
134                MenuFlow::QuitGame => return false,
135            }
136        }
137    }
138}
139
140fn run_submenu_and_reset<F>(terminal: &mut DefaultTerminal, selected: &mut usize, f: F)
141where
142    F: FnOnce(&mut DefaultTerminal),
143{
144    f(terminal);
145    *selected = 3;
146}
147
148#[derive(PartialEq, Clone, Debug)]
149pub enum MainMenuInput {
150    Fruits,
151    Speed,
152    Start,
153    Parameters,
154    Doc,
155    Highs,
156    Main,
157    QuitGame,
158    Next,
159    Previous,
160    Enter,
161}
162
163/// Check input on the greeting screen
164/// Return Some(GreetingOption) if input is valid, with the chosen Greeting Option, None otherwise
165/// # Panics                                                                                              
166/// if impossible to get key event, better crash as game will be unplayable  
167#[must_use]
168pub fn main_menu_event() -> MainMenuInput {
169    // Read keyboard key event
170    if let event::Event::Key(key) = event::read().expect("Error reading key event") {
171        match key.kind {
172            //If a key is pressed
173            KeyEventKind::Press => {
174                flush_input_buffer();
175                // If it is a directional key
176                if START_KEYS.contains(&key.code) {
177                    MainMenuInput::Start
178                    // if it is a quit key
179                } else if QUIT_KEYS.contains(&key.code) {
180                    MainMenuInput::QuitGame
181                } else if PARAMETERS_KEYS.contains(&key.code) {
182                    MainMenuInput::Parameters
183                } else if FRUITS_KEYS.contains(&key.code) {
184                    MainMenuInput::Fruits
185                } else if VELOCITY_KEYS.contains(&key.code) {
186                    MainMenuInput::Speed
187                } else if HELP_KEYS.contains(&key.code) {
188                    MainMenuInput::Doc
189                } else if HIGH_SCORE_KEYS.contains(&key.code) {
190                    MainMenuInput::Highs
191                } else if NEXT_KEYS.contains(&key.code) {
192                    MainMenuInput::Next
193                } else if PREVIOUS_KEYS.contains(&key.code) {
194                    MainMenuInput::Previous
195                } else if ENTER_KEYS.contains(&key.code) {
196                    MainMenuInput::Enter
197                } else {
198                    MainMenuInput::Main
199                }
200            }
201            _ => MainMenuInput::Main,
202        }
203    } else {
204        MainMenuInput::Main
205    }
206}
207fn flush_input_buffer() {
208    while event::poll(Duration::from_secs(0)).unwrap_or(false) {
209        let _ = crossterm::event::read(); // Discard any buffered events
210    }
211}