From 80738dd217f5b384e561fce8d86c1b1c0ca80547 Mon Sep 17 00:00:00 2001 From: "Elijah C. Voigt" Date: Thu, 2 May 2024 23:17:25 -0700 Subject: [PATCH] Merging moves implemented ish At least on the backend. Not sure they're rendered. God bless test driven development, amirite? --- src/game.rs | 251 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 169 insertions(+), 82 deletions(-) diff --git a/src/game.rs b/src/game.rs index 4b0c786..6e9eab0 100644 --- a/src/game.rs +++ b/src/game.rs @@ -290,11 +290,12 @@ impl From<(usize, usize)> for BoardIndex { } } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub(crate) enum MoveType { Valid, Invalid, Capture, + Merge, } #[derive(Debug, Component, PartialEq, Clone, Copy, Eq, Hash, Default)] @@ -366,14 +367,10 @@ impl Board { fn new() -> Board { Board::from_ascii( - r#"...###.. - ####q### - ...###.. - ..#.#.#."#, - // r#".....dqq - // dpp..pdq - // qdp..ppd - // qqd....."#, + r#".....dqq + dpp..pdq + qdp..ppd + qqd....."#, ) } @@ -513,92 +510,93 @@ impl Board { /// Determine given a piece, a to, and a from, what type of move this would be pub(crate) fn move_type( &self, - piece: Piece, from: BoardIndex, to: BoardIndex, ) -> Option { - // Iterate over the piece's moves - piece - .moves_at(&from) - .iter() - // Find if the given `to` move is one of those - .find(|idx| to == **idx) - // Determine if this is valid/legal in this situation - .and_then(|_| { - let dest_at = self.at(to); - let curr_side = Board::side(from).unwrap(); - let dest_side = Board::side(to).unwrap(); - - match (curr_side, dest_side) { - (Side::A, Side::A) | (Side::B, Side::B) => { - match dest_at { - // Cannot move on top of a friendly - Some(_) => Some(MoveType::Invalid), - // Any other spot is valid - None => { - // If there is another piece between A and B - if self - .line(from, to) - .any(|board_index| self.at(board_index).is_some()) - { - // Invalid move, there is a piece between A and B - Some(MoveType::Invalid) - } else { - // Otherwise it's a valid - Some(MoveType::Valid) + self.at(from).map(|piece| { + // Given that the side does not have a queen||drone + // And the piece is a drone||pawn + // We can do field promotions + let side = Board::side(from).expect("Piece has valid index"); + let side_has_queen = self.on(side).iter().any(|(piece, _)| **piece == Piece::Queen); + let side_has_drone = self.on(side).iter().any(|(piece, _)| **piece == Piece::Drone); + + // Iterate over the piece's moves + piece + .moves_at(&from) + .iter() + // Find if the given `to` move is one of those + .find(|idx| to == **idx) + // Determine if this is valid/legal in this situation + .and_then(|_| { + let dest_at = self.at(to); + let curr_side = Board::side(from).unwrap(); + let dest_side = Board::side(to).unwrap(); + + match (curr_side, dest_side) { + (Side::A, Side::A) | (Side::B, Side::B) => { + match dest_at { + // Cannot move on top of a friendly + Some(to_piece) => { + (!self.line(from, to) + .any(|board_index| self.at(board_index) + .is_some()) + ).then(|| { + match (piece, to_piece) { + (Piece::Pawn, Piece::Pawn) => { + (!side_has_drone).then_some(MoveType::Merge) + } + (Piece::Drone, Piece::Pawn) | (Piece::Pawn, Piece::Drone) => { + (!side_has_queen).then_some(MoveType::Merge) + }, + _ => { + Some(MoveType::Invalid) + } + } + }).flatten() + }, + // Any other spot is valid + None => { + // If there is another piece between A and B + (!self + .line(from, to) + .any(|board_index| self.at(board_index).is_some())) + .then_some(MoveType::Valid) } } } - } - // Check for moving across the canal - (Side::A, Side::B) | (Side::B, Side::A) => { - match dest_at { - Some(_) => { - // If there is another piece between A and B - if self - .line(from, to) - .any(|board_index| self.at(board_index).is_some()) - { - // Invalid move, there is a piece between A and B - Some(MoveType::Invalid) - } else { - // Otherwise it's a capture - Some(MoveType::Capture) + // Check for moving across the canal + (Side::A, Side::B) | (Side::B, Side::A) => { + match dest_at { + Some(_) => { + // If there is no other piece between A and B + (!self + .line(from, to) + .any(|board_index| self.at(board_index).is_some())) + .then_some(MoveType::Capture) } - } - None => { - // move is valid if it does not un-do the previous move - match self.moves.last() { - Some(previous) => { - // If the last `from` is the current destination - // The move is not valid - if previous.from == to { - Some(MoveType::Invalid) - // Otherwise we're good to move there - } else { - Some(MoveType::Valid) + None => { + // move is valid if it does not un-do the previous move + match self.moves.last() { + Some(previous) => { + (previous.from != to).then_some(MoveType::Valid) } - } - // First move in the game, this is valid (and impossible) - None => { - // If there is another piece between A and B - if self - .line(from, to) - .any(|board_index| self.at(board_index).is_some()) - { - // Invalid move, there is a piece between A and B - Some(MoveType::Invalid) - } else { - // Otherwise it's a valid - Some(MoveType::Valid) + // First move in the game, this is valid (and impossible) + None => { + // If there is no other pieces between A and B + // the move is valid + (!self + .line(from, to) + .any(|board_index| self.at(board_index).is_some())) + .then_some(MoveType::Valid) } } } } } } - } - }) + }) + }).flatten().or(Some(MoveType::Invalid)) } /// Returns the possible moves the piece at this tile can make. @@ -606,7 +604,7 @@ impl Board { if let Some(piece) = self.at(current_board_index) { piece.moves_at(¤t_board_index).iter().filter_map(|move_index| { // Get the move type (or none if totally invalid) - match self.move_type(*piece, current_board_index, *move_index) { + match self.move_type(current_board_index, *move_index) { None | Some(MoveType::Invalid) => None, _ => Some(*move_index), } @@ -757,6 +755,23 @@ mod test { "Mostly blocked queen, moves include captures" ); } + + // Pawn can merge with pawn + { + let given = board.valid_moves((0, 0).into()); + let expected: HashSet = HashSet::from([(1, 1).into()]); + assert_eq!(expected, given, "Pawn can merge to make a drone"); + + let move_type = board.move_type((0, 0).into(), (1, 1).into()); + assert_eq!(Some(MoveType::Merge), move_type); + } + + // Pawn cannot merge with queen + { + let given = board.valid_moves((2, 1).into()); + let expected: HashSet = HashSet::from([(3, 0).into(), (3, 2).into(), (1, 2).into()]); + assert_eq!(expected, given, "Pawn cannot merge with queen"); + } } /// Can move up to but not over friendlies @@ -836,6 +851,78 @@ mod test { assert_eq!(expected, given, "Drone moves at"); } + + #[test] + fn test_capture_01() { + let board = Board::from_ascii( + r#"...p.... + ........ + .ppd.q.. + ........"#, + ); + + // Drone can move in all expected ways + { + let expected: HashSet = HashSet::from([ + (2, 1).into(), + (3, 0).into(), + (3, 2).into(), + (3, 3).into(), + (4, 1).into(), + (5, 1).into(), + ]); + let actual = board.valid_moves((3, 1).into()); + assert_eq!(expected, actual); + + // Merging drone and pawn to make a queen + let capture_move = board.move_type((3, 1).into(), (5, 1).into()); + assert_eq!(Some(MoveType::Capture), capture_move); + + // Merging does not cross the canal + let merge_move = board.move_type((3, 1).into(), (2, 1).into()); + assert_eq!(Some(MoveType::Merge), merge_move); + + let long_merge_move = board.move_type((3, 1).into(), (3, 3).into()); + assert_eq!(Some(MoveType::Merge), long_merge_move); + + // Still cannot cross over friendlies + let jump_move = board.move_type((3, 1).into(), (1, 1).into()); + assert_eq!(Some(MoveType::Invalid), jump_move); + } + } + + #[test] + fn test_capture_02() { + let board = Board::from_ascii( + r#"........ + ..p.p... + ...p.... + ........"#, + ); + + { + // All normal moves for pawn are valid + let given = board.valid_moves((3, 1).into()); + let expected: HashSet = HashSet::from([(2, 0).into(), (4, 0).into(), (2, 2).into(), (4, 2).into()]); + assert_eq!(expected, given); + + // Pawn + Pawn on same side = Promotion + let merge_move = board.move_type((3, 1).into(), (2, 2).into()); + assert_eq!(Some(MoveType::Merge), merge_move); + + // Pawn + Pawn on other side = Capture + let capture_move = board.move_type((3, 1).into(), (4, 2).into()); + assert_eq!(Some(MoveType::Capture), capture_move); + + // Pawn + Empty = Move + let place_move = board.move_type((3, 1).into(), (2, 0).into()); + assert_eq!(Some(MoveType::Valid), place_move); + + // Pawn + Invalid Empty = Invalid + let invalid_move = board.move_type((3, 1).into(), (7, 7).into()); + assert_eq!(Some(MoveType::Invalid), invalid_move); + } + } } impl std::fmt::Display for Board {