[explorer] Add fen and make commands

Clean up the implementation of the place command.

Track state with a State struct that contains a position and a builder. The place
command will place a new piece and then regenerate the position.

The make command makes a move. The syntax is:

    make [color:w|b] [shape] [from square] [to square]

The fen command prints a FEN string representing the position.
This commit is contained in:
Eryn Wells 2024-01-28 09:50:39 -08:00
parent 6bd3787a24
commit 8eb180df67

View file

@ -29,28 +29,31 @@ fn command_line() -> Command {
{all-args} {all-args}
"; ";
// strip out name/version
const APPLET_TEMPLATE: &str = "\
{about-with-newline}\n\
{usage-heading}\n {usage}\n\
\n\
{all-args}{after-help}\
";
Command::new("explorer") Command::new("explorer")
.multicall(true) .multicall(true)
.arg_required_else_help(true) .arg_required_else_help(true)
.subcommand_required(true) .subcommand_required(true)
.subcommand_value_name("APPLET") .subcommand_value_name("CMD")
.subcommand_help_heading("APPLETS") .subcommand_help_heading("COMMANDS")
.help_template(PARSER_TEMPLATE) .help_template(PARSER_TEMPLATE)
.subcommand(Command::new("print").about("Print the board")) .subcommand(Command::new("fen").about("Print the current position as a FEN string"))
.subcommand(
Command::new("make")
.arg(Arg::new("piece").required(true))
.arg(Arg::new("from").required(true))
.arg(Arg::new("to").required(true))
.about("Make a move"),
)
.subcommand( .subcommand(
Command::new("place") Command::new("place")
.arg(Arg::new("color").required(true))
.arg(Arg::new("piece").required(true)) .arg(Arg::new("piece").required(true))
.arg(Arg::new("square").required(true)), .arg(Arg::new("square").required(true))
.about("Place a piece on the board"),
) )
.subcommand(Command::new("quit").about("Quit the program")) .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<CommandResult, String> { fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
@ -67,20 +70,72 @@ fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
result.should_continue = false; result.should_continue = false;
result.should_print_position = false; result.should_print_position = false;
} }
Some(("place", _matches)) => { Some(("fen", _matches)) => {
let piece_arg = _matches.get_one::<String>("piece").unwrap(); println!(
let shape = match piece_arg.chars().nth(0) { "{}",
Some(c) => Shape::try_from(c).map_err(|_| ()), state
None => Err(()), .position
} .to_fen()
.map_err(|_| "Error: invalid piece specifier")?; .map_err(|_| "error: Unable to generate FEN for current position")?
);
let square_arg = _matches.get_one::<String>("square").unwrap(); result.should_print_position = false;
let square = Square::from_algebraic_str(square_arg) }
Some(("make", matches)) => {
let shape = matches
.get_one::<String>("piece")
.ok_or("Missing piece descriptor")?;
let shape = Shape::try_from(shape).map_err(|_| "Invalid piece descriptor")?;
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")?;
let mv = MoveBuilder::new(
Piece::new(state.position.player_to_move(), shape),
from_square,
to_square,
)
.build();
state.position = MakeMoveBuilder::new(&state.position)
.make(&mv)
.map_err(|err| format!("error: Cannot make move: {:?}", err))?
.build();
state.builder = PositionBuilder::from_position(&state.position);
}
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)
.map_err(|_| "Error: invalid square specifier")?; .map_err(|_| "Error: invalid square specifier")?;
pos.place_piece(&Piece::new(Color::White, shape), &square) let piece = PlacedPiece::new(Piece::new(color, shape), square);
.map_err(|_| "Error: Unable to place piece")?;
state.builder.place_piece(piece);
state.position = state.builder.build();
}
Some(("starting", _matches)) => {
let starting_position = Position::starting();
state.builder = PositionBuilder::from_position(&starting_position);
state.position = starting_position;
} }
Some((name, _matches)) => unimplemented!("{name}"), Some((name, _matches)) => unimplemented!("{name}"),
None => unreachable!("Subcommand required"), None => unreachable!("Subcommand required"),
@ -107,7 +162,7 @@ fn main() -> Result<(), String> {
println!("{} to move.", state.position.player_to_move()); println!("{} to move.", state.position.player_to_move());
} }
let readline = editor.readline("? "); let readline = editor.readline("\n? ");
match readline { match readline {
Ok(line) => { Ok(line) => {
let line = line.trim(); let line = line.trim();