From f1ec0a08d9865cd953333cea6ae2bdf9f9b68235 Mon Sep 17 00:00:00 2001 From: Moritz Date: Sat, 15 Nov 2025 11:43:03 +0100 Subject: [PATCH] fixed uci --- src/engine.rs | 11 +++++- src/lib.rs | 1 + src/main.rs | 49 +------------------------ src/parsing.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++------ src/uci.rs | 64 ++++++++++++++++++++++++++++++++ 5 files changed, 164 insertions(+), 60 deletions(-) create mode 100644 src/uci.rs diff --git a/src/engine.rs b/src/engine.rs index c80d3ac..740dc32 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,5 +1,6 @@ // ... (your use statements) use crate::board::Board; +use crate::r#move::Move; use crate::search::minimax::minimax; 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); } + 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) { let (opt_move, _score) = minimax(&mut self.board, depth); diff --git a/src/lib.rs b/src/lib.rs index 955196f..bb08255 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,3 +7,4 @@ pub mod display; pub mod parsing; pub mod search; pub mod engine; +pub mod uci; diff --git a/src/main.rs b/src/main.rs index e249057..bd43db4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,52 +1,7 @@ -use std::io::{self, BufRead}; use chess_engine::engine::Engine; +use chess_engine::uci::uci_mainloop; fn main() { - // Create a new engine instance let mut engine = Engine::new("Yakari".to_string(), "EiSiMo".to_string()); - - 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 - } - } - } - } - + uci_mainloop(&mut engine); } diff --git a/src/parsing.rs b/src/parsing.rs index 421f6a4..6f20a09 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -1,6 +1,6 @@ use std::mem; -use crate::board::{Board, Color, PieceType, CASTLING_BK_FLAG, CASTLING_BQ_FLAG, CASTLING_WK_FLAG, CASTLING_WQ_FLAG}; -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::board::*; +use crate::r#move::*; use crate::square::Square; impl Board { @@ -35,27 +35,27 @@ impl Board { match c { 'P' => { 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' => { 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' => { 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' => { 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' => { 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' => { 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 { /// Converts a square index (0-63) to algebraic notation (e.g., 0 -> "a1", 63 -> "h8"). fn square_val_to_alg(val: u16) -> String { @@ -246,6 +245,16 @@ impl Move { 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::(sq_index) } + } + /// Converts the move to coordinate notation (e.g., "e2e4", "e7e8q", "e1g1"). pub fn to_algebraic(&self) -> String { let flags = self.get_flags(); @@ -267,11 +276,77 @@ impl Move { }; format!("{}{}{}", from_str, to_str, promo_char) } else { - // This covers Quiet, DoublePawn, Capture, EnPassant + // This covers Quiet, DoublePawn, Capture, EnPassant, Castles format!("{}{}", from_str, to_str) } } - // TODO - // pub fn from_algebraic(s: &str, board: &Board) -> Move {} + /// Creates a Move from algebraic notation (e.g., "e2e4") and a board state. + /// 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) + } + } } \ No newline at end of file diff --git a/src/uci.rs b/src/uci.rs new file mode 100644 index 0000000..b2e1f53 --- /dev/null +++ b/src/uci.rs @@ -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 + } + } + } + } +} \ No newline at end of file