[explorer, moves, position] Implement a moves command in explorer

The moves command writes all possible moves to the terminal.

Move the previous implementation of the moves command, which marked squares that
a piece could move to, to a 'movement' command.
This commit is contained in:
Eryn Wells 2025-05-28 16:25:55 -07:00
parent 43abbe3fe2
commit 942d9fe47b
4 changed files with 124 additions and 16 deletions

View file

@ -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<CommandResult> {
@ -158,19 +167,8 @@ fn respond(line: &str, state: &mut State) -> anyhow::Result<CommandResult> {
result.should_print_position = false;
}
Some(("moves", matches)) => {
let square = matches
.get_one::<String>("square")
.ok_or(CommandHandlingError::MissingArgument("square"))?;
let square = square.parse::<Square>()?;
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<CommandResult> {
let moves: Vec<GeneratedMove> = if let Some(square) = matches
.get_one::<String>("square")
.and_then(|square| square.parse::<Square>().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<String> = 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:<max_length$}");
}
println!();
}
}
Ok(CommandResult {
should_continue: true,
should_print_position: false,
})
}
fn do_movement_command(
state: &mut State,
matches: &clap::ArgMatches,
) -> anyhow::Result<CommandResult> {
let square = *matches
.get_one::<Square>("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}"))?;