Refactor towards simpler code
This commit is contained in:
parent
b6a6d570e1
commit
32ec8107ab
@ -1,16 +1,17 @@
|
|||||||
use std::{collections::HashSet, fmt::Display};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
fmt::Display,
|
||||||
|
mem,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
coord::{at, Coord},
|
cmove::CMove, constants::BOARD_SIZE, piece::Piece, square::{Square, SquarePair}
|
||||||
piece::Piece,
|
|
||||||
r#move::Move,
|
|
||||||
square::{sq, Square},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct Board {
|
pub(crate) struct Board {
|
||||||
pub(crate) cells: [[Square; 4]; 4],
|
pub(crate) cells: [[Option<Piece>; BOARD_SIZE]; BOARD_SIZE],
|
||||||
pub(crate) legal_moves: HashSet<Move>,
|
pub(crate) legal_moves: HashSet<CMove>,
|
||||||
pub(crate) game_state: GameState,
|
pub(crate) game_state: GameState,
|
||||||
pieces_remaining: u8,
|
pieces_remaining: u8,
|
||||||
}
|
}
|
||||||
@ -26,74 +27,55 @@ pub enum GameState {
|
|||||||
impl Board {
|
impl Board {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Board {
|
Board {
|
||||||
cells: [
|
cells: [[None; BOARD_SIZE]; BOARD_SIZE],
|
||||||
[sq!("a4"), sq!("b4"), sq!("c4"), sq!("d4")],
|
|
||||||
[sq!("a3"), sq!("b3"), sq!("c3"), sq!("d3")],
|
|
||||||
[sq!("a2"), sq!("b2"), sq!("c2"), sq!("d2")],
|
|
||||||
[sq!("a1"), sq!("b1"), sq!("c1"), sq!("d1")],
|
|
||||||
],
|
|
||||||
legal_moves: HashSet::new(),
|
legal_moves: HashSet::new(),
|
||||||
pieces_remaining: 0,
|
pieces_remaining: 0,
|
||||||
game_state: GameState::NotStarted,
|
game_state: GameState::NotStarted,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set(&mut self, square: Square) -> Square {
|
pub(crate) fn set(&mut self, square: Square) -> Option<Piece> {
|
||||||
let coord = square.coord();
|
let new_is_occuppied = square.piece.is_some();
|
||||||
let new_is_occuppied = square.occupied().is_some();
|
let existing = mem::replace(&mut self.cells[square.file][square.rank], square.piece);
|
||||||
let existing = std::mem::replace(&mut self.cells[coord.file][coord.rank], square);
|
|
||||||
|
|
||||||
// If placing a piece on a blank, increment piece count
|
// If placing a piece on a blank, increment piece count
|
||||||
if existing.is_empty() && new_is_occuppied {
|
if existing.is_none() && new_is_occuppied {
|
||||||
self.pieces_remaining += 1;
|
self.pieces_remaining += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If placing a blank on a piece, decrement piece count
|
// If placing a blank on a piece, decrement piece count
|
||||||
if existing.occupied().is_some() && !new_is_occuppied {
|
if existing.is_some() && !new_is_occuppied {
|
||||||
self.pieces_remaining -= 1;
|
self.pieces_remaining -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.calc_legal_moves();
|
self.board_state_changed();
|
||||||
self.calc_game_state();
|
|
||||||
existing
|
existing
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn make_move(&mut self, mv: Move) -> Option<Move> {
|
pub(crate) fn make_move(&mut self, mv: CMove) -> Option<CMove> {
|
||||||
if self.legal_moves.contains(&mv) {
|
if !self.legal_moves.contains(&mv) {
|
||||||
// Remove from source
|
|
||||||
let source = std::mem::replace(
|
|
||||||
&mut self.cells[mv.from.coord_ref().file][mv.from.coord_ref().rank],
|
|
||||||
Square::Empty(mv.from.coord()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let target = Square::Occupied(source.occupied().unwrap(), mv.to.coord());
|
|
||||||
|
|
||||||
// Place it on target
|
|
||||||
std::mem::replace(
|
|
||||||
&mut self.cells[mv.to.coord_ref().file][mv.to.coord_ref().rank],
|
|
||||||
target,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.pieces_remaining -= 1;
|
|
||||||
self.calc_legal_moves();
|
|
||||||
self.calc_game_state();
|
|
||||||
Some(mv)
|
|
||||||
} else {
|
|
||||||
println!("Invalid move - {}", mv.notation());
|
println!("Invalid move - {}", mv.notation());
|
||||||
println!("Legal moves - ");
|
println!("Legal moves - ");
|
||||||
for m in &self.legal_moves {
|
for m in &self.legal_moves {
|
||||||
println!("{}", m.notation());
|
println!("{}", m.notation());
|
||||||
}
|
}
|
||||||
None
|
return None;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn empty_squares(&self) -> Vec<Coord> {
|
let from_piece = mem::replace(&mut self.cells[mv.from.file][mv.from.rank], None);
|
||||||
|
mem::replace(&mut self.cells[mv.to.file][mv.to.rank], from_piece);
|
||||||
|
|
||||||
|
self.pieces_remaining -= 1;
|
||||||
|
self.board_state_changed();
|
||||||
|
Some(mv)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn empty_squares(&self) -> Vec<Square> {
|
||||||
let mut empty_squares = Vec::new();
|
let mut empty_squares = Vec::new();
|
||||||
for file in 0..4 {
|
for file in 0..BOARD_SIZE {
|
||||||
for rank in 0..4 {
|
for rank in 0..BOARD_SIZE {
|
||||||
if self.cells[file][rank].is_empty() {
|
if self.cells[file][rank].is_none() {
|
||||||
empty_squares.push(Coord::new(file, rank));
|
empty_squares.push(Square::new(file, rank, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,16 +88,19 @@ impl Board {
|
|||||||
|
|
||||||
fn print(&self, pretty: bool) -> String {
|
fn print(&self, pretty: bool) -> String {
|
||||||
let mut board_string = String::new();
|
let mut board_string = String::new();
|
||||||
for rank in 0..4 {
|
for rank in 0..BOARD_SIZE {
|
||||||
let mut row = String::new();
|
let mut row = String::new();
|
||||||
for file in 0..4 {
|
for file in 0..BOARD_SIZE {
|
||||||
print_square(&mut row, &self.cells[file][rank], pretty);
|
let piece = self.cells[file][rank];
|
||||||
|
row.push_str(&get_square_for_display(&piece, pretty));
|
||||||
}
|
}
|
||||||
|
|
||||||
if pretty {
|
if pretty {
|
||||||
board_string.push_str(&format!("{:^40}\n", row));
|
board_string.push_str(&format!("{:^40}\n", row));
|
||||||
} else {
|
} else {
|
||||||
board_string.push_str(&row);
|
board_string.push_str(&row);
|
||||||
}
|
}
|
||||||
|
|
||||||
board_string.push('\n');
|
board_string.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,26 +108,92 @@ impl Board {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn calc_legal_moves(&mut self) {
|
fn calc_legal_moves(&mut self) {
|
||||||
self.legal_moves.clear();
|
self.legal_moves = self
|
||||||
for file in 0..4 {
|
.all_possible_move_pairs()
|
||||||
for rank in 0..4 {
|
.into_iter()
|
||||||
if let Square::Occupied(piece, _) = self.cells[file][rank] {
|
.filter(SquarePair::is_different)
|
||||||
let source = &self.cells[file][rank];
|
.filter_map(|pair| self.is_legal_move(pair))
|
||||||
let mut moves = match piece {
|
.collect()
|
||||||
Piece::King => self.king_legal_moves(source),
|
}
|
||||||
Piece::Pawn => self.pawn_legal_moves(source),
|
|
||||||
Piece::Knight => self.knight_legal_moves(source),
|
fn is_legal_move(&self, pair: SquarePair) -> Option<CMove> {
|
||||||
Piece::Bishop => self.bishop_legal_moves(source),
|
// The below block is just to make the compiler happy. Start will always
|
||||||
Piece::Rook => self.rook_legal_moves(source),
|
// have a piece
|
||||||
Piece::Queen => self.queen_legal_moves(source),
|
let Some(piece) = pair.start.piece else {
|
||||||
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
moves.into_iter().for_each(|m| {
|
let legal = match piece {
|
||||||
self.legal_moves.insert(m);
|
Piece::King => self.is_king_legal(&pair),
|
||||||
});
|
Piece::Queen => self.is_queen_legal(&pair),
|
||||||
}
|
Piece::Bishop => self.is_bishop_legal(&pair),
|
||||||
|
Piece::Knight => self.is_knight_legal(&pair),
|
||||||
|
Piece::Rook => self.is_rook_legal(&pair),
|
||||||
|
Piece::Pawn => self.is_pawn_legal(&pair),
|
||||||
|
};
|
||||||
|
|
||||||
|
if legal {
|
||||||
|
return Some(CMove::new(pair.start, pair.end));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_king_legal(&self, pair: &SquarePair) -> bool {
|
||||||
|
pair.dx <= 1 && pair.dy <= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_queen_legal(&self, pair: &SquarePair) -> bool {
|
||||||
|
self.is_path_free(pair)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_bishop_legal(&self, pair: &SquarePair) -> bool {
|
||||||
|
pair.dx == pair.dy && self.is_path_free(pair)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_knight_legal(&self, pair: &SquarePair) -> bool {
|
||||||
|
(pair.dx == 2 && pair.dy == 1) || (pair.dx == 1 && pair.dy == 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_rook_legal(&self, pair: &SquarePair) -> bool {
|
||||||
|
if pair.dx != 0 && pair.dy != 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.is_path_free(pair)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_pawn_legal(&self, pair: &SquarePair) -> bool {
|
||||||
|
pair.dx == 1 && pair.dy == 1 && pair.y_dir == -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_path_free(&self, pair: &SquarePair) -> bool {
|
||||||
|
// There is no straight line or diagonal to get through
|
||||||
|
if pair.dx != pair.dy && pair.dx != 0 && pair.dy != 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x_inc = pair.x_dir;
|
||||||
|
let y_inc = pair.y_dir;
|
||||||
|
let mut x: i8 = pair.start.file.try_into().unwrap();
|
||||||
|
let mut y: i8 = pair.start.rank.try_into().unwrap();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
x = x + x_inc;
|
||||||
|
y = y + y_inc;
|
||||||
|
|
||||||
|
let file: usize = x.try_into().unwrap();
|
||||||
|
let rank: usize = y.try_into().unwrap();
|
||||||
|
if rank == pair.end.rank && file == pair.end.file {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.cells[file][rank].is_some() {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calc_game_state(&mut self) {
|
fn calc_game_state(&mut self) {
|
||||||
@ -157,203 +208,35 @@ impl Board {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn king_legal_moves(&self, start: &Square) -> Vec<Move> {
|
/// This is just a cartesian product of {occupied_squares} x {occupied_squares}
|
||||||
self.rect(start.coord(), 1)
|
fn all_possible_move_pairs(&self) -> impl IntoIterator<Item = SquarePair> {
|
||||||
|
let ret = self
|
||||||
|
.all_occupied_squares()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| Board::create_move(start, s))
|
.map(|start| {
|
||||||
.collect()
|
self.all_occupied_squares()
|
||||||
}
|
|
||||||
|
|
||||||
fn pawn_legal_moves(&self, start: &Square) -> Vec<Move> {
|
|
||||||
self.rect(start.coord(), 1)
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|target| {
|
.map(move |end| SquarePair::new(start.clone(), end))
|
||||||
target.coord_ref().rank < start.coord_ref().rank
|
|
||||||
&& target.coord_ref().file != start.coord_ref().file
|
|
||||||
})
|
})
|
||||||
.map(|s| Board::create_move(start, s))
|
.flatten()
|
||||||
.collect()
|
.collect::<Vec<SquarePair>>();
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn knight_legal_moves(&self, start: &Square) -> Vec<Move> {
|
fn all_occupied_squares(&self) -> impl IntoIterator<Item = Square> {
|
||||||
self.rect(start.coord(), 2)
|
let mut ret = Vec::new();
|
||||||
.into_iter()
|
|
||||||
.filter(|target| {
|
|
||||||
let dx = (start.coord_ref().file as isize - target.coord_ref().file as isize).abs();
|
|
||||||
let dy = (start.coord_ref().rank as isize - target.coord_ref().rank as isize).abs();
|
|
||||||
(dx == 1 && dy == 2) || (dx == 2 && dy == 1)
|
|
||||||
})
|
|
||||||
.map(|s| Board::create_move(start, s))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bishop_legal_moves(&self, start: &Square) -> Vec<Move> {
|
for i in 0..BOARD_SIZE {
|
||||||
self.diag(start.coord())
|
for j in 0..BOARD_SIZE {
|
||||||
.into_iter()
|
let p = &self.cells[i][j];
|
||||||
.map(|s| Board::create_move(start, s))
|
if p.is_some() {
|
||||||
.collect()
|
ret.push(Square::new(i, j, *p))
|
||||||
}
|
|
||||||
|
|
||||||
fn rook_legal_moves(&self, start: &Square) -> Vec<Move> {
|
|
||||||
self.line(start.coord())
|
|
||||||
.into_iter()
|
|
||||||
.map(|s| Board::create_move(start, s))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn queen_legal_moves(&self, start: &Square) -> Vec<Move> {
|
|
||||||
let line = self.line(start.coord()).into_iter();
|
|
||||||
let diag = self.diag(start.coord()).into_iter();
|
|
||||||
line.chain(diag)
|
|
||||||
.map(|s| Board::create_move(start, s))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rect(&self, start: Coord, radius: usize) -> Vec<Square> {
|
|
||||||
let mut range = Vec::new();
|
|
||||||
let x_min = start.file.saturating_sub(radius);
|
|
||||||
let y_min = start.rank.saturating_sub(radius);
|
|
||||||
let x_max = start.file + radius + 1;
|
|
||||||
let y_max = start.rank + radius + 1;
|
|
||||||
|
|
||||||
for file in (x_min)..(x_max) {
|
|
||||||
for rank in (y_min)..(y_max) {
|
|
||||||
if file > 3 || rank > 3 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file, rank) == (start.file, start.rank) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.cells[file][rank].occupied().is_none() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
range.push(self.cells[file][rank].clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
range
|
|
||||||
}
|
|
||||||
|
|
||||||
fn diag(&self, start: Coord) -> Vec<Square> {
|
|
||||||
let mut range = Vec::new();
|
|
||||||
|
|
||||||
// North West
|
|
||||||
if (start.rank > 0 && start.file > 0) {
|
|
||||||
let mut north = start.rank;
|
|
||||||
let mut west = start.file;
|
|
||||||
while north != 0 && west != 0 {
|
|
||||||
north -= 1;
|
|
||||||
west -= 1;
|
|
||||||
if let Some(piece) = self.cells[west][north].occupied() {
|
|
||||||
range.push(self.cells[west][north].clone());
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// North East
|
ret
|
||||||
if (start.rank > 0 && start.file < 3) {
|
|
||||||
let mut north = start.rank;
|
|
||||||
let mut east = start.file;
|
|
||||||
while north != 0 && east < 3 {
|
|
||||||
north -= 1;
|
|
||||||
east += 1;
|
|
||||||
if let Some(piece) = self.cells[east][north].occupied() {
|
|
||||||
range.push(self.cells[east][north].clone());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// South West
|
|
||||||
if (start.rank < 3 && start.file > 0) {
|
|
||||||
let mut south = start.rank;
|
|
||||||
let mut west = start.file;
|
|
||||||
while south < 3 && west != 0 {
|
|
||||||
south += 1;
|
|
||||||
west -= 1;
|
|
||||||
if let Some(piece) = self.cells[west][south].occupied() {
|
|
||||||
range.push(self.cells[west][south].clone());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// South East
|
|
||||||
if (start.rank < 3 && start.file < 3) {
|
|
||||||
let mut south = start.rank;
|
|
||||||
let mut east = start.file;
|
|
||||||
while south < 3 && east < 3 {
|
|
||||||
south += 1;
|
|
||||||
east += 1;
|
|
||||||
if let Some(piece) = self.cells[east][south].occupied() {
|
|
||||||
range.push(self.cells[east][south].clone());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
range
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line(&self, start: Coord) -> Vec<Square> {
|
|
||||||
let mut range = Vec::new();
|
|
||||||
|
|
||||||
if (start.rank > 0) {
|
|
||||||
// North
|
|
||||||
let mut north = start.rank;
|
|
||||||
while north != 0 {
|
|
||||||
north -= 1;
|
|
||||||
if let Some(piece) = self.cells[start.file][north].occupied() {
|
|
||||||
range.push(self.cells[start.file][north].clone());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start.rank < 3) {
|
|
||||||
// South
|
|
||||||
let mut south = start.rank;
|
|
||||||
while south < 3 {
|
|
||||||
south += 1;
|
|
||||||
if let Some(piece) = self.cells[start.file][south].occupied() {
|
|
||||||
range.push(self.cells[start.file][south].clone());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start.file > 0) {
|
|
||||||
//West
|
|
||||||
let mut west = start.file;
|
|
||||||
while west != 0 {
|
|
||||||
west -= 1;
|
|
||||||
if let Some(piece) = self.cells[west][start.rank].occupied() {
|
|
||||||
range.push(self.cells[west][start.rank].clone());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start.file < 3) {
|
|
||||||
// East
|
|
||||||
let mut east = start.file;
|
|
||||||
while east < 3 {
|
|
||||||
east += 1;
|
|
||||||
if let Some(piece) = self.cells[east][start.rank].occupied() {
|
|
||||||
range.push(self.cells[east][start.rank].clone());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
range
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_move(start: &Square, target: Square) -> Move {
|
|
||||||
Move::new(start.clone(), target)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_string(board_string: String) -> Option<Board> {
|
pub(crate) fn from_string(board_string: String) -> Option<Board> {
|
||||||
@ -365,8 +248,8 @@ impl Board {
|
|||||||
let mut file = 0;
|
let mut file = 0;
|
||||||
let mut rank = 0;
|
let mut rank = 0;
|
||||||
let mut chars = board_string.chars();
|
let mut chars = board_string.chars();
|
||||||
for r in 0..4 {
|
for r in 0..BOARD_SIZE {
|
||||||
for f in 0..4 {
|
for f in 0..BOARD_SIZE {
|
||||||
let c = chars.next().unwrap();
|
let c = chars.next().unwrap();
|
||||||
let piece = match c {
|
let piece = match c {
|
||||||
'K' => Piece::King,
|
'K' => Piece::King,
|
||||||
@ -379,36 +262,41 @@ impl Board {
|
|||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let square = Square::Occupied(piece, Coord::new(f, r));
|
let square = Square::new(f, r, Some(piece));
|
||||||
board.set(square);
|
board.set(square);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(board)
|
Some(board)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn board_state_changed(&mut self) {
|
||||||
|
self.calc_legal_moves();
|
||||||
|
self.calc_game_state();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_square(row: &mut String, square: &Square, pretty: bool) {
|
fn get_square_for_display(piece: &Option<Piece>, pretty: bool) -> String {
|
||||||
let contents = if let Square::Occupied(piece, _) = square {
|
let contents = if let Some(piece) = piece {
|
||||||
if pretty {
|
if pretty {
|
||||||
piece.pretty()
|
piece.pretty()
|
||||||
} else {
|
} else {
|
||||||
piece.notation()
|
piece.notation()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
"."
|
".".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
if pretty {
|
if pretty {
|
||||||
row.push_str(&format!(" {} ", contents));
|
format!(" {} ", contents)
|
||||||
} else {
|
} else {
|
||||||
row.push_str(contents);
|
contents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::engine::cmove::mv;
|
||||||
use crate::engine::piece::p;
|
use crate::engine::piece::p;
|
||||||
use crate::engine::r#move::mv;
|
|
||||||
use crate::engine::square::sq;
|
use crate::engine::square::sq;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -425,7 +313,7 @@ mod tests {
|
|||||||
|
|
||||||
macro_rules! validate_legal_moves {
|
macro_rules! validate_legal_moves {
|
||||||
($board:expr, $($move:expr,)*) => {
|
($board:expr, $($move:expr,)*) => {
|
||||||
let mut legal_moves = $board.legal_moves.iter().map(|m| m.clone()).collect::<Vec<Move>>();
|
let mut legal_moves = $board.legal_moves.iter().map(|m| m.clone()).collect::<Vec<CMove>>();
|
||||||
|
|
||||||
$(
|
$(
|
||||||
assert!(legal_moves.contains(&$move));
|
assert!(legal_moves.contains(&$move));
|
||||||
@ -447,16 +335,16 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_board_place() {
|
fn test_board_place() {
|
||||||
let mut board = Board::new();
|
let mut board = Board::new();
|
||||||
assert!(board.set(sq!("K", "a1")).is_empty());
|
assert!(board.set(sq!("Ka1")).is_none());
|
||||||
assert!(board.set(sq!("Q", "a2")).is_empty());
|
assert!(board.set(sq!("Qa2")).is_none());
|
||||||
assert!(board.set(sq!("B", "c3")).is_empty());
|
assert!(board.set(sq!("Bc3")).is_none());
|
||||||
assert!(board.set(sq!("N", "c4")).is_empty());
|
assert!(board.set(sq!("Nc4")).is_none());
|
||||||
assert!(board.set(sq!("R", "d1")).is_empty());
|
assert!(board.set(sq!("Rd1")).is_none());
|
||||||
assert!(board.set(sq!("P", "d4")).is_empty());
|
assert!(board.set(sq!("Pd4")).is_none());
|
||||||
assert!(board.set(sq!("N", "b2")).is_empty());
|
assert!(board.set(sq!("Nb2")).is_none());
|
||||||
let existing = board.set(sq!("P", "c4"));
|
let existing = board.set(sq!("Pc4"));
|
||||||
assert!(existing.occupied().is_some());
|
assert!(existing.is_some());
|
||||||
assert_eq!(Piece::Knight, existing.occupied().unwrap());
|
assert_eq!(Piece::Knight, existing.unwrap());
|
||||||
validate_board!(board, "..PP", "..B.", "QN..", "K..R");
|
validate_board!(board, "..PP", "..B.", "QN..", "K..R");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,60 +353,62 @@ mod tests {
|
|||||||
let mut board = Board::new();
|
let mut board = Board::new();
|
||||||
assert_eq!(0, board.pieces_remaining);
|
assert_eq!(0, board.pieces_remaining);
|
||||||
assert_eq!(0, board.legal_moves.len());
|
assert_eq!(0, board.legal_moves.len());
|
||||||
assert!(board.make_move(mv!("R", "b2", "d1", "N")).is_none());
|
assert!(board.make_move(mv!("Rb2", "Nd1")).is_none());
|
||||||
|
|
||||||
board.set(sq!("Q", "a4"));
|
board.set(sq!("Qa4"));
|
||||||
board.set(sq!("K", "a2"));
|
board.set(sq!("Ka2"));
|
||||||
board.set(sq!("P", "a1"));
|
board.set(sq!("Pa1"));
|
||||||
board.set(sq!("P", "b3"));
|
board.set(sq!("Pb3"));
|
||||||
board.set(sq!("R", "b2"));
|
board.set(sq!("Rb2"));
|
||||||
board.set(sq!("P", "c4"));
|
board.set(sq!("Pc4"));
|
||||||
board.set(sq!("K", "c3"));
|
board.set(sq!("Kc3"));
|
||||||
board.set(sq!("B", "c1"));
|
board.set(sq!("Bc1"));
|
||||||
board.set(sq!("B", "d2"));
|
board.set(sq!("Bd2"));
|
||||||
board.set(sq!("N", "d1"));
|
board.set(sq!("Nd1"));
|
||||||
|
|
||||||
assert_eq!(10, board.pieces_remaining);
|
assert_eq!(10, board.pieces_remaining);
|
||||||
|
|
||||||
|
board.pretty_print();
|
||||||
|
|
||||||
// Q . P .
|
// Q . P .
|
||||||
// . P K .
|
// . P K .
|
||||||
// K R . B
|
// K R . B
|
||||||
// P . B N
|
// P . B N
|
||||||
validate_legal_moves!(
|
validate_legal_moves!(
|
||||||
board,
|
board,
|
||||||
mv!("K", "a2", "a1", "P"),
|
mv!("Ka2", "Pa1"),
|
||||||
mv!("K", "a2", "b2", "R"),
|
mv!("Ka2", "Rb2"),
|
||||||
mv!("K", "a2", "b3", "P"),
|
mv!("Ka2", "Pb3"),
|
||||||
mv!("K", "c3", "b2", "R"),
|
mv!("Kc3", "Rb2"),
|
||||||
mv!("K", "c3", "b3", "P"),
|
mv!("Kc3", "Pb3"),
|
||||||
mv!("K", "c3", "c4", "P"),
|
mv!("Kc3", "Pc4"),
|
||||||
mv!("K", "c3", "d2", "B"),
|
mv!("Kc3", "Bd2"),
|
||||||
mv!("P", "a1", "b2", "R"),
|
mv!("Pa1", "Rb2"),
|
||||||
mv!("P", "b3", "c4", "P"),
|
mv!("Pb3", "Pc4"),
|
||||||
mv!("P", "b3", "a4", "Q"),
|
mv!("Pb3", "Qa4"),
|
||||||
mv!("Q", "a4", "a2", "K"),
|
mv!("Qa4", "Ka2"),
|
||||||
mv!("Q", "a4", "b3", "P"),
|
mv!("Qa4", "Pb3"),
|
||||||
mv!("Q", "a4", "c4", "P"),
|
mv!("Qa4", "Pc4"),
|
||||||
mv!("R", "b2", "a2", "K"),
|
mv!("Rb2", "Ka2"),
|
||||||
mv!("R", "b2", "b3", "P"),
|
mv!("Rb2", "Pb3"),
|
||||||
mv!("R", "b2", "d2", "B"),
|
mv!("Rb2", "Bd2"),
|
||||||
mv!("B", "c1", "b2", "R"),
|
mv!("Bc1", "Rb2"),
|
||||||
mv!("B", "c1", "d2", "B"),
|
mv!("Bc1", "Bd2"),
|
||||||
mv!("B", "d2", "c3", "K"),
|
mv!("Bd2", "Kc3"),
|
||||||
mv!("B", "d2", "c1", "B"),
|
mv!("Bd2", "Bc1"),
|
||||||
mv!("N", "d1", "b2", "R"),
|
mv!("Nd1", "Rb2"),
|
||||||
mv!("N", "d1", "c3", "K"),
|
mv!("Nd1", "Kc3"),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(10, board.pieces_remaining);
|
assert_eq!(10, board.pieces_remaining);
|
||||||
|
|
||||||
// Validate some illegal moves
|
// Validate some illegal moves
|
||||||
assert!(board.make_move(mv!("K", "a2", "a2", "P")).is_none());
|
assert!(board.make_move(mv!("Ka2", "Pa2")).is_none());
|
||||||
assert!(board.make_move(mv!("R", "b2", "d1", "N")).is_none());
|
assert!(board.make_move(mv!("Rb2", "Nd1")).is_none());
|
||||||
|
|
||||||
board.set(sq!("b2"));
|
board.set(sq!(".b2"));
|
||||||
board.set(sq!("c4"));
|
board.set(sq!(".c4"));
|
||||||
board.set(sq!("R", "c1"));
|
board.set(sq!("Rc1"));
|
||||||
|
|
||||||
// Q . . .
|
// Q . . .
|
||||||
// . P K .
|
// . P K .
|
||||||
@ -526,19 +416,19 @@ mod tests {
|
|||||||
// P . R N
|
// P . R N
|
||||||
validate_legal_moves!(
|
validate_legal_moves!(
|
||||||
board,
|
board,
|
||||||
mv!("K", "a2", "a1", "P"),
|
mv!("Ka2", "Pa1"),
|
||||||
mv!("K", "a2", "b3", "P"),
|
mv!("Ka2", "Pb3"),
|
||||||
mv!("K", "c3", "b3", "P"),
|
mv!("Kc3", "Pb3"),
|
||||||
mv!("K", "c3", "d2", "B"),
|
mv!("Kc3", "Bd2"),
|
||||||
mv!("P", "b3", "a4", "Q"),
|
mv!("Pb3", "Qa4"),
|
||||||
mv!("B", "d2", "c3", "K"),
|
mv!("Bd2", "Kc3"),
|
||||||
mv!("B", "d2", "c1", "R"),
|
mv!("Bd2", "Rc1"),
|
||||||
mv!("Q", "a4", "a2", "K"),
|
mv!("Qa4", "Ka2"),
|
||||||
mv!("Q", "a4", "b3", "P"),
|
mv!("Qa4", "Pb3"),
|
||||||
mv!("R", "c1", "a1", "P"),
|
mv!("Rc1", "Pa1"),
|
||||||
mv!("R", "c1", "c3", "K"),
|
mv!("Rc1", "Kc3"),
|
||||||
mv!("R", "c1", "d1", "N"),
|
mv!("Rc1", "Nd1"),
|
||||||
mv!("N", "d1", "c3", "K"),
|
mv!("Nd1", "Kc3"),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(8, board.pieces_remaining);
|
assert_eq!(8, board.pieces_remaining);
|
||||||
@ -554,21 +444,21 @@ mod tests {
|
|||||||
// . P . .
|
// . P . .
|
||||||
// . . R .
|
// . . R .
|
||||||
// N . . .
|
// N . . .
|
||||||
board.set(sq!("K", "a4"));
|
board.set(sq!("Ka4"));
|
||||||
assert_eq!(GameState::Won, board.game_state);
|
assert_eq!(GameState::Won, board.game_state);
|
||||||
|
|
||||||
board.set(sq!("P", "b3"));
|
board.set(sq!("Pb3"));
|
||||||
board.set(sq!("R", "c2"));
|
board.set(sq!("Rc2"));
|
||||||
board.set(sq!("N", "a1"));
|
board.set(sq!("Na1"));
|
||||||
|
|
||||||
assert_eq!(GameState::InProgress, board.game_state);
|
assert_eq!(GameState::InProgress, board.game_state);
|
||||||
assert_eq!(4, board.pieces_remaining);
|
assert_eq!(4, board.pieces_remaining);
|
||||||
|
|
||||||
assert!(board.make_move(mv!("N", "a1", "c2", "R")).is_some());
|
assert!(board.make_move(mv!("Na1", "Rc2")).is_some());
|
||||||
assert_eq!(3, board.pieces_remaining);
|
assert_eq!(3, board.pieces_remaining);
|
||||||
assert_eq!(GameState::InProgress, board.game_state);
|
assert_eq!(GameState::InProgress, board.game_state);
|
||||||
|
|
||||||
assert!(board.make_move(mv!("P", "b3", "a4", "K")).is_some());
|
assert!(board.make_move(mv!("Pb3", "Ka4")).is_some());
|
||||||
assert_eq!(2, board.pieces_remaining);
|
assert_eq!(2, board.pieces_remaining);
|
||||||
assert_eq!(GameState::Lost, board.game_state);
|
assert_eq!(GameState::Lost, board.game_state);
|
||||||
|
|
||||||
@ -577,8 +467,8 @@ mod tests {
|
|||||||
// . . N .
|
// . . N .
|
||||||
// . . . .
|
// . . . .
|
||||||
|
|
||||||
board.set(sq!("P", "a1"));
|
board.set(sq!("Pa1"));
|
||||||
board.set(sq!("Q", "a3"));
|
board.set(sq!("Qa3"));
|
||||||
|
|
||||||
// P . . .
|
// P . . .
|
||||||
// Q . . .
|
// Q . . .
|
||||||
@ -587,8 +477,8 @@ mod tests {
|
|||||||
assert_eq!(4, board.pieces_remaining);
|
assert_eq!(4, board.pieces_remaining);
|
||||||
assert_eq!(GameState::InProgress, board.game_state);
|
assert_eq!(GameState::InProgress, board.game_state);
|
||||||
|
|
||||||
board.make_move(mv!("Q", "a3", "a4", "P"));
|
board.make_move(mv!("Qa3", "Pa4"));
|
||||||
board.make_move(mv!("N", "c2", "a1", "P"));
|
board.make_move(mv!("Nc2", "Pa1"));
|
||||||
assert_eq!(2, board.pieces_remaining);
|
assert_eq!(2, board.pieces_remaining);
|
||||||
assert_eq!(GameState::InProgress, board.game_state);
|
assert_eq!(GameState::InProgress, board.game_state);
|
||||||
|
|
||||||
@ -596,7 +486,7 @@ mod tests {
|
|||||||
// . . . .
|
// . . . .
|
||||||
// . . . .
|
// . . . .
|
||||||
// N . . .
|
// N . . .
|
||||||
board.make_move(mv!("Q", "a4", "a1", "N"));
|
board.make_move(mv!("Qa4", "Na1"));
|
||||||
assert_eq!(1, board.pieces_remaining);
|
assert_eq!(1, board.pieces_remaining);
|
||||||
assert_eq!(GameState::Won, board.game_state);
|
assert_eq!(GameState::Won, board.game_state);
|
||||||
}
|
}
|
||||||
|
48
src/engine/cmove.rs
Normal file
48
src/engine/cmove.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use super::{board::Board, piece::Piece, square::Square};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Hash, Eq, Clone)]
|
||||||
|
pub(crate) struct CMove {
|
||||||
|
pub(crate) from_piece: Piece,
|
||||||
|
pub(crate) from: Square,
|
||||||
|
pub(crate) to_piece: Piece,
|
||||||
|
pub(crate) to: Square,
|
||||||
|
|
||||||
|
// Used to disambiguate when looking at notation
|
||||||
|
disambig: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CMove {
|
||||||
|
pub(crate) fn new(from: Square, to: Square) -> Self {
|
||||||
|
let qualifier = String::from("");
|
||||||
|
let from_piece = from.piece.expect("Trying to move a blank");
|
||||||
|
let to_piece = to.piece.expect("Trying to capture a blank");
|
||||||
|
CMove {
|
||||||
|
from_piece,
|
||||||
|
from,
|
||||||
|
to_piece,
|
||||||
|
to,
|
||||||
|
disambig: "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn notation(&self) -> String {
|
||||||
|
let piece_qualifier = match &self.from_piece {
|
||||||
|
Piece::Pawn => self.from.file_notation(),
|
||||||
|
p => p.notation(),
|
||||||
|
};
|
||||||
|
format!(
|
||||||
|
"{}{}x{}",
|
||||||
|
piece_qualifier,
|
||||||
|
self.disambig,
|
||||||
|
self.to.notation()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! mv {
|
||||||
|
($from:literal, $to:literal) => {{
|
||||||
|
CMove::new(sq!($from), sq!($to))
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use mv;
|
1
src/engine/constants.rs
Normal file
1
src/engine/constants.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub(crate) const BOARD_SIZE: usize = 4;
|
@ -1,101 +0,0 @@
|
|||||||
use core::fmt;
|
|
||||||
|
|
||||||
#[derive(Clone, Eq, Hash, PartialEq)]
|
|
||||||
pub(crate) struct Coord {
|
|
||||||
// a = 0, b = 1, c = 2, d = 3
|
|
||||||
pub(crate) file: usize,
|
|
||||||
|
|
||||||
// 1 = 0, 2 = 1, 3 = 2, 4 = 3
|
|
||||||
pub(crate) rank: usize,
|
|
||||||
pub(crate) notation: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Coord {
|
|
||||||
pub(crate) fn parse(coord: &str) -> Self {
|
|
||||||
let mut chars = coord.chars();
|
|
||||||
let file = chars.next().expect("file missing");
|
|
||||||
let file = match file {
|
|
||||||
'a' => 0,
|
|
||||||
'b' => 1,
|
|
||||||
'c' => 2,
|
|
||||||
'd' => 3,
|
|
||||||
_ => panic!("file should be between a-d"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let rank = chars.next().unwrap().to_digit(10).expect("rank missing") as usize;
|
|
||||||
if rank < 1 || rank > 4 {
|
|
||||||
panic!("rank should be between 1-4");
|
|
||||||
}
|
|
||||||
let rank = 4 - rank;
|
|
||||||
|
|
||||||
Coord {
|
|
||||||
file,
|
|
||||||
rank,
|
|
||||||
notation: coord.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn new(file: usize, rank: usize) -> Self {
|
|
||||||
Coord {
|
|
||||||
file,
|
|
||||||
rank,
|
|
||||||
notation: Self::get_notation(file, rank),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_notation(file: usize, rank: usize) -> String {
|
|
||||||
format!("{}{}", "abcd".chars().nth(file).unwrap(), 4 - rank)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! at {
|
|
||||||
($coord:literal) => {
|
|
||||||
Coord::parse($coord)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) use at;
|
|
||||||
|
|
||||||
impl fmt::Debug for Coord {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}({},{})", self.notation, self.file, self.rank)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
macro_rules! validate_coord {
|
|
||||||
($notation:literal, $file:expr, $rank:expr) => {
|
|
||||||
let coord = at!($notation);
|
|
||||||
assert_eq!(coord.file, $file);
|
|
||||||
assert_eq!(coord.rank, $rank);
|
|
||||||
assert_eq!(coord.notation, $notation);
|
|
||||||
let coord = Coord::new($file, $rank);
|
|
||||||
assert_eq!(coord.file, $file);
|
|
||||||
assert_eq!(coord.rank, $rank);
|
|
||||||
assert_eq!(coord.notation, $notation);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_coord_parse() {
|
|
||||||
validate_coord!("a1", 0, 3);
|
|
||||||
validate_coord!("a2", 0, 2);
|
|
||||||
validate_coord!("a3", 0, 1);
|
|
||||||
validate_coord!("a4", 0, 0);
|
|
||||||
validate_coord!("b1", 1, 3);
|
|
||||||
validate_coord!("b2", 1, 2);
|
|
||||||
validate_coord!("b3", 1, 1);
|
|
||||||
validate_coord!("b4", 1, 0);
|
|
||||||
validate_coord!("c1", 2, 3);
|
|
||||||
validate_coord!("c2", 2, 2);
|
|
||||||
validate_coord!("c3", 2, 1);
|
|
||||||
validate_coord!("c4", 2, 0);
|
|
||||||
validate_coord!("d1", 3, 3);
|
|
||||||
validate_coord!("d2", 3, 2);
|
|
||||||
validate_coord!("d3", 3, 1);
|
|
||||||
validate_coord!("d4", 3, 0);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
|
pub(crate) mod constants;
|
||||||
pub(crate) mod board;
|
pub(crate) mod board;
|
||||||
pub(crate) mod coord;
|
|
||||||
pub(crate) mod r#move;
|
|
||||||
pub(crate) mod piece;
|
|
||||||
pub(crate) mod square;
|
pub(crate) mod square;
|
||||||
|
pub(crate) mod cmove;
|
||||||
|
pub(crate) mod piece;
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
use super::{board::Board, coord::Coord, piece::Piece, square::Square};
|
|
||||||
|
|
||||||
#[derive(PartialEq, Hash, Eq, Clone)]
|
|
||||||
pub(crate) struct Move {
|
|
||||||
pub(crate) from: Square,
|
|
||||||
pub(crate) to: Square,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Move {
|
|
||||||
pub(crate) fn new(from: Square, to: Square) -> Self {
|
|
||||||
Move { from, to }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn notation(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"{} -> {}",
|
|
||||||
self.from.notation(),
|
|
||||||
self.to.coord_ref().notation
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! mv {
|
|
||||||
($piece:literal, $from:literal, $to:literal, $target:literal) => {
|
|
||||||
Move::new(sq!($piece, $from), sq!($target, $to))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) use mv;
|
|
@ -9,38 +9,43 @@ pub(crate) enum Piece {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Piece {
|
impl Piece {
|
||||||
pub(crate) fn parse(piece: &str) -> Self {
|
pub(crate) fn parse(piece: &str) -> Option<Self> {
|
||||||
match piece {
|
match piece {
|
||||||
"K" => Piece::King,
|
"K" => Some(Piece::King),
|
||||||
"Q" => Piece::Queen,
|
"Q" => Some(Piece::Queen),
|
||||||
"B" => Piece::Bishop,
|
"B" => Some(Piece::Bishop),
|
||||||
"N" => Piece::Knight,
|
"N" => Some(Piece::Knight),
|
||||||
"R" => Piece::Rook,
|
"R" => Some(Piece::Rook),
|
||||||
"P" => Piece::Pawn,
|
"P" => Some(Piece::Pawn),
|
||||||
|
"." => None,
|
||||||
p => panic!("Invalid piece {}", p),
|
p => panic!("Invalid piece {}", p),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn notation(&self) -> &str {
|
pub(crate) fn notation(&self) -> String {
|
||||||
match self {
|
let n = match self {
|
||||||
Piece::King => "K",
|
Piece::King => "K",
|
||||||
Piece::Queen => "Q",
|
Piece::Queen => "Q",
|
||||||
Piece::Bishop => "B",
|
Piece::Bishop => "B",
|
||||||
Piece::Knight => "N",
|
Piece::Knight => "N",
|
||||||
Piece::Rook => "R",
|
Piece::Rook => "R",
|
||||||
Piece::Pawn => "P",
|
Piece::Pawn => "P",
|
||||||
}
|
};
|
||||||
|
|
||||||
|
n.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn pretty(&self) -> &str {
|
pub(crate) fn pretty(&self) -> String {
|
||||||
match self {
|
let n = match self {
|
||||||
Piece::King => "♔",
|
Piece::King => "♔",
|
||||||
Piece::Queen => "♕",
|
Piece::Queen => "♕",
|
||||||
Piece::Bishop => "♗",
|
Piece::Bishop => "♗",
|
||||||
Piece::Knight => "♘",
|
Piece::Knight => "♘",
|
||||||
Piece::Rook => "♖",
|
Piece::Rook => "♖",
|
||||||
Piece::Pawn => "♙",
|
Piece::Pawn => "♙",
|
||||||
}
|
};
|
||||||
|
|
||||||
|
n.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,11 +63,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_piece_parse() {
|
fn test_piece_parse() {
|
||||||
assert_eq!(p!("K"), Piece::King);
|
assert_eq!(p!("K"), Some(Piece::King));
|
||||||
assert_eq!(p!("Q"), Piece::Queen);
|
assert_eq!(p!("Q"), Some(Piece::Queen));
|
||||||
assert_eq!(p!("B"), Piece::Bishop);
|
assert_eq!(p!("B"), Some(Piece::Bishop));
|
||||||
assert_eq!(p!("N"), Piece::Knight);
|
assert_eq!(p!("N"), Some(Piece::Knight));
|
||||||
assert_eq!(p!("R"), Piece::Rook);
|
assert_eq!(p!("R"), Some(Piece::Rook));
|
||||||
assert_eq!(p!("P"), Piece::Pawn);
|
assert_eq!(p!("P"), Some(Piece::Pawn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,57 +1,167 @@
|
|||||||
use super::{coord::Coord, piece::Piece};
|
use crate::engine::constants::BOARD_SIZE;
|
||||||
|
|
||||||
#[derive(Clone, Hash, Eq, PartialEq, Debug)]
|
use super::piece::Piece;
|
||||||
pub(crate) enum Square {
|
use core::fmt;
|
||||||
Empty(Coord),
|
|
||||||
Occupied(Piece, Coord),
|
#[derive(Clone, Eq, Hash, PartialEq)]
|
||||||
|
pub(crate) struct Square {
|
||||||
|
// a = 0, b = 1, c = 2, d = 3 and so on.
|
||||||
|
pub(crate) file: usize,
|
||||||
|
|
||||||
|
// 1 = 0, 2 = 1, 3 = 2, 4 = 3 and so on.
|
||||||
|
pub(crate) rank: usize,
|
||||||
|
|
||||||
|
pub(crate) piece: Option<Piece>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SquarePair {
|
||||||
|
pub(crate) start: Square,
|
||||||
|
pub(crate) end: Square,
|
||||||
|
pub(crate) dx: usize,
|
||||||
|
pub(crate) dy: usize,
|
||||||
|
pub(crate) x_dir: i8,
|
||||||
|
pub(crate) y_dir: i8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Square {
|
impl Square {
|
||||||
pub(crate) fn is_empty(&self) -> bool {
|
pub(crate) fn new(file: usize, rank: usize, piece: Option<Piece>) -> Self {
|
||||||
match self {
|
Square { file, rank, piece }
|
||||||
Square::Empty(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn coord_ref(&self) -> &Coord {
|
pub(crate) fn parse(notation: &str) -> Self {
|
||||||
match self {
|
let mut chars = notation.chars();
|
||||||
Square::Empty(coord) => coord,
|
let piece = chars.next().expect("Piece missing");
|
||||||
Square::Occupied(_, coord) => coord,
|
let piece = Piece::parse(&piece.to_string());
|
||||||
|
let file = chars.next().expect("File missing");
|
||||||
|
let file = match file {
|
||||||
|
'a' => 0,
|
||||||
|
'b' => 1,
|
||||||
|
'c' => 2,
|
||||||
|
'd' => 3,
|
||||||
|
_ => panic!("file should be between a-d"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let rank = chars.next().unwrap().to_digit(10).expect("rank missing") as usize;
|
||||||
|
if rank < 1 || rank > BOARD_SIZE {
|
||||||
|
panic!("rank should be between 1-{}", BOARD_SIZE);
|
||||||
}
|
}
|
||||||
|
let rank = BOARD_SIZE - rank;
|
||||||
|
Square::new(file, rank, piece)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn coord(&self) -> Coord {
|
pub(crate) fn file_notation(&self) -> String {
|
||||||
match self {
|
String::from("abcd".chars().nth(self.file).unwrap())
|
||||||
Square::Empty(coord) => coord.clone(),
|
|
||||||
Square::Occupied(_, coord) => coord.clone(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn occupied(&self) -> Option<Piece> {
|
pub(crate) fn rank_notation(&self) -> String {
|
||||||
match self {
|
format!("{}", BOARD_SIZE - self.rank)
|
||||||
Square::Occupied(piece, _) => Some(*piece),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn notation(&self) -> String {
|
pub(crate) fn notation(&self) -> String {
|
||||||
match self {
|
format!(
|
||||||
Square::Empty(coord) => coord.notation.clone(),
|
"{}{}{}",
|
||||||
Square::Occupied(piece, coord) => {
|
self.piece_notation(),
|
||||||
format!("{}{}", piece.notation(), coord.notation.clone())
|
self.file_notation(),
|
||||||
|
BOARD_SIZE - self.rank
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_occupied(&self) -> bool {
|
||||||
|
self.piece.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn piece_notation(&self) -> String {
|
||||||
|
if self.piece.is_none() {
|
||||||
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
self.piece.unwrap().notation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SquarePair {
|
||||||
|
pub(crate) fn new(start: Square, end: Square) -> Self {
|
||||||
|
let mut dx = 0;
|
||||||
|
let mut dy = 0;
|
||||||
|
let mut x_dir = 0;
|
||||||
|
let mut y_dir = 0;
|
||||||
|
if start.file > end.file {
|
||||||
|
x_dir = -1;
|
||||||
|
dx = start.file - end.file;
|
||||||
|
} else if end.file > start.file {
|
||||||
|
x_dir = 1;
|
||||||
|
dx = end.file - start.file;
|
||||||
|
}
|
||||||
|
|
||||||
|
if start.rank > end.rank {
|
||||||
|
y_dir = -1;
|
||||||
|
dy = start.rank - end.rank;
|
||||||
|
} else if end.rank > start.rank {
|
||||||
|
y_dir = 1;
|
||||||
|
dy = end.rank - start.rank;
|
||||||
|
}
|
||||||
|
|
||||||
|
SquarePair {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
dx,
|
||||||
|
dy,
|
||||||
|
x_dir,
|
||||||
|
y_dir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_different(&self) -> bool {
|
||||||
|
self.dx != 0 || self.dy != 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! sq {
|
macro_rules! sq {
|
||||||
($coord:literal) => {
|
($sq:literal) => {
|
||||||
Square::Empty(at!($coord))
|
Square::parse($sq)
|
||||||
};
|
|
||||||
($piece:literal, $coord:literal) => {
|
|
||||||
Square::Occupied(p!($piece), at!($coord))
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use sq;
|
pub(crate) use sq;
|
||||||
|
|
||||||
|
impl fmt::Debug for Square {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}({},{})", self.notation(), self.file, self.rank)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
macro_rules! validate_square {
|
||||||
|
($notation:literal, $file:expr, $rank:expr) => {
|
||||||
|
let notation = format!("{}{}", "K", $notation);
|
||||||
|
let square = Square::parse(¬ation);
|
||||||
|
assert_eq!(square.file, $file);
|
||||||
|
assert_eq!(square.rank, $rank);
|
||||||
|
assert_eq!(square.piece, Some(Piece::King));
|
||||||
|
assert_eq!(square.notation(), notation);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_square_parse() {
|
||||||
|
validate_square!("a1", 0, 3);
|
||||||
|
validate_square!("a2", 0, 2);
|
||||||
|
validate_square!("a3", 0, 1);
|
||||||
|
validate_square!("a4", 0, 0);
|
||||||
|
validate_square!("b1", 1, 3);
|
||||||
|
validate_square!("b2", 1, 2);
|
||||||
|
validate_square!("b3", 1, 1);
|
||||||
|
validate_square!("b4", 1, 0);
|
||||||
|
validate_square!("c1", 2, 3);
|
||||||
|
validate_square!("c2", 2, 2);
|
||||||
|
validate_square!("c3", 2, 1);
|
||||||
|
validate_square!("c4", 2, 0);
|
||||||
|
validate_square!("d1", 3, 3);
|
||||||
|
validate_square!("d2", 3, 2);
|
||||||
|
validate_square!("d3", 3, 1);
|
||||||
|
validate_square!("d4", 3, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::{fmt::Display, thread::Builder, time::Duration};
|
use std::{fmt::Display, thread::Builder, time::Duration};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
engine::{board::Board, coord::Coord, piece::Piece, square::Square},
|
engine::{board::Board, piece::Piece, square::Square},
|
||||||
solver::{self, solver::Solver},
|
solver::{self, solver::Solver},
|
||||||
};
|
};
|
||||||
use indicatif::ProgressBar;
|
use indicatif::ProgressBar;
|
||||||
@ -20,6 +20,14 @@ pub(crate) fn generate(num_pieces: u32, num_solutions: u32) -> GenerateStats {
|
|||||||
Piece::King,
|
Piece::King,
|
||||||
Piece::Queen,
|
Piece::Queen,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if num_pieces > candidate_pieces.len().try_into().unwrap() {
|
||||||
|
panic!(
|
||||||
|
"Number of pieces to place on the board should be <= {}",
|
||||||
|
candidate_pieces.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let attempts: u32 = 1000;
|
let attempts: u32 = 1000;
|
||||||
let bar = ProgressBar::new_spinner();
|
let bar = ProgressBar::new_spinner();
|
||||||
bar.enable_steady_tick(Duration::from_millis(100));
|
bar.enable_steady_tick(Duration::from_millis(100));
|
||||||
@ -121,9 +129,9 @@ fn try_generate(
|
|||||||
|
|
||||||
let index = rand.gen_range(0..candidate_pieces.len());
|
let index = rand.gen_range(0..candidate_pieces.len());
|
||||||
let piece = candidate_pieces[index];
|
let piece = candidate_pieces[index];
|
||||||
let coord = empty_squares.choose(&mut rand).unwrap().clone();
|
let mut random_square = empty_squares.choose(&mut rand).unwrap().clone();
|
||||||
|
random_square.piece = Some(piece);
|
||||||
board.set(Square::Occupied(piece.clone(), coord.clone()));
|
board.set(random_square.clone());
|
||||||
let solutions = Solver::new(board.clone()).solve();
|
let solutions = Solver::new(board.clone()).solve();
|
||||||
if solutions.len() > 0 {
|
if solutions.len() > 0 {
|
||||||
placed = true;
|
placed = true;
|
||||||
@ -131,7 +139,9 @@ fn try_generate(
|
|||||||
candidate_pieces.remove(index);
|
candidate_pieces.remove(index);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
board.set(Square::Empty(coord));
|
|
||||||
|
random_square.piece = None;
|
||||||
|
board.set(random_square);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +164,7 @@ mod tests {
|
|||||||
fn generator_smoke() {
|
fn generator_smoke() {
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
let gen_stats = generate(5, 5);
|
let gen_stats = generate(5, 5);
|
||||||
let board = gen_stats.board.unwrap();
|
let board = gen_stats.board.expect("No puzzle was generated");
|
||||||
assert_eq!(board.game_state, GameState::InProgress);
|
assert_eq!(board.game_state, GameState::InProgress);
|
||||||
|
|
||||||
let solutions = Solver::new(board).solve();
|
let solutions = Solver::new(board).solve();
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
board::{Board, GameState},
|
board::{Board, GameState},
|
||||||
r#move::Move,
|
cmove::CMove,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) struct Solver {
|
pub(crate) struct Solver {
|
||||||
pub(crate) board: Board,
|
pub(crate) board: Board,
|
||||||
moves: Vec<Move>,
|
moves: Vec<CMove>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Solver {
|
impl Solver {
|
||||||
@ -16,7 +16,7 @@ impl Solver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone(&self, m: Move) -> Self {
|
fn clone(&self, m: CMove) -> Self {
|
||||||
let mut moves = self.moves.clone();
|
let mut moves = self.moves.clone();
|
||||||
let mut board = self.board.clone();
|
let mut board = self.board.clone();
|
||||||
moves.push(m.clone());
|
moves.push(m.clone());
|
||||||
@ -24,7 +24,7 @@ impl Solver {
|
|||||||
Solver { board, moves }
|
Solver { board, moves }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn solve(&self) -> Vec<Vec<Move>> {
|
pub(crate) fn solve(&self) -> Vec<Vec<CMove>> {
|
||||||
let mut solutions = Vec::new();
|
let mut solutions = Vec::new();
|
||||||
if let GameState::Won = self.board.game_state {
|
if let GameState::Won = self.board.game_state {
|
||||||
solutions.push(self.moves.clone());
|
solutions.push(self.moves.clone());
|
||||||
@ -48,7 +48,6 @@ impl Solver {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
coord::{at, Coord},
|
|
||||||
piece::{p, Piece},
|
piece::{p, Piece},
|
||||||
square::{sq, Square},
|
square::{sq, Square},
|
||||||
};
|
};
|
||||||
@ -63,25 +62,23 @@ mod tests {
|
|||||||
// B . B N
|
// B . B N
|
||||||
// P . N .
|
// P . N .
|
||||||
|
|
||||||
board.set(sq!("P", "a1"));
|
board.set(sq!("Pa1"));
|
||||||
board.set(sq!("B", "a2"));
|
board.set(sq!("Ba2"));
|
||||||
board.set(sq!("R", "a3"));
|
board.set(sq!("Ra3"));
|
||||||
board.set(sq!("R", "b4"));
|
board.set(sq!("Rb4"));
|
||||||
board.set(sq!("N", "c1"));
|
board.set(sq!("Nc1"));
|
||||||
board.set(sq!("B", "c2"));
|
board.set(sq!("Bc2"));
|
||||||
board.set(sq!("N", "d2"));
|
board.set(sq!("Nd2"));
|
||||||
board.set(sq!("P", "d3"));
|
board.set(sq!("Pd3"));
|
||||||
|
|
||||||
let solver = Solver::new(board.clone());
|
let solver = Solver::new(board.clone());
|
||||||
let solutions = solver.solve();
|
let solutions = solver.solve();
|
||||||
|
|
||||||
assert_eq!(10, solutions.len());
|
|
||||||
|
|
||||||
for solution in solutions {
|
for solution in solutions {
|
||||||
let mut board = board.clone();
|
let mut board = board.clone();
|
||||||
solution.iter().for_each(|m| {
|
solution
|
||||||
board.make_move(m.clone());
|
.into_iter()
|
||||||
});
|
.for_each(|m| assert!(board.make_move(m).is_some()));
|
||||||
assert_eq!(GameState::Won, board.game_state);
|
assert_eq!(GameState::Won, board.game_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,13 +91,13 @@ mod tests {
|
|||||||
// P . N .
|
// P . N .
|
||||||
|
|
||||||
let mut board = Board::new();
|
let mut board = Board::new();
|
||||||
board.set(sq!("P", "a1"));
|
board.set(sq!("Pa1"));
|
||||||
board.set(sq!("B", "a2"));
|
board.set(sq!("Ba2"));
|
||||||
board.set(sq!("R", "a3"));
|
board.set(sq!("Ra3"));
|
||||||
board.set(sq!("R", "b4"));
|
board.set(sq!("Rb4"));
|
||||||
board.set(sq!("N", "c1"));
|
board.set(sq!("Nc1"));
|
||||||
board.set(sq!("B", "c2"));
|
board.set(sq!("Bc2"));
|
||||||
board.set(sq!("N", "d2"));
|
board.set(sq!("Nd2"));
|
||||||
|
|
||||||
let solver = Solver::new(board.clone());
|
let solver = Solver::new(board.clone());
|
||||||
let solutions = solver.solve();
|
let solutions = solver.solve();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user