fixed uci

This commit is contained in:
Moritz 2025-11-15 11:43:03 +01:00
parent 66cea5a2bf
commit f1ec0a08d9
5 changed files with 164 additions and 60 deletions

View file

@ -1,5 +1,6 @@
// ... (your use statements) // ... (your use statements)
use crate::board::Board; use crate::board::Board;
use crate::r#move::Move;
use crate::search::minimax::minimax; use crate::search::minimax::minimax;
pub struct Engine { pub struct Engine {
@ -19,10 +20,18 @@ impl Engine {
} }
} }
pub fn setpos(&mut self, fen: &str) { pub fn setpos_fen(&mut self, fen: &str) {
self.board = Board::from_fen(fen); self.board = Board::from_fen(fen);
} }
pub fn setpos_startpos(&mut self, moves: &[&str]) {
self.board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
for mv_str in moves {
let mv = Move::from_algebraic(mv_str, &self.board);
self.board.make_move(mv);
}
}
pub fn search(&mut self, depth: u8) { pub fn search(&mut self, depth: u8) {
let (opt_move, _score) = minimax(&mut self.board, depth); let (opt_move, _score) = minimax(&mut self.board, depth);

View file

@ -7,3 +7,4 @@ pub mod display;
pub mod parsing; pub mod parsing;
pub mod search; pub mod search;
pub mod engine; pub mod engine;
pub mod uci;

View file

@ -1,52 +1,7 @@
use std::io::{self, BufRead};
use chess_engine::engine::Engine; use chess_engine::engine::Engine;
use chess_engine::uci::uci_mainloop;
fn main() { fn main() {
// Create a new engine instance
let mut engine = Engine::new("Yakari".to_string(), "EiSiMo".to_string()); let mut engine = Engine::new("Yakari".to_string(), "EiSiMo".to_string());
uci_mainloop(&mut engine);
loop {
// Start the main UCI loop
for line in io::stdin().lock().lines() {
let input = line.unwrap_or_else(|_| "quit".to_string());
let tokens: Vec<&str> = input.split_whitespace().collect();
if tokens.is_empty() {
continue;
}
match tokens[0] {
"uci" => {
println!("id name {}", engine.name);
println!("id author {}", engine.author);
println!("uciok");
}
"isready" => {
println!("readyok");
}
"position" => {
// Example: "position startpos moves e2e4 e7e5"
// Or: "position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
// You'll need to write a parser for this!
// For now, let's just handle the "fen" part simply.
if tokens.len() > 1 && tokens[1] == "fen" {
let fen = tokens[2..].join(" ");
engine.setpos(&fen);
}
}
"go" => {
// Example: "go depth 6"
// For now, we'll just use the fixed depth from your search function.
engine.search(5);
}
"quit" => {
break; // Exit the loop and the program
}
_ => {
// Unknown command, just ignore
}
}
}
}
} }

View file

@ -1,6 +1,6 @@
use std::mem; use std::mem;
use crate::board::{Board, Color, PieceType, CASTLING_BK_FLAG, CASTLING_BQ_FLAG, CASTLING_WK_FLAG, CASTLING_WQ_FLAG}; use crate::board::*;
use crate::r#move::{Move, MOVE_FLAG_BK_CASTLE, MOVE_FLAG_BQ_CASTLE, MOVE_FLAG_PROMO_B, MOVE_FLAG_PROMO_B_CAP, MOVE_FLAG_PROMO_N, MOVE_FLAG_PROMO_N_CAP, MOVE_FLAG_PROMO_Q, MOVE_FLAG_PROMO_Q_CAP, MOVE_FLAG_PROMO_R, MOVE_FLAG_PROMO_R_CAP, MOVE_FLAG_WK_CASTLE, MOVE_FLAG_WQ_CASTLE, MOVE_FROM_MASK, MOVE_TO_MASK}; use crate::r#move::*;
use crate::square::Square; use crate::square::Square;
impl Board { impl Board {
@ -35,27 +35,27 @@ impl Board {
match c { match c {
'P' => { 'P' => {
pieces[PieceType::Pawn as usize][color_idx] |= mask; pieces[PieceType::Pawn as usize][color_idx] |= mask;
pieces_on_squares[sq as usize] = Some(PieceType::Pawn); // <-- ADDED pieces_on_squares[sq as usize] = Some(PieceType::Pawn);
} }
'N' => { 'N' => {
pieces[PieceType::Knight as usize][color_idx] |= mask; pieces[PieceType::Knight as usize][color_idx] |= mask;
pieces_on_squares[sq as usize] = Some(PieceType::Knight); // <-- ADDED pieces_on_squares[sq as usize] = Some(PieceType::Knight);
} }
'B' => { 'B' => {
pieces[PieceType::Bishop as usize][color_idx] |= mask; pieces[PieceType::Bishop as usize][color_idx] |= mask;
pieces_on_squares[sq as usize] = Some(PieceType::Bishop); // <-- ADDED pieces_on_squares[sq as usize] = Some(PieceType::Bishop);
} }
'R' => { 'R' => {
pieces[PieceType::Rook as usize][color_idx] |= mask; pieces[PieceType::Rook as usize][color_idx] |= mask;
pieces_on_squares[sq as usize] = Some(PieceType::Rook); // <-- ADDED pieces_on_squares[sq as usize] = Some(PieceType::Rook);
} }
'Q' => { 'Q' => {
pieces[PieceType::Queen as usize][color_idx] |= mask; pieces[PieceType::Queen as usize][color_idx] |= mask;
pieces_on_squares[sq as usize] = Some(PieceType::Queen); // <-- ADDED pieces_on_squares[sq as usize] = Some(PieceType::Queen);
} }
'K' => { 'K' => {
pieces[PieceType::King as usize][color_idx] |= mask; pieces[PieceType::King as usize][color_idx] |= mask;
pieces_on_squares[sq as usize] = Some(PieceType::King); // <-- ADDED pieces_on_squares[sq as usize] = Some(PieceType::King);
} }
_ => {} _ => {}
} }
@ -237,7 +237,6 @@ impl Board {
} }
} }
impl Move { impl Move {
/// Converts a square index (0-63) to algebraic notation (e.g., 0 -> "a1", 63 -> "h8"). /// Converts a square index (0-63) to algebraic notation (e.g., 0 -> "a1", 63 -> "h8").
fn square_val_to_alg(val: u16) -> String { fn square_val_to_alg(val: u16) -> String {
@ -246,6 +245,16 @@ impl Move {
format!("{}{}", file, rank) format!("{}{}", file, rank)
} }
/// Converts algebraic notation (e.g., "a1") to a Square.
/// Assumes valid input.
fn alg_to_square(alg: &str) -> Square {
let file = (alg.as_bytes()[0] - b'a') as u8;
let rank = (alg.as_bytes()[1] - b'1') as u8;
let sq_index = rank * 8 + file;
// This is unsafe, but we assume valid algebraic notation
unsafe { mem::transmute::<u8, Square>(sq_index) }
}
/// Converts the move to coordinate notation (e.g., "e2e4", "e7e8q", "e1g1"). /// Converts the move to coordinate notation (e.g., "e2e4", "e7e8q", "e1g1").
pub fn to_algebraic(&self) -> String { pub fn to_algebraic(&self) -> String {
let flags = self.get_flags(); let flags = self.get_flags();
@ -267,11 +276,77 @@ impl Move {
}; };
format!("{}{}{}", from_str, to_str, promo_char) format!("{}{}{}", from_str, to_str, promo_char)
} else { } else {
// This covers Quiet, DoublePawn, Capture, EnPassant // This covers Quiet, DoublePawn, Capture, EnPassant, Castles
format!("{}{}", from_str, to_str) format!("{}{}", from_str, to_str)
} }
} }
// TODO /// Creates a Move from algebraic notation (e.g., "e2e4") and a board state.
// pub fn from_algebraic(s: &str, board: &Board) -> Move {} /// Assumes the move is valid and legal for the given board state.
pub fn from_algebraic(s: &str, board: &Board) -> Move {
let from_sq = Self::alg_to_square(&s[0..2]);
let to_sq = Self::alg_to_square(&s[2..4]);
let moving_piece = board.pieces_on_squares[from_sq as usize]
.expect("Invalid move: No piece on 'from' square.");
let is_capture = board.pieces_on_squares[to_sq as usize].is_some();
// 1. Handle Promotions
if s.len() == 5 {
let promo_char = s.chars().nth(4).unwrap();
match (promo_char, is_capture) {
('q', false) => return Move::new(from_sq, to_sq, MOVE_FLAG_PROMO_Q),
('n', false) => return Move::new(from_sq, to_sq, MOVE_FLAG_PROMO_N),
('r', false) => return Move::new(from_sq, to_sq, MOVE_FLAG_PROMO_R),
('b', false) => return Move::new(from_sq, to_sq, MOVE_FLAG_PROMO_B),
('q', true) => return Move::new(from_sq, to_sq, MOVE_FLAG_PROMO_Q_CAP),
('n', true) => return Move::new(from_sq, to_sq, MOVE_FLAG_PROMO_N_CAP),
('r', true) => return Move::new(from_sq, to_sq, MOVE_FLAG_PROMO_R_CAP),
('b', true) => return Move::new(from_sq, to_sq, MOVE_FLAG_PROMO_B_CAP),
_ => panic!("Invalid promotion character"),
}
}
// 2. Handle Castling
if moving_piece == PieceType::King {
let from_idx = from_sq as u8;
let to_idx = to_sq as u8;
// White King Side: e1g1 (idx 4 -> 6)
if from_idx == 4 && to_idx == 6 { return Move::new(from_sq, to_sq, MOVE_FLAG_WK_CASTLE); }
// White Queen Side: e1c1 (idx 4 -> 2)
if from_idx == 4 && to_idx == 2 { return Move::new(from_sq, to_sq, MOVE_FLAG_WQ_CASTLE); }
// Black King Side: e8g8 (idx 60 -> 62)
if from_idx == 60 && to_idx == 62 { return Move::new(from_sq, to_sq, MOVE_FLAG_BK_CASTLE); }
// Black Queen Side: e8c8 (idx 60 -> 58)
if from_idx == 60 && to_idx == 58 { return Move::new(from_sq, to_sq, MOVE_FLAG_BQ_CASTLE); }
}
// 3. Handle Pawn Special Moves
if moving_piece == PieceType::Pawn {
// Double Pawn Push
let rank_diff = (to_sq as i8 - from_sq as i8).abs();
if rank_diff == 16 {
return Move::new(from_sq, to_sq, MOVE_FLAG_DOUBLE_PAWN);
}
// En Passant
// Must be diagonal move, to the en_passant_target square, and not a normal capture
if Some(to_sq) == board.en_passant_target && !is_capture {
let from_file = from_sq as u8 % 8;
let to_file = to_sq as u8 % 8;
if from_file != to_file {
return Move::new(from_sq, to_sq, MOVE_FLAG_EN_PASSANT);
}
}
}
// 4. Handle Normal Captures / Quiet Moves
if is_capture {
Move::new(from_sq, to_sq, MOVE_FLAG_CAPTURE)
} else {
Move::new(from_sq, to_sq, MOVE_FLAG_QUIET)
}
}
} }

