1use crate::attacks;
2use crate::board::{
3 BK_CASTLE, BQ_CASTLE, Board, StateInfo, WK_CASTLE, WQ_CASTLE, is_move_trivially_legal,
4};
5use crate::types::*;
6
7const PROMOTION_PIECES: [PieceType; 4] = [
8 PieceType::Queen,
9 PieceType::Rook,
10 PieceType::Bishop,
11 PieceType::Knight,
12];
13
14pub fn generate_pseudo_legal(board: &Board, moves: &mut MoveList) {
21 let us = board.side_to_move();
22 let them = us.flip();
23 let occupied = board.occupied();
24 let target = !occupied | board.pieces_color(them);
25
26 let mut p = board.pieces_color_pt(us, PieceType::Pawn);
27 while !p.is_empty() {
28 let from = p.pop_lsb();
29 generate_pawn_moves_for(board, us, them, from, moves);
30 }
31
32 let mut knights = board.pieces_color_pt(us, PieceType::Knight);
33 while !knights.is_empty() {
34 let from = knights.pop_lsb();
35 let attacks = attacks::knight_attacks(from) & target;
36 let mut a = attacks;
37 while !a.is_empty() {
38 let to = a.pop_lsb();
39 moves.push(Move::make_move(from, to));
40 }
41 }
42
43 let mut bishops = board.pieces_color_pt(us, PieceType::Bishop);
44 while !bishops.is_empty() {
45 let from = bishops.pop_lsb();
46 let attacks = attacks::bishop_attacks(from, occupied) & target;
47 let mut a = attacks;
48 while !a.is_empty() {
49 let to = a.pop_lsb();
50 moves.push(Move::make_move(from, to));
51 }
52 }
53
54 let mut rooks = board.pieces_color_pt(us, PieceType::Rook);
55 while !rooks.is_empty() {
56 let from = rooks.pop_lsb();
57 let attacks = attacks::rook_attacks(from, occupied) & target;
58 let mut a = attacks;
59 while !a.is_empty() {
60 let to = a.pop_lsb();
61 moves.push(Move::make_move(from, to));
62 }
63 }
64
65 let mut queens = board.pieces_color_pt(us, PieceType::Queen);
66 while !queens.is_empty() {
67 let from = queens.pop_lsb();
68 let attacks = attacks::queen_attacks(from, occupied) & target;
69 let mut a = attacks;
70 while !a.is_empty() {
71 let to = a.pop_lsb();
72 moves.push(Move::make_move(from, to));
73 }
74 }
75
76 let mut commoners = board.pieces_color_pt(us, PieceType::Commoner);
77 while !commoners.is_empty() {
78 let from = commoners.pop_lsb();
79 let attacks = attacks::king_attacks(from) & target;
80 let mut a = attacks;
81 while !a.is_empty() {
82 let to = a.pop_lsb();
83 moves.push(Move::make_move(from, to));
84 }
85 }
86
87 generate_castling(board, us, moves);
89}
90
91fn generate_pawn_moves_for(
92 board: &Board,
93 us: Color,
94 them: Color,
95 from: Square,
96 moves: &mut MoveList,
97) {
98 let from_rank = rank_of(from);
99 let from_f = file_of(from) as i8;
100
101 let (push_dir, push_double, start_rank, promo_rank) = match us {
102 Color::White => (8i8, 16i8, Rank::R2, Rank::R8),
103 Color::Black => (-8i8, -16i8, Rank::R7, Rank::R1),
104 };
105
106 let from_idx = from as i8;
107
108 let to_idx = from_idx + push_dir;
110 let to_sq = Square::from_index(to_idx);
111 if to_sq != Square::NONE && board.empty(to_sq) {
112 if rank_of(to_sq) == promo_rank {
113 for &pt in &PROMOTION_PIECES {
114 moves.push(Move::make_promotion(from, to_sq, pt));
115 }
116 } else {
117 moves.push(Move::make_move(from, to_sq));
118 }
119
120 if from_rank == start_rank {
122 let to_idx2 = from_idx + push_double;
123 let to_sq2 = Square::from_index(to_idx2);
124 if to_sq2 != Square::NONE && board.empty(to_sq2) {
125 moves.push(Move::make_move(from, to_sq2));
126 }
127 }
128 }
129
130 for df in &[-1i8, 1i8] {
132 let target_f = from_f + df;
133 if !(0..=7).contains(&target_f) {
134 continue;
135 }
136 let to_idx = from_idx + push_dir + df;
137 let to_sq = Square::from_index(to_idx);
138 if to_sq == Square::NONE {
139 continue;
140 }
141 if file_of(to_sq) as i8 != target_f {
143 continue;
144 }
145 if board.pieces_color(them) & Bitboard::square_bb(to_sq) != Bitboard::EMPTY {
146 if rank_of(to_sq) == promo_rank {
147 for &pt in &PROMOTION_PIECES {
148 moves.push(Move::make_promotion(from, to_sq, pt));
149 }
150 } else {
151 moves.push(Move::make_move(from, to_sq));
152 }
153 }
154 }
155
156 if let Some(ep_sq) = board.ep_square() {
158 let ep_f = file_of(ep_sq) as i8;
159 if ep_f == from_f - 1 || ep_f == from_f + 1 {
160 let df = ep_f - from_f;
161 let to_idx = from_idx + push_dir + df;
162 let to_sq = Square::from_index(to_idx);
163 if to_sq == ep_sq {
164 moves.push(Move::make_enpassant(from, ep_sq));
165 }
166 }
167 }
168}
169
170fn generate_castling(board: &Board, us: Color, moves: &mut MoveList) {
171 let (
172 king_side_right,
173 queen_side_right,
174 king_sq,
175 king_side_rook_sq,
176 queen_side_rook_sq,
177 king_side_squares,
178 queen_side_squares,
179 ) = match us {
180 Color::White => (
181 WK_CASTLE,
182 WQ_CASTLE,
183 Square::E1,
184 Square::H1,
185 Square::A1,
186 [Square::F1, Square::G1],
187 [Square::B1, Square::C1, Square::D1],
188 ),
189 Color::Black => (
190 BK_CASTLE,
191 BQ_CASTLE,
192 Square::E8,
193 Square::H8,
194 Square::A8,
195 [Square::F8, Square::G8],
196 [Square::B8, Square::C8, Square::D8],
197 ),
198 };
199
200 if board.castling_rights() & king_side_right != 0 {
202 let mut clear = true;
203 for &sq in &king_side_squares {
204 if !board.empty(sq) {
205 clear = false;
206 break;
207 }
208 }
209 if clear {
210 moves.push(Move::make_castling(king_sq, king_side_rook_sq));
211 }
212 }
213
214 if board.castling_rights() & queen_side_right != 0 {
216 let mut clear = true;
217 for &sq in &queen_side_squares {
218 if !board.empty(sq) {
219 clear = false;
220 break;
221 }
222 }
223 if clear {
224 moves.push(Move::make_castling(king_sq, queen_side_rook_sq));
225 }
226 }
227}
228
229pub fn generate_legal(board: &Board, moves: &mut MoveList) {
234 let mut state = StateInfo::new();
235 board.populate_state(&mut state);
236 generate_pseudo_legal(board, moves);
237
238 let orig_len = moves.len();
240 if orig_len == 0 {
241 return;
242 }
243
244 let new_len = {
245 let ms = moves.as_mut_slice();
246 let mut write_idx = 0;
247 for read_idx in 0..orig_len {
248 let m = ms[read_idx];
249 if is_move_trivially_legal(board, m, &state) || board.legal(m, &state) {
250 ms[write_idx] = m;
251 write_idx += 1;
252 }
253 }
254 write_idx
255 };
256 moves.set_len(new_len);
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262 use crate::board::Board;
263
264 #[test]
265 fn test_starting_position_move_count() {
266 let board = Board::new();
267 let mut moves = MoveList::new();
268 generate_pseudo_legal(&board, &mut moves);
269 let mut legal_moves = MoveList::new();
270 generate_legal(&board, &mut legal_moves);
271 assert_eq!(legal_moves.len(), 20);
273 }
274
275 #[test]
276 fn test_knight_moves_start() {
277 let board = Board::new();
278 let mut moves = MoveList::new();
279 generate_pseudo_legal(&board, &mut moves);
280 let knight_moves: Vec<Move> = moves
281 .as_slice()
282 .iter()
283 .filter(|&&m| {
284 let from = m.from_sq();
285 from == Square::B1 || from == Square::G1
286 })
287 .copied()
288 .collect();
289 assert_eq!(knight_moves.len(), 4);
291 }
292}