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 {
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<MoveType> {
// 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(&current_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<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
@ -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<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 {

Loading…
Cancel
Save