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

236 lines
8.2 KiB

use chess::{
Board as ChessBoard, 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: ChessBoard, 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(board: ChessBoard, playing_as: Color, move_history: Vec<ChessBoard>) -> f64 {
// 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 += 5.0;
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
}
#[derive(Clone)]
pub struct Board {
pub current_board: ChessBoard,
pub board_history: Vec<ChessBoard>,
}
pub struct Game;
impl minimax::Game for Game {
type S = Board;
type M = ChessMove;
fn generate_moves(state: &Self::S, moves: &mut Vec<Self::M>) {
let iter = MoveGen::new_legal(&state.current_board);
for mov in iter {
moves.push(mov)
}
}
fn get_winner(b: &Board) -> Option<minimax::Winner> {
match b.current_board.status() {
BoardStatus::Ongoing => None,
BoardStatus::Stalemate => Some(minimax::Winner::Draw),
BoardStatus::Checkmate => Some(minimax::Winner::PlayerJustMoved),
}
}
fn apply(b: &mut Board, m: ChessMove) -> Option<Board> {
let mut new_board_history = b.board_history.clone();
new_board_history.push(b.current_board);
Some(Board {
current_board: b.current_board.make_move_new(m),
board_history: new_board_history,
})
}
fn zobrist_hash(b: &Board) -> u64 {
b.current_board.get_hash()
}
}
#[derive(Default, Clone)]
pub struct Evaluator;
impl minimax::Evaluator for Evaluator {
type G = Game;
fn evaluate(&self, b: &Board) -> minimax::Evaluation {
static_board_eval(
b.current_board,
b.current_board.side_to_move(),
b.board_history.clone(),
) as i16
}
}