[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:
parent
43abbe3fe2
commit
942d9fe47b
4 changed files with 124 additions and 16 deletions
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use chessfriend_board::{fen::FromFenStr, Board};
|
use chessfriend_board::{fen::FromFenStr, Board};
|
||||||
use chessfriend_core::{Color, Piece, Shape, Square};
|
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 chessfriend_position::{fen::ToFenStr, PlacePieceStrategy, Position, ValidateMove};
|
||||||
|
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
|
@ -64,8 +64,13 @@ fn command_line() -> Command {
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
Command::new("moves")
|
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))
|
.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(
|
.subcommand(
|
||||||
Command::new("reset")
|
Command::new("reset")
|
||||||
|
@ -86,8 +91,12 @@ fn command_line() -> Command {
|
||||||
enum CommandHandlingError<'a> {
|
enum CommandHandlingError<'a> {
|
||||||
#[error("lexer error")]
|
#[error("lexer error")]
|
||||||
LexerError,
|
LexerError,
|
||||||
|
|
||||||
#[error("missing {0} argument")]
|
#[error("missing {0} argument")]
|
||||||
MissingArgument(&'a str),
|
MissingArgument(&'a str),
|
||||||
|
|
||||||
|
#[error("no piece on {0}")]
|
||||||
|
NoPiece(Square),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn respond(line: &str, state: &mut State) -> anyhow::Result<CommandResult> {
|
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;
|
result.should_print_position = false;
|
||||||
}
|
}
|
||||||
Some(("moves", matches)) => {
|
Some(("moves", matches)) => result = do_moves_command(state, matches)?,
|
||||||
let square = matches
|
Some(("movement", matches)) => result = do_movement_command(state, 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(("starting", _matches)) => {
|
Some(("starting", _matches)) => {
|
||||||
let starting_position = Position::starting();
|
let starting_position = Position::starting();
|
||||||
state.position = starting_position;
|
state.position = starting_position;
|
||||||
|
@ -203,6 +201,73 @@ fn do_reset_command(
|
||||||
Ok(CommandResult::default())
|
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> {
|
fn main() -> Result<(), String> {
|
||||||
let mut editor = DefaultEditor::new().map_err(|err| format!("Error: {err}"))?;
|
let mut editor = DefaultEditor::new().map_err(|err| format!("Error: {err}"))?;
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,20 @@ pub use pawn::PawnMoveGenerator;
|
||||||
pub use slider::{BishopMoveGenerator, QueenMoveGenerator, RookMoveGenerator};
|
pub use slider::{BishopMoveGenerator, QueenMoveGenerator, RookMoveGenerator};
|
||||||
|
|
||||||
use crate::Move;
|
use crate::Move;
|
||||||
|
use chessfriend_core::Square;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct GeneratedMove {
|
pub struct GeneratedMove {
|
||||||
pub(crate) ply: Move,
|
pub(crate) ply: Move,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GeneratedMove {
|
||||||
|
#[must_use]
|
||||||
|
pub fn origin(&self) -> Square {
|
||||||
|
self.ply.origin_square()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for GeneratedMove {
|
impl std::fmt::Display for GeneratedMove {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
self.ply.fmt(f)
|
self.ply.fmt(f)
|
||||||
|
|
|
@ -11,4 +11,5 @@ mod macros;
|
||||||
mod testing;
|
mod testing;
|
||||||
|
|
||||||
pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy};
|
pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy};
|
||||||
|
pub use chessfriend_moves::GeneratedMove;
|
||||||
pub use position::{Position, ValidateMove};
|
pub use position::{Position, ValidateMove};
|
||||||
|
|
|
@ -4,6 +4,13 @@ mod captures;
|
||||||
mod make_move;
|
mod make_move;
|
||||||
mod unmake_move;
|
mod unmake_move;
|
||||||
|
|
||||||
|
use chessfriend_moves::{
|
||||||
|
generators::{
|
||||||
|
AllPiecesMoveGenerator, BishopMoveGenerator, KingMoveGenerator, KnightMoveGenerator,
|
||||||
|
PawnMoveGenerator, QueenMoveGenerator, RookMoveGenerator,
|
||||||
|
},
|
||||||
|
GeneratedMove,
|
||||||
|
};
|
||||||
pub use make_move::{MakeMoveError, ValidateMove};
|
pub use make_move::{MakeMoveError, ValidateMove};
|
||||||
|
|
||||||
use crate::move_record::MoveRecord;
|
use crate::move_record::MoveRecord;
|
||||||
|
@ -12,7 +19,7 @@ use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_board::{
|
use chessfriend_board::{
|
||||||
display::DiagramFormatter, fen::ToFenStr, Board, PlacePieceError, PlacePieceStrategy,
|
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};
|
use std::{cell::OnceCell, fmt};
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -79,6 +86,33 @@ impl Position {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Position {
|
||||||
|
pub fn all_moves(&self, color: Option<Color>) -> AllPiecesMoveGenerator {
|
||||||
|
AllPiecesMoveGenerator::new(&self.board, color)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn moves_for_piece(
|
||||||
|
&self,
|
||||||
|
square: Square,
|
||||||
|
) -> Option<Box<dyn Iterator<Item = GeneratedMove>>> {
|
||||||
|
self.get_piece(square)
|
||||||
|
.map(|piece| Self::generator(&self.board, piece))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn generator(board: &Board, piece: Piece) -> Box<dyn Iterator<Item = GeneratedMove>> {
|
||||||
|
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 {
|
impl Position {
|
||||||
pub fn active_sight(&self) -> BitBoard {
|
pub fn active_sight(&self) -> BitBoard {
|
||||||
self.board.active_sight()
|
self.board.active_sight()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue