1use crate::bitboard::*;
2use crate::constants::*;
3
4use once_cell::sync::Lazy;
5
6extern crate rand;
7
8use rand::Rng;
9
10use std::io::{BufWriter, Write};
11
12pub type Rank = usize;
14pub type File = usize;
16pub type Square = usize;
18
19#[derive(Clone)]
21pub struct PawnInfo {
22 pub pushes: Vec<Square>,
23 pub captures: Vec<Square>,
24}
25
26pub type Move = u32;
28
29pub trait MoveTrait {
31 fn ft(from_sq: Square, to_sq: Square) -> Move;
33 fn from_sq(self) -> Square;
35 fn to_sq(self) -> Square;
37 fn uci(self) -> String;
39}
40
41impl MoveTrait for Move {
42 fn ft(from_sq: Square, to_sq: Square) -> Move {
44 ((from_sq << FROM_SQ_SHIFT) + (to_sq << TO_SQ_SHIFT)) as u32
45 }
46 fn from_sq(self) -> Square {
48 ((self >> FROM_SQ_SHIFT) & SQUARE_MASK) as Square
49 }
50 fn to_sq(self) -> Square {
52 ((self >> TO_SQ_SHIFT) & SQUARE_MASK) as Square
53 }
54 fn uci(self) -> String {
56 format!("{}{}", self.from_sq().uci(), self.to_sq().uci())
57 }
58}
59
60#[derive(Copy, Clone)]
62pub enum Delta {
63 N,
64 NE,
65 NNE,
66 NEE,
67 E,
68 SE,
69 SEE,
70 SSE,
71 S,
72 SW,
73 SSW,
74 SWW,
75 W,
76 NW,
77 NWW,
78 NNW,
79}
80
81pub trait DeltaBuffer {
83 fn len(&self) -> usize;
85 fn get(&self, i: usize) -> Δ
87}
88
89impl DeltaBuffer for [Delta; 1] {
91 fn len(&self) -> usize {
93 1
94 }
95 fn get(&self, i: usize) -> &Delta {
97 return &self[i];
98 }
99}
100
101impl DeltaBuffer for [Delta; 4] {
103 fn len(&self) -> usize {
105 4
106 }
107 fn get(&self, i: usize) -> &Delta {
109 return &self[i];
110 }
111}
112
113impl DeltaBuffer for [Delta; 8] {
115 fn len(&self) -> usize {
117 8
118 }
119 fn get(&self, i: usize) -> &Delta {
121 return &self[i];
122 }
123}
124
125pub fn rank_file(rank: Rank, file: File) -> Square {
127 rank * NUM_FILES + file
128}
129
130pub trait SquareTrait {
132 fn from_uci(uci: String) -> (Square, bool);
134 fn rank(self) -> Rank;
136 fn file(self) -> File;
138 fn uci(self) -> String;
140 fn bitboard(self) -> Bitboard;
142 fn add_delta(self, delta: &Delta) -> (Square, bool);
145 fn add_delta_occup(self, delta: &Delta, occup: Bitboard) -> (Square, bool);
147}
148
149impl SquareTrait for Square {
151 fn from_uci(uci: String) -> (Square, bool) {
153 if uci.len() != 2 {
154 return (SQUARE_A1, false);
155 }
156 let file = match &uci[0..1] {
157 "a" => 0,
158 "b" => 1,
159 "c" => 2,
160 "d" => 3,
161 "e" => 4,
162 "f" => 5,
163 "g" => 6,
164 "h" => 7,
165 _ => panic!("invalid file {:?}", &uci[0..1]),
166 };
167 let rank = match &uci[1..2] {
168 "1" => 0,
169 "2" => 1,
170 "3" => 2,
171 "4" => 3,
172 "5" => 4,
173 "6" => 5,
174 "7" => 6,
175 "8" => 7,
176 _ => panic!("invalid rank {:?}", &uci[1..2]),
177 };
178 (rank * NUM_FILES + file, true)
179 }
180 fn rank(self) -> Rank {
182 self >> RANK_SHIFT
183 }
184
185 fn file(self) -> File {
187 self & FILE_MASK
188 }
189
190 fn uci(self) -> String {
192 format!("{}{}", FILE_NAMES[self.file()], self.rank() + 1)
193 }
194
195 fn bitboard(self) -> Bitboard {
197 1 << (LAST_FILE - self.file()) + self.rank() * NUM_FILES
198 }
199
200 fn add_delta(self, delta: &Delta) -> (Square, bool) {
203 let file = self.file();
204 let rank = self.rank();
205 match delta {
206 Delta::N => {
207 return if rank > ONE_BEFORE_LAST_RANK {
208 (0, false)
209 } else {
210 (rank_file(rank + 1, file), true)
211 }
212 }
213 Delta::NE => {
214 return if rank > ONE_BEFORE_LAST_RANK || file > ONE_BEFORE_LAST_FILE {
215 (0, false)
216 } else {
217 (rank_file(rank + 1, file + 1), true)
218 }
219 }
220 Delta::NNE => {
221 return if rank > TWO_BEFORE_LAST_RANK || file > ONE_BEFORE_LAST_FILE {
222 (0, false)
223 } else {
224 (rank_file(rank + 2, file + 1), true)
225 }
226 }
227 Delta::NEE => {
228 return if rank > ONE_BEFORE_LAST_RANK || file > TWO_BEFORE_LAST_FILE {
229 (0, false)
230 } else {
231 (rank_file(rank + 1, file + 2), true)
232 }
233 }
234 Delta::E => {
235 return if file > ONE_BEFORE_LAST_FILE {
236 (0, false)
237 } else {
238 (rank_file(rank, file + 1), true)
239 }
240 }
241 Delta::SE => {
242 return if rank < 1 || file > ONE_BEFORE_LAST_FILE {
243 (0, false)
244 } else {
245 (rank_file(rank - 1, file + 1), true)
246 }
247 }
248 Delta::SEE => {
249 return if rank < 1 || file > TWO_BEFORE_LAST_FILE {
250 (0, false)
251 } else {
252 (rank_file(rank - 1, file + 2), true)
253 }
254 }
255 Delta::SSE => {
256 return if rank < 2 || file > ONE_BEFORE_LAST_FILE {
257 (0, false)
258 } else {
259 (rank_file(rank - 2, file + 1), true)
260 }
261 }
262 Delta::S => {
263 return if rank < 1 {
264 (0, false)
265 } else {
266 (rank_file(rank - 1, file), true)
267 }
268 }
269 Delta::SW => {
270 return if rank < 1 || file < 1 {
271 (0, false)
272 } else {
273 (rank_file(rank - 1, file - 1), true)
274 }
275 }
276 Delta::SSW => {
277 return if rank < 2 || file < 1 {
278 (0, false)
279 } else {
280 (rank_file(rank - 2, file - 1), true)
281 }
282 }
283 Delta::SWW => {
284 return if rank < 1 || file < 2 {
285 (0, false)
286 } else {
287 (rank_file(rank - 1, file - 2), true)
288 }
289 }
290 Delta::W => {
291 return if file < 1 {
292 (0, false)
293 } else {
294 (rank_file(rank, file - 1), true)
295 }
296 }
297 Delta::NW => {
298 return if rank > ONE_BEFORE_LAST_RANK || file < 1 {
299 (0, false)
300 } else {
301 (rank_file(rank + 1, file - 1), true)
302 }
303 }
304 Delta::NWW => {
305 return if rank > ONE_BEFORE_LAST_RANK || file < 2 {
306 (0, false)
307 } else {
308 (rank_file(rank + 1, file - 2), true)
309 }
310 }
311 Delta::NNW => {
312 return if rank > TWO_BEFORE_LAST_RANK || file < 1 {
313 (0, false)
314 } else {
315 (rank_file(rank + 2, file - 1), true)
316 }
317 }
318 }
319 }
320
321 fn add_delta_occup(self, delta: &Delta, occup: Bitboard) -> (Square, bool) {
323 let (sq, ok) = self.add_delta(delta);
324 if !ok {
325 return (0, false);
326 }
327 if sq.bitboard() & occup != 0 {
328 return (0, false);
329 }
330 return (sq, true);
331 }
332}
333
334pub fn jump_attack<T: DeltaBuffer>(sq: Square, deltas: &T, occup: Bitboard) -> Bitboard {
336 let mut bb: Bitboard = 0;
337 for i in 0..deltas.len() {
338 let (test_sq, ok) = sq.add_delta_occup(deltas.get(i), occup);
339 if ok {
340 bb |= test_sq.bitboard();
341 }
342 }
343 bb
344}
345
346pub fn sliding_attack<T: DeltaBuffer>(sq: Square, deltas: &T, occup: Bitboard) -> Bitboard {
348 let mut bb: Bitboard = 0;
349 for i in 0..deltas.len() {
350 let mut test_sq = sq;
351 loop {
352 let (new_test_sq, ok) = test_sq.add_delta(deltas.get(i));
353 if ok {
354 test_sq = new_test_sq;
355 bb |= test_sq.bitboard();
356 if (test_sq.bitboard()) & occup != 0 {
357 break;
358 }
359 } else {
360 break;
361 }
362 }
363 }
364 bb
365}
366
367pub type AttackTable = [Bitboard; BOARD_AREA];
369
370pub static PAWN_INFOS: Lazy<Vec<Vec<PawnInfo>>> = Lazy::new(|| {
372 let mut cpis: Vec<Vec<PawnInfo>> = Vec::new();
373 for col in BLACK..WHITE + 1 {
374 let mut spis: Vec<PawnInfo> = Vec::new();
375 for sq in 0..BOARD_AREA {
376 let mut pi: PawnInfo = PawnInfo {
377 pushes: Vec::new(),
378 captures: Vec::new(),
379 };
380 let push_delta: &Delta = if col == WHITE { &Delta::N } else { &Delta::S };
381 let (push_one_sq, ok) = sq.add_delta(push_delta);
382 if ok {
383 pi.pushes.push(push_one_sq);
384 let (capt_left_sq, ok) = push_one_sq.add_delta(&Delta::W);
385 if ok {
386 pi.captures.push(capt_left_sq);
387 }
388 let (capt_right_sq, ok) = push_one_sq.add_delta(&Delta::E);
389 if ok {
390 pi.captures.push(capt_right_sq);
391 }
392 if sq.rank() == PAWN_START_RANKS[col] {
393 let (push_two_sq, ok) = push_one_sq.add_delta(push_delta);
394 if ok {
395 pi.pushes.push(push_two_sq);
396 } else {
397 panic!("illegal pawn start rank")
398 }
399 }
400 }
401 spis.push(pi);
402 }
403 cpis.push(spis);
404 }
405 cpis
406});
407
408pub static KNIGHT_ATTACK: Lazy<AttackTable> = Lazy::new(|| {
410 let mut at = EMPTY_ATTACK_TABLE;
411 for sq in 0..BOARD_AREA {
412 at[sq] = jump_attack(sq, &KNIGHT_DELTAS, 0);
413 }
414 at
415});
416pub static BISHOP_ATTACK: Lazy<AttackTable> = Lazy::new(|| {
418 let mut at = EMPTY_ATTACK_TABLE;
419 for sq in 0..BOARD_AREA {
420 at[sq] = sliding_attack(sq, &BISHOP_DELTAS, 0);
421 }
422 at
423});
424pub static BISHOP_MAGIC_ATTACK: Lazy<AttackTable> = Lazy::new(|| {
426 let mut at = EMPTY_ATTACK_TABLE;
427 for sq in 0..BOARD_AREA {
428 at[sq] = magic_attack(sq, sliding_attack(sq, &BISHOP_DELTAS, 0));
429 }
430 at
431});
432pub static ROOK_ATTACK: Lazy<AttackTable> = Lazy::new(|| {
434 let mut at = EMPTY_ATTACK_TABLE;
435 for sq in 0..BOARD_AREA {
436 at[sq] = sliding_attack(sq, &ROOK_DELTAS, 0);
437 }
438 at
439});
440pub static ROOK_MAGIC_ATTACK: Lazy<AttackTable> = Lazy::new(|| {
442 let mut at = EMPTY_ATTACK_TABLE;
443 for sq in 0..BOARD_AREA {
444 at[sq] = magic_attack(sq, sliding_attack(sq, &ROOK_DELTAS, 0));
445 }
446 at
447});
448pub static QUEEN_ATTACK: Lazy<AttackTable> = Lazy::new(|| {
450 let mut at = EMPTY_ATTACK_TABLE;
451 for sq in 0..BOARD_AREA {
452 at[sq] = sliding_attack(sq, &QUEEN_DELTAS, 0);
453 }
454 at
455});
456pub static LANCER_ATTACKS: Lazy<[AttackTable; NUM_LANCERS]> = Lazy::new(|| {
458 let mut ats: [AttackTable; NUM_LANCERS] = [
459 EMPTY_ATTACK_TABLE,
460 EMPTY_ATTACK_TABLE,
461 EMPTY_ATTACK_TABLE,
462 EMPTY_ATTACK_TABLE,
463 EMPTY_ATTACK_TABLE,
464 EMPTY_ATTACK_TABLE,
465 EMPTY_ATTACK_TABLE,
466 EMPTY_ATTACK_TABLE,
467 ];
468 for ld in 0..NUM_LANCERS {
469 for sq in 0..BOARD_AREA {
470 ats[ld][sq] = sliding_attack(sq, &[LANCER_DELTAS[ld]], 0);
471 }
472 }
473 ats
474});
475pub static KING_ATTACK: Lazy<AttackTable> = Lazy::new(|| {
477 let mut at = EMPTY_ATTACK_TABLE;
478 for sq in 0..BOARD_AREA {
479 at[sq] = jump_attack(sq, &QUEEN_DELTAS, 0);
480 }
481 at
482});
483pub static KING_AREA: Lazy<AttackTable> = Lazy::new(|| {
485 let mut at = EMPTY_ATTACK_TABLE;
486 for sq in 0..BOARD_AREA {
487 at[sq] = jump_attack(sq, &QUEEN_DELTAS, 0) | sq.bitboard();
488 }
489 at
490});
491
492pub fn translate_mask_to_occupancy(mask: usize, mobility: Bitboard) -> Bitboard {
494 let mut occup: Bitboard = 0;
495 let mut mob = mobility;
496 let mut m = mask;
497 loop {
498 let (bb, ok) = mob.pop_bitboard();
499 if ok {
500 if m & 1 != 0 {
501 occup |= bb;
502 }
503 m >>= 1;
504 } else {
505 return occup;
506 }
507 }
508}
509
510pub fn new_magic() -> u64 {
512 rand::thread_rng().gen::<u64>()
513}
514
515pub fn mobility_index(mobility: Bitboard, magic: u64, shift: usize) -> usize {
517 let (result, _) = mobility.overflowing_mul(magic);
518 (result >> (64 - shift)) as usize
519}
520
521pub fn detect_collision(magic: u64, shift: usize, mobility: Bitboard) -> bool {
523 let variations = mobility.variation_count();
524 let mut mapped = vec![(false, 0); 1 << shift];
525
526 let mut mask: usize = 0;
527
528 loop {
529 if mask < variations {
530 let occup = translate_mask_to_occupancy(mask, mobility);
531 let index = mobility_index(occup, magic, shift);
532 let (used, bb) = mapped[index];
533 if used {
534 if bb != occup {
535 return true;
536 }
537 } else {
538 mapped[index] = (true, occup);
539 }
540 mask += 1;
541 } else {
542 return false;
543 }
544 }
545}
546
547pub fn find_magic_for_shift(shift: usize, mobility: Bitboard, max_tries: usize) -> (u64, bool) {
549 for _ in 0..max_tries {
550 let magic = new_magic();
551 if !detect_collision(magic, shift, mobility) {
552 return (magic, true);
553 }
554 }
555 return (0, false);
556}
557
558pub fn find_magic_and_shift(
560 mobility: Bitboard,
561 max_shift: usize,
562 min_shift: usize,
563 max_tries: usize,
564) -> (u64, usize, bool) {
565 let mut last_good_shift: usize = 0;
566 let mut last_good_magic: u64 = 0;
567 let mut has_magic = false;
568 for i in 0..(max_shift - min_shift + 1) {
569 let shift = max_shift - i;
570 let (magic, ok) = find_magic_for_shift(shift, mobility, max_tries);
571 if ok {
572 last_good_shift = shift;
573 last_good_magic = magic;
574 has_magic = true;
575 }
576 }
577 if has_magic {
578 return (last_good_magic, last_good_shift, true);
579 }
580 return (0, 0, false);
581}
582
583pub fn magic_attack(sq: Square, attack: Bitboard) -> Bitboard {
585 let mut mask = BITBOARD_MIDDLE;
586
587 if sq.rank() == 0 {
588 mask |= BITBOARD_RANK_1_MIDDLE;
589 }
590 if sq.rank() == LAST_RANK {
591 mask |= BITBOARD_RANK_8_MIDDLE;
592 }
593 if sq.file() == 0 {
594 mask |= BITBOARD_FILE_A_MIDDLE;
595 }
596 if sq.file() == LAST_FILE {
597 mask |= BITBOARD_FILE_H_MIDDLE;
598 }
599
600 attack & mask
601}
602
603pub fn log_find_magic_and_shift(
605 bw: &mut BufWriter<std::fs::File>,
606 sq: Square,
607 attack: Bitboard,
608 name: &str,
609) {
610 let data = format!(
611 "{}\nmobility for {} at square {} , count {}\n\n",
612 attack.pretty_print_string(),
613 name,
614 sq.uci(),
615 attack.count_ones()
616 );
617
618 (*bw)
619 .write_all(data.as_bytes())
620 .expect("Unable to write data.");
621
622 println!("{}", data);
623
624 let result = find_magic_and_shift(attack, 14, if name == "bishop" { 5 } else { 10 }, 5000);
625
626 if !result.2 {
627 panic!("shift too large")
628 }
629
630 let data = format!(
631 "magic kind {} square {} magic {:016X} shift {}\n\n",
632 name,
633 sq.uci(),
634 result.0,
635 result.1
636 );
637
638 (*bw)
639 .write_all(data.as_bytes())
640 .expect("Unable to write data.");
641
642 println!("{}", data);
643}
644
645pub fn find_and_log_magics() {
647 let file = std::fs::File::create("magics.txt").expect("Error: Unable to create magics file.");
648
649 let mut bw = BufWriter::new(file);
650
651 for sq in 0..BOARD_AREA {
652 log_find_magic_and_shift(&mut bw, sq, magic_attack(sq, ROOK_ATTACK[sq]), "rook");
653
654 log_find_magic_and_shift(&mut bw, sq, magic_attack(sq, BISHOP_ATTACK[sq]), "bishop");
655 }
656}
657
658pub struct MagicInfo {
660 pub sq: Square,
661 pub magic: u64,
662 pub shift: usize,
663}
664
665pub fn total_magic_space(magics: [MagicInfo; BOARD_AREA]) -> usize {
667 let mut total = 0;
668 for i in 0..BOARD_AREA {
669 let mi = &magics[i];
670 total += 1 << mi.shift;
671 }
672 total
673}
674
675pub fn create_magic_lookup_table<T: DeltaBuffer>(
677 mis: &[MagicInfo; BOARD_AREA],
678 at: &AttackTable,
679 deltas: &T,
680) -> Vec<Vec<Bitboard>> {
681 let mut table = vec![vec![0; 0]; BOARD_AREA];
682 for sq in 0..BOARD_AREA {
683 let mobility = at[sq];
684 let variations = mobility.variation_count();
685
686 let mut mask: usize = 0;
687
688 let sq = mis[sq].sq; let magic = mis[sq].magic;
691 let shift = mis[sq].shift;
692
693 table[sq] = vec![0; 1 << shift];
694
695 loop {
696 if mask < variations {
697 let occup = translate_mask_to_occupancy(mask, mobility);
698 let index = mobility_index(occup, magic, shift);
699
700 table[sq][index] = sliding_attack(sq, deltas, occup);
701
702 mask += 1;
703 } else {
704 break;
705 }
706 }
707 }
708 table
709}
710
711pub static MAGIC_LOOKUP_BISHOP: Lazy<Vec<Vec<Bitboard>>> =
713 Lazy::new(|| create_magic_lookup_table(&BISHOP_MAGICS, &BISHOP_MAGIC_ATTACK, &BISHOP_DELTAS));
714
715pub static MAGIC_LOOKUP_ROOK: Lazy<Vec<Vec<Bitboard>>> =
717 Lazy::new(|| create_magic_lookup_table(&ROOK_MAGICS, &ROOK_MAGIC_ATTACK, &ROOK_DELTAS));
718
719#[derive(Copy, Clone)]
721pub enum MoveGenMode {
722 Violent,
723 Quiet,
724 All,
725}
726
727pub fn get_sliding_mobility(
729 gen_mode: MoveGenMode,
730 sq: Square,
731 occup_us: Bitboard,
732 occup_them: Bitboard,
733 mis: &[MagicInfo; BOARD_AREA],
734 at: &AttackTable,
735 lookup_table: &Vec<Vec<Bitboard>>,
736) -> Bitboard {
737 let magic = mis[sq].magic;
738 let shift = mis[sq].shift;
739 let magic_occup = (occup_us | occup_them) & at[sq];
740 let index = mobility_index(magic_occup, magic, shift);
741 match gen_mode {
742 MoveGenMode::All => lookup_table[sq][index] & !occup_us,
743 MoveGenMode::Violent => lookup_table[sq][index] & occup_them,
744 MoveGenMode::Quiet => lookup_table[sq][index] & !(occup_us | occup_them),
745 }
746}
747
748pub fn bishop_mobility(
750 sq: Square,
751 gen_mode: MoveGenMode,
752 occup_us: Bitboard,
753 occup_them: Bitboard,
754) -> Bitboard {
755 get_sliding_mobility(
756 gen_mode,
757 sq,
758 occup_us,
759 occup_them,
760 &BISHOP_MAGICS,
761 &BISHOP_MAGIC_ATTACK,
762 &MAGIC_LOOKUP_BISHOP,
763 )
764}
765
766pub fn rook_mobility(
768 sq: Square,
769 gen_mode: MoveGenMode,
770 occup_us: Bitboard,
771 occup_them: Bitboard,
772) -> Bitboard {
773 get_sliding_mobility(
774 gen_mode,
775 sq,
776 occup_us,
777 occup_them,
778 &ROOK_MAGICS,
779 &ROOK_MAGIC_ATTACK,
780 &MAGIC_LOOKUP_ROOK,
781 )
782}
783
784pub fn jailer_mobility(
786 sq: Square,
787 gen_mode: MoveGenMode,
788 occup_us: Bitboard,
789 occup_them: Bitboard,
790) -> Bitboard {
791 rook_mobility(sq, gen_mode, occup_us, occup_them) & (!occup_them)
792}
793
794pub fn queen_mobility(
796 sq: Square,
797 gen_mode: MoveGenMode,
798 occup_us: Bitboard,
799 occup_them: Bitboard,
800) -> Bitboard {
801 bishop_mobility(sq, gen_mode, occup_us, occup_them)
802 | rook_mobility(sq, gen_mode, occup_us, occup_them)
803}
804
805pub fn lancer_mobility(
807 sq: Square,
808 gen_mode: MoveGenMode,
809 occup_us: Bitboard,
810 occup_them: Bitboard,
811 lancer_mask: Bitboard,
812) -> Bitboard {
813 queen_mobility(sq, gen_mode, 0, occup_them) & lancer_mask & (!occup_us)
814}
815
816pub fn get_jump_mobility(
818 gen_mode: MoveGenMode,
819 occup_us: Bitboard,
820 occup_them: Bitboard,
821 attack: Bitboard,
822) -> Bitboard {
823 match gen_mode {
824 MoveGenMode::All => attack & !occup_us,
825 MoveGenMode::Violent => attack & occup_them,
826 MoveGenMode::Quiet => attack & !(occup_us | occup_them),
827 }
828}
829
830pub fn knight_mobility(
832 sq: Square,
833 gen_mode: MoveGenMode,
834 occup_us: Bitboard,
835 occup_them: Bitboard,
836) -> Bitboard {
837 get_jump_mobility(gen_mode, occup_us, occup_them, KNIGHT_ATTACK[sq])
838}
839
840pub fn king_mobility(
842 sq: Square,
843 gen_mode: MoveGenMode,
844 occup_us: Bitboard,
845 occup_them: Bitboard,
846) -> Bitboard {
847 get_jump_mobility(gen_mode, occup_us, occup_them, KING_ATTACK[sq])
848}