1use std::{
2 collections::HashMap,
3 fmt::Display,
4 ops::{Index, IndexMut},
5};
6
7#[derive(Default, Debug, Clone, Copy)]
8pub struct Options {
9 pub bottom_oriented: bool,
10 pub filler: char,
11}
12
13#[derive(Debug)]
14pub struct DebugCanvas {
15 points: HashMap<(i64, i64), char>,
16 min_row: i64,
17 max_row: i64,
18 min_col: i64,
19 max_col: i64,
20 options: Options,
21}
22impl DebugCanvas {
23 fn is_empty(&self) -> bool {
24 self.points.is_empty()
25 }
26 pub fn size(&self) -> (u64, u64) {
27 if self.is_empty() {
28 (0, 0)
29 } else {
30 (
31 (self.max_row - self.min_row + 1) as u64,
32 (self.max_col - self.min_col + 1) as u64,
33 )
34 }
35 }
36
37 pub fn new() -> Self {
38 Self::with_options(Options::default())
39 }
40 pub fn with_options(options: Options) -> Self {
41 Self {
42 options,
43 points: HashMap::new(),
44 min_row: i64::MAX,
45 max_row: i64::MIN,
46 min_col: i64::MAX,
47 max_col: i64::MIN,
48 }
49 }
50
51 pub fn clear(&mut self) {
52 *self = Self::with_options(self.options);
53 }
54
55 pub fn remove(&mut self, (row, col): (i64, i64)) {
56 self.points.remove(&(row, col));
57 self.max_col = self.points.keys().map(|&f| f.1).max().unwrap_or(i64::MIN);
58 self.min_col = self.points.keys().map(|&f| f.1).min().unwrap_or(i64::MAX);
59 self.max_row = self.points.keys().map(|&f| f.0).max().unwrap_or(i64::MIN);
60 self.min_row = self.points.keys().map(|&f| f.0).min().unwrap_or(i64::MAX);
61 }
62}
63
64impl Default for DebugCanvas {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl Display for DebugCanvas {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 if self.is_empty() {
73 return Ok(());
74 }
75 let (n, m) = self.size();
76 for i in 0..n {
77 if i > 0 {
78 writeln!(f)?;
79 }
80 for j in 0..m {
81 let row = if self.options.bottom_oriented {
82 self.min_row + i as i64
83 } else {
84 self.max_row - i as i64
85 };
86 let col = self.min_col + j as i64;
87 write!(f, "{}", self[(row, col)])?;
88 }
89 }
90 writeln!(f)?;
91 Ok(())
92 }
93}
94
95impl<T> Index<(T, T)> for DebugCanvas
96where
97 T: Into<i64>,
98{
99 type Output = char;
100
101 fn index(&self, (row, col): (T, T)) -> &Self::Output {
102 self.points
103 .get(&(row.into(), col.into()))
104 .unwrap_or(&self.options.filler)
105 }
106}
107
108impl<T> IndexMut<(T, T)> for DebugCanvas
109where
110 T: Into<i64>,
111{
112 fn index_mut(&mut self, (row, col): (T, T)) -> &mut Self::Output {
113 let (row, col) = (row.into(), col.into());
114 self.max_col = self.max_col.max(col);
115 self.min_col = self.min_col.min(col);
116 self.max_row = self.max_row.max(row);
117 self.min_row = self.min_row.min(row);
118 self.points.entry((row, col)).or_insert(' ')
119 }
120}
121
122#[cfg(test)]
123mod tests {
124
125 use super::*;
126
127 #[test]
128 fn test_blank_canvas() {
129 let mut canvas = DebugCanvas::new();
130 assert_eq!(format!("{canvas}"), "");
131 canvas[(3, 2)] = '#';
132 assert_eq!(format!("{canvas}"), "#");
133 canvas[(3, 2)] = '.';
134 assert_eq!(format!("{canvas}"), ".");
135 canvas[(3, 3)] = '-';
136 assert_eq!(format!("{canvas}"), ".-");
137 canvas[(2, 3)] = 'a';
138 assert_eq!(
139 format!("{canvas}"),
140 "
141.-
142 a"
143 .trim_start()
144 );
145 canvas[(2, 5)] = 'b';
146 assert_eq!(
147 format!("{canvas}"),
148 "
149.-
150 a b"
151 .trim_start()
152 );
153 canvas.remove((2, 5));
154 assert_eq!(
155 format!("{canvas}"),
156 "
157.-
158 a"
159 .trim_start()
160 );
161 canvas.remove((2, 8));
162 assert_eq!(
163 format!("{canvas}"),
164 "
165.-
166 a"
167 .trim_start()
168 );
169 }
170}