bit_board/
bitboardstatic.rs

1use std::fmt;
2use std::ops::{BitAndAssign, BitOrAssign};
3
4use bitvec::prelude::*;
5
6use crate::{DimensionMismatch, bitboard::BitBoard};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct BitBoardStatic<const W: usize> {
10    /// The statically sized array of `W` words.
11    board: BitArray<[usize; W]>,
12
13    /// How many rows does the board have
14    pub n_rows: usize,
15
16    /// How many columns does the board have
17    pub n_cols: usize,
18}
19
20impl<const W: usize> fmt::Display for BitBoardStatic<W> {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        // column indices
23        write!(f, "   ")?; // space for row labels
24        for col in 0..self.n_cols {
25            write!(f, "{}", col % 10)?; // wrap every 10 for readability
26        }
27        writeln!(f)?;
28
29        for row in 0..self.n_rows {
30            // row index, right-aligned to 2 spaces
31            write!(f, "{:>2} ", row)?;
32            for col in 0..self.n_cols {
33                let idx = row * self.n_cols + col;
34                let bit = self.board[idx];
35                let c = if bit { 'X' } else { '.' };
36                write!(f, "{}", c)?;
37            }
38            writeln!(f)?;
39        }
40        Ok(())
41    }
42}
43
44// W is the number of usize integers needed to hold the board
45impl<const W: usize> BitBoardStatic<W> {
46    pub fn new(n_rows: usize, n_cols: usize) -> Self {
47        // Make sure it fits in the allotted size
48        let total_bits = n_rows * n_cols;
49        let available_bits = W * (usize::BITS as usize);
50        assert!(
51            total_bits <= available_bits,
52            "The number of bits required by the board ({} * {}) exceeds the allocated storage ({} bits).",
53            n_rows,
54            n_cols,
55            available_bits
56        );
57
58        Self {
59            board: BitArray::default(),
60            n_rows,
61            n_cols,
62        }
63    }
64}
65
66impl<const W: usize> BitBoard for BitBoardStatic<W> {
67    fn n_rows(&self) -> usize {
68        self.n_rows
69    }
70
71    fn n_cols(&self) -> usize {
72        self.n_cols
73    }
74
75    fn board_mut(&mut self) -> &mut BitSlice {
76        &mut self.board
77    }
78
79    fn board(&self) -> &BitSlice {
80        &self.board
81    }
82
83    /// Performs a bitwise OR operation between two bitboards.
84    fn or(&self, other: &impl BitBoard) -> Result<Self, DimensionMismatch> {
85        if (self.n_rows() != other.n_rows()) || (self.n_cols() != other.n_cols()) {
86            return Err(DimensionMismatch);
87        }
88
89        let mut result = *self;
90        result.board_mut().bitor_assign(other.board());
91        Ok(result)
92    }
93
94    /// Performs a bitwise AND operation between two bitboards.
95    fn and(&self, other: &impl BitBoard) -> Result<Self, DimensionMismatch> {
96        if (self.n_rows() != other.n_rows()) || (self.n_cols() != other.n_cols()) {
97            return Err(DimensionMismatch);
98        }
99
100        let mut result = *self;
101        result.board_mut().bitand_assign(other.board());
102        Ok(result)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108
109    use super::*;
110
111    use rstest::rstest;
112
113    #[test]
114    fn can_construct() {
115        let bb = BitBoardStatic::<1>::new(2, 2);
116        println!("{:?}", bb);
117    }
118
119    #[test]
120    #[should_panic(expected = "row cannot be greater than n_rows")]
121    fn row_too_big() {
122        let bb = BitBoardStatic::<1>::new(2, 2);
123        bb.index_of(10, 0);
124    }
125
126    #[test]
127    #[should_panic(expected = "col cannot be greater than n_col")]
128    fn col_too_big() {
129        let bb = BitBoardStatic::<1>::new(2, 2);
130        bb.index_of(0, 10);
131    }
132
133    #[rstest]
134    #[case(0, 0, 0)]
135    #[case(0, 1, 1)]
136    #[case(1, 0, 2)]
137    #[case(1, 1, 3)]
138    fn index_of(#[case] row: usize, #[case] col: usize, #[case] expected: usize) {
139        let bb = BitBoardStatic::<1>::new(2, 2);
140
141        assert_eq!(expected, bb.index_of(row, col))
142    }
143
144    #[rstest]
145    #[case(0, 0, 0)]
146    #[case(1, 0, 1)]
147    #[case(2, 0, 2)]
148    #[case(3, 0, 3)]
149    #[case(4, 0, 4)]
150    fn col_vec_index_of(#[case] row: usize, #[case] col: usize, #[case] expected: usize) {
151        let bb = BitBoardStatic::<1>::new(5, 1);
152
153        assert_eq!(expected, bb.index_of(row, col))
154    }
155
156    #[rstest]
157    #[case(0, 0, 0)]
158    #[case(0, 1, 1)]
159    #[case(0, 2, 2)]
160    #[case(0, 3, 3)]
161    #[case(0, 4, 4)]
162    fn row_vec_index_of(#[case] row: usize, #[case] col: usize, #[case] expected: usize) {
163        let bb = BitBoardStatic::<1>::new(1, 5);
164
165        assert_eq!(expected, bb.index_of(row, col))
166    }
167
168    #[rstest]
169    #[case(0)]
170    #[case(1)]
171    #[case(2)]
172    #[case(3)]
173    #[case(4)]
174    fn set_col(#[case] col: usize) {
175        let mut bb = BitBoardStatic::<1>::new(5, 5);
176        bb.set_col(col, true);
177
178        for ridx in 0..bb.n_rows {
179            for cidx in 0..bb.n_cols {
180                if cidx == col {
181                    assert!(bb.board[bb.index_of(ridx, cidx)])
182                } else {
183                    assert!(!bb.board[bb.index_of(ridx, cidx)])
184                }
185            }
186        }
187    }
188
189    #[rstest]
190    #[case(0)]
191    #[case(1)]
192    #[case(2)]
193    #[case(3)]
194    #[case(4)]
195    fn set_row(#[case] row: usize) {
196        let mut bb = BitBoardStatic::<1>::new(5, 5);
197        bb.set_row(row, true);
198
199        for ridx in 0..bb.n_rows {
200            for cidx in 0..bb.n_cols {
201                if ridx == row {
202                    assert!(bb.board[bb.index_of(ridx, cidx)])
203                } else {
204                    assert!(!bb.board[bb.index_of(ridx, cidx)])
205                }
206            }
207        }
208    }
209
210    #[test]
211    fn can_set_all_bits() {
212        // Create the board
213        let nr = 3;
214        let nc = 3;
215        let mut bb = BitBoardStatic::<1>::new(nr, nc);
216        bb.set(0, 0, true);
217
218        // Set each bit, and check that all bits are 1
219        for ridx in 0..nr {
220            for cidx in 0..nc {
221                bb.set(ridx, cidx, true);
222            }
223        }
224
225        assert!(bb.board[..nr * nc].all());
226
227        // Unset each bit, and check that all bits are 0
228        for ridx in 0..nr {
229            for cidx in 0..nc {
230                bb.set(ridx, cidx, false);
231            }
232        }
233
234        assert!(bb.board[..nr * nc].not_any());
235    }
236
237    #[rstest]
238    #[case(0, 0, bitvec![0, 1, 1, 0])]
239    #[case(0, 1, bitvec![1, 0, 0, 1])]
240    #[case(1, 0, bitvec![1, 0, 0, 1])]
241    #[case(1, 1, bitvec![0, 1, 1, 0])]
242    fn set_caridnal_neighbors_2x2(
243        #[case] row: usize,
244
245        #[case] col: usize,
246
247        #[case] expect_bv: BitVec,
248    ) {
249        let mut bb = BitBoardStatic::<1>::new(2, 2);
250        bb.set_cardinal_neighbors(row, col, true);
251
252        let mut expect_board = BitArray::<[usize; 1]>::default();
253        expect_board[..expect_bv.len()].copy_from_bitslice(&expect_bv);
254
255        let expect = BitBoardStatic::<1> {
256            board: expect_board,
257            n_rows: 2,
258            n_cols: 2,
259        };
260
261        assert_eq!(expect, bb);
262    }
263
264    #[rstest]
265    #[case(0, 0, bitvec![0, 1, 0, 1, 0, 0, 0, 0, 0])]
266    #[case(0, 1, bitvec![1, 0, 1, 0, 1, 0, 0, 0, 0])]
267    #[case(0, 2, bitvec![0, 1, 0, 0, 0, 1, 0, 0, 0])]
268    #[case(1, 0, bitvec![1, 0, 0, 0, 1, 0, 1, 0, 0])]
269    #[case(1, 1, bitvec![0, 1, 0, 1, 0, 1, 0, 1, 0])]
270    #[case(1, 2, bitvec![0, 0, 1, 0, 1, 0, 0, 0, 1])]
271    #[case(2, 0, bitvec![0, 0, 0, 1, 0, 0, 0, 1, 0])]
272    #[case(2, 1, bitvec![0, 0, 0, 0, 1, 0, 1, 0, 1])]
273    #[case(2, 2, bitvec![0, 0, 0, 0, 0, 1, 0, 1, 0])]
274    fn set_caridnal_neighbors_3x3(
275        #[case] row: usize,
276        #[case] col: usize,
277        #[case] expect_bv: BitVec,
278    ) {
279        let mut bb = BitBoardStatic::<1>::new(3, 3);
280        bb.set_cardinal_neighbors(row, col, true);
281
282        let mut expect_board = BitArray::<[usize; 1]>::default();
283        expect_board[..expect_bv.len()].copy_from_bitslice(&expect_bv);
284
285        let expect = BitBoardStatic::<1> {
286            board: expect_board,
287            n_rows: 3,
288            n_cols: 3,
289        };
290
291        assert_eq!(expect, bb);
292    }
293
294    #[rstest]
295    #[case(0, 0, bitvec![0, 1, 1, 1])]
296    #[case(0, 1, bitvec![1, 0, 1, 1])]
297    #[case(1, 0, bitvec![1, 1, 0, 1])]
298    #[case(1, 1, bitvec![1, 1, 1, 0])]
299    fn set_all_neighbors_2x2(#[case] row: usize, #[case] col: usize, #[case] expect_bv: BitVec) {
300        let mut bb = BitBoardStatic::<1>::new(2, 2);
301        bb.set_all_neighbors(row, col, true);
302
303        let mut expect_board = BitArray::<[usize; 1]>::default();
304        expect_board[..expect_bv.len()].copy_from_bitslice(&expect_bv);
305
306        let expect = BitBoardStatic::<1> {
307            board: expect_board,
308            n_rows: 2,
309            n_cols: 2,
310        };
311
312        assert_eq!(expect, bb);
313    }
314
315    #[rstest]
316    #[case(0, 0, bitvec![0, 1, 0, 1, 1, 0, 0, 0, 0])]
317    #[case(0, 1, bitvec![1, 0, 1, 1, 1, 1, 0, 0, 0])]
318    #[case(0, 2, bitvec![0, 1, 0, 0, 1, 1, 0, 0, 0])]
319    #[case(1, 0, bitvec![1, 1, 0, 0, 1, 0, 1, 1, 0])]
320    #[case(1, 1, bitvec![1, 1, 1, 1, 0, 1, 1, 1, 1])]
321    #[case(1, 2, bitvec![0, 1, 1, 0, 1, 0, 0, 1, 1])]
322    #[case(2, 0, bitvec![0, 0, 0, 1, 1, 0, 0, 1, 0])]
323    #[case(2, 1, bitvec![0, 0, 0, 1, 1, 1, 1, 0, 1])]
324    #[case(2, 2, bitvec![0, 0, 0, 0, 1, 1, 0, 1, 0])]
325    fn set_all_neighbors_3x3(#[case] row: usize, #[case] col: usize, #[case] expect_bv: BitVec) {
326        let mut bb = BitBoardStatic::<1>::new(3, 3);
327        bb.set_all_neighbors(row, col, true);
328
329        let mut expect_board = BitArray::<[usize; 1]>::default();
330        expect_board[..expect_bv.len()].copy_from_bitslice(&expect_bv);
331
332        let expect = BitBoardStatic::<1> {
333            board: expect_board,
334            n_rows: 3,
335            n_cols: 3,
336        };
337
338        assert_eq!(expect, bb);
339    }
340
341    #[rstest]
342    #[case(1, 1, 1, 2)]
343    #[case(2, 1, 1, 2)]
344    #[case(2, 1, 2, 7)]
345    fn and_dimension_mismatch(
346        #[case] b1r: usize,
347        #[case] b1c: usize,
348        #[case] b2r: usize,
349        #[case] b2c: usize,
350    ) {
351        let bb1 = BitBoardStatic::<1>::new(b1r, b1c);
352        let bb8 = BitBoardStatic::<1>::new(b2r, b2c);
353        assert!(bb1.and(&bb8).is_err());
354    }
355
356    #[rstest]
357    #[case(1, 1, 1, 2)]
358    #[case(2, 1, 1, 2)]
359    #[case(2, 1, 2, 7)]
360    fn or_dimension_mismatch(
361        #[case] b1r: usize,
362        #[case] b1c: usize,
363        #[case] b2r: usize,
364        #[case] b2c: usize,
365    ) {
366        let bb1 = BitBoardStatic::<1>::new(b1r, b1c);
367        let bb8 = BitBoardStatic::<1>::new(b2r, b2c);
368        assert!(bb1.or(&bb8).is_err());
369    }
370
371    #[rstest]
372    #[case(bitvec![0, 0, 0, 0], bitvec![0, 0, 0, 0], bitvec![0, 0, 0, 0])] // empty AND empty
373    #[case(bitvec![1, 1, 1, 1], bitvec![1, 1, 1, 1], bitvec![1, 1, 1, 1])] // full AND full
374    #[case(bitvec![0, 0, 0, 0], bitvec![1, 1, 1, 1], bitvec![0, 0, 0, 0])] // empty AND full
375    #[case(bitvec![1, 1, 1, 1], bitvec![1, 0, 0, 1], bitvec![1, 0, 0, 1])] // full AND partial
376    #[case(bitvec![1, 0, 1, 0], bitvec![0, 1, 0, 1], bitvec![0, 0, 0, 0])] // alternating patterns
377    #[case(bitvec![1, 1, 0, 0], bitvec![1, 0, 1, 0], bitvec![1, 0, 0, 0])] // partial patterns
378    fn and_operations(
379        #[case] board1_bv: BitVec,
380        #[case] board2_bv: BitVec,
381        #[case] expected: BitVec,
382    ) {
383        let mut board1_arr = BitArray::<[usize; 1]>::default();
384        board1_arr[..board1_bv.len()].copy_from_bitslice(&board1_bv);
385        let bb1 = BitBoardStatic::<1> {
386            board: board1_arr,
387            n_rows: 2,
388            n_cols: 2,
389        };
390
391        let mut board2_arr = BitArray::<[usize; 1]>::default();
392        board2_arr[..board2_bv.len()].copy_from_bitslice(&board2_bv);
393
394        let bb2 = BitBoardStatic::<1> {
395            board: board2_arr,
396            n_rows: 2,
397            n_cols: 2,
398        };
399
400        let result = bb1.and(&bb2).unwrap();
401        assert_eq!(result.board()[..expected.len()].to_bitvec(), expected);
402        assert_eq!(result.n_rows(), 2);
403        assert_eq!(result.n_cols(), 2);
404    }
405
406    #[rstest]
407    #[case(bitvec![0, 0, 0, 0], bitvec![0, 0, 0, 0], bitvec![0, 0, 0, 0])] // empty OR empty
408    #[case(bitvec![1, 1, 1, 1], bitvec![1, 1, 1, 1], bitvec![1, 1, 1, 1])] // full OR full
409    #[case(bitvec![0, 0, 0, 0], bitvec![1, 1, 1, 1], bitvec![1, 1, 1, 1])] // empty OR full
410    #[case(bitvec![0, 0, 0, 0], bitvec![1, 0, 0, 1], bitvec![1, 0, 0, 1])] // empty OR partial
411    #[case(bitvec![1, 0, 1, 0], bitvec![0, 1, 0, 1], bitvec![1, 1, 1, 1])] // alternating patterns
412    #[case(bitvec![1, 1, 0, 0], bitvec![0, 0, 1, 1], bitvec![1, 1, 1, 1])] // complementary patterns
413    #[case(bitvec![1, 0, 0, 1], bitvec![0, 1, 1, 0], bitvec![1, 1, 1, 1])] // diagonal patterns
414    fn or_operations(
415        #[case] board1_bv: BitVec,
416
417        #[case] board2_bv: BitVec,
418
419        #[case] expected: BitVec,
420    ) {
421        let mut board1_arr = BitArray::<[usize; 1]>::default();
422        board1_arr[..board1_bv.len()].copy_from_bitslice(&board1_bv);
423
424        let bb1 = BitBoardStatic::<1> {
425            board: board1_arr,
426            n_rows: 2,
427            n_cols: 2,
428        };
429
430        let mut board2_arr = BitArray::<[usize; 1]>::default();
431        board2_arr[..board2_bv.len()].copy_from_bitslice(&board2_bv);
432        let bb2 = BitBoardStatic::<1> {
433            board: board2_arr,
434            n_rows: 2,
435            n_cols: 2,
436        };
437
438        let result = bb1.or(&bb2).unwrap();
439        assert_eq!(result.board()[..expected.len()].to_bitvec(), expected);
440        assert_eq!(result.n_rows(), 2);
441        assert_eq!(result.n_cols(), 2);
442    }
443
444    #[test]
445    fn and_or_larger_boards() {
446        let mut bb1 = BitBoardStatic::<1>::new(3, 3);
447        bb1.set_row(0, true); // First row all true
448        bb1.set(2, 2, true); // Bottom right corner
449
450        let mut bb2 = BitBoardStatic::<1>::new(3, 3);
451        bb2.set_col(0, true); // First column all true
452        bb2.set(1, 1, true); // Center
453
454        // Test AND operation
455
456        let and_result = bb1.and(&bb2).unwrap();
457        assert!(and_result.board()[0]); // (0,0) - both have true
458        assert!(!and_result.board()[1]); // (0,1) - only bb1 has true
459        assert!(!and_result.board()[2]); // (0,2) - only bb1 has true
460        assert!(!and_result.board()[3]); // (1,0) - only bb2 has true
461        assert!(!and_result.board()[4]); // (1,1) - only bb2 has true
462        assert!(!and_result.board()[6]); // (2,0) - only bb2 has true
463
464        // Test OR operation
465
466        let or_result = bb1.or(&bb2).unwrap();
467        assert!(or_result.board()[0]); // (0,0) - both have true
468        assert!(or_result.board()[1]); // (0,1) - bb1 has true
469        assert!(or_result.board()[2]); // (0,2) - bb1 has true
470        assert!(or_result.board()[3]); // (1,0) - bb2 has true
471        assert!(or_result.board()[4]); // (1,1) - bb2 has true
472        assert!(!or_result.board()[5]); // (1,2) - neither has true
473        assert!(or_result.board()[6]); // (2,0) - bb2 has true
474        assert!(!or_result.board()[7]); // (2,1) - neither has true
475        assert!(or_result.board()[8]); // (2,2) - bb1 has true
476    }
477
478    #[test]
479    fn and_or_preserve_original_boards() {
480        let mut bb1 = BitBoardStatic::<1>::new(2, 2);
481        bb1.set(0, 0, true);
482        let bb1_original = bb1;
483
484        let mut bb2 = BitBoardStatic::<1>::new(2, 2);
485        bb2.set(1, 1, true);
486        let bb2_original = bb2;
487
488        // Perform operations
489        let _and_result = bb1.and(&bb2).unwrap();
490        let _or_result = bb1.or(&bb2).unwrap();
491
492        // Original boards should be unchanged
493        assert_eq!(bb1, bb1_original);
494        assert_eq!(bb2, bb2_original);
495    }
496}