Make mvv_lva to map pieces to their material score instead of abritrary matrix
Add time limits, use anyhow crate to improve error handling, remove depth limit on quiescence search
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
use std::{
|
||||
io::{BufRead, Write},
|
||||
str::SplitWhitespace,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail};
|
||||
|
||||
use crate::{
|
||||
board::{board::Color, game::Game},
|
||||
movegen::r#move::Move,
|
||||
@@ -22,7 +25,7 @@ pub enum Command {
|
||||
Quit,
|
||||
}
|
||||
|
||||
fn parse_command(parts: &mut SplitWhitespace) -> Result<Command, String> {
|
||||
fn parse_command(parts: &mut SplitWhitespace) -> anyhow::Result<Command> {
|
||||
match parts.next() {
|
||||
Some("uci") => Ok(Command::Uci),
|
||||
Some("isready") => Ok(Command::IsReady),
|
||||
@@ -30,7 +33,7 @@ fn parse_command(parts: &mut SplitWhitespace) -> Result<Command, String> {
|
||||
Some("position") => Ok(Command::Position),
|
||||
Some("go") => Ok(Command::Go),
|
||||
Some("quit") => Ok(Command::Quit),
|
||||
_ => Err("Unrecognised command".to_string()),
|
||||
_ => bail!("Unrecognised command"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +44,9 @@ pub enum Response {
|
||||
Info(String),
|
||||
}
|
||||
|
||||
fn write_response(handle_out: &mut impl Write, response: &Response) -> Result<(), String> {
|
||||
writeln!(handle_out, "{response}").map_err(|e| e.to_string())?;
|
||||
handle_out.flush().map_err(|e| e.to_string())?;
|
||||
fn write_response(handle_out: &mut impl Write, response: &Response) -> anyhow::Result<()> {
|
||||
writeln!(handle_out, "{response}").map_err(|e| anyhow!(e))?;
|
||||
handle_out.flush().map_err(|e| anyhow!(e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -107,16 +110,18 @@ impl Default for UciParameters {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uci_position(position: &mut SplitWhitespace) -> Result<Game, String> {
|
||||
let state = position.next().ok_or("Expected startpos or fen")?;
|
||||
pub fn uci_position(position: &mut SplitWhitespace) -> anyhow::Result<Game> {
|
||||
let state = position
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("Expected startpos or fen"))?;
|
||||
let mut game = match state {
|
||||
"startpos" => Game::new(),
|
||||
"fen" => {
|
||||
let fen_parts: Vec<&str> = position.take_while(|&part| part != "moves").collect();
|
||||
let fen = fen_parts.join(" ");
|
||||
Game::from_fen(&fen)?
|
||||
Game::from_fen(&fen).map_err(|e| anyhow!("Failed to parse FEN: {fen}, {e}"))?
|
||||
}
|
||||
_ => return Err("Expected startpos or fen".to_string()),
|
||||
_ => bail!("Expected startpos or fen"),
|
||||
};
|
||||
|
||||
if Some("moves") != position.next() {
|
||||
@@ -124,7 +129,8 @@ pub fn uci_position(position: &mut SplitWhitespace) -> Result<Game, String> {
|
||||
}
|
||||
|
||||
for mv_str in position {
|
||||
let mv = Move::parse_from_str(&game, mv_str)?;
|
||||
let mv = Move::parse_from_str(&game, mv_str)
|
||||
.map_err(|e| anyhow!("Failed to parse move: {e}"))?;
|
||||
game.make_move(&mv);
|
||||
}
|
||||
|
||||
@@ -135,7 +141,7 @@ pub fn uci_go(
|
||||
go_iter: &mut SplitWhitespace,
|
||||
game: &mut Game,
|
||||
tt: &mut TranspositionTable,
|
||||
) -> Result<Move, String> {
|
||||
) -> anyhow::Result<Move> {
|
||||
let mut params = UciParameters::new();
|
||||
while let Some(subcommand) = go_iter.next() {
|
||||
match subcommand {
|
||||
@@ -147,23 +153,30 @@ pub fn uci_go(
|
||||
}
|
||||
}
|
||||
|
||||
let time = Instant::now();
|
||||
let remaining_time = match game.current_player() {
|
||||
Color::White => params.wtime.unwrap_or(REMAINING_TIME_DEFAULT),
|
||||
Color::Black => params.btime.unwrap_or(REMAINING_TIME_DEFAULT),
|
||||
};
|
||||
|
||||
iterative_deepening::iterative_deepening(game, MAX_DEPTH, remaining_time, tt)
|
||||
.ok_or_else(|| "No move selected".to_string())
|
||||
iterative_deepening::iterative_deepening(game, MAX_DEPTH, remaining_time, tt)?.ok_or_else(
|
||||
|| {
|
||||
anyhow!(
|
||||
"No stored best move found. Time: {}",
|
||||
remaining_time - time.elapsed().as_millis()
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_next<T: std::str::FromStr>(go_iter: &mut SplitWhitespace, val: &str) -> Result<T, String> {
|
||||
fn parse_next<T: std::str::FromStr>(go_iter: &mut SplitWhitespace, val: &str) -> anyhow::Result<T> {
|
||||
go_iter
|
||||
.next()
|
||||
.ok_or_else(|| format!("Expected {val}"))
|
||||
.and_then(|v| v.parse::<T>().map_err(|_| format!("Invalid {val}")))
|
||||
.ok_or_else(|| anyhow!("Expected {val}"))
|
||||
.and_then(|v| v.parse::<T>().map_err(|_| anyhow!("Invalid {val}")))
|
||||
}
|
||||
|
||||
pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> Result<(), String> {
|
||||
pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> anyhow::Result<()> {
|
||||
let mut params = UciParameters::new();
|
||||
let mut tt = TranspositionTable::new(MAX_TT_SIZE);
|
||||
|
||||
@@ -182,19 +195,19 @@ pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> Result<(), Str
|
||||
params.add_game(uci_position(&mut parts)?);
|
||||
Response::Info("Initialized position".to_string())
|
||||
}
|
||||
Command::Go => {
|
||||
if let Some(ref mut game) = params.game {
|
||||
let best_move = uci_go(&mut parts, game, &mut tt)?;
|
||||
Response::BestMove(best_move.parse_into_str())
|
||||
} else {
|
||||
Response::Info("Going?".to_string())
|
||||
}
|
||||
}
|
||||
Command::Go => params.game.as_mut().map_or_else(
|
||||
|| Response::Info("Failed to unwrap from UciParameter".to_string()),
|
||||
|game| match uci_go(&mut parts, game, &mut tt) {
|
||||
Ok(best_move) => Response::BestMove(best_move.parse_into_str()),
|
||||
Err(e) => Response::Info(e.to_string()),
|
||||
},
|
||||
),
|
||||
// TODO: Command::Stop => (),
|
||||
Command::Quit => break,
|
||||
};
|
||||
|
||||
write_response(&mut output, &response)?;
|
||||
output.flush().map_err(|e| e.to_string())?;
|
||||
output.flush().map_err(|e| anyhow!(e))?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -221,7 +234,7 @@ mod tests {
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_uci_position() -> Result<(), String> {
|
||||
fn test_uci_position() -> anyhow::Result<()> {
|
||||
let command_position = "position startpos";
|
||||
let mut parts = command_position.split_whitespace();
|
||||
let command = parse_command(&mut parts)?;
|
||||
@@ -242,10 +255,10 @@ mod tests {
|
||||
const FEN_MATE_IN_1: &str = "8/8/8/8/8/4q1k1/8/5K2 b - - 0 1";
|
||||
|
||||
#[test]
|
||||
fn test_uci_go() -> Result<(), String> {
|
||||
fn test_uci_go() -> anyhow::Result<()> {
|
||||
init_attacks();
|
||||
let mut tt = TranspositionTable::new(MAX_TT_SIZE);
|
||||
let mut game = from_fen(FEN_MATE_IN_1)?;
|
||||
let mut game = from_fen(FEN_MATE_IN_1).unwrap();
|
||||
let command_go = "go depth 2";
|
||||
let mut parts = command_go.split_whitespace();
|
||||
let response = uci_go(&mut parts, &mut game, &mut tt)?;
|
||||
@@ -256,7 +269,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uci_loop() -> Result<(), String> {
|
||||
fn test_uci_loop() -> anyhow::Result<()> {
|
||||
init_attacks();
|
||||
let commands = "uci\n\
|
||||
ucinewgame\n\
|
||||
@@ -282,7 +295,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cute_chess_bug() -> Result<(), String> {
|
||||
fn test_cute_chess_bug() -> anyhow::Result<()> {
|
||||
init_attacks();
|
||||
let commands = "uci\n\
|
||||
ucinewgame\n\
|
||||
|
||||
Reference in New Issue
Block a user