|
|
|
|
@ -118,6 +118,13 @@ impl Piece {
|
|
|
|
|
.iter(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn moves_at(&self, from: &BoardIndex) -> Vec<BoardIndex> {
|
|
|
|
|
self.moves().map(|(x, y)| {
|
|
|
|
|
*from + (*x, *y)
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Component, Clone, PartialEq)]
|
|
|
|
|
@ -280,12 +287,29 @@ impl std::ops::Add<(isize, isize)> for BoardIndex {
|
|
|
|
|
|
|
|
|
|
fn add(self, (a, b): (isize, isize)) -> Self {
|
|
|
|
|
BoardIndex {
|
|
|
|
|
x: self.x.saturating_add_signed(a),
|
|
|
|
|
y: self.y.saturating_add_signed(b),
|
|
|
|
|
x: self.x.saturating_add_signed(a).max(7),
|
|
|
|
|
y: self.y.saturating_add_signed(b).max(3),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::ops::Add<(usize, usize)> for BoardIndex {
|
|
|
|
|
type Output = BoardIndex;
|
|
|
|
|
|
|
|
|
|
fn add(self, (a, b): (usize, usize)) -> Self {
|
|
|
|
|
BoardIndex {
|
|
|
|
|
x: self.x.saturating_add(a).max(7),
|
|
|
|
|
y: self.y.saturating_add(b).max(3),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<(usize, usize)> for BoardIndex {
|
|
|
|
|
fn from((x, y): (usize, usize)) -> BoardIndex {
|
|
|
|
|
BoardIndex { x, y }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub(crate) enum MoveType {
|
|
|
|
|
Valid,
|
|
|
|
|
@ -320,24 +344,33 @@ impl Board {
|
|
|
|
|
|
|
|
|
|
fn from_ascii(art: &str) -> Board {
|
|
|
|
|
use Piece::*;
|
|
|
|
|
let mut inner: Vec<Vec<Option<Piece>>> = vec![Vec::with_capacity(8), Vec::with_capacity(8), Vec::with_capacity(8), Vec::with_capacity(8)];
|
|
|
|
|
let mut inner: Vec<Vec<Option<Piece>>> = vec![
|
|
|
|
|
Vec::with_capacity(8),
|
|
|
|
|
Vec::with_capacity(8),
|
|
|
|
|
Vec::with_capacity(8),
|
|
|
|
|
Vec::with_capacity(8),
|
|
|
|
|
];
|
|
|
|
|
let mut index = BoardIndex { x: 0, y: 3 };
|
|
|
|
|
art.chars().for_each(|c| {
|
|
|
|
|
match c {
|
|
|
|
|
'q' => {
|
|
|
|
|
'q' | 'Q' => {
|
|
|
|
|
inner[index.y].push(Some(Queen));
|
|
|
|
|
}
|
|
|
|
|
'd' => {
|
|
|
|
|
'd' | 'D' => {
|
|
|
|
|
inner[index.y].push(Some(Drone));
|
|
|
|
|
},
|
|
|
|
|
'p' => {
|
|
|
|
|
}
|
|
|
|
|
'p' | 'P' => {
|
|
|
|
|
inner[index.y].push(Some(Pawn));
|
|
|
|
|
},
|
|
|
|
|
'.' => {
|
|
|
|
|
}
|
|
|
|
|
'.' | '#' => {
|
|
|
|
|
inner[index.y].push(None);
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
'\n' => {
|
|
|
|
|
assert_eq!(inner[index.y].len(), 8, "Each row must be 8 characters long!");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
inner[index.y].len(),
|
|
|
|
|
8,
|
|
|
|
|
"Each row must be 8 characters long!"
|
|
|
|
|
);
|
|
|
|
|
index.y -= 1;
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
@ -356,7 +389,8 @@ impl Board {
|
|
|
|
|
r#".....dqq
|
|
|
|
|
dpp..pdq
|
|
|
|
|
qdp..ppd
|
|
|
|
|
qqd....."#)
|
|
|
|
|
qqd....."#,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Show all pieces on one side of the board
|
|
|
|
|
@ -501,9 +535,10 @@ impl Board {
|
|
|
|
|
) -> Option<MoveType> {
|
|
|
|
|
// Iterate over the piece's moves
|
|
|
|
|
piece
|
|
|
|
|
.moves()
|
|
|
|
|
.moves_at(&from)
|
|
|
|
|
.iter()
|
|
|
|
|
// Find if the given `to` move is one of those
|
|
|
|
|
.find(|(x, y)| to == from + (*x, *y))
|
|
|
|
|
.find(|BoardIndex { x, y }| to == from + (*x, *y))
|
|
|
|
|
// Determine if this is valid/legal in this situation
|
|
|
|
|
.and_then(|_| {
|
|
|
|
|
let dest_at = self.at(to);
|
|
|
|
|
@ -584,17 +619,18 @@ impl Board {
|
|
|
|
|
|
|
|
|
|
/// Returns the possible moves the piece at this tile can make.
|
|
|
|
|
pub(crate) fn valid_moves(&self, current_board_index: BoardIndex) -> HashSet<BoardIndex> {
|
|
|
|
|
tiles()
|
|
|
|
|
.filter_map(|(board_index, _)| {
|
|
|
|
|
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)
|
|
|
|
|
self.at(current_board_index).and_then(|piece| {
|
|
|
|
|
match self.move_type(*piece, current_board_index, board_index) {
|
|
|
|
|
match self.move_type(*piece, current_board_index, *move_index) {
|
|
|
|
|
None | Some(MoveType::Invalid) => None,
|
|
|
|
|
_ => Some(board_index),
|
|
|
|
|
_ => Some(*move_index),
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
} else {
|
|
|
|
|
HashSet::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn current_epoch(&self) -> usize {
|
|
|
|
|
@ -606,14 +642,161 @@ mod test {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_01() {
|
|
|
|
|
let mut board = Board::new();
|
|
|
|
|
print!("{}", board);
|
|
|
|
|
println!("{:?}", board.at(BoardIndex { x: 0, y: 0 }));
|
|
|
|
|
println!("{:?}", board.at(BoardIndex { x: 1, y: 0 }));
|
|
|
|
|
println!("{:?}", board.at(BoardIndex { x: 2, y: 0 }));
|
|
|
|
|
println!("{:?}", board.at(BoardIndex { x: 3, y: 0 }));
|
|
|
|
|
todo!()
|
|
|
|
|
fn pawn_simple_moves() {
|
|
|
|
|
let board = Board::from_ascii(
|
|
|
|
|
r#"........
|
|
|
|
|
.#.#....
|
|
|
|
|
..p.....
|
|
|
|
|
.#.#...."#,
|
|
|
|
|
);
|
|
|
|
|
let given = board.valid_moves((2, 1).into());
|
|
|
|
|
let expected: HashSet<BoardIndex> =
|
|
|
|
|
HashSet::from([(1, 0).into(), (3, 0).into(), (1, 2).into(), (3, 2).into()]);
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected, given, "Basic pawn moves");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn drone_simple_moves() {
|
|
|
|
|
let board = Board::from_ascii(
|
|
|
|
|
r#"..#.....
|
|
|
|
|
..#.....
|
|
|
|
|
##d##...
|
|
|
|
|
..#....."#,
|
|
|
|
|
);
|
|
|
|
|
let given = board.valid_moves((2, 1).into());
|
|
|
|
|
let expected: HashSet<BoardIndex> = HashSet::from([
|
|
|
|
|
(2, 0).into(),
|
|
|
|
|
(2, 2).into(),
|
|
|
|
|
(2, 3).into(),
|
|
|
|
|
(0, 1).into(),
|
|
|
|
|
(1, 1).into(),
|
|
|
|
|
(3, 1).into(),
|
|
|
|
|
(4, 1).into(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected, given, "Basic drone moves");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn queen_simple_moves() {
|
|
|
|
|
let board = Board::from_ascii(
|
|
|
|
|
r#"...###..
|
|
|
|
|
####q###
|
|
|
|
|
...###..
|
|
|
|
|
..#.#.#."#,
|
|
|
|
|
);
|
|
|
|
|
let given = board.valid_moves((4, 2).into());
|
|
|
|
|
let expected: HashSet<BoardIndex> = HashSet::from([
|
|
|
|
|
(0, 2).into(),
|
|
|
|
|
(1, 0).into(),
|
|
|
|
|
(1, 2).into(),
|
|
|
|
|
(2, 0).into(),
|
|
|
|
|
(2, 2).into(),
|
|
|
|
|
(3, 1).into(),
|
|
|
|
|
(3, 2).into(),
|
|
|
|
|
(3, 3).into(),
|
|
|
|
|
(4, 0).into(),
|
|
|
|
|
(4, 1).into(),
|
|
|
|
|
(4, 3).into(),
|
|
|
|
|
(5, 1).into(),
|
|
|
|
|
(5, 2).into(),
|
|
|
|
|
(5, 3).into(),
|
|
|
|
|
(6, 0).into(),
|
|
|
|
|
(6, 2).into(),
|
|
|
|
|
(7, 0).into(),
|
|
|
|
|
(7, 2).into(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected, given, "Basic queen moves");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn empty_moves() {
|
|
|
|
|
let board = Board::from_ascii(
|
|
|
|
|
r#"........
|
|
|
|
|
.p....q.
|
|
|
|
|
........
|
|
|
|
|
...d...."#,
|
|
|
|
|
);
|
|
|
|
|
let given = board.valid_moves((2, 1).into());
|
|
|
|
|
let expected: HashSet<BoardIndex> = HashSet::from([]);
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected, given, "Empty moves");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// When a piece is blocked on all sides by friendly, cannot move
|
|
|
|
|
#[test]
|
|
|
|
|
fn blocking_friendly() {
|
|
|
|
|
let board = Board::from_ascii(
|
|
|
|
|
r#"p.......
|
|
|
|
|
..dp....
|
|
|
|
|
.pqp....
|
|
|
|
|
.ppp...."#,
|
|
|
|
|
);
|
|
|
|
|
// Check queen
|
|
|
|
|
{
|
|
|
|
|
let given = board.valid_moves((2, 1).into());
|
|
|
|
|
let expected: HashSet<BoardIndex> = HashSet::from([(1, 2).into()]);
|
|
|
|
|
assert_eq!(expected, given, "Mostly surrounded queen, some moves");
|
|
|
|
|
}
|
|
|
|
|
// Check pawn
|
|
|
|
|
{
|
|
|
|
|
let given = board.valid_moves((3, 1).into());
|
|
|
|
|
let expected: HashSet<BoardIndex> = HashSet::from([(4, 0).into(), (4, 2).into()]);
|
|
|
|
|
assert_eq!(expected, given, "Partially blocked pawn, some moves");
|
|
|
|
|
}
|
|
|
|
|
// Check drone
|
|
|
|
|
{
|
|
|
|
|
let given = board.valid_moves((2, 2).into());
|
|
|
|
|
let expected: HashSet<BoardIndex> =
|
|
|
|
|
HashSet::from([(0, 2).into(), (1, 2).into(), (2, 3).into()]);
|
|
|
|
|
assert_eq!(expected, given, "Partially blocked drone, some moves");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// When an enemy is on one side, can move to capture just that piece
|
|
|
|
|
#[test]
|
|
|
|
|
fn case_03() {
|
|
|
|
|
let board = Board::from_ascii(
|
|
|
|
|
r#"........
|
|
|
|
|
........
|
|
|
|
|
ppp.....
|
|
|
|
|
pq..p..p"#,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let given = board.valid_moves((1, 0).into());
|
|
|
|
|
let expected: HashSet<BoardIndex> =
|
|
|
|
|
HashSet::from([(2, 0).into(), (3, 0).into(), (4, 0).into()]);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
expected, given,
|
|
|
|
|
"Mostly blocked queen, moves include captures"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Can move up to but not over friendlies
|
|
|
|
|
#[test]
|
|
|
|
|
fn case_02() {
|
|
|
|
|
let board = Board::from_ascii(
|
|
|
|
|
r#"......qq
|
|
|
|
|
dpp#d#d.
|
|
|
|
|
qd##qppd
|
|
|
|
|
qq.###.."#,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let given = board.valid_moves((4, 1).into());
|
|
|
|
|
let expected: HashSet<BoardIndex> =
|
|
|
|
|
HashSet::from([
|
|
|
|
|
(3,0).into(), (4,0).into(), (5,0).into(), (2, 1).into(), (2, 3).into(), (3, 1).into(), (5, 1).into(), (3, 2).into(), (5, 2).into()
|
|
|
|
|
]);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
expected, given,
|
|
|
|
|
"Mostly blocked queen, moves include captures"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|