use std::thread;
use chrono::Utc;
use shakmaty::{
fen::Fen, CastlingMode, Chess, Color, FromSetup, Move, MoveList, Outcome, Position, Role,
};
use crate::state::Store;
use super::Engine;
const DEPTH: usize = 3;
const MIN_KEEP: usize = 3;
enum ScoreResult {
Score(i32, MoveList, Chess),
Outcome(Outcome),
}
fn piece_score(color: Color, game: &Chess) -> i32 {
let board = game.board();
let bb = board.by_color(color);
(bb.intersect(board.by_role(Role::Pawn)).count()
+ bb.intersect(board.by_role(Role::Bishop)).count() * 3
+ bb.intersect(board.by_role(Role::Knight)).count() * 3
+ bb.intersect(board.by_role(Role::Rook)).count() * 5
+ bb.intersect(board.by_role(Role::Queen)).count() * 9) as i32
}
fn score_move(color: Color, mut game: Chess, m: &Move, len: i32) -> ScoreResult {
game.play_unchecked(m);
if let Some(outcome) = game.outcome() {
return ScoreResult::Outcome(outcome);
}
let legal_moves = game.legal_moves();
let (us, them) = (piece_score(color, &game), piece_score(color.other(), &game));
let count = legal_moves.len();
ScoreResult::Score((len + us) - ((count as i32) + them), legal_moves, game)
}
fn eval_outcome(color: Color, outcome: Outcome) -> i32 {
match outcome {
Outcome::Draw => 0,
Outcome::Decisive { winner } => {
if winner == color {
i32::MAX
} else {
i32::MIN
}
}
}
}
fn eval_move(color: Color, depth: usize, game: Chess, m: &Move, from: i32) -> i32 {
match score_move(color, game.clone(), m, from) {
ScoreResult::Outcome(outcome) => eval_outcome(color, outcome),
ScoreResult::Score(score, oponent_moves, game) => {
if depth == 0 {
return score;
}
let mut variant_scores: Vec<i32> = Vec::new();
for op_move in oponent_moves {
let mut op_game = game.clone();
op_game.play_unchecked(&op_move);
let legal_moves = op_game.legal_moves();
let len = legal_moves.len();
let n_keep = MIN_KEEP; let mut scores: Vec<(Move, i32)> = Vec::with_capacity(len);
for m in legal_moves {
match score_move(color, op_game.clone(), &m, len as i32) {
ScoreResult::Outcome(outcome) => {
scores.push((m, eval_outcome(color, outcome)))
}
ScoreResult::Score(score, _, _) => scores.push((m, score)),
}
}
scores.sort_by_key(|(_, s)| *s);
variant_scores.push(
scores
.into_iter()
.rev()
.take(n_keep)
.map(|(m, _)| eval_move(color, depth - 1, op_game.clone(), &m, len as i32))
.max()
.unwrap_or(i32::MIN),
);
}
variant_scores.into_iter().max().unwrap_or(i32::MIN)
}
}
}
fn bestmove(game: Chess) -> Option<Move> {
let color = game.turn();
let moves = game.legal_moves();
let len = moves.len();
let fen = Fen::from_position(game.clone(), shakmaty::EnPassantMode::Always);
log::debug!("bestmove {}", fen);
if moves.len() == 0 {
None
} else {
let scores: Vec<(&Move, i32)> = moves
.iter()
.map(|m| {
let start = Utc::now();
log::debug!("start-eval {}", m.to_uci(CastlingMode::Standard));
let score = eval_move(color, DEPTH, game.clone(), m, len as i32);
log::debug!("..............score = {} ", score);
log::debug!(
"..............time = {}",
(Utc::now() - start).num_milliseconds()
);
(m, score)
})
.collect();
scores
.into_iter()
.reduce(|(am, ai), (m, i)| if i > ai { (m, i) } else { (am, ai) })
.map(|(m, _)| m.clone())
}
}
pub struct SimpleEngine {
store: Store,
}
impl Engine for SimpleEngine {
fn new_game(&self) {}
fn go(
&self,
fen: shakmaty::fen::Fen,
_white_time: chrono::Duration,
_black_time: chrono::Duration,
) {
let store = self.store.clone();
thread::spawn(move || {
Chess::from_setup(fen.as_setup().clone(), shakmaty::CastlingMode::Standard)
.ok()
.and_then(bestmove)
.map(|mv| {
store.update_engine(super::EngineState::PendingMove(mv));
});
});
}
}
pub fn connect_engine(store: Store) -> SimpleEngine {
SimpleEngine { store }
}