Add Pseudo Move Generator (#12)
All checks were successful
Python tests (make) / test (push) Successful in 11s

work on #7

Reviewed-on: #12
Co-authored-by: Josh <josh@joshuaschuett.com>
Co-committed-by: Josh <josh@joshuaschuett.com>
This commit is contained in:
2025-08-18 00:28:00 +00:00
committed by Josh
parent c02ec7875d
commit 1db90828ac
9 changed files with 1140 additions and 162 deletions

View File

@@ -25,6 +25,39 @@
#define GET_BIT(bb, sq) ((bb) & (1ULL << (sq)))
#define TOGGLE_BIT(bb, sq) ((bb) ^= (1ULL << (sq)))
// King castling helpers.
#define BB(sq) (1ULL << (sq))
// a1 = 0, h8 = 63 (rank*8 + file)
enum Square {
A1,B1,C1,D1,E1,F1,G1,H1,
A2,B2,C2,D2,E2,F2,G2,H2,
A3,B3,C3,D3,E3,F3,G3,H3,
A4,B4,C4,D4,E4,F4,G4,H4,
A5,B5,C5,D5,E5,F5,G5,H5,
A6,B6,C6,D6,E6,F6,G6,H6,
A7,B7,C7,D7,E7,F7,G7,H7,
A8,B8,C8,D8,E8,F8,G8,H8
};
#define WK_EMPTY_MASK (BB(F1) | BB(G1))
#define WQ_EMPTY_MASK (BB(B1) | BB(C1) | BB(D1))
#define BK_EMPTY_MASK (BB(F8) | BB(G8))
#define BQ_EMPTY_MASK (BB(B8) | BB(C8) | BB(D8))
// squares king is on / passes / lands (use for attacked-squares test later)
#define WK_THRU_MASK (BB(E1) | BB(F1) | BB(G1))
#define WQ_THRU_MASK (BB(E1) | BB(D1) | BB(C1))
#define BK_THRU_MASK (BB(E8) | BB(F8) | BB(G8))
#define BQ_THRU_MASK (BB(E8) | BB(D8) | BB(C8))
// king target squares
#define WK_TO G1
#define WQ_TO C1
#define BK_TO G8
#define BQ_TO C8
enum Color { WHITE = 0, BLACK = 1, BOTH = 2 };
enum Piece {
P, N, B, R, Q, K, // 0..5 white
@@ -38,6 +71,23 @@ enum Castling {
CASTLE_BQ = 1 << 3
};
enum MoveFlags {
MF_NONE = 0,
MF_CAPTURE = 1 << 0,
MF_PROMO = 1 << 1,
MF_ENPASSANT = 1 << 2,
MF_CASTLE = 1 << 3,
MF_DOUBLE_PUSH = 1 << 4,
};
struct Move {
uint16_t from;
uint16_t to;
uint8_t piece;
uint8_t promo;
uint8_t flags;
};
struct Board {
uint64_t pieces[12]; // Each set of pieces get a bitboard for each player.
uint64_t occ[3]; // Color occupancy bitboards.
@@ -55,4 +105,13 @@ struct Board {
void create_knight_attack_cache();
void create_pawn_attack_cache();
void create_king_attack_cache();
void print_board();
void print_board();
void gen_white_pawn_quiet_pushes(const struct Board *board, struct Move *moves, int *count);
void gen_black_pawn_quiet_pushes(const struct Board *board, struct Move *moves, int *count);
void gen_white_pawn_push_promotions(const struct Board *board, struct Move *moves, int *count);
void gen_black_pawn_push_promotions(const struct Board *board, struct Move *moves, int *count);
void gen_white_pawn_captures(const struct Board *board, struct Move *moves, int *count);
void gen_black_pawn_captures(const struct Board *board, struct Move *moves, int *count);
void gen_white_pawn_capture_promotions(const struct Board *board, struct Move *moves, int *count);
void gen_black_pawn_capture_promotions(const struct Board *board, struct Move *moves, int *count);
int gen_pseudo_moves(const struct Board *board, struct Move *out, bool captures_only);

View File

@@ -61,14 +61,14 @@ void create_king_attack_cache(void) {
}
}
static int first_set_index(uint64_t bb) {
int first_set_index(uint64_t bb) {
for (int i = 0; i < 64; ++i) {
if ((bb >> i) & 1ULL) return i;
}
return -1;
}
static int pop_lsb_index(uint64_t *bb) {
int pop_lsb_index(uint64_t *bb) {
if (*bb == 0) return -1;
int idx = first_set_index(*bb);
// Clears bit.
@@ -76,8 +76,463 @@ static int pop_lsb_index(uint64_t *bb) {
return idx;
}
void print_board(const struct Board *b) {
static const char PIECE_CH[12] = {
uint64_t ray_attacks(int start_square, int delta_file, int delta_rank, uint64_t occupied)
{
uint64_t attacks = 0;
int start_file = start_square % 8;
int start_rank = start_square / 8;
int next_file = start_file + delta_file;
int next_rank = start_rank + delta_rank;
while (next_file >= 0 && next_file < 8 && next_rank >= 0 && next_rank < 8) {
int next_square = next_rank * 8 + next_file;
uint64_t next_bit = 1ULL << next_square;
// can attack this square
attacks |= next_bit;
// blocker: include it, then stop
if (occupied & next_bit)
break;
next_file += delta_file;
next_rank += delta_rank;
}
return attacks;
}
uint64_t rook_attacks(int square, uint64_t occupied) {
return ray_attacks(square, +1, 0, occupied) // east
| ray_attacks(square, -1, 0, occupied) // west
| ray_attacks(square, 0, +1, occupied) // north
| ray_attacks(square, 0, -1, occupied); // south
}
uint64_t bishop_attacks(int square, uint64_t occupied) {
return ray_attacks(square, +1, +1, occupied) // NE
| ray_attacks(square, -1, +1, occupied) // NW
| ray_attacks(square, +1, -1, occupied) // SE
| ray_attacks(square, -1, -1, occupied); // SW
}
uint64_t queen_attacks(int square, uint64_t occupied) {
// Simply combine both types of attacks
return rook_attacks(square, occupied) | bishop_attacks(square, occupied);
}
void push_move(struct Move *out, int *count, int from, int to, uint8_t piece, uint8_t promo, uint8_t flags)
{
out[*count] = (struct Move){
.from = (uint16_t)from,
.to = (uint16_t)to,
.piece = piece,
.promo = promo, // 0 for non-promo;
.flags = flags
};
(*count)++;
}
/**
PAWN struct Movement Psuedo Moves
Separate the white and black pawn logic. Although the logic is similar,
by separating the logic, we can isolate the different cases and have clearer parent functions.
These pieces are also the only ones that move in opposite ways, meaning we only need
to separate out seemingly related code for this case. Additionally, pawns have the
most complicated movement patterns and behaviors in the game. Separating out the logic should
hopefully make debugging easier.
*/
void gen_white_pawn_quiet_pushes(const struct Board *board, struct Move *out, int *count) {
const uint64_t occ = board->occ[BOTH];
const uint64_t empty = ~occ;
const uint64_t pawns = board->pieces[P];
// One-step pushes: shift pawns up by 8 onto empty squares
uint64_t one_step = (pawns << 8) & empty;
// Exclude promotions here (dest on rank 8) — handled by a separate promo-push function
uint64_t one_step_no_promo = one_step & ~RANK_8;
// Two-step pushes: those one-step pawns that landed on rank 3 can go one more if still empty
// (means they originally stood on rank 2)
uint64_t two_step = ((one_step & RANK_3) << 8) & empty;
uint64_t bb = one_step_no_promo;
while (bb) {
int to = pop_lsb_index(&bb);
int from = to - 8;
push_move(out, count, from, to, P, 0, MF_NONE);
}
bb = two_step;
while (bb) {
int to = pop_lsb_index(&bb);
int from = to - 16;
push_move(out, count, from, to, P, 0, MF_DOUBLE_PUSH);
}
}
void gen_black_pawn_quiet_pushes(const struct Board *board, struct Move *out, int *count) {
const uint64_t occ = board->occ[BOTH];
const uint64_t empty = ~occ;
const uint64_t pawns = board->pieces[p];
// One-step pushes: shift pawns down by 8 onto empty squares
uint64_t one_step = (pawns >> 8) & empty;
// Exclude promotions here (dest on rank 1) — handle in a promo-push function
uint64_t one_step_no_promo = one_step & ~RANK_1;
uint64_t two_step = ((one_step & RANK_6) >> 8) & empty;
uint64_t bb = one_step_no_promo;
while (bb) {
int to = pop_lsb_index(&bb);
int from = to + 8;
push_move(out, count,from, to, p, 0, MF_NONE);
}
bb = two_step;
while (bb) {
int to = pop_lsb_index(&bb);
int from = to + 16;
push_move(out, count,from, to, p, 0, MF_DOUBLE_PUSH);
}
}
// We will only allow pawns to promote to queen. Technically, they should be allowed
// to promote to any of the following: Rook, Bishop, Knight, Queen.
void gen_white_pawn_push_promotions(const struct Board *board, struct Move *out, int *count) {
const uint64_t occ = board->occ[BOTH];
const uint64_t empty = ~occ;
const uint64_t pawns = board->pieces[P];
// destinations on rank 8 reachable by a single push
uint64_t promos = ((pawns << 8) & empty) & RANK_8;
while (promos) {
int to = pop_lsb_index(&promos);
int from = to - 8;
push_move(out, count, from, to, P, Q, MF_PROMO);
}
}
void gen_black_pawn_push_promotions(const struct Board *board, struct Move *out, int *count) {
const uint64_t occ = board->occ[BOTH];
const uint64_t empty = ~occ;
const uint64_t pawns = board->pieces[p];
// destinations on rank 1 reachable by a single push
uint64_t promos = ((pawns >> 8) & empty) & RANK_1;
while (promos) {
int to = pop_lsb_index(&promos);
int from = to + 8;
push_move(out, count, from, to, p, q, MF_PROMO);
}
}
void gen_white_pawn_capture_promotions(const struct Board *board, struct Move *out, int *count) {
const uint64_t opp = board->occ[BLACK];
// left capture (from white view): +7, mask off file A
uint64_t left = ((board->pieces[P] & ~FILE_A) << 7) & opp & RANK_8;
while (left) {
int to = pop_lsb_index(&left);
int from = to - 7;
push_move(out, count,from, to, P, Q, MF_CAPTURE | MF_PROMO);
}
// right capture: +9, mask off file H
uint64_t right = ((board->pieces[P] & ~FILE_H) << 9) & opp & RANK_8;
while (right) {
int to = pop_lsb_index(&right);
int from = to - 9;
push_move(out, count,from, to, P, Q, MF_CAPTURE | MF_PROMO);
}
}
void gen_black_pawn_capture_promotions(const struct Board *board, struct Move *out, int *count) {
const uint64_t opp = board->occ[WHITE];
// from black view, “left” is -7 (mask off file H before shifting)
uint64_t left = ((board->pieces[p] & ~FILE_H) >> 7) & opp & RANK_1;
while (left) {
int to = pop_lsb_index(&left);
int from = to + 7;
push_move(out, count,from, to, p, q, MF_CAPTURE | MF_PROMO);
}
// “right” is -9 (mask off file A)
uint64_t right = ((board->pieces[p] & ~FILE_A) >> 9) & opp & RANK_1;
while (right) {
int to = pop_lsb_index(&right);
int from = to + 9;
push_move(out, count,from, to, p, q, MF_CAPTURE | MF_PROMO);
}
}
void gen_white_pawn_captures(const struct Board *board, struct Move *out, int *count) {
const uint64_t pawns = board->pieces[P];
const uint64_t opp = board->occ[BLACK];
// Normal captures (exclude promotion rank)
uint64_t left_caps = ((pawns & ~FILE_A) << 7) & opp & ~RANK_8;
uint64_t right_caps = ((pawns & ~FILE_H) << 9) & opp & ~RANK_8;
// Emit normal captures
while (left_caps) {
int to = pop_lsb_index(&left_caps);
int from = to - 7;
push_move(out, count,from, to, P, 0, MF_CAPTURE);
}
while (right_caps) {
int to = pop_lsb_index(&right_caps);
int from = to - 9;
push_move(out, count,from, to, P, 0, MF_CAPTURE);
}
// En passant (destination is ep_square)
if (board->ep_square >= 0) {
uint64_t ep = 1ULL << board->ep_square;
// From-squares that can capture onto ep (reverse of +7/+9)
uint64_t ep_from = (((ep & ~FILE_H) >> 7) | ((ep & ~FILE_A) >> 9)) & pawns;
while (ep_from) {
int from = pop_lsb_index(&ep_from);
int to = board->ep_square;
// EP never promotes, so no promo piece; still a capture
push_move(out, count,from, to, P, 0, MF_CAPTURE | MF_ENPASSANT);
}
}
}
void gen_black_pawn_captures(const struct Board *board, struct Move *out, int *count) {
const uint64_t pawns = board->pieces[p];
const uint64_t opp = board->occ[WHITE];
// Normal captures (exclude promotion rank)
uint64_t left_caps = ((pawns & ~FILE_H) >> 7) & opp & ~RANK_1; // from black POV, "left" is -7
uint64_t right_caps = ((pawns & ~FILE_A) >> 9) & opp & ~RANK_1; // "right" is -9
while (left_caps) {
int to = pop_lsb_index(&left_caps);
int from = to + 7;
push_move(out, count,from, to, p, 0, MF_CAPTURE);
}
while (right_caps) {
int to = pop_lsb_index(&right_caps);
int from = to + 9;
push_move(out, count,from, to, p, 0, MF_CAPTURE);
}
// En passant
if (board->ep_square >= 0) {
uint64_t ep = 1ULL << board->ep_square;
// From-squares that can capture onto ep (reverse of -7/-9)
uint64_t ep_from = (((ep & ~FILE_A) << 7) | ((ep & ~FILE_H) << 9)) & pawns;
while (ep_from) {
int from = pop_lsb_index(&ep_from);
int to = board->ep_square;
push_move(out, count,from, to, p, 0, MF_CAPTURE | MF_ENPASSANT);
}
}
}
/**
Pieces that are not PAWNs
*/
void gen_knight_moves(const struct Board *board, struct Move *out, int *count, bool captures_only) {
enum Color side = board->side_to_move;
uint64_t own = board->occ[side];
uint64_t opp = board->occ[side ^ 1];
uint8_t pid = (side == WHITE) ? N : n;
uint64_t bb = (side == WHITE) ? board->pieces[N] : board->pieces[n];
while (bb) {
int from = pop_lsb_index(&bb);
uint64_t mask = KNIGHT_ATTACKS[from] & ~own;
uint64_t caps = mask & opp;
if (!captures_only) {
uint64_t quiet = mask & ~opp;
while (quiet) {
int to = pop_lsb_index(&quiet);
push_move(out, count, from, to, pid, 0, MF_NONE);
}
}
while (caps) {
int to = pop_lsb_index(&caps);
push_move(out, count, from, to, pid, 0, MF_CAPTURE);
}
}
}
void gen_bishop_moves(const struct Board *board, struct Move *out, int *count, bool captures_only) {
enum Color side = board->side_to_move;
uint64_t own = board->occ[side];
uint64_t opp = board->occ[side ^ 1];
uint64_t occ = board->occ[BOTH];
uint8_t pid = (side == WHITE) ? B : b;
uint64_t bb = (side == WHITE) ? board->pieces[B] : board->pieces[b];
while (bb) {
int from = pop_lsb_index(&bb);
uint64_t mask = bishop_attacks(from, occ) & ~own;
uint64_t caps = mask & opp;
if (!captures_only) {
uint64_t quiet = mask & ~opp;
while (quiet) {
int to = pop_lsb_index(&quiet);
push_move(out, count, from, to, pid, 0, MF_NONE);
}
}
while (caps) {
int to = pop_lsb_index(&caps);
push_move(out, count, from, to, pid, 0, MF_CAPTURE);
}
}
}
void gen_rook_moves(const struct Board *board, struct Move *out, int *count, bool captures_only) {
enum Color side = board->side_to_move;
uint64_t own = board->occ[side];
uint64_t opp = board->occ[side ^ 1];
uint64_t occ = board->occ[BOTH];
uint8_t pid = (side == WHITE) ? R : r;
uint64_t bb = (side == WHITE) ? board->pieces[R] : board->pieces[r];
while (bb) {
int from = pop_lsb_index(&bb);
uint64_t mask = rook_attacks(from, occ) & ~own;
uint64_t caps = mask & opp;
if (!captures_only) {
uint64_t quiet = mask & ~opp;
while (quiet) {
int to = pop_lsb_index(&quiet);
push_move(out, count,from, to, pid, 0, MF_NONE);
}
}
while (caps) {
int to = pop_lsb_index(&caps);
push_move(out, count,from, to, pid, 0, MF_CAPTURE);
}
}
}
void gen_queen_moves(const struct Board *board, struct Move *out, int *count, bool captures_only) {
enum Color side = board->side_to_move;
uint64_t own = board->occ[side];
uint64_t opp = board->occ[side ^ 1];
uint64_t occ = board->occ[BOTH];
uint8_t pid = (side == WHITE) ? Q : q;
uint64_t bb = (side == WHITE) ? board->pieces[Q] : board->pieces[q];
while (bb) {
int from = pop_lsb_index(&bb);
uint64_t mask = (rook_attacks(from, occ) | bishop_attacks(from, occ)) & ~own;
uint64_t caps = mask & opp;
if (!captures_only) {
uint64_t quiet = mask & ~opp;
while (quiet) {
int to = pop_lsb_index(&quiet);
push_move(out, count,from, to, pid, 0, MF_NONE);
}
}
while (caps) {
int to = pop_lsb_index(&caps);
push_move(out, count,from, to, pid, 0, MF_CAPTURE);
}
}
}
void gen_king_moves(const struct Board *board, struct Move *out, int *count, bool captures_only) {
enum Color side = board->side_to_move;
uint64_t own = board->occ[side];
uint64_t opp = board->occ[side ^ 1];
uint8_t pid = (side == WHITE) ? K : k;
uint64_t kk = (side == WHITE) ? board->pieces[K] : board->pieces[k];
if (!kk) return;
int from = first_set_index(kk);
uint64_t mask = KING_ATTACKS[from] & ~own;
uint64_t caps = mask & opp;
if (!captures_only) {
uint64_t quiet = mask & ~opp;
while (quiet) {
int to = pop_lsb_index(&quiet);
push_move(out, count,from, to, pid, 0, MF_NONE);
}
}
while (caps) {
int to = pop_lsb_index(&caps);
push_move(out, count,from, to, pid, 0, MF_CAPTURE);
}
if (!captures_only) {
uint64_t occ = board->occ[BOTH];
if (side == WHITE) {
if ((board->castling_rights & CASTLE_WK) && !(occ & WK_EMPTY_MASK)) {
push_move(out, count,E1, WK_TO, K, 0, MF_CASTLE);
}
if ((board->castling_rights & CASTLE_WQ) && !(occ & WQ_EMPTY_MASK)) {
push_move(out, count,E1, WQ_TO, K, 0, MF_CASTLE);
}
} else {
if ((board->castling_rights & CASTLE_BK) && !(occ & BK_EMPTY_MASK)) {
push_move(out, count,E8, BK_TO, k, 0, MF_CASTLE);
}
if ((board->castling_rights & CASTLE_BQ) && !(occ & BQ_EMPTY_MASK)) {
push_move(out, count,E8, BQ_TO, k, 0, MF_CASTLE);
}
}
}
}
int gen_pseudo_moves(const struct Board *board, struct Move *moves, bool captures_only) {
int count = 0;
if (board->side_to_move == WHITE) {
if (!captures_only) {
gen_white_pawn_quiet_pushes(board, moves, &count);
gen_white_pawn_push_promotions(board, moves, &count);
}
gen_white_pawn_captures(board, moves, &count);
gen_white_pawn_capture_promotions(board, moves, &count);
} else {
if (!captures_only) {
gen_black_pawn_quiet_pushes(board, moves, &count);
gen_black_pawn_push_promotions(board, moves, &count);
}
gen_black_pawn_captures(board, moves, &count);
gen_black_pawn_capture_promotions(board, moves, &count);
}
gen_knight_moves(board, moves, &count, captures_only);
gen_king_moves(board, moves, &count, captures_only);
gen_rook_moves(board, moves, &count, captures_only);
gen_bishop_moves(board, moves, &count, captures_only);
gen_queen_moves(board, moves, &count, captures_only);
return count;
}
void print_board(const struct Board *board) {
const char PIECE_CH[12] = {
'P','N','B','R','Q','K',
'p','n','b','r','q','k'
};
@@ -86,7 +541,7 @@ void print_board(const struct Board *b) {
for (int i = 0; i < 64; ++i) grid[i] = '.';
for (int p = 0; p < 12; ++p) {
uint64_t bb = b->pieces[p];
uint64_t bb = board->pieces[p];
while (bb) {
int sq = pop_lsb_index(&bb);
grid[sq] = PIECE_CH[p];

View File

@@ -1,33 +1,116 @@
/**
A quick test script to debug and visually inspect functions
and operations.
*/
// engine/src/test_pawns.c
#include <stdio.h>
#include <string.h>
#include "bitboard.h"
#include "fen.h"
enum { MAX_MOVES = 256 };
static int popcount64(uint64_t x){ int c=0; while(x){ x&=x-1; ++c; } return c; }
static const char NAME[12] = {'P','N','B','R','Q','K','p','n','b','r','q','k'};
void sq_to_coord(int sq, char out[3]) {
out[0] = 'a' + (sq % 8);
out[1] = '1' + (sq / 8);
out[2] = '\0';
}
void print_move(struct Move *m) {
char f[3], t[3];
sq_to_coord(m->from, f);
sq_to_coord(m->to, t);
printf("%s%s", f, t);
if (m->flags) {
printf(" [");
int first = 1;
if (m->flags & MF_CAPTURE) { printf("%sCAP", first?"":"|"); first = 0; }
if (m->flags & MF_DOUBLE_PUSH) { printf("%sDP", first?"":"|"); first = 0; }
if (m->flags & MF_ENPASSANT) { printf("%sEP", first?"":"|"); first = 0; }
if (m->flags & MF_PROMO) { printf("%sPROMO->%c", first?"":"|", (char)(m->promo ? "PNBRQKpnbrqk"[m->promo] : 'Q')); }
printf("]");
}
}
void dump_moves(const char *title, struct Move *moves, int count) {
printf("%s (%d):\n", title, count);
for (int i = 0; i < count; ++i) { print_move(&moves[i]); putchar('\n'); }
putchar('\n');
}
int main(void) {
const char *fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
// change this FEN to target specific cases you want to test
//const char *fen_startpos = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
const char *fen_startpos = "8/8/8/8/8/8/8/8 w KQkq - 0 1";
const char *fen = fen_startpos;
struct Board b;
memset(&b, 0, sizeof(b));
int rc = load_fen(&b, fen);
for (int i=0;i<12;i++) printf("%c: %d\n", NAME[i], popcount64(b.pieces[i]));
if (rc != 0) {
fprintf(stderr, "load_fen failed (rc=%d) for FEN:\n%s\n", rc, fen);
if (load_fen(&b, fen) != 0) {
fprintf(stderr, "load_fen failed\n");
return 1;
}
print_board(&b);
struct Move moves[MAX_MOVES];
int count;
/*
// --- WHITE pawns: quiet pushes (single + double, no promos) ---
count = 0;
gen_white_pawn_quiet_pushes(&b, moves, &count);
dump_moves("white quiet pushes", moves, count);
// --- BLACK pawns: quiet pushes ---
gen_black_pawn_quiet_pushes(&b, moves, &count);
dump_moves("black quiet pushes", moves, count);
printf("COUNT: %d", count);
*/
create_knight_attack_cache();
//create_king_attack_cache();
//create_pawn_attack_cache();
int n = gen_pseudo_moves(&b, moves, false);
printf("COUNT: %d", n);
/*
// --- WHITE pawns: quiet push promotions (queen-only) ---
count = 0;
gen_white_pawn_push_promotions_q(&b, moves, &count);
dump_moves("white push promotions (Q only)", moves, count);
// --- WHITE pawns: captures (non-promo) + en passant ---
count = 0;
gen_white_pawn_captures(&b, moves, &count);
dump_moves("white captures (non-promo + EP)", moves, count);
// --- WHITE pawns: capture promotions (queen-only) ---
count = 0;
gen_white_pawn_capture_promotions_q(&b, moves, &count);
dump_moves("white capture promotions (Q only)", moves, count);
// Flip side to move if you want to test black without changing FEN side
b.side_to_move = BLACK;
// --- BLACK pawns: quiet pushes ---
count = 0;
gen_black_pawn_quiet_pushes(&b, moves, &count);
dump_moves("black quiet pushes", moves, count);
// --- BLACK pawns: quiet push promotions (queen-only) ---
count = 0;
gen_black_pawn_push_promotions_q(&b, moves, &count);
dump_moves("black push promotions (q only)", moves, count);
// --- BLACK pawns: captures (non-promo) + en passant ---
count = 0;
gen_black_pawn_captures(&b, moves, &count);
dump_moves("black captures (non-promo + EP)", moves, count);
// --- BLACK pawns: capture promotions (queen-only) ---
count = 0;
gen_black_pawn_capture_promotions_q(&b, moves, &count);
dump_moves("black capture promotions (q only)", moves, count);
*/
return 0;
}
}