All checks were successful
Python tests (make) / test (push) Successful in 12s
Reviewed-on: #31 Co-authored-by: Josh <josh@joshuaschuett.com> Co-committed-by: Josh <josh@joshuaschuett.com>
840 lines
27 KiB
C
840 lines
27 KiB
C
#include "bitboard.h"
|
|
#include <stdio.h>
|
|
|
|
uint64_t PAWN_ATTACKS[2][64];
|
|
uint64_t KNIGHT_ATTACKS[64];
|
|
uint64_t KING_ATTACKS[64];
|
|
|
|
void create_knight_attack_cache(void) {
|
|
for (int sq = 0; sq < 64; sq++) {
|
|
uint64_t b = 1ULL << sq;
|
|
uint64_t mask = 0ULL;
|
|
|
|
mask |= (b & ~FILE_A) << 15;
|
|
mask |= (b & ~FILE_H) << 17;
|
|
mask |= (b & ~(FILE_A | FILE_B)) << 6;
|
|
mask |= (b & ~(FILE_G | FILE_H)) << 10;
|
|
mask |= (b & ~FILE_A) >> 17;
|
|
mask |= (b & ~FILE_H) >> 15;
|
|
mask |= (b & ~(FILE_A | FILE_B)) >> 10;
|
|
mask |= (b & ~(FILE_G | FILE_H)) >> 6;
|
|
|
|
KNIGHT_ATTACKS[sq] = mask;
|
|
}
|
|
}
|
|
|
|
void create_pawn_attack_cache(void) {
|
|
for (int sq = 0; sq < 64; sq++) {
|
|
uint64_t b = 1ULL << sq;
|
|
|
|
// White: NE (+9), NW (+7), but never from rank 8
|
|
uint64_t w = ((b & ~FILE_H & ~RANK_8) << 9) | ((b & ~FILE_A & ~RANK_8) << 7);
|
|
|
|
// Black: SE (-7), SW (-9), but never from rank 1
|
|
uint64_t bl = ((b & ~FILE_H & ~RANK_1) >> 7) | ((b & ~FILE_A & ~RANK_1) >> 9);
|
|
|
|
PAWN_ATTACKS[WHITE][sq] = w;
|
|
PAWN_ATTACKS[BLACK][sq] = bl;
|
|
}
|
|
}
|
|
|
|
void create_king_attack_cache(void) {
|
|
for (int sq = 0; sq < 64; sq++) {
|
|
uint64_t b = 1ULL << sq;
|
|
uint64_t mask = 0ULL;
|
|
|
|
// North / South
|
|
mask |= (b & ~RANK_8) << 8;
|
|
mask |= (b & ~RANK_1) >> 8;
|
|
|
|
// East / West
|
|
mask |= (b & ~FILE_H) << 1;
|
|
mask |= (b & ~FILE_A) >> 1;
|
|
|
|
// Diagonals
|
|
mask |= (b & ~FILE_A & ~RANK_8) << 7;
|
|
mask |= (b & ~FILE_H & ~RANK_8) << 9;
|
|
mask |= (b & ~FILE_A & ~RANK_1) >> 9;
|
|
mask |= (b & ~FILE_H & ~RANK_1) >> 7;
|
|
|
|
KING_ATTACKS[sq] = mask;
|
|
}
|
|
}
|
|
|
|
int first_set_index(uint64_t bb) {
|
|
for (int i = 0; i < 64; ++i) {
|
|
if ((bb >> i) & 1ULL) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int pop_lsb_index(uint64_t *bb) {
|
|
if (*bb == 0) return -1;
|
|
int idx = first_set_index(*bb);
|
|
// Clears bit.
|
|
*bb &= ~(1ULL << idx);
|
|
return idx;
|
|
}
|
|
|
|
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);
|
|
push_move(out, count, from, to, P, R, MF_PROMO);
|
|
push_move(out, count, from, to, P, B, MF_PROMO);
|
|
push_move(out, count, from, to, P, N, 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);
|
|
push_move(out, count, from, to, p, r, MF_PROMO);
|
|
push_move(out, count, from, to, p, b, MF_PROMO);
|
|
push_move(out, count, from, to, p, n, 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);
|
|
push_move(out, count, from, to, P, R, MF_CAPTURE | MF_PROMO);
|
|
push_move(out, count, from, to, P, B, MF_CAPTURE | MF_PROMO);
|
|
push_move(out, count, from, to, P, N, 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);
|
|
push_move(out, count, from, to, P, R, MF_CAPTURE | MF_PROMO);
|
|
push_move(out, count, from, to, P, B, MF_CAPTURE | MF_PROMO);
|
|
push_move(out, count, from, to, P, N, 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);
|
|
push_move(out, count, from, to, p, r, MF_CAPTURE | MF_PROMO);
|
|
push_move(out, count, from, to, p, b, MF_CAPTURE | MF_PROMO);
|
|
push_move(out, count, from, to, p, n, 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);
|
|
push_move(out, count, from, to, p, r, MF_CAPTURE | MF_PROMO);
|
|
push_move(out, count, from, to, p, b, MF_CAPTURE | MF_PROMO);
|
|
push_move(out, count, from, to, p, n, 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;
|
|
}
|
|
|
|
uint64_t attackers_to(const struct Board *board, int sq, enum Color by) {
|
|
uint64_t occ = board->occ[BOTH];
|
|
|
|
uint64_t pawns = (by == WHITE) ? board->pieces[P] : board->pieces[p];
|
|
uint64_t knights = (by == WHITE) ? board->pieces[N] : board->pieces[n];
|
|
uint64_t bishops = (by == WHITE) ? board->pieces[B] : board->pieces[b];
|
|
uint64_t rooks = (by == WHITE) ? board->pieces[R] : board->pieces[r];
|
|
uint64_t queens = (by == WHITE) ? board->pieces[Q] : board->pieces[q];
|
|
uint64_t kings = (by == WHITE) ? board->pieces[K] : board->pieces[k];
|
|
|
|
uint64_t attacks = 0;
|
|
|
|
attacks |= PAWN_ATTACKS[by ^ 1][sq] & pawns;
|
|
attacks |= KNIGHT_ATTACKS[sq] & knights;
|
|
attacks |= KING_ATTACKS[sq] & kings;
|
|
|
|
// Squares than can see sq given the occupancy of both sides but intersect
|
|
// with the specified color pieces.
|
|
attacks |= bishop_attacks(sq, occ) & (bishops | queens);
|
|
attacks |= rook_attacks(sq, occ) & (rooks | queens);
|
|
|
|
return attacks;
|
|
}
|
|
|
|
bool in_check(const struct Board *board, enum Color side) {
|
|
uint64_t king_bb = (side == WHITE) ? board->pieces[K] : board->pieces[k];
|
|
|
|
// Do we need this check here?
|
|
// This would be an illegal position.
|
|
if (!king_bb) return false;
|
|
|
|
int king_sq = pop_lsb_index(&king_bb);
|
|
|
|
// Is the king square in the attack map of opposite side?
|
|
return attackers_to(board, king_sq, side ^ 1) != 0;
|
|
}
|
|
|
|
bool square_attacked(const struct Board *board, int sq, enum Color by) {
|
|
// Should we have another way to calculate this and have
|
|
// earlier exits in the code? For example, we could iterate
|
|
// each piece type and return boolean sooner and skip the
|
|
// ray scanning.
|
|
return attackers_to(board, sq, by) != 0;
|
|
}
|
|
|
|
int piece_at(const struct Board *board, int sq) {
|
|
uint64_t mask = 1ULL << sq;
|
|
for (int piece = 0; piece < 12; ++piece)
|
|
if (board->pieces[piece] & mask) return piece;
|
|
return -1;
|
|
}
|
|
|
|
void rebuild_occ(struct Board *board) {
|
|
uint64_t white=0;
|
|
uint64_t black=0;
|
|
|
|
for (int i = P; i <= K; ++i) white |= board->pieces[i];
|
|
for (int i = p; i <= k; ++i) black |= board->pieces[i];
|
|
|
|
board->occ[WHITE] = white;
|
|
board->occ[BLACK] = black;
|
|
board->occ[BOTH] = white | black;
|
|
}
|
|
|
|
uint8_t castlerights_without(uint8_t castlerights, uint8_t mask) {
|
|
// Update castle rights by removing mask.
|
|
return castlerights & ~mask;
|
|
}
|
|
|
|
uint8_t update_castlerights_after_move(
|
|
uint8_t cr, int moved_pid, int from, int to, int cap_pid
|
|
) {
|
|
// King moved: lose both
|
|
if (moved_pid == K) cr &= ~(CASTLE_WK | CASTLE_WQ);
|
|
if (moved_pid == k) cr &= ~(CASTLE_BK | CASTLE_BQ);
|
|
|
|
// Rook moved from its home square
|
|
if (moved_pid == R) {
|
|
if (from == H1) cr &= ~CASTLE_WK;
|
|
if (from == A1) cr &= ~CASTLE_WQ;
|
|
} else if (moved_pid == r) {
|
|
if (from == H8) cr &= ~CASTLE_BK;
|
|
if (from == A8) cr &= ~CASTLE_BQ;
|
|
}
|
|
|
|
// Captured a rook on its home square.
|
|
// Obviously if a rook has moved and is captured,
|
|
// the prior check accounts for that.
|
|
if (cap_pid == R) {
|
|
if (to == H1) cr &= ~CASTLE_WK;
|
|
if (to == A1) cr &= ~CASTLE_WQ;
|
|
} else if (cap_pid == r) {
|
|
if (to == H8) cr &= ~CASTLE_BK;
|
|
if (to == A8) cr &= ~CASTLE_BQ;
|
|
}
|
|
return cr;
|
|
}
|
|
|
|
void put_piece(struct Board *board, int pid, int sq) {
|
|
board->pieces[pid] |= 1ULL << sq;
|
|
}
|
|
|
|
void del_piece(struct Board *board, int pid, int sq) {
|
|
board->pieces[pid] &= ~(1ULL << sq);
|
|
}
|
|
|
|
// Create a copy of the board to simulate move and test state.
|
|
bool apply_move_on_copy(struct Board *in, struct Board *out, struct Move m) {
|
|
*out = *in;
|
|
const enum Color us = in->side_to_move;
|
|
const enum Color opp = us ^ 1;
|
|
|
|
// Reset enpassant.
|
|
out->ep_square = -1;
|
|
|
|
// Handle capture / EP capture
|
|
int captured_pid = -1;
|
|
if (m.flags & MF_ENPASSANT) {
|
|
int cap_sq = (us == WHITE) ? (m.to - 8) : (m.to + 8);
|
|
captured_pid = piece_at(out, cap_sq);
|
|
if (captured_pid < 0) return false;
|
|
del_piece(out, captured_pid, cap_sq);
|
|
} else if (m.flags & MF_CAPTURE) {
|
|
captured_pid = piece_at(out, m.to);
|
|
if (captured_pid < 0) {
|
|
return false;
|
|
};
|
|
del_piece(out, captured_pid, m.to);
|
|
}
|
|
|
|
// Move (with promotion)
|
|
del_piece(out, m.piece, m.from);
|
|
// move.promo is the promotion piece.
|
|
int placed = (m.flags & MF_PROMO) ? m.promo : m.piece;
|
|
put_piece(out, placed, m.to);
|
|
|
|
// Castling: move rook
|
|
if (m.flags & MF_CASTLE) {
|
|
if (us == WHITE) {
|
|
if (m.to == WK_TO) {
|
|
del_piece(out, R, H1);
|
|
put_piece(out, R, F1);
|
|
}
|
|
else {
|
|
del_piece(out, R, A1);
|
|
put_piece(out, R, D1);
|
|
}
|
|
} else {
|
|
if (m.to == BK_TO) {
|
|
del_piece(out, r, H8);
|
|
put_piece(out, r, F8);
|
|
}
|
|
else {
|
|
del_piece(out, r, A8);
|
|
put_piece(out, r, D8);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Double pawn push -> set EP square
|
|
if (m.flags & MF_DOUBLE_PUSH) out->ep_square = (us == WHITE) ? (m.to - 8) : (m.to + 8);
|
|
|
|
// Update castling rights
|
|
out->castling_rights = update_castlerights_after_move(
|
|
in->castling_rights, m.piece, m.from, m.to, captured_pid
|
|
);
|
|
|
|
|
|
// Apply full and half move clock counts.
|
|
bool is_capture = (m.flags & MF_CAPTURE) || (m.flags & MF_ENPASSANT);
|
|
bool is_pawn = (m.piece == P) || (m.piece == p);
|
|
|
|
out->halfmove_clock = (is_capture || is_pawn) ? 0 : in->halfmove_clock + 1;
|
|
out->fullmove_number = in->fullmove_number + (in->side_to_move == BLACK);
|
|
|
|
// Side to move flips
|
|
out->side_to_move = opp;
|
|
|
|
rebuild_occ(out);
|
|
return true;
|
|
}
|
|
|
|
bool castle_path_safe(const struct Board *board, enum Color side, int king_to) {
|
|
enum Color opp = side ^ 1;
|
|
if (side == WHITE) {
|
|
if (king_to == WK_TO) {
|
|
// E1-F1-G1
|
|
if (square_attacked(board, E1, opp)) return false;
|
|
if (square_attacked(board, F1, opp)) return false;
|
|
} else {
|
|
// E1-D1-C1
|
|
if (square_attacked(board, E1, opp)) return false;
|
|
if (square_attacked(board, D1, opp)) return false;
|
|
}
|
|
} else {
|
|
if (king_to == BK_TO) {
|
|
// E8-F8-G8
|
|
if (square_attacked(board, E8, opp)) return false;
|
|
if (square_attacked(board, F8, opp)) return false;
|
|
} else {
|
|
// E8-D8-C8
|
|
if (square_attacked(board, E8, opp)) return false;
|
|
if (square_attacked(board, D8, opp)) return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool move_is_legal(struct Board *board, struct Move move) {
|
|
enum Color us = board->side_to_move;
|
|
enum Color opp = us ^ 1;
|
|
|
|
// Castle-through-check rule
|
|
if (move.flags & MF_CASTLE) {
|
|
if (!castle_path_safe(board, us, move.to)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
struct Board after;
|
|
if (!apply_move_on_copy(board, &after, move)) {
|
|
return false;
|
|
}
|
|
|
|
|
|
// Our king must not be in check after the move
|
|
uint64_t king_bb = (us == WHITE) ? after.pieces[K] : after.pieces[k];
|
|
|
|
if (!king_bb) return false;
|
|
|
|
int king_sq = first_set_index(king_bb);
|
|
return !square_attacked(&after, king_sq, opp);
|
|
}
|
|
|
|
int get_legal_moves(struct Board *board, struct Move *out) {
|
|
struct Move tmp[256];
|
|
int n = gen_pseudo_moves(board, tmp, false);
|
|
|
|
int count = 0;
|
|
for (int i = 0; i < n; ++i)
|
|
if (move_is_legal(board, tmp[i]))
|
|
out[count++] = tmp[i];
|
|
return count;
|
|
}
|
|
|
|
uint64_t perft(struct Board *board, int depth) {
|
|
if (depth == 0) return 1;
|
|
|
|
struct Move moves[256];
|
|
int n = get_legal_moves(board, moves);
|
|
|
|
if (depth == 1) return (uint64_t) n;
|
|
|
|
uint64_t nodes = 0;
|
|
for (int i = 0; i < n; ++i) {
|
|
struct Board after;
|
|
if (!apply_move_on_copy(board, &after, moves[i])) {
|
|
// Shouldn't happen with legal moves, but be defensive
|
|
continue;
|
|
}
|
|
nodes += perft(&after, depth - 1);
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
void print_board(const struct Board *board) {
|
|
const char PIECE_CH[12] = {
|
|
'P','N','B','R','Q','K',
|
|
'p','n','b','r','q','k'
|
|
};
|
|
|
|
char grid[64];
|
|
for (int i = 0; i < 64; ++i) grid[i] = '.';
|
|
|
|
for (int p = 0; p < 12; ++p) {
|
|
uint64_t bb = board->pieces[p];
|
|
while (bb) {
|
|
int sq = pop_lsb_index(&bb);
|
|
grid[sq] = PIECE_CH[p];
|
|
}
|
|
}
|
|
|
|
for (int r = 7; r >= 0; --r) {
|
|
for (int f = 0; f < 8; ++f) {
|
|
putchar(grid[r * 8 + f]);
|
|
if (f < 7) putchar(' ');
|
|
}
|
|
putchar('\n');
|
|
}
|
|
} |