1use crate::bitboard::Bitboard;
2use crate::constants::{MAX_PIECES_PER_SHAPE, NUM_SHAPES, WIN_MASKS};
3use crate::game::current_player;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6pub struct Move {
7 pub player: u8,
8 pub shape: u8,
9 pub position: u8,
10}
11
12impl Move {
13 pub fn new(player: u8, shape: u8, position: u8) -> Self {
14 debug_assert!(player <= 1);
15 debug_assert!(shape < 4);
16 debug_assert!(position < 16);
17 Self {
18 player,
19 shape,
20 position,
21 }
22 }
23}
24
25pub fn is_move_legal(bb: &Bitboard, player: u8, shape: u8, position: u8) -> bool {
27 if bb.is_position_occupied(position) {
28 return false;
29 }
30 if bb.shape_piece_count(player, shape) >= MAX_PIECES_PER_SHAPE as u32 {
31 return false;
32 }
33 let opponent = 1 - player;
34 let opp_bits = bb.planes[Bitboard::plane_index(opponent, shape)];
35 let pos_mask = 1u16 << position;
36 for &wm in &WIN_MASKS {
37 if (pos_mask & wm != 0) && (opp_bits & wm != 0) {
38 return false;
39 }
40 }
41 true
42}
43
44pub fn generate_legal_moves(bb: &Bitboard) -> Vec<Move> {
46 let player = match current_player(bb) {
47 Some(p) => p,
48 None => return Vec::new(),
49 };
50 let occupied = bb.occupied();
51 let mut moves = Vec::new();
52
53 for shape in 0..NUM_SHAPES as u8 {
54 if bb.shape_piece_count(player, shape) >= MAX_PIECES_PER_SHAPE as u32 {
55 continue;
56 }
57 let opp_bits = bb.planes[Bitboard::plane_index(1 - player, shape)];
58 for pos in 0..16u8 {
59 if occupied & (1u16 << pos) != 0 {
60 continue;
61 }
62 let pos_mask = 1u16 << pos;
63 let blocked = WIN_MASKS
64 .iter()
65 .any(|&wm| (pos_mask & wm != 0) && (opp_bits & wm != 0));
66 if !blocked {
67 moves.push(Move::new(player, shape, pos));
68 }
69 }
70 }
71 moves
72}
73
74#[inline]
76pub fn apply_move(bb: &Bitboard, mv: &Move) -> Bitboard {
77 bb.with_move(mv.player, mv.shape, mv.position)
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn initial_legal_moves() {
86 let moves = generate_legal_moves(&Bitboard::EMPTY);
87 assert_eq!(moves.len(), 64);
89 assert!(moves.iter().all(|m| m.player == 0));
90 }
91
92 #[test]
93 fn occupied_position_blocked() {
94 let bb = Bitboard::EMPTY.with_move(0, 0, 0);
95 assert!(!is_move_legal(&bb, 1, 1, 0));
96 }
97
98 #[test]
99 fn opponent_shape_on_line_blocked() {
100 let bb = Bitboard::EMPTY.with_move(0, 0, 0);
102 assert!(!is_move_legal(&bb, 1, 0, 1)); assert!(!is_move_legal(&bb, 1, 0, 4)); assert!(is_move_legal(&bb, 1, 0, 10));
108 }
109
110 #[test]
111 fn max_pieces_per_shape() {
112 let bb = Bitboard::EMPTY
113 .with_move(0, 0, 0)
114 .with_move(1, 1, 5)
115 .with_move(0, 0, 10);
116 assert!(!is_move_legal(&bb, 0, 0, 15));
118 assert!(is_move_legal(&bb, 0, 1, 15));
120 }
121}