use std::io::{self, Write};
use crossterm::{
cursor::MoveTo,
event::{
self, Event, KeyCode, KeyEvent,
KeyEventKind::{Press, Repeat},
KeyModifiers,
},
style::{Color, Print, PrintStyledContent, Stylize},
terminal::{Clear, ClearType},
QueueableCommand,
};
use crate::{
fmt_helpers::{fmt_duration, fmt_hertz, fmt_tetromino_counts},
game_mode_presets::GameModePreset,
tui_menus::{title_bar, Menu, MenuUpdate},
Application, ScoreEntry,
};
impl<T: Write> Application<T> {
pub fn run_menu_game_ended(&mut self, game_scoring: &ScoreEntry) -> io::Result<MenuUpdate> {
let ScoreEntry {
game_meta_data,
end_cause,
is_win,
time: time_elapsed,
lineclears,
points: points_scored,
pieces: pieces_locked,
fall_delay_reached,
lock_delay_reached,
} = game_scoring;
let selection = vec![
Menu::NewGame,
Menu::Settings,
Menu::ScoresAndReplays {
cursor_pos: 0,
camera_pos: 0,
},
Menu::Statistics,
Menu::Quit,
];
let color_tetromino_rainbow = "1643502"
.chars()
.map(|ch| {
self.settings
.palette()
.get(
&falling_tetromino_engine::Tetromino::VARIANTS
[ch.to_string().parse::<usize>().unwrap()]
.tile_id(),
)
.unwrap_or(&Color::Reset)
})
.copied()
.collect::<Vec<_>>();
let mut timing_offset = 0usize;
let mut coloring_width = 2;
let animation_delay =
std::time::Duration::from_secs_f64(self.settings.graphics().fps.get().recip());
if *is_win
&& game_meta_data.title == GameModePreset::TITLE_CLASSIC
&& !self.settings.game_mode_preferences.master_mode_unlocked
{
self.settings.game_mode_preferences.master_mode_unlocked = true;
} else if *is_win
&& game_meta_data.title == GameModePreset::TITLE_PUZZLE
&& !self
.settings
.game_mode_preferences
.experimental_mode_unlocked
{
self.settings
.game_mode_preferences
.experimental_mode_unlocked = true;
}
let mut selected = 0usize;
let mut refresh_fully = true;
loop {
let w_main = Self::W_MAIN.into();
let (x_main, y_main) = Self::viewport_offset();
let y_selection = Self::H_MAIN / 5;
let clear_type = if refresh_fully {
refresh_fully = false;
ClearType::All
} else {
ClearType::CurrentLine
};
self.term
.queue(MoveTo(x_main, y_main + y_selection))?
.queue(Clear(clear_type))?;
if *is_win {
let line = format!(
"{:^w_main$}",
format!("++ Game Completed ({}) ++", game_meta_data.title)
);
for (x_offset, c) in line.chars().enumerate() {
let added_offsets = timing_offset + x_offset;
let mut rainbow_offset = added_offsets / coloring_width;
if self.settings.graphics().fps.get() >= 42.0 {
coloring_width = 9;
rainbow_offset += 1;
let modulod_offsets = added_offsets % coloring_width;
if modulod_offsets == 0 {
rainbow_offset -= 1;
} else if modulod_offsets == coloring_width - 1 {
rainbow_offset += 1;
}
}
self.term
.queue(MoveTo(
x_main + u16::try_from(x_offset).unwrap(),
y_main + y_selection,
))?
.queue(PrintStyledContent(c.bold().with(
color_tetromino_rainbow[rainbow_offset % color_tetromino_rainbow.len()],
)))?;
}
} else {
self.term.queue(PrintStyledContent(
format!(
"{:^w_main$}",
format!("-- Game Over: {end_cause} ({}) --", game_meta_data.title)
)
.bold(),
))?;
}
self.term
.queue(MoveTo(x_main, y_main + y_selection + 2))?
.queue(Print(format!("{:^w_main$}", title_bar(&self.settings))))?;
timing_offset = timing_offset.saturating_add(1);
let mut stats = vec![
format!("Time elapsed: {}", fmt_duration(*time_elapsed)),
format!("Lines: {lineclears}"),
format!("Score: {points_scored}"),
format!(
"Pieces: {}",
fmt_tetromino_counts(pieces_locked, &self.settings.mini_tet_style().tets)
),
format!(
"Gravity reached: {}",
fmt_hertz(fall_delay_reached.as_hertz())
),
];
if let Some(lock_delay_reached) = lock_delay_reached {
stats.push(format!(
"Lock delay: {}ms",
lock_delay_reached.saturating_duration().as_millis()
));
}
for (i, s) in stats.iter().enumerate() {
self.term
.queue(MoveTo(
x_main,
y_main + y_selection + 3 + u16::try_from(i).unwrap(),
))?
.queue(Print(format!("{s:^w_main$}")))?;
}
self.term
.queue(MoveTo(
x_main,
y_main + y_selection + 3 + u16::try_from(stats.len()).unwrap(),
))?
.queue(Print(format!("{:^w_main$}", title_bar(&self.settings))))?;
let names = selection
.iter()
.map(|menu| menu.to_string())
.collect::<Vec<_>>();
for (i, name) in names.into_iter().enumerate() {
self.term
.queue(MoveTo(
x_main,
y_main + y_selection + 3 + u16::try_from(stats.len() + 2 + i).unwrap(),
))?
.queue(Print(format!(
"{:^w_main$}",
if i == selected {
format!(">> {name} <<")
} else {
name
}
)))?;
}
self.term.flush()?;
if !event::poll(animation_delay)? {
continue;
}
match event::read()? {
Event::Key(KeyEvent {
code: KeyCode::Char('c' | 'C'),
modifiers: KeyModifiers::CONTROL,
kind: Press | Repeat,
state: _,
}) => break Ok(MenuUpdate::Push(Menu::Quit)),
Event::Key(KeyEvent {
code: KeyCode::Esc | KeyCode::Char('q' | 'Q') | KeyCode::Backspace,
kind: Press,
..
}) => break Ok(MenuUpdate::Pop),
Event::Key(KeyEvent {
code: KeyCode::Enter | KeyCode::Char('e' | 'E'),
kind: Press,
..
}) => {
if !selection.is_empty() {
let menu = selection.into_iter().nth(selected).unwrap();
break Ok(MenuUpdate::Push(menu));
}
}
Event::Key(KeyEvent {
code: KeyCode::Up | KeyCode::Char('k' | 'K'),
kind: Press | Repeat,
..
}) => {
selected += selection.len() - 1;
}
Event::Key(KeyEvent {
code: KeyCode::Down | KeyCode::Char('j' | 'J'),
kind: Press | Repeat,
..
}) => {
selected += 1;
}
Event::Key(KeyEvent {
code: KeyCode::Char('l' | 'L'),
modifiers,
kind: Press | Repeat,
..
}) if { modifiers.contains(KeyModifiers::CONTROL.union(KeyModifiers::ALT)) } => {
self.temp_data.loadfile_result = self.savefile_load();
}
Event::Key(KeyEvent {
code: KeyCode::Char('s' | 'S'),
modifiers,
kind: Press | Repeat,
..
}) if { modifiers.contains(KeyModifiers::CONTROL.union(KeyModifiers::ALT)) } => {
self.temp_data.storefile_result = self.savefile_store();
}
Event::Resize(..) => {}
_ => {}
}
selected = selected.rem_euclid(selection.len());
refresh_fully = true;
}
}
}