use num_format::{Format, ToFormattedString};
use serde_json::Value;
pub mod emhs;
pub mod glf;
pub mod vpreg;
const NUMERIC_KEYS: &[&str] = &["score", "counter", "nth time"];
pub const COL_LABEL: usize = 0;
pub const COL_INITIALS: usize = 1;
pub const COL_SCORE: usize = 2;
pub const COL_UNITS: usize = 3;
pub const HEADERS: [&str; 4] = ["LABEL", "INITIALS", "SCORE", "UNITS"];
pub fn extract_rows(resolved: &Value) -> Vec<Vec<String>> {
extract_sections(resolved)
.into_iter()
.flat_map(|s| s.rows)
.collect()
}
#[derive(Debug, PartialEq, Eq)]
pub struct Section {
pub header: String,
pub rows: Vec<Vec<String>>,
pub ranked: bool,
}
pub fn extract_sections(resolved: &Value) -> Vec<Section> {
let mut sections = Vec::new();
if let Some(entries) = resolved.get("high_scores").and_then(|v| v.as_array()) {
let rows: Vec<Vec<String>> = entries.iter().filter_map(score_row_from_entry).collect();
sections.extend(split_high_scores(rows));
}
for key in ["mode_champions", "more_mode_champions"] {
let Some(entries) = resolved.get(key).and_then(|v| v.as_array()) else {
continue;
};
for entry in entries {
if let Some(row) = score_row_from_entry(entry) {
sections.push(Section {
header: row[COL_LABEL].to_uppercase(),
rows: vec![row],
ranked: false,
});
}
}
}
sections
}
pub(crate) fn split_high_scores(rows: Vec<Vec<String>>) -> Vec<Section> {
match rows.len() {
0 => Vec::new(),
1 => vec![Section {
header: rows[0][COL_LABEL].to_uppercase(),
rows,
ranked: false,
}],
_ => {
let first_label = &rows[0][COL_LABEL];
if is_ranked_label(first_label) {
vec![Section {
header: "HIGH SCORES".to_string(),
rows,
ranked: true,
}]
} else {
let mut iter = rows.into_iter();
let top = iter.next().unwrap();
let rest: Vec<_> = iter.collect();
let top_header = top[COL_LABEL].to_uppercase();
let mut sections = vec![Section {
header: top_header,
rows: vec![top],
ranked: false,
}];
if !rest.is_empty() {
sections.push(Section {
header: "HIGH SCORES".to_string(),
rows: rest,
ranked: true,
});
}
sections
}
}
}
}
fn is_ranked_label(label: &str) -> bool {
let l = label.trim().to_ascii_lowercase();
if l.starts_with('#') {
return true;
}
const RANKED_PREFIXES: &[&str] = &[
"first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth",
"tenth", "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th",
];
if RANKED_PREFIXES.iter().any(|p| l.starts_with(p)) {
return true;
}
l.contains('#')
}
pub fn render_pinemhi<F: Format>(sections: &[Section], locale: &F) -> String {
let mut out = String::new();
for (idx, section) in sections.iter().enumerate() {
if idx > 0 {
out.push('\n');
}
out.push_str(§ion.header);
out.push('\n');
let mut formatted = section.rows.clone();
pretty_score_column(&mut formatted, locale);
let initials_w = formatted
.iter()
.map(|r| r.get(COL_INITIALS).map_or(0, |s| s.chars().count()))
.max()
.unwrap_or(0);
let score_w = formatted
.iter()
.map(|r| r.get(COL_SCORE).map_or(0, |s| s.chars().count()))
.max()
.unwrap_or(0);
let rank_w = if section.ranked {
format!("{}.", formatted.len()).chars().count()
} else {
0
};
for (i, row) in formatted.iter().enumerate() {
let initials = row.get(COL_INITIALS).map(String::as_str).unwrap_or("");
let score = row.get(COL_SCORE).map(String::as_str).unwrap_or("");
let mut line = String::new();
if section.ranked {
let rank = format!("{}.", i + 1);
line.push_str(&format!("{rank:<rank_w$} "));
}
if initials_w > 0 {
line.push_str(&format!("{initials:<initials_w$}"));
if score_w > 0 {
line.push_str(" ");
}
}
if score_w > 0 {
line.push_str(&format!("{score:>score_w$}"));
}
out.push_str(line.trim_end());
out.push('\n');
}
}
out
}
fn score_row_from_entry(entry: &Value) -> Option<Vec<String>> {
let label = entry.get("label").and_then(|v| v.as_str()).unwrap_or("");
let initials = entry
.get("initials")
.and_then(|v| v.get("value"))
.and_then(|v| v.as_str())
.map(str::trim_end)
.unwrap_or("");
let score_obj = NUMERIC_KEYS.iter().find_map(|k| entry.get(*k));
let numeric_value = score_obj.and_then(|o| o.get("value"));
let units = score_obj
.and_then(|o| o.get("units"))
.and_then(|v| v.as_str())
.unwrap_or("");
let score_str = match numeric_value {
Some(Value::Number(n)) => {
if number_is_zero(n) {
return None;
}
n.to_string()
}
Some(Value::String(s)) => s.clone(),
_ => String::new(),
};
if label.is_empty() && initials.is_empty() && score_str.is_empty() {
return None;
}
Some(vec![
label.to_string(),
initials.to_string(),
score_str,
units.to_string(),
])
}
pub fn pretty_score_column<F: Format>(rows: &mut [Vec<String>], locale: &F) {
for row in rows {
if row.len() <= COL_SCORE {
continue;
}
let units = row.get(COL_UNITS).cloned().unwrap_or_default();
let score = &mut row[COL_SCORE];
match units.as_str() {
"seconds" => {
if let Ok(n) = score.parse::<u64>() {
*score = format_seconds_as_time(n);
}
}
_ => {
if let Ok(n) = score.parse::<u64>() {
*score = n.to_formatted_string(locale);
} else if let Ok(n) = score.parse::<i64>() {
*score = n.to_formatted_string(locale);
}
}
}
}
}
fn format_seconds_as_time(total: u64) -> String {
let hours = total / 3600;
let minutes = (total % 3600) / 60;
let seconds = total % 60;
if hours > 0 {
format!("{hours}:{minutes:02}:{seconds:02}")
} else {
format!("{minutes}:{seconds:02}")
}
}
fn number_is_zero(n: &serde_json::Number) -> bool {
if let Some(u) = n.as_u64() {
return u == 0;
}
if let Some(i) = n.as_i64() {
return i == 0;
}
if let Some(f) = n.as_f64() {
return f == 0.0;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use num_format::Locale;
use pretty_assertions::assert_eq;
use serde_json::json;
const TEST_LOCALE: &Locale = &Locale::en;
#[test]
fn extracts_high_scores_in_array_order_with_raw_numeric_scores() {
let resolved = json!({
"high_scores": [
{"label": "Grand Champion", "initials": {"value": "JBJ"}, "score": {"value": 180000000}},
{"label": "First Place", "initials": {"value": "DRF"}, "score": {"value": 165000000}},
]
});
assert_eq!(
extract_rows(&resolved),
vec![
vec![
"Grand Champion".to_string(),
"JBJ".to_string(),
"180000000".to_string(),
"".to_string(),
],
vec![
"First Place".to_string(),
"DRF".to_string(),
"165000000".to_string(),
"".to_string(),
],
]
);
}
#[test]
fn extracts_units_when_map_provides_them() {
let resolved = json!({
"mode_champions": [
{"label": "Destroy Ring Champion", "initials": {"value": "EYE"},
"score": {"value": 600, "units": "seconds"}},
]
});
let rows = extract_rows(&resolved);
assert_eq!(rows[0][COL_SCORE], "600");
assert_eq!(rows[0][COL_UNITS], "seconds");
}
#[test]
fn pretty_score_column_groups_integer_score_thousands() {
let mut rows = vec![
vec!["A".into(), "AAA".into(), "180000000".into(), "".into()],
vec!["B".into(), "BBB".into(), "7".into(), "".into()],
];
pretty_score_column(&mut rows, TEST_LOCALE);
assert_eq!(rows[0][COL_SCORE], "180,000,000");
assert_eq!(rows[1][COL_SCORE], "7");
}
#[test]
fn pretty_score_column_renders_seconds_as_time() {
let mut rows = vec![vec![
"Destroy Ring Champion".into(),
"EYE".into(),
"600".into(),
"seconds".into(),
]];
pretty_score_column(&mut rows, TEST_LOCALE);
assert_eq!(rows[0][COL_SCORE], "10:00");
}
#[test]
fn pretty_score_column_renders_long_seconds_as_h_mm_ss() {
let mut rows = vec![vec![
"Marathon".into(),
"AAA".into(),
"13507".into(),
"seconds".into(),
]];
pretty_score_column(&mut rows, TEST_LOCALE);
assert_eq!(rows[0][COL_SCORE], "3:45:07");
}
#[test]
fn pretty_score_column_leaves_string_scores_alone() {
let mut rows = vec![vec!["A".into(), "AAA".into(), "10:00.00".into(), "".into()]];
pretty_score_column(&mut rows, TEST_LOCALE);
assert_eq!(rows[0][COL_SCORE], "10:00.00");
}
#[test]
fn appends_mode_champions_after_high_scores() {
let resolved = json!({
"high_scores": [
{"label": "GC", "initials": {"value": "AAA"}, "score": {"value": 1000}},
],
"mode_champions": [
{"label": "Castle Champion", "initials": {"value": "JCY"}, "score": {"value": 6}},
],
});
let rows = extract_rows(&resolved);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0][0], "GC");
assert_eq!(rows[1][0], "Castle Champion");
}
#[test]
fn drops_entries_with_zero_numeric_value() {
let resolved = json!({
"mode_champions": [
{"label": "King #1", "initials": {"value": "KOP"}, "counter": {"value": 1}},
{"label": "King #2", "initials": {"value": "KOP"}, "counter": {"value": 0}},
{"label": "King #3", "initials": {"value": "KOP"}, "counter": {"value": 0}},
]
});
let rows = extract_rows(&resolved);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0][0], "King #1");
}
#[test]
fn falls_back_through_score_then_counter_then_nth_time() {
let resolved = json!({
"mode_champions": [
{"label": "Old style", "initials": {"value": "ABC"}, "nth time": {"value": 7}},
]
});
let rows = extract_rows(&resolved);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0][2], "7");
}
#[test]
fn accepts_string_score_values() {
let resolved = json!({
"mode_champions": [
{"label": "Destroy Ring Champion", "initials": {"value": "EYE"}, "score": {"value": "10:00.00"}},
]
});
let rows = extract_rows(&resolved);
assert_eq!(rows[0][2], "10:00.00");
}
#[test]
fn returns_empty_when_resolved_has_no_score_arrays() {
let resolved = json!({"audits": [], "game_state": {}});
assert!(extract_rows(&resolved).is_empty());
}
#[test]
fn pretty_score_column_uses_passed_locale_for_grouping_separator() {
let mut rows_en = vec![vec!["A".into(), "AAA".into(), "1234567".into(), "".into()]];
pretty_score_column(&mut rows_en, &Locale::en);
assert_eq!(rows_en[0][COL_SCORE], "1,234,567");
let mut rows_de = vec![vec!["A".into(), "AAA".into(), "1234567".into(), "".into()]];
pretty_score_column(&mut rows_de, &Locale::de);
assert_eq!(rows_de[0][COL_SCORE], "1.234.567");
}
#[test]
fn extract_sections_splits_grand_champion_from_ranked_rest() {
let resolved = json!({
"high_scores": [
{"label": "Grand Champion", "initials": {"value": "SLL"}, "score": {"value": 52000000}},
{"label": "First Place", "initials": {"value": "BRE"}, "score": {"value": 44000000}},
{"label": "Second Place", "initials": {"value": "LFS"}, "score": {"value": 40000000}},
]
});
let sections = extract_sections(&resolved);
assert_eq!(sections.len(), 2);
assert_eq!(sections[0].header, "GRAND CHAMPION");
assert_eq!(sections[0].ranked, false);
assert_eq!(sections[0].rows.len(), 1);
assert_eq!(sections[1].header, "HIGH SCORES");
assert_eq!(sections[1].ranked, true);
assert_eq!(sections[1].rows.len(), 2);
}
#[test]
fn extract_sections_keeps_ranked_list_together_when_no_distinct_top() {
let resolved = json!({
"high_scores": [
{"label": "First Place", "initials": {"value": "AAA"}, "score": {"value": 1000}},
{"label": "Second Place", "initials": {"value": "BBB"}, "score": {"value": 900}},
{"label": "Third Place", "initials": {"value": "CCC"}, "score": {"value": 800}},
]
});
let sections = extract_sections(&resolved);
assert_eq!(sections.len(), 1);
assert_eq!(sections[0].header, "HIGH SCORES");
assert_eq!(sections[0].ranked, true);
assert_eq!(sections[0].rows.len(), 3);
}
#[test]
fn extract_sections_keeps_ranked_list_for_hash_labels() {
let resolved = json!({
"high_scores": [
{"label": "#1", "initials": {"value": "AAA"}, "score": {"value": 1000}},
{"label": "#2", "initials": {"value": "BBB"}, "score": {"value": 900}},
]
});
let sections = extract_sections(&resolved);
assert_eq!(sections.len(), 1);
assert_eq!(sections[0].header, "HIGH SCORES");
assert_eq!(sections[0].ranked, true);
}
#[test]
fn extract_sections_single_high_score_uses_its_label_as_header() {
let resolved = json!({
"high_scores": [
{"label": "High Score", "initials": {"value": ""}, "score": {"value": 1989660}},
]
});
let sections = extract_sections(&resolved);
assert_eq!(sections.len(), 1);
assert_eq!(sections[0].header, "HIGH SCORE");
assert_eq!(sections[0].ranked, false);
}
#[test]
fn extract_sections_emits_one_section_per_mode_champion() {
let resolved = json!({
"high_scores": [
{"label": "Grand Champion", "initials": {"value": "SLL"}, "score": {"value": 1000}},
],
"mode_champions": [
{"label": "Castle Champion", "initials": {"value": "JCY"}, "score": {"value": 6}},
{"label": "Joust Champion", "initials": {"value": "DWF"}, "score": {"value": 5}},
]
});
let sections = extract_sections(&resolved);
assert_eq!(sections.len(), 3);
assert_eq!(sections[0].header, "GRAND CHAMPION");
assert_eq!(sections[1].header, "CASTLE CHAMPION");
assert_eq!(sections[2].header, "JOUST CHAMPION");
assert!(sections.iter().all(|s| !s.ranked));
}
#[test]
fn render_pinemhi_emits_section_headers_and_ranked_bodies() {
let resolved = json!({
"high_scores": [
{"label": "Grand Champion", "initials": {"value": "SLL"}, "score": {"value": 52000000}},
{"label": "First Place", "initials": {"value": "BRE"}, "score": {"value": 44000000}},
]
});
let sections = extract_sections(&resolved);
let rendered = render_pinemhi(§ions, TEST_LOCALE);
assert_eq!(
rendered,
"GRAND CHAMPION\nSLL 52,000,000\n\nHIGH SCORES\n1. BRE 44,000,000\n"
);
}
#[test]
fn render_pinemhi_aligns_score_column_when_initials_widths_differ() {
let resolved = json!({
"high_scores": [
{"label": "#1", "initials": {"value": "JEK"}, "score": {"value": 2_400_000_000_u64}},
{"label": "#2", "initials": {"value": "LON"}, "score": {"value": 2_100_000_000_u64}},
{"label": "#3", "initials": {"value": "NF"}, "score": {"value": 1_950_000_000_u64}},
]
});
let sections = extract_sections(&resolved);
let rendered = render_pinemhi(§ions, TEST_LOCALE);
assert_eq!(
rendered,
"HIGH SCORES\n\
1. JEK 2,400,000,000\n\
2. LON 2,100,000,000\n\
3. NF 1,950,000,000\n"
);
}
#[test]
fn render_pinemhi_pads_rank_for_two_digit_ranks() {
let rows: Vec<_> = (1..=10)
.map(|i| {
serde_json::json!({
"label": format!("#{i}"),
"initials": {"value": "AAA"},
"score": {"value": 1000 - i * 10},
})
})
.collect();
let resolved = serde_json::json!({ "high_scores": rows });
let sections = extract_sections(&resolved);
let rendered = render_pinemhi(§ions, TEST_LOCALE);
assert!(rendered.contains("\n1. AAA 990\n"));
assert!(rendered.contains("\n9. AAA 910\n"));
assert!(rendered.contains("\n10. AAA 900\n"));
}
#[test]
fn render_pinemhi_drops_trailing_whitespace_when_score_is_empty() {
let resolved = json!({
"mode_champions": [
{"label": "The Roof Champion", "initials": {"value": "XAQ"}}
]
});
let sections = extract_sections(&resolved);
let rendered = render_pinemhi(§ions, TEST_LOCALE);
assert_eq!(rendered, "THE ROOF CHAMPION\nXAQ\n");
}
#[test]
fn render_pinemhi_omits_initials_line_when_no_initials() {
let resolved = json!({
"high_scores": [
{"label": "High Score", "initials": {"value": ""}, "score": {"value": 1989660}},
]
});
let sections = extract_sections(&resolved);
let rendered = render_pinemhi(§ions, TEST_LOCALE);
assert_eq!(rendered, "HIGH SCORE\n1,989,660\n");
}
}