Преглед изворни кода

add SEE and Check extensions

Nicolas Winkler пре 2 година
родитељ
комит
b0f2884f21
4 измењених фајлова са 233 додато и 47 уклоњено
  1. 2 2
      src/evaluate.rs
  2. 9 0
      src/main.rs
  3. 174 23
      src/movegen.rs
  4. 48 22
      src/search.rs

+ 2 - 2
src/evaluate.rs

@@ -179,8 +179,8 @@ fn king_safety(game: &Board, side: Side) -> PosValue {
     let enemies = game.get_all_side(!side);
 
     if king != 0 {
-        let diag_rays = generate_sliding_destinations(king, 0, king, false, true);
-        let straight_rays = generate_sliding_destinations(0, 0, king, true, false); 
+        let diag_rays = generate_sliding_destinations(king, 0, square(king), false, true);
+        let straight_rays = generate_sliding_destinations(0, 0, square(king), true, false); 
 
         let bishops_in_rays = diag_rays & game.bishops(!side);
         let rooks_in_rays = straight_rays & game.rooks(!side);

+ 9 - 0
src/main.rs

@@ -15,10 +15,19 @@ extern crate simplelog;
 extern crate rand;
 extern crate static_assertions;
 
+use board::Board;
 use engine::Engine;
 
+use crate::movegen::{PieceCaptureType, PAWN, KNIGHT, SimpleMove};
+
 
 fn main() {
+
+    /*let board = Board::from_fen_str("1k1r3q/1ppn3p/p4b2/4p3/8/P2N2P1/1PP1R1BP/2K1Q3 w - - 0 0").unwrap();
+    //let see = movegen::see_score::<false>(board.clone(), 35, false);
+    let see = movegen::calculate_see(board.clone(), movegen::Move::Default { mov: SimpleMove{ from: 20, to: 35 }, pc: PieceCaptureType::new(KNIGHT, Some(PAWN)) }, false);
+    println!("see: {}", see);*/
+
     let mut engine = Engine::new();
     engine.run();
 }

+ 174 - 23
src/movegen.rs

@@ -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;

+ 48 - 22
src/search.rs

@@ -82,8 +82,9 @@ impl SearchControl {
     }
 
     pub fn set_depth(&mut self, depth: i32) {
+        const MAX_EXTEND: i32 = 3;
         self.initial_depth = depth;
-        self.killer_moves = vec![[Move::nullmove(); 2]; depth as usize];
+        self.killer_moves = vec![[Move::nullmove(); 2]; (depth + MAX_EXTEND) as usize];
     }
 
     fn insert_killer(&mut self, ply_depth: usize, killer: Move) {
@@ -236,10 +237,6 @@ impl SearchControl {
                         }
                     }
                     else {
-                        if val.abs() > 10000 {
-                            let neg = is_mate_in_p1(-val);
-                            println!("neg of: {:?}", neg);
-                        }
                         let infostring = format!("info depth {} score cp {} time {} nodes {} nps {} hashfull {} pv{}", depth, val, elapsed.as_millis(), nodes, nps, hashfull, pv);
                         println!("{}", infostring);
                         info!("{}", infostring);
@@ -359,6 +356,7 @@ pub fn search(mut board: Board, sc: &mut SearchControl, hashs: Arc<RwLock<Cache>
     }
 }
 
+
 pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut alpha: PosValue, beta: PosValue, mut depth: i32) -> PosValue {
 
     let last_move = sc.last_move;
@@ -385,8 +383,16 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
         }
     }
 
+    let check = is_check(game, game.turn);
+
     if depth == 0 {
-        return quiescence_search(game, sc, hash, alpha, beta, 9);
+        // check extend
+        if check {
+            depth = 1;
+        }
+        else {
+            return quiescence_search(game, sc, hash, alpha, beta, 9);
+        }
     }
 
     sc.nodes += 1;
