search.rs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. use std::sync::{RwLock, Arc, Mutex, mpsc};
  2. use std::time::{Instant, Duration};
  3. use movegen::*;
  4. use board::Board;
  5. use evaluate::*;
  6. use log::info;
  7. use rand::prelude::*;
  8. use ttable::*;
  9. use crate::engine::SearchMessage;
  10. pub struct SearchCommon {
  11. pub hash: Cache,
  12. }
  13. ///
  14. /// struct to contain data for a search
  15. ///
  16. pub struct SearchControl {
  17. /// node counter
  18. pub nodes: usize,
  19. pub board: Board,
  20. pub hash: Arc<RwLock<Cache>>,
  21. /// depth the current iteration was started at
  22. initial_depth: i32,
  23. pub killer_moves: Vec<[Move; 2]>,
  24. pub last_move: Option<Move>,
  25. pub countermoves: Arc<Mutex<CountermoveTable>>,
  26. /// current halfmove clock for discarding old hash entries
  27. pub halfmove_age: u16,
  28. /// channel which is probed from time to time to check whether the
  29. /// search should be aborted
  30. stop_channel: mpsc::Receiver<SearchMessage>,
  31. /// whether the search is currently being terminated (after receiving Stop on stop_channel)
  32. stopping: bool,
  33. search_started: Instant,
  34. pub movetime: Option<Duration>,
  35. pub remaining_time: Option<Duration>,
  36. reductions_allowed: bool,
  37. }
  38. #[derive(Debug)]
  39. pub enum SearchResult {
  40. Finished(Move, PosValue),
  41. NoMove(PosValue),
  42. Cancelled(Option<(Move, PosValue)>),
  43. FailHigh(PosValue),
  44. FailLow(PosValue),
  45. PerftFinished,
  46. Invalid
  47. }
  48. impl SearchControl {
  49. pub fn new(board: &Board, stop_channel: mpsc::Receiver<SearchMessage>, hash: Arc<RwLock<Cache>>) -> Self {
  50. SearchControl {
  51. nodes: 0,
  52. board: board.clone(),
  53. hash,
  54. killer_moves: Vec::new(),
  55. last_move: None,
  56. countermoves: Arc::new(Mutex::new(CountermoveTable::new())),
  57. halfmove_age: 0,
  58. stop_channel,
  59. stopping: false,
  60. search_started: Instant::now(),
  61. movetime: None,
  62. remaining_time: None,
  63. initial_depth: 0,
  64. reductions_allowed: true
  65. }
  66. }
  67. pub fn set_depth(&mut self, depth: i32) {
  68. const MAX_EXTEND: i32 = 3;
  69. self.initial_depth = depth;
  70. self.killer_moves = vec![[Move::nullmove(); 2]; (depth + MAX_EXTEND) as usize];
  71. }
  72. fn insert_killer(&mut self, ply_depth: usize, killer: Move) {
  73. if self.is_killer(ply_depth, &killer) {
  74. return;
  75. }
  76. let nkm = self.killer_moves[ply_depth].len();
  77. for i in 1..nkm {
  78. self.killer_moves[ply_depth][i - 1] = self.killer_moves[ply_depth][i];
  79. }
  80. self.killer_moves[ply_depth][nkm - 1] = killer;
  81. }
  82. fn is_killer(&self, ply_depth: usize, killer: &Move) -> bool {
  83. self.killer_moves[ply_depth].contains(killer)
  84. }
  85. fn reconstruct_pv(&self) -> Vec<Move> {
  86. let mut pv: Vec<Move> = Vec::new();
  87. let mut g = self.board.clone();
  88. // max 100 to avoid infinite loops
  89. for _ in 0..100 {
  90. let mce = self.hash.read().unwrap().lookup(&g).map(CacheEntry::clone);
  91. if let Some(ce) = mce {
  92. if ce.mov == (SimpleMove{ from: 0, to: 0 }) {
  93. break;
  94. }
  95. let movs = generate_moves(&g, g.turn);
  96. let bm = movs.iter().find(|m| (**m).to_simple() == ce.mov);
  97. if let Some(found) = bm {
  98. g.apply(*found);
  99. pv.push(*found);
  100. }
  101. else {
  102. break;
  103. }
  104. }
  105. else {
  106. break;
  107. }
  108. }
  109. return pv;
  110. }
  111. /// is called every 1024 nodes to check if the search will be stopped
  112. fn check_stop(&self) -> bool {
  113. if let Some(mt) = self.movetime {
  114. if self.search_started.elapsed() >= mt {
  115. return true;
  116. }
  117. }
  118. if let Some(rt) = self.remaining_time {
  119. if self.search_started.elapsed() >= (rt / 17) {
  120. return true;
  121. }
  122. }
  123. if let Ok(SearchMessage::Stop) = self.stop_channel.try_recv() {
  124. return true;
  125. }
  126. if self.stopping {
  127. return true;
  128. }
  129. return false;
  130. }
  131. fn windowed_search(&mut self, depth: i32, expected_value: PosValue, initial_window_size: i32) -> SearchResult {
  132. let mut alpha_coeff = 1;
  133. let mut beta_coeff = 1;
  134. let mut alpha = expected_value - initial_window_size * alpha_coeff;
  135. let mut beta = expected_value + initial_window_size * beta_coeff;
  136. let mut result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
  137. loop {
  138. match result {
  139. SearchResult::FailHigh(_val) => {
  140. beta_coeff *= 3;
  141. beta = expected_value + initial_window_size * beta_coeff;
  142. if beta_coeff >= 9 {
  143. beta = MAX_VALUE;
  144. }
  145. result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
  146. },
  147. SearchResult::FailLow(_val) => {
  148. alpha_coeff *= 3;
  149. alpha = expected_value - initial_window_size * alpha_coeff;
  150. if alpha_coeff >= 9 {
  151. alpha = MIN_VALUE;
  152. }
  153. result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
  154. },
  155. SearchResult::Finished(_mov, val) => {
  156. // successful search within bounds
  157. break;
  158. }
  159. SearchResult::Invalid => {
  160. alpha = MIN_VALUE; beta = MAX_VALUE;
  161. result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
  162. }
  163. _ => break,
  164. }
  165. }
  166. return result;
  167. }
  168. pub fn iterative_deepening(&mut self, max_depth: Option<i32>) -> bool {
  169. let mut best_move: Option<Move> = None;
  170. let mut best_val: Option<PosValue> = None;
  171. self.search_started = Instant::now();
  172. self.halfmove_age = self.board.ply_number() as _;
  173. for depth in 1.. {
  174. self.set_depth(depth);
  175. let result =
  176. if let Some(bv) = best_val {
  177. const WINDOW_RADIUS: PosValue = 20;
  178. self.windowed_search(depth, bv, WINDOW_RADIUS)
  179. } else {
  180. search(self.board.clone(), self, self.hash.clone(), MIN_VALUE, MAX_VALUE, depth)
  181. };
  182. match result {
  183. SearchResult::Finished(mov, val) => {
  184. best_move = Some(mov);
  185. best_val = Some(val);
  186. let elapsed = self.search_started.elapsed();
  187. let nodes = self.nodes;
  188. let nps = (nodes as f64 / elapsed.as_nanos() as f64 * 1000000000.0) as i64;
  189. let hashfull = self.hash.read().unwrap().fullness_permill();
  190. let pv_array = self.reconstruct_pv();
  191. let pv = &pv_array.iter().filter(|&m| !m.is_null()).map(|m| m.to_string()).fold(String::new(), |a, b| a + " " + &b)[0..];
  192. if let Some(plies) = crate::evaluate::is_mate_in_plies(val) {
  193. //println!("plies = {}", plies);
  194. let turns = (plies + if plies > 0 { 1 } else { -1 }) / 2;
  195. let infostring = format!("info depth {} score mate {} time {} nodes {} nps {} hashfull {} pv{}", depth, turns, elapsed.as_millis(), nodes, nps, hashfull, pv);
  196. println!("{}", infostring);
  197. info!("{}", infostring);
  198. if max_depth.is_none() && plies.abs() * 2 <= depth {
  199. break;
  200. }
  201. }
  202. else {
  203. let infostring = format!("info depth {} score cp {} time {} nodes {} nps {} hashfull {} pv{}", depth, val, elapsed.as_millis(), nodes, nps, hashfull, pv);
  204. println!("{}", infostring);
  205. info!("{}", infostring);
  206. }
  207. },
  208. SearchResult::NoMove(val) => {
  209. let infostring = format!("info depth {} score cp {}", depth, val);
  210. println!("{}", infostring);
  211. info!("{}", infostring);
  212. break;
  213. }
  214. _ => {
  215. break;
  216. }
  217. }
  218. if let Some(d) = max_depth {
  219. if depth >= d {
  220. break;
  221. }
  222. }
  223. }
  224. if let (Some(bm), Some(_bv)) = (best_move, best_val) {
  225. info!("bestmove {}", bm.to_string());
  226. println!("bestmove {}", bm.to_string());
  227. }
  228. else {
  229. info!("bestmove 0000");
  230. println!("bestmove 0000");
  231. }
  232. return true;
  233. }
  234. }
  235. ///
  236. /// searches for moves and returns the best move found plus its value
  237. ///
  238. pub fn search(mut board: Board, sc: &mut SearchControl, hashs: Arc<RwLock<Cache>>, mut alpha: PosValue, beta: PosValue, depth: i32) -> SearchResult {
  239. let mut hash = hashs.write().unwrap();
  240. if depth == 0 {
  241. return SearchResult::Invalid;
  242. }
  243. let cache_entry = hash.lookup(&board).map(CacheEntry::clone);
  244. let ply_depth = (sc.initial_depth - depth) as usize;
  245. let turn = board.turn;
  246. let moves = generate_legal_sorted_moves(&mut board, &mut hash, &sc.killer_moves[ply_depth], cache_entry, false, turn);
  247. //let mut moves = generate_legal_moves(game, game.turn);
  248. //sort_moves(game, hash, &sc.killer_moves[ply_depth], &mut moves);
  249. info!("moves: {:?}", moves.iter().map(|mv| mv.to_string()).collect::<Vec<String>>());
  250. let mut cancelled = false;
  251. if moves.is_empty() {
  252. if is_check(&board, board.turn) {
  253. return SearchResult::NoMove(checkmated());
  254. }
  255. else {
  256. return SearchResult::NoMove(0);
  257. }
  258. }
  259. let mut best_move: Option<Move> = None;
  260. for mov in moves {
  261. let undo = board.apply(mov);
  262. let val = increase_mate_in(
  263. -negamax(&mut board, sc, &mut hash, decrease_mate_in(-beta), decrease_mate_in(-alpha), depth - 1)
  264. );
  265. //info!("moveval {} -> {}\n", mov.to_string(), val);
  266. board.undo_move(undo);
  267. if sc.stopping {
  268. cancelled = true;
  269. break;
  270. }
  271. if val >= beta {
  272. hash.cache(&board, CacheEntry::new_lower(depth as _, sc.halfmove_age as _, mov.to_simple(), val));
  273. return SearchResult::FailHigh(val);
  274. }
  275. if val > alpha {
  276. alpha = val;
  277. best_move = Some(mov);
  278. }
  279. }
  280. if cancelled {
  281. return SearchResult::Cancelled(None);
  282. }
  283. if let Some(mov) = best_move {
  284. // alpha is exact
  285. let ce = CacheEntry::new_value(depth as _, sc.halfmove_age as _, mov.to_simple(), alpha);
  286. hash.cache(&board, ce);
  287. return SearchResult::Finished(mov, alpha);
  288. }
  289. else {
  290. hash.cache(&board, CacheEntry::new_upper(depth as _, sc.halfmove_age as _, SimpleMove { from: 0, to: 0 }, alpha));
  291. return SearchResult::FailLow(alpha);
  292. }
  293. }
  294. pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut alpha: PosValue, beta: PosValue, mut depth: i32) -> PosValue {
  295. let last_move = sc.last_move;
  296. // we can't beat an alpha this good
  297. if alpha >= mate_in_plies(1) {
  298. return alpha;
  299. }
  300. let cache_entry = hash.lookup(game).map(CacheEntry::clone);
  301. if let Some(e) = &cache_entry {
  302. if e.depth() as i32 >= depth {
  303. //println!("TABLE HIT!");
  304. match e.entry_type {
  305. EntryType::Value => { return e.value(); },
  306. EntryType::LowerBound => {
  307. if e.value() >= beta { return beta; }
  308. },
  309. EntryType::UpperBound => {
  310. if e.value() < alpha { return alpha; }
  311. },
  312. EntryType::NoBound => {}
  313. }
  314. }
  315. }
  316. let check = is_check(game, game.turn);
  317. if depth == 0 {
  318. // check extend
  319. if check {
  320. depth = 1;
  321. }
  322. else {
  323. return quiescence_search(game, sc, hash, alpha, beta, 9);
  324. }
  325. }
  326. sc.nodes += 1;
  327. if sc.nodes % 1024 == 0 {
  328. if sc.check_stop() {
  329. sc.stopping = true;
  330. return 0;
  331. }
  332. }
  333. let ply_depth = (sc.initial_depth - depth) as usize;
  334. /*let moves = generate_legal_sorted_moves(
  335. game,
  336. hash,
  337. &sc.killer_moves[ply_depth],
  338. cache_entry,
  339. game.turn);*/
  340. let mut moves = MoveGenerator::generate_legal_moves(
  341. game,
  342. cache_entry.as_ref(),
  343. &sc.killer_moves[ply_depth],
  344. if depth >= 3 { last_move } else { None },
  345. sc.countermoves.clone(),
  346. game.turn
  347. );
  348. //info!("nega moves: {:?}", moves.iter().map(|mv| mv.to_string()).collect::<Vec<String>>());
  349. if moves.is_empty() {
  350. if check {
  351. // mate
  352. return checkmated();
  353. }
  354. else {
  355. // stalemate
  356. return 0;
  357. }
  358. }
  359. // Nullmove
  360. if sc.reductions_allowed && !check && depth >= 4 && game.get_all_side(game.turn).count_ones() > 7 {
  361. let reduce = if depth > 5 { if depth > 7 { 5 } else { 4 }} else { 3 };
  362. let nmov = Move::nullmove();
  363. let undo = game.apply(nmov);
  364. sc.reductions_allowed = false;
  365. let val = -negamax(game, sc, hash, -beta, -alpha, depth - reduce);
  366. sc.reductions_allowed = true;
  367. game.undo_move(undo);
  368. if is_mate_in_plies(val).is_none() {
  369. if val >= beta {
  370. depth -= reduce - 1;
  371. //return beta;
  372. }
  373. }
  374. }
  375. // futility pruning
  376. if false && depth == 1 && !check && game_lateness(game) < 60 {
  377. const FUTILITY_THRESHOLD: PosValue = 240;
  378. let curr_eval = quiescence_search(game, sc, hash, alpha, beta, 9);
  379. if curr_eval + FUTILITY_THRESHOLD <= alpha {
  380. return alpha;
  381. }
  382. }
  383. let mut best_move: Option<Move> = None;
  384. while let Some(mov) = moves.next() {
  385. let mut reduce = 0;
  386. let reductions_allowed_before = sc.reductions_allowed;
  387. if depth > 4 && moves.is_late() && !mov.is_promotion() && reductions_allowed_before {
  388. reduce = 1;
  389. if depth >= 8 {
  390. reduce = 2;
  391. }
  392. sc.reductions_allowed = false;
  393. }
  394. //println!("mov: {}", mov.to_string());
  395. let undo = game.apply(mov);
  396. sc.last_move = Some(mov);
  397. let mut val = increase_mate_in(
  398. -negamax(game, sc, hash, -decrease_mate_in(beta), -decrease_mate_in(alpha), depth - 1 - reduce)
  399. );
  400. game.undo_move(undo);
  401. sc.reductions_allowed = reductions_allowed_before;
  402. if reduce != 0 && (val >= beta || val > alpha) {
  403. reduce = 0;
  404. let undo = game.apply(mov);
  405. sc.last_move = Some(mov);
  406. val = increase_mate_in(
  407. -negamax(game, sc, hash, -decrease_mate_in(beta), -decrease_mate_in(alpha), depth - 1 - reduce)
  408. );
  409. game.undo_move(undo);
  410. }
  411. // return if the search has been cancelled
  412. if sc.stopping {
  413. return alpha;
  414. }
  415. if val >= beta {
  416. hash.cache(game, CacheEntry::new_lower(depth as _, sc.halfmove_age as _, mov.to_simple(), val));
  417. if !mov.is_capture() {
  418. sc.insert_killer(ply_depth, mov);
  419. if depth >= 2 {
  420. if let Some(lm) = last_move {
  421. sc.countermoves.as_ref().lock().unwrap().update_score(game.turn, lm, mov, (depth * depth) as i16);
  422. }
  423. }
  424. }
  425. return val;
  426. }
  427. if val > alpha {
  428. alpha = val;
  429. best_move = Some(mov);
  430. if sc.initial_depth >= 10 && cache_entry.is_some() && cache_entry.as_ref().unwrap().entry_type == EntryType::Value {
  431. //println!("mov: {}", mov.to_string());
  432. }
  433. if depth >= 2 {
  434. if let Some(_lm) = last_move {
  435. //sc.countermoves.as_ref().lock().unwrap().update_score(lm, mov, (depth * depth) as i16);
  436. }
  437. }
  438. if alpha >= mate_in_plies(1) {
  439. break;
  440. }
  441. }
  442. }
  443. if let Some(mov) = best_move {
  444. // alpha is exact
  445. hash.cache(game, CacheEntry::new_value(depth as _, sc.halfmove_age as _, mov.to_simple(), alpha));
  446. }
  447. else {
  448. hash.cache(game, CacheEntry::new_upper(depth as _, sc.halfmove_age as _, SimpleMove { from: 0, to: 0 }, alpha));
  449. }
  450. //info!("best alpha {}", alpha);
  451. return alpha;
  452. }
  453. fn quiescence_search(board: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut alpha: PosValue, beta: PosValue, depth: i32) -> PosValue {
  454. sc.nodes += 1;
  455. if sc.nodes % 1024 == 0 {
  456. if sc.check_stop() {
  457. sc.stopping = true;
  458. return 0;
  459. }
  460. }
  461. let last_move = sc.last_move;
  462. if board.get_piece(KING, board.turn) == 0 { return checkmated(); }
  463. let check = is_check(board, board.turn);
  464. let val = evaluate(board);
  465. if !check {
  466. if val >= beta {
  467. return beta;
  468. }
  469. if val > alpha {
  470. alpha = val;
  471. }
  472. }
  473. if depth <= 0 {
  474. return alpha;
  475. }
  476. //let mut moves = generate_legal_sorted_moves(game, hash, &[], None, true, game.turn);
  477. let mut moves = generate_legal_moves(board, board.turn, !check);
  478. //sort_moves_no_hash(game, &mut moves);
  479. //sort_moves_least_valuable_attacker(board, &mut moves, last_move);
  480. let mut val_movs: Vec<_> = moves.iter()
  481. .map(|m| (*m, calculate_see(board.clone(), *m, board.turn)))
  482. .filter(|(_m, v)| *v > 0)
  483. .collect();
  484. val_movs.sort_unstable_by_key(|(_m, v)| -*v);
  485. moves = val_movs.iter().map(|(m, _v)| *m).collect();
  486. //moves.sort_by_cached_key(|m| -calculate_see(board.clone(), *m, board.turn));
  487. let apply_delta_pruning = game_lateness(board) < 50;
  488. for mov in moves {
  489. if apply_delta_pruning {
  490. const PIECE_VALUES: [i32; 6] = [
  491. 100, // Pawn
  492. 300, // Knight
  493. 320, // Bishop
  494. 400, // Rook
  495. 800, // Queen
  496. 100000 // King
  497. ];
  498. const SAFETY_MARGIN: i32 = 200;
  499. let target_sq = mov.to_simple().to;
  500. if let Some((piece_type, _side)) = board.get_square(target_sq) {
  501. if val + PIECE_VALUES[piece_type as usize] + SAFETY_MARGIN < alpha {
  502. continue;
  503. }
  504. }
  505. }
  506. let undo = board.apply(mov);
  507. sc.last_move = Some(mov);
  508. let val = increase_mate_in(
  509. -quiescence_search(board, sc, hash, decrease_mate_in(-beta), decrease_mate_in(-alpha), depth - 1)
  510. );
  511. board.undo_move(undo);
  512. if sc.stopping {
  513. return alpha
  514. }
  515. if val >= beta {
  516. return beta;
  517. }
  518. if val > alpha {
  519. alpha = val;
  520. }
  521. }
  522. return alpha;
  523. }
  524. impl SearchControl {
  525. pub fn new_perft(board: &Board, stop_channel: mpsc::Receiver<SearchMessage>, depth: i32) -> Self {
  526. SearchControl {
  527. nodes: 0,
  528. board: board.clone(),
  529. hash: Arc::new(RwLock::new(Cache::new(0))),
  530. initial_depth: depth,
  531. killer_moves: Vec::new(),
  532. last_move: None,
  533. countermoves: Arc::new(Mutex::new(CountermoveTable::new())),
  534. halfmove_age: board.ply_number() as _,
  535. stop_channel,
  536. stopping: false,
  537. search_started: Instant::now(),
  538. movetime: None,
  539. remaining_time: None,
  540. reductions_allowed: false
  541. }
  542. }
  543. pub fn perft(&mut self, depth: i32) -> bool {
  544. let board = &mut self.board;
  545. let moves = generate_legal_moves(board, board.turn, false);
  546. if depth <= 1 {
  547. self.nodes += moves.len();
  548. if self.nodes % 1024 < moves.len() {
  549. if self.check_stop() {
  550. return true;
  551. }
  552. }
  553. return false;
  554. }
  555. for mov in moves {
  556. let nodes_before = self.nodes;
  557. let undo = self.board.apply(mov);
  558. let do_return = self.perft(depth - 1);
  559. self.board.undo_move(undo);
  560. if depth >= self.initial_depth {
  561. println!("{}: {}", mov.to_string(), self.nodes - nodes_before);
  562. }
  563. if do_return {
  564. return true;
  565. }
  566. }
  567. return false;
  568. }
  569. }
  570. pub struct CountermoveTable {
  571. table: Box<[[[[i16; 12]; 64]; 12]]>,
  572. }
  573. impl CountermoveTable {
  574. pub fn new() -> Self {
  575. //CountermoveTable { table: Box::new([[[[0; 12]; 64]; 12]; 64]) }
  576. CountermoveTable { table: vec![[[[0; 12]; 64]; 12]; 64].into_boxed_slice() }
  577. }
  578. pub fn get_score(&self, side: Side, last_move: Move, this_move: Move) -> i16 {
  579. let side_offs = if side == BLACK { 6 } else { 0 };
  580. self.table
  581. [last_move.to() as usize]
  582. [last_move.piece_type() as usize + side_offs]
  583. [this_move.to() as usize]
  584. [this_move.piece_type() as usize + side_offs]
  585. }
  586. pub fn update_score(&mut self, side: Side, last_move: Move, this_move: Move, increase: i16) {
  587. let side_offs = if side == BLACK { 6 } else { 0 };
  588. let entry = &mut self.table
  589. [last_move.to() as usize]
  590. [last_move.piece_type() as usize + side_offs]
  591. [this_move.to() as usize]
  592. [this_move.piece_type() as usize + side_offs];
  593. *entry = entry.saturating_add(increase);
  594. }
  595. }
  596. #[cfg(test)]
  597. mod tests {
  598. use super::*;
  599. #[test]
  600. fn test_move_generation() {
  601. let positions = [
  602. "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8",
  603. "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10"
  604. ];
  605. let perft_results: [Vec<usize>; 2] = [
  606. vec![44, 1486, 62379, 2103487],
  607. vec![46, 2079, 89890, 3894594]
  608. ];
  609. for (i, &position) in positions.iter().enumerate() {
  610. let board = Board::from_fen_str(position).unwrap();
  611. for (j, &p_res) in perft_results[i].iter().enumerate() {
  612. let depth = j + 1;
  613. let (_, stop_check) = mpsc::channel::<SearchMessage>();
  614. let mut pc = SearchControl::new_perft(&board, stop_check, depth as _);
  615. pc.perft(depth as _);
  616. assert_eq!(pc.nodes, p_res);
  617. }
  618. }
  619. }
  620. }