// Eryn Wells use chessfriend_board::{fen::FromFenStr, Board}; use chessfriend_core::{Color, Piece, Shape, Square}; use chessfriend_moves::Builder as MoveBuilder; use chessfriend_position::{fen::ToFenStr, PlacePieceStrategy, Position, ValidateMove}; use clap::{Arg, Command}; use rustyline::error::ReadlineError; use rustyline::DefaultEditor; struct CommandResult { should_continue: bool, should_print_position: bool, } impl Default for CommandResult { fn default() -> Self { CommandResult { should_continue: true, should_print_position: true, } } } struct State { position: Position, } fn command_line() -> Command { // strip out usage const PARSER_TEMPLATE: &str = "\ {all-args} "; Command::new("explorer") .multicall(true) .arg_required_else_help(true) .subcommand_required(true) .subcommand_value_name("CMD") .subcommand_help_heading("COMMANDS") .help_template(PARSER_TEMPLATE) .subcommand(Command::new("fen").about("Print the current position as a FEN string")) .subcommand( Command::new("make") .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)) .about("Place a piece on the board"), ) .subcommand( Command::new("sight") .arg(Arg::new("square").required(true)) .about("Show sight of a piece on a square"), ) .subcommand( Command::new("moves") .arg(Arg::new("square").required(true)) .about("Show moves of a piece on a square"), ) .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 { let args = shlex::split(line).ok_or("error: Invalid quoting")?; let matches = command_line() .try_get_matches_from(args) .map_err(|e| e.to_string())?; let mut result = CommandResult::default(); match matches.subcommand() { Some(("print", _matches)) => {} Some(("quit", _matches)) => { result.should_continue = false; result.should_print_position = false; } Some(("fen", _matches)) => { println!( "{}", state .position .to_fen_str() .map_err(|_| "error: Unable to generate FEN for current position")? ); result.should_print_position = false; } Some(("make", matches)) => { 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 ply = MoveBuilder::new() .from(from_square) .to(to_square) .build() .map_err(|err| format!("Error: {err:?}"))?; state .position .make_move(ply, ValidateMove::Yes) .map_err(|err| format!("Error: {err}"))?; } 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")?; let piece = Piece::new(color, shape); state .position .place_piece(piece, square, PlacePieceStrategy::default()) .map_err(|err| format!("Error: could not place piece: {err:?}"))?; } Some(("sight", matches)) => { let square = matches .get_one::("square") .ok_or("Missing square")?; let square = Square::from_algebraic_str(square) .map_err(|_| "Error: invalid square specifier")?; let sight = state.position.sight(square); let display = state.position.display().highlight(sight); println!("\n{display}"); result.should_print_position = false; } Some(("moves", matches)) => { let square = matches .get_one::("square") .ok_or("Missing square")?; let square = Square::from_algebraic_str(square) .map_err(|_| "Error: invalid square specifier")?; let movement = state.position.movement(square); let display = state.position.display().highlight(movement); println!("\n{display}"); result.should_print_position = false; } Some(("starting", _matches)) => { let starting_position = Position::starting(); state.position = starting_position; } Some((name, _matches)) => unimplemented!("{name}"), None => unreachable!("Subcommand required"), } Ok(result) } fn main() -> Result<(), String> { let mut editor = DefaultEditor::new().map_err(|err| format!("Error: {err}"))?; let starting_position = Position::starting(); let mut state = State { position: starting_position, }; let mut should_print_position = true; loop { if should_print_position { println!("{}", &state.position); println!("{} to move.", state.position.board.active_color); } let readline = editor.readline("\n? "); match readline { Ok(line) => { let line = line.trim(); if line.is_empty() { continue; } match respond(line, &mut state) { Ok(result) => { should_print_position = result.should_print_position; if !result.should_continue { break; } } Err(message) => println!("{message}"), } } Err(ReadlineError::Interrupted) => { println!("CTRL-C"); break; } Err(ReadlineError::Eof) => { println!("CTRL-D"); break; } Err(err) => { println!("Error: {err}"); break; } } } Ok(()) }