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.
239 lines
8.2 KiB
239 lines
8.2 KiB
use std::f64::{INFINITY, NEG_INFINITY};
|
|
|
|
use chess::{
|
|
Board as ChessBoard, BoardStatus, ChessMove, Color, File, MoveGen, Piece, Rank, Square,
|
|
};
|
|
use itertools::Itertools;
|
|
|
|
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)]
|
|
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
|
|
}
|
|
}
|