You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
inkwell/src/eval.rs

274 lines
9.3 KiB

use std::f64::{INFINITY, NEG_INFINITY};
use chess::{Board, BoardStatus, ChessMove, Color, File, MoveGen, Piece, Rank, Square};
use crate::piece_tables;
use crate::piece_values::get_piece_value;
fn square_to_index(square: Square, playing_as: Color) -> usize {
let mut idx = 0;
if playing_as == Color::White {
match square.get_rank() {
Rank::First => idx += 0,
Rank::Second => idx += 8,
Rank::Third => idx += 16,
Rank::Fourth => idx += 24,
Rank::Fifth => idx += 32,
Rank::Sixth => idx += 40,
Rank::Seventh => idx += 48,
Rank::Eighth => idx += 56,
};
match square.get_file() {
File::A => idx += 0,
File::B => idx += 1,
File::C => idx += 2,
File::D => idx += 3,
File::E => idx += 4,
File::F => idx += 5,
File::G => idx += 6,
File::H => idx += 7,
};
} else {
match square.get_rank() {
Rank::Eighth => idx += 0,
Rank::Seventh => idx += 8,
Rank::Sixth => idx += 16,
Rank::Fifth => idx += 24,
Rank::Fourth => idx += 32,
Rank::Third => idx += 40,
Rank::Second => idx += 48,
Rank::First => idx += 56,
};
match square.get_file() {
File::H => idx += 0,
File::G => idx += 1,
File::F => idx += 2,
File::E => idx += 3,
File::D => idx += 4,
File::C => idx += 5,
File::B => idx += 6,
File::A => idx += 7,
}
}
idx
}
fn evaluate_piece_square(board: Board, square: Square, playing_as: Color) -> (f64, f64, f64) {
let mut mg_value: f64 = 0.0;
let mut eg_value: f64 = 0.0;
let mut gamephase: f64 = 0.0;
let piece = board.piece_on(square);
match piece {
Some(Piece::Pawn) => {
mg_value += get_piece_value(piece)
+ (piece_tables::MIDGAME_PAWNS[square_to_index(square, playing_as)] / 10.0);
eg_value += get_piece_value(piece)
+ (piece_tables::ENDGAME_PAWNS[square_to_index(square, playing_as)] / 10.0);
gamephase += piece_tables::GAMEPHASE[get_piece_value(piece).floor() as usize];
}
Some(Piece::Knight) => {
mg_value += get_piece_value(piece)
+ (piece_tables::MIDGAME_HORSEYS[square_to_index(square, playing_as)] / 10.0);
eg_value += get_piece_value(piece)
+ (piece_tables::ENDGAME_HORSEYS[square_to_index(square, playing_as)] / 10.0);
gamephase += piece_tables::GAMEPHASE[get_piece_value(piece).floor() as usize];
}
Some(Piece::Bishop) => {
mg_value += get_piece_value(piece)
+ (piece_tables::MIDGAME_BISHOPS[square_to_index(square, playing_as)] / 10.0);
eg_value += get_piece_value(piece)
+ (piece_tables::ENDGAME_BISHOPS[square_to_index(square, playing_as)] / 10.0);
gamephase += piece_tables::GAMEPHASE[get_piece_value(piece).floor() as usize];
}
Some(Piece::Rook) => {
mg_value += get_piece_value(piece)
+ (piece_tables::MIDGAME_ROOKS[square_to_index(square, playing_as)] / 10.0);
eg_value += get_piece_value(piece)
+ (piece_tables::ENDGAME_ROOKS[square_to_index(square, playing_as)] / 10.0);
gamephase += piece_tables::GAMEPHASE[get_piece_value(piece).floor() as usize];
}
Some(Piece::Queen) => {
mg_value += get_piece_value(piece)
+ (piece_tables::MIDGAME_QUEENS[square_to_index(square, playing_as)] / 10.0);
eg_value += get_piece_value(piece)
+ (piece_tables::ENDGAME_QUEENS[square_to_index(square, playing_as)] / 10.0);
gamephase += piece_tables::GAMEPHASE[get_piece_value(piece).floor() as usize];
}
Some(Piece::King) => {
mg_value += piece_tables::MIDGAME_KINGS[square_to_index(square, playing_as)] / 10.0;
eg_value += piece_tables::ENDGAME_KINGS[square_to_index(square, playing_as)] / 10.0;
}
None => {}
}
(mg_value, eg_value, gamephase)
}
fn static_board_eval(
mov: ChessMove,
old_board: Board,
board: Board,
playing_as: Color,
move_history: Vec<(ChessMove, Board)>,
) -> f64 {
if move_history.len() > 6 {
if move_history[move_history.len() - 1].0 == mov
&& move_history[move_history.len() - 2].0 == mov
{
return -666.0;
}
}
// basic material counting + midgame or endgame determination
let mut our_mg_value: f64 = 0.0;
let mut our_eg_value: f64 = 0.0;
let mut opponent_mg_value: f64 = 0.0;
let mut opponent_eg_value: f64 = 0.0;
let mut gamephase: f64 = 0.0;
board.color_combined(playing_as).for_each(|square| {
let tmp_gamephase;
(our_mg_value, our_eg_value, tmp_gamephase) =
evaluate_piece_square(board, square, playing_as);
gamephase += tmp_gamephase;
});
board.color_combined(!playing_as).for_each(|square| {
let tmp_gamephase;
(opponent_mg_value, opponent_eg_value, tmp_gamephase) =
evaluate_piece_square(board, square, !playing_as);
gamephase += tmp_gamephase;
});
let mg_score = our_mg_value - opponent_mg_value;
let eg_score = our_eg_value - opponent_eg_value;
let mg_phase = gamephase.min(24.0);
let eg_phase = 24.0 - mg_phase;
let mut current_eval = (mg_score * mg_phase + eg_score * eg_phase) / 24.0;
// checking heuristic
let mut are_we_in_check = false;
let mut is_opponent_in_check = false;
board
.checkers()
.filter(|sq| board.color_on(*sq).unwrap() == playing_as)
.for_each(|_| {
if !are_we_in_check {
current_eval -= 66.6;
are_we_in_check = true;
} else {
current_eval -= 12.5;
}
});
board
.checkers()
.filter(|sq| board.color_on(*sq).unwrap() == !playing_as)
.for_each(|_| {
if !is_opponent_in_check && move_history.len() > 10 {
current_eval += 17.5;
is_opponent_in_check = true;
} else {
current_eval += 3.5;
}
});
// deprioritize king movement
if board.piece_on(mov.get_source()).unwrap_or(Piece::Pawn) == Piece::King && !are_we_in_check {
current_eval -= 12.5;
}
// incentivize capturing if material value of capturer is lower than material value of captured
// FIXME: this should take into account whether a piece is protected at all (and if so let pieces be captured by ones of equal and above importance)
if let (Some(captured_piece), Some(capturing_piece)) = (
old_board.piece_on(mov.get_dest()),
old_board.piece_on(mov.get_source()),
) {
current_eval +=
(get_piece_value(Some(captured_piece)) - get_piece_value(Some(capturing_piece)))
}
current_eval
}
pub fn eval_board(
board: Board,
last_board: Board,
is_maximizing_player: bool,
depth: u8,
move_history: Vec<(ChessMove, Board)>,
original_playing_as: Color,
mut alpha: f64,
mut beta: f64,
) -> f64 {
let last_move = if move_history.len() > 0 {
Some(move_history.last().unwrap().0)
} else {
None
};
if depth == 0 || board.status() == BoardStatus::Checkmate {
return static_board_eval(
last_move.unwrap(),
last_board,
board,
!board.side_to_move(),
move_history,
);
} else {
let moves_iter = MoveGen::new_legal(&board).into_iter();
if is_maximizing_player {
let mut max_eval = NEG_INFINITY;
for mov in moves_iter {
let mut new_move_history = move_history.clone();
let new_board = board.make_move_new(mov);
new_move_history.push((mov, new_board));
let eval = eval_board(
new_board,
board,
false,
depth - 1,
new_move_history,
original_playing_as,
alpha,
beta,
);
max_eval = max_eval.max(eval);
alpha = alpha.max(eval);
if beta <= alpha {
break;
}
}
return max_eval;
} else {
let mut min_eval = INFINITY;
for mov in moves_iter {
let mut new_move_history = move_history.clone();
let new_board = board.make_move_new(mov);
new_move_history.push((mov, new_board));
let eval = eval_board(
new_board,
board,
true,
depth - 1,
new_move_history,
original_playing_as,
alpha,
beta,
);
min_eval = min_eval.min(eval);
beta = beta.min(eval);
if beta <= alpha {
break;
}
}
return min_eval;
}
}
}