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            MenuFlow::StartGame => return true,
105            MenuFlow::QuitGame => return false,
106        }
107    }
108}
109
110fn run_submenu_and_reset<F>(terminal: &mut DefaultTerminal, selected: &mut usize, f: F)
111where
112    F: FnOnce(&mut DefaultTerminal),
113{
114    f(terminal);
115    *selected = 3;
116}
117
118#[derive(PartialEq, Clone, Debug)]
119pub enum MainMenuInput {
120    Fruits,
121    Speed,
122    Start,
123    Parameters,
124    Doc,
125    Highs,
126    Main,
127    QuitGame,
128    Next,
129    Previous,
130    Enter,
131}
132
133/// Check input on the greeting screen
134/// Return Some(GreetingOption) if input is valid, with the chosen Greeting Option, None otherwise
135/// # Panics                                                                                              
136/// if impossible to get key event, better crash as game will be unplayable  
137#[must_use]
138pub fn main_menu_event() -> MainMenuInput {
139    // Read keyboard key event
140    if let event::Event::Key(key) = event::read().expect("Error reading key event") {
141        match key.kind {
142            //If a key is pressed
143            KeyEventKind::Press => {
144                flush_input_buffer();
145                // If it is a directional key
146                if START_KEYS.contains(&key.code) {
147                    MainMenuInput::Start
148                    // if it is a quit key
149                } else if QUIT_KEYS.contains(&key.code) {
150                    MainMenuInput::QuitGame
151                } else if PARAMETERS_KEYS.contains(&key.code) {
152                    MainMenuInput::Parameters
153                } else if FRUITS_KEYS.contains(&key.code) {
154                    MainMenuInput::Fruits
155                } else if VELOCITY_KEYS.contains(&key.code) {
156                    MainMenuInput::Speed
157                } else if HELP_KEYS.contains(&key.code) {
158                    MainMenuInput::Doc
159                } else if HIGH_SCORE_KEYS.contains(&key.code) {
160                    MainMenuInput::Highs
161                } else if NEXT_KEYS.contains(&key.code) {
162                    MainMenuInput::Next
163                } else if PREVIOUS_KEYS.contains(&key.code) {
164                    MainMenuInput::Previous
165                } else if ENTER_KEYS.contains(&key.code) {
166                    MainMenuInput::Enter
167                } else {
168                    MainMenuInput::Main
169                }
170            }
171            _ => MainMenuInput::Main,
172        }
173    } else {
174        MainMenuInput::Main
175    }
176}
177fn flush_input_buffer() {
178    while event::poll(std::time::Duration::from_secs(0)).unwrap_or(false) {
179        let _ = crossterm::event::read(); // Discard any buffered events
180    }
181}