1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! What screen a PinMAME game has, from the ROM name.
//!
//! `data/rom_displays.csv` was made by running every game of the PinMAME 3.7
//! collection far enough for the library to announce its displays: a dot
//! matrix (128x32, 128x16, 192x64...), a video screen (Mr. Game's rotated CRT
//! is 256x248, nearly square) or only segment displays. The B2S fix uses it to
//! leave alone the tables that have no dot matrix, and to look for a hole of
//! the shape the game's screen really has.
use std::collections::HashMap;
use std::sync::OnceLock;
/// The screen a game draws its scores on.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Screen {
/// A dot matrix display of `width` x `height` dots.
Dmd { width: u32, height: u32 },
/// A CRT or LCD video screen of `width` x `height` pixels.
Video { width: u32, height: u32 },
/// Segment displays only: no dot matrix to show.
Segments,
/// A ROM the list does not know (a recent or custom one).
Unknown,
}
impl Screen {
/// Width over height of the screen, `None` when there is no such screen.
pub fn aspect(self) -> Option<f32> {
match self {
Screen::Dmd { width, height } | Screen::Video { width, height } if height > 0 => {
Some(width as f32 / height as f32)
}
_ => None,
}
}
}
fn table() -> &'static HashMap<&'static str, Screen> {
static TABLE: OnceLock<HashMap<&'static str, Screen>> = OnceLock::new();
TABLE.get_or_init(|| {
let mut map = HashMap::new();
for line in include_str!("../data/rom_displays.csv").lines() {
if line.starts_with('#') {
continue;
}
let mut f = line.split(',');
let (Some(name), Some(kind), Some(w), Some(h)) =
(f.next(), f.next(), f.next(), f.next())
else {
continue;
};
let (Ok(width), Ok(height)) = (w.trim().parse(), h.trim().parse()) else {
continue;
};
let screen = match kind {
"dmd" => Screen::Dmd { width, height },
"video" => Screen::Video { width, height },
"none" => Screen::Segments,
_ => continue,
};
map.insert(name, screen);
}
map
})
}
/// The screen of the game `rom` (case does not matter).
pub fn screen_of(rom: &str) -> Screen {
let rom = rom.trim().to_ascii_lowercase();
table()
.get(rom.as_str())
.copied()
.unwrap_or(Screen::Unknown)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_games() {
assert_eq!(
screen_of("mm_109c"),
Screen::Dmd {
width: 128,
height: 32
}
);
assert_eq!(
screen_of("MOTRSHOW"),
Screen::Video {
width: 256,
height: 248
}
);
assert_eq!(screen_of("bk2k_l4"), Screen::Segments);
assert_eq!(screen_of("no_such_rom"), Screen::Unknown);
}
#[test]
fn aspect_of_the_screens() {
assert_eq!(screen_of("mm_109c").aspect(), Some(4.0));
let video = screen_of("motrshow").aspect().unwrap();
assert!((video - 256.0 / 248.0).abs() < 1e-6);
assert_eq!(screen_of("bk2k_l4").aspect(), None);
}
}