1use crate::controls::input::GreetingOption;
2use crate::controls::speed;
3use crate::controls::speed::SpeedConfig;
4use crate::graphics::menus::layout_utils::frame_vertically_centered_rect;
5use crate::graphics::sprites::fruit::FRUITS_SCORES_PROBABILITIES;
6use clap::ValueEnum;
7use ratatui::style::Stylize;
8use ratatui::text::{Line, Span, Text};
9use ratatui::widgets::{Block, BorderType, Paragraph};
10use ratatui::{DefaultTerminal, Frame};
11use std::thread::sleep;
12use std::time::Duration;
13
14pub fn main_greeting_menu(
20 terminal: &mut DefaultTerminal,
21 opt: &Option<GreetingOption>,
22 selected: &GreetingOption,
23) {
24 terminal
26 .draw(|frame| {
27 let area = frame.area();
28 match opt {
29 None | Some(GreetingOption::MainMenu) => {
30 let lines = vec![
31 Line::from("███████╗ ███╗ ██╗ █████╗ ██╗ ██╗ ███████╗"),
32 Line::from("██╔════╝ ████╗ ██║ ██╔══██╗ ██║ ██╔╝ ██╔════╝"),
33 Line::from("███████╗ ██╔██╗ ██║ ███████║ █████╔╝ █████╗ "),
34 Line::from("╚════██║ ██║╚██╗██║ ██╔══██║ ██╔═██╗ ██╔══╝ "),
35 Line::from("███████║ ██║ ╚████║ ██║ ██║ ██║ ██╗ ███████╗"),
36 Line::from("╚══════╝ ╚══════╝╚═ ╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝"),
37 Line::from(get_button_span(selected)),
38 Line::from("+--------+------+------+-------+-----+-------+"),
39 Line::from("|Controls| ←↕→ | Q | P / ⎵ | M | R | "),
40 Line::from("+--------+------+------+-------+-----+-------+"),
41 Line::from("|Effects | Move | Quit | Pause | Menu| Start |"),
42 Line::from("+--------+------+------+-------+-----+-------+"),
43 Line::from("Welcome to the craziest Snake ever! 🐍".green()),
44 Line::from("Have a good game ! 🎮".green()),
45 ];
46 let nb_lines = lines.len();
47
48 frame.render_widget(
49 Paragraph::new(Text::from(lines)).centered(),
51 frame_vertically_centered_rect(area, nb_lines),
52 );
53 }
54 Some(GreetingOption::Fruits) => {
55 fruit_menu(frame, selected);
56 }
57 Some(GreetingOption::Velocity) => {
58 speed_menu(frame, selected);
59 }
60 Some(GreetingOption::StartGame | GreetingOption::QuitGame) => {
61 }
63 Some(GreetingOption::Parameters) => {
64 parameters_menu(frame, selected);
65 }
66 Some(GreetingOption::Help) => {
67 help_menu(frame, selected);
68 }
69 Some(GreetingOption::Enter | GreetingOption::Next | GreetingOption::Previous) => {
70 let lines = vec![
71 Line::from(
72 "You find an easter egg here! Not yet playable, please report\
73 Guess some edge cases are possible :p ",
74 ),
75 Line::from(get_button_span(selected)),
76 ];
77 let nb_lines = lines.len();
78 frame.render_widget(
79 Paragraph::new(Text::from(lines)).centered(),
80 frame_vertically_centered_rect(area, nb_lines),
81 );
82 }
83 }
84 frame.render_widget(Block::bordered().border_type(BorderType::Double), area);
87 })
88 .expect("Unusable terminal render");
89 sleep(Duration::from_millis(100));
90}
91
92fn fruit_menu(frame: &mut Frame, selected: &GreetingOption) {
94 let mut fruits_lines = Vec::new();
96 let tab_jonction = Line::from(" +---------+-------+-------------+-------------+");
97 fruits_lines.push(tab_jonction.clone());
98 fruits_lines.push(Line::from(
99 "| Fruit | Score | Probability | Size Effect |",
100 ));
101 fruits_lines.push(tab_jonction.clone());
102 for (fruit, score, probability, size_effect) in FRUITS_SCORES_PROBABILITIES {
103 fruits_lines.push(Line::from(format!(
105 " | {fruit:<6} | {score:>5} | {probability:>11} | {size_effect:>11} |"
106 )));
107 }
108 fruits_lines.push(tab_jonction);
109 fruits_lines.push(Line::from(get_button_span(selected)));
110 let nb_lines = fruits_lines.len();
111 frame.render_widget(
115 Paragraph::new(Text::from(fruits_lines)).centered(),
116 frame_vertically_centered_rect(frame.area(), nb_lines),
117 );
118}
119fn speed_menu(frame: &mut Frame, selected: &GreetingOption) {
121 let mut speed_lines = Vec::new();
123 let speed_tab_jonction = Line::from("+------------+-------+----------------+---------+ ");
124 speed_lines.push(speed_tab_jonction.clone());
125 speed_lines.push(Line::from(
126 "| Speed Name | Value | Score Modifier | Symbol | ",
127 ));
128 speed_lines.push(speed_tab_jonction.clone());
129 for s in speed::Speed::value_variants() {
130 let SpeedConfig {
131 name,
132 ms_value,
133 score_modifier,
134 symbol,
135 } = s.config();
136 speed_lines.push(Line::from(format!(
138 "| {name:<10} | {ms_value:>5} | {score_modifier:>14} | {symbol:<6} | "
139 )));
140 }
141 speed_lines.push(speed_tab_jonction);
142 speed_lines.push(Line::from(get_button_span(selected)));
143 let nb_lines = speed_lines.len();
144 frame.render_widget(
145 Paragraph::new(Text::from(speed_lines)).centered(),
146 frame_vertically_centered_rect(frame.area(), nb_lines),
147 );
148}
149fn parameters_menu(frame: &mut Frame, selected: &GreetingOption) {
151 let lines = vec![
152 Line::from("Parameters are not yet implemented 😜"),
153 Line::from("For now use the command line parameter or load a parameters file"),
154 Line::from("Upvote this issue for faster release or bring me a coffee on kofi🥤 "),
155 Line::from(get_button_span(selected)),
156 ];
157 let nb_lines = lines.len();
158 frame.render_widget(
159 Paragraph::new(Text::from(lines)).centered(),
160 frame_vertically_centered_rect(frame.area(), nb_lines),
161 );
162}
163fn help_menu(frame: &mut Frame, selected: &GreetingOption) {
164 let lines = vec![
168 Line::from("Snake Game Rules:".bold().yellow()),
169 Line::from(""),
170 Line::from(format!(
171 "• {:<80}",
172 "Control the snake using the arrow keys (←↕→)"
173 )),
174 Line::from(format!(
175 "• {:<80}",
176 "Eat fruits to grow longer and score points"
177 )),
178 Line::from(format!(
179 "• {:<80}",
180 "Different fruits give various scores / effects, some even reduce size (and score)"
181 )),
182 Line::from(format!(
183 "• {:<80}",
184 "Avoid hitting yourself or your own tail"
185 )),
186 Line::from(format!(
187 "• {:<80}",
188 "Walls are circulars so your head will appear on the other side of the screen"
189 )),
190 Line::from(format!(
191 "• {:<80}",
192 "Game speeds can be changed to increase difficulty and score multipliers"
193 )),
194 Line::from(format!("• {:<80}", "Press P or Space to pause the game")),
195 Line::from(format!("• {:<80}", "Press Q to quit anytime")),
196 Line::from(format!("• {:<80}", "Press R to start a new game")),
197 Line::from(format!("• {:<80}", "Press M to return to the menu")),
198 Line::from(""),
199 Line::from(format!(
200 "{:<80}",
201 "Check the Fruit and Speed menus for more details on game mechanics!"
202 )),
203 Line::from(""),
204 Line::from(format!(
205 "{:<80}",
206 "If you enjoy this game consider bringing me a coffee on kofi🥤 "
207 )),
208 Line::from(get_button_span(selected)),
209 ];
210
211 let nb_lines = lines.len();
212 frame.render_widget(
213 Paragraph::new(Text::from(lines)).centered(),
214 frame_vertically_centered_rect(frame.area(), nb_lines),
215 );
216}
217
218fn get_button_span(selected: &GreetingOption) -> Vec<Span> {
219 let selected_main: Vec<Span> = vec![" [ ".red(), "Main".into(), " ] ".red().bold()];
220 let default_main: Vec<Span> = vec![" [".into(), "M".yellow(), "ain]".into()];
221 let selected_fruit: Vec<Span> = vec![" [ ".red(), "Fruit".into(), " ] ".red().bold()];
222 let default_fruit: Vec<Span> = vec![" [".into(), "F".yellow(), "ruit]".into()];
223 let selected_velocity: Vec<Span> = vec![" [ ".red(), "Speed".into(), " ] ".red().bold()];
224 let default_velocity: Vec<Span> = vec![" [".into(), "S".yellow(), "peed]".into()];
225 let selected_start: Vec<Span> = vec![" [ ".red(), "Run".into(), " ] ".red().bold()];
226 let default_start: Vec<Span> = vec![" [".into(), "R".yellow(), "un]".into()];
227 let selected_setup: Vec<Span> = vec![" [ ".red(), "Edit⚙️".into(), " ] ".red().bold()];
228 let default_setup: Vec<Span> = vec![" [".into(), "E".yellow(), "dit⚙️]".into()];
229 let selected_help: Vec<Span> = vec![" [ ".red(), "Help".into(), " ] ".red().bold()];
230 let default_help: Vec<Span> = vec![" [".into(), "H".yellow(), "elp]".into()];
231 let mut vec_line_button = vec!["↔".into()];
232 if selected == &GreetingOption::MainMenu {
233 vec_line_button.extend(selected_main);
234 } else {
235 vec_line_button.extend(default_main);
236 }
237 if selected == &GreetingOption::Fruits {
238 vec_line_button.extend(selected_fruit);
239 } else {
240 vec_line_button.extend(default_fruit);
241 }
242 if selected == &GreetingOption::Velocity {
243 vec_line_button.extend(selected_velocity);
244 } else {
245 vec_line_button.extend(default_velocity);
246 }
247 if selected == &GreetingOption::StartGame {
248 vec_line_button.extend(selected_start);
249 } else {
250 vec_line_button.extend(default_start);
251 }
252 if selected == &GreetingOption::Parameters {
253 vec_line_button.extend(selected_setup);
254 } else {
255 vec_line_button.extend(default_setup);
256 }
257 if selected == &GreetingOption::Help {
258 vec_line_button.extend(selected_help);
259 } else {
260 vec_line_button.extend(default_help);
261 }
262 vec_line_button
263}