use std::time::Duration;
use crossterm::event::{KeyCode, KeyModifiers};
use falling_tetromino_engine::{Button, ExtNonNegF64, Input, Tetromino};
use crate::keybinds::Keybinds;
pub type KeybindsLegend = Vec<(/*(KeyCode, KeyModifiers)*/ String, &'static str)>;
pub trait FmtBool {
fn fmt_on_off(self) -> &'static str;
}
impl FmtBool for bool {
fn fmt_on_off(self) -> &'static str {
if self {
"on"
} else {
"off"
}
}
}
pub fn fmt_duration(dur: Duration) -> String {
if dur.as_secs() / 60 == 0 {
format!("{}.{:02}s", dur.as_secs() % 60, dur.as_millis() % 1000 / 10)
} else {
format!(
"{}min {}.{:02}s",
dur.as_secs() / 60,
dur.as_secs() % 60,
dur.as_millis() % 1000 / 10
)
}
}
pub fn fmt_hertz(f: ExtNonNegF64) -> String {
const LOWERBOUND: f64 = 0.1e-6;
const UPPERBOUND: f64 = 0.1e+6;
if f.get() <= LOWERBOUND {
"0 Hz".to_owned()
} else if UPPERBOUND <= f.get() {
"∞ Hz".to_owned()
} else {
format!("{:.01} Hz", f.get())
}
}
pub trait FmtTetromino {
fn fmt_small(&self) -> &str;
fn fmt_small_ascii(&self) -> &str;
fn fmt_mini(&self) -> &str;
fn fmt_mini_ascii(&self) -> &str;
}
impl FmtTetromino for Tetromino {
fn fmt_small(&self) -> &'static str {
use Tetromino::*;
match self {
O => "██",
I => "▄▄▄▄",
S => "▄█▀",
Z => "▀█▄",
T => "▄█▄",
L => "▄▄█",
J => "█▄▄",
}
}
fn fmt_small_ascii(&self) -> &'static str {
use Tetromino::*;
match self {
O => "::",
I => "....",
S => ".:°",
Z => "°:.",
T => ".:.",
L => "..:",
J => ":..",
}
}
fn fmt_mini(&self) -> &'static str {
use Tetromino::*;
match self {
O => "⠶", I => "⡇", S => "⠳", Z => "⠞", T => "⠗", L => "⠧", J => "⠼", }
}
fn fmt_mini_ascii(&self) -> &'static str {
use Tetromino::*;
match self {
O => "O",
I => "I",
S => "S",
Z => "Z",
T => "T",
L => "L",
J => "J",
}
}
}
pub fn fmt_tetromino_counts(counts: &[u32; Tetromino::VARIANTS.len()]) -> String {
counts
.iter()
.zip(Tetromino::VARIANTS)
.map(|(n, t)| format!("{n}{}", t.fmt_mini_ascii().to_ascii_lowercase()))
.collect::<Vec<_>>()
.join(" ")
}
pub fn fmt_button(b: Button) -> &'static str {
use Button as B;
match b {
B::MoveLeft => "←",
B::MoveRight => "→",
B::RotateLeft => "↺",
B::RotateRight => "↻",
B::Rotate180 => "↔",
B::DropSoft => "↓",
B::DropHard => "⤓",
B::TeleDown => "⇓",
B::TeleLeft => "⇐",
B::TeleRight => "⇒",
B::HoldPiece => "⇋",
}
}
pub fn fmt_button_ascii(b: Button) -> &'static str {
use Button as B;
match b {
B::MoveLeft => "<",
B::MoveRight => ">",
B::RotateLeft => "L",
B::RotateRight => "R",
B::Rotate180 => "O",
B::DropSoft => "v",
B::DropHard => "!",
B::TeleDown => "w",
B::TeleLeft => "{",
B::TeleRight => "}",
B::HoldPiece => "H",
}
}
pub fn fmt_button_input(input: Input, as_ascii: bool) -> String {
match input {
Input::Activate(b) => format!(
"++|{}|",
if as_ascii {
fmt_button_ascii
} else {
fmt_button
}(b)
),
Input::Deactivate(b) => format!(
"--|{}|",
if as_ascii {
fmt_button_ascii
} else {
fmt_button
}(b)
),
}
}
pub fn fmt_key(key: KeyCode) -> String {
use crossterm::event::ModifierKeyCode as M;
use KeyCode as K;
match key {
K::Backspace => "Back",
K::Left => "←",
K::Right => "→",
K::Up => "↑",
K::Down => "↓",
K::Delete => "Del",
K::PageUp => "PgUp",
K::PageDown => "PgDn",
K::F(k) => return format!("F{k}"),
K::Char(' ') => "Space",
K::Char(c) => {
return c.to_string();
}
K::Modifier(M::LeftAlt) => "LAlt",
K::Modifier(M::RightAlt) => "RAlt",
K::Modifier(M::LeftShift) => "LShift",
K::Modifier(M::RightShift) => "RShift",
K::Modifier(M::LeftControl) => "LCtrl",
K::Modifier(M::RightControl) => "RCtrl",
K::Modifier(M::IsoLevel3Shift) => "AltGr",
K::Modifier(M::IsoLevel5Shift) => "Iso5",
K::Modifier(M::LeftSuper) => "LSuper",
K::Modifier(M::RightSuper) => "RSuper",
K::Modifier(M::LeftHyper) => "LHyper",
K::Modifier(M::RightHyper) => "RHyper",
K::Modifier(M::LeftMeta) => "LMeta",
K::Modifier(M::RightMeta) => "RMeta",
k => return format!("{:?}", k),
}
.to_string()
}
pub fn fmt_keymods(keymod: KeyModifiers) -> String {
use KeyModifiers as KMs;
[
keymod.contains(KMs::CONTROL).then_some("Ctrl"),
keymod.contains(KMs::SHIFT).then_some("Shift"),
keymod.contains(KMs::ALT).then_some("Alt"),
keymod.contains(KMs::SUPER).then_some("Super"),
keymod.contains(KMs::HYPER).then_some("Hyper"),
keymod.contains(KMs::META).then_some("Meta"),
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
.join("+")
}
pub fn fmt_key_keymods((key, keymods): (KeyCode, KeyModifiers)) -> String {
if keymods.is_empty() {
format!("[{}]", fmt_key(key))
} else {
format!("[{}+{}]", fmt_keymods(keymods), fmt_key(key))
}
}
pub fn fmt_keybinds_of(button: Button, keybinds: &Keybinds) -> String {
keybinds
.iter()
.filter_map(|(key_keymods, b)| (*b == button).then_some(fmt_key_keymods(*key_keymods)))
.collect::<Vec<_>>()
.join("")
}
pub fn get_play_keybinds_legend(keybinds: &Keybinds) -> KeybindsLegend {
let fk = |k| fmt_key_keymods((k, KeyModifiers::NONE));
let fb = |b| fmt_keybinds_of(b, keybinds);
let icon_pause = fk(KeyCode::Esc);
let icons_move = format!("{}{}", fb(Button::MoveLeft), fb(Button::MoveRight));
let icons_rotate = format!(
"{}{}{}",
fb(Button::RotateLeft),
fb(Button::Rotate180),
fb(Button::RotateRight)
);
let icons_drop = format!("{}{}", fb(Button::DropSoft), fb(Button::DropHard));
let icons_hold = fb(Button::HoldPiece);
vec![
(icon_pause, "pause"),
(icons_move, "move"),
(icons_rotate, "rotate"),
(icons_drop, "drop"),
(icons_hold, "hold"),
]
}
pub fn replay_keybinds_legend() -> KeybindsLegend {
let fk = |k| fmt_key_keymods((k, KeyModifiers::NONE));
let icon_pause = fk(KeyCode::Char(' '));
let icons_speed = format!("{}{}", fk(KeyCode::Down), fk(KeyCode::Up));
let icons_skip = format!("{}{}", fk(KeyCode::Left), fk(KeyCode::Right));
let icons_enter = fk(KeyCode::Enter);
let icon_stop = fk(KeyCode::Esc);
vec![
(icon_pause, "pause"),
(icons_speed, "speed -/+"),
(icons_skip, "timeskip -/+"),
(icons_enter, "game from here"),
(icon_stop, "stop"),
]
}
pub fn arabic_to_roman(mut num: u32) -> String {
if 4000 <= num {
return num.to_string();
}
const ADDITIVE_NUMERAL_PARTS: [(&str, u32); 13] = [
("M", 1000),
("CM", 900),
("D", 500),
("CD", 400),
("C", 100),
("XC", 90),
("L", 50),
("XL", 40),
("X", 10),
("IX", 9),
("V", 5),
("IV", 4),
("I", 1),
];
let mut string = String::new();
for (str, value) in ADDITIVE_NUMERAL_PARTS {
while num >= value {
num -= value;
string.push_str(str);
}
}
string
}