diff --git a/explorer/src/main.rs b/explorer/src/main.rs index 9f63e83..7fe6fcb 100644 --- a/explorer/src/main.rs +++ b/explorer/src/main.rs @@ -2,7 +2,7 @@ use chessfriend_board::{fen::FromFenStr, Board}; use chessfriend_core::{Color, Piece, Shape, Square}; -use chessfriend_moves::Builder as MoveBuilder; +use chessfriend_moves::{Builder as MoveBuilder, GeneratedMove}; use chessfriend_position::{fen::ToFenStr, PlacePieceStrategy, Position, ValidateMove}; use clap::{Arg, Command}; @@ -64,8 +64,13 @@ fn command_line() -> Command { ) .subcommand( Command::new("moves") + .arg(Arg::new("square").required(false)) + .about("Show moves of a piece on a square. With no argument, show all moves for the active color."), + ) + .subcommand( + Command::new("movement") .arg(Arg::new("square").required(true)) - .about("Show moves of a piece on a square"), + .about("Show moves of a piece on a square."), ) .subcommand( Command::new("reset") @@ -86,8 +91,12 @@ fn command_line() -> Command { enum CommandHandlingError<'a> { #[error("lexer error")] LexerError, + #[error("missing {0} argument")] MissingArgument(&'a str), + + #[error("no piece on {0}")] + NoPiece(Square), } fn respond(line: &str, state: &mut State) -> anyhow::Result { @@ -158,19 +167,8 @@ fn respond(line: &str, state: &mut State) -> anyhow::Result { result.should_print_position = false; } - Some(("moves", matches)) => { - let square = matches - .get_one::("square") - .ok_or(CommandHandlingError::MissingArgument("square"))?; - let square = square.parse::()?; - - let movement = state.position.movement(square); - - let display = state.position.display().highlight(movement); - println!("\n{display}"); - - result.should_print_position = false; - } + Some(("moves", matches)) => result = do_moves_command(state, matches)?, + Some(("movement", matches)) => result = do_movement_command(state, matches)?, Some(("starting", _matches)) => { let starting_position = Position::starting(); state.position = starting_position; @@ -203,6 +201,73 @@ fn do_reset_command( Ok(CommandResult::default()) } +fn do_moves_command( + state: &mut State, + matches: &clap::ArgMatches, +) -> anyhow::Result { + let moves: Vec = if let Some(square) = matches + .get_one::("square") + .and_then(|square| square.parse::().ok()) + { + state + .position + .moves_for_piece(square) + .map(|it| it.filter(|ply| ply.origin() == square)) + .map(Iterator::collect) + .ok_or(CommandHandlingError::NoPiece(square))? + } else { + state.position.all_moves(None).collect() + }; + + let formatted_moves: Vec = moves + .iter() + .map(|ply| { + let piece = state.position.get_piece(ply.origin()).unwrap(); + format!("{piece}{ply}") + }) + .collect(); + + if !formatted_moves.is_empty() { + let max_length = formatted_moves + .iter() + .map(|s| s.chars().count()) + .max() + .unwrap_or(8) + + 2; + + let columns_count = 80 / max_length; + for row in formatted_moves.chunks(columns_count) { + for ply in row { + print!("{ply: anyhow::Result { + let square = *matches + .get_one::("square") + .ok_or(CommandHandlingError::MissingArgument("square"))?; + + let movement = state.position.movement(square); + let display = state.position.display().highlight(movement); + println!("\n{display}"); + + Ok(CommandResult { + should_continue: true, + should_print_position: false, + }) +} + fn main() -> Result<(), String> { let mut editor = DefaultEditor::new().map_err(|err| format!("Error: {err}"))?; diff --git a/moves/src/generators.rs b/moves/src/generators.rs index e2c0b4d..9c8f391 100644 --- a/moves/src/generators.rs +++ b/moves/src/generators.rs @@ -16,12 +16,20 @@ pub use pawn::PawnMoveGenerator; pub use slider::{BishopMoveGenerator, QueenMoveGenerator, RookMoveGenerator}; use crate::Move; +use chessfriend_core::Square; #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct GeneratedMove { pub(crate) ply: Move, } +impl GeneratedMove { + #[must_use] + pub fn origin(&self) -> Square { + self.ply.origin_square() + } +} + impl std::fmt::Display for GeneratedMove { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.ply.fmt(f) diff --git a/position/src/lib.rs b/position/src/lib.rs index a62da34..af97f5d 100644 --- a/position/src/lib.rs +++ b/position/src/lib.rs @@ -11,4 +11,5 @@ mod macros; mod testing; pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy}; +pub use chessfriend_moves::GeneratedMove; pub use position::{Position, ValidateMove}; diff --git a/position/src/position.rs b/position/src/position.rs index ea94971..700190c 100644 --- a/position/src/position.rs +++ b/position/src/position.rs @@ -4,6 +4,13 @@ mod captures; mod make_move; mod unmake_move; +use chessfriend_moves::{ + generators::{ + AllPiecesMoveGenerator, BishopMoveGenerator, KingMoveGenerator, KnightMoveGenerator, + PawnMoveGenerator, QueenMoveGenerator, RookMoveGenerator, + }, + GeneratedMove, +}; pub use make_move::{MakeMoveError, ValidateMove}; use crate::move_record::MoveRecord; @@ -12,7 +19,7 @@ use chessfriend_bitboard::BitBoard; use chessfriend_board::{ display::DiagramFormatter, fen::ToFenStr, Board, PlacePieceError, PlacePieceStrategy, }; -use chessfriend_core::{Color, Piece, Square}; +use chessfriend_core::{Color, Piece, Shape, Square}; use std::{cell::OnceCell, fmt}; #[must_use] @@ -79,6 +86,33 @@ impl Position { } } +impl Position { + pub fn all_moves(&self, color: Option) -> AllPiecesMoveGenerator { + AllPiecesMoveGenerator::new(&self.board, color) + } + + #[must_use] + pub fn moves_for_piece( + &self, + square: Square, + ) -> Option>> { + self.get_piece(square) + .map(|piece| Self::generator(&self.board, piece)) + } + + #[must_use] + fn generator(board: &Board, piece: Piece) -> Box> { + match piece.shape { + Shape::Pawn => Box::new(PawnMoveGenerator::new(board, Some(piece.color))), + Shape::Knight => Box::new(KnightMoveGenerator::new(board, Some(piece.color))), + Shape::Bishop => Box::new(BishopMoveGenerator::new(board, Some(piece.color))), + Shape::Rook => Box::new(RookMoveGenerator::new(board, Some(piece.color))), + Shape::Queen => Box::new(QueenMoveGenerator::new(board, Some(piece.color))), + Shape::King => Box::new(KingMoveGenerator::new(board, Some(piece.color))), + } + } +} + impl Position { pub fn active_sight(&self) -> BitBoard { self.board.active_sight()