Skip to main content

rsnaker/controls/
main_menu.rs

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