testsAndMisc/C/lichess_random_engine/movegen.c

459 lines
19 KiB
C
Raw Normal View History

#include "movegen.h"
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.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;
}
}
static char piece_to_char(Piece p){
switch(p){
case WP: return 'P'; case WN: return 'N'; case WB: return 'B'; case WR: return 'R'; case WQ: return 'Q'; case WK: return 'K';
case BP: return 'p'; case BN: return 'n'; case BB: return 'b'; case BR: return 'r'; case BQ: return 'q'; case BK: return 'k';
default: return '.';
}
}
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;
default: 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;
}