use std::collections::HashMap;
pub const RESET: &str = "\x1b[0m";
pub const BOLD: &str = "\x1b[1m";
pub const DIM: &str = "\x1b[2m";
pub const CYAN: &str = "\x1b[36m";
pub const YELLOW: &str = "\x1b[33m";
pub fn element_color(en: &str) -> Option<&'static str> {
let map: &[(&str, &str)] = &[
("Fire", "\x1b[38;5;196m"),
("Water", "\x1b[38;5;39m"),
("Thunder", "\x1b[38;5;220m"),
("Ice", "\x1b[38;5;231m"),
("Dragon", "\x1b[38;5;129m"),
("Poison", "\x1b[38;5;91m"),
("Sleep", "\x1b[38;5;83m"),
("Paralysis", "\x1b[38;5;226m"),
("Blast", "\x1b[38;5;208m"),
("Stun", "\x1b[38;5;215m"),
];
map.iter().find(|(k, _)| *k == en).map(|(_, v)| *v)
}
pub fn name_colored_by_element(name: &str, elements: &[String]) -> String {
match elements.first().and_then(|e| element_color(e)) {
Some(c) => format!("{c}{name}{RESET}"),
None => name.to_string(),
}
}
pub fn ailment_color(ailment: &str) -> &'static str {
let lower = ailment.to_lowercase();
let rules: &[(&str, &str)] = &[
("fire", "\x1b[38;5;196m"),
("water", "\x1b[38;5;39m"),
("thunder", "\x1b[38;5;220m"),
("hellfire", "\x1b[38;5;220m"),
("ice", "\x1b[38;5;231m"),
("snowman", "\x1b[38;5;231m"),
("dragon", "\x1b[38;5;129m"),
("poison", "\x1b[38;5;91m"),
("sleep", "\x1b[38;5;83m"),
("paralysis", "\x1b[38;5;226m"),
("blast", "\x1b[38;5;208m"),
("stun", "\x1b[38;5;215m"),
("fatigue", "\x1b[38;5;215m"),
("soiled", "\x1b[38;5;215m"),
("bleed", "\x1b[38;5;52m"),
("blood", "\x1b[38;5;52m"),
("drain", "\x1b[38;5;52m"),
];
for (needle, color) in rules {
if lower.contains(needle) {
return color;
}
}
RESET
}
pub fn stars(n: u32) -> String {
if n == 0 || n > 3 {
return String::new();
}
let filled: String = "★".repeat(n as usize);
let empty: String = "☆".repeat((3 - n) as usize);
format!("{filled}{empty}")
}
pub fn game_abbr(full: &str) -> &'static str {
let map: &[(&str, &str)] = &[
("Monster Hunter Freedom Unite", "MHFU"),
("Monster Hunter 3 Ultimate", "MH3U"),
("Monster Hunter 4 Ultimate", "MH4U"),
("Monster Hunter Generations Ultimate", "MHGU"),
("Monster Hunter World", "MHW"),
("Monster Hunter Rise", "MHRise"),
("Monster Hunter Wilds", "MHWilds"),
("Monster Hunter Stories", "MHST"),
("Monster Hunter Stories 2", "MHST2"),
];
map.iter()
.find(|(k, _)| *k == full)
.map(|(_, v)| *v)
.unwrap_or("")
}
pub fn game_code_abbr(code: &str) -> String {
let canonical = normalize_game(code).unwrap_or(code);
let map: &[(&str, &str)] = &[
("mhfu", "MHFU"),
("mh3u", "MH3U"),
("mh4u", "MH4U"),
("mhgu", "MHGU"),
("mhw", "MHW"),
("mhwi", "MHWI"),
("mhrise", "MHRise"),
("mhrs", "MHRS"),
("mhwilds", "MHWilds"),
("mhst", "MHST"),
("mhst2", "MHST2"),
];
map.iter()
.find(|(k, _)| *k == canonical)
.map(|(_, v)| (*v).to_string())
.unwrap_or_else(|| code.to_uppercase())
}
pub fn normalize_game(token: &str) -> Option<&'static str> {
let mut m = HashMap::new();
let pairs: &[(&str, &str)] = &[
("mhfu", "mhfu"),
("Monster Hunter Freedom Unite", "mhfu"),
("mh3u", "mh3u"),
("Monster Hunter 3 Ultimate", "mh3u"),
("mh4u", "mh4u"),
("Monster Hunter 4 Ultimate", "mh4u"),
("mhgu", "mhgu"),
("Monster Hunter Generations Ultimate", "mhgu"),
("mhw", "mhw"),
("Monster Hunter World", "mhw"),
("mhwi", "mhwi"),
("Monster Hunter World: Iceborne", "mhwi"),
("mhrise", "mhrise"),
("Monster Hunter Rise", "mhrise"),
("mhrs", "mhrs"),
("Monster Hunter Rise: Sunbreak", "mhrs"),
("mhwilds", "mhwilds"),
("wilds", "mhwilds"),
("Monster Hunter Wilds", "mhwilds"),
("mhst", "mhst"),
("Monster Hunter Stories", "mhst"),
("mhst2", "mhst2"),
("Monster Hunter Stories 2", "mhst2"),
];
for (k, v) in pairs {
m.insert(*k, *v);
}
m.get(token).copied().or_else(|| {
let up = token.to_uppercase();
let abbrev: &[(&str, &str)] = &[
("MHFU", "mhfu"),
("MH3U", "mh3u"),
("MH4U", "mh4u"),
("MHGU", "mhgu"),
("MHW", "mhw"),
("MHWI", "mhwi"),
("MHRISE", "mhrise"),
("MHRS", "mhrs"),
("MHWILDS", "mhwilds"),
("MHST", "mhst"),
("MHST2", "mhst2"),
];
abbrev.iter().find(|(k, _)| *k == up).map(|(_, v)| *v)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn element_colors_known() {
assert_eq!(element_color("Fire"), Some("\x1b[38;5;196m"));
assert_eq!(element_color("Dragon"), Some("\x1b[38;5;129m"));
assert_eq!(element_color("Unknown"), None);
}
#[test]
fn name_colored_uses_first_element() {
let out = name_colored_by_element("Rathalos", &["Fire".to_string()]);
assert!(out.contains("\x1b[38;5;196m"), "Fire color: {out:?}");
assert!(out.contains("Rathalos"));
assert!(out.ends_with("\x1b[0m"), "must reset at end: {out:?}");
}
#[test]
fn name_colored_multi_element_uses_first() {
let out = name_colored_by_element("Kushala Daora", &["Dragon".into(), "Ice".into()]);
assert!(
out.contains("\x1b[38;5;129m"),
"should pick first element (Dragon=129): {out:?}"
);
assert!(
!out.contains("\x1b[38;5;231m"),
"must NOT use second element (Ice=231): {out:?}"
);
}
#[test]
fn name_colored_no_element_is_plain() {
let out = name_colored_by_element("Deviljho", &[]);
assert_eq!(out, "Deviljho", "no-element name must be uncolored");
}
#[test]
fn name_colored_unknown_element_is_plain() {
let out = name_colored_by_element("Mystery", &["Quantum".to_string()]);
assert_eq!(out, "Mystery", "unknown token must fall back to plain");
}
#[test]
fn ailment_color_sniffs_keyword() {
assert_eq!(ailment_color("Fireblight"), "\x1b[38;5;196m");
assert_eq!(ailment_color("Thunderblight"), "\x1b[38;5;220m");
assert_eq!(ailment_color("Hellfireblight"), "\x1b[38;5;196m");
assert_eq!(ailment_color("Blastblight"), "\x1b[38;5;208m");
assert_eq!(ailment_color("Effluvium"), RESET);
assert_eq!(ailment_color("Bleeding"), "\x1b[38;5;52m");
}
#[test]
fn stars_format() {
assert_eq!(stars(0), "");
assert_eq!(stars(1), "★☆☆");
assert_eq!(stars(2), "★★☆");
assert_eq!(stars(3), "★★★");
assert_eq!(stars(4), "");
}
#[test]
fn game_abbr_known() {
assert_eq!(game_abbr("Monster Hunter World"), "MHW");
assert_eq!(game_abbr("Monster Hunter Wilds"), "MHWilds");
assert_eq!(game_abbr("Unknown Title"), "");
}
#[test]
fn normalize_game_handles_codes_abbrs_and_titles() {
assert_eq!(normalize_game("mhw"), Some("mhw"));
assert_eq!(normalize_game("MHW"), Some("mhw"));
assert_eq!(normalize_game("Monster Hunter World"), Some("mhw"));
assert_eq!(normalize_game("mhwilds"), Some("mhwilds"));
assert_eq!(normalize_game("MHWILDS"), Some("mhwilds"));
assert_eq!(normalize_game("wilds"), Some("mhwilds"));
assert_eq!(normalize_game("garbage"), None);
}
#[test]
fn game_code_abbr_maps_api_vocabulary() {
assert_eq!(game_code_abbr("mhw"), "MHW");
assert_eq!(game_code_abbr("wilds"), "MHWilds");
assert_eq!(game_code_abbr("mhwilds"), "MHWilds");
assert_eq!(game_code_abbr("future"), "FUTURE");
}
}