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, |term| {
88                setup_and_run_fruits_table_parameters(term, options);
89            });
90        }
91        Highs => {
92            info!("You choose to see the wall of fame, good luck beating them ! πŸ’―");
93            run_submenu_and_reset(terminal, selected, setup_and_run_highs_table_parameters);
94        }
95        Speed => {
96            info!("You choose to see the speeding information, good luck flash guy ! 🐒");
97            run_submenu_and_reset(terminal, selected, setup_and_run_speed_table_parameters);
98        }
99        Run => {
100            return MenuFlow::StartGame;
101        }
102        Doc => {
103            info!("You choose to see the doc information, good luck with books ! πŸ“–");
104            run_submenu_and_reset(terminal, selected, setup_and_run_doc_table_parameters);
105        }
106        Main => {
107            display_main_menu(terminal, &SWITCH_MENUS_OPTION[*selected]);
108        }
109    }
110    MenuFlow::StayOnMainScreen
111}
112
113pub fn controls_main_switch_menu(
114    terminal: &mut DefaultTerminal,
115    options: &mut GameOptions,
116) -> bool {
117    let mut selected = 3;
118
119    loop {
120        display_main_menu(terminal, &SWITCH_MENUS_OPTION[selected]);
121
122        //If there is a user input, the screen could change, otherwise keep the same screen and go for the snake
123        if event::poll(Duration::from_millis(SPEED_MOVING_SNAKE_SLEEP_TIME_MS / 2))
124            .expect("Error polling event")
125        {
126            let input = main_menu_event();
127
128            let flow = enter_menu_screen(&input, &mut selected, terminal, options);
129
130            match flow {
131                MenuFlow::StayOnMainScreen => {
132                    //for re-displaying after quiting a submenu without waiting for another input
133                    //display_main_menu(terminal, &SWITCH_MENUS_OPTION[selected], &edge_snake);
134                }
135                MenuFlow::StartGame => return true,
136                MenuFlow::QuitGame => return false,
137            }
138        }
139    }
140}
141
142fn run_submenu_and_reset<F>(terminal: &mut DefaultTerminal, selected: &mut usize, f: F)
143where
144    F: FnOnce(&mut DefaultTerminal),
145{
146    f(terminal);
147    *selected = 3;
148}
149
150#[derive(PartialEq, Clone, Debug)]
151pub enum MainMenuInput {
152    Fruits,
153    Speed,
154    Start,
155    Parameters,
156    Doc,
157    Highs,
158    Main,
159    QuitGame,
160    Next,
161    Previous,
162    Enter,
163}
164
165/// Check input on the greeting screen
166/// Return Some(GreetingOption) if input is valid, with the chosen Greeting Option, None otherwise
167/// # Panics                                                                                              
168/// if impossible to get key event, better crash as game will be unplayable  
169#[must_use]
170pub fn main_menu_event() -> MainMenuInput {
171    // Read keyboard key event
172    if let event::Event::Key(key) = event::read().expect("Error reading key event") {
173        match key.kind {
174            //If a key is pressed
175            KeyEventKind::Press => {
176                flush_input_buffer();
177                // If it is a directional key
178                if START_KEYS.contains(&key.code) {
179                    MainMenuInput::Start
180                    // if it is a quit key
181                } else if QUIT_KEYS.contains(&key.code) {
182                    MainMenuInput::QuitGame
183                } else if PARAMETERS_KEYS.contains(&key.code) {
184                    MainMenuInput::Parameters
185                } else if FRUITS_KEYS.contains(&key.code) {
186                    MainMenuInput::Fruits
187                } else if VELOCITY_KEYS.contains(&key.code) {
188                    MainMenuInput::Speed
189                } else if HELP_KEYS.contains(&key.code) {
190                    MainMenuInput::Doc
191                } else if HIGH_SCORE_KEYS.contains(&key.code) {
192                    MainMenuInput::Highs
193                } else if NEXT_KEYS.contains(&key.code) {
194                    MainMenuInput::Next
195                } else if PREVIOUS_KEYS.contains(&key.code) {
196                    MainMenuInput::Previous
197                } else if ENTER_KEYS.contains(&key.code) {
198                    MainMenuInput::Enter
199                } else {
200                    MainMenuInput::Main
201                }
202            }
203            _ => MainMenuInput::Main,
204        }
205    } else {
206        MainMenuInput::Main
207    }
208}
209fn flush_input_buffer() {
210    while event::poll(Duration::from_secs(0)).unwrap_or(false) {
211        let _ = crossterm::event::read(); // Discard any buffered events
212    }
213}