add double check resolution + enum iterator
All checks were successful
CI / Saltfish (push) Successful in -2m33s
CI / Saltfish [custom_pieces] (push) Successful in -2m34s

This commit is contained in:
fabolous005 2024-04-20 07:20:45 +02:00
parent f5f7b531d4
commit 9a5f9288d3
9 changed files with 352 additions and 166 deletions

View File

@ -9,9 +9,11 @@ custom_pieces = []
[dependencies] [dependencies]
toml = { version = "0.8.12" } toml = { version = "0.8.12" }
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.198", features = ["derive"] }
serde_derive = { version = "1.0.197" } serde_derive = { version = "1.0.198" }
num_cpus = "1.16.0" num_cpus = "1.16.0"
fern = { version = "0.6.2", features = ["colored"] } fern = { version = "0.6.2", features = ["colored"] }
log = "0.4.21" log = "0.4.21"
humantime = "2.1.0" humantime = "2.1.0"
strum = "0.26.2"
strum_macros = "0.26.2"

237
src/chess/board.rs Normal file
View File

@ -0,0 +1,237 @@
use crate::chess::piece::PieceVariant;
use crate::chess::{moves::MoveVariant, piece::Piece};
use crate::chess::square::Square;
use crate::chess::moves::Move;
use log::{debug, trace};
use super::position::Position;
#[derive(Debug, Default)]
pub struct Castling {
king_side: bool,
queen_side: bool,
}
impl Castling {
pub fn king_side(self) -> bool {
self.king_side
}
pub fn queen_side(self) -> bool {
self.queen_side
}
}
impl Castling {
fn from_fen(fen: String) -> [Self; 2] {
let mut castling = [Castling::default(), Castling::default()];
for c in fen.chars() {
match c {
'K' => castling[0].king_side = true,
'Q' => castling[0].queen_side = true,
'k' => castling[1].king_side = true,
'q' => castling[1].queen_side = true,
'-' => (),
_ => panic!("Invalid castling character"),
}
}
castling
}
}
// TODO: implement halfmove clock and fullmove number
#[derive(Debug)]
pub struct Board {
rows: [[Option<Piece>; 8]; 8],
whites_turn: bool,
castling: [Castling; 2],
en_pessant: Option<Square>,
position: Position,
}
// |-----------------------------------------------|
// | 0;0 | 1;0 | 2;0 | 3;0 | 4;0 | 5;0 | 6;0 | 7;0 |
// |-----------------------------------------------|
// | 0;1 | 1;1 | 2;1 | 3;1 | 4;1 | 5;1 | 6;1 | 7;1 | Black
// |-----------------------------------------------|
// | 0;2 | 1;2 | 2;2 | 3;2 | 4;2 | 5;2 | 6;2 | 7;2 |
// |-----------------------------------------------|
// | 0;3 | 1;3 | 2;3 | 3;3 | 4;3 | 5;3 | 6;3 | 7;3 |
// |-----------------------------------------------|
// | 0;4 | 1;4 | 2;4 | 3;4 | 4;4 | 5;4 | 6;4 | 7;4 |
// |-----------------------------------------------|
// | 0;5 | 1;5 | 2;5 | 3;5 | 4;5 | 5;5 | 6;5 | 7;5 |
// |-----------------------------------------------|
// | 0;6 | 1;6 | 2;6 | 3;6 | 4;6 | 5;6 | 6;6 | 7;6 | White
// |-----------------------------------------------|
// | 0;7 | 1;7 | 2;7 | 3;7 | 4;7 | 5;7 | 6;7 | 7;7 |
// |-----------------------------------------------|
// Chess board repesented in code
//
// NOTE: capture left/right is implemented relative to color
// TODO: make this a doc comment
impl Default for Board {
fn default() -> Self {
Board {
rows: [
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
],
whites_turn: true,
castling: [
Castling { king_side: false, queen_side: false },
Castling { king_side: false, queen_side: false },
],
en_pessant: None,
position: Position::default(),
}
}
}
/// Creating Position
impl Board {
pub fn new() -> Self {
Board::default()
}
pub fn from_fen(fen: String) -> Self {
trace!("{fen}");
let mut pos = Board::default();
let mut parts = fen.split_whitespace();
trace!("{:?}", parts);
// parse board
parts.next().unwrap().split('/').enumerate().for_each(|(y, row)| {
let mut x = 0;
row.chars().for_each(|c| {
if c.is_ascii_digit() {
x += c.to_digit(10).unwrap();
} else {
pos.rows[y][x as usize] = Piece::from_fen(c);
x += 1;
}
});
});
// parse active color
pos.whites_turn = parts.next().unwrap().starts_with('w');
// parse castling
pos.castling = Castling::from_fen(parts.next().unwrap().to_string());
// parse en passent
pos.en_pessant = Square::en_passent_from_fen(parts.next().unwrap().to_string());
pos
}
/*
TODO: Decide if Position should be mutable or not
pub fn set_rows(mut self, rows: [[Option<Piece>; 8]; 8]) -> Self {
self.rows = rows;
self
}
pub fn set_whites_turn(mut self, whites_turn: bool) -> Self {
self.whites_turn = whites_turn;
self
}
pub fn set_castling(mut self, castling: [Castling; 2]) -> Self {
self.castling = castling;
self
}
pub fn set_en_passent(mut self, en_passent: Option<Square>) -> Self {
self.en_pessant = en_passent;
self
}
*/
}
/// Exposing position fields
impl Board {
pub fn rows(self) -> [[Option<Piece>; 8]; 8] {
self.rows
}
pub fn whites_turn(self) -> bool {
self.whites_turn
}
pub fn castling(self) -> [Castling; 2] {
self.castling
}
pub fn en_passent(self) -> Option<Square> {
self.en_pessant
}
pub fn position(self) -> Position {
self.position
}
pub fn ref_rows(&mut self) -> &mut [[Option<Piece>; 8]; 8] {
&mut self.rows
}
pub fn ref_whites_turn(&mut self) -> &mut bool {
&mut self.whites_turn
}
pub fn ref_castling(&mut self) -> &mut [Castling; 2] {
&mut self.castling
}
pub fn ref_en_passent(&mut self) -> &mut Option<Square> {
&mut self.en_pessant
}
pub fn ref_position(&mut self) -> &mut Position {
&mut self.position
}
}
impl Board {
pub fn king_pos(&self, white: bool) -> Square {
for (y, row) in self.rows.iter().enumerate() {
for (x, piece) in row.iter().enumerate() {
if Some(Piece::new(white, PieceVariant::King)) == *piece {
return Square::new(x as u8, y as u8);
}
}
}
panic!("No King found on board");
}
}
/// Use position
impl Board {
pub fn get_moves(&mut self) -> Vec<Move> {
if self.ref_position().ref_check().is_some() {
if self.ref_position().ref_check().unwrap() {
let king_pos = self.king_pos(self.whites_turn);
self.ref_position().ref_set_moves(MoveVariant::king_move(king_pos));
} else {
todo!("take, block or king move");
}
}
todo!("finish");
}
pub fn threatened(&self, square: Square) -> bool {
todo!();
}
pub fn threatened_by(&self, square: Square) -> Square {
todo!();
}
}

