Skip to main content

quantik_core/
game.rs

1use crate::bitboard::Bitboard;
2use crate::constants::{NUM_SHAPES, WIN_MASKS};
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum WinStatus {
6    NoWin,
7    Player0Wins,
8    Player1Wins,
9}
10
11/// Check whether any win line contains all 4 distinct shapes (regardless of color).
12pub fn has_winning_line(bb: &Bitboard) -> bool {
13    let shape_unions: [u16; NUM_SHAPES] = std::array::from_fn(|s| bb.planes[s] | bb.planes[s + 4]);
14
15    WIN_MASKS
16        .iter()
17        .any(|&mask| shape_unions.iter().all(|&su| su & mask != 0))
18}
19
20/// Determine winner.  The last player to move (the one with more pieces on the
21/// board) is credited with the win.
22pub fn check_winner(bb: &Bitboard) -> WinStatus {
23    if !has_winning_line(bb) {
24        return WinStatus::NoWin;
25    }
26    let p0 = bb.player_piece_count(0);
27    let p1 = bb.player_piece_count(1);
28    if p0 > p1 {
29        WinStatus::Player0Wins
30    } else {
31        WinStatus::Player1Wins
32    }
33}
34
35/// Whose turn is it?  Player 0 moves first; after that they alternate.
36/// Returns `None` if the piece counts are inconsistent.
37pub fn current_player(bb: &Bitboard) -> Option<u8> {
38    let p0 = bb.player_piece_count(0);
39    let p1 = bb.player_piece_count(1);
40    if p0 == p1 {
41        Some(0)
42    } else if p0 == p1 + 1 {
43        Some(1)
44    } else {
45        None
46    }
47}
48
49pub fn is_game_over(bb: &Bitboard) -> bool {
50    check_winner(bb) != WinStatus::NoWin
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn empty_board_no_win() {
59        assert_eq!(check_winner(&Bitboard::EMPTY), WinStatus::NoWin);
60        assert_eq!(current_player(&Bitboard::EMPTY), Some(0));
61    }
62
63    #[test]
64    fn row_0_win() {
65        // Place A(p0) at 0, B(p1) at 1, C(p0) at 2, D(p1) at 3
66        let bb = Bitboard::EMPTY
67            .with_move(0, 0, 0) // A at pos 0
68            .with_move(1, 1, 1) // b at pos 1
69            .with_move(0, 2, 2) // C at pos 2
70            .with_move(1, 3, 3); // d at pos 3
71        assert!(has_winning_line(&bb));
72        // p0 has 2, p1 has 2 → p1 wins (equal means last mover is p1 when 4 total)
73        // Actually: p0=2, p1=2 → p0==p1 so winner is p1 per check_winner logic
74        assert_eq!(check_winner(&bb), WinStatus::Player1Wins);
75    }
76
77    #[test]
78    fn current_player_alternates() {
79        let bb = Bitboard::EMPTY;
80        assert_eq!(current_player(&bb), Some(0));
81        let bb = bb.with_move(0, 0, 0);
82        assert_eq!(current_player(&bb), Some(1));
83        let bb = bb.with_move(1, 1, 5);
84        assert_eq!(current_player(&bb), Some(0));
85    }
86}