Refactor project structure to use submodules

This commit is contained in:
stefiosif
2024-08-14 21:58:20 +03:00
parent 69cabd5b58
commit ed711f905d
21 changed files with 121 additions and 89 deletions

236
src/interface/uci.rs Normal file
View File

@@ -0,0 +1,236 @@
use std::{
io::{self, BufRead, Write},
str::SplitWhitespace,
};
use crate::{board::game::Game, board::square::Square, movegen::r#move::Move, search::search};
#[derive(PartialEq, Eq, Debug)]
pub enum Command {
Uci,
IsReady,
UciNewGame,
Position,
Go,
Quit,
}
fn parse_command(parts: &mut SplitWhitespace) -> Result<Command, String> {
match parts.next() {
Some("uci") => Ok(Command::Uci),
Some("isready") => Ok(Command::IsReady),
Some("ucinewgame") => Ok(Command::UciNewGame),
Some("position") => Ok(Command::Position),
Some("go") => Ok(Command::Go),
Some("quit") => Ok(Command::Quit),
_ => Err("Unrecognised command".to_string()),
}
}
pub enum Response {
UciOk,
ReadyOk,
BestMove(String),
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())?;
Ok(())
}
use std::fmt;
impl fmt::Display for Response {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UciOk => write!(f, "id name ippos\nid author stefiosif\nuciok"),
Self::ReadyOk => write!(f, "readyok"),
Self::BestMove(best_move) => write!(f, "bestmove {}", best_move),
Self::Info(info) => write!(f, "{}", info),
}
}
}
struct UciParameters {
movetime: Option<usize>,
depth: Option<u8>,
game: Option<Game>,
}
impl UciParameters {
const fn new() -> Self {
Self {
movetime: None,
depth: None,
game: None,
}
}
fn add_movetime(&mut self, movetime: usize) {
self.movetime = Some(movetime);
}
fn add_depth(&mut self, depth: u8) {
self.depth = Some(depth);
}
fn add_game(&mut self, game: Game) {
self.game = Some(game);
}
}
impl Default for UciParameters {
fn default() -> Self {
Self::new()
}
}
pub fn uci_position(position: &mut SplitWhitespace) -> Result<Game, String> {
let state = position.next().ok_or("Expected startpos or fen")?;
let mut game = match state {
"startpos" => Game::new(),
"fen" => Game::new_from_fen(position.next().ok_or("Expected fen string")?)?,
_ => return Err("Expected startpos or fen".to_string()),
};
if Some("moves") != position.next() {
return Ok(game);
}
for mv_str in position {
let mv = Move::parse_from_str(mv_str)?;
game.board.make_move(&mv, game.board.state.next_turn());
}
Ok(game)
}
const MAX_DEPTH: u8 = 5;
pub fn uci_go(go: &mut SplitWhitespace, game: &mut Game) -> Result<Move, String> {
let mut params = UciParameters::new();
while let Some(subcommand) = go.next() {
match subcommand {
"depth" => {
let depth_str = go.next().ok_or("Expected depth value")?;
let depth = depth_str.parse::<u8>().map_err(|_| "Invalid depth value")?;
params.add_depth(depth);
}
"movetime" => {
let movetime_str = go.next().ok_or("Expected movetime value")?;
let movetime = movetime_str
.parse::<usize>()
.map_err(|_| "Invalid movetime value")?;
params.add_movetime(movetime);
}
_ => (),
}
}
search::search(game, params.depth.unwrap_or(MAX_DEPTH));
Ok(Move::new(Square::B8, Square::C6))
}
pub fn uci_loop() -> Result<(), String> {
let stdin = io::stdin();
let stdout = io::stdout();
let handle_in = stdin.lock();
let mut handle_out = stdout.lock();
let mut params = UciParameters::new();
for line in handle_in.lines() {
let line_str = line.unwrap_or_else(|_| "quit".to_string());
let mut parts = line_str.split_whitespace();
let command = parse_command(&mut parts)?;
let response = match command {
Command::Uci => Response::UciOk,
Command::IsReady => Response::ReadyOk,
Command::UciNewGame => Response::Info("Clear cached game.".to_string()),
Command::Position => {
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)?;
Response::BestMove(best_move.parse_into_str())
} else {
Response::Info("Going!".to_string())
}
}
Command::Quit => break,
};
write_response(&mut handle_out, response)?;
handle_out.flush().map_err(|e| e.to_string())?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use crate::{
board::fen::from_fen,
interface::uci::{parse_command, Command},
};
use super::uci_position;
const FEN: [&str; 2] = [
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"r1bqkbnr/pppppppp/2n4B/8/3P4/8/PPP1PPPP/RN1QKBNR b KQkq - 2 2",
];
#[test]
fn test_uci_position() -> Result<(), String> {
let command_position = "position startpos";
let mut parts = command_position.split_whitespace();
let command = parse_command(&mut parts)?;
assert_eq!(command, Command::Position);
assert_eq!(from_fen(FEN[0]).unwrap(), uci_position(&mut parts)?);
let command_position_moves = "position startpos moves d2d4 b8c6 c1h6";
let mut parts = command_position_moves.split_whitespace();
let command = parse_command(&mut parts)?;
assert_eq!(command, Command::Position);
assert_eq!(from_fen(FEN[1]).unwrap(), uci_position(&mut parts)?);
Ok(())
}
#[test]
fn test_uci_go() -> Result<(), String> {
let mut _game = from_fen(FEN[0])?;
Ok(())
}
#[test]
fn test_uci_loop() -> Result<(), String> {
let mut _game = from_fen(FEN[0])?;
Ok(())
}
}
/*
```
- CuteChess: `uci`
- Engine: `id name MyChessEngine`
- Engine: `id author John Doe`
- Engine: `option name Hash type spin default 32 min 1 max 512`
- Engine: `uciok`
- CuteChess: `setoption name Hash value 128`
- CuteChess: `ucinewgame`
- CuteChess: `position startpos moves e2e4 e7e5`
- CuteChess: `go wtime 600000 btime 600000 winc 0 binc 0`
- Engine: `info depth 1 score cp 20 nodes 15 nps 1500 time 10 pv e2e4`
- Engine: `bestmove e2e4`
- CuteChess: `quit`
```
*/