testsAndMisc-archive/C/lichess_random_engine/movegen.c
Krzysztof kuhy Rudnicki 01091c09ce Add tests and fix pre-commit issues across all projects
- C/lichess_random_engine, vocabulary_curve, misc/split,
  1dvelocitysimulator, opening_learner: test suites added
- CPP/miscelanious: tests added
- TS/battery-status, champions_leauge_scores, two-inputs: tests added
- python_pkg/fm24_searcher, wake_alarm: new packages added
- Fix ruff/cppcheck/eslint/clang-format failures
- Update .gitignore for C/C++ build artifacts
2026-04-12 20:45:24 +02:00

949 lines
25 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "movegen.h"
#include <ctype.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
static inline int on_board(int sq) { return (sq & 0x88) == 0; }
static inline int rank_of(int sq) { return sq >> 4; }
static inline int file_of(int sq) { return sq & 7; }
static inline int color_of(Piece p) { return (p >= BP); }
static inline int is_white(Piece p) { return p >= WP && p <= WK; }
static inline int is_black(Piece p) { return p >= BP && p <= BK; }
static Piece make_piece(char c)
{
switch (c)
{
case 'P':
return WP;
case 'N':
return WN;
case 'B':
return WB;
case 'R':
return WR;
case 'Q':
return WQ;
case 'K':
return WK;
case 'p':
return BP;
case 'n':
return BN;
case 'b':
return BB;
case 'r':
return BR;
case 'q':
return BQ;
case 'k':
return BK;
default:
return EMPTY;
}
}
void set_startpos(Position *pos)
{
memset(pos, 0, sizeof(*pos));
for (int i = 0; i < BOARD_SIZE; i++)
{
pos->board[i] = EMPTY;
}
const char *start = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
char fen[128];
strcpy(fen, start);
strcat(fen, " w KQkq - 0 1");
parse_fen(pos, fen);
}
int parse_fen(Position *pos, const char *fen)
{
memset(pos, 0, sizeof(*pos));
for (int i = 0; i < BOARD_SIZE; i++)
{
pos->board[i] = EMPTY;
}
pos->ep_square = -1;
pos->castle = 0;
pos->halfmove_clock = 0;
pos->fullmove_number = 1;
// pieces
int sq = 0x70; // A8
const char *p = fen;
while (*p && *p != ' ')
{
if (*p == '/')
{
sq = (sq & 0x70) - 0x10;
p++;
continue;
}
if (isdigit((unsigned char)*p))
{
sq += (*p - '0');
p++;
continue;
}
Piece pc = make_piece(*p++);
if (!on_board(sq))
{
return 0;
}
pos->board[sq++] = pc;
}
if (*p != ' ')
{
return 0;
}
p++;
// side
if (*p == 'w')
{
pos->side = WHITE;
}
else if (*p == 'b')
{
pos->side = BLACK;
}
else
{
return 0;
}
p++;
if (*p != ' ')
{
return 0;
}
p++;
// castling
if (*p == '-')
{
p++;
}
else
{
while (*p && *p != ' ')
{
if (*p == 'K')
{
pos->castle |= 1 << 0;
}
else if (*p == 'Q')
{
pos->castle |= 1 << 1;
}
else if (*p == 'k')
{
pos->castle |= 1 << 2;
}
else if (*p == 'q')
{
pos->castle |= 1 << 3;
}
else
{
return 0;
}
p++;
}
}
if (*p != ' ')
{
return 0;
}
p++;
// en-passant
if (*p == '-')
{
pos->ep_square = -1;
p++;
}
else
{
if (p[0] >= 'a' && p[0] <= 'h' && p[1] >= '1' && p[1] <= '8')
{
int f = p[0] - 'a';
int r = p[1] - '1';
pos->ep_square = (r << 4) | f;
p += 2;
}
else
{
return 0;
}
}
if (*p == ' ')
{
p++;
}
// halfmove clock
if (isdigit((unsigned char)*p))
{
pos->halfmove_clock = strtol(p, (char **)&p, 10);
}
if (*p == ' ')
{
p++;
}
// fullmove number
if (isdigit((unsigned char)*p))
{
pos->fullmove_number = strtol(p, NULL, 10);
}
return 1;
}
static int add_move(Move *moves, int count, int max, int from, int to, int cap, int promo, int ep,
int castle)
{
if (count >= max)
{
return count;
}
Move m;
m.from = (uint8_t)from;
m.to = (uint8_t)to;
m.promo = (uint8_t)promo;
m.is_capture = (uint8_t)cap;
m.is_enpassant = (uint8_t)ep;
m.is_castle = (uint8_t)castle;
moves[count++] = m;
return count;
}
// Check detection via attack lookup
static int square_attacked_by(const Position *pos, int sq, Color by)
{
// Knights
static const int kn[] = {33, 31, 18, 14, -33, -31, -18, -14};
for (int i = 0; i < 8; i++)
{
int s = sq + kn[i];
if (!on_board(s))
{
continue;
}
Piece p = pos->board[s];
if (by == WHITE && p == WN)
{
return 1;
}
if (by == BLACK && p == BN)
{
return 1;
}
}
// Kings
static const int kd[] = {1, -1, 16, -16, 17, 15, -17, -15};
for (int i = 0; i < 8; i++)
{
int s = sq + kd[i];
if (!on_board(s))
{
continue;
}
Piece p = pos->board[s];
if (by == WHITE && p == WK)
{
return 1;
}
if (by == BLACK && p == BK)
{
return 1;
}
}
// Pawns
if (by == WHITE)
{
int s1 = sq - 15;
int s2 = sq - 17; // white pawns attack up-left/up-right from their perspective
if (on_board(s1) && pos->board[s1] == WP)
{
return 1;
}
if (on_board(s2) && pos->board[s2] == WP)
{
return 1;
}
}
else
{
int s1 = sq + 15;
int s2 = sq + 17;
if (on_board(s1) && pos->board[s1] == BP)
{
return 1;
}
if (on_board(s2) && pos->board[s2] == BP)
{
return 1;
}
}
// Sliders: bishops/queens diagonals
static const int bd[] = {17, 15, -17, -15};
for (int d = 0; d < 4; ++d)
{
int s = sq + bd[d];
while (on_board(s))
{
Piece p = pos->board[s];
if (p != EMPTY)
{
if (by == WHITE && (p == WB || p == WQ))
{
return 1;
}
if (by == BLACK && (p == BB || p == BQ))
{
return 1;
}
break;
}
s += bd[d];
}
}
// Rooks/queens
static const int rd[] = {1, -1, 16, -16};
for (int d = 0; d < 4; ++d)
{
int s = sq + rd[d];
while (on_board(s))
{
Piece p = pos->board[s];
if (p != EMPTY)
{
if (by == WHITE && (p == WR || p == WQ))
{
return 1;
}
if (by == BLACK && (p == BR || p == BQ))
{
return 1;
}
break;
}
s += rd[d];
}
}
return 0;
}
int in_check(const Position *pos, Color side)
{
// find king square
Piece k = (side == WHITE) ? WK : BK;
int ks = -1;
for (int sq = 0; sq < BOARD_SIZE; ++sq)
{
if (!on_board(sq))
{
sq = (sq | 7);
continue;
}
if (pos->board[sq] == k)
{
ks = sq;
break;
}
}
if (ks < 0)
{
return 0;
}
return square_attacked_by(pos, ks, (side == WHITE) ? BLACK : WHITE);
}
static int gen_moves_internal(const Position *pos, Move *moves, int max_moves, int captures_only)
{
int count = 0;
Color us = pos->side;
int forward = (us == WHITE) ? 16 : -16;
int start_rank = (us == WHITE) ? 1 : 6;
int promo_rank = (us == WHITE) ? 6 : 1; // rank before promotion move (from rank)
for (int sq = 0; sq < BOARD_SIZE; ++sq)
{
if (!on_board(sq))
{
sq = (sq | 7);
continue;
}
Piece p = pos->board[sq];
if (p == EMPTY)
{
continue;
}
if ((us == WHITE && !is_white(p)) || (us == BLACK && !is_black(p)))
{
continue;
}
switch (p)
{
case WP:
case BP:
{
int dir = (p == WP) ? 16 : -16;
int r = rank_of(sq);
// quiet pushes
if (!captures_only)
{
int to = sq + dir;
if (on_board(to) && pos->board[to] == EMPTY)
{
if (r == promo_rank)
{
count = add_move(moves, count, max_moves, sq, to, 0,
(us == WHITE ? WQ : BQ), 0, 0);
count = add_move(moves, count, max_moves, sq, to, 0,
(us == WHITE ? WR : BR), 0, 0);
count = add_move(moves, count, max_moves, sq, to, 0,
(us == WHITE ? WB : BB), 0, 0);
count = add_move(moves, count, max_moves, sq, to, 0,
(us == WHITE ? WN : BN), 0, 0);
}
else
{
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
// double push from start rank
if (r == start_rank)
{
int to2 = to + dir;
if (on_board(to2) && pos->board[to2] == EMPTY)
{
count = add_move(moves, count, max_moves, sq, to2, 0, 0, 0, 0);
}
}
}
}
}
// captures
int caps[2] = {sq + dir + 1, sq + dir - 1};
for (int i = 0; i < 2; i++)
{
int to = caps[i];
if (!on_board(to))
{
continue;
}
Piece tp = pos->board[to];
if (tp != EMPTY && color_of(tp) != us)
{
if (r == promo_rank)
{
count = add_move(moves, count, max_moves, sq, to, 1,
(us == WHITE ? WQ : BQ), 0, 0);
count = add_move(moves, count, max_moves, sq, to, 1,
(us == WHITE ? WR : BR), 0, 0);
count = add_move(moves, count, max_moves, sq, to, 1,
(us == WHITE ? WB : BB), 0, 0);
count = add_move(moves, count, max_moves, sq, to, 1,
(us == WHITE ? WN : BN), 0, 0);
}
else
{
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
}
}
}
// en-passant
if (pos->ep_square >= 0)
{
for (int i = 0; i < 2; i++)
{
int to = caps[i];
if (!on_board(to))
{
continue;
}
if (to == pos->ep_square)
{
count = add_move(moves, count, max_moves, sq, to, 1, 0, 1, 0);
}
}
}
}
break;
case WN:
case BN:
{
static const int d[8] = {33, 31, 18, 14, -33, -31, -18, -14};
for (int i = 0; i < 8; i++)
{
int to = sq + d[i];
if (!on_board(to))
{
continue;
}
Piece tp = pos->board[to];
if (tp == EMPTY)
{
if (!captures_only)
{
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
}
}
else if (color_of(tp) != us)
{
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
}
}
}
break;
case WB:
case BB:
case WR:
case BR:
case WQ:
case BQ:
{
static const int bd[4] = {17, 15, -17, -15};
static const int rd[4] = {1, -1, 16, -16};
const int *dirs = NULL;
int ndirs = 0;
if (p == WB || p == BB)
{
dirs = bd;
ndirs = 4;
}
else if (p == WR || p == BR)
{
dirs = rd;
ndirs = 4;
}
else
{ // queen
// iterate both sets
for (int i = 0; i < 4; i++)
{
int to = sq + bd[i];
while (on_board(to))
{
Piece tp = pos->board[to];
if (tp == EMPTY)
{
if (!captures_only)
{
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
}
}
else
{
if (color_of(tp) != us)
{
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
}
break;
}
to += bd[i];
}
}
for (int i = 0; i < 4; i++)
{
int to = sq + rd[i];
while (on_board(to))
{
Piece tp = pos->board[to];
if (tp == EMPTY)
{
if (!captures_only)
{
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
}
}
else
{
if (color_of(tp) != us)
{
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
}
break;
}
to += rd[i];
}
}
break;
}
for (int i = 0; i < ndirs; i++)
{
int to = sq + dirs[i];
while (on_board(to))
{
Piece tp = pos->board[to];
if (tp == EMPTY)
{
if (!captures_only)
{
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
}
}
else
{
if (color_of(tp) != us)
{
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
}
break;
}
to += dirs[i];
}
}
}
break;
case WK:
case BK:
{
static const int kd[8] = {1, -1, 16, -16, 17, 15, -17, -15};
for (int i = 0; i < 8; i++)
{
int to = sq + kd[i];
if (!on_board(to))
{
continue;
}
Piece tp = pos->board[to];
if (tp == EMPTY)
{
if (!captures_only)
{
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
}
}
else if (color_of(tp) != us)
{
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
}
}
// castling (very basic, no check-through validation here; filter later)
if (!captures_only)
{
// Only if not currently in check and path squares are not attacked
Color them = (us == WHITE) ? BLACK : WHITE;
if (us == WHITE)
{
if ((pos->castle & (1 << 0)) && pos->board[0x04] == WK &&
pos->board[0x05] == EMPTY && pos->board[0x06] == EMPTY)
{
if (!in_check(pos, WHITE) && !square_attacked_by(pos, 0x05, them) &&
!square_attacked_by(pos, 0x06, them))
{
count = add_move(moves, count, max_moves, sq, 0x06, 0, 0, 0, 1);
}
}
if ((pos->castle & (1 << 1)) && pos->board[0x03] == EMPTY &&
pos->board[0x02] == EMPTY && pos->board[0x01] == EMPTY)
{
if (!in_check(pos, WHITE) && !square_attacked_by(pos, 0x03, them) &&
!square_attacked_by(pos, 0x02, them))
{
count = add_move(moves, count, max_moves, sq, 0x02, 0, 0, 0, 1);
}
}
}
else
{
if ((pos->castle & (1 << 2)) && pos->board[0x74] == BK &&
pos->board[0x75] == EMPTY && pos->board[0x76] == EMPTY)
{
if (!in_check(pos, BLACK) && !square_attacked_by(pos, 0x75, them) &&
!square_attacked_by(pos, 0x76, them))
{
count = add_move(moves, count, max_moves, sq, 0x76, 0, 0, 0, 1);
}
}
if ((pos->castle & (1 << 3)) && pos->board[0x73] == EMPTY &&
pos->board[0x72] == EMPTY && pos->board[0x71] == EMPTY)
{
if (!in_check(pos, BLACK) && !square_attacked_by(pos, 0x73, them) &&
!square_attacked_by(pos, 0x72, them))
{
count = add_move(moves, count, max_moves, sq, 0x72, 0, 0, 0, 1);
}
}
}
}
}
break;
}
}
return count;
}
int gen_moves_pseudo(const Position *pos, Move *moves, int max_moves, int captures_only)
{
return gen_moves_internal(pos, moves, max_moves, captures_only);
}
int gen_moves(const Position *pos, Move *moves, int max_moves, int captures_only)
{
int count = gen_moves_internal(pos, moves, max_moves, captures_only);
// Filter illegal moves leaving our king in check
for (int i = 0; i < count;)
{
Position tmp = *pos;
Piece cap = EMPTY;
make_move(&tmp, &moves[i], &cap);
int illegal = in_check(&tmp, pos->side);
if (illegal)
{
moves[i] = moves[count - 1];
count--;
}
else
{
i++;
}
}
return count;
}
void make_move(Position *pos, const Move *m, Piece *captured_out)
{
Piece fromP = pos->board[m->from];
Piece toP = pos->board[m->to];
*captured_out = toP;
// en-passant capture
if (m->is_enpassant)
{
int cap_sq = (pos->side == WHITE) ? (m->to - 16) : (m->to + 16);
*captured_out = pos->board[cap_sq];
pos->board[cap_sq] = EMPTY;
}
// move piece
pos->board[m->to] = fromP;
pos->board[m->from] = EMPTY;
// promotion
if (m->promo)
{
pos->board[m->to] = (Piece)m->promo;
}
// castling rook move
if (m->is_castle)
{
if (fromP == WK && m->to == 0x06)
{
pos->board[0x05] = WR;
pos->board[0x07] = EMPTY;
}
else if (fromP == WK && m->to == 0x02)
{
pos->board[0x03] = WR;
pos->board[0x00] = EMPTY;
}
else if (fromP == BK && m->to == 0x76)
{
pos->board[0x75] = BR;
pos->board[0x77] = EMPTY;
}
else if (fromP == BK && m->to == 0x72)
{
pos->board[0x73] = BR;
pos->board[0x70] = EMPTY;
}
}
// update castling rights conservatively
if (fromP == WK)
{
pos->castle &= ~(1 << 0);
pos->castle &= ~(1 << 1);
}
if (fromP == BK)
{
pos->castle &= ~(1 << 2);
pos->castle &= ~(1 << 3);
}
if (m->from == 0x00 || m->to == 0x00)
{
pos->castle &= ~(1 << 1);
}
if (m->from == 0x07 || m->to == 0x07)
{
pos->castle &= ~(1 << 0);
}
if (m->from == 0x70 || m->to == 0x70)
{
pos->castle &= ~(1 << 3);
}
if (m->from == 0x77 || m->to == 0x77)
{
pos->castle &= ~(1 << 2);
}
// en-passant square
pos->ep_square = -1;
if (fromP == WP && (m->to - m->from) == 32)
{
pos->ep_square = m->from + 16;
}
if (fromP == BP && (m->from - m->to) == 32)
{
pos->ep_square = m->from - 16;
}
// halfmove clock
if (fromP == WP || fromP == BP || m->is_capture)
{
pos->halfmove_clock = 0;
}
else
{
pos->halfmove_clock++;
}
// side to move
pos->side = (pos->side == WHITE) ? BLACK : WHITE;
if (pos->side == WHITE)
{
pos->fullmove_number++;
}
}
void unmake_move(Position *pos, const Move *m, Piece captured)
{
pos->side = (pos->side == WHITE) ? BLACK : WHITE;
if (pos->side == BLACK)
{
pos->fullmove_number--;
}
Piece moved = pos->board[m->to];
// undo castling rook move
if (m->is_castle)
{
if (moved == WK && m->to == 0x06)
{
pos->board[0x07] = WR;
pos->board[0x05] = EMPTY;
}
else if (moved == WK && m->to == 0x02)
{
pos->board[0x00] = WR;
pos->board[0x03] = EMPTY;
}
else if (moved == BK && m->to == 0x76)
{
pos->board[0x77] = BR;
pos->board[0x75] = EMPTY;
}
else if (moved == BK && m->to == 0x72)
{
pos->board[0x70] = BR;
pos->board[0x73] = EMPTY;
}
}
// undo promotion
if (m->promo)
{
moved = (pos->side == WHITE) ? WP : BP;
}
pos->board[m->from] = moved;
if (m->is_enpassant)
{
pos->board[m->to] = EMPTY;
int cap_sq = (pos->side == WHITE) ? (m->to - 16) : (m->to + 16);
pos->board[cap_sq] = captured;
}
else
{
pos->board[m->to] = captured;
}
// Note: We do not restore previous castle/ep/halfmove here (for perft driver we will handle
// state by copying Position before make_move) For correctness in deeper engine, wed need a
// move stack with state; perft here uses position copies for make/unmake. To keep unmake
// consistent for our usage (make->unmake on a copy), we keep simple.
}
int square_from_algebraic(const char *uci4, int is_from)
{
// uci like e2e4 or e7e8q
if (!uci4 || strlen(uci4) < 4)
{
return -1;
}
int f = uci4[is_from ? 0 : 2] - 'a';
int r = uci4[is_from ? 1 : 3] - '1';
if (f < 0 || f > 7 || r < 0 || r > 7)
{
return -1;
}
return (r << 4) | f;
}
int move_from_uci(const Position *pos, const char *uci, Move *out)
{
int from = square_from_algebraic(uci, 1);
int to = square_from_algebraic(uci, 0);
if (from < 0 || to < 0)
{
return 0;
}
char promo = 0;
if (strlen(uci) >= 5)
{
promo = uci[4];
}
Move moves[256];
int n = gen_moves(pos, moves, 256, 0);
for (int i = 0; i < n; i++)
{
if (moves[i].from == from && moves[i].to == to)
{
if (moves[i].promo)
{
// map promo char
Piece pp = 0;
if (promo == 'q' || promo == 'Q')
{
pp = (pos->side == WHITE) ? WQ : BQ;
}
else if (promo == 'r' || promo == 'R')
{
pp = (pos->side == WHITE) ? WR : BR;
}
else if (promo == 'b' || promo == 'B')
{
pp = (pos->side == WHITE) ? WB : BB;
}
else if (promo == 'n' || promo == 'N')
{
pp = (pos->side == WHITE) ? WN : BN;
}
if (pp && pp == moves[i].promo)
{
*out = moves[i];
return 1;
}
}
else
{
if (!promo)
{
*out = moves[i];
return 1;
}
}
}
}
return 0;
}