Merging moves implemented ish

At least on the backend. Not sure they're rendered.

God bless test driven development, amirite?
main
Elijah C. Voigt 2 years ago
parent 40b40ae887
commit 80738dd217

@ -290,11 +290,12 @@ impl From<(usize, usize)> for BoardIndex {
} }
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub(crate) enum MoveType { pub(crate) enum MoveType {
Valid, Valid,
Invalid, Invalid,
Capture, Capture,
Merge,
} }
#[derive(Debug, Component, PartialEq, Clone, Copy, Eq, Hash, Default)] #[derive(Debug, Component, PartialEq, Clone, Copy, Eq, Hash, Default)]
@ -366,14 +367,10 @@ impl Board {
fn new() -> Board { fn new() -> Board {
Board::from_ascii( Board::from_ascii(
r#"...###.. r#".....dqq
####q### 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 /// Determine given a piece, a to, and a from, what type of move this would be
pub(crate) fn move_type( pub(crate) fn move_type(
&self, &self,
piece: Piece,
from: BoardIndex, from: BoardIndex,
to: BoardIndex, to: BoardIndex,
) -> Option<MoveType> { ) -> Option<MoveType> {
// Iterate over the piece's moves self.at(from).map(|piece| {
piece // Given that the side does not have a queen||drone
.moves_at(&from) // And the piece is a drone||pawn
.iter() // We can do field promotions
// Find if the given `to` move is one of those let side = Board::side(from).expect("Piece has valid index");
.find(|idx| to == **idx) let side_has_queen = self.on(side).iter().any(|(piece, _)| **piece == Piece::Queen);
// Determine if this is valid/legal in this situation let side_has_drone = self.on(side).iter().any(|(piece, _)| **piece == Piece::Drone);
.and_then(|_| {
let dest_at = self.at(to); // Iterate over the piece's moves
let curr_side = Board::side(from).unwrap(); piece
let dest_side = Board::side(to).unwrap(); .moves_at(&from)
.iter()
match (curr_side, dest_side) { // Find if the given `to` move is one of those
(Side::A, Side::A) | (Side::B, Side::B) => { .find(|idx| to == **idx)
match dest_at { // Determine if this is valid/legal in this situation
// Cannot move on top of a friendly .and_then(|_| {
Some(_) => Some(MoveType::Invalid), let dest_at = self.at(to);
// Any other spot is valid let curr_side = Board::side(from).unwrap();
None => { let dest_side = Board::side(to).unwrap();
// If there is another piece between A and B
if self match (curr_side, dest_side) {
.line(from, to) (Side::A, Side::A) | (Side::B, Side::B) => {
.any(|board_index| self.at(board_index).is_some()) match dest_at {
{ // Cannot move on top of a friendly
// Invalid move, there is a piece between A and B Some(to_piece) => {
Some(MoveType::Invalid) (!self.line(from, to)
} else { .any(|board_index| self.at(board_index)
// Otherwise it's a valid .is_some())
Some(MoveType::Valid) ).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
// Check for moving across the canal (Side::A, Side::B) | (Side::B, Side::A) => {
(Side::A, Side::B) | (Side::B, Side::A) => { match dest_at {
match dest_at { Some(_) => {
Some(_) => { // If there is no other piece between A and B
// If there is another piece between A and B (!self
if self .line(from, to)
.line(from, to) .any(|board_index| self.at(board_index).is_some()))
.any(|board_index| self.at(board_index).is_some()) .then_some(MoveType::Capture)
{
// Invalid move, there is a piece between A and B
Some(MoveType::Invalid)
} else {
// Otherwise it's a capture
Some(MoveType::Capture)
} }
} None => {
None => { // move is valid if it does not un-do the previous move
// move is valid if it does not un-do the previous move match self.moves.last() {
match self.moves.last() { Some(previous) => {
Some(previous) => { (previous.from != to).then_some(MoveType::Valid)
// 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)
} }
} // First move in the game, this is valid (and impossible)
// First move in the game, this is valid (and impossible) None => {
None => { // If there is no other pieces between A and B
// If there is another piece between A and B // the move is valid
if self (!self
.line(from, to) .line(from, to)
.any(|board_index| self.at(board_index).is_some()) .any(|board_index| self.at(board_index).is_some()))
{ .then_some(MoveType::Valid)
// Invalid move, there is a piece between A and B
Some(MoveType::Invalid)
} else {
// Otherwise it's a valid
Some(MoveType::Valid)
} }
} }
} }
} }
} }
} }
} })
}) }).flatten().or(Some(MoveType::Invalid))
} }
/// Returns the possible moves the piece at this tile can make. /// 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) { if let Some(piece) = self.at(current_board_index) {
piece.moves_at(&current_board_index).iter().filter_map(|move_index| { piece.moves_at(&current_board_index).iter().filter_map(|move_index| {
// Get the move type (or none if totally invalid) // 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, None | Some(MoveType::Invalid) => None,
_ => Some(*move_index), _ => Some(*move_index),
} }
@ -757,6 +755,23 @@ mod test {
"Mostly blocked queen, moves include captures" "Mostly blocked queen, moves include captures"
); );
} }
// Pawn can merge with pawn
{
let given = board.valid_moves((0, 0).into());
let expected: HashSet<BoardIndex> = 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<BoardIndex> = 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 /// Can move up to but not over friendlies
@ -836,6 +851,78 @@ mod test {
assert_eq!(expected, given, "Drone moves at"); 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<BoardIndex> = 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<BoardIndex> = 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 { impl std::fmt::Display for Board {

Loading…
Cancel
Save