diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5c7247b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,7 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [] +} \ No newline at end of file diff --git a/src/board.rs b/src/board.rs index ceaa702..75d427b 100644 --- a/src/board.rs +++ b/src/board.rs @@ -11,6 +11,14 @@ pub struct Board { impl fmt::Display for Board { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let horiz_sep_line = "-".repeat(BOARD_SIZE * 2 + 1); + + // Print numbers at top so I can read the board easier + write!(f, " ")?; + for j in 0..BOARD_SIZE { + write!(f, "{} ", j)?; + } + writeln!(f)?; + for i in 0..BOARD_SIZE { writeln!(f, " {}", horiz_sep_line)?; @@ -24,12 +32,26 @@ impl fmt::Display for Board { } writeln!(f)?; } - // put a line at the bottom of the board too - writeln!(f, "{}", horiz_sep_line)?; + writeln!(f, " {}", horiz_sep_line)?; + + // Print the current score + let (white_score, black_score) = self.get_score(); + writeln!(f, "White Score: {}\nBlack Score: {}", white_score, black_score); + + // Print game over screen + if self.game_over() == true { + match self.get_winner() { + Some(Piece::Black) => writeln!(f, "Black Wins"), + Some(Piece::White) => writeln!(f, "White Wins"), + None => writeln!(f, "Tie"), + }? + } Ok(()) } + + } impl Board { @@ -160,6 +182,56 @@ impl Board { } captured } + + // keep score of each color + pub fn get_score(&self) -> (usize, usize) { + let mut white_score = 0; + let mut black_score = 0; + + for row in &self.board { + for &cell in row { + match cell { + Some(Piece::White) => white_score += 1, + Some(Piece::Black) => black_score += 1, + _ => {} + } + } + } + (white_score, black_score) + } + + // Get the winning piece (for game over screen mainly) + pub fn get_winner(&self) -> Option { + let (white_score, black_score) = self.get_score(); + + // White wins + if white_score > black_score { + Some(Piece::White) + } + + // Black Wins + else if black_score > white_score { + Some(Piece::Black) + } + + // Tie + else { + None + } + } + + pub fn game_over(&self) -> bool { + let (white_score, black_score) = self.get_score(); + let max_score = BOARD_SIZE * BOARD_SIZE; + let combined_score = black_score + white_score; + + if max_score == combined_score { + true + } + else { + false + } + } } #[cfg(test)] @@ -262,4 +334,132 @@ mod test { assert_eq!(board.place(2, 5, Piece::Black), Ok(()), "{}", board); } -} + + // Test corner capture from top-left corner + #[test] + fn corner_capture_top_left() { + let mut board = Board::new(); + + // Black pieces at (0, 0) and (1, 0), and (0, 1) to create capture opportunity + board.place_unchecked(0, 0, Piece::Black); + board.place_unchecked(1, 0, Piece::White); + board.place_unchecked(0, 1, Piece::White); + + board.propegate_from(0, 0); + + // Capture white piece at (0, 1) and (1, 0) + assert_eq!(board.get(0, 1), &Some(Piece::Black)); + assert_eq!(board.get(1, 0), &Some(Piece::Black)); + } + + // Test corner capture from top-right corner + #[test] + fn corner_capture_top_right() { + let mut board = Board::new(); + + // Black pieces at (0, 7) and (1, 7), and (0, 6) to create capture opportunity + board.place_unchecked(0, 7, Piece::Black); + board.place_unchecked(1, 7, Piece::White); + board.place_unchecked(0, 6, Piece::White); + + board.propegate_from(0, 7); + + // Capture white piece at (0, 6) and (1, 7) + assert_eq!(board.get(0, 6), &Some(Piece::Black)); + assert_eq!(board.get(1, 7), &Some(Piece::Black)); + } + + // Test corner capture from bottom-left corner + #[test] + fn corner_capture_bottom_left() { + let mut board = Board::new(); + + // Black pieces at (7, 0) and (6, 0), and (7, 1) to create capture opportunity + board.place_unchecked(7, 0, Piece::Black); + board.place_unchecked(6, 0, Piece::White); + board.place_unchecked(7, 1, Piece::White); + + board.propegate_from(7, 0); + + // Capture white piece at (7, 1) and (6, 0) + assert_eq!(board.get(7, 1), &Some(Piece::Black)); + assert_eq!(board.get(6, 0), &Some(Piece::Black)); + } + + // Test corner capture from bottom-right corner + #[test] + fn corner_capture_bottom_right() { + let mut board = Board::new(); + + // Black pieces at (7, 7) and (6, 7), and (7, 6) to create capture opportunity + board.place_unchecked(7, 7, Piece::Black); + board.place_unchecked(6, 7, Piece::White); + board.place_unchecked(7, 6, Piece::White); + + board.propegate_from(7, 7); + + // Capture white piece at (7, 6) and (6, 7) + assert_eq!(board.get(7, 6), &Some(Piece::Black)); + assert_eq!(board.get(6, 7), &Some(Piece::Black)); + } + + // Test capture from top-left corner (horizontal) + #[test] + fn capture_top_left_horiz() { + let mut board = Board::new(); + + // Create a scenario where a capture should happen horizontally from (0, 0) + board.place_unchecked(0, 0, Piece::Black); + board.place_unchecked(0, 1, Piece::White); + board.place_unchecked(0, 2, Piece::Black); + + board.propegate_from(0, 2); + + assert_eq!(board.get(0, 1), &Some(Piece::Black)); + } + + // Test capture from top-right corner (horizontal) + #[test] + fn capture_top_right_horiz() { + let mut board = Board::new(); + + // Create a scenario where a capture should happen horizontally from (0, 7) + board.place_unchecked(0, 7, Piece::Black); + board.place_unchecked(0, 6, Piece::White); + board.place_unchecked(0, 5, Piece::Black); + + board.propegate_from(0, 5); + + assert_eq!(board.get(0, 6), &Some(Piece::Black)); + } + + // Test capture from top-left corner (vertical) + #[test] + fn capture_top_left_vert() { + let mut board = Board::new(); + + // Create a scenario where a capture should happen vertically from (0, 0) + board.place_unchecked(0, 0, Piece::Black); + board.place_unchecked(1, 0, Piece::White); + board.place_unchecked(2, 0, Piece::Black); + + board.propegate_from(2, 0); + + assert_eq!(board.get(1, 0), &Some(Piece::Black)); + } + + // Test capture from bottom-left corner (vertical) + #[test] + fn capture_bottom_left_vert() { + let mut board = Board::new(); + + // Create a scenario where a capture should happen vertically from (7, 0) + board.place_unchecked(7, 0, Piece::Black); + board.place_unchecked(6, 0, Piece::White); + board.place_unchecked(5, 0, Piece::Black); + + board.propegate_from(5, 0); + + assert_eq!(board.get(6, 0), &Some(Piece::Black)); + } +} \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index e47247d..53fa0cf 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,5 +1,4 @@ use crate::{agent::Agent, board::Board}; -use std::time::Duration; pub struct Game { players: [Box; 2], @@ -29,31 +28,59 @@ impl Game { } } + // Handle when a move is made pub fn step(&mut self, player_i: usize) { - let player_move = self.players[player_i].next_move(&self.board); - if let Some((i, j)) = player_move { - if let Ok(()) = self.board.place(i, j, self.players[player_i].color()) { + let player_color = self.players[player_i].color(); + + loop { + let player_move = self.players[player_i].next_move(&self.board); + + if let Some((i, j)) = player_move { + match self.board.place(i, j, player_color) { + // Check if its a valid move + Ok(_) => { + println!("Player {} placed at ({}, {})", player_i, i, j); + break; + } + + // Lets the player try again if the move is invalid + // Now we dont need to restart the game if we mess up + Err(err) => { + println!("Invalid move by Player {}: {}. Try again.", player_i, err); + } + } } else { - todo!("handle invalid player move"); + println!("Player {} did not make a move!", player_i); + return; // No valid move available } - } else { - panic!("player {} did not make a move", player_i); } } // TODO! make this loop good pub fn game_loop(&mut self) { - let mut i = 0; + let mut current_player: usize = 0; + loop { // alternate which player plays - i += 1; - i %= self.players.len(); + current_player += 1; + current_player %= self.players.len(); println!("{}", self); - self.step(i); + // Check if the game is over + if self.board.game_over() == true { + break; + } + + self.step(current_player); // std::thread::sleep(Duration::from_millis(200)); } + // Print Game Over Screen + println!("{}", self); + match self.board.get_winner() { + Some(winner) => println!("Game Over! {} Wins!", winner.text()), + None => print!("It's a tie!! You either both suck or are both really good..."), + } } }