Add king moves

This commit is contained in:
cool-mist 2025-01-03 00:29:28 +05:30
parent 111d0a282a
commit 61ddacd20d
10 changed files with 422 additions and 1 deletions

2
.gitignore vendored
View File

@ -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
View 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
View File

@ -0,0 +1,6 @@
[package]
name = "sol_chess"
version = "0.1.0"
edition = "2021"
[dependencies]

177
src/engine/board.rs Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
mod engine;
fn main() {}