|
@@ -4,6 +4,7 @@ use bitboard::*;
|
|
|
use board::Board;
|
|
|
use static_assertions::const_assert_eq;
|
|
|
use ttable::{Cache, EntryType};
|
|
|
+use std::cmp::max;
|
|
|
use std::iter::Iterator;
|
|
|
use std::sync::Arc;
|
|
|
use std::sync::Mutex;
|
|
@@ -172,6 +173,15 @@ impl Move {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ fn captures(&self) -> Option<PieceType> {
|
|
|
+ match self {
|
|
|
+ Move::Default{ mov: _, pc } => pc.capture_type(),
|
|
|
+ Move::Promotion{ mov: _, pc } => pc.capture_type(),
|
|
|
+ Move::EnPassant{ mov: _, beaten: _ } => None,
|
|
|
+ Move::Castling{ mov: _, side: _, left: _ } => None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
pub fn is_promotion(&self) -> bool {
|
|
|
match self {
|
|
|
Move::Promotion { mov: _, pc: _ } => true,
|
|
@@ -213,6 +223,7 @@ impl Move {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -257,10 +268,11 @@ enum SortingState {
|
|
|
/// generates and sorts moves which can then be extracted via an Iterator interface
|
|
|
///
|
|
|
pub struct MoveGenerator {
|
|
|
+ board: Board,
|
|
|
moves: Vec<Move>,
|
|
|
side: Side,
|
|
|
|
|
|
- captures: Vec<Move>,
|
|
|
+ captures: Vec<(Move, i32)>,
|
|
|
counters: Vec<(Move, i16)>,
|
|
|
quiets: Vec<Move>,
|
|
|
|
|
@@ -275,6 +287,7 @@ pub struct MoveGenerator {
|
|
|
impl MoveGenerator {
|
|
|
pub fn generate_legal_moves(game: &mut Board, ce: Option<&CacheEntry>, killers: &[Move], last_move: Option<Move>, counters_table: Arc<Mutex<CountermoveTable>>, side: Side) -> Self {
|
|
|
MoveGenerator {
|
|
|
+ board: game.clone(),
|
|
|
moves: generate_legal_moves(game, side, false),
|
|
|
side,
|
|
|
captures: Vec::with_capacity(32),
|
|
@@ -317,7 +330,7 @@ impl Iterator for MoveGenerator {
|
|
|
if self.captures.is_empty() {
|
|
|
for m in &self.moves {
|
|
|
if m.is_capture() {
|
|
|
- self.captures.push(*m);
|
|
|
+ self.captures.push((*m, calculate_see(self.board.clone(), *m, self.board.turn)));
|
|
|
}
|
|
|
else {
|
|
|
self.quiets.push(*m);
|
|
@@ -330,19 +343,17 @@ impl Iterator for MoveGenerator {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // lower mvvlva score is better
|
|
|
- let mut best_mvv_lva = crate::evaluate::MAX_VALUE;
|
|
|
+ let mut best_score = crate::evaluate::MIN_VALUE;
|
|
|
let mut best_move: Option<Move> = None;
|
|
|
|
|
|
- for c in &self.captures {
|
|
|
- let score = mvv_lva_score(c);
|
|
|
- if score < best_mvv_lva {
|
|
|
+ for (c, score) in &self.captures {
|
|
|
+ if *score > best_score {
|
|
|
best_move = Some(*c);
|
|
|
- best_mvv_lva = score;
|
|
|
+ best_score = *score;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- self.captures.retain(|m| Some(*m) != best_move);
|
|
|
+ self.captures.retain(|(m, _s)| Some(*m) != best_move);
|
|
|
if self.captures.is_empty() {
|
|
|
self.state = SortingState::Killers;
|
|
|
}
|
|
@@ -624,22 +635,162 @@ fn mvv_lva_score(mov: &Move) -> i32 {
|
|
|
Move::Default { mov: _, pc } => {
|
|
|
let attack = PIECE_VALUES[pc.piece_type() as usize];
|
|
|
let victim = pc.capture_type().map(|ct| PIECE_VALUES[ct as usize]).unwrap_or(0);
|
|
|
- attack - victim * 5
|
|
|
+ attack - victim
|
|
|
},
|
|
|
Move::Promotion { mov: _, pc } => {
|
|
|
let victim = pc.capture_type().map(|ct| PIECE_VALUES[ct as usize]).unwrap_or(0);
|
|
|
- PIECE_VALUES[PAWN as usize] - victim * 5
|
|
|
+ PIECE_VALUES[PAWN as usize] - victim
|
|
|
},
|
|
|
_ => 0
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-fn see_score(board: &mut Board, sq: Square, side: Side) -> i32 {
|
|
|
+const SEE_PIECE_VALUES: [i32; 6] = [
|
|
|
+ 100, // Pawn
|
|
|
+ 300, // Knight
|
|
|
+ 320, // Bishop
|
|
|
+ 500, // Rook
|
|
|
+ 1000, // Queen
|
|
|
+ 100000 // King
|
|
|
+];
|
|
|
+
|
|
|
+pub fn calculate_see(mut board: Board, mov: Move, side: Side) -> i32 {
|
|
|
+ let capture_type = mov.captures();
|
|
|
+ board.apply(mov);
|
|
|
+
|
|
|
+ if let Some(ct) = capture_type {
|
|
|
+ return SEE_PIECE_VALUES[ct as usize] - see_score::<true>(&mut board, mov.to(), !side);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+fn see_score<const NO_CAPTURE_ALLLOWED: bool>(board: &mut Board, sq: Square, side: Side) -> i32 {
|
|
|
+ let sq_bb = from_square(sq);
|
|
|
+ let friends = board.get_all_side(side);
|
|
|
+ let others = board.get_all_side(!side);
|
|
|
+
|
|
|
+ let (piece_type, piece_side) =
|
|
|
+ if let Some(piece) = board.get_square(sq) {
|
|
|
+ piece
|
|
|
+ }
|
|
|
+ else { return 0; };
|
|
|
+
|
|
|
+
|
|
|
+ let pawns = if side == BLACK { northeast_one(sq_bb) | northwest_one(sq_bb) }
|
|
|
+ else { southeast_one(sq_bb) | southwest_one(sq_bb) };
|
|
|
+ let actual_pawns = board.get_piece(PAWN, side);
|
|
|
+ if (sq_bb & (ROW_1 | ROW_8)) == 0 && (pawns & actual_pawns) != 0 {
|
|
|
+ for pawn in BitboardIterator(pawns & actual_pawns) {
|
|
|
+ *board.pawns_mut(side) ^= pawn | sq_bb;
|
|
|
+ *board.get_piece_mut(piece_type, piece_side) ^= sq_bb;
|
|
|
+
|
|
|
+ let subsee = SEE_PIECE_VALUES[piece_type as usize] - see_score::<true>(board, sq, !side);
|
|
|
+ if NO_CAPTURE_ALLLOWED {
|
|
|
+ return max(0, subsee);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return subsee;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let knights = get_knight_targets(sq);
|
|
|
+ let actual_knights = board.get_piece(KNIGHT, side);
|
|
|
+ if (knights & actual_knights) != 0 {
|
|
|
+ for knight in BitboardIterator(knights & actual_knights) {
|
|
|
+ *board.knights_mut(side) ^= knight | sq_bb;
|
|
|
+ *board.get_piece_mut(piece_type, piece_side) ^= sq_bb;
|
|
|
+
|
|
|
+ let subsee = SEE_PIECE_VALUES[piece_type as usize] - see_score::<true>(board, sq, !side);
|
|
|
+ if NO_CAPTURE_ALLLOWED {
|
|
|
+ return max(0, subsee);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return subsee;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let diags = generate_sliding_destinations(others, friends, sq, false, true);
|
|
|
+ let actual_bishops = board.get_piece(BISHOP, side);
|
|
|
+ if (diags & actual_bishops) != 0 {
|
|
|
+ for bishop in BitboardIterator(diags & actual_bishops) {
|
|
|
+ *board.bishops_mut(side) ^= bishop | sq_bb;
|
|
|
+ *board.get_piece_mut(piece_type, piece_side) ^= sq_bb;
|
|
|
+
|
|
|
+ let subsee = SEE_PIECE_VALUES[piece_type as usize] - see_score::<true>(board, sq, !side);
|
|
|
+ if NO_CAPTURE_ALLLOWED {
|
|
|
+ return max(0, subsee);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return subsee;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let straights = generate_sliding_destinations(others, friends, sq, true, false);
|
|
|
+ let actual_rooks = board.get_piece(ROOK, side);
|
|
|
+ if (straights & actual_rooks) != 0 {
|
|
|
+ for rook in BitboardIterator(straights & actual_rooks) {
|
|
|
+ *board.rooks_mut(side) ^= rook | sq_bb;
|
|
|
+ *board.get_piece_mut(piece_type, piece_side) ^= sq_bb;
|
|
|
+
|
|
|
+ let subsee = SEE_PIECE_VALUES[piece_type as usize] - see_score::<true>(board, sq, !side);
|
|
|
+ if NO_CAPTURE_ALLLOWED {
|
|
|
+ return max(0, subsee);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return subsee;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let actual_queens = board.get_piece(QUEEN, side);
|
|
|
+ if ((straights | diags) & actual_queens) != 0 {
|
|
|
+ for queen in BitboardIterator((straights | diags) & actual_queens) {
|
|
|
+ *board.queens_mut(side) ^= queen | sq_bb;
|
|
|
+ *board.get_piece_mut(piece_type, piece_side) ^= sq_bb;
|
|
|
+
|
|
|
+ let subsee = SEE_PIECE_VALUES[piece_type as usize] - see_score::<true>(board, sq, !side);
|
|
|
+ if NO_CAPTURE_ALLLOWED {
|
|
|
+ return max(0, subsee);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return subsee;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let file = north_one(sq_bb) | sq_bb | south_one(sq_bb);
|
|
|
+ let kings= west_one(file) | file | east_one(file);
|
|
|
+ let actual_king= board.get_piece(KING, side);
|
|
|
+ if (kings & actual_king) != 0 {
|
|
|
+ for king in BitboardIterator(kings & actual_king) {
|
|
|
+ *board.kings_mut(side) ^= king | sq_bb;
|
|
|
+ *board.get_piece_mut(piece_type, piece_side) ^= sq_bb;
|
|
|
+
|
|
|
+ let subsee = SEE_PIECE_VALUES[piece_type as usize] - see_score::<true>(board, sq, !side);
|
|
|
+ if NO_CAPTURE_ALLLOWED {
|
|
|
+ return max(0, subsee);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return subsee;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
0
|
|
|
}
|
|
|
|
|
|
-pub fn sort_moves_least_valuable_attacker(_game: &mut Board, move_list: &mut Vec<Move>) {
|
|
|
- move_list.sort_by_cached_key(mvv_lva_score)
|
|
|
+pub fn sort_moves_least_valuable_attacker(_game: &mut Board, move_list: &mut Vec<Move>, last_move: Option<Move>) {
|
|
|
+ if let Some(lm) = last_move {
|
|
|
+ move_list.sort_by_cached_key(|m| {
|
|
|
+ mvv_lva_score(m) + if m.to() == lm.to() { -1000 } else { 0 }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ move_list.sort_by_cached_key(mvv_lva_score)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
pub fn sort_moves_no_hash(game: &mut Board, move_list: &mut Vec<Move>) {
|
|
@@ -887,7 +1038,7 @@ pub fn count_sliding_move_bitboard(friends: Bitboard, others: Bitboard, pieces:
|
|
|
let mask = if captures_only { others } else { !(0 as Bitboard) };
|
|
|
let mut sum = 0;
|
|
|
for piece in pieces_iter {
|
|
|
- let destinations = generate_sliding_destinations(friends, others, piece, straight, diagonal);
|
|
|
+ let destinations = generate_sliding_destinations(friends, others, square(piece), straight, diagonal);
|
|
|
|
|
|
sum += (destinations & mask).count_ones();
|
|
|
}
|
|
@@ -901,7 +1052,7 @@ fn generate_sliding_moves(game: &Board, friends: Bitboard, others: Bitboard, pie
|
|
|
let mask = if captures_only { others } else { !(0 as Bitboard) };
|
|
|
|
|
|
for piece in pieces_iter {
|
|
|
- let destinations = generate_sliding_destinations(friends, others, piece, straight, diagonal);
|
|
|
+ let destinations = generate_sliding_destinations(friends, others, square(piece), straight, diagonal);
|
|
|
let dest_iter = BitboardIterator(destinations & mask);
|
|
|
for dest in dest_iter {
|
|
|
let smove = SimpleMove{ from: square(piece), to: square(dest) };
|
|
@@ -916,17 +1067,17 @@ fn generate_sliding_moves(game: &Board, friends: Bitboard, others: Bitboard, pie
|
|
|
}
|
|
|
|
|
|
pub fn generate_sliding_destinations(friends: Bitboard, others: Bitboard,
|
|
|
- piece: Bitboard, straight: bool,
|
|
|
+ piece: Square, straight: bool,
|
|
|
diagonal: bool) -> Bitboard {
|
|
|
let occupied = friends | others;
|
|
|
|
|
|
|
|
|
let mut destinations: Bitboard = 0;
|
|
|
if diagonal {
|
|
|
- destinations |= crate::magic::magic_bishop(square(piece), friends | others) & !friends;
|
|
|
+ destinations |= crate::magic::magic_bishop(piece, friends | others) & !friends;
|
|
|
}
|
|
|
if straight {
|
|
|
- destinations |= crate::magic::magic_rook(square(piece), friends | others) & !friends;
|
|
|
+ destinations |= crate::magic::magic_rook(piece, friends | others) & !friends;
|
|
|
}
|
|
|
|
|
|
return destinations & !friends;
|
|
@@ -1024,8 +1175,8 @@ pub fn is_check(game: &Board, side: Side) -> bool {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- let diagonal = generate_sliding_destinations(friends, others, king, false, true);
|
|
|
- let straight = generate_sliding_destinations(friends, others, king, true, false);
|
|
|
+ let diagonal = generate_sliding_destinations(friends, others, king_square, false, true);
|
|
|
+ let straight = generate_sliding_destinations(friends, others, king_square, true, false);
|
|
|
|
|
|
if diagonal & game.get_piece(BISHOP, !side) != 0 {
|
|
|
return true;
|
|
@@ -1080,8 +1231,8 @@ pub fn is_attacked(game: &Board, sq: Square, side: Side) -> bool {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- let diagonal = generate_sliding_destinations(others, friends, sq_bb, false, true);
|
|
|
- let straight = generate_sliding_destinations(others, friends, sq_bb, true, false);
|
|
|
+ let diagonal = generate_sliding_destinations(others, friends, sq, false, true);
|
|
|
+ let straight = generate_sliding_destinations(others, friends, sq, true, false);
|
|
|
|
|
|
if diagonal & game.get_piece(BISHOP, side) != 0 {
|
|
|
return true;
|