@@ -416,7 +422,7 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
 
     //info!("nega moves: {:?}", moves.iter().map(|mv| mv.to_string()).collect::<Vec<String>>());
 
-    let check = is_check(game, game.turn);
+
     if moves.is_empty() {
         if check {
             // mate
@@ -429,9 +435,9 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
     }
 
     // Nullmove
-    if sc.reductions_allowed && !check && depth >= 4 && game.get_all_side(game.turn).count_ones() > 5 {
+    if sc.reductions_allowed && !check && depth >= 4 && game.get_all_side(game.turn).count_ones() > 7 {
 
-        let reduce = if depth > 5 { if depth > 7 { 4 } else { 3 }} else { 2 };
+        let reduce = if depth > 5 { if depth > 7 { 5 } else { 4 }} else { 3 };
 
         let nmov = Move::nullmove();
         let undo = game.apply(nmov);
@@ -545,25 +551,41 @@ fn quiescence_search(board: &mut Board, sc: &mut SearchControl, hash: &mut Cache
         }
     }
 
+    let last_move = sc.last_move;
+
+    if board.get_piece(KING, board.turn) == 0 { return checkmated(); }
+
+    let check = is_check(board, board.turn);
+
     let val = evaluate(board);
-    if val >= beta {
-        return beta;
-    }
-    if val > alpha {
-        alpha = val;
+
+    if !check {
+        if val >= beta {
+            return beta;
+        }
+        if val > alpha {
+            alpha = val;
+        }
     }
 
     if depth <= 0 {
         return alpha;
     }
 
-    if board.get_piece(KING, board.turn) == 0 { return checkmated(); }
-
     //let mut moves = generate_legal_sorted_moves(game, hash, &[], None, true, game.turn);
-    let mut moves = generate_legal_moves(board, board.turn, true);
+    let mut moves = generate_legal_moves(board, board.turn, !check);
 
     //sort_moves_no_hash(game, &mut moves);
-    sort_moves_least_valuable_attacker(board, &mut moves);
+    //sort_moves_least_valuable_attacker(board, &mut moves, last_move);
+
+    let mut val_movs: Vec<_> = moves.iter()
+        .map(|m| (*m, calculate_see(board.clone(), *m, board.turn)))
+        .filter(|(_m, v)| *v > 0)
+        .collect();
+    val_movs.sort_unstable_by_key(|(_m, v)| -*v);
+    moves = val_movs.iter().map(|(m, _v)| *m).collect();
+    //moves.sort_by_cached_key(|m| -calculate_see(board.clone(), *m, board.turn));
+
 
     let apply_delta_pruning = game_lateness(board) < 50;
 
@@ -572,7 +594,7 @@ fn quiescence_search(board: &mut Board, sc: &mut SearchControl, hash: &mut Cache
         if apply_delta_pruning {
             const PIECE_VALUES: [i32; 6] = [
                 100, // Pawn
-                220, // Knight
+                300, // Knight
                 320, // Bishop
                 400, // Rook
                 800, // Queen
@@ -588,7 +610,11 @@ fn quiescence_search(board: &mut Board, sc: &mut SearchControl, hash: &mut Cache
         }
 
         let undo = board.apply(mov);
-        let val = -quiescence_search(board, sc, hash, decrease_mate_in(-beta), decrease_mate_in(-alpha), depth - 1);
+        sc.last_move = Some(mov);
+
+        let val = increase_mate_in(
+            -quiescence_search(board, sc, hash, decrease_mate_in(-beta), decrease_mate_in(-alpha), depth - 1)
+        );
         board.undo_move(undo);
 
         if sc.stopping {
@@ -598,8 +624,8 @@ fn quiescence_search(board: &mut Board, sc: &mut SearchControl, hash: &mut Cache
         if val >= beta {
             return beta;
         }
-        if increase_mate_in(val) > alpha {
-            alpha = increase_mate_in(val);
+        if val > alpha {
+            alpha = val;
         }
     }
     return alpha;