diff --git a/bitboard/src/bitboard.rs b/bitboard/src/bitboard.rs index 3858ebb..f9babd4 100644 --- a/bitboard/src/bitboard.rs +++ b/bitboard/src/bitboard.rs @@ -2,7 +2,7 @@ use crate::library::{library, FILES, RANKS}; use crate::LeadingBitScanner; -use chessfriend_core::{Direction, Square}; +use chessfriend_core::{Color, Direction, Square}; use std::fmt; use std::ops::Not; diff --git a/board/src/macros.rs b/board/src/macros.rs index 0ee322b..13d2c38 100644 --- a/board/src/macros.rs +++ b/board/src/macros.rs @@ -28,4 +28,18 @@ macro_rules! test_position { pos } }; + (empty) => { + { + let pos = Position::empty(); + println!("{pos}"); + pos + } + }; + (starting) => { + { + let pos = Position::starting(); + println!("{pos}"); + pos + } + }; } diff --git a/board/src/move_generator/king.rs b/board/src/move_generator/king.rs index 1d0dda2..1f35af5 100644 --- a/board/src/move_generator/king.rs +++ b/board/src/move_generator/king.rs @@ -74,7 +74,7 @@ impl MoveGeneratorInternal for KingMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::position; + use crate::{position, r#move::AlgebraicMoveFormatter, PositionBuilder}; use chessfriend_bitboard::bitboard; use chessfriend_core::{piece, Square}; use std::collections::HashSet; @@ -154,4 +154,40 @@ mod tests { generated_moves ); } + + #[test] + fn black_king_in_check_by_rook() { + let pos = PositionBuilder::new() + .place_piece(piece!(White King on E1)) + .place_piece(piece!(White Rook on E4)) + .place_piece(piece!(Black King on E7)) + .to_move(Color::Black) + .build(); + + assert!(pos.is_king_in_check()); + + let generator = KingMoveGenerator::new(&pos, Color::Black); + let generated_moves: HashSet = generator.iter().cloned().collect(); + + let king = piece!(Black King); + let from_square = Square::E7; + let expected_moves = HashSet::from_iter([ + MoveBuilder::new(king, from_square, Square::D6).build(), + MoveBuilder::new(king, from_square, Square::D7).build(), + MoveBuilder::new(king, from_square, Square::D8).build(), + MoveBuilder::new(king, from_square, Square::F6).build(), + MoveBuilder::new(king, from_square, Square::F7).build(), + MoveBuilder::new(king, from_square, Square::F8).build(), + ]); + + assert_eq!( + generated_moves, + expected_moves, + "Difference: {:?}", + generated_moves + .symmetric_difference(&expected_moves) + .map(|mv| format!("{}", AlgebraicMoveFormatter::new(mv, &pos))) + .collect::>() + ); + } } diff --git a/board/src/position/diagram_formatter.rs b/board/src/position/diagram_formatter.rs index 80bbbe0..9dfb5c7 100644 --- a/board/src/position/diagram_formatter.rs +++ b/board/src/position/diagram_formatter.rs @@ -41,7 +41,6 @@ impl<'a> fmt::Display for DiagramFormatter<'a> { mod tests { use super::*; use crate::{position, Position}; - use chessfriend_core::piece; #[test] #[ignore] diff --git a/board/src/position/position.rs b/board/src/position/position.rs index acb6efa..6a09226 100644 --- a/board/src/position/position.rs +++ b/board/src/position/position.rs @@ -333,8 +333,7 @@ mod tests { #[test] fn piece_in_starting_position() { - let pos = Position::starting(); - println!("{pos}"); + let pos = test_position!(starting); assert_eq!( pos.piece_on_square(Square::H1), diff --git a/board/src/sight.rs b/board/src/sight.rs index eead56e..76005a2 100644 --- a/board/src/sight.rs +++ b/board/src/sight.rs @@ -1,9 +1,8 @@ // Eryn Wells use crate::position::piece_sets::PieceBitBoards; -use crate::Position; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square}; +use chessfriend_core::{Color, Direction, PlacedPiece, Shape, Square}; pub(crate) trait SightExt { fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option) -> BitBoard; @@ -13,11 +12,13 @@ pub(crate) trait SightExt { pieces: &PieceBitBoards, en_passant_square: Option, ) -> BitBoard; + fn black_pawn_sight( &self, pieces: &PieceBitBoards, en_passant_square: Option, ) -> BitBoard; + fn knight_sight(&self, pieces: &PieceBitBoards) -> BitBoard; fn bishop_sight(&self, pieces: &PieceBitBoards) -> BitBoard; fn rook_sight(&self, pieces: &PieceBitBoards) -> BitBoard; diff --git a/explorer/Cargo.toml b/explorer/Cargo.toml index 3281cf4..aabf2e3 100644 --- a/explorer/Cargo.toml +++ b/explorer/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] chessfriend_core = { path = "../core" } -chessfriend_position = { path = "../position" } +chessfriend_position = { path = "../board" } clap = { version = "4.4.12", features = ["derive"] } rustyline = "13.0.0" shlex = "1.2.0" diff --git a/explorer/src/main.rs b/explorer/src/main.rs index 9bf7000..8edb00e 100644 --- a/explorer/src/main.rs +++ b/explorer/src/main.rs @@ -1,4 +1,4 @@ -use chessfriend_core::Square; +use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; use chessfriend_position::{fen::ToFen, MakeMoveBuilder, MoveBuilder, Position, PositionBuilder}; use clap::{Arg, Command}; use rustyline::error::ReadlineError; @@ -29,28 +29,31 @@ fn command_line() -> Command { {all-args} "; - // strip out name/version - const APPLET_TEMPLATE: &str = "\ - {about-with-newline}\n\ - {usage-heading}\n {usage}\n\ - \n\ - {all-args}{after-help}\ - "; - Command::new("explorer") .multicall(true) .arg_required_else_help(true) .subcommand_required(true) - .subcommand_value_name("APPLET") - .subcommand_help_heading("APPLETS") + .subcommand_value_name("CMD") + .subcommand_help_heading("COMMANDS") .help_template(PARSER_TEMPLATE) - .subcommand(Command::new("print").about("Print the board")) + .subcommand(Command::new("fen").about("Print the current position as a FEN string")) + .subcommand( + Command::new("make") + .arg(Arg::new("piece").required(true)) + .arg(Arg::new("from").required(true)) + .arg(Arg::new("to").required(true)) + .about("Make a move"), + ) .subcommand( Command::new("place") + .arg(Arg::new("color").required(true)) .arg(Arg::new("piece").required(true)) - .arg(Arg::new("square").required(true)), + .arg(Arg::new("square").required(true)) + .about("Place a piece on the board"), ) - .subcommand(Command::new("quit").about("Quit the program")) + .subcommand(Command::new("print").about("Print the board")) + .subcommand(Command::new("quit").alias("exit").about("Quit the program")) + .subcommand(Command::new("starting").about("Reset the board to the starting position")) } fn respond(line: &str, state: &mut State) -> Result { @@ -67,20 +70,72 @@ fn respond(line: &str, state: &mut State) -> Result { result.should_continue = false; result.should_print_position = false; } - Some(("place", _matches)) => { - let piece_arg = _matches.get_one::("piece").unwrap(); - let shape = match piece_arg.chars().nth(0) { - Some(c) => Shape::try_from(c).map_err(|_| ()), - None => Err(()), - } - .map_err(|_| "Error: invalid piece specifier")?; + Some(("fen", _matches)) => { + println!( + "{}", + state + .position + .to_fen() + .map_err(|_| "error: Unable to generate FEN for current position")? + ); - let square_arg = _matches.get_one::("square").unwrap(); - let square = Square::from_algebraic_str(square_arg) + result.should_print_position = false; + } + Some(("make", matches)) => { + let shape = matches + .get_one::("piece") + .ok_or("Missing piece descriptor")?; + let shape = Shape::try_from(shape).map_err(|_| "Invalid piece descriptor")?; + + let from_square = Square::from_algebraic_str( + matches.get_one::("from").ok_or("Missing square")?, + ) + .map_err(|_| "Error: invalid square specifier")?; + + let to_square = Square::from_algebraic_str( + matches.get_one::("to").ok_or("Missing square")?, + ) + .map_err(|_| "Error: invalid square specifier")?; + + let mv = MoveBuilder::new( + Piece::new(state.position.player_to_move(), shape), + from_square, + to_square, + ) + .build(); + + state.position = MakeMoveBuilder::new(&state.position) + .make(&mv) + .map_err(|err| format!("error: Cannot make move: {:?}", err))? + .build(); + state.builder = PositionBuilder::from_position(&state.position); + } + Some(("place", matches)) => { + let color = matches + .get_one::("color") + .ok_or("Missing color descriptor")?; + let color = Color::try_from(color).map_err(|_| "Invalid color descriptor")?; + + let shape = matches + .get_one::("piece") + .ok_or("Missing piece descriptor")?; + let shape = Shape::try_from(shape).map_err(|_| "Invalid piece descriptor")?; + + let square = matches + .get_one::("square") + .ok_or("Missing square")?; + let square = Square::from_algebraic_str(square) .map_err(|_| "Error: invalid square specifier")?; - pos.place_piece(&Piece::new(Color::White, shape), &square) - .map_err(|_| "Error: Unable to place piece")?; + let piece = PlacedPiece::new(Piece::new(color, shape), square); + + state.builder.place_piece(piece); + state.position = state.builder.build(); + } + Some(("starting", _matches)) => { + let starting_position = Position::starting(); + state.builder = PositionBuilder::from_position(&starting_position); + state.position = starting_position; } Some((name, _matches)) => unimplemented!("{name}"), None => unreachable!("Subcommand required"), @@ -107,7 +162,7 @@ fn main() -> Result<(), String> { println!("{} to move.", state.position.player_to_move()); } - let readline = editor.readline("? "); + let readline = editor.readline("\n? "); match readline { Ok(line) => { let line = line.trim();