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
11pub 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
20pub 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
35pub 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 let bb = Bitboard::EMPTY
67 .with_move(0, 0, 0) .with_move(1, 1, 1) .with_move(0, 2, 2) .with_move(1, 3, 3); assert!(has_winning_line(&bb));
72 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}