File size: 3,728 Bytes
5f5fb32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use pyo3::prelude::*;

#[pyclass]
#[derive(Clone)]
pub struct BitBoard(pub u64, pub u64);

#[pymethods]
impl BitBoard {
    #[new]
    pub fn default() -> Self {
        Self(0x0000_0008_1000_0000, 0x0000_0010_0800_0000)
    }

    /// Returns bitboards of `self`.
    #[must_use]
    pub const fn get(&self) -> [u64; 2] {
        [self.0, self.1]
    }

    /// Passes without playing.
    pub fn pass_move(&self) -> Self {
        Self(self.1, self.0)
    }

    /// Returns the `Game` state after playing the given move.
    /// Panics when `place` is larger than 63.
    #[must_use]
    pub fn make_move(&self, place: usize) -> Option<Self> {
        let diff = (0..4)
            .map(|i| unsafe {
                use crate::consts::*;
                use bitintr::*;
                //maybe we should change INDEX to LUT of raw pointers
                //and save some instructions
                //not sure if that saves some CPU cycles
                //wait before bench
                u64::from(*RESULT.get_unchecked(
                    //https://github.com/rust-lang/rust/issues/51713
                    INDEX[place][i] as usize * 32
                        + self.0.pext(MASK[place][i][0]) as usize * 64
                        + self.1.pext(MASK[place][i][1]) as usize,
                ))
                .pdep(MASK[place][i][1])
            })
            .fold(0, core::ops::BitOr::bitor);
        //for arm processors, we should use brev and hyperbola quintessence, as arm has rbit instruction
        //https://www.chessprogramming.org/Hyperbola_Quintessence
        //or maybe magic bitboards
        //even RISC-V has bdep bext
        if diff == 0 || ((self.0 | self.1) & 1 << place != 0) {
            None
        } else {
            Some(Self(self.1 ^ diff, self.0 ^ diff ^ 1 << place))
        }
    }

    /// Returns the bitboard representation of available moves.
    #[must_use]
    pub fn available_moves(&self) -> u64 {
        //the below should compile to AVX2 instructions(256bit)
        [-9, -8, -7, -1, 1, 7, 8, 9]
            //we use iter_mut because of the noalias bug
            .iter_mut()
            .map(|i| self.gen(*i))
            .fold(0, core::ops::BitOr::bitor)
            & !self.0
            & !self.1
    }

    pub fn available_moves_list(&self) -> Vec<usize> {
        let mask = self.available_moves();
        (0..64).filter(|i| mask >> i & 1 == 1).collect()
    }

    #[must_use]
    fn gen(&self, dir: isize) -> u64 {
        //rotate might be faster on AVX-512
        fn shift(x: u64, y: isize) -> u64 {
            if y > 0 {
                x >> y
            } else {
                x << -y
            }
        }
        let x = self.0;
        //if we change above to rotate, we should also modify the following
        let y = self.1
            & match dir.rem_euclid(8) {
                0 => !0,
                1 | 7 => 0x7E7E_7E7E_7E7E_7E7E,
                _ => unreachable!(),
            };
        let d = dir;
        let x = x | y & shift(x, d);
        let y = y & shift(y, d);
        let d = d * 2;
        let x = x | y & shift(x, d);
        let y = y & shift(y, d);
        let d = d * 2;
        let x = x | y & shift(x, d);
        shift(x ^ self.0, dir)
    }

    pub fn count(&self) -> (i32, i32) {
        (self.0.count_ones() as i32, self.1.count_ones() as i32)
    }
}

#[cfg(test)]
mod tests {
    use super::BitBoard;

    #[test]
    fn default_test() {
        let x = BitBoard::default();
        assert_eq!(x.available_moves(), 0x0000_1020_0408_0000);

        let x = x.make_move(44).unwrap();
        assert_eq!(x.get(), [0x0000_0000_0800_0000, 0x0000_1018_1000_0000]);
        assert_eq!(x.count(), (1, 4));
    }
}