rsnaker/controls/
main_menu.rs

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