2024-02-25 09:52:49 -08:00
|
|
|
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
|
2025-05-19 08:41:48 -07:00
|
|
|
use chessfriend_board::{fen::FromFenStr, Board};
|
|
|
|
use chessfriend_core::{Color, Piece, Shape, Square};
|
2024-02-25 09:52:49 -08:00
|
|
|
use chessfriend_moves::Builder as MoveBuilder;
|
2025-05-19 08:41:48 -07:00
|
|
|
use chessfriend_position::{fen::ToFenStr, PlacePieceStrategy, Position, ValidateMove};
|
2023-12-28 15:09:15 -07:00
|
|
|
use clap::{Arg, Command};
|
|
|
|
use rustyline::error::ReadlineError;
|
|
|
|
use rustyline::DefaultEditor;
|
|
|
|
|
2024-01-24 17:16:33 -08:00
|
|
|
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,
|
2023-12-28 15:09:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2024-01-28 09:50:39 -08:00
|
|
|
.subcommand_value_name("CMD")
|
|
|
|
.subcommand_help_heading("COMMANDS")
|
2023-12-28 15:09:15 -07:00
|
|
|
.help_template(PARSER_TEMPLATE)
|
2024-01-28 09:50:39 -08:00
|
|
|
.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"),
|
|
|
|
)
|
2023-12-28 15:09:15 -07:00
|
|
|
.subcommand(
|
|
|
|
Command::new("place")
|
2024-01-28 09:50:39 -08:00
|
|
|
.arg(Arg::new("color").required(true))
|
2023-12-28 15:09:15 -07:00
|
|
|
.arg(Arg::new("piece").required(true))
|
2024-01-28 09:50:39 -08:00
|
|
|
.arg(Arg::new("square").required(true))
|
|
|
|
.about("Place a piece on the board"),
|
2023-12-28 15:09:15 -07:00
|
|
|
)
|
2025-05-19 08:28:23 -07:00
|
|
|
.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"),
|
|
|
|
)
|
2025-05-19 08:42:34 -07:00
|
|
|
.subcommand(
|
|
|
|
Command::new("reset")
|
|
|
|
.subcommand(Command::new("clear").about("Reset to a cleared board"))
|
|
|
|
.subcommand(Command::new("starting").about("Reset to the starting position"))
|
|
|
|
.subcommand(
|
|
|
|
Command::new("fen")
|
|
|
|
.arg(Arg::new("fen").required(true))
|
|
|
|
.about("Reset to a position described by a FEN string"),
|
|
|
|
)
|
|
|
|
.about("Reset the board"),
|
|
|
|
)
|
2024-01-28 09:50:39 -08:00
|
|
|
.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"))
|
2023-12-28 15:09:15 -07:00
|
|
|
}
|
|
|
|
|
2024-01-24 17:16:33 -08:00
|
|
|
fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
|
2023-12-28 15:09:15 -07:00
|
|
|
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())?;
|
|
|
|
|
2024-01-24 17:16:33 -08:00
|
|
|
let mut result = CommandResult::default();
|
|
|
|
|
2023-12-28 15:09:15 -07:00
|
|
|
match matches.subcommand() {
|
|
|
|
Some(("print", _matches)) => {}
|
|
|
|
Some(("quit", _matches)) => {
|
2024-01-24 17:16:33 -08:00
|
|
|
result.should_continue = false;
|
|
|
|
result.should_print_position = false;
|
2023-12-28 15:09:15 -07:00
|
|
|
}
|
2024-01-28 09:50:39 -08:00
|
|
|
Some(("fen", _matches)) => {
|
|
|
|
println!(
|
|
|
|
"{}",
|
|
|
|
state
|
|
|
|
.position
|
2025-05-19 08:41:48 -07:00
|
|
|
.to_fen_str()
|
2024-01-28 09:50:39 -08:00
|
|
|
.map_err(|_| "error: Unable to generate FEN for current position")?
|
|
|
|
);
|
2023-12-28 15:09:15 -07:00
|
|
|
|
2024-01-28 09:50:39 -08:00
|
|
|
result.should_print_position = false;
|
|
|
|
}
|
|
|
|
Some(("make", matches)) => {
|
|
|
|
let from_square = Square::from_algebraic_str(
|
|
|
|
matches.get_one::<String>("from").ok_or("Missing square")?,
|
|
|
|
)
|
|
|
|
.map_err(|_| "Error: invalid square specifier")?;
|
|
|
|
|
|
|
|
let to_square = Square::from_algebraic_str(
|
|
|
|
matches.get_one::<String>("to").ok_or("Missing square")?,
|
|
|
|
)
|
|
|
|
.map_err(|_| "Error: invalid square specifier")?;
|
|
|
|
|
2025-05-19 08:38:52 -07:00
|
|
|
let ply = MoveBuilder::new()
|
2024-02-25 09:52:49 -08:00
|
|
|
.from(from_square)
|
|
|
|
.to(to_square)
|
|
|
|
.build()
|
2025-05-19 08:38:52 -07:00
|
|
|
.map_err(|err| format!("Error: {err:?}"))?;
|
2024-01-28 09:50:39 -08:00
|
|
|
|
2025-05-19 08:38:52 -07:00
|
|
|
state
|
|
|
|
.position
|
|
|
|
.make_move(ply, ValidateMove::Yes)
|
|
|
|
.map_err(|err| format!("Error: {err}"))?;
|
2024-01-28 09:50:39 -08:00
|
|
|
}
|
|
|
|
Some(("place", matches)) => {
|
|
|
|
let color = matches
|
|
|
|
.get_one::<String>("color")
|
|
|
|
.ok_or("Missing color descriptor")?;
|
|
|
|
let color = Color::try_from(color).map_err(|_| "Invalid color descriptor")?;
|
|
|
|
|
|
|
|
let shape = matches
|
|
|
|
.get_one::<String>("piece")
|
|
|
|
.ok_or("Missing piece descriptor")?;
|
|
|
|
let shape = Shape::try_from(shape).map_err(|_| "Invalid piece descriptor")?;
|
|
|
|
|
|
|
|
let square = matches
|
|
|
|
.get_one::<String>("square")
|
|
|
|
.ok_or("Missing square")?;
|
|
|
|
let square = Square::from_algebraic_str(square)
|
2023-12-28 15:09:15 -07:00
|
|
|
.map_err(|_| "Error: invalid square specifier")?;
|
|
|
|
|
2025-05-19 08:41:48 -07:00
|
|
|
let piece = Piece::new(color, shape);
|
2024-01-28 09:50:39 -08:00
|
|
|
|
2025-05-19 08:41:48 -07:00
|
|
|
state
|
|
|
|
.position
|
|
|
|
.place_piece(piece, square, PlacePieceStrategy::default())
|
|
|
|
.map_err(|err| format!("Error: could not place piece: {err:?}"))?;
|
2024-01-28 09:50:39 -08:00
|
|
|
}
|
2025-05-19 08:28:23 -07:00
|
|
|
Some(("sight", matches)) => {
|
|
|
|
let square = matches
|
|
|
|
.get_one::<String>("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::<String>("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;
|
|
|
|
}
|
2024-01-28 09:50:39 -08:00
|
|
|
Some(("starting", _matches)) => {
|
|
|
|
let starting_position = Position::starting();
|
|
|
|
state.position = starting_position;
|
2023-12-28 15:09:15 -07:00
|
|
|
}
|
2025-05-19 08:42:34 -07:00
|
|
|
Some(("reset", matches)) => do_reset_command(state, matches)?,
|
2023-12-28 15:09:15 -07:00
|
|
|
Some((name, _matches)) => unimplemented!("{name}"),
|
|
|
|
None => unreachable!("Subcommand required"),
|
|
|
|
}
|
|
|
|
|
2024-01-24 17:16:33 -08:00
|
|
|
Ok(result)
|
2023-12-28 15:09:15 -07:00
|
|
|
}
|
|
|
|
|
2025-05-19 08:42:34 -07:00
|
|
|
fn do_reset_command(state: &mut State, matches: &clap::ArgMatches) -> Result<(), String> {
|
|
|
|
match matches.subcommand() {
|
|
|
|
None | Some(("clear", _)) => state.position = Position::empty(),
|
|
|
|
Some(("starting", _)) => state.position = Position::starting(),
|
|
|
|
Some(("fen", matches)) => {
|
|
|
|
let fen = matches.get_one::<String>("fen").ok_or("Missing FEN")?;
|
|
|
|
let board = Board::from_fen_str(fen).map_err(|err| format!("{err}"))?;
|
|
|
|
state.position = Position::new(board);
|
|
|
|
}
|
|
|
|
Some((name, _matches)) => unimplemented!("{name}"),
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-12-28 15:09:15 -07:00
|
|
|
fn main() -> Result<(), String> {
|
2025-05-19 08:41:48 -07:00
|
|
|
let mut editor = DefaultEditor::new().map_err(|err| format!("Error: {err}"))?;
|
2023-12-28 15:09:15 -07:00
|
|
|
|
2024-01-24 17:16:33 -08:00
|
|
|
let starting_position = Position::starting();
|
|
|
|
let mut state = State {
|
|
|
|
position: starting_position,
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut should_print_position = true;
|
2023-12-28 15:09:15 -07:00
|
|
|
|
|
|
|
loop {
|
2024-01-24 17:16:33 -08:00
|
|
|
if should_print_position {
|
|
|
|
println!("{}", &state.position);
|
2025-05-19 08:41:48 -07:00
|
|
|
println!("{} to move.", state.position.board.active_color);
|
2024-01-24 17:16:33 -08:00
|
|
|
}
|
2023-12-28 15:09:15 -07:00
|
|
|
|
2024-01-28 09:50:39 -08:00
|
|
|
let readline = editor.readline("\n? ");
|
2023-12-28 15:09:15 -07:00
|
|
|
match readline {
|
|
|
|
Ok(line) => {
|
|
|
|
let line = line.trim();
|
|
|
|
if line.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-01-24 17:16:33 -08:00
|
|
|
match respond(line, &mut state) {
|
|
|
|
Ok(result) => {
|
|
|
|
should_print_position = result.should_print_position;
|
|
|
|
if !result.should_continue {
|
2023-12-28 15:09:15 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2025-05-19 08:41:48 -07:00
|
|
|
Err(message) => println!("{message}"),
|
2023-12-28 15:09:15 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(ReadlineError::Interrupted) => {
|
|
|
|
println!("CTRL-C");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Err(ReadlineError::Eof) => {
|
|
|
|
println!("CTRL-D");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Err(err) => {
|
2025-05-19 08:41:48 -07:00
|
|
|
println!("Error: {err}");
|
2023-12-28 15:09:15 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|