stego.git

move.c

espurr
/* 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)
{
	
}