View File

@ -1,5 +1,6 @@
// TODO: remove this pub later // TODO: remove this pub later
pub mod position; pub mod board;
pub mod piece; pub mod piece;
pub mod moves; pub mod moves;
mod position;
mod square; mod square;

View File

@ -3,6 +3,7 @@ use super::{piece::PieceVariant, square::Square};
#[cfg(feature = "custom_pieces")] #[cfg(feature = "custom_pieces")]
use serde_derive::Deserialize; use serde_derive::Deserialize;
#[derive(Debug)]
pub struct Move { pub struct Move {
from: Square, from: Square,
to: Square, to: Square,
@ -42,7 +43,24 @@ impl Change {
} }
} }
impl Change {
pub fn x(self) -> i8 {
self.x
}
pub fn y(self) -> i8 {
self.y
}
pub fn ref_x(&self) -> &i8 {
&self.x
}
pub fn ref_y(&self) -> &i8 {
&self.y
}
}
#[derive(strum_macros::EnumIter)]
pub enum MoveVariant { pub enum MoveVariant {
King, King,
KingCastle, KingCastle,
@ -75,6 +93,20 @@ impl MoveVariant {
Change { x: -1, y: -1 }, Change { x: -1, y: -1 },
] ]
} }
pub fn king_move(current: Square) -> Vec<Move> {
let mut moves = Vec::new();
for change in Self::king().iter() {
let new_square = current.add_change(change);
if let Some(new_square) = new_square {
moves.push(Move {
from: current.clone(),
to: new_square,
promotion: None
});
}
}
moves
}
pub fn queen() -> [Change; 8] { pub fn queen() -> [Change; 8] {
[ [

View File

@ -2,7 +2,7 @@
#[cfg(feature = "custom_pieces")] #[cfg(feature = "custom_pieces")]
use serde_derive::Deserialize; use serde_derive::Deserialize;
#[derive(Debug)] #[derive(PartialEq, Debug)]
pub enum PieceVariant { pub enum PieceVariant {
King, King,
Queen, Queen,
@ -12,7 +12,7 @@ pub enum PieceVariant {
Pawn Pawn
} }
#[derive(Debug)] #[derive(PartialEq, Debug)]
pub struct Piece { pub struct Piece {
white: bool, white: bool,
variant: PieceVariant variant: PieceVariant
@ -46,7 +46,7 @@ impl Piece {
#[cfg(feature = "custom_pieces")] #[cfg(feature = "custom_pieces")]
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub enum Iterability { pub enum Iterability {
Infinite(u8), Infinite,
ForcedNum(u8), ForcedNum(u8),
Range([u8; 2]) Range([u8; 2])
} }
@ -54,7 +54,7 @@ pub enum Iterability {
#[cfg(feature = "custom_pieces")] #[cfg(feature = "custom_pieces")]
impl Default for Iterability { impl Default for Iterability {
fn default() -> Self { fn default() -> Self {
Self::Infinite(0) Self::Infinite
} }
} }

View File

@ -1,177 +1,62 @@
use crate::chess::piece::Piece;
use crate::chess::square::Square;
use crate::chess::moves::Move; use crate::chess::moves::Move;
use log::{debug, trace};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Castling {
king_side: bool,
queen_side: bool,
}
impl Castling {
pub fn king_side(self) -> bool {
self.king_side
}
pub fn queen_side(self) -> bool {
self.queen_side
}
}
impl Castling {
fn from_fen(fen: String) -> [Self; 2] {
let mut castling = [Castling::default(), Castling::default()];
for c in fen.chars() {
match c {
'K' => castling[0].king_side = true,
'Q' => castling[0].queen_side = true,
'k' => castling[1].king_side = true,
'q' => castling[1].queen_side = true,
'-' => (),
_ => panic!("Invalid castling character"),
}
}
castling
}
}
// TODO: implement halfmove clock and fullmove number
#[derive(Debug)]
pub struct Position { pub struct Position {
rows: [[Option<Piece>; 8]; 8], // None => not in check
whites_turn: bool, // Some(false) => single check
castling: [Castling; 2], // Some(true) => double check
en_pessant: Option<Square> check: Option<bool>,
castle_possibilities: [[bool; 2]; 2],
moves: Vec<Move>,
} }
// |-----------------------------------------------|
// | 0;0 | 1;0 | 2;0 | 3;0 | 4;0 | 5;0 | 6;0 | 7;0 |
// |-----------------------------------------------|
// | 0;1 | 1;1 | 2;1 | 3;1 | 4;1 | 5;1 | 6;1 | 7;1 | Black
// |-----------------------------------------------|
// | 0;2 | 1;2 | 2;2 | 3;2 | 4;2 | 5;2 | 6;2 | 7;2 |
// |-----------------------------------------------|
// | 0;3 | 1;3 | 2;3 | 3;3 | 4;3 | 5;3 | 6;3 | 7;3 |
// |-----------------------------------------------|
// | 0;4 | 1;4 | 2;4 | 3;4 | 4;4 | 5;4 | 6;4 | 7;4 |
// |-----------------------------------------------|
// | 0;5 | 1;5 | 2;5 | 3;5 | 4;5 | 5;5 | 6;5 | 7;5 |
// |-----------------------------------------------|
// | 0;6 | 1;6 | 2;6 | 3;6 | 4;6 | 5;6 | 6;6 | 7;6 | White
// |-----------------------------------------------|
// | 0;7 | 1;7 | 2;7 | 3;7 | 4;7 | 5;7 | 6;7 | 7;7 |
// |-----------------------------------------------|
// Chess board repesented in code
//
// NOTE: capture left/right is implemented relative to color
// TODO: make this a doc comment
impl Default for Position {
fn default() -> Self {
Position {
rows: [
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
[None, None, None, None, None, None, None, None],
],
whites_turn: true,
castling: [
Castling { king_side: false, queen_side: false },
Castling { king_side: false, queen_side: false },
],
en_pessant: None
}
}
}
/// Creating Position
impl Position { impl Position {
pub fn new() -> Self { pub fn check(self) -> Option<bool> {
Position::default() self.check
}
pub fn castle_possibilities(self) -> [[bool; 2]; 2] {
self.castle_possibilities
}
pub fn moves(self) -> Vec<Move> {
self.moves
} }
pub fn from_fen(fen: String) -> Self { pub fn ref_check(&self) -> &Option<bool> {
trace!("{fen}"); &self.check
let mut pos = Position::default();
let mut parts = fen.split_whitespace();
trace!("{:?}", parts);
// parse board
parts.next().unwrap().split('/').enumerate().for_each(|(y, row)| {
let mut x = 0;
row.chars().for_each(|c| {
if c.is_ascii_digit() {
x += c.to_digit(10).unwrap();
} else {
pos.rows[y][x as usize] = Piece::from_fen(c);
x += 1;
} }
}); pub fn ref_castle_possibilities(&self) -> &[[bool; 2]; 2] {
}); &self.castle_possibilities
// parse active color
pos.whites_turn = parts.next().unwrap().starts_with('w');
// parse castling
pos.castling = Castling::from_fen(parts.next().unwrap().to_string());
// parse en passent
pos.en_pessant = Square::en_passent_from_fen(parts.next().unwrap().to_string());
pos
} }
pub fn ref_moves(&self) -> &Vec<Move> {
/* &self.moves
TODO: Decide if Position should be mutable or not
pub fn set_rows(mut self, rows: [[Option<Piece>; 8]; 8]) -> Self {
self.rows = rows;
self
} }
pub fn set_whites_turn(mut self, whites_turn: bool) -> Self {
self.whites_turn = whites_turn;
self
}
pub fn set_castling(mut self, castling: [Castling; 2]) -> Self {
self.castling = castling;
self
}
pub fn set_en_passent(mut self, en_passent: Option<Square>) -> Self {
self.en_pessant = en_passent;
self
}
*/
} }
/// Exposing position fields
impl Position { impl Position {
pub fn rows(self) -> [[Option<Piece>; 8]; 8] { pub fn set_check(mut self, check: Option<bool>) -> Self {
self.rows self.check = check;
self
} }
pub fn set_castle_possibilities(mut self, castle_possibilities: [[bool; 2]; 2]) -> Self {
pub fn whites_turn(self) -> bool { self.castle_possibilities = castle_possibilities;
self.whites_turn self
} }
pub fn set_moves(mut self, moves: Vec<Move>) -> Self {
pub fn castling(self) -> [Castling; 2] { self.moves = moves;
self.castling self
}
pub fn en_passent(self) -> Option<Square> {
self.en_pessant
} }
} }
/// Use position
impl Position { impl Position {
pub fn get_moves(self) -> Vec<Move> { pub fn ref_set_check(&mut self, check: Option<bool>) {
todo!("finish") self.check = check;
}
pub fn ref_set_castle_possibilities(&mut self, castle_possibilities: [[bool; 2]; 2]) {
self.castle_possibilities = castle_possibilities;
}
pub fn ref_set_moves(&mut self, moves: Vec<Move>) {
self.moves = moves;
} }
} }

View File

@ -1,6 +1,8 @@
use log::{debug, trace}; use log::{debug, trace};
#[derive(Debug)] use super::moves::Change;
#[derive(Debug, Clone)]
pub struct Square { pub struct Square {
x: u8, x: u8,
y: u8, y: u8,
@ -24,3 +26,30 @@ impl Square {
} }
} }
} }
impl Square {
pub fn x(self) -> u8 {
self.x
}
pub fn y(self) -> u8 {
self.y
}
pub fn ref_x(&self) -> &u8 {
&self.x
}
pub fn ref_y(&self) -> &u8 {
&self.y
}
}
impl Square {
pub fn add_change(&self, change: &Change) -> Option<Self> {
let x = self.x as i8 + change.ref_x();
let y = self.y as i8 + change.ref_y();
if !(0..7).contains(&x) || !(0..7).contains(&y) {
None
} else {
Some(Square::new(x as u8, y as u8))
}
}
}

View File

@ -13,7 +13,7 @@ use clap::Parser;
pub fn get_config() -> Option<Config> { pub fn get_config() -> Option<Config> {
let args = Args::try_parse().or_else(|err|{ let args = Args::try_parse().or_else(|err|{
if err.use_stderr() { if ! err.use_stderr() {
println!("{}", err); println!("{}", err);
return Err(()) return Err(())
} }

View File

@ -5,7 +5,7 @@
)] )]
use crate::{chess::position::Position, config::get_config}; use crate::{chess::board::Board, config::get_config};
use log::trace; use log::trace;
mod chess; mod chess;
@ -15,9 +15,9 @@ mod logging;
fn main() { fn main() {
let config = get_config().unwrap(); let config = get_config().unwrap_or_else(||panic!(""));
trace!("{:?}", config); trace!("{:?}", config);
let board = Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1".to_string()); let board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1".to_string());
trace!("{:?}", board); trace!("{:?}", board);
} }