stego.git
move.c

/* stego - chess engine
* Copyright (C) 2025 ArcNyxx <me@arcnyxx.net>
* see LICENCE file for licensing information */
#include <stdlib.h>
#include "attack.h"
#include "bits.h"
#include "move.h"
#include "stego.h"
#include "table.h"
/* all non-king pieces attacking a certain square */
static u64
mv_checkers_mask(board_t *bd, sq_t sq, u64 occ)
{
return (atk_rook(sq, occ) & (bd->equeen | bd->erook)) |
(atk_bishop(sq, occ) & (bd->equeen | bd->ebishop)) |
(atk_knight(sq) & bd->eknight) |
(atk_pawn(bd, sq) & bd->epawn);
}
/* all pieces attacking a certain square */
static u64
mv_attackers_mask(board_t *bd, sq_t sq, u64 occ)
{
return (atk_king(sq) & bd->eking) | mv_checkers_mask(bd, sq, occ);
}
/* spaces between a checking slider and friendly king */
static u64
mv_quiet_mask(board_t *bd, sq_t king, u64 checker)
{
/* checker must have line of sight to king */
if (popcount(rotab[king] & checker))
/* anding together the slider attacks from the king's and
* slider's perspectives gets the spaces between them */
return atk_rook(king, bd->occupied) &
atk_rook(lsb(checker), bd->occupied);
return atk_bishop(king, bd->occupied) &
atk_bishop(lsb(checker), bd->occupied);
}
static void
mv_king(board_t *bd, sq_t king)
{
u64 mask = kitab[king] & ~bd->friend, iter = mask;
while (iter != 0) {
sq_t move = lsb(iter);
/* remove the friendly king from the mask of occupied squares
* so enemy sliding pieces can "see through" it and attack
* squares the king may move to (which would still be in
* check) */
if (mv_attackers_mask(bd, move, bd->occupied ^ bd->king))
mask ^= 1ULL << move;
iter ^= 1ULL << move;
}
bd->moves[king] = mask;
}
static void
mv_king_castle(board_t *bd, sq_t king)
{
/* for a particular castling type, check whether the required squares
* are cleared, whether the square the king jumps through is not in
* check, and whether the square the king jumps to is not in check */
#define CASTLE(type, mask, move) \
if (type && !(bd->occupied & (mask)) && (bd->moves[king] & (mask)) && \
!mv_attackers_mask(bd, move, bd->occupied)) \
bd->moves[king] |= 1ULL << move;
if (bd->white) {
CASTLE(bd->wkcastle, 1ULL << 61 | 1ULL << 62, 62)
CASTLE(bd->wqcastle, 1ULL << 57 | 1ULL << 58 | 1ULL << 59, 59)
} else {
CASTLE(bd->bkcastle, 1ULL << 5 | 1ULL << 6, 6)
CASTLE(bd->bqcastle, 1ULL << 1 | 1ULL << 2 | 1ULL << 3, 2)
}
}
static void
mv_queen(board_t *bd, sq_t queen)
{
bd->moves[queen] = (atk_rook(queen, bd->occupied) |
atk_bishop(queen, bd->occupied)) &
bd->general & ~bd->moves[queen];
}
static void
mv_rook(board_t *bd, sq_t rook)
{
bd->moves[rook] = atk_rook(rook, bd->occupied) &
bd->general & ~bd->moves[rook];
}
static void
mv_bishop(board_t *bd, sq_t bishop)
{
bd->moves[bishop] = atk_bishop(bishop, bd->occupied) &
bd->general & ~bd->moves[bishop];
}
static void
mv_knight(board_t *bd, sq_t knight)
{
bd->moves[knight] = atk_knight(knight) &
bd->general & ~bd->moves[knight];
}
static u64
mv_pawn_push(board_t *bd, sq_t pawn)
{
u64 mask;
#define OC (bd->occupied)
if (bd->white)
mask = (wptab[pawn] & ~OC) | (wltab[pawn] & ~OC & ~(OC >> 8));
else
mask = (bptab[pawn] & ~OC) | (bltab[pawn] & ~OC & ~(OC << 8));
return mask & ~bd->friend & bd->quiet & ~bd->moves[pawn];
}
static u64
mv_pawn_capture(board_t *bd, sq_t pawn, sq_t king)
{
/* basic captures */
u64 mask = atk_pawn(bd, pawn) & bd->enemy & bd->capture &
~bd->moves[pawn], enp = 0;
/* enpassant captures */
if (bd->white)
enp = wctab[pawn] & (1ULL << bd->enpassant) >> 8 &
(bd->capture >> 8 | bd->quiet) & ~bd->moves[pawn];
else
enp = bctab[pawn] & (1ULL << bd->enpassant) << 8 &
(bd->capture << 8 | bd->quiet) & ~bd->moves[pawn];
/* an edge case occurs when the king is separated from a rook or queen
* by two pawns along a single rank. if an enpassant capture occurs,
* the king will be able to be captured, which is not allowed */
if (enp) {
u64 occ = (bd->occupied | 1 | 1ULL << 63) ^
1ULL << pawn ^ 1ULL << bd->enpassant;
const sq_t ea = lsb(eabtab[king] & occ);
const sq_t we = msb(webtab[king] & occ);
const u64 rank = rotab[king] ^ notab[king] ^ eatab[ea] ^
sotab[king] ^ wetab[we];
const u64 rq = bd->equeen | bd->erook;
if (rank != 0 && (1ULL << lsb(rank) & rq ||
1ULL << msb(rank) & rq))
enp = 0;
}
return mask | enp;
}
void
mv(board_t *bd)
{
/* set up general masks */
bd->friend = bd->king | bd->queen | bd->rook | bd->bishop |
bd->knight | bd->pawn;
bd->enemy = bd->eking | bd->equeen | bd->erook | bd->ebishop |
bd->eknight | bd->epawn;
bd->occupied = bd->friend | bd->enemy;
bd->capture = ~0ULL, bd->quiet = ~0ULL;
/* generate king moves */
sq_t king = lsb(bd->king);
mv_king(bd, king);
/* inspect number of checkers */
u64 checkers = mv_checkers_mask(bd, king, bd->occupied);
if (popcount(checkers) == 2)
return; /* double check, only king moves legal */
/* set up capture and quiet move masks */
if (checkers != 0) {
/* single check, only capture attacker or quiet move to block */
bd->capture = checkers;
if (checkers | bd->equeen | bd->erook | bd->ebishop)
bd->quiet = mv_quiet_mask(bd, king, checkers);
else
bd->quiet = 0;
mv_king_castle(bd, king);
}
/* pin evaluation */
u64 pin_moves;
sq_t slider;
/* for each direction, find the nearest enemy piece (if it exists along
* a certain ray), stop the ray on the enemy piece, and, if the piece is
* the correct type of slider and there is only one friendly piece
* between it and the king, apply a mask to the friendly piece moves */
#define PIN(tab, fn, ptype) \
pin_moves = tab[king]; \
if ((pin_moves & bd->enemy) == 0) \
goto next ## tab; \
slider = fn(pin_moves & bd->enemy); \
pin_moves ^= tab[slider]; \
if ((slider & (ptype)) && popcount(pin_moves & bd->friend) == 1) \
bd->moves[lsb(pin_moves & bd->friend)] = ~pin_moves; \
next ## tab:
PIN(notab, msb, bd->equeen | bd->erook);
PIN(netab, msb, bd->equeen | bd->ebishop);
PIN(eatab, lsb, bd->equeen | bd->erook);
PIN(setab, lsb, bd->equeen | bd->ebishop);
PIN(sotab, lsb, bd->equeen | bd->erook);
PIN(swtab, lsb, bd->equeen | bd->ebishop);
PIN(wetab, msb, bd->equeen | bd->erook);
PIN(nwtab, msb, bd->equeen | bd->ebishop);
/* general move generation */
bd->general = ~bd->friend & bd->capture & bd->quiet;
#define MOVE(type) \
for (u64 iter = bd->type; iter != 0; ) { \
sq_t type = lsb(iter); \
mv_ ## type(bd, type); \
iter ^= 1ULL << type; \
}
MOVE(queen)
MOVE(rook)
MOVE(bishop)
MOVE(knight)
for (u64 iter = bd->pawn; iter != 0; ) {
sq_t pawn = lsb(iter);
bd->moves[pawn] = mv_pawn_push(bd, pawn) |
mv_pawn_capture(bd, pawn, king);
iter ^= 1ULL << pawn;
}
}
void
mk(board_t *bd, sq_t from, sq_t to)
{
u64 from_mask = 1ULL << from, to_mask = 1ULL << to,
mask = from_mask | to_mask;
bool reset = true;
if (bd->king & from_mask) {
bd->king ^= mask;
if (bd->white)
bd->wkcastle = bd->wqcastle = false;
else
bd->bkcastle = bd->bqcastle = false;
if (abs(to - from) == 2)
switch (to) {
default: break;
case 62: bd->rook ^= 0b10100000ULL << 56; break;
case 58: bd->rook ^= 0b00001001ULL << 56; break;
case 1: bd->rook ^= 0b10100000ULL; break;
case 5: bd->rook ^= 0b00001001ULL; break;
}
} else if (bd->queen & from_mask) {
bd->queen ^= mask;
} else if (bd->rook & from_mask) {
bd->rook ^= mask;
switch (from) {
default: break;
case 63: bd->wkcastle = false; break;
case 56: bd->wqcastle = false; break;
case 7: bd->bkcastle = false; break;
case 0: bd->bqcastle = false; break;
}
} else if (bd->bishop & from_mask) {
bd->bishop ^= mask;
} else if (bd->knight & from_mask) {
bd->knight ^= mask;
} else if (bd->pawn & from_mask) {
bd->pawn ^= mask;
if (abs(to - from) == 16) {
bd->enpassant = to;
reset = false;
} else if ((abs(to - from) == 9 || abs(to - from) == 7) &&
!(bd->enemy & to_mask)) {
bd->epawn ^= 1ULL << bd->enpassant;
}
}
if (reset)
bd->enpassant = 64;
bd->equeen &= ~to_mask;
bd->erook &= ~to_mask;
bd->ebishop &= ~to_mask;
bd->eknight &= ~to_mask;
bd->epawn &= ~to_mask;
bd->white = !bd->white;
u64 tmp;
#define SWAP(src, dest) \
tmp = src; \
src = dest; \
dest = tmp;
SWAP(bd->king, bd->eking)
SWAP(bd->queen, bd->equeen)
SWAP(bd->rook, bd->erook)
SWAP(bd->bishop, bd->ebishop)
SWAP(bd->knight, bd->eknight)
SWAP(bd->pawn, bd->epawn)
}
void
unmk(board_t *bd)
{
}