1#[derive(Clone, Debug)]
8pub struct RankMap {
9 width: u16,
10 height: u16,
11 ranks: Vec<Option<f32>>,
13}
14
15impl RankMap {
16 pub fn new(width: u16, height: u16) -> Self {
18 RankMap {
19 width,
20 height,
21 ranks: vec![None; width as usize * height as usize],
22 }
23 }
24
25 pub fn width(&self) -> u16 {
26 self.width
27 }
28
29 pub fn height(&self) -> u16 {
30 self.height
31 }
32
33 #[inline]
38 fn index(&self, x: u16, y: u16) -> Option<usize> {
39 (x < self.width && y < self.height).then(|| y as usize * self.width as usize + x as usize)
40 }
41
42 pub fn set(&mut self, x: u16, y: u16, rank: f32) {
44 if let Some(i) = self.index(x, y) {
45 self.ranks[i] = Some(rank);
46 }
47 }
48
49 #[inline]
51 pub fn rank_at(&self, x: u16, y: u16) -> Option<f32> {
52 self.ranks[self.index(x, y)?]
53 }
54
55 #[inline]
57 pub fn visible_at(&self, x: u16, y: u16, progress: f32) -> bool {
58 matches!(self.rank_at(x, y), Some(r) if r <= progress)
59 }
60
61 pub fn ink_count(&self) -> usize {
63 self.ranks.iter().filter(|r| r.is_some()).count()
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
73 fn out_of_bounds_reads_are_none() {
74 let mut map = RankMap::new(4, 3);
75 map.set(1, 1, 0.5); assert_eq!(map.rank_at(1, 1), Some(0.5));
78 assert_eq!(map.rank_at(5, 0), None, "x wrapped into the next row");
79 assert!(!map.visible_at(5, 0, 1.0));
80 assert_eq!(map.rank_at(0, 3), None);
81 assert_eq!(map.rank_at(u16::MAX, u16::MAX), None);
82 }
83
84 #[test]
85 fn out_of_bounds_writes_are_ignored() {
86 let mut map = RankMap::new(4, 3);
87 map.set(5, 0, 0.5); assert_eq!(map.rank_at(1, 1), None);
89 assert_eq!(map.ink_count(), 0);
90 }
91
92 #[test]
93 fn empty_map_is_inert() {
94 let map = RankMap::new(0, 0);
95 assert_eq!(map.rank_at(0, 0), None);
96 assert_eq!(map.ink_count(), 0);
97 }
98}