64
src/uci.rs Normal file
View file

@ -0,0 +1,64 @@
// uci.rs
use std::io::{self, BufRead};
use crate::engine::Engine;
pub fn uci_mainloop(engine: &mut Engine) {
loop {
// Start the main UCI loop
for line in io::stdin().lock().lines() {
let input = line.unwrap_or_else(|_| "quit".to_string());
let tokens: Vec<&str> = input.split_whitespace().collect();
if tokens.is_empty() {
continue;
}
match tokens[0] {
"uci" => {
println!("id name {}", engine.name);
println!("id author {}", engine.author);
println!("uciok");
}
"isready" => {
println!("readyok");
}
"ucinewgame" => {
// not yet implemented
}
"position" => {
if tokens.len() > 1 {
if tokens[1] == "fen" {
let fen = tokens[2..].join(" ");
engine.setpos_fen(&fen);
} else if tokens[1] == "startpos" {
// Check explicitly for the "moves" keyword
if tokens.len() > 2 && tokens[2] == "moves" {
// Command: "position startpos moves e2e4 e7e5 ..."
// Pass only the tokens *after* "moves"
engine.setpos_startpos(&tokens[3..]);
} else {
// Command: "position startpos"
// Pass an empty slice
engine.setpos_startpos(&[]);
}
}
}
}
"go" => {
// TODO add a lot functionality
engine.search(5);
}
"stop" => {
// TODO stop search as soon as possible
}
"quit" => {
return;
}
_ => {
// Unknown command, just ignore
}
}
}
}
}