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
use super::{Board, game::Game};

#[derive(Debug, Clone)]
pub struct ZobristHashTable<const T: usize> {
    pub table: Vec<u64>,
    pub squares: u16,
    pub teams: u16,
    pub pieces: u16,
    pub base_len: usize,
    pub extra_len: usize
}

impl<const T: usize> ZobristHashTable<T> {
    fn get_moving_team_index(&self, moving_team: u16) -> usize {
        moving_team as usize
    }

    fn get_gap_index(&self, moving_team: u16, position: u16) -> usize {
        (moving_team as usize) + position as usize
    }

    fn get_first_move_index(&self, moving_team: u16, position: u16) -> usize {
        (moving_team as usize) + (position + (self.squares)) as usize
    }

    fn get_piece_index(&self, moving_team: u16, position: u16, piece_type: u16, team: u16) -> usize {
        (moving_team as usize) + (position + (self.squares * (1 + (piece_type + (self.pieces * team))))) as usize
    }

    pub fn compute(&self, board: &Board<T>) -> u64 {
        let mut hash = 0;
        let moving_team = board.state.moving_team;

        hash ^= self.table[self.get_moving_team_index(moving_team)];

        for gap_pos in board.state.gaps.iter_set_bits(board.state.squares) {
            hash ^= self.table[self.get_gap_index(moving_team, gap_pos)];
        }

        for first_move_pos in board.state.first_move.iter_set_bits(board.state.squares) {
            hash ^= self.table[self.get_first_move_index(moving_team, first_move_pos)];
        }

        for piece_type in 0..self.pieces {
            for team in 0..self.teams {
                let piece_team_board = board.state.pieces[piece_type as usize] & board.state.teams[team as usize];
                for piece_pos in piece_team_board.iter_set_bits(board.state.squares) {
                    hash ^= self.table[self.get_piece_index(moving_team, piece_pos, piece_type, team)];
                }
            }
        }       

        hash
    }

    pub fn generate(squares: u16, teams: u16, pieces: u16, extra_hashes: usize, get_random: impl Fn() -> u64) -> ZobristHashTable<T> {
        let base_len = (
            (teams - 1) + (squares) + (squares * (1 + ((pieces - 1) + (pieces * (teams - 1))))) + 1
        ) as usize;
        let hashes = base_len + (extra_hashes as usize);
        let mut zobrist = ZobristHashTable {
            table: vec![ 0; hashes ],
            squares: squares,
            pieces,
            teams: teams,
            base_len,
            extra_len: extra_hashes
        };

        for hash in 0..hashes {
            zobrist.table[hash] = get_random();
        }

        zobrist
    }
}

#[cfg(test)]
mod tests {
    use fastrand::u64;

    use crate::games::chess::Chess;

    use super::ZobristHashTable;

    #[test]
    fn chess_zobrist_test() {
        let chess = Chess::create();
        
        let startpos = chess.default();
        let kiwipete = chess.from_fen("rnbqkbnr/pppppppp/8/8/8/4P3/PPPP1PPP/RNBQKBNR w KQkq - 0 1");

        assert_ne!(chess.zobrist.compute(&startpos), chess.zobrist.compute(&kiwipete), "Waaa?");
    }
}