feat: added lichess db puzzle to gitignore

This commit is contained in:
Krzysztof Rudnicki 2025-09-07 15:45:25 +02:00
parent 749546fdb2
commit 175a2b256c
11 changed files with 844 additions and 1181 deletions

6
.vscode/tasks.json vendored
View File

@ -30,6 +30,12 @@
"command": "python -m pip install -r requirements.txt && pytest -q",
"group": "build"
},
{
"label": "pytest quick",
"type": "shell",
"command": "python -m pip install -r requirements.txt && pytest -q",
"group": "build"
},
{
"label": "pytest quick",
"type": "shell",

View File

@ -2,9 +2,13 @@ CC := gcc
CFLAGS := -O2 -std=c11 -Wall -Wextra -Wno-unused-parameter
LDFLAGS :=
SRC := main.c
SRC := main.c movegen.c search.c
BIN := random_engine
# Perft driver
PERFT_SRC := perft.c movegen.c
PERFT_BIN := perft
.PHONY: all clean rebuild
all: $(BIN)
@ -12,7 +16,13 @@ all: $(BIN)
$(BIN): $(SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
$(PERFT_BIN): $(PERFT_SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
clean:
rm -f $(BIN)
rm -f $(BIN) $(PERFT_BIN)
rebuild: clean all
.PHONY: perft
perft: $(PERFT_BIN)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,458 @@
#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;
}

View File

@ -0,0 +1,50 @@
#ifndef MOVEGEN_H
#define MOVEGEN_H
#include <stdint.h>
// 0x88 board representation
#define BOARD_SIZE 128
typedef enum { WHITE = 0, BLACK = 1 } Color;
typedef enum {
EMPTY = 0,
WP = 1, WN, WB, WR, WQ, WK,
BP = 7, BN, BB, BR, BQ, BK
} Piece;
typedef struct {
// from and to squares in 0x88 (0..127), promotion piece in Piece enum or 0
uint8_t from, to;
uint8_t promo; // 0 if none
uint8_t is_capture; // 1 if capture
uint8_t is_enpassant; // 1 if en-passant capture
uint8_t is_castle; // 1 if castle
} Move;
typedef struct {
Piece board[BOARD_SIZE];
Color side;
// Castling rights: bit 0 white king-side, 1 white queen-side, 2 black king-side, 3 black queen-side
uint8_t castle;
int8_t ep_square; // -1 if none, else 0x88 square index
int halfmove_clock;
int fullmove_number;
} Position;
// Parsing and utilities
int parse_fen(Position *pos, const char *fen);
void set_startpos(Position *pos);
int square_from_algebraic(const char *uci4, int is_from);
int move_from_uci(const Position *pos, const char *uci, Move *out);
void make_move(Position *pos, const Move *m, Piece *captured_out);
void unmake_move(Position *pos, const Move *m, Piece captured);
int in_check(const Position *pos, Color side);
// Move generation
// Generates all pseudo-legal moves into moves[], returns count. If captures_only!=0, only captures (incl. ep) are generated
int gen_moves(const Position *pos, Move *moves, int max_moves, int captures_only);
int gen_moves_pseudo(const Position *pos, Move *moves, int max_moves, int captures_only);
#endif // MOVEGEN_H

BIN
C/lichess_random_engine/perft Executable file

Binary file not shown.

View File

@ -0,0 +1,72 @@
#include "movegen.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static unsigned long long perft(Position pos, int depth){
if (depth==0) return 1ULL;
Move moves[256];
unsigned long long nodes = 0ULL;
int n = gen_moves(&pos, moves, 256, 0);
for (int i=0;i<n;i++){
Position child = pos;
Piece cap=EMPTY;
make_move(&child, &moves[i], &cap);
nodes += perft(child, depth-1);
}
return nodes;
}
static void uci_from_move(const Move *m, char *buf){
int ff = (m->from & 7), fr = (m->from >> 4);
int tf = (m->to & 7), tr = (m->to >> 4);
buf[0] = 'a' + ff; buf[1] = '1' + fr; buf[2] = 'a' + tf; buf[3] = '1' + tr; int i=4;
if (m->promo){ char pc='q'; switch(m->promo){ case WQ: case BQ: pc='q'; break; case WR: case BR: pc='r'; break; case WB: case BB: pc='b'; break; case WN: case BN: pc='n'; break; default: pc='q'; }
buf[i++]=pc; }
buf[i]=0;
}
static void run_case(const char *fen, int depth, unsigned long long expected){
Position p; if (!parse_fen(&p, fen)){ fprintf(stderr, "Bad FEN: %s\n", fen); return; }
unsigned long long n = perft(p, depth);
printf("perft(%d) = %llu %s\n", depth, n, (expected? (n==expected?"OK":"MISMATCH"):""));
}
int main(int argc, char**argv){
if (argc>=3){
const char *fen = argv[1];
int depth = atoi(argv[2]);
Position p; if (!parse_fen(&p, fen)){ fprintf(stderr, "Bad FEN input\n"); return 2; }
if (argc>=4 && strcmp(argv[3], "--divide")==0){
Move moves[256]; int n = gen_moves(&p, moves, 256, 0);
unsigned long long total=0ULL;
for (int i=0;i<n;i++){
Position c = p; Piece cap=EMPTY; make_move(&c, &moves[i], &cap);
unsigned long long sub = perft(c, depth-1);
char u[8]; uci_from_move(&moves[i], u);
printf("%s: %llu\n", u, sub);
total += sub;
}
printf("Total: %llu\n", total);
} else if (argc>=4 && strcmp(argv[3], "--divide-pseudo")==0){
Move moves[256]; int n = gen_moves_pseudo(&p, moves, 256, 0);
for (int i=0;i<n;i++){ char u[8]; uci_from_move(&moves[i], u); printf("%s\n", u); }
printf("Total pseudo: %d\n", n);
} else {
unsigned long long n = perft(p, depth);
printf("%llu\n", n);
}
return 0;
}
// Some well-known positions (depth limited to be fast). Expected nodes are standard perft values.
// Start position
run_case("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 1, 20ULL);
run_case("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 2, 400ULL);
// Kiwipete
run_case("r3k2r/p1ppqpb1/bn2pnp1/2PpP3/1p2P3/2N2N2/PBPP1PPP/R2Q1RK1 w kq - 0 1", 1, 48ULL);
run_case("r3k2r/p1ppqpb1/bn2pnp1/2PpP3/1p2P3/2N2N2/PBPP1PPP/R2Q1RK1 w kq - 0 1", 2, 2039ULL);
// Simple EP
run_case("rnbqkbnr/pppppppp/8/8/3Pp3/8/PPP1PPPP/RNBQKBNR b KQkq d3 0 1", 1, 29ULL);
return 0;
}

View File

@ -0,0 +1,60 @@
#include "search.h"
#include <limits.h>
#include <stddef.h>
static int piece_value(Piece p){
switch(p){
case WP: case BP: return 100;
case WN: case BN: return 320;
case WB: case BB: return 330;
case WR: case BR: return 500;
case WQ: case BQ: return 900;
case WK: case BK: return 0; // king is invaluable; PST handled later if needed
default: return 0;
}
}
int evaluate(const Position *pos){
int score = 0;
for (int sq=0; sq<BOARD_SIZE; ++sq){
if ((sq & 0x88)) { sq = (sq|7); continue; }
Piece p = pos->board[sq];
if (p==EMPTY) continue;
int v = piece_value(p);
if (p>=WP && p<=WK) score += v; else if (p>=BP && p<=BK) score -= v;
}
// Score from side-to-move perspective
return (pos->side==WHITE)? score : -score;
}
int alphabeta(Position pos, int depth, int alpha, int beta, int *pv_from, int *pv_to){
if (depth<=0){
return evaluate(&pos);
}
Move moves[256];
int n = gen_moves(&pos, moves, 256, 0);
if (n==0){
// Checkmate or stalemate
if (in_check(&pos, pos.side)) return -30000 + (10 - depth); // checkmated
return 0; // stalemate
}
int best_score = INT_MIN/2;
int best_from = -1, best_to = -1;
for (int i=0;i<n;i++){
Position child = pos;
Piece cap=EMPTY;
make_move(&child, &moves[i], &cap);
int score = -alphabeta(child, depth-1, -beta, -alpha, NULL, NULL);
if (score > best_score){
best_score = score;
best_from = moves[i].from;
best_to = moves[i].to;
}
if (best_score > alpha) alpha = best_score;
if (alpha >= beta) break; // beta cutoff
}
if (pv_from) *pv_from = best_from;
if (pv_to) *pv_to = best_to;
return best_score;
}

View File

@ -0,0 +1,17 @@
#ifndef SEARCH_H
#define SEARCH_H
#include "movegen.h"
typedef struct {
int depth;
int nodes;
} SearchLimits;
// Evaluate position in centipawns from the side-to-move perspective.
int evaluate(const Position *pos);
// Negamax alpha-beta returning score in centipawns from side-to-move perspective.
int alphabeta(Position pos, int depth, int alpha, int beta, int *pv_from, int *pv_to);
#endif // SEARCH_H

4
PYTHON/.gitignore vendored
View File

@ -213,4 +213,6 @@ marimo/_lsp/
__marimo__/
# Streamlit
.streamlit/secrets.toml
.streamlit/secrets.toml
*lichess_db_puzzle.csv*

View File

@ -1 +1 @@
39
43