Skip to main content

mnk_games/
board.rs

1use std::error::Error;
2use std::ops::Not;
3use std::{fmt, iter};
4
5/// One of two players.
6#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
7pub enum Player {
8    /// The player who makes the first move.
9    X,
10    /// The player who makes the second move.
11    O,
12}
13
14impl fmt::Display for Player {
15    /// Writes `"X"` for [`Player::X`] and `"O"` for [`Player::O`].
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        match *self {
18            Self::X => write!(f, "X"),
19            Self::O => write!(f, "O"),
20        }
21    }
22}
23
24impl Not for Player {
25    type Output = Self;
26
27    fn not(self) -> Self::Output {
28        match self {
29            Self::X => Self::O,
30            Self::O => Self::X,
31        }
32    }
33}
34
35/// A space that can be played on by a [`Player`].
36#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
37pub enum Space {
38    /// A space that has not been played on yet.
39    #[default]
40    Empty,
41    /// A space that has been taken by the indicated [`Player`].
42    Stone(Player),
43}
44
45impl fmt::Display for Space {
46    /// Writes a space character for [`Space::Empty`] and the player name for a [`Space::Stone`].
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match *self {
49            Self::Empty => write!(f, " "),
50            Self::Stone(player) => write!(f, "{player}"),
51        }
52    }
53}
54
55impl From<Option<Player>> for Space {
56    /// Maps [`None`] and [`Some`] to [`Space::Empty`] and [`Space::Stone`], respectively.
57    fn from(player: Option<Player>) -> Self {
58        player.map_or(Self::Empty, Self::Stone)
59    }
60}
61
62impl From<Space> for Option<Player> {
63    /// Maps [`Space::Empty`] and [`Space::Stone`] to [`None`] and [`Some`],
64    /// respectively.
65    fn from(space: Space) -> Self {
66        match space {
67            Space::Empty => None,
68            Space::Stone(player) => Some(player),
69        }
70    }
71}
72
73/// An error which can occur when trying to place a stone.
74#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
75#[non_exhaustive]
76pub enum PlaceError {
77    /// An error which can occur when the location already contains a [`Space::Stone`].
78    Occupied {
79        /// The player who owns the blocking [`Space::Stone`].
80        player: Player,
81    },
82    /// An error which can occur when the intended location is not within the board's bounds.
83    OutOfBounds {
84        /// The intended (potentially out-of-bounds) row.
85        row: usize,
86        /// The intended (potentially out-of-bounds) column.
87        column: usize,
88    },
89}
90
91impl fmt::Display for PlaceError {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        match self {
94            Self::Occupied { player } => {
95                write!(f, "already occupied by {player}")
96            }
97            Self::OutOfBounds { row, column } => {
98                write!(f, "out of bounds (row {row}, column {column})")
99            }
100        }
101    }
102}
103
104impl Error for PlaceError {}
105
106/// A game board with `R` rows and `C` columns of [`Space`]s.
107///
108/// Methods for this struct are 0-indexed. Row indices at least `R` and column indices at least `C`
109/// are considered out of bounds.
110///
111/// This struct performs very little input validation. It is intended to be wrapped by other types
112/// that perform more thorough validation based on a particular game's rules, not used in
113/// user-facing code directly.
114#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
115pub struct MnkBoard<const R: usize, const C: usize> {
116    row_array: [[Space; C]; R],
117}
118
119impl<const R: usize, const C: usize> MnkBoard<R, C> {
120    /// Returns a board filled with [`Space::Empty`].
121    #[must_use]
122    pub const fn new() -> Self {
123        Self {
124            row_array: [[Space::Empty; C]; R],
125        }
126    }
127
128    /// Returns `true` if every [`Space`] on the board is a [`Space::Stone`] and `false` otherwise.
129    #[must_use]
130    pub fn full(&self) -> bool {
131        self.row_array
132            .iter()
133            .all(|row| row.iter().all(|space| space != &Space::Empty))
134    }
135
136    /// Attempts to place a stone on the board.
137    ///
138    /// If and only if the [`Space`] at the specified row and column is [`Space::Empty`], replaces
139    /// it with a [`Space::Stone`] corresponding to `player`.
140    ///
141    /// # Errors
142    ///
143    ///  - [`PlaceError::Occupied`] if the corresponding `Space` is already a `Space::Stone`.
144    ///  - [`PlaceError::OutOfBounds`] if either index is out of bounds.
145    pub fn place(&mut self, player: Player, row: usize, column: usize) -> Result<(), PlaceError> {
146        let location = self
147            .row_array
148            .get_mut(row)
149            .and_then(|row| row.get_mut(column));
150        location.map_or(
151            Err(PlaceError::OutOfBounds { row, column }),
152            |space| match space {
153                Space::Stone(player) => Err(PlaceError::Occupied { player: *player }),
154                Space::Empty => {
155                    *space = Space::Stone(player);
156                    Ok(())
157                }
158            },
159        )
160    }
161
162    /// Place a stone on the board without bounds or overlap checking.
163    ///
164    /// Places a new [`Space::Stone`] even if the [`Space`] is already one. [`MnkBoard::place`] is
165    /// a safe alternative.
166    ///
167    /// # Safety
168    ///
169    /// Both `row` and `column` must be in bounds.
170    pub unsafe fn place_unchecked(&mut self, player: Player, row: usize, column: usize) {
171        let location;
172        unsafe {
173            location = self
174                .row_array
175                .get_unchecked_mut(row)
176                .get_unchecked_mut(column);
177        }
178        *location = Space::Stone(player);
179    }
180
181    /// Returns the [`Space`] at the specified row and column.
182    ///
183    /// Returns [`None`] if either index is out of bounds.
184    #[must_use]
185    pub fn get(&self, row: usize, column: usize) -> Option<&Space> {
186        self.row_array.get(row).and_then(|row| row.get(column))
187    }
188
189    /// Returns the [`Space`] at the specified row and column, without checking bounds.
190    ///
191    /// [`MnkBoard::get`] is a safe alternative.
192    ///
193    /// # Safety
194    ///
195    /// Both `row` and `column` must be in bounds.
196    #[must_use]
197    pub unsafe fn get_unchecked(&self, row: usize, column: usize) -> &Space {
198        unsafe { self.row_array.get_unchecked(row).get_unchecked(column) }
199    }
200
201    /// Converts (row, column) pairs to their corresponding [`Space`]s.
202    ///
203    /// # Panics
204    ///
205    /// If a coordinate pair is out of bounds.
206    pub(crate) fn coords_to_spaces(
207        &self,
208        coords: impl Iterator<Item = (usize, usize)>,
209    ) -> impl Iterator<Item = &'_ Space> {
210        coords.map(move |(r, c)| &self.row_array[r][c])
211    }
212
213    /// Returns an [`Iterator`] over the rows of the board.
214    pub(crate) fn rows(&self) -> impl Iterator<Item = impl Iterator<Item = &'_ Space>> {
215        self.row_array.iter().map(|row| row.iter())
216    }
217
218    /// Returns an [`Iterator`] over the columns of the board.
219    pub(crate) fn columns(&self) -> impl Iterator<Item = impl Iterator<Item = &'_ Space>> {
220        (0..C).map(move |c| self.row_array.iter().map(move |row| &row[c]))
221    }
222
223    /// Returns an [`Iterator`] over diagonals that start at the top and move right.
224    ///
225    /// Only iterates over diagonals of length at least `must_use`.
226    pub(crate) fn top_right_diagonals(
227        &self,
228        min_length: usize,
229    ) -> impl Iterator<Item = impl Iterator<Item = &'_ Space>> {
230        (0..=(C - min_length))
231            .map(move |left_col| self.coords_to_spaces(iter::zip(0..R, left_col..C)))
232    }
233
234    /// Returns an [`Iterator`] over diagonals that start on the left and move down.
235    ///
236    /// Only iterates over diagonals of length at least `must_use`. Skips the highest such diagonal.
237    /// (This avoids overlap with [`MnkBoard::top_right_diagonals`].)
238    pub(crate) fn left_down_diagonals(
239        &self,
240        min_length: usize,
241    ) -> impl Iterator<Item = impl Iterator<Item = &'_ Space>> {
242        (1..=(R - min_length))
243            .map(move |top_row| self.coords_to_spaces(iter::zip(top_row..R, 0..C)))
244    }
245
246    /// Returns an [`Iterator`] over the diagonals that start at the top and move left.
247    ///
248    /// Only iterates over diagonals of length at least `must_use`.
249    pub(crate) fn top_left_diagonals(
250        &self,
251        min_length: usize,
252    ) -> impl Iterator<Item = impl Iterator<Item = &'_ Space>> {
253        ((min_length - 1)..C)
254            .map(move |last_col| self.coords_to_spaces(iter::zip(0..R, (0..=last_col).rev())))
255    }
256
257    /// Returns an [`Iterator`] over the diagonals that start on the right and move down.
258    ///
259    /// Only iterates over diagonals of length at least `must_use`. Skips the highest such diagonal.
260    /// (This avoids overlap with [`MnkBoard::top_left_diagonals`].)
261    pub(crate) fn right_down_diagonals(
262        &self,
263        min_length: usize,
264    ) -> impl Iterator<Item = impl Iterator<Item = &'_ Space>> {
265        (1..=(R - min_length))
266            .map(move |last_row| self.coords_to_spaces(iter::zip(last_row..R, (0..C).rev())))
267    }
268}
269
270impl<const R: usize, const C: usize> Default for MnkBoard<R, C> {
271    /// Returns a board filled with [`Space::Empty`].
272    fn default() -> Self {
273        Self::new()
274    }
275}
276
277impl<const R: usize, const C: usize> From<[[Space; C]; R]> for MnkBoard<R, C> {
278    /// Converts a row-major array into an `MnkBoard`.
279    fn from(rows: [[Space; C]; R]) -> Self {
280        Self { row_array: rows }
281    }
282}
283
284impl<const R: usize, const C: usize> From<MnkBoard<R, C>> for [[Space; C]; R] {
285    /// Converts an `MnkBoard` into a row-major array.
286    fn from(game: MnkBoard<R, C>) -> Self {
287        game.row_array
288    }
289}
290
291impl<const R: usize, const C: usize> fmt::Display for MnkBoard<R, C> {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        let border = "+-".repeat(C) + "+";
294        let vertical_sep = "\n".to_owned() + &border + "\n";
295        let middle_rows = self
296            .row_array
297            .map(|row| format!("|{}|", row.map(|square| square.to_string()).join("|")))
298            .join(&vertical_sep);
299        write!(f, "{border}\n{middle_rows}\n{border}")
300    }
301}
302
303#[cfg(test)]
304mod test_placers {
305    use super::*;
306
307    #[test]
308    fn place_success() {
309        let mut empty = MnkBoard::<2, 2>::new();
310
311        let top_left = empty.place(Player::X, 0, 0);
312        assert_eq!(top_left, Ok(()));
313        assert_eq!(
314            empty.row_array,
315            [
316                [Space::Stone(Player::X), Space::Empty],
317                [Space::Empty, Space::Empty]
318            ]
319        );
320
321        let top_right = empty.place(Player::O, 0, 1);
322        assert_eq!(top_right, Ok(()));
323        assert_eq!(
324            empty.row_array,
325            [
326                [Space::Stone(Player::X), Space::Stone(Player::O)],
327                [Space::Empty, Space::Empty]
328            ]
329        );
330
331        let bottom_left = empty.place(Player::O, 1, 0);
332        assert_eq!(bottom_left, Ok(()));
333        assert_eq!(
334            empty.row_array,
335            [
336                [Space::Stone(Player::X), Space::Stone(Player::O)],
337                [Space::Stone(Player::O), Space::Empty]
338            ]
339        );
340
341        let bottom_right = empty.place(Player::X, 1, 1);
342        assert_eq!(bottom_right, Ok(()));
343        assert_eq!(
344            empty.row_array,
345            [
346                [Space::Stone(Player::X), Space::Stone(Player::O)],
347                [Space::Stone(Player::O), Space::Stone(Player::X)]
348            ]
349        );
350    }
351
352    #[test]
353    fn place_occupied() {
354        let mut full = MnkBoard::from([
355            [Space::Stone(Player::X), Space::Stone(Player::O)],
356            [Space::Stone(Player::O), Space::Stone(Player::X)],
357        ]);
358
359        let top_left_x = full.place(Player::X, 0, 0);
360        assert_eq!(top_left_x, Err(PlaceError::Occupied { player: Player::X }));
361        let top_left_o = full.place(Player::O, 0, 0);
362        assert_eq!(top_left_o, Err(PlaceError::Occupied { player: Player::X }));
363
364        let top_right_x = full.place(Player::X, 0, 1);
365        assert_eq!(top_right_x, Err(PlaceError::Occupied { player: Player::O }));
366        let top_right_o = full.place(Player::O, 0, 1);
367        assert_eq!(top_right_o, Err(PlaceError::Occupied { player: Player::O }));
368
369        let bottom_left_x = full.place(Player::X, 1, 0);
370        assert_eq!(
371            bottom_left_x,
372            Err(PlaceError::Occupied { player: Player::O })
373        );
374        let bottom_left_o = full.place(Player::O, 1, 0);
375        assert_eq!(
376            bottom_left_o,
377            Err(PlaceError::Occupied { player: Player::O })
378        );
379
380        let bottom_right_x = full.place(Player::X, 1, 1);
381        assert_eq!(
382            bottom_right_x,
383            Err(PlaceError::Occupied { player: Player::X })
384        );
385        let bottom_right_o = full.place(Player::O, 1, 1);
386        assert_eq!(
387            bottom_right_o,
388            Err(PlaceError::Occupied { player: Player::X })
389        );
390    }
391
392    #[test]
393    fn place_out_of_bounds() {
394        let mut empty: MnkBoard<2, 2> = MnkBoard::new();
395
396        let high_row_x = empty.place(Player::X, 2, 0);
397        assert_eq!(
398            high_row_x,
399            Err(PlaceError::OutOfBounds { row: 2, column: 0 })
400        );
401        let high_row_o = empty.place(Player::O, 2, 0);
402        assert_eq!(
403            high_row_o,
404            Err(PlaceError::OutOfBounds { row: 2, column: 0 })
405        );
406
407        let high_column_x = empty.place(Player::X, 0, 2);
408        assert_eq!(
409            high_column_x,
410            Err(PlaceError::OutOfBounds { row: 0, column: 2 })
411        );
412        let high_column_o = empty.place(Player::O, 0, 2);
413        assert_eq!(
414            high_column_o,
415            Err(PlaceError::OutOfBounds { row: 0, column: 2 })
416        );
417    }
418
419    #[test]
420    fn place_unchecked_empty() {
421        let mut empty = MnkBoard::<2, 2>::new();
422
423        unsafe {
424            empty.place_unchecked(Player::X, 0, 0);
425        }
426        assert_eq!(
427            empty.row_array,
428            [
429                [Space::Stone(Player::X), Space::Empty],
430                [Space::Empty, Space::Empty]
431            ]
432        );
433
434        unsafe {
435            empty.place_unchecked(Player::O, 0, 1);
436        }
437        assert_eq!(
438            empty.row_array,
439            [
440                [Space::Stone(Player::X), Space::Stone(Player::O)],
441                [Space::Empty, Space::Empty]
442            ]
443        );
444
445        unsafe {
446            empty.place_unchecked(Player::O, 1, 0);
447        }
448        assert_eq!(
449            empty.row_array,
450            [
451                [Space::Stone(Player::X), Space::Stone(Player::O)],
452                [Space::Stone(Player::O), Space::Empty]
453            ]
454        );
455
456        unsafe {
457            empty.place_unchecked(Player::X, 1, 1);
458        }
459        assert_eq!(
460            empty.row_array,
461            [
462                [Space::Stone(Player::X), Space::Stone(Player::O)],
463                [Space::Stone(Player::O), Space::Stone(Player::X)]
464            ]
465        );
466    }
467
468    #[test]
469    fn place_unchecked_occupied() {
470        let mut all_x = MnkBoard::from([
471            [Space::Stone(Player::X), Space::Stone(Player::X)],
472            [Space::Stone(Player::X), Space::Stone(Player::X)],
473        ]);
474
475        unsafe {
476            all_x.place_unchecked(Player::O, 0, 0);
477        }
478        assert_eq!(
479            all_x.row_array,
480            [
481                [Space::Stone(Player::O), Space::Stone(Player::X)],
482                [Space::Stone(Player::X), Space::Stone(Player::X)],
483            ]
484        );
485
486        unsafe {
487            all_x.place_unchecked(Player::O, 0, 1);
488        }
489        assert_eq!(
490            all_x.row_array,
491            [
492                [Space::Stone(Player::O), Space::Stone(Player::O)],
493                [Space::Stone(Player::X), Space::Stone(Player::X)],
494            ]
495        );
496
497        unsafe {
498            all_x.place_unchecked(Player::O, 1, 0);
499        }
500        assert_eq!(
501            all_x.row_array,
502            [
503                [Space::Stone(Player::O), Space::Stone(Player::O)],
504                [Space::Stone(Player::O), Space::Stone(Player::X)],
505            ]
506        );
507
508        unsafe {
509            all_x.place_unchecked(Player::O, 1, 1);
510        }
511        assert_eq!(
512            all_x.row_array,
513            [
514                [Space::Stone(Player::O), Space::Stone(Player::O)],
515                [Space::Stone(Player::O), Space::Stone(Player::O)],
516            ]
517        );
518    }
519}
520
521#[cfg(test)]
522mod test_getters {
523    use super::*;
524
525    fn square() -> MnkBoard<2, 2> {
526        MnkBoard::from([
527            [Space::Stone(Player::X), Space::Empty],
528            [Space::Empty, Space::Stone(Player::O)],
529        ])
530    }
531
532    #[test]
533    fn get_in_bounds() {
534        let board = square();
535
536        assert_eq!(board.get(0, 0), Some(&Space::Stone(Player::X)));
537        assert_eq!(board.get(0, 1), Some(&Space::Empty));
538        assert_eq!(board.get(1, 0), Some(&Space::Empty));
539        assert_eq!(board.get(1, 1), Some(&Space::Stone(Player::O)));
540    }
541
542    #[test]
543    fn get_out_of_bounds() {
544        let board = square();
545
546        assert_eq!(board.get(2, 0), None);
547        assert_eq!(board.get(0, 2), None);
548    }
549
550    #[test]
551    fn get_unchecked() {
552        let board = square();
553
554        let top_left;
555        let top_right;
556        let bottom_left;
557        let bottom_right;
558        unsafe {
559            top_left = board.get_unchecked(0, 0);
560            top_right = board.get_unchecked(0, 1);
561            bottom_left = board.get_unchecked(1, 0);
562            bottom_right = board.get_unchecked(1, 1);
563        }
564        assert_eq!(top_left, &Space::Stone(Player::X));
565        assert_eq!(top_right, &Space::Empty);
566        assert_eq!(bottom_left, &Space::Empty);
567        assert_eq!(bottom_right, &Space::Stone(Player::O));
568    }
569}
570
571#[cfg(test)]
572mod test_square_board {
573    // These tests use `Vec::contains` for durability against changes in iteration order.
574    use super::*;
575
576    fn square_board() -> MnkBoard<5, 5> {
577        MnkBoard::from([
578            [
579                Space::Empty,
580                Space::Stone(Player::X),
581                Space::Stone(Player::O),
582                Space::Empty,
583                Space::Stone(Player::X),
584            ],
585            [
586                Space::Stone(Player::X),
587                Space::Stone(Player::O),
588                Space::Empty,
589                Space::Stone(Player::X),
590                Space::Stone(Player::O),
591            ],
592            [
593                Space::Stone(Player::O),
594                Space::Empty,
595                Space::Stone(Player::X),
596                Space::Stone(Player::O),
597                Space::Empty,
598            ],
599            [
600                Space::Stone(Player::O),
601                Space::Stone(Player::X),
602                Space::Empty,
603                Space::Stone(Player::O),
604                Space::Stone(Player::X),
605            ],
606            [
607                Space::Stone(Player::X),
608                Space::Stone(Player::O),
609                Space::Empty,
610                Space::Stone(Player::O),
611                Space::Stone(Player::X),
612            ],
613        ])
614    }
615
616    #[test]
617    fn rows() {
618        let board = square_board();
619        let rows: Vec<Vec<&Space>> = board.rows().map(Iterator::collect).collect();
620        assert_eq!(rows.len(), 5);
621
622        let top_row = vec![
623            &Space::Empty,
624            &Space::Stone(Player::X),
625            &Space::Stone(Player::O),
626            &Space::Empty,
627            &Space::Stone(Player::X),
628        ];
629        assert!(rows.contains(&top_row));
630
631        let second_row = vec![
632            &Space::Stone(Player::X),
633            &Space::Stone(Player::O),
634            &Space::Empty,
635            &Space::Stone(Player::X),
636            &Space::Stone(Player::O),
637        ];
638        assert!(rows.contains(&second_row));
639
640        let third_row = vec![
641            &Space::Stone(Player::O),
642            &Space::Empty,
643            &Space::Stone(Player::X),
644            &Space::Stone(Player::O),
645            &Space::Empty,
646        ];
647        assert!(rows.contains(&third_row));
648
649        let fourth_row = vec![
650            &Space::Stone(Player::O),
651            &Space::Stone(Player::X),
652            &Space::Empty,
653            &Space::Stone(Player::O),
654            &Space::Stone(Player::X),
655        ];
656        assert!(rows.contains(&fourth_row));
657
658        let fifth_row = vec![
659            &Space::Stone(Player::X),
660            &Space::Stone(Player::O),
661            &Space::Empty,
662            &Space::Stone(Player::O),
663            &Space::Stone(Player::X),
664        ];
665        assert!(rows.contains(&fifth_row));
666    }
667
668    #[test]
669    fn columns() {
670        let board = square_board();
671        let columns: Vec<Vec<&Space>> = board.columns().map(Iterator::collect).collect();
672        assert_eq!(columns.len(), 5);
673
674        let first_col = vec![
675            &Space::Empty,
676            &Space::Stone(Player::X),
677            &Space::Stone(Player::O),
678            &Space::Stone(Player::O),
679            &Space::Stone(Player::X),
680        ];
681        assert!(columns.contains(&first_col));
682
683        let second_col = vec![
684            &Space::Stone(Player::X),
685            &Space::Stone(Player::O),
686            &Space::Empty,
687            &Space::Stone(Player::X),
688            &Space::Stone(Player::O),
689        ];
690        assert!(columns.contains(&second_col));
691
692        let third_col = vec![
693            &Space::Stone(Player::O),
694            &Space::Empty,
695            &Space::Stone(Player::X),
696            &Space::Empty,
697            &Space::Empty,
698        ];
699        assert!(columns.contains(&third_col));
700
701        let fourth_col = vec![
702            &Space::Empty,
703            &Space::Stone(Player::X),
704            &Space::Stone(Player::O),
705            &Space::Stone(Player::O),
706            &Space::Stone(Player::O),
707        ];
708        assert!(columns.contains(&fourth_col));
709
710        let fifth_col = vec![
711            &Space::Stone(Player::X),
712            &Space::Stone(Player::O),
713            &Space::Empty,
714            &Space::Stone(Player::X),
715            &Space::Stone(Player::X),
716        ];
717        assert!(columns.contains(&fifth_col));
718    }
719
720    #[test]
721    fn top_right() {
722        let board = square_board();
723        let diags: Vec<Vec<&Space>> = board
724            .top_right_diagonals(3)
725            .map(Iterator::collect)
726            .collect();
727        assert_eq!(diags.len(), 3);
728
729        let first_diag = vec![
730            &Space::Empty,
731            &Space::Stone(Player::O),
732            &Space::Stone(Player::X),
733            &Space::Stone(Player::O),
734            &Space::Stone(Player::X),
735        ];
736        assert!(diags.contains(&first_diag));
737
738        let second_diag = vec![
739            &Space::Stone(Player::X),
740            &Space::Empty,
741            &Space::Stone(Player::O),
742            &Space::Stone(Player::X),
743        ];
744        assert!(diags.contains(&second_diag));
745
746        let third_diag = vec![
747            &Space::Stone(Player::O),
748            &Space::Stone(Player::X),
749            &Space::Empty,
750        ];
751        assert!(diags.contains(&third_diag));
752    }
753
754    #[test]
755    fn left_down() {
756        let board = square_board();
757        let diags: Vec<Vec<&Space>> = board
758            .left_down_diagonals(3)
759            .map(Iterator::collect)
760            .collect();
761        assert_eq!(diags.len(), 2);
762
763        let first_diag = vec![
764            &Space::Stone(Player::X),
765            &Space::Empty,
766            &Space::Empty,
767            &Space::Stone(Player::O),
768        ];
769        assert!(diags.contains(&first_diag));
770
771        let second_diag = vec![
772            &Space::Stone(Player::O),
773            &Space::Stone(Player::X),
774            &Space::Empty,
775        ];
776        assert!(diags.contains(&second_diag));
777    }
778
779    #[test]
780    fn top_left() {
781        let board = square_board();
782        let diags: Vec<Vec<&Space>> = board.top_left_diagonals(3).map(Iterator::collect).collect();
783        assert_eq!(diags.len(), 3);
784
785        let first_diag = vec![
786            &Space::Stone(Player::O),
787            &Space::Stone(Player::O),
788            &Space::Stone(Player::O),
789        ];
790        assert!(diags.contains(&first_diag));
791        let second_diag = vec![
792            &Space::Empty,
793            &Space::Empty,
794            &Space::Empty,
795            &Space::Stone(Player::O),
796        ];
797        assert!(diags.contains(&second_diag));
798
799        let third_diag = vec![
800            &Space::Stone(Player::X),
801            &Space::Stone(Player::X),
802            &Space::Stone(Player::X),
803            &Space::Stone(Player::X),
804            &Space::Stone(Player::X),
805        ];
806        assert!(diags.contains(&third_diag));
807    }
808
809    #[test]
810    fn right_down() {
811        let board = square_board();
812        let diags: Vec<Vec<&Space>> = board
813            .right_down_diagonals(3)
814            .map(Iterator::collect)
815            .collect();
816        assert_eq!(diags.len(), 2);
817
818        let first_diag = vec![
819            &Space::Stone(Player::O),
820            &Space::Stone(Player::O),
821            &Space::Empty,
822            &Space::Stone(Player::O),
823        ];
824        assert!(diags.contains(&first_diag));
825
826        let second_diag = vec![&Space::Empty, &Space::Stone(Player::O), &Space::Empty];
827        assert!(diags.contains(&second_diag));
828    }
829}
830
831#[cfg(test)]
832mod test_rectangular_boards {
833    use super::*;
834
835    fn tall_board() -> MnkBoard<5, 4> {
836        MnkBoard::from([
837            [
838                Space::Empty,
839                Space::Stone(Player::X),
840                Space::Stone(Player::O),
841                Space::Empty,
842            ],
843            [
844                Space::Stone(Player::X),
845                Space::Stone(Player::O),
846                Space::Empty,
847                Space::Stone(Player::X),
848            ],
849            [
850                Space::Stone(Player::O),
851                Space::Empty,
852                Space::Stone(Player::X),
853                Space::Stone(Player::O),
854            ],
855            [
856                Space::Stone(Player::O),
857                Space::Stone(Player::X),
858                Space::Empty,
859                Space::Stone(Player::O),
860            ],
861            [
862                Space::Stone(Player::X),
863                Space::Stone(Player::O),
864                Space::Empty,
865                Space::Stone(Player::O),
866            ],
867        ])
868    }
869
870    fn wide_board() -> MnkBoard<4, 5> {
871        MnkBoard::from([
872            [
873                Space::Empty,
874                Space::Stone(Player::X),
875                Space::Stone(Player::O),
876                Space::Empty,
877                Space::Stone(Player::X),
878            ],
879            [
880                Space::Stone(Player::X),
881                Space::Stone(Player::O),
882                Space::Empty,
883                Space::Stone(Player::X),
884                Space::Stone(Player::O),
885            ],
886            [
887                Space::Stone(Player::O),
888                Space::Empty,
889                Space::Stone(Player::X),
890                Space::Stone(Player::O),
891                Space::Empty,
892            ],
893            [
894                Space::Stone(Player::O),
895                Space::Stone(Player::X),
896                Space::Empty,
897                Space::Stone(Player::O),
898                Space::Stone(Player::X),
899            ],
900        ])
901    }
902
903    #[test]
904    fn tall_top_right_diags() {
905        let board = tall_board();
906        let diags: Vec<Vec<&Space>> = board
907            .top_right_diagonals(3)
908            .map(Iterator::collect)
909            .collect();
910        assert_eq!(diags.len(), 2);
911
912        let first_diag = vec![
913            &Space::Empty,
914            &Space::Stone(Player::O),
915            &Space::Stone(Player::X),
916            &Space::Stone(Player::O),
917        ];
918        assert!(diags.contains(&first_diag));
919
920        let second_diag = vec![
921            &Space::Stone(Player::X),
922            &Space::Empty,
923            &Space::Stone(Player::O),
924        ];
925        assert!(diags.contains(&second_diag));
926    }
927
928    #[test]
929    fn tall_left_down_diags() {
930        let board = tall_board();
931        let diags: Vec<Vec<&Space>> = board
932            .left_down_diagonals(3)
933            .map(Iterator::collect)
934            .collect();
935        assert_eq!(diags.len(), 2);
936
937        let first_diag = vec![
938            &Space::Stone(Player::X),
939            &Space::Empty,
940            &Space::Empty,
941            &Space::Stone(Player::O),
942        ];
943        assert!(diags.contains(&first_diag));
944
945        let second_diag = vec![
946            &Space::Stone(Player::O),
947            &Space::Stone(Player::X),
948            &Space::Empty,
949        ];
950        assert!(diags.contains(&second_diag));
951    }
952
953    #[test]
954    fn tall_top_left_diags() {
955        let board = tall_board();
956        let diags: Vec<Vec<&Space>> = board.top_left_diagonals(3).map(Iterator::collect).collect();
957        assert_eq!(diags.len(), 2);
958
959        let first_diag = vec![
960            &Space::Empty,
961            &Space::Empty,
962            &Space::Empty,
963            &Space::Stone(Player::O),
964        ];
965        assert!(diags.contains(&first_diag));
966
967        let second_diag = vec![
968            &Space::Stone(Player::O),
969            &Space::Stone(Player::O),
970            &Space::Stone(Player::O),
971        ];
972        assert!(diags.contains(&second_diag));
973    }
974
975    #[test]
976    fn tall_right_down_diags() {
977        let board = tall_board();
978        let diags: Vec<Vec<&Space>> = board
979            .right_down_diagonals(3)
980            .map(Iterator::collect)
981            .collect();
982        assert_eq!(diags.len(), 2);
983
984        let first_diag = vec![
985            &Space::Stone(Player::X),
986            &Space::Stone(Player::X),
987            &Space::Stone(Player::X),
988            &Space::Stone(Player::X),
989        ];
990        assert!(diags.contains(&first_diag));
991
992        let second_diag = vec![
993            &Space::Stone(Player::O),
994            &Space::Empty,
995            &Space::Stone(Player::O),
996        ];
997        assert!(diags.contains(&second_diag));
998    }
999
1000    #[test]
1001    fn wide_top_right_diags() {
1002        let board = wide_board();
1003        let diags: Vec<Vec<&Space>> = board
1004            .top_right_diagonals(3)
1005            .map(Iterator::collect)
1006            .collect();
1007        assert_eq!(diags.len(), 3);
1008
1009        let first_diag = vec![
1010            &Space::Empty,
1011            &Space::Stone(Player::O),
1012            &Space::Stone(Player::X),
1013            &Space::Stone(Player::O),
1014        ];
1015        assert!(diags.contains(&first_diag));
1016
1017        let second_diag = vec![
1018            &Space::Stone(Player::X),
1019            &Space::Empty,
1020            &Space::Stone(Player::O),
1021            &Space::Stone(Player::X),
1022        ];
1023        assert!(diags.contains(&second_diag));
1024
1025        let third_diag = vec![
1026            &Space::Stone(Player::O),
1027            &Space::Stone(Player::X),
1028            &Space::Empty,
1029        ];
1030        assert!(diags.contains(&third_diag));
1031    }
1032
1033    #[test]
1034    fn wide_left_down_diags() {
1035        let board = wide_board();
1036        let diags: Vec<Vec<&Space>> = board
1037            .left_down_diagonals(3)
1038            .map(Iterator::collect)
1039            .collect();
1040        let diag = vec![&Space::Stone(Player::X), &Space::Empty, &Space::Empty];
1041        assert_eq!(diags, [diag]);
1042    }
1043
1044    #[test]
1045    fn wide_top_left_diags() {
1046        let board = wide_board();
1047        let diags: Vec<Vec<&Space>> = board.top_left_diagonals(3).map(Iterator::collect).collect();
1048        assert_eq!(diags.len(), 3);
1049
1050        let first_diag = vec![
1051            &Space::Stone(Player::X),
1052            &Space::Stone(Player::X),
1053            &Space::Stone(Player::X),
1054            &Space::Stone(Player::X),
1055        ];
1056        assert!(diags.contains(&first_diag));
1057
1058        let second_diag = vec![
1059            &Space::Empty,
1060            &Space::Empty,
1061            &Space::Empty,
1062            &Space::Stone(Player::O),
1063        ];
1064        assert!(diags.contains(&second_diag));
1065
1066        let third_diag = vec![
1067            &Space::Stone(Player::O),
1068            &Space::Stone(Player::O),
1069            &Space::Stone(Player::O),
1070        ];
1071        assert!(diags.contains(&third_diag));
1072    }
1073
1074    #[test]
1075    fn wide_right_up_diags() {
1076        let board = wide_board();
1077        let diags: Vec<Vec<&Space>> = board
1078            .right_down_diagonals(3)
1079            .map(Iterator::collect)
1080            .collect();
1081        let diag = vec![
1082            &Space::Stone(Player::O),
1083            &Space::Stone(Player::O),
1084            &Space::Empty,
1085        ];
1086        assert_eq!(diags, [diag]);
1087    }
1088}
1089
1090#[cfg(test)]
1091mod test_mnk_board_display {
1092    use super::*;
1093
1094    #[test]
1095    fn squares() {
1096        let one = MnkBoard::from([[Space::Stone(Player::X)]]);
1097        assert_eq!(
1098            one.to_string(),
1099            "+-+\n\
1100             |X|\n\
1101             +-+"
1102        );
1103
1104        let two = MnkBoard::from([
1105            [Space::Stone(Player::X), Space::Empty],
1106            [Space::Empty, Space::Stone(Player::O)],
1107        ]);
1108        assert_eq!(
1109            two.to_string(),
1110            "+-+-+\n\
1111             |X| |\n\
1112             +-+-+\n\
1113             | |O|\n\
1114             +-+-+"
1115        );
1116
1117        let three = MnkBoard::from([
1118            [
1119                Space::Stone(Player::X),
1120                Space::Empty,
1121                Space::Stone(Player::O),
1122            ],
1123            [
1124                Space::Stone(Player::O),
1125                Space::Stone(Player::X),
1126                Space::Empty,
1127            ],
1128            [
1129                Space::Stone(Player::X),
1130                Space::Stone(Player::O),
1131                Space::Empty,
1132            ],
1133        ]);
1134        assert_eq!(
1135            three.to_string(),
1136            "+-+-+-+\n\
1137             |X| |O|\n\
1138             +-+-+-+\n\
1139             |O|X| |\n\
1140             +-+-+-+\n\
1141             |X|O| |\n\
1142             +-+-+-+"
1143        );
1144    }
1145
1146    #[test]
1147    fn rectangles() {
1148        let tall = MnkBoard::from([
1149            [Space::Stone(Player::X), Space::Empty],
1150            [Space::Empty, Space::Stone(Player::O)],
1151            [Space::Stone(Player::X), Space::Stone(Player::O)],
1152        ]);
1153        assert_eq!(
1154            tall.to_string(),
1155            "+-+-+\n\
1156             |X| |\n\
1157             +-+-+\n\
1158             | |O|\n\
1159             +-+-+\n\
1160             |X|O|\n\
1161             +-+-+"
1162        );
1163
1164        let wide = MnkBoard::from([
1165            [
1166                Space::Stone(Player::X),
1167                Space::Empty,
1168                Space::Stone(Player::X),
1169            ],
1170            [
1171                Space::Empty,
1172                Space::Stone(Player::O),
1173                Space::Stone(Player::O),
1174            ],
1175        ]);
1176        assert_eq!(
1177            wide.to_string(),
1178            "+-+-+-+\n\
1179             |X| |X|\n\
1180             +-+-+-+\n\
1181             | |O|O|\n\
1182             +-+-+-+"
1183        );
1184    }
1185}