diff --git a/src/connect4/board.rs b/src/connect4/board.rs index 9a41333..572fa87 100644 --- a/src/connect4/board.rs +++ b/src/connect4/board.rs @@ -4,10 +4,14 @@ use crate::{Player, State}; type Position = Option; -const BOARD_WIDTH: usize = 7; -const BOARD_HEIGHT: usize = 4; -const BOARD_POSITIONS: usize = BOARD_WIDTH * BOARD_HEIGHT; +const WIDTH: usize = 7; +const HEIGTH: usize = 4; +const BOARD_POSITIONS: usize = WIDTH * HEIGTH; +/// 0 1 2 3 4 5 6 +/// 7 8 9 10 11 12 13 +/// 14 15 16 17 18 19 20 +/// 21 22 23 24 25 26 27 pub struct Board { positions: [Position; BOARD_POSITIONS], } @@ -20,7 +24,14 @@ impl Board { } pub fn result(&self) -> State { - self.check_board() + match self.check_board() { + State::Winner(winner) => State::Winner(winner), + State::InProgress if self.positions.iter().all(|position| position.is_some()) => { + State::Draw + } + State::InProgress => State::InProgress, + State::Draw => unreachable!("check_board cannot tell a draw"), + } } fn check_board(&self) -> State { @@ -30,15 +41,15 @@ impl Board { } fn check_columns(&self) -> State { - for i in 0..BOARD_WIDTH { - self.check_four(i, i + BOARD_WIDTH, i + 2 * BOARD_WIDTH, i + 3 * BOARD_WIDTH)?; + for i in 0..WIDTH { + self.check_four(i, i + WIDTH, i + 2 * WIDTH, i + 3 * WIDTH)?; } State::InProgress } fn check_rows(&self) -> State { - for row_start in 0..BOARD_HEIGHT { + for row_start in 0..HEIGTH { for offset in 0..4 { let start = row_start + offset; self.check_four(start, start + 1, start + 2, start + 3)?; @@ -49,6 +60,17 @@ impl Board { } fn check_diagonals(&self) -> State { + // */* + for start in 3..WIDTH { + const DIFF: usize = WIDTH - 1; + self.check_four(start, start + DIFF, start + 2 * DIFF, start + 3 * DIFF)?; + } + + // *\* + for start in 0..4 { + const DIFF: usize = WIDTH + 1; + self.check_four(start, start + DIFF, start + 2 * DIFF, start + 3 * DIFF)?; + } State::InProgress } @@ -73,3 +95,74 @@ impl Index for Board { &self.positions[index] } } + +#[cfg(test)] +mod tests { + use crate::{Player, State}; + + use super::Board; + + fn parse_board(board: &str) -> Board { + let positions = board + .chars() + .filter(|char| !char.is_whitespace()) + .map(|char| match char { + 'X' => Some(Player::X), + 'O' => Some(Player::O), + '_' => None, + char => panic!("Invalid char in board: `{char}`"), + }) + .collect::>() + .try_into() + .expect(&format!( + "not enough positions provided: {}", + board.chars().filter(|c| !c.is_whitespace()).count() + )); + + Board { positions } + } + + fn test(board: &str, state: State) { + let board = parse_board(board); + assert_eq!(board.result(), state); + } + + #[test] + fn draw() { + test( + " + XOOOXOX + XOOOXOX + OXXXOXO + XOOOXXX + ", + State::Draw, + ); + } + + #[test] + fn full_winner() { + test( + " + XOOOXOX + XOOOXOX + OXXXOXO + XOOOXOX + ", + State::Winner(Player::O), + ); + } + + #[test] + fn three_rows() { + test( + " + XXX_OOO + _XXX___ + X_OOO__ + OOO____ + ", + State::InProgress, + ); + } +}