podch/board.rs
1use std::collections::HashMap;
2use std::fmt;
3use std::fmt::Write;
4use std::ops::Shl;
5
6use crate::Stone;
7
8/// Rectangular game board
9pub trait Board {
10 /// Height and width
11 ///
12 /// # Examples
13 /// ```
14 /// use podch::Board;
15 /// let height = 2;
16 /// let width = 3;
17 /// let board = podch::VecBoard::new(height, width);
18 /// assert_eq!(board.size(), (2, 3));
19 /// ```
20 fn size(&self) -> (usize, usize);
21
22 /// Construct an empty board with `height` and `width`
23 ///
24 /// # Examples
25 /// ```
26 /// use podch::Board;
27 /// let board = podch::VecBoard::new(2, 3);
28 /// assert_eq!(board.get(0, 0), podch::Stone::None);
29 /// assert_eq!(board.get(1, 2), podch::Stone::None);
30 /// ```
31 fn new(height: usize, width: usize) -> Self;
32
33 /// Get what stone is at the square in `row` and `col`umn
34 ///
35 /// # Examples
36 /// ```
37 /// use podch::{Stone, Board};
38 /// let board = podch::VecBoard::from_vec(vec![Stone::None, Stone::Light, Stone::Dark, Stone::Dark], 2);
39 /// assert_eq!(board.get(0, 0), Stone::None);
40 /// assert_eq!(board.get(0, 1), Stone::Light);
41 /// assert_eq!(board.get(1, 0), Stone::Dark);
42 /// ```
43 ///
44 /// # Panics
45 /// * if either index is out of bounds, i.e. `row` >= height OR `col` >= width
46 /// ```rust,should_panic
47 /// use podch::Board;
48 /// let board = podch::BinBoard::new(2, 3);
49 /// let (height, width) = board.size();
50 /// board.get(height, width);
51 /// ```
52 fn get(&self, row: usize, col: usize) -> Stone;
53
54 /// Set `value` to the square in `row` and `col`umn
55 ///
56 /// # Examples
57 /// ```
58 /// use podch::Board;
59 /// let mut board = podch::BinBoard::new(2, 3);
60 /// board.set(0, 1, podch::Stone::Dark);
61 /// assert_eq!(board.get(0, 1), podch::Stone::Dark);
62 /// board.set(0, 1, podch::Stone::None);
63 /// assert_eq!(board.get(0, 1), podch::Stone::None);
64 /// ```
65 ///
66 /// # Panics
67 /// * if either index is out of bounds, i.e. `row` >= height OR `col` >= width
68 /// ```rust,should_panic
69 /// use podch::Board;
70 /// let mut board = podch::BinBoard::new(2, 3);
71 /// board.set(board.size().0, 0, podch::Stone::Dark);
72 /// ```
73 fn set(&mut self, row: usize, col: usize, val: Stone);
74}
75
76/// Wrapper for `Display` for `Board`s
77///
78/// Stones are displayed as digits `1` and `2`, `0` for None.
79/// Rows are separated by linebreaks (`\n`).
80///
81/// # Examples
82/// ```
83/// use podch::Stone;
84/// let board = podch::VecBoard::from_vec(vec![Stone::None, Stone::Dark, Stone::Light, Stone::Light, Stone::None, Stone::Dark], 3);
85/// let display = podch::BoardDisplay::from(&board);
86/// assert_eq!(display.to_string(), "012\n201");
87/// ```
88#[derive(Debug, Copy, Clone)]
89pub struct BoardDisplay<'a, T: Board> {
90 board: &'a T,
91}
92
93impl<'a, T: Board> From<&'a T> for BoardDisplay<'a, T> {
94 fn from(board: &'a T) -> Self {
95 Self {
96 board,
97 }
98 }
99}
100
101impl<T: Board> fmt::Display for BoardDisplay<'_, T> {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 let size = self.board.size();
104 for i in 0..size.0 - 1 {
105 for j in 0..size.1 {
106 f.write_char(self.board.get(i, j).into())?;
107 }
108 f.write_char('\n')?;
109 }
110 for j in 0..size.1 {
111 f.write_char(self.board.get(size.0 - 1, j).into())?;
112 }
113 Ok(())
114 }
115}
116
117/// Board using height*width bytes of memory
118#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
119pub struct VecBoard {
120 vec: Vec<Stone>,
121 width: usize,
122}
123
124impl VecBoard {
125 /// Construct a board from concatenation of rows with `width`
126 ///
127 /// # Examples
128 /// ```
129 /// use podch::{Stone::*, Board};
130 /// let vec = vec![None, Dark, Light, Dark, None, Light];
131 /// let board = podch::VecBoard::from_vec(vec, 3);
132 /// assert_eq!(board.size(), (2, 3));
133 /// assert_eq!(podch::BoardDisplay::from(&board).to_string(), "012\n102");
134 /// ```
135 ///
136 /// # Panics
137 /// * if length of `vec` is not a multiple of `width`
138 /// ```rust,should_panic
139 /// use podch::Stone::*;
140 /// let vec = vec![None, Dark, Light, Dark, None, Light, Dark]; // vec.len() == 7
141 /// let board = podch::VecBoard::from_vec(vec, 3);
142 /// ```
143 pub fn from_vec(vec: Vec<Stone>, width: usize) -> Self {
144 if vec.len() % width != 0 {
145 panic!("Square number = {} is not a multiple of width = {}", vec.len(), width);
146 }
147 Self {
148 vec,
149 width,
150 }
151 }
152}
153
154impl Board for VecBoard {
155 fn size(&self) -> (usize, usize) {
156 (self.vec.len() / self.width, self.width)
157 }
158
159 fn new(height: usize, width: usize) -> Self {
160 Self {
161 vec: vec![Stone::None; height * width],
162 width,
163 }
164 }
165
166 fn get(&self, row: usize, col: usize) -> Stone {
167 self.vec[row * self.width + col]
168 }
169
170 fn set(&mut self, row: usize, col: usize, val: Stone) {
171 self.vec[row * self.width + col] = val;
172 }
173}
174
175/// Board using height*width/4 bytes of memory
176#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
177pub struct BinBoard {
178 vec: Vec<u8>,
179 width: usize,
180}
181
182impl BinBoard {
183 /// Construct board from concatenation of rows with `width`
184 ///
185 /// # Examples
186 /// ```
187 /// use podch::{Board, Stone::*};
188 /// let mut iter = (0..6).map(|x| podch::Stone::from(x % 3));
189 /// let board = podch::BinBoard::from_iter(&mut iter, 3);
190 /// assert_eq!(board.size(), (2, 3));
191 /// assert_eq!(podch::BoardDisplay::from(&board).to_string(), "012\n012");
192 /// let vec = vec![None, Dark, Light, Dark, None, Light];
193 /// let mut iter = vec.iter().map(|s| *s);
194 /// let board = podch::BinBoard::from_iter(&mut iter, 3);
195 /// assert_eq!(podch::BoardDisplay::from(&board).to_string(), "012\n102");
196 /// ```
197 ///
198 /// # Panics
199 /// * if length of `iter` is not a multiple of `width`
200 /// ```rust,should_panic
201 /// use podch::Stone::*;
202 /// let vec = vec![None, Dark, Light, Dark, None, Light, Dark]; // vec.len() == 7
203 /// let board = podch::BinBoard::from_iter(&mut vec.iter().map(|s| *s), 3);
204 /// ```
205 pub fn from_iter<T: Iterator<Item=Stone>>(iter: &mut T, width: usize) -> Self {
206 let mut vec = Vec::new();
207 let mut i = 4usize;
208 let mut len = 0usize;
209 for stone in iter {
210 if i > 3 {
211 vec.push(0);
212 i = 0;
213 }
214 *vec.last_mut().unwrap() |= u8::shl(stone.into(), i * 2);
215 i += 1;
216 len += 1;
217 }
218 if len % width != 0 {
219 panic!("Square number = {} is not a multiple of width = {}", vec.len(), width);
220 }
221 while i < 4 {
222 *vec.last_mut().unwrap() |= 3 << i * 2;
223 i += 1;
224 }
225 Self {
226 vec,
227 width,
228 }
229 }
230}
231
232impl Board for BinBoard {
233 fn size(&self) -> (usize, usize) {
234 let mut nulls = 0usize;
235 while (self.vec.last().unwrap() >> (3 - nulls) * 2) & 3 == 3 {
236 nulls += 1;
237 }
238 ((self.vec.len() * 4 - nulls) / self.width, self.width)
239 }
240
241 fn new(height: usize, width: usize) -> Self {
242 let mut vec = vec![Stone::None.into(); (height * width + 3) / 4];
243 if height * width % 4 != 0 {
244 *vec.last_mut().unwrap() = 0xffu8;
245 for i in 0..height * width % 4 {
246 *vec.last_mut().unwrap() ^= 3 << (i * 2);
247 }
248 }
249 Self {
250 vec,
251 width,
252 }
253 }
254
255 fn get(&self, row: usize, col: usize) -> Stone {
256 let i = row * self.width + col;
257 ((self.vec[i / 4] >> i % 4 * 2) & 3).into()
258 }
259
260 fn set(&mut self, row: usize, col: usize, val: Stone) {
261 let i = row * self.width + col;
262 assert_ne!((self.vec[i / 4] >> i % 4 * 2) & 3, 3);
263 self.vec[i / 4] &= u8::MAX ^ 3 << i % 4 * 2;
264 self.vec[i / 4] |= u8::shl(val.into(), i % 4 * 2);
265 }
266}
267
268impl<T: Board> From<&T> for BinBoard {
269 fn from(board: &T) -> Self {
270 let (height, width) = board.size();
271 let mut this = BinBoard::new(height, width);
272 let mut i = 0;
273 for x in 0..height {
274 for y in 0..width {
275 this.vec[i / 4] |= u8::shl(board.get(x, y).into(), i % 4 * 2);
276 i += 1;
277 }
278 }
279 this
280 }
281}
282
283/// Mirror of a board with a few modifications
284///
285/// # Examples
286/// ```
287/// use podch::{Stone::*, Board};
288/// let board = podch::VecBoard::from_vec(vec![None, Dark, Light, Dark, Light, None], 3);
289/// let mut edited = podch::EditedBoard::new(&board, 0, 1, None);
290/// assert_eq!(podch::BoardDisplay::from(&edited).to_string(), "002\n120");
291/// edited.set(1, 2, Dark);
292/// assert_eq!(edited.get(1, 2), Dark);
293/// assert_eq!(edited.get(0, 1), None);
294/// let mut copy = podch::EditedBoard::from(&board);
295/// assert_eq!(podch::BoardDisplay::from(©).to_string(), podch::BoardDisplay::from(&board).to_string());
296/// copy.set(0, 1, None);
297/// assert_ne!(copy.get(0, 1), board.get(0, 1));
298/// ```
299#[derive(Debug, Clone)]
300pub struct EditedBoard<'a, B: Board> {
301 board: &'a B,
302 map: HashMap<(usize, usize), Stone>,
303}
304
305impl<'a, B: Board> EditedBoard<'a, B> {
306 /// Construct mirror of `board` with stone `val` in `row` and `col`umn
307 ///
308 /// # Panics
309 /// * if either index is out of bounds, i.e. `row` >= height OR `col` >= width
310 /// ```rust,should_panic
311 /// use podch::{Stone::*, Board};
312 /// let board = podch::VecBoard::from_vec(vec![Dark, Light, None, None, Dark, Light], 3);
313 /// let mut edited = podch::EditedBoard::new(&board, 2, 0, Dark);
314 /// ```
315 pub fn new(board: &'a B, row: usize, col: usize, val: Stone) -> Self {
316 if row >= board.size().0 || col >= board.size().1 {
317 panic!("Square {}, {} is out of bounds", row, col);
318 }
319 Self {
320 board,
321 map: HashMap::from([((row, col), val)]),
322 }
323 }
324}
325
326impl<'a, B: Board> From<&'a B> for EditedBoard<'a, B> {
327 fn from(board: &'a B) -> Self {
328 Self {
329 board,
330 map: HashMap::new(),
331 }
332 }
333}
334
335impl<B: Board> Board for EditedBoard<'_, B> {
336 fn size(&self) -> (usize, usize) {
337 self.board.size()
338 }
339
340 fn new(_height: usize, _width: usize) -> Self {
341 unimplemented!()
342 }
343
344 fn get(&self, row: usize, col: usize) -> Stone {
345 match self.map.get(&(row, col)) {
346 None => self.board.get(row, col),
347 Some(x) => *x,
348 }
349 }
350
351 fn set(&mut self, row: usize, col: usize, val: Stone) {
352 if row >= self.size().0 || col >= self.size().1 {
353 panic!("Square {}, {} is out of bounds", row, col);
354 }
355 self.map.insert((row, col), val);
356 }
357}