Files
chess-engine/engine-move-generation.h

253 lines
11 KiB
C

#pragma once
#define MOVE_MAX 128
#define MOVE_CASTLE_KINGSIDE_WHITE (struct move) \
{.from = SQ_E1, .to = SQ_G1}
#define MOVE_CASTLE_QUEENSIDE_WHITE (struct move) \
{.from = SQ_E1, .to = SQ_C1}
#define MOVE_CASTLE_KINGSIDE_BLACK (struct move) \
{.from = SQ_E8, .to = SQ_G8}
#define MOVE_CASTLE_QUEENSIDE_BLACK (struct move) \
{.from = SQ_E8, .to = SQ_C8}
static inline
struct move move_make(Index8 from, Index8 to)
{
return (struct move){.from = from, .to = to};
}
enum move_gen_type {
MG_ALL,
MG_CAPTURES,
MG_CHECKS,
MG_QUIETS,
};
static void all_pseudolegal_from_piece(struct pos const* restrict pos,
enum move_gen_type type,
Piece8 piece,
Side8 us,
Bb64 piece_mask,
Bb64 allowed,
size_t* restrict out_count,
struct move out[restrict static MOVE_MAX])
{
assuming(piece != PIECE_PAWN);
Side8 them = other_side(us);
Bb64 const our_occ = ~pos->pieces[us][PIECE_EMPTY];
Bb64 const all_occ = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]);
if (type == MG_CHECKS) {
allowed &= non_pawn_piece_attacks(piece, pos->pieces[them][PIECE_KING], all_occ);
}
/* pray this gets constprop'ed */
while (piece_mask) {
Sq8 from = bitboard_pop_lsb(&piece_mask);
Bb64 move_mask = non_pawn_piece_attacks_from_index(piece, from, all_occ)
& ~our_occ
& allowed
;
while (move_mask) {
Sq8 const to = bitboard_pop_lsb(&move_mask);
assuming(*out_count < MOVE_MAX); \
out[(*out_count)++] = move_make(from, to);
}
}
}
#define DEFINE_ALL_PSEUDOLEGAL_PAWN_MOVES(side, opp_side, side_enum, inverse_direction, pawn_rank) \
static void all_pseudolegal_pawn_moves_##side(struct pos const* restrict pos,\
enum move_gen_type type,\
Bb64 piece_mask,\
Bb64 allowed,\
size_t* restrict out_count,\
struct move out[restrict static MOVE_MAX])\
{\
Bb64 const all_occ = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]);\
\
if (type == MG_CHECKS) {\
allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\
}\
Bb64 sp = pawn_single_push_##side(piece_mask, ~all_occ)\
& allowed\
;\
while (sp) {\
Sq8 const to = bitboard_pop_lsb(&sp);\
Sq8 const from = SQ_SHIFT_##inverse_direction(to, 1);\
out[(*out_count)++] = move_make(from, to);\
}\
\
Bb64 dp = pawn_double_push_##side(piece_mask & pawn_rank, ~all_occ)\
& allowed\
;\
while (dp) {\
Sq8 const to = bitboard_pop_lsb(&dp);\
Sq8 const from = SQ_SHIFT_##inverse_direction(to, 2);\
out[(*out_count)++] = move_make(from, to);\
}\
}
DEFINE_ALL_PSEUDOLEGAL_PAWN_MOVES(white, black, SIDE_WHITE, SOUTH, RANK_MASK_2)
DEFINE_ALL_PSEUDOLEGAL_PAWN_MOVES(black, white, SIDE_BLACK, NORTH, RANK_MASK_7)
#define DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(side, opp_side, side_enum, inverse_direction) \
static void all_pseudolegal_pawn_attacks_##side(struct pos const* restrict pos,\
enum move_gen_type type,\
Bb64 piece_mask,\
Bb64 allowed,\
size_t* restrict out_count,\
struct move out[restrict static MOVE_MAX])\
{\
Bb64 const their_occ = ~pos->pieces[other_side(side_enum)][PIECE_EMPTY];\
\
if (type == MG_CHECKS) {\
allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\
}\
\
Bb64 ratk = pawn_attacks_right_##side(piece_mask)\
& (their_occ | pos->ep_targets)\
& allowed\
;\
while (ratk) {\
Sq8 const to = bitboard_pop_lsb(&ratk);\
Sq8 const from = SQ_SHIFT_WEST(SQ_SHIFT_##inverse_direction(to, 1), 1);\
assuming(*out_count < MOVE_MAX); \
out[(*out_count)++] = move_make(from, to);\
}\
\
Bb64 latk = pawn_attacks_left_##side(piece_mask)\
& (their_occ | pos->ep_targets)\
& allowed\
;\
while (latk) {\
Sq8 const to = bitboard_pop_lsb(&latk);\
Sq8 const from = SQ_SHIFT_EAST(SQ_SHIFT_##inverse_direction(to, 1), 1);\
assuming(*out_count < MOVE_MAX); \
out[(*out_count)++] = move_make(from, to);\
}\
}
DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(white, black, SIDE_WHITE, SOUTH)
DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(black, white, SIDE_BLACK, NORTH)
static void all_pseudolegal_moves(struct pos const* restrict pos,
enum move_gen_type type,
Side8 us,
size_t* restrict out_count,
struct move out[restrict static MOVE_MAX])
{
Side8 const them = other_side(us);
if (type == MG_CHECKS && pos->pieces[them][PIECE_KING] == 0ULL) {
return;
}
Bb64 const kmask = pos->pieces[us][PIECE_KING];
assuming(kmask);
Sq8 const ksq = bitboard_lsb(kmask);
Bb64 const chk = checkers(pos, us);
Bb64 const their_threats = all_threats_from_side(pos, them);
Bb64 const their_occ = ~pos->pieces[them][PIECE_EMPTY];
Bb64 allowed;
if (type == MG_CAPTURES) {
allowed = their_occ;
} else if (type == MG_QUIETS) {
allowed = ~their_occ;
} else if (type == MG_ALL) {
allowed = ~0ULL;
} else {
allowed = ~0ULL;
}
Bb64 king_allowed = allowed;
if (chk) {
if (bitboard_more_than_one(chk)) {
if (type == MG_ALL) {
/* no other piece can fix check if the king has two attackers or more */
all_pseudolegal_from_piece(pos, type, PIECE_KING, us, pos->pieces[us][PIECE_KING], ~their_threats, out_count, out);
}
return;
}
Sq8 const asq = bitboard_lsb(chk);
Bb64 const blocking_mask = between_mask(ksq, asq);
allowed &= blocking_mask | chk;
}
if (type == MG_CAPTURES || type == MG_CHECKS || type == MG_ALL) {
Bb64 const x = (type == MG_CHECKS ? pos->ep_targets : 0ULL);
if (us == SIDE_WHITE) {
all_pseudolegal_pawn_attacks_white(pos, type, pos->pieces[us][PIECE_PAWN], allowed | x, out_count, out);
} else {
all_pseudolegal_pawn_attacks_black(pos, type, pos->pieces[us][PIECE_PAWN], allowed | x, out_count, out);
}
}
if (type == MG_QUIETS || type == MG_CHECKS || type == MG_ALL) {
if (us == SIDE_WHITE) {
all_pseudolegal_pawn_moves_white(pos, type, pos->pieces[us][PIECE_PAWN], allowed, out_count, out);
} else {
all_pseudolegal_pawn_moves_black(pos, type, pos->pieces[us][PIECE_PAWN], allowed, out_count, out);
}
}
all_pseudolegal_from_piece(pos, type, PIECE_BISHOP, us, pos->pieces[us][PIECE_BISHOP], allowed, out_count, out);
all_pseudolegal_from_piece(pos, type, PIECE_KNIGHT, us, pos->pieces[us][PIECE_KNIGHT], allowed, out_count, out);
all_pseudolegal_from_piece(pos, type, PIECE_ROOK, us, pos->pieces[us][PIECE_ROOK], allowed, out_count, out);
all_pseudolegal_from_piece(pos, type, PIECE_QUEEN, us, pos->pieces[us][PIECE_QUEEN], allowed, out_count, out);
all_pseudolegal_from_piece(pos, type, PIECE_KING, us, pos->pieces[us][PIECE_KING], ~their_threats & king_allowed, out_count, out);
if (type == MG_CHECKS) {
Bb64 discovering = pinning_lines_to_target(pos, us, bitboard_lsb(pos->pieces[them][PIECE_KING]));
all_pseudolegal_from_piece(pos, MG_ALL, PIECE_BISHOP, us, discovering & pos->pieces[us][PIECE_BISHOP], ~discovering, out_count, out);
all_pseudolegal_from_piece(pos, MG_ALL, PIECE_KNIGHT, us, discovering & pos->pieces[us][PIECE_KNIGHT], ~discovering, out_count, out);
all_pseudolegal_from_piece(pos, MG_ALL, PIECE_ROOK, us, discovering & pos->pieces[us][PIECE_ROOK], ~discovering, out_count, out);
all_pseudolegal_from_piece(pos, MG_ALL, PIECE_QUEEN, us, discovering & pos->pieces[us][PIECE_QUEEN], ~discovering, out_count, out);
all_pseudolegal_from_piece(pos, MG_ALL, PIECE_KING, us, discovering & pos->pieces[us][PIECE_KING], ~discovering & ~their_threats, out_count, out);
}
/* castling */
if (!chk && type != MG_CAPTURES) {
bool can_castle_kingside, can_castle_queenside;
Bb64 const blocked = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]) | their_threats;
if (us == SIDE_WHITE) {
can_castle_kingside = !(blocked & (SQMASK_F1 | SQMASK_G1))
&& (pos->pieces[us][PIECE_ROOK] & SQMASK_H1)
&& !pos->castling_illegal[us][CASTLE_KINGSIDE];
can_castle_queenside = !(blocked & (SQMASK_C1 | SQMASK_D1))
&& (pos->pieces[us][PIECE_ROOK] & SQMASK_A1)
&& !pos->castling_illegal[us][CASTLE_QUEENSIDE];
} else {
can_castle_kingside = !(blocked & (SQMASK_F8 | SQMASK_G8))
&& (pos->pieces[us][PIECE_ROOK] & SQMASK_H8)
&& !pos->castling_illegal[us][CASTLE_KINGSIDE];
can_castle_queenside = !(blocked & (SQMASK_C8 | SQMASK_D8))
&& (pos->pieces[us][PIECE_ROOK] & SQMASK_A8)
&& !pos->castling_illegal[us][CASTLE_QUEENSIDE];
}
if (can_castle_kingside) {
out[(*out_count)++] = us == SIDE_WHITE
? MOVE_CASTLE_KINGSIDE_WHITE
: MOVE_CASTLE_KINGSIDE_BLACK;
}
if (can_castle_queenside) {
out[(*out_count)++] = us == SIDE_WHITE
? MOVE_CASTLE_QUEENSIDE_WHITE
: MOVE_CASTLE_QUEENSIDE_BLACK;
}
}
}