From bf17017694e96d094b46abd6f3cd04fb6a585323 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 18 Jun 2025 08:21:21 -0700 Subject: [PATCH] [explorer] Add several commands to help with debugging flags : Print flags for the current board position. This prints the castling rights and whether the player can castle (regardless of whether they have the right). make : Finally reimplement the make command. Change the format so it takes a move in the UCI long algebraic style. perft : Run perft to a given depth on the current board position. --- explorer/src/main.rs | 101 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 92 insertions(+), 9 deletions(-) diff --git a/explorer/src/main.rs b/explorer/src/main.rs index 77ef939..b409cd0 100644 --- a/explorer/src/main.rs +++ b/explorer/src/main.rs @@ -1,12 +1,14 @@ // Eryn Wells -use chessfriend_board::ZobristState; +use chessfriend_board::castle::CastleEvaluationError; use chessfriend_board::{Board, fen::FromFenStr}; +use chessfriend_board::{CastleParameters, ZobristState}; use chessfriend_core::random::RandomNumberGenerator; -use chessfriend_core::{Color, Piece, Shape, Square}; -use chessfriend_moves::GeneratedMove; +use chessfriend_core::{Color, Piece, Shape, Square, Wing}; +use chessfriend_moves::algebraic::AlgebraicMoveComponents; +use chessfriend_moves::{GeneratedMove, ValidateMove}; use chessfriend_position::{PlacePieceStrategy, Position, fen::ToFenStr}; -use clap::{Arg, Command}; +use clap::{Arg, Command, value_parser}; use rustyline::DefaultEditor; use rustyline::error::ReadlineError; use std::sync::Arc; @@ -45,6 +47,7 @@ fn command_line() -> Command { .subcommand_help_heading("COMMANDS") .help_template(PARSER_TEMPLATE) .subcommand(Command::new("fen").about("Print the current position as a FEN string")) + .subcommand(Command::new("flags").about("Print flags for the current position")) .subcommand( Command::new("load") .arg(Arg::new("fen").required(true)) @@ -53,8 +56,7 @@ fn command_line() -> Command { ) .subcommand( Command::new("make") - .arg(Arg::new("from").required(true)) - .arg(Arg::new("to").required(true)) + .arg(Arg::new("move").required(true)) .alias("m") .about("Make a move"), ) @@ -81,6 +83,14 @@ fn command_line() -> Command { .arg(Arg::new("square").required(true)) .about("Show moves of a piece on a square"), ) + .subcommand( + Command::new("perft") + .arg(Arg::new("depth") + .required(true) + .value_parser(value_parser!(usize)) + ) + .about("Run Perft on the current position to the given depth") + ) .subcommand( Command::new("reset") .subcommand(Command::new("clear").about("Reset to a cleared board")) @@ -107,6 +117,12 @@ enum CommandHandlingError<'a> { #[error("no piece on {0}")] NoPiece(Square), + + #[error("{value:?} is not a valid value for {argument_name:?}")] + ValueError { + argument_name: &'static str, + value: String, + }, } fn respond(line: &str, state: &mut State) -> anyhow::Result { @@ -116,6 +132,7 @@ fn respond(line: &str, state: &mut State) -> anyhow::Result { let mut result = CommandResult::default(); match matches.subcommand() { + Some(("flags", matches)) => result = do_flags_command(state, matches), Some(("load", matches)) => result = do_load_command(state, matches)?, Some(("print", _matches)) => {} Some(("quit", _matches)) => { @@ -126,9 +143,8 @@ fn respond(line: &str, state: &mut State) -> anyhow::Result { println!("{}", state.position.to_fen_str()?); result.should_print_position = false; } - Some(("make", _matches)) => { - unimplemented!() - } + Some(("make", matches)) => result = do_make_command(state, matches)?, + Some(("perft", matches)) => result = do_perft_command(state, matches)?, Some(("place", matches)) => { let color = matches .get_one::("color") @@ -175,6 +191,34 @@ fn respond(line: &str, state: &mut State) -> anyhow::Result { Ok(result) } +fn do_flags_command(state: &mut State, _matches: &clap::ArgMatches) -> CommandResult { + let board = &state.position.board; + + println!("Castling:"); + + for (color, wing) in [ + (Color::White, Wing::KingSide), + (Color::White, Wing::QueenSide), + (Color::Black, Wing::KingSide), + (Color::Black, Wing::QueenSide), + ] { + let has_right = board.color_has_castling_right_unwrapped(color, wing); + let can_castle = board.color_can_castle(wing, Some(color)); + + let can_castle_message = match can_castle { + Ok(_) => "ok".to_string(), + Err(error) => format!("{error}"), + }; + + println!(" {color} {wing}: {has_right}, {can_castle_message}"); + } + + CommandResult { + should_continue: true, + should_print_position: false, + } +} + fn do_load_command(state: &mut State, matches: &clap::ArgMatches) -> anyhow::Result { let fen_string = matches .get_one::("fen") @@ -191,6 +235,26 @@ fn do_load_command(state: &mut State, matches: &clap::ArgMatches) -> anyhow::Res }) } +fn do_make_command(state: &mut State, matches: &clap::ArgMatches) -> anyhow::Result { + let move_string = matches + .get_one::("move") + .ok_or(CommandHandlingError::MissingArgument("move"))?; + + let algebraic_move: AlgebraicMoveComponents = move_string.parse()?; + + let encoded_move = state + .position + .move_from_algebraic_components(algebraic_move) + .ok_or(CommandHandlingError::ValueError { + argument_name: "move", + value: move_string.to_string(), + })?; + + state.position.make_move(encoded_move, ValidateMove::Yes)?; + + Ok(CommandResult::default()) +} + fn do_reset_command( state: &mut State, matches: &clap::ArgMatches, @@ -282,6 +346,25 @@ fn do_movement_command( }) } +fn do_perft_command( + state: &mut State, + matches: &clap::ArgMatches, +) -> anyhow::Result { + let depth = *matches + .get_one::("depth") + .ok_or(CommandHandlingError::MissingArgument("depth"))?; + + let mut position = state.position.clone(); + let nodes_count = position.perft(depth); + + println!("nodes {nodes_count}"); + + Ok(CommandResult { + should_continue: true, + should_print_position: false, + }) +} + fn do_zobrist_command(state: &mut State, _matches: &clap::ArgMatches) -> CommandResult { if let Some(hash) = state.position.zobrist_hash() { println!("hash:{hash}");