Browse Source

added null moves

Nicolas Winkler 3 years ago
parent
commit
0d99eab355
8 changed files with 592 additions and 201 deletions
  1. 134 89
      src/engine.rs
  2. 92 17
      src/evaluate.rs
  3. 106 27
      src/game.rs
  4. 43 3
      src/hash.rs
  5. 64 16
      src/interface.rs
  6. 10 0
      src/main.rs
  7. 70 2
      src/movegen.rs
  8. 73 47
      src/search.rs

+ 134 - 89
src/engine.rs

@@ -7,15 +7,16 @@ use log::{info};
 use std::time::{Duration, Instant};
 use std::collections::{VecDeque};
 use zobrist::ZobristTable;
-use hash::Cache;
+use hash::{Cache, RepetitionTable};
 
 use std::sync::Arc;
 use std::sync::mpsc::{Receiver, Sender};
 
 pub enum EngineMsg {
-    SetBoard(Game),
+    SetBoard{ pos: Game, moves: Vec<String> },
     SetPiece(Bitboard),
     Search(SearchInfo),
+    Print,
     Ping,
     Stop,
     NewGame,
@@ -23,18 +24,15 @@ pub enum EngineMsg {
     GetInfo,
 }
 
-pub enum SearchInfo {
-    Depth(isize),
-    Movetime(isize),
-    Time {
-        wtime: isize,
-        btime: isize,
-        winc: isize,
-        binc: isize,
-        movestogo: isize
-    },
-    Infinite,
-
+pub struct SearchInfo {
+    pub depth: Option<i32>,
+    pub movetime: Option<i32>,
+    pub wtime: Option<isize>,
+    pub btime: Option<isize>,
+    pub winc: Option<isize>,
+    pub binc: Option<isize>,
+    pub movestogo: Option<isize>,
+    pub infinite: bool
 }
 
 pub enum InterfaceMsg {
@@ -42,6 +40,7 @@ pub enum InterfaceMsg {
 
 pub struct Engine {
     game: Game,
+    move_history: RepetitionTable,
     messages: VecDeque<EngineMsg>,
     r: Receiver<EngineMsg>,
     s: Sender<InterfaceMsg>,
@@ -49,10 +48,26 @@ pub struct Engine {
     zobrist_table: Arc<ZobristTable>
 }
 
+impl SearchInfo {
+    pub fn new() -> Self {
+        SearchInfo {
+            depth: None,
+            movetime: None,
+            wtime: None,
+            btime: None,
+            winc: None,
+            binc: None,
+            movestogo: None,
+            infinite: false
+        }
+    }
+}
+
 impl Engine {
     pub fn new(r: Receiver<EngineMsg>, s: Sender<InterfaceMsg>) -> Self {
         let mut eng = Engine {
             game: Game::default(),
+            move_history: RepetitionTable::new(),
             messages: VecDeque::new(),
             r, s,
             hash: Cache::new(),
@@ -65,11 +80,29 @@ impl Engine {
     pub fn run(&mut self) {
         while let Some(msg) = self.dequeue_message() {
             match msg {
-                EngineMsg::SetBoard(g) => {
-                    self.game = g;
+                EngineMsg::SetBoard{ pos, moves } => {
+                    self.game = pos;
+                    self.game.zobrist = Some((self.zobrist_table.clone(), self.game.calculate_zobrist(&self.zobrist_table)));
+                    self.move_history.clear();
+                    self.move_history.increment(self.game.calculate_zobrist(&self.zobrist_table));
+                    for mov in moves {
+                        let m = self.game.parse_move(&mov);
+                        if let Ok(mm) = m {
+                            self.game.apply(mm);
+                            self.move_history.increment(self.game.calculate_zobrist(&self.zobrist_table));
+                        }
+                        else {
+                            println!("{}", self.game.beautiful_print());
+                            println!("error parsing move {}", mov);
+                            break;
+                        }
+                    }
                 },
                 EngineMsg::Search(ref info) => {
                     self.start_search(info);
+                },
+                EngineMsg::Print => {
+                    println!("{}", self.game.beautiful_print());
                 }
                 _ => {}
             }
@@ -88,94 +121,106 @@ impl Engine {
         }
     }
 
+    fn try_dequeue_message(&mut self) -> Option<EngineMsg> {
+        if self.messages.is_empty() {
+            self.r.try_recv().ok()
+        }
+        else {
+            self.messages.pop_front()
+        }
+    }
+
     fn start_search(&mut self, si: &SearchInfo) {
         if let None = self.game.zobrist {
             self.game.zobrist = Some((self.zobrist_table.clone(), self.game.calculate_zobrist(&self.zobrist_table)));
         }
-        match si {
-            SearchInfo::Depth(depth) => {
-                let receiver = &mut self.r;
-                let messages = &mut self.messages;
-                let mut check_fn = || -> bool {
-                    if let Ok(msg) = receiver.try_recv() {
-                        if let EngineMsg::Stop = msg {
-                            return true;
-                        }
-                        else {
-                            messages.push_back(msg);
-                        }
+        let side = self.game.turn;
+        let receiver = &mut self.r;
+        let messages = &mut self.messages;
+        let before = Instant::now();
+        let mut depth = 1;
+        let mut check_fn =
+            || -> bool {
+                if !messages.is_empty() {
+                    if let EngineMsg::Stop = messages[0] {
+                        return true;
                     }
-                    return false;
-                };
-                let mut sc = SearchControl{ nodes: 0, check: &mut check_fn };
-                let before = Instant::now();
-                let search_result = search(&mut self.game, &mut sc, &mut self.hash, *depth as i32);
-
-                if let SearchResult::Finished(best_move, best_val) = search_result {
-                    let time = before.elapsed();
-                    let nps = (sc.nodes as f64 / time.as_millis() as f64 * 1000.0) as i64;
-                    info!("bestmove {}", best_move.to_string());
-                    info!("searched {} nodes in {} ms ({} nodes/s)", sc.nodes, time.as_millis(), (sc.nodes as f64 / time.as_millis() as f64 * 1000.0) as i64);
-    
-                    println!("info nps {}", nps.to_string());
-                    println!("bestmove {}", best_move.to_string());
                 }
-            },
-            SearchInfo::Movetime(millis) => {
-                let before = Instant::now();
-                let mut depth = 1;
-                let receiver = &mut self.r;
-                let messages = &mut self.messages;
-                let mut check_fn = || -> bool {
-                    if let Ok(msg) = receiver.try_recv() {
-                        if let EngineMsg::Stop = msg {
-                            return true;
-                        }
-                        else {
-                            messages.push_back(msg);
-                        }
-                    }
-                    if before.elapsed() > Duration::from_millis(*millis as _) {
+                if let Ok(msg) = receiver.try_recv() {
+                    if let EngineMsg::Stop = msg {
+                        messages.push_back(msg);
                         return true;
                     }
-                    return false;
-                };
+                    else {
+                        messages.push_back(msg);
+                    }
+                }
 
-                let mut sc = SearchControl{ nodes: 0, check: &mut check_fn };
-                let mut best_move = Move::default();
-                let mut best_val: PosValue = i32::max_value() as _;
-                loop {
-                    let search_result = search(&mut self.game, &mut sc, &mut self.hash, depth as i32);
-                    if let SearchResult::Finished(bm, bv) = search_result {
-                        info!("depth: {} bm: {}, bv: {}", depth, bm.to_string(), bv);
-                        best_move = bm;
-                        best_val = bv;
-    
-                        let elapsed = before.elapsed();
-                        let nodes = sc.nodes;
-                        let nps = (nodes as f64 / elapsed.as_nanos() as f64 * 1000000000.0) as i64;
-                        let cp = best_val as i64;
-                        if let Some(turns) = crate::evaluate::is_mate_in_p1(bv) {
-                            println!("info depth {} score mate {} time {} nodes {} nps {} pv {}", depth, turns, elapsed.as_millis(), nodes, nps, best_move.to_string());
-                            info!("info depth {} score mate {} time {} nodes {} nps {} pv {}", depth, turns, elapsed.as_millis(), nodes, nps, best_move.to_string());
-                            break;
-                        }
-                        else {
-                            println!("info depth {} score cp {} time {} nodes {} nps {} pv {}", depth, cp, elapsed.as_millis(), nodes, nps, best_move.to_string());
-                            info!("info depth {} score cp {} time {} nodes {} nps {} pv {}", depth, cp, elapsed.as_millis(), nodes, nps, best_move.to_string());
+                if let Some(millis) = si.movetime {
+                    if before.elapsed() > Duration::from_millis(millis as _) {
+                        return true;
+                    }
+                }
+                else if side == WHITE {
+                    if let Some(wtime) = si.wtime {
+                        if before.elapsed() > Duration::from_millis((wtime / 35) as _) {
+                            return true;
                         }
-                        depth += 1;
                     }
-                    else {
-                        break;
+                }
+                else if side == BLACK {
+                    if let Some(btime) = si.btime {
+                        if before.elapsed() > Duration::from_millis((btime / 35) as _) {
+                            return true;
+                        }
                     }
                 }
-                info!("bestmove {}", best_move.to_string());
+                return false;
+            };
 
-                println!("bestmove {}", best_move.to_string());
-            },
-            _ => {}
+        let mut sc = SearchControl{ nodes: 0, check: &mut check_fn, move_history: &mut self.move_history, initial_depth: 0 };
+        let mut best_move = Move::default();
+        let mut best_val: PosValue;
+        loop {
+
+            sc.initial_depth = depth;
+            let search_result = search(&mut self.game, &mut sc, &mut self.hash, depth as i32);
+
+            if let SearchResult::Finished(bm, bv) = search_result {
+                info!("depth: {} bm: {}, bv: {}", depth, bm.to_string(), bv);
+                best_move = bm;
+                best_val = bv;
+
+                let elapsed = before.elapsed();
+                let nodes = sc.nodes;
+                let nps = (nodes as f64 / elapsed.as_nanos() as f64 * 1000000000.0) as i64;
+                let cp = best_val as i64;
+                if let Some(plies) = crate::evaluate::is_mate_in_p1(bv) {
+                    let turns = plies / 2;
+                    println!("info depth {} score mate {} time {} nodes {} nps {} pv {}", depth, turns, elapsed.as_millis(), nodes, nps, best_move.to_string());
+                    info!("info depth {} score mate {} time {} nodes {} nps {} pv {}", depth, turns, elapsed.as_millis(), nodes, nps, best_move.to_string());
+                    break;
+                }
+                else {
+                    println!("info depth {} score cp {} time {} nodes {} nps {} pv {}", depth, cp, elapsed.as_millis(), nodes, nps, best_move.to_string());
+                    info!("info depth {} score cp {} time {} nodes {} nps {} pv {}", depth, cp, elapsed.as_millis(), nodes, nps, best_move.to_string());
+                }
+                depth += 1;
+            }
+            else {
+                break;
+            }
+
+
+            if let Some(d) = si.depth {
+                if depth > d {
+                    break;
+                }
+            }
         }
+
+        info!("bestmove {}", best_move.to_string());
+        println!("bestmove {}", best_move.to_string());
     }
 }
 

+ 92 - 17
src/evaluate.rs

@@ -9,7 +9,8 @@ pub type PosValue = i32;
 pub const MIN_VALUE: PosValue = i32::MIN + 1;
 pub const MAX_VALUE: PosValue = i32::MAX;
 
-const MATE_SHIFT: usize = 24;
+const MATE_VALUE: PosValue = 1i32 << 26;
+const MATE_CUTOFF: PosValue = 1i32 << 24;
 
 pub fn mate() -> PosValue {
     mate_in_p1(1)
@@ -19,12 +20,12 @@ pub fn mate() -> PosValue {
  * constructs a PosValue that indicates that from a given position mate
  * can be achieved in turns-1 moves (not halfmoves)
  */
-pub fn mate_in_p1(turns: i8) -> PosValue {
-    if turns >= 0 || turns == -128 {
-        (((127 - turns) as PosValue) << MATE_SHIFT) as i32
+pub fn mate_in_p1(turns: i32) -> PosValue {
+    if turns < 0 {
+        return -mate_in_p1(-turns);
     }
     else {
-        -mate_in_p1(-turns)
+        return MATE_VALUE - turns;
     }
 }
 
@@ -35,23 +36,38 @@ pub fn mate_in_p1(turns: i8) -> PosValue {
  * turns is negative if the moving side is getting mated
  * in turns moves
  */
-pub fn is_mate_in_p1(pos_val: PosValue) -> Option<i8> {
-    let highest_byte = (pos_val >> MATE_SHIFT) as i8;
-
-    if highest_byte != 0 && highest_byte != -1 {
-        if pos_val < 0 {
-            Some(-127 - highest_byte)
-        }
-        else {
-            Some(127 - highest_byte)
-        }
-
+pub fn is_mate_in_p1(pos_val: PosValue) -> Option<i32> {
+    if pos_val > MATE_CUTOFF && pos_val <= MATE_VALUE {
+        Some(-pos_val + MATE_VALUE)
+    }
+    else if pos_val < -MATE_CUTOFF {
+        Some(-pos_val - MATE_VALUE)
     }
     else {
         None
     }
 }
 
+pub fn increase_mate_in(mut val: PosValue) -> PosValue {
+    if let Some(turns) = is_mate_in_p1(val) {
+        if turns < 0 {
+            val = mate_in_p1(turns - 1);
+        }
+        if turns > 0 {
+            val = mate_in_p1(turns + 1);
+        }
+        if turns == 0 {
+            if val < 0 {
+                val = mate_in_p1(-1);
+            }
+            else {
+                val = mate_in_p1(1);
+            }
+        }
+    }
+    val
+}
+
 
 fn side_value(game: &Game, side: Side) -> u32 {
     let adv_pawn_mask = match side { WHITE => ROW_7, BLACK => ROW_2 };
@@ -114,5 +130,64 @@ pub fn evaluate(game: &Game) -> PosValue {
     let kv = knight_value(game, game.turn) - knight_value(game, !game.turn);
     let king_safety = king_safety(game, game.turn) - king_safety(game, !game.turn);
     let king_there = king_there(game, game.turn) - king_there(game, !game.turn);
-    return sv + kv + king_safety + mat_val + king_there;
+
+    let mge = sv + kv + king_safety + king_there;
+    let lge = late_game_eval(game);
+
+    let lateness = game_lateness(game);
+
+    let ge = (mge * (100 - lateness) + lge * lateness) / 100;
+
+
+    return mat_val + ge;
+}
+
+pub fn game_lateness(game: &Game) -> i32 {
+    let all_pieces = game.get_all_side(WHITE) | game.get_all_side(BLACK);
+    let n_pieces = all_pieces.count_ones();
+    let captured_pieces = 32 - n_pieces;
+
+    if captured_pieces > 15 {
+        let lateness = (captured_pieces - 15) * 100 / 10;
+        return std::cmp::min(lateness as _, 100);
+    }
+    return 0;
+}
+
+pub fn late_game_eval(game: &Game) -> PosValue {
+    let pp = pawn_push_value(game, game.turn) - pawn_push_value(game, !game.turn);
+    let bp = blocked_pawns(game, game.turn) - blocked_pawns(game, !game.turn);
+
+    pp + bp
+}
+
+pub fn pawn_push_value(game: &Game, side: Side) -> PosValue {
+    let val = if side == WHITE {
+        (game.get_piece(PAWN, side) & ROW_3).count_ones() * 10
+        + (game.get_piece(PAWN, side) & ROW_4).count_ones() * 20
+        + (game.get_piece(PAWN, side) & ROW_5).count_ones() * 30
+        + (game.get_piece(PAWN, side) & ROW_6).count_ones() * 40
+        + (game.get_piece(PAWN, side) & ROW_7).count_ones() * 50
+    }
+    else {
+        (game.get_piece(PAWN, side) & ROW_6).count_ones() * 10
+        + (game.get_piece(PAWN, side) & ROW_5).count_ones() * 20
+        + (game.get_piece(PAWN, side) & ROW_4).count_ones() * 30
+        + (game.get_piece(PAWN, side) & ROW_3).count_ones() * 40
+        + (game.get_piece(PAWN, side) & ROW_2).count_ones() * 50
+    };
+    val as PosValue
+}
+
+pub fn blocked_pawns(game: &Game, side: Side) -> PosValue {
+    let pawns = game.get_piece(PAWN, side);
+    let pushed_pawns = if side == WHITE {
+        north_one(pawns)
+    } else {
+        south_one(pawns)
+    };
+
+    let blocked_pawns = pushed_pawns & game.get_all_side(!side);
+
+    blocked_pawns.count_ones() as _
 }

+ 106 - 27
src/game.rs

@@ -4,6 +4,7 @@ use log::info;
 use std::sync::Arc;
 use zobrist::{ZobristTable};
 use zobrist;
+use hash::RepetitionTable;
 
 #[derive(Clone)]
 pub struct Game
@@ -59,6 +60,7 @@ impl Default for Game {
     }
 }
 
+/*
 impl std::hash::Hash for Game {
     fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
         if let Some((ref _zt, ref zb)) = &self.zobrist {
@@ -78,7 +80,7 @@ impl PartialEq for Game {
 
 impl Eq for Game {
 }
-
+*/
 
 impl Game {
     pub fn empty() -> Game {
@@ -157,10 +159,10 @@ impl Game {
 
         // parse en passant
         game.en_passant = Move::parse_square(en_passant).map(|sq| indices_from_square(sq).0);
-        info!("en passant on file {:?}", game.en_passant);
+        //info!("en passant on file {:?}", game.en_passant);
 
         game.halfmoves_since_last_event = halfmoves_since_last_event.parse::<i8>().unwrap_or(0);
-        //game.turn_number = turn_number.parse::<i32>().unwrap_or(0);
+        game.turn_number = turn_number.parse::<i32>().unwrap_or(0);
         
         Some(game)
     }
@@ -179,11 +181,31 @@ impl Game {
             let origin = Move::parse_square(&mov[0..2]);
             let target = Move::parse_square(&mov[2..4]);
 
+            let promote_to = if mov.len() == 5 {
+                Some(match mov.chars().nth(4) {
+                    Some('Q') | Some('q') => QUEEN,
+                    Some('N') | Some('n') => KNIGHT,
+                    Some('B') | Some('b') => BISHOP,
+                    Some('R') | Some('r') => ROOK,
+                    _ => panic!("invalid move: {}", mov)
+                })
+            }
+            else {
+                None
+            };
 
             if let (Some(from), Some(to)) = (origin, target) {
                 let (piece_type, side) = self.get_square(from);
                 //println!("from: {}", from);
 
+                let (cap, cs) = self.get_square(to);
+                let captured = if cap != NO_PIECE { Some(cap) } else { None };
+
+                if cap != NO_PIECE && cs == side {
+                    // cannot capture own piece
+                    return Err(());
+                }
+
                 if piece_type == NO_PIECE {
                     return Err(());
                 }
@@ -191,20 +213,33 @@ impl Game {
                 if side != self.turn { // wrong side to move
                     return Err(());
                 }
-                
-                if mov.len() == 5 {
-                    Err(())
+
+                if let Some(promote_to) = promote_to {
+                    if piece_type != PAWN {
+                        return Err(());
+                    }
+                    Ok(Move::Promotion{ mov: SimpleMove{ from, to }, promote_to, captured })
                 }
                 else {
-                    //println!("pt: {}", piece_type);
-                    let (cap_piece, cap_side) = self.get_square(to);
-
-                    if cap_piece == NO_PIECE {
-                        Ok(Move::Default{ mov: SimpleMove{ from, to }, piece_type, captured: None })
+                    if piece_type == KING && (from as i32 - to as i32).abs() == 2 {
+                        let left = from < to;
+                        //println!("OMG castling!");
+                        return Ok(Move::Castling{ side, left });
                     }
-                    else {
-                        Ok(Move::Default{ mov: SimpleMove{ from, to }, piece_type, captured: Some(cap_piece) })
+
+                    // pawn capture
+                    let (from_file, from_row) = indices_from_square(from);
+                    let (to_file, to_row) = indices_from_square(to);
+                    if piece_type == PAWN && from_file != to_file {
+                        let others = self.get_all_side(!side);
+                        if others & from_square(to) == 0 {
+                            let beaten = square_from_indices(to_file, from_row);
+                            return Ok(Move::EnPassant{ mov: SimpleMove{ from, to }, beaten });
+                        }
                     }
+
+                    //println!("pt: {}", piece_type);
+                    Ok(Move::Default{ mov: SimpleMove{ from, to }, piece_type, captured })
                 }
             }
             else {
@@ -538,6 +573,19 @@ impl Game {
 
     pub fn apply(&mut self, mov: Move) -> MoveUndo {
 
+        /*if !self.is_zobrist_correct() {
+            println!("{}", self.beautiful_print());
+            println!("incorrect zobrist before apply {} {:?}", mov.to_string(), mov);
+            info!("incorrect zobrist before apply");
+            panic!("zobrist");
+            let val = if let Some((ref zt, _zv)) = self.zobrist {
+                self.calculate_zobrist(zt)
+            } else { 0 };
+            if let Some((ref _zt, ref mut zv)) = self.zobrist {
+                *zv = val;
+            }
+        }*/
+
         // save irrecoverable values
         let castling_rights_before = self.castling_rights.clone();
         let halfmoves_since_last_event_before = self.halfmoves_since_last_event.clone();
@@ -584,6 +632,23 @@ impl Game {
                     *self.get_piece_mut(other_piece, other_side) &= !from_square(mov.to);
                 }
 
+
+                if pt == PAWN {
+                    let row1 = indices_from_square(mov.from).1;
+                    let row2 = indices_from_square(mov.to).1;
+                    // check for double push
+                    if i32::abs(row1 as i32 - row2 as i32) >= 2 {
+                        let opponent_pawns = self.get_piece(PAWN, !side);
+                        let target_bb = from_square(mov.to);
+                        if (west_one(target_bb) | east_one(target_bb)) & opponent_pawns != 0 {
+                            self.en_passant = Some(indices_from_square(mov.to).0);
+                        }
+                    }
+                }
+                else {
+                    self.en_passant = None;
+                }
+
                 let moved_piece = mov.apply_to(self.get_piece(pt, side));
                 self.set_piece(pt, side, moved_piece);
                 if let Some((ref zt, ref mut zv)) = self.zobrist {
@@ -615,6 +680,8 @@ impl Game {
                     let hup = zt.piece_hash(ROOK, side, square(rook)) ^ zt.piece_hash(ROOK, side, square(new_rook));
                     *zv ^= hup ^ hupk;
                 }
+
+                self.en_passant = None;
             },
             Move::EnPassant { mov, beaten } => {
                 if let Some(_ep) = self.en_passant {
@@ -627,6 +694,8 @@ impl Game {
                         let hupo = zt.piece_hash(PAWN, !side, beaten);
                         *zv ^= hup ^ hupo;
                     }
+
+                    self.en_passant = None;
                 }
                 else {
                     panic!("internal en passant error");
@@ -661,7 +730,9 @@ impl Game {
                     let hup = zt.piece_hash(promote_to, side, mov.to);
                     *zv ^= hup;
                 }
+                self.en_passant = None;
             },
+            Move::Nullmove => {}
         }
 
         if self.turn == BLACK {
@@ -676,15 +747,18 @@ impl Game {
             *zv ^= hup ^ castling_hup ^ enpass_hup;
         }
 
-        if !self.is_zobrist_correct() {
-            println!("incorrect zobrist after apply");
+        /*if !self.is_zobrist_correct() {
+            println!("{}", self.beautiful_print());
+            println!("incorrect zobrist after apply {} {:?}", mov.to_string(), mov);
+            info!("incorrect zobrist after apply");
+            panic!("zobrist");
             let val = if let Some((ref zt, _zv)) = self.zobrist {
                 self.calculate_zobrist(zt)
             } else { 0 };
             if let Some((ref _zt, ref mut zv)) = self.zobrist {
                 *zv = val;
             }
-        }
+        }*/
 
         MoveUndo {
             castling_rights_before,
@@ -696,6 +770,13 @@ impl Game {
 
     pub fn undo_move(&mut self, umov: MoveUndo) {
 
+        /*if !self.is_zobrist_correct() {
+            println!("{}", self.beautiful_print());
+            println!("incorrect zobrist before undo {} {:?}", umov.mov.to_string(), umov.mov);
+            info!("incorrect zobrist before undo {} {:?}", umov.mov.to_string(), umov.mov);
+            panic!("zobrist");
+        }*/
+
         if let Some((ref zt, ref mut zv)) = self.zobrist {
             let crhup = zt.all_castling_rights_hash(self.castling_rights) ^ zt.all_castling_rights_hash(umov.castling_rights_before);
             let enpass_hup = self.en_passant.map(|f| zt.en_passant_hash(f)).unwrap_or(0) ^ umov.en_passant_before.map(|f| zt.en_passant_hash(f)).unwrap_or(0);
@@ -792,7 +873,8 @@ impl Game {
                         *zv ^= hup;
                     }
                 }
-            }
+            },
+            Move::Nullmove => {}
         }
 
         self.turn = side;
@@ -804,21 +886,18 @@ impl Game {
             *zv ^= zt.turn_hash();
         }
 
+        /*if !self.is_zobrist_correct() {
+            println!("{}", self.beautiful_print());
+            println!("incorrect zobrist after undo {} {:?}", umov.mov.to_string(), umov.mov);
+            info!("incorrect zobrist after undo {} {:?}", umov.mov.to_string(), umov.mov);
+            panic!("zobrist");
+        }*/
+
         /*let val = if let Some((ref zt, _zv)) = self.zobrist {
             self.calculate_zobrist(zt)
         } else { 0 };
         if let Some((ref _zt, ref mut zv)) = self.zobrist {
             *zv = val;
         }*/
-
-        if !self.is_zobrist_correct() {
-            println!("incorrect zobrist after undo {}!", umov.mov.to_string());
-            let val = if let Some((ref zt, _zv)) = self.zobrist {
-                self.calculate_zobrist(zt)
-            } else { 0 };
-            if let Some((ref _zt, ref mut zv)) = self.zobrist {
-                *zv = val;
-            }
-        }
     }
 }

+ 43 - 3
src/hash.rs

@@ -50,6 +50,11 @@ pub struct Cache {
     max_capacity: usize
 }
 
+#[derive(Clone)]
+pub struct RepetitionTable {
+    hashmap: HashMap<zobrist::Hash, i32>,
+}
+
 impl Cache {
     pub fn new() -> Self {
         Cache {
@@ -64,12 +69,47 @@ impl Cache {
     
     pub fn cache(&mut self, game_pos: &Game, ce: CacheEntry) {
         if self.hashmap.len() > self.max_capacity {
-            let first_key = self.hashmap.keys().next().unwrap().clone();
-            self.hashmap.remove(&first_key);
+            //let first_key = self.hashmap.keys().next().unwrap().clone();
+            //self.hashmap.remove(&first_key);
+            self.hashmap.clear();
         }
         self.hashmap.insert(game_pos.zobrist.clone().unwrap().1, ce);
         if self.hashmap.len() % (1024 * 32) == 0 {
             info!("hash contains {} items", self.hashmap.len());
         }
     }
-}
+}
+
+
+impl RepetitionTable {
+    pub fn new() -> Self {
+        RepetitionTable {
+            hashmap: HashMap::with_capacity(256)
+        }
+    }
+
+    pub fn clear(&mut self) {
+        self.hashmap.clear();
+    }
+
+    pub fn increment(&mut self, hash: zobrist::Hash) -> i32 {
+        if let Some(entry) = self.hashmap.get_mut(&hash) {
+            *entry += 1;
+            *entry
+        }
+        else {
+            self.hashmap.insert(hash, 1);
+            1
+        }
+    }
+
+    pub fn decrement(&mut self, hash: zobrist::Hash) {
+        if let Some(entry) = self.hashmap.get_mut(&hash) {
+            *entry -= 1;
+            if *entry == 0 {
+                self.hashmap.remove(&hash);
+            }
+        }
+    }
+}
+

+ 64 - 16
src/interface.rs

@@ -1,6 +1,7 @@
 
 use search::apply_move;
 use std::io::{self, BufRead};
+use std::collections::BTreeMap;
 use std::sync::mpsc::{Receiver, Sender};
 
 use std::process::exit;
@@ -33,6 +34,7 @@ fn run_command(mut cmd: Vec<&str>, r: &Receiver<InterfaceMsg>, s: &Sender<Engine
             "uci" => cmd_uci(cmd),
             "isready" => cmd_isready(cmd),
             "position" => cmd_position(cmd, r, s),
+            "print" => cmd_print(cmd, r, s),
             "go" => cmd_go(cmd, r, s),
             "stop" => cmd_stop(cmd, r, s),
             "ucinewgame" => cmd_newgame(cmd, r, s),
@@ -57,33 +59,79 @@ fn cmd_position(mut args: Vec<&str>, r: &Receiver<InterfaceMsg>, s: &Sender<Engi
     args.drain(0..1);
 
     let mut game = Game::default();
+
     if position == "startpos" {
     }
     else if position == "fen" {
         let fen_parts: Vec<&str> = Vec::from(&args[0..6]).into_iter().collect::<Vec<&str>>();
-        let moves = &args[6..];
-
         game = Game::from_fen(fen_parts.as_slice()).unwrap();
-
-        for mov in moves {
-            let m = game.parse_move(mov);
-            if let Ok(mm) = m {
-                game = apply_move(&game, mm);
-            }
-            else {
-                println!("error");
+        args.drain(0..6);
+    }
+    let moves = 
+        if args.len() > 0 {
+            if args[0] != "moves" {
+                info!("unexpected {}", args[6]);
+                println!("unexpected {}", args[6]);
             }
+            args.drain(0..1);
+
+
+            args.into_iter().map(|m| String::from(m)).collect::<Vec<String>>()
         }
-    }
-    s.send(EngineMsg::SetBoard(game)).unwrap();
+        else {
+            Vec::new()
+        };
+
+    s.send(EngineMsg::SetBoard{ pos: game, moves }).unwrap();
+}
+
+fn cmd_print(mut _args: Vec<&str>, _r: &Receiver<InterfaceMsg>, s: &Sender<EngineMsg>) {
+    s.send(EngineMsg::Print).unwrap();
 }
 
 fn cmd_go(args: Vec<&str>, _r: &Receiver<InterfaceMsg>, s: &Sender<EngineMsg>) {
-    let mut movetime = 1000;
-    if args.len() > 1 && args[0] == "movetime" {
-        movetime = args[1].parse::<isize>().unwrap_or(1000);
+    let mut options: BTreeMap<String, String> = BTreeMap::new();
+    let mut opt_name: Option<&str> = None;
+    let mut si = SearchInfo::new();
+
+    for arg in args {
+        if let Some(on) = opt_name {
+            options.insert(on.to_owned(), arg.to_owned());
+            opt_name = None;
+        }
+        else if arg == "infinite" {
+            si.infinite = true;
+        }
+        else {
+            opt_name = Some(arg);
+        } 
+    }
+
+    si.movetime = options.get("movetime").map(|x| x.parse::<_>().unwrap_or(0));
+    si.depth = options.get("depth").map(|x| x.parse::<_>().unwrap_or(0));
+    si.wtime = options.get("wtime").map(|x| x.parse::<_>().unwrap_or(0));
+    si.btime = options.get("btime").map(|x| x.parse::<_>().unwrap_or(0));
+    si.winc = options.get("winc").map(|x| x.parse::<_>().unwrap_or(0));
+    si.binc = options.get("binc").map(|x| x.parse::<_>().unwrap_or(0));
+    si.movestogo = options.get("movestogo").map(|x| x.parse::<_>().unwrap_or(0));
+
+
+    s.send(EngineMsg::Search(si)).unwrap();
+
+    /*if args.len() > 1 && args[0] == "movetime" {
+        let movetime = args[1].parse::<isize>().unwrap_or(1000);
+        s.send(EngineMsg::Search(SearchInfo::Movetime(movetime))).unwrap();
+    }
+    else if args.len() > 1 && args[0] == "depth" {
+        let depth = args[1].parse::<isize>().unwrap_or(1);
+        s.send(EngineMsg::Search(SearchInfo::Depth(depth))).unwrap();
+    }
+    else if args.len() > 1 {
+    //wtime 923997 winc 5000 btime 918725 binc 5000
     }
-    s.send(EngineMsg::Search(SearchInfo::Movetime(movetime))).unwrap();
+    else {
+        s.send(EngineMsg::Search(SearchInfo::Infinite)).unwrap();
+    }*/
 }
 
 fn cmd_stop(_args: Vec<&str>, _r: &Receiver<InterfaceMsg>, s: &Sender<EngineMsg>) {

+ 10 - 0
src/main.rs

@@ -32,6 +32,16 @@ fn main() {
     
     //assert_eq!(std::mem::size_of::<crate::movegen::Move>(), 6);
 
+    /*let mit = mate_in_p1(4);
+    let mis = mate_in_p1(-7);
+    if mit < mis {
+        println!("ohno");
+    }
+    println!("mit: {}", mit);
+    if let Some(t) = is_mate_in_p1(-mit) {
+        println!("t: {}", t);
+    }*/
+
     //let logfile = File::create("/home/nicolas/debug.log").unwrap();
     //simplelog::WriteLogger::init(LevelFilter::Info, Config::default(), logfile).unwrap();
 

+ 70 - 2
src/movegen.rs

@@ -34,6 +34,7 @@ pub enum Move {
     Castling { side: Side, left: bool },
     EnPassant { mov: SimpleMove, beaten: Square },
     Promotion { mov: SimpleMove, promote_to: PieceType, captured: Option<PieceType> },
+    Nullmove
 }
 
 
@@ -105,6 +106,9 @@ impl Move {
                 else if *promote_to == BISHOP { "B" }
                 else { "" }
             },
+            Move::Nullmove => {
+                String::from("0000")
+            }
         }
     }
 }
@@ -212,6 +216,7 @@ pub fn sort_moves(game: &Game, hash: &mut Cache, move_list: &mut Vec<Move>) {
             Move::Castling { side: _, left: _ } => 0,
             Move::Promotion { mov: _, promote_to: _, captured: _ } => -15,
             Move::EnPassant { mov: _, beaten: _ } => -10,
+            Move::Nullmove => 0,
         }
     });
 }
@@ -560,24 +565,87 @@ fn generate_castling_moves(game: &Game, side: Side, move_list: &mut Vec<Move>) {
     }
 }
 
+pub fn is_check(game: &Game, side: Side) -> bool {
+    let king = game.get_piece(KING, side);
+    let king_square = square(king);
+    let friends = game.get_all_side(side);
+    let others = game.get_all_side(!side);
+
+    let knights = get_knight_targets(king_square);
+    if knights & game.get_piece(KNIGHT, !side) != 0 {
+        return true;
+    }
+
+    let diagonal = generate_sliding_destinations(friends, others, king, false, true);
+    let straight = generate_sliding_destinations(friends, others, king, true, false);
+
+    if diagonal & game.get_piece(BISHOP, !side) != 0 {
+        return true;
+    }
+    if diagonal & game.get_piece(QUEEN, !side) != 0 {
+        return true;
+    }
+    if straight & game.get_piece(ROOK, !side) != 0 {
+        return true;
+    }
+    if straight & game.get_piece(QUEEN, !side) != 0 {
+        return true;
+    }
+
+    if side == BLACK {
+        if ((southeast_one(king) | southwest_one(king)) & game.get_piece(PAWN, !side)) != 0 {
+            return true;
+        }
+    }
+    else {
+        if ((northeast_one(king) | northwest_one(king)) & game.get_piece(PAWN, !side)) != 0 {
+            return true;
+        }
+    }
+
+    let king_area = north_one(king)
+              | south_one(king)
+              | east_one(king)
+              | west_one(king)
+              | northeast_one(king)
+              | northwest_one(king)
+              | southwest_one(king)
+              | southeast_one(king);
+    if king_area & game.get_piece(KING, !side) != 0 {
+        return true;
+    }
+
+    return false;
+}
+
 /**
  * checks if side's king is in check
  */
-pub fn is_check(game: &Game, side: Side) -> bool {
-    let king_square = square(game.get_piece(KING, side));
+pub fn is_check_old(game: &Game, side: Side) -> bool {
+    let king = game.get_piece(KING, side);
+    let king_square = square(king);
     let possible_attacks = generate_possattacking_moves(game, !side);
     for mov in possible_attacks {
         if let Move::Default{ mov, piece_type: _, captured: _ } = mov {
             if mov.to == king_square {
+                if !is_check(game, side) {
+                    panic!("incorrect non-check {}", game.beautiful_print());
+                }
                 return true;
             }
         }
         else if let Move::Promotion{ mov, promote_to: _, captured: _ } = mov {
             if mov.to == king_square {
+                if !is_check(game, side) {
+                    panic!("incorrect non-check {}", game.beautiful_print());
+                }
                 return true;
             }
         }
     }
+    if is_check(game, side) {
+        panic!("incorrect check {}", game.beautiful_print());
+    }
     false
 }
 /*

+ 73 - 47
src/search.rs

@@ -4,14 +4,20 @@ use game::Game;
 use evaluate::*;
 use log::info;
 use rand::prelude::*;
-use std::collections::HashMap;
-use zobrist::ZobristTable;
+use zobrist;
 use hash::*;
 
 
 pub struct SearchControl<'a> {
+    /// node counter
     pub nodes: usize,
-    pub check: &'a mut dyn FnMut() -> bool
+
+    /// function to check if the search should be exited
+    pub check: &'a mut dyn FnMut() -> bool,
+    pub move_history: &'a RepetitionTable,
+
+    /// depth the search was started at
+    pub initial_depth: i32,
 }
 
 pub enum SearchResult {
@@ -25,7 +31,6 @@ pub enum SearchResult {
  * searches for moves and returns the best move found plus its value
  */
 pub fn search(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, depth: i32) -> SearchResult {
-
     if depth == 0 {
         return SearchResult::Invalid;
     }
@@ -50,29 +55,27 @@ pub fn search(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, depth:
         let undo = game.apply(mov);
         //assert_eq!(new_game, *game, );
 
-        let hash_entry: Option<CacheEntry> = None;//hash.lookup(&new_game);
-        if let Some(he) = hash_entry {
-            match he.entry_type {
-                EntryType::Value => {
-                    if he.depth >= depth {
-                        info!("table hit!");
-                        valued_moves.push((mov, he.value));
-                        continue;
-                    }
-                },
-                EntryType::UpperBound => {
-                    /*if he.depth >= depth {
-                        if 
-                    }*/
-                },
-                _ => {}
-            }
-        }
-
         //info!("searching {}", mov.to_string());
         let (mut val, ret) = negamax(game, sc, hash, -beta, -alpha, depth - 1);
         val = -val;
 
+        if let Some(turns) = is_mate_in_p1(val) {
+            if turns < 0 {
+                val = mate_in_p1(turns - 1);
+            }
+            if turns > 0 {
+                val = mate_in_p1(turns + 1);
+            }
+            if turns == 0 {
+                if val < 0 {
+                    val = mate_in_p1(-1);
+                }
+                else {
+                    val = mate_in_p1(1);
+                }
+            }
+        }
+
         if ret {
             //return (Move::default(), 0);
             cancelled = true;
@@ -127,14 +130,8 @@ pub fn search(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, depth:
 }
 
 fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alpha: PosValue, beta: PosValue, depth: i32) -> (PosValue, bool) {
-    if depth == 0 {
-        return (quiescence_search(game, sc, alpha, beta, 9), false);
-    }
-
-    let mut is_corr: PosValue = -1;
 
-    let hash_entry = hash.lookup(game);
-    if let Some(e) = hash_entry {
+    if let Some(e) = hash.lookup(game) {
         if e.depth >= depth {
             //println!("TABLE HIT!");
             match e.entry_type {
@@ -145,21 +142,25 @@ fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alpha:
         }
     }
 
+    if depth == 0 {
+        return quiescence_search(game, sc, hash, alpha, beta, 9);
+    }
+
     sc.nodes += 1;
-    if sc.nodes % 128 == 0 {
+    if sc.nodes % 1024 == 0 {
         if (sc.check)() {
             return (0 as _, true);
         }
     }
 
-    if game.get_piece(KING, game.turn) == 0 { return (MIN_VALUE, false); }
+    if game.get_piece(KING, game.turn) == 0 { return (-mate(), false); }
 
-    let mut moves = generate_legal_moves(game, game.turn);
 
-    sort_moves(game, hash, &mut moves);
+    let mut moves = generate_legal_moves(game, game.turn);
 
+    let check = is_check(game, game.turn);
     if moves.len() == 0 {
-        if is_check(game, game.turn) {
+        if check {
             // mate
             return (-mate(), false);
         }
@@ -169,6 +170,27 @@ fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alpha:
         }
     }
 
+    // Nullmove
+    if !check && depth >= 4 && game_lateness(game) < 80 {
+        let nmov = Move::Nullmove;
+        let undo = game.apply(nmov);
+
+        let (mut val, ret) = negamax(game, sc, hash, -beta, -alpha, depth / 2 - 2);
+        val = -val;
+        val = increase_mate_in(val);
+
+        if val >= beta {
+            game.undo_move(undo);
+            //hash.cache(game, CacheEntry::new_beta(depth, beta));
+            //println!("but ret {}: {}", depth, beta);
+            return (beta, false);
+        }
+        game.undo_move(undo);
+    }
+
+
+    sort_moves(game, hash, &mut moves);
+
     let mut alpha_is_exact = false;
 
     for mov in moves {
@@ -177,11 +199,7 @@ fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alpha:
         let (mut val, ret) = negamax(game, sc, hash, -beta, -alpha, depth - 1);
         val = -val;
 
-        if let Some(turns) = is_mate_in_p1(val) {
-            if turns < 0 {
-                val = mate_in_p1(turns - 1);
-            }
-        }
+        val = increase_mate_in(val);
 
         if ret {
             game.undo_move(undo);
@@ -212,39 +230,46 @@ fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alpha:
     return (alpha, false);
 }
 
-fn quiescence_search(game: &mut Game, sc: &mut SearchControl, mut alpha: PosValue, beta: PosValue, depth: i32) -> PosValue {
+fn quiescence_search(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alpha: PosValue, beta: PosValue, depth: i32) -> (PosValue, bool) {
     let val = evaluate(game);
+
     sc.nodes += 1;
+    if sc.nodes % 1024 == 0 {
+        if (sc.check)() {
+            return (0 as _, true);
+        }
+    }
 
     if val >= beta {
-        return beta;
+        return (beta, false);
     }
     if val > alpha {
         alpha = val;
     }
 
     if depth <= 0 {
-        return alpha;
+        return (alpha, false);
     }
 
-    if game.get_piece(KING, game.turn) == 0 { return -mate(); }
+    if game.get_piece(KING, game.turn) == 0 { return (-mate(), false); }
 
     let moves = generate_attacking_moves(game, game.turn);
 
     for mov in moves {
         let undo = game.apply(mov);
 
-        let val = -quiescence_search(game, sc, -beta, -alpha, depth - 1);
+        let (mut val, ret) = quiescence_search(game, sc, hash, -beta, -alpha, depth - 1);
+        val = -val;
         if val >= beta {
             game.undo_move(undo);
-            return beta;
+            return (beta, false);
         }
         if val > alpha {
             alpha = val;
         }
         game.undo_move(undo);
     }
-    return alpha;
+    return (alpha, false);
 }
 
 
@@ -340,6 +365,7 @@ pub fn apply_move(game: &Game, mov: Move) -> Game {
                 }
             }
         },
+        Move::Nullmove => {}
     }
 
     new_game.turn = !new_game.turn;