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(&copy).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}