Add king moves
This commit is contained in:
parent
111d0a282a
commit
61ddacd20d
2
.gitignore
vendored
2
.gitignore
vendored
@ -18,4 +18,4 @@ Cargo.lock
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
.idea/
|
||||
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "sol_chess"
|
||||
version = "0.1.0"
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "sol_chess"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
177
src/engine/board.rs
Normal file
177
src/engine/board.rs
Normal file
@ -0,0 +1,177 @@
|
||||
use super::{
|
||||
coord::{at, Coord},
|
||||
piece::Piece,
|
||||
square::Square,
|
||||
};
|
||||
|
||||
pub(crate) struct Board {
|
||||
cells: [[Square; 4]; 4],
|
||||
}
|
||||
|
||||
pub(crate) struct Move {
|
||||
piece: Piece,
|
||||
from: Coord,
|
||||
to: Coord,
|
||||
target: Piece,
|
||||
}
|
||||
|
||||
impl Board {
|
||||
pub(crate) fn new() -> Self {
|
||||
Board {
|
||||
cells: [
|
||||
[
|
||||
Square::Empty(at!("a4")),
|
||||
Square::Empty(at!("b4")),
|
||||
Square::Empty(at!("c4")),
|
||||
Square::Empty(at!("d4")),
|
||||
],
|
||||
[
|
||||
Square::Empty(at!("a3")),
|
||||
Square::Empty(at!("b3")),
|
||||
Square::Empty(at!("c3")),
|
||||
Square::Empty(at!("d3")),
|
||||
],
|
||||
[
|
||||
Square::Empty(at!("a2")),
|
||||
Square::Empty(at!("b2")),
|
||||
Square::Empty(at!("c2")),
|
||||
Square::Empty(at!("d2")),
|
||||
],
|
||||
[
|
||||
Square::Empty(at!("a1")),
|
||||
Square::Empty(at!("b1")),
|
||||
Square::Empty(at!("c1")),
|
||||
Square::Empty(at!("d1")),
|
||||
],
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn place(&mut self, square: Square) -> Square {
|
||||
let coord = square.coord();
|
||||
std::mem::replace(&mut self.cells[coord.file][coord.rank], square)
|
||||
}
|
||||
|
||||
pub(crate) fn legal_moves(&self) -> Vec<Move> {
|
||||
let mut legal_moves = Vec::new();
|
||||
for file in 0..4 {
|
||||
for rank in 0..4 {
|
||||
if let Square::Occupied(piece, _) = self.cells[file][rank] {
|
||||
let mut moves = match piece {
|
||||
Piece::King => self.king_legal_moves(Coord::new(file, rank)),
|
||||
_ => Vec::with_capacity(0),
|
||||
};
|
||||
|
||||
legal_moves.append(&mut moves);
|
||||
}
|
||||
}
|
||||
}
|
||||
legal_moves
|
||||
}
|
||||
|
||||
fn king_legal_moves(&self, start: Coord) -> Vec<Move> {
|
||||
let mut candidates = Vec::new();
|
||||
let x = start.file;
|
||||
let y = start.rank;
|
||||
for file in (x.saturating_sub(1))..(x.saturating_add(2)) {
|
||||
for rank in (y.saturating_sub(1))..(y.saturating_add(2)) {
|
||||
if file == x && rank == y {
|
||||
continue;
|
||||
}
|
||||
let target = &self.cells[file][rank];
|
||||
if let Square::Occupied(piece, _) = target {
|
||||
candidates.push(Move {
|
||||
piece: Piece::King,
|
||||
from: Coord::new(x, y),
|
||||
to: Coord::new(file, rank),
|
||||
target: *piece,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
candidates
|
||||
}
|
||||
|
||||
pub(crate) fn print(&self) -> String {
|
||||
let mut builder: Vec<char> = Vec::new();
|
||||
for rank in 0..4 {
|
||||
for file in 0..4 {
|
||||
match self.cells[file][rank] {
|
||||
Square::Empty(_) => builder.push('.'),
|
||||
Square::Occupied(Piece::King, _) => builder.push('K'),
|
||||
Square::Occupied(Piece::Queen, _) => builder.push('Q'),
|
||||
Square::Occupied(Piece::Bishop, _) => builder.push('B'),
|
||||
Square::Occupied(Piece::Knight, _) => builder.push('N'),
|
||||
Square::Occupied(Piece::Rook, _) => builder.push('R'),
|
||||
Square::Occupied(Piece::Pawn, _) => builder.push('P'),
|
||||
}
|
||||
}
|
||||
builder.push('\n');
|
||||
}
|
||||
|
||||
builder.iter().collect::<String>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::engine::piece::p;
|
||||
use crate::engine::square::sq;
|
||||
|
||||
use super::*;
|
||||
|
||||
macro_rules! validate_board {
|
||||
($board:expr, $row1:literal, $row2:literal, $row3:literal, $row4:literal) => {
|
||||
let printed = $board.print();
|
||||
assert_eq!(
|
||||
printed,
|
||||
format!("{}\n{}\n{}\n{}\n", $row1, $row2, $row3, $row4)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_board_place() {
|
||||
let mut board = Board::new();
|
||||
assert!(board.place(sq!("K", "a1")).is_empty());
|
||||
assert!(board.place(sq!("Q", "a2")).is_empty());
|
||||
assert!(board.place(sq!("B", "c3")).is_empty());
|
||||
assert!(board.place(sq!("N", "c4")).is_empty());
|
||||
assert!(board.place(sq!("R", "d1")).is_empty());
|
||||
assert!(board.place(sq!("P", "d4")).is_empty());
|
||||
assert!(board.place(sq!("N", "b2")).is_empty());
|
||||
let existing = board.place(sq!("P", "c4"));
|
||||
assert!(existing.occupied().is_some());
|
||||
assert_eq!(Piece::Knight, existing.occupied().unwrap());
|
||||
validate_board!(board, "..PP", "..B.", "QN..", "K..R");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_legal_moves_king_corner() {
|
||||
let mut board = Board::new();
|
||||
board.place(sq!("K", "a2"));
|
||||
board.place(sq!("P", "a1"));
|
||||
board.place(sq!("P", "c4"));
|
||||
|
||||
let legal_moves = board.legal_moves();
|
||||
assert_eq!(legal_moves.len(), 1);
|
||||
|
||||
board.place(sq!("P", "b1"));
|
||||
let legal_moves = board.legal_moves();
|
||||
assert_eq!(legal_moves.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_legal_moves_king_center() {
|
||||
let mut board = Board::new();
|
||||
board.place(sq!("K", "c3"));
|
||||
board.place(sq!("P", "a1"));
|
||||
board.place(sq!("P", "c4"));
|
||||
board.place(sq!("P", "b2"));
|
||||
board.place(sq!("P", "b3"));
|
||||
|
||||
let legal_moves = board.legal_moves();
|
||||
assert_eq!(legal_moves.len(), 3);
|
||||
}
|
||||
}
|
97
src/engine/coord.rs
Normal file
97
src/engine/coord.rs
Normal file
@ -0,0 +1,97 @@
|
||||
use core::fmt;
|
||||
|
||||
#[derive(Clone, 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(rank: usize, file: 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);
|
||||
};
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
5
src/engine/mod.rs
Normal file
5
src/engine/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub(crate) mod board;
|
||||
pub(crate) mod coord;
|
||||
pub(crate) mod r#move;
|
||||
pub(crate) mod piece;
|
||||
pub(crate) mod square;
|
28
src/engine/move.rs
Normal file
28
src/engine/move.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use super::{coord::Coord, piece::Piece};
|
||||
|
||||
pub(crate) struct Move {
|
||||
piece: Piece,
|
||||
from: Coord,
|
||||
to: Coord,
|
||||
target: Piece,
|
||||
}
|
||||
|
||||
impl Move {
|
||||
pub(crate) fn new(piece: Piece, from: Coord, to: Coord, target: Piece) -> Self {
|
||||
Move {
|
||||
piece,
|
||||
from,
|
||||
to,
|
||||
target,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn notation(&self) -> String {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
self.piece.notation(),
|
||||
self.from.notation,
|
||||
self.to.notation
|
||||
)
|
||||
}
|
||||
}
|
57
src/engine/piece.rs
Normal file
57
src/engine/piece.rs
Normal file
@ -0,0 +1,57 @@
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub(crate) enum Piece {
|
||||
King,
|
||||
Queen,
|
||||
Bishop,
|
||||
Knight,
|
||||
Rook,
|
||||
Pawn,
|
||||
}
|
||||
|
||||
impl Piece {
|
||||
pub(crate) fn parse(piece: &str) -> Self {
|
||||
match piece {
|
||||
"K" => Piece::King,
|
||||
"Q" => Piece::Queen,
|
||||
"B" => Piece::Bishop,
|
||||
"N" => Piece::Knight,
|
||||
"R" => Piece::Rook,
|
||||
"P" => Piece::Pawn,
|
||||
p => panic!("Invalid piece {}", p),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn notation(&self) -> &str {
|
||||
match self {
|
||||
Piece::King => "K",
|
||||
Piece::Queen => "Q",
|
||||
Piece::Bishop => "B",
|
||||
Piece::Knight => "N",
|
||||
Piece::Rook => "R",
|
||||
Piece::Pawn => "P",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! p {
|
||||
($piece:literal) => {
|
||||
Piece::parse($piece)
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use p;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_piece_parse() {
|
||||
assert_eq!(p!("K"), Piece::King);
|
||||
assert_eq!(p!("Q"), Piece::Queen);
|
||||
assert_eq!(p!("B"), Piece::Bishop);
|
||||
assert_eq!(p!("N"), Piece::Knight);
|
||||
assert_eq!(p!("R"), Piece::Rook);
|
||||
assert_eq!(p!("P"), Piece::Pawn);
|
||||
}
|
||||
}
|
41
src/engine/square.rs
Normal file
41
src/engine/square.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use super::{coord::Coord, piece::Piece};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum Square {
|
||||
Empty(Coord),
|
||||
Occupied(Piece, Coord),
|
||||
}
|
||||
|
||||
impl Square {
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Square::Empty(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn coord(&self) -> Coord {
|
||||
match self {
|
||||
Square::Empty(coord) => coord.clone(),
|
||||
Square::Occupied(_, coord) => coord.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn occupied(&self) -> Option<Piece> {
|
||||
match self {
|
||||
Square::Occupied(piece, _) => Some(*piece),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! sq {
|
||||
($coord:literal) => {
|
||||
Square::Empty(at!($coord))
|
||||
};
|
||||
($piece:literal, $coord:literal) => {
|
||||
Square::Occupied(p!($piece), at!($coord))
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use sq;
|
3
src/main.rs
Normal file
3
src/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod engine;
|
||||
|
||||
fn main() {}
|
Loading…
x
Reference in New Issue
Block a user