Re-architecture for moves

Now that moves are a bit more complicated I did a pass at calculating
which moves are valid for a given piece.

The speed/memory usage on this version is likely higher, but we're
dealing with really small numbers of moves so I'm not worried about
it. Optimizations should be simple if I look for them.

As for the re-architect, we generate a list of possible moves for a
piece, check that against some candidates spot, and if it _could_ be a
valid move, we do additional checks like if this is a capture, if this
is an invalidating move, or if this would "Jump" (which was my main goal
with this refactor).
main
Elijah C. Voigt 2 years ago
parent f0befe0928
commit 3157666d84

@ -66,6 +66,60 @@ pub(crate) enum Piece {
Queen, Queen,
} }
impl Piece {
fn moves<'a>(&self) -> std::slice::Iter<'_, (isize, isize)> {
match self {
Piece::Pawn => [(1, 1), (1, -1), (-1, 1), (-1, -1)].iter(),
Piece::Drone => [
(0, 2),
(0, 1),
(-2, 0),
(-1, 0),
(1, 0),
(2, 0),
(0, -1),
(0, -2),
]
.iter(),
Piece::Queen => [
(-3, 3),
(0, 3),
(3, 3),
(-2, 2),
(0, 2),
(2, 2),
(-1, 1),
(0, 1),
(1, 1),
(-7, 0),
(-6, 0),
(-5, 0),
(-4, 0),
(-3, 0),
(-2, 0),
(-1, 0),
(1, 0),
(2, 0),
(3, 0),
(4, 0),
(5, 0),
(6, 0),
(7, 0),
(-1, -1),
(0, -1),
(1, -1),
(-2, -2),
(0, -2),
(2, -2),
(-3, -3),
(0, -3),
(3, -3),
]
.iter(),
}
}
}
#[derive(Debug, Component, Clone, PartialEq)] #[derive(Debug, Component, Clone, PartialEq)]
pub(crate) enum Tile { pub(crate) enum Tile {
Dark, Dark,
@ -210,6 +264,35 @@ pub(crate) struct BoardIndex {
pub y: usize, pub y: usize,
} }
impl std::ops::Add for BoardIndex {
type Output = BoardIndex;
fn add(self, other: Self) -> Self {
BoardIndex {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
impl std::ops::Add<(isize, isize)> for BoardIndex {
type Output = BoardIndex;
fn add(self, (a, b): (isize, isize)) -> Self {
BoardIndex {
x: self.x.saturating_add_signed(a),
y: self.y.saturating_add_signed(b),
}
}
}
#[derive(Debug)]
pub(crate) enum MoveType {
Valid,
Invalid,
Capture,
}
#[derive(Debug, Component, PartialEq, Clone, Copy, Eq, Hash, Default)] #[derive(Debug, Component, PartialEq, Clone, Copy, Eq, Hash, Default)]
pub(crate) enum Side { pub(crate) enum Side {
A, A,
@ -397,167 +480,128 @@ impl Board {
} }
} }
/// Returns the possible moves the piece at this tile can make. // Returns a list of indexes between two points, excluding the start and end
/// TODO: Implement "no jumping" over pieces fn line(&self, from: BoardIndex, to: BoardIndex) -> impl Iterator<Item = BoardIndex> {
pub(crate) fn valid_moves(&self, current_board_index: BoardIndex) -> HashSet<BoardIndex> { let mut curr = from;
let BoardIndex { x, y } = current_board_index;
// Longest possible move is 10, so we create a generator over 11
let f = |(a, b): (Option<usize>, Option<usize>)| { (0..11)
if let (Some(this_x), Some(this_y)) = (a, b) { .map(move |_| {
// This has a valid x position let x = if curr.x > to.x {
let valid_x = (0..=7).contains(&this_x); curr.x.saturating_sub(1)
if valid_x { } else if curr.x < to.x {
// It has a valid y position curr.x.saturating_add(1)
let valid_y = (0..=3).contains(&this_y); } else {
if valid_y { to.x
// The checked board index
let this_board_index = BoardIndex {
x: this_x,
y: this_y,
}; };
// Only propose tiles that are empty or capture a piece on the other side
let valid_capture = { let y = if curr.y > to.y {
match self.at(this_board_index) { curr.y.saturating_sub(1)
Some(_) => { } else if curr.y < to.y {
Board::side(this_board_index) curr.y.saturating_add(1)
!= Board::side(current_board_index) } else {
} to.y
None => true,
}
}; };
if valid_capture {
// You cannot move a piece from A to B and then back to A when it was just moved from SideA->SideB curr = BoardIndex { x, y };
// Move rejection is not allowed
let rejection = { (curr != to).then_some(curr)
if let Some(Move { })
from: last_from, .filter_map(|bi| bi)
to: Some(last_to), }
..
}) = self.moves.last() /// 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()
// Find if the given `to` move is one of those
.find(|(x, y)| to == from + (*x, *y))
// 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 => 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())
{ {
// TODO: I think this is more logic than we need to express // Invalid move, there is a piece between A and B
// the sentiment... Some(MoveType::Invalid)
*last_from == this_board_index
&& *last_to == current_board_index
&& Board::side(this_board_index)
!= Board::side(current_board_index)
} else { } else {
false // Otherwise it's a capture
Some(MoveType::Capture)
} }
};
// If all tests pass, this is a valid move
(!rejection).then_some(this_board_index)
} else {
None
} }
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 { } else {
None Some(MoveType::Valid)
} }
} else {
None
} }
// 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 { } else {
None // Otherwise it's a valid
Some(MoveType::Valid)
}
}
}
}
}
}
}
})
} }
};
match self.at(BoardIndex { x, y }) { /// Returns the possible moves the piece at this tile can make.
// One space in any diagonal /// TODO: Implement "no jumping" over pieces
Some(Piece::Pawn) => std::iter::empty() pub(crate) fn valid_moves(&self, current_board_index: BoardIndex) -> HashSet<BoardIndex> {
.chain( tiles()
(-1..=1) .filter_map(|(board_index, _)| {
.zip(-1..=1) // Get the move type (or none if totally invalid)
.map(move |(a, b)| (x.checked_add_signed(a), y.checked_add_signed(b))), self.at(current_board_index).and_then(|piece| {
) match self.move_type(*piece, current_board_index, board_index) {
.chain( None | Some(MoveType::Invalid) => None,
(-1..=1) _ => Some(board_index),
.zip((-1..=1).rev())
.map(move |(a, b)| (x.checked_add_signed(a), y.checked_add_signed(b))),
)
.filter_map(f)
.collect(),
// One or two spaces in either horizontal
Some(Piece::Drone) => std::iter::empty()
// Checking moves on the X axis
.chain(
(-2..=-1)
.rev()
.map(|i| f((x.checked_add_signed(i), Some(y))))
.take_while(|x| x.is_some()),
)
.chain(
(1..=2)
.map(|i| f((x.checked_add_signed(i), Some(y))))
.take_while(|x| x.is_some()),
)
// Checking moves on the Y axis
.chain(
(1..=2)
.map(|i| f((Some(x), y.checked_add_signed(i))))
.take_while(|x| x.is_some()),
)
.chain(
(-2..=-1)
.rev()
.map(|i| f((Some(x), y.checked_add_signed(i))))
.take_while(|x| x.is_some()),
)
.filter_map(|x| x)
.collect(),
// Any distance in any straight line
Some(Piece::Queen) => std::iter::empty()
.chain(
(-7..=-1)
.rev()
.map(|i| f((x.checked_add_signed(i), Some(y))))
.take_while(|x| x.is_some()),
)
.chain(
(1..=7)
.map(|i| f((x.checked_add_signed(i), Some(y))))
.take_while(|x| x.is_some()),
)
.chain(
(-3..=-1)
.rev()
.map(|i| f((Some(x), y.checked_add_signed(i))))
.take_while(|x| x.is_some()),
)
.chain(
(1..=3)
.map(|i| f((Some(x), y.checked_add_signed(i))))
.take_while(|x| x.is_some()),
)
.chain(
(-3..=-1)
.rev()
.zip((-3..=-1).rev())
.map(move |(a, b)| f((x.checked_add_signed(a), y.checked_add_signed(b))))
.take_while(|x| x.is_some()),
)
.chain(
(-3..=-1)
.rev()
.zip(1..=3)
.map(move |(a, b)| f((x.checked_add_signed(a), y.checked_add_signed(b))))
.take_while(|x| x.is_some()),
)
.chain(
(1..=3)
.zip((-3..=-1).rev())
.map(move |(a, b)| f((x.checked_add_signed(a), y.checked_add_signed(b))))
.take_while(|x| x.is_some()),
)
.chain(
(1..=3)
.zip(1..=3)
.map(move |(a, b)| f((x.checked_add_signed(a), y.checked_add_signed(b))))
.take_while(|x| x.is_some()),
)
.filter_map(|x| x)
.collect(),
None => std::iter::empty().collect(),
} }
})
})
.collect()
} }
pub(crate) fn current_epoch(&self) -> usize { pub(crate) fn current_epoch(&self) -> usize {

Loading…
Cancel
Save