use std::{io, str::FromStr};
use chessframe::{
bitboard::EMPTY,
board::Board,
chess_move::ChessMove,
color::Color,
piece::{Piece, PIECES},
uci::*,
};
const PIECE_VALUES: [isize; 6] = [100, 300, 325, 500, 900, 0];
const MVV_LVA: [[i8; 6]; 6] = [
[15, 14, 13, 12, 11, 10], [25, 24, 23, 22, 21, 20], [35, 34, 33, 32, 31, 30], [45, 44, 43, 42, 41, 40], [55, 54, 53, 52, 51, 50], [0, 0, 0, 0, 0, 0], ];
fn get_mvv_lva(victim: Piece, attacker: Piece) -> i8 {
unsafe {
*MVV_LVA
.get_unchecked(victim.to_index())
.get_unchecked(attacker.to_index())
}
}
struct SimpleMoveMaker {
board: Option<Board>,
quitting: bool,
}
impl SimpleMoveMaker {
const SEARCH_DEPTH: usize = 7;
const MATE_SCORE: isize = 1_000_000_000;
pub fn new() -> SimpleMoveMaker {
SimpleMoveMaker {
board: None,
quitting: false,
}
}
pub fn run(&mut self) {
loop {
self.handle_command();
if self.quitting {
break;
}
}
}
pub fn search_base(board: &Board, depth: usize) -> (isize, Option<ChessMove>) {
let mut max = isize::MIN;
let mut best_move = None;
let alpha = isize::MIN;
let beta = isize::MAX;
let mut moves = board.generate_moves_vec(!EMPTY);
Self::sort_moves(board, &mut moves);
for mv in moves {
if let Ok(board) = board.make_move_new(&mv) {
let score = -Self::search(&board, alpha, beta, depth - 1);
if score > max {
max = score;
best_move = Some(mv);
}
}
}
(max, best_move)
}
#[rustfmt::skip]
fn search(board: &Board, mut alpha: isize, beta: isize, depth: usize) -> isize {
if depth == 0 {
return Self::quiescence_search(board, alpha, beta);
}
let mut legal_moves = false;
let mut max = isize::MIN;
let mut moves = board.generate_moves_vec(!EMPTY);
Self::sort_moves(board, &mut moves);
for mv in moves {
if let Ok(board) = board.make_move_new(&mv) {
legal_moves = true;
let score = -Self::search(&board, -beta, -alpha, depth - 1);
if score > max {
max = score;
if score > alpha {
alpha = score;
}
}
if score >= beta {
return max;
}
}
}
if !legal_moves {
if board.in_check() {
return -SimpleMoveMaker::MATE_SCORE + SimpleMoveMaker::SEARCH_DEPTH as isize - depth as isize;
} else {
return 0;
}
}
max
}
fn quiescence_search(board: &Board, mut alpha: isize, beta: isize) -> isize {
let evaluation = Self::evaluate(board);
if evaluation >= beta {
return beta;
}
if evaluation > alpha {
alpha = evaluation;
}
let mut moves = board.generate_moves_vec(board.occupancy(!board.side_to_move));
Self::sort_moves(board, &mut moves);
for mv in moves {
if let Ok(board) = board.make_move_new(&mv) {
let score = -Self::quiescence_search(&board, -beta, -alpha);
if score >= beta {
return beta;
}
if score > alpha {
alpha = score;
}
}
}
alpha
}
fn evaluate(board: &Board) -> isize {
let mut score = 0;
for piece in PIECES.iter() {
score += board.pieces_color(*piece, Color::White).count_ones() as isize
* PIECE_VALUES[piece.to_index()];
score -= board.pieces_color(*piece, Color::Black).count_ones() as isize
* PIECE_VALUES[piece.to_index()];
}
if board.in_check() {
score -= 50;
}
let perspective = if board.side_to_move == Color::White {
1
} else {
-1
};
score * perspective
}
fn sort_moves(board: &Board, moves: &mut Vec<ChessMove>) {
moves.sort_by_key(|mv| -Self::score_move(board, *mv));
}
fn score_move(board: &Board, mv: ChessMove) -> isize {
let moved = unsafe { board.get_piece(mv.from).unwrap_unchecked() };
if let Some(capture) = board.get_piece(mv.to) {
return get_mvv_lva(capture, moved) as isize;
}
0
}
}
impl Uci for SimpleMoveMaker {
fn send_command(&mut self, command: UciCommand) {
match command {
UciCommand::Id { name, author } => {
println!("id name {}", name);
println!("id author {}", author);
}
UciCommand::UciOk => {
println!("uciok");
}
UciCommand::ReadyOk => {
println!("readyok");
}
UciCommand::BestMove { best_move, ponder } => {
if let Some(ponder) = ponder {
println!("bestmove {} ponder {}", best_move, ponder);
} else {
println!("bestmove {}", best_move);
}
}
UciCommand::Info(info) => {
println!("{}", info);
}
_ => {}
}
}
fn read_command(&mut self) -> Option<UciCommand> {
let mut line = String::new();
io::stdin().read_line(&mut line).unwrap();
UciCommand::from_str(line.trim()).ok()
}
#[rustfmt::skip]
fn handle_command(&mut self) {
if let Some(command) = self.read_command() {
match command {
UciCommand::Uci => {
self.send_command(UciCommand::Id {
name: "Simple Move Maker".to_string(),
author: "Zirconium419122".to_string(),
});
self.send_command(UciCommand::UciOk);
}
UciCommand::Debug(debug) => {
if debug {
self.send_command(UciCommand::Info(Info {
string: Some("Debug mode not supported!".to_string()),
..Default::default()
}));
}
}
UciCommand::IsReady => {
self.send_command(UciCommand::ReadyOk);
}
UciCommand::UciNewGame => self.board = None,
UciCommand::Position { fen, moves } => {
if fen == "startpos" {
self.board = Some(Board::default());
} else {
self.board = Some(Board::from_fen(&fen));
};
if let Some(moves) = moves {
let board = self.board.as_mut().unwrap();
for mv in moves {
let mv = board.infer_move(&mv).unwrap();
let _ = board.make_move(&mv);
}
}
}
UciCommand::Go { .. } => {
if let Some(ref board) = self.board {
let (score, best_move) = Self::search_base(&board, SimpleMoveMaker::SEARCH_DEPTH);
if let Some(best_move) = best_move {
if score.abs() >= SimpleMoveMaker::MATE_SCORE - 100 {
let correction = if score > 0 { 1 } else { -1 };
let moves_to_mate = SimpleMoveMaker::MATE_SCORE - score.abs();
let mate_in_moves = (moves_to_mate / 2) + 1;
let mut score = Score::default();
score.mate = Some(correction * mate_in_moves);
self.send_command(UciCommand::Info(Info {
pv: Some(best_move.to_string()),
score: Some(score),
..Default::default()
}));
} else {
let cp = score;
let mut score = Score::default();
score.cp = Some(cp);
self.send_command(UciCommand::Info(Info {
pv: Some(best_move.to_string()),
score: Some(score),
..Default::default()
}));
}
self.send_command(UciCommand::BestMove {
best_move: best_move.to_string(),
ponder: None,
});
}
}
}
UciCommand::Stop => {}
UciCommand::Quit => self.quitting = true,
_ => {}
}
}
}
}
fn main() {
let mut engine = SimpleMoveMaker::new();
engine.run();
}