The Dawn of Skynet

Discussion of chess software programming and technical issues.

Moderator: Ras

Werewolf
Posts: 2014
Joined: Thu Sep 18, 2008 10:24 pm

The Dawn of Skynet

Post by Werewolf »

In another post someone recommended I share this project.

I am a very casual programmer with very limited experience. However, given the rise of AI I have decided to write "my own" chess engine. I will share the code every time a breakthough is made. Any comments welcome. The below is semi-functional in Fritz but unlike a previous version it plays horribly. But let's see how it advances over time.

Primary coder is ChatGPT 5.

/*
GPT-5 Pro Chess — a simple, fast(ish) brute-force UCI engine in C.

Design goals per user spec:
- UCI compatible; works in Fritz/ChessBase
- "Brute force" alpha-beta with iterative deepening + quiescence (captures; checks are handled by in-check extension)
- No exotic pruning like LMR/null-move/razoring. Basic move ordering only (TT move, MVV-LVA, killers, history).
- Simple evaluation: material + development + central control + mobility + king safety (non-linear attackers near king)
- Depth gauge + nodes per second (nps) reported via UCI "info" lines
- Hash option (MB) and Threads option (accepted; currently single-threaded by design for simplicity and robustness)

Build (MSYS2 UCRT64 GCC):
gcc -O3 -pipe -DNDEBUG -std=c17 -march=native -pthread -o gpt5prochess.exe gpt5prochess.c

This is a single-file engine to make it easy to drop into Cursor and build.
License: MIT
Author: OpenAI (GPT-5 Pro)
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <inttypes.h>
#include <ctype.h>
#include <time.h>
#include <assert.h>
#include <stdbool.h>

#ifdef _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif

/* ========================= Utility: time in milliseconds ========================= */
static inline uint64_t now_ms(void) {
#ifdef _WIN32
return (uint64_t)GetTickCount64();
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL;
#endif
}

/* ========================= 0x88 board representation ========================= */
#define SQ_OK(sq) (!((sq) & 0x88))
#define FILE_OF(sq) ((sq) & 7)
#define RANK_OF(sq) ((sq) >> 4)
#define SQ(f,r) (((r)<<4) | (f))

enum { WHITE = 0, BLACK = 1 };
enum { EMPTY=0,
WP=1, WN=2, WB=3, WR=4, WQ=5, WK=6,
BP=7, BN=8, BB=9, BR=10, BQ=11, BK=12 };

static inline int color_of(int p){ return p>=BP; }
static inline int type_of(int p){ if(p==EMPTY) return 0; return (p-1)%6 + 1; }

static const int piece_value[13] = {
0, 100, 320, 330, 500, 900, 20000, 100, 320, 330, 500, 900, 20000
};

/* Directions (0x88) */
static const int knight_dirs[8] = { 33, 31, 18, 14, -33, -31, -18, -14 };
static const int bishop_dirs[4] = { 15, 17, -15, -17 };
static const int rook_dirs[4] = { 16, 1, -16, -1 };
static const int king_dirs[8] = { 16, 1, -16, -1, 15, 17, -15, -17 };

/* Castling rights bitmask */
#define W_KS 1
#define W_QS 2
#define B_KS 4
#define B_QS 8

/* Move encoding: 32-bit
bits 0..6 from (0..127)
bits 7..13 to (0..127)
bits 14..17 promo piece type (1..6) (color inferred by side-to-move)
bits 18..23 flags
*/
typedef uint32_t Move;
#define MFROM(m) ((int)((m) & 0x7F))
#define MTO(m) ((int)(((m) >> 7) & 0x7F))
#define MPROM(m) ((int)(((m) >> 14) & 0xF))
#define MFLAGS(m) ((int)(((m) >> 18) & 0x3F))
static inline Move MOVE(int from, int to, int promo, int flags){
return (Move)( (from & 0x7F) | ((to & 0x7F)<<7) | ((promo & 0xF)<<14) | ((flags & 0x3F)<<18) );
}

/* Move flags */
#define FL_CAPTURE 1
#define FL_DPP 2 /* double pawn push */
#define FL_EPCAP 4
#define FL_CASTLE 8
#define FL_PROMO 16

/* Search constants */
#define MAX_PLY 128
#define INF 32000
#define MATE 30000
#define MATE_IN(ply) (MATE - (ply))

/* ========================= Engine state ========================= */
static int board[128];
static int side_to_move = WHITE;
static int castle_rights = W_KS|W_QS|B_KS|B_QS;
static int ep_square = -1; /* -1 = none, otherwise 0..127 */
static int halfmove_clock = 0;
static int fullmove_number = 1;
static int king_sq[2] = { SQ(4,0), SQ(4,7) };

/* For repetition detection */
static uint64_t hash_history[2048];
static int ply_history = 0;

/* ========================= Zobrist hashing ========================= */
static uint64_t zob_piece[13][128];
static uint64_t zob_side;
static uint64_t zob_castle[16];
static uint64_t zob_ep[16];
static uint64_t pos_key = 0;

static uint64_t xorshift64(uint64_t *s){
uint64_t x = *s;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
*s = x;
return x;
}
static void init_zobrist(void){
uint64_t seed = 0x9E3779B97F4A7C15ULL; /* fixed seed */
for(int p=0;p<13;p++)
for(int sq=0;sq<128;sq++)
zob_piece[p][sq] = xorshift64(&seed);
zob_side = xorshift64(&seed);
for(int i=0;i<16;i++) zob_castle = xorshift64(&seed);
for(int i=0;i<16;i++) zob_ep = xorshift64(&seed);
}
static uint64_t compute_hash(void){
uint64_t h=0;
for(int sq=0;sq<128;sq++){
if(!SQ_OK(sq)) { sq = (sq|7); continue; }
int p = board[sq];
if(p) h ^= zob_piece[p][sq];
}
if(side_to_move==BLACK) h ^= zob_side;
h ^= zob_castle[castle_rights & 15];
if(ep_square!=-1) h ^= zob_ep[FILE_OF(ep_square)];
return h;
}

/* ========================= Random helpers ========================= */
static inline int sign(int x){ return (x>0)-(x<0); }

/* ========================= Move list ========================= */
typedef struct {
Move m;
int score;
} ScoredMove;

typedef struct {
ScoredMove list[256];
int count;
} MoveList;

static inline void ml_init(MoveList* ml){ ml->count = 0; }
static inline void ml_add(MoveList* ml, Move m, int score){
ml->list[ml->count].m = m;
ml->list[ml->count].score = score;
ml->count++;
}

/* ========================= Killer & History heuristics ========================= */
static Move killer_moves[2][MAX_PLY];
static int history_heur[2][128][128]; /* color, from, to */

static inline void history_clear(void){
memset(killer_moves, 0, sizeof(killer_moves));
memset(history_heur, 0, sizeof(history_heur));
}

/* ========================= TT (Transposition Table) ========================= */
typedef struct {
uint64_t key;
int16_t score;
int8_t depth;
uint8_t flag; /* 0=exact, 1=alpha, 2=beta */
uint32_t move;
} TTEntry;

static TTEntry *tt = NULL;
static size_t tt_size = 1<<20; /* entries */
static size_t tt_mask = (1<<20)-1;

static void tt_resize_mb(size_t mb){
if(tt) { free(tt); tt=NULL; }
size_t bytes = (size_t)mb * 1024ULL * 1024ULL;
size_t entries = bytes / sizeof(TTEntry);
if(entries < (1<<18)) entries = (1<<18); /* min size ~ 256k entries */
/* round down to power of two */
size_t pow2 = 1;
while((pow2<<1) <= entries) pow2 <<= 1;
tt_size = pow2;
tt_mask = tt_size - 1;
tt = (TTEntry*)malloc(tt_size * sizeof(TTEntry));
if(!tt){
fprintf(stderr, "Failed to allocate TT (%zu MB). Falling back to 16 MB.\n", mb);
tt_size = (1<<18);
tt_mask = tt_size - 1;
tt = (TTEntry*)malloc(tt_size * sizeof(TTEntry));
if(!tt){ fprintf(stderr, "Critical: cannot allocate TT.\n"); exit(1); }
}
memset(tt, 0, tt_size * sizeof(TTEntry));
}

static inline int score_to_tt(int score, int ply){
if(score > MATE_IN(100)) return score + ply; /* mate scores from root perspective */
if(score < -MATE_IN(100)) return score - ply;
return score;
}
static inline int score_from_tt(int score, int ply){
if(score > MATE_IN(100)) return score - ply;
if(score < -MATE_IN(100)) return score + ply;
return score;
}

static inline TTEntry* tt_probe(uint64_t key){
return &tt[key & tt_mask];
}
static inline void tt_store(uint64_t key, int depth, int flag, int score, Move m, int ply){
TTEntry* e = &tt[key & tt_mask];
if(e->key && e->depth > depth && e->key==key) return; /* keep deeper entry */
e->key = key;
e->depth = (int8_t) (depth>127?127:depth);
e->flag = (uint8_t)flag;
e->score = (int16_t)score_to_tt(score, ply);
e->move = m;
}

/* ========================= Position I/O ========================= */
static void set_startpos(void){
memset(board, 0, sizeof(board));
const int back_w[8] = { WR, WN, WB, WQ, WK, WB, WN, WR };
const int back_b[8] = { BR, BN, BB, BQ, BK, BB, BN, BR };
for(int f=0;f<8;f++){
board[SQ(f,0)] = back_w[f];
board[SQ(f,1)] = WP;
board[SQ(f,6)] = BP;
board[SQ(f,7)] = back_b[f];
}
side_to_move = WHITE;
castle_rights = W_KS|W_QS|B_KS|B_QS;
ep_square = -1;
halfmove_clock = 0;
fullmove_number = 1;
king_sq[WHITE] = SQ(4,0);
king_sq[BLACK] = SQ(4,7);
pos_key = compute_hash();
ply_history = 0;
hash_history[ply_history++] = pos_key;
}

static int parse_fen(const char* fen){
memset(board, 0, sizeof(board));
side_to_move = WHITE; castle_rights = 0; ep_square=-1; halfmove_clock=0; fullmove_number=1;
int f=0, r=7;
const char* p = fen;
while(*p && r>=0){
char c = *p++;
if(c==' '){ break; }
if(c=='/'){ f=0; r--; continue; }
if(isdigit((unsigned char)c)){ f += c - '0'; continue; }
int piece=0;
switch(c){
case 'P': piece=WP; break; case 'N': piece=WN; break; case 'B': piece=WB; break;
case 'R': piece=WR; break; case 'Q': piece=WQ; break; case 'K': piece=WK; break;
case 'p': piece=BP; break; case 'n': piece=BN; break; case 'b': piece=BB; break;
case 'r': piece=BR; break; case 'q': piece=BQ; break; case 'k': piece=BK; break;
default: return 0;
}
if(f>7||r<0) return 0;
board[SQ(f,r)] = piece;
if(piece==WK) king_sq[WHITE]=SQ(f,r);
if(piece==BK) king_sq[BLACK]=SQ(f,r);
f++;
}
/* side */
while(*p==' ') p++;
if(*p=='w') side_to_move=WHITE; else if(*p=='b') side_to_move=BLACK; else return 0;
while(*p && *p!=' ') p++;
if(*p==' ') p++;
/* castling */
castle_rights=0;
if(*p=='-') p++; else {
while(*p && *p!=' '){
if(*p=='K') castle_rights |= W_KS;
else if(*p=='Q') castle_rights |= W_QS;
else if(*p=='k') castle_rights |= B_KS;
else if(*p=='q') castle_rights |= B_QS;
else break;
p++;
}
}
while(*p==' ') p++;
/* ep */
if(*p=='-'){ ep_square=-1; p++; }
else{
if(p[0]>='a' && p[0]<='h' && p[1]>='1' && p[1]<='8'){
int file = p[0]-'a';
int rank = p[1]-'1';
ep_square = SQ(file, rank);
p+=2;
}else return 0;
}
while(*p==' ') p++;
/* halfmove */
if(isdigit((unsigned char)*p)){ halfmove_clock = atoi(p); while(*p && *p!=' ') p++; }
while(*p==' ') p++;
/* fullmove */
if(isdigit((unsigned char)*p)){ fullmove_number = atoi(p); }
pos_key = compute_hash();
ply_history = 0;
hash_history[ply_history++] = pos_key;
return 1;
}

/* ========================= Attack detection ========================= */
static bool square_attacked_by(int sq, int color){
/* pawns */
if(color==WHITE){
int s1 = sq - 15, s2 = sq - 17;
if(SQ_OK(s1) && board[s1]==WP) return true;
if(SQ_OK(s2) && board[s2]==WP) return true;
}else{
int s1 = sq + 15, s2 = sq + 17;
if(SQ_OK(s1) && board[s1]==BP) return true;
if(SQ_OK(s2) && board[s2]==BP) return true;
}
/* knights */
for(int i=0;i<8;i++){
int s = sq + knight_dirs;
if(!SQ_OK(s)) continue;
int p = board[s];
if(p && color_of(p)==color && type_of(p)==2) return true;
}
/* bishops/queens */
for(int d=0;d<4;d++){
int dir = bishop_dirs[d];
int s = sq + dir;
while(SQ_OK(s)){
int p = board[s];
if(p){
if(color_of(p)==color){
int t = type_of(p);
if(t==3 || t==5) return true;
}
break;
}
s += dir;
}
}
/* rooks/queens */
for(int d=0;d<4;d++){
int dir = rook_dirs[d];
int s = sq + dir;
while(SQ_OK(s)){
int p = board[s];
if(p){
if(color_of(p)==color){
int t = type_of(p);
if(t==4 || t==5) return true;
}
break;
}
s += dir;
}
}
/* king */
for(int i=0;i<8;i++){
int s = sq + king_dirs;
if(!SQ_OK(s)) continue;
int p = board[s];
if(p && color_of(p)==color && type_of(p)==6) return true;
}
return false;
}

static inline bool in_check(int color){
return square_attacked_by(king_sq[color], color^1);
}

/* ========================= Move generation (pseudo-legal) ========================= */
static inline int mvv_lva(int attacker, int victim){
/* capture ordering: victim value * 10 - attacker value (bigger first) */
return piece_value[type_of(victim)]*10 - piece_value[type_of(attacker)];
}

static void gen_moves_side(MoveList* ml, int side, bool captures_only){
ml_init(ml);
for(int sq=0;sq<128;sq++){
if(!SQ_OK(sq)){ sq = (sq|7); continue; }
int p = board[sq];
if(!p || color_of(p)!=side) continue;
int pt = type_of(p);
if(pt==1){ /* pawn */
if(side==WHITE){
int up = sq + 16;
if(!captures_only){
if(SQ_OK(up) && board[up]==EMPTY){
int toRank = RANK_OF(up);
if(toRank==7){
ml_add(ml, MOVE(sq,up,5,FL_PROMO), 0); /* promote to Q */
ml_add(ml, MOVE(sq,up,4,FL_PROMO), 0); /* R */
ml_add(ml, MOVE(sq,up,3,FL_PROMO), 0); /* B */
ml_add(ml, MOVE(sq,up,2,FL_PROMO), 0); /* N */
}else{
ml_add(ml, MOVE(sq,up,0,0), 0);
if(RANK_OF(sq)==1){
int up2 = sq + 32;
if(SQ_OK(up2) && board[up2]==EMPTY){
ml_add(ml, MOVE(sq,up2,0,FL_DPP), 0);
}
}
}
}
}
int capL = sq + 15, capR = sq + 17;
if(SQ_OK(capL)){
int v = board[capL];
if(v && color_of(v)!=side){
int promo = (RANK_OF(capL)==7)?5:0;
ml_add(ml, MOVE(sq,capL,promo,FL_CAPTURE | (promo?FL_PROMO:0)),
mvv_lva(p,v));
}else if(capL==ep_square){
ml_add(ml, MOVE(sq,capL,0,FL_EPCAP|FL_CAPTURE), 1050);
}
}
if(SQ_OK(capR)){
int v = board[capR];
if(v && color_of(v)!=side){
int promo = (RANK_OF(capR)==7)?5:0;
ml_add(ml, MOVE(sq,capR,promo,FL_CAPTURE | (promo?FL_PROMO:0)),
mvv_lva(p,v));
}else if(capR==ep_square){
ml_add(ml, MOVE(sq,capR,0,FL_EPCAP|FL_CAPTURE), 1050);
}
}
}else{ /* black */
int dn = sq - 16;
if(!captures_only){
if(SQ_OK(dn) && board[dn]==EMPTY){
int toRank = RANK_OF(dn);
if(toRank==0){
ml_add(ml, MOVE(sq,dn,5,FL_PROMO), 0);
ml_add(ml, MOVE(sq,dn,4,FL_PROMO), 0);
ml_add(ml, MOVE(sq,dn,3,FL_PROMO), 0);
ml_add(ml, MOVE(sq,dn,2,FL_PROMO), 0);
}else{
ml_add(ml, MOVE(sq,dn,0,0), 0);
if(RANK_OF(sq)==6){
int dn2 = sq - 32;
if(SQ_OK(dn2) && board[dn2]==EMPTY){
ml_add(ml, MOVE(sq,dn2,0,FL_DPP), 0);
}
}
}
}
}
int capL = sq - 17, capR = sq - 15;
if(SQ_OK(capL)){
int v = board[capL];
if(v && color_of(v)!=side){
int promo = (RANK_OF(capL)==0)?5:0;
ml_add(ml, MOVE(sq,capL,promo,FL_CAPTURE | (promo?FL_PROMO:0)),
mvv_lva(p,v));
}else if(capL==ep_square){
ml_add(ml, MOVE(sq,capL,0,FL_EPCAP|FL_CAPTURE), 1050);
}
}
if(SQ_OK(capR)){
int v = board[capR];
if(v && color_of(v)!=side){
int promo = (RANK_OF(capR)==0)?5:0;
ml_add(ml, MOVE(sq,capR,promo,FL_CAPTURE | (promo?FL_PROMO:0)),
mvv_lva(p,v));
}else if(capR==ep_square){
ml_add(ml, MOVE(sq,capR,0,FL_EPCAP|FL_CAPTURE), 1050);
}
}
}
}else if(pt==2){ /* knight */
for(int i=0;i<8;i++){
int to = sq + knight_dirs;
if(!SQ_OK(to)) continue;
int v = board[to];
if(v && color_of(v)==side) continue;
if(v) ml_add(ml, MOVE(sq,to,0,FL_CAPTURE), mvv_lva(p,v));
else if(!captures_only) ml_add(ml, MOVE(sq,to,0,0), 0);
}
}else if(pt==3 || pt==4 || pt==5){ /* bishop/rook/queen */
const int* dirs = (pt==3)?bishop_dirs : (pt==4?rook_dirs:king_dirs /* both sets */);
int dir_count = (pt==3 || pt==4) ? 4 : 8;
for(int d=0; d<dir_count; d++){
int to = sq + dirs[d];
while(SQ_OK(to)){
int v = board[to];
if(v){
if(color_of(v)!=side)
ml_add(ml, MOVE(sq,to,0,FL_CAPTURE), mvv_lva(p,v));
break;
}else{
if(!captures_only) ml_add(ml, MOVE(sq,to,0,0), 0);
}
to += dirs[d];
}
}
}else if(pt==6){ /* king */
for(int i=0;i<8;i++){
int to = sq + king_dirs;
if(!SQ_OK(to)) continue;
int v = board[to];
if(v && color_of(v)==side) continue;
if(v) ml_add(ml, MOVE(sq,to,0,FL_CAPTURE), mvv_lva(p,v));
else if(!captures_only) ml_add(ml, MOVE(sq,to,0,0), 0);
}
/* Castling */
if(!captures_only){
if(side==WHITE){
if((castle_rights & W_KS) &&
board[SQ(5,0)]==EMPTY && board[SQ(6,0)]==EMPTY &&
!square_attacked_by(SQ(4,0),BLACK) &&
!square_attacked_by(SQ(5,0),BLACK) &&
!square_attacked_by(SQ(6,0),BLACK)){
ml_add(ml, MOVE(SQ(4,0), SQ(6,0), 0, FL_CASTLE), 0);
}
if((castle_rights & W_QS) &&
board[SQ(3,0)]==EMPTY && board[SQ(2,0)]==EMPTY && board[SQ(1,0)]==EMPTY &&
!square_attacked_by(SQ(4,0),BLACK) &&
!square_attacked_by(SQ(3,0),BLACK) &&
!square_attacked_by(SQ(2,0),BLACK)){
ml_add(ml, MOVE(SQ(4,0), SQ(2,0), 0, FL_CASTLE), 0);
}
}else{
if((castle_rights & B_KS) &&
board[SQ(5,7)]==EMPTY && board[SQ(6,7)]==EMPTY &&
!square_attacked_by(SQ(4,7),WHITE) &&
!square_attacked_by(SQ(5,7),WHITE) &&
!square_attacked_by(SQ(6,7),WHITE)){
ml_add(ml, MOVE(SQ(4,7), SQ(6,7), 0, FL_CASTLE), 0);
}
if((castle_rights & B_QS) &&
board[SQ(3,7)]==EMPTY && board[SQ(2,7)]==EMPTY && board[SQ(1,7)]==EMPTY &&
!square_attacked_by(SQ(4,7),WHITE) &&
!square_attacked_by(SQ(3,7),WHITE) &&
!square_attacked_by(SQ(2,7),WHITE)){
ml_add(ml, MOVE(SQ(4,7), SQ(2,7), 0, FL_CASTLE), 0);
}
}
}
}
}
}

/* ========================= Make/Unmake ========================= */
typedef struct {
int castle, ep, halfmove;
uint64_t hash;
int captured;
int moved_piece;
int from, to;
int promo;
} Undo;

static Undo undo_stack[MAX_PLY];
static int sp = 0;

static inline void hash_toggle_piece(int p, int sq){ pos_key ^= zob_piece[p][sq]; }
static inline void hash_toggle_side(void){ pos_key ^= zob_side; }
static inline void hash_set_castle(int oldc, int newc){ pos_key ^= zob_castle[oldc & 15]; pos_key ^= zob_castle[newc & 15]; }
static inline void hash_set_ep(int old_ep, int new_ep){
if(old_ep!=-1) pos_key ^= zob_ep[FILE_OF(old_ep)];
if(new_ep!=-1) pos_key ^= zob_ep[FILE_OF(new_ep)];
}

static inline void remove_castle_rights_by_move(int piece, int from, int to){
int old = castle_rights, newc = castle_rights;
if(piece==WK){ newc &= ~(W_KS|W_QS); }
if(piece==BK){ newc &= ~(B_KS|B_QS); }
if(piece==WR){
if(from==SQ(0,0)) newc &= ~W_QS;
if(from==SQ(7,0)) newc &= ~W_KS;
}
if(piece==BR){
if(from==SQ(0,7)) newc &= ~B_QS;
if(from==SQ(7,7)) newc &= ~B_KS;
}
if(to==SQ(0,0)) newc &= ~W_QS;
if(to==SQ(7,0)) newc &= ~W_KS;
if(to==SQ(0,7)) newc &= ~B_QS;
if(to==SQ(7,7)) newc &= ~B_KS;
if(newc != old){ hash_set_castle(old, newc); castle_rights = newc; }
}

static int make_move(Move m){
int from = MFROM(m), to = MTO(m);
int flags = MFLAGS(m);
int promo = MPROM(m);
int us = side_to_move;
int them = us^1;
int piece = board[from];
int captured = 0;

Undo* u = &undo_stack[sp++];
u->castle = castle_rights;
u->ep = ep_square;
u->halfmove = halfmove_clock;
u->hash = pos_key;
u->captured = EMPTY;
u->moved_piece = piece;
u->from = from;
u->to = to;
u->promo = promo;

/* clear EP */
hash_set_ep(ep_square, -1);
ep_square = -1;

/* update halfmove */
if(type_of(piece)==1) halfmove_clock = 0; else halfmove_clock++;

/* hash piece move */
hash_toggle_piece(piece, from);
board[from] = EMPTY;

if(flags & FL_EPCAP){
int cap_sq = (us==WHITE) ? (to - 16) : (to + 16);
captured = board[cap_sq];
u->captured = captured;
board[cap_sq] = EMPTY;
hash_toggle_piece(captured, cap_sq);
halfmove_clock = 0;
} else if(board[to]!=EMPTY){
captured = board[to];
u->captured = captured;
hash_toggle_piece(captured, to);
halfmove_clock = 0;
}

if(flags & FL_CASTLE){
if(piece==WK && to==SQ(6,0)){
/* white short */
board[SQ(5,0)] = WR; board[SQ(7,0)] = EMPTY;
hash_toggle_piece(WR, SQ(5,0)); hash_toggle_piece(WR, SQ(7,0));
}else if(piece==WK && to==SQ(2,0)){
/* white long */
board[SQ(3,0)] = WR; board[SQ(0,0)] = EMPTY;
hash_toggle_piece(WR, SQ(3,0)); hash_toggle_piece(WR, SQ(0,0));
}else if(piece==BK && to==SQ(6,7)){
board[SQ(5,7)] = BR; board[SQ(7,7)] = EMPTY;
hash_toggle_piece(BR, SQ(5,7)); hash_toggle_piece(BR, SQ(7,7));
}else if(piece==BK && to==SQ(2,7)){
board[SQ(3,7)] = BR; board[SQ(0,7)] = EMPTY;
hash_toggle_piece(BR, SQ(3,7)); hash_toggle_piece(BR, SQ(0,7));
}
}

/* promotions */
if((flags & FL_PROMO) && promo){
int prom_piece = (us==WHITE) ? (promo) : (promo+6);
/* pawn has been removed from 'from' already */
board[to] = prom_piece;
hash_toggle_piece(prom_piece, to);
}else{
board[to] = piece;
hash_toggle_piece(piece, to);
}

/* update king sq */
if(type_of(piece)==6){
king_sq[us] = to;
}

/* set EP on double pawn push */
if(flags & FL_DPP){
ep_square = (us==WHITE) ? (to - 16) : (to + 16);
hash_set_ep(-1, ep_square);
}

/* update castle rights */
remove_castle_rights_by_move(piece, from, to);

/* side to move */
side_to_move ^= 1;
hash_toggle_side();

/* legality: did we leave our own king in check? */
if(in_check(them)){
/* illegal, undo */
side_to_move ^= 1;
hash_toggle_side();

/* undo piece restoration */
hash_toggle_piece(board[to], to);
board[to] = EMPTY;

if(flags & FL_EPCAP){
int cap_sq = (us==WHITE) ? (to - 16) : (to + 16);
board[from] = piece; hash_toggle_piece(piece, from);
board[cap_sq] = captured; hash_toggle_piece(captured, cap_sq);
}else{
board[from] = piece; hash_toggle_piece(piece, from);
if(captured){ board[to] = captured; hash_toggle_piece(captured, to); }
}
/* undo rook for castling */
if(flags & FL_CASTLE){
if(piece==WK && to==SQ(6,0)){
board[SQ(7,0)] = WR; hash_toggle_piece(WR, SQ(7,0));
board[SQ(5,0)] = EMPTY; hash_toggle_piece(WR, SQ(5,0));
}else if(piece==WK && to==SQ(2,0)){
board[SQ(0,0)] = WR; hash_toggle_piece(WR, SQ(0,0));
board[SQ(3,0)] = EMPTY; hash_toggle_piece(WR, SQ(3,0));
}else if(piece==BK && to==SQ(6,7)){
board[SQ(7,7)] = BR; hash_toggle_piece(BR, SQ(7,7));
board[SQ(5,7)] = EMPTY; hash_toggle_piece(BR, SQ(5,7));
}else if(piece==BK && to==SQ(2,7)){
board[SQ(0,7)] = BR; hash_toggle_piece(BR, SQ(0,7));
board[SQ(3,7)] = EMPTY; hash_toggle_piece(BR, SQ(3,7));
}
}
/* restore castle/ep/halfmove/hash */
hash_set_castle(castle_rights, u->castle);
castle_rights = u->castle;
hash_set_ep(ep_square, u->ep);
ep_square = u->ep;
halfmove_clock = u->halfmove;
pos_key = u->hash;
sp--;
return 0;
}

/* ok */
if(us==BLACK) fullmove_number++;
return 1;
}

static void unmake_move(void){
Undo* u = &undo_stack[--sp];
int from = u->from, to = u->to;
int piece_moved = u->moved_piece;
int captured = u->captured;
int flags = 0;
/* Recover flags from context if needed (not necessary for undo correctness) */

side_to_move ^= 1;
hash_toggle_side();

/* remove piece from 'to' */
int on_to = board[to];
hash_toggle_piece(on_to, to);
board[to] = EMPTY;

/* undo castling rook move if castle */
if(type_of(piece_moved)==6 && abs(FILE_OF(to)-FILE_OF(from))==2){
if(piece_moved==WK && to==SQ(6,0)){
board[SQ(7,0)] = WR; hash_toggle_piece(WR, SQ(7,0));
board[SQ(5,0)] = EMPTY; hash_toggle_piece(WR, SQ(5,0));
}else if(piece_moved==WK && to==SQ(2,0)){
board[SQ(0,0)] = WR; hash_toggle_piece(WR, SQ(0,0));
board[SQ(3,0)] = EMPTY; hash_toggle_piece(WR, SQ(3,0));
}else if(piece_moved==BK && to==SQ(6,7)){
board[SQ(7,7)] = BR; hash_toggle_piece(BR, SQ(7,7));
board[SQ(5,7)] = EMPTY; hash_toggle_piece(BR, SQ(5,7));
}else if(piece_moved==BK && to==SQ(2,7)){
board[SQ(0,7)] = BR; hash_toggle_piece(BR, SQ(0,7));
board[SQ(3,7)] = EMPTY; hash_toggle_piece(BR, SQ(3,7));
}
}

/* put back pawn if it was a promotion, else moved piece */
if(type_of(piece_moved)==1 && (RANK_OF(to)==7 || RANK_OF(to)==0) && type_of(on_to)!=1){
/* promotion was made; restore pawn at 'from' */
int pawn = (color_of(piece_moved)==WHITE)? WP : BP;
board[from] = pawn; hash_toggle_piece(pawn, from);
}else{
board[from] = piece_moved; hash_toggle_piece(piece_moved, from);
}
if(type_of(piece_moved)==6) king_sq[color_of(piece_moved)] = from;

/* restore captured */
if(u->captured){
if(type_of(u->captured)==1 && FILE_OF(to)!=FILE_OF(from) && board[to]==EMPTY){
/* was en-passant */
int cap_sq = (color_of(u->captured)==WHITE) ? (to - 16) : (to + 16);
board[cap_sq] = u->captured; hash_toggle_piece(u->captured, cap_sq);
}else{
board[to] = u->captured; hash_toggle_piece(u->captured, to);
}
}

/* restore state */
hash_set_castle(castle_rights, u->castle);
castle_rights = u->castle;
hash_set_ep(ep_square, u->ep);
ep_square = u->ep;
halfmove_clock = u->halfmove;
pos_key = u->hash;

if(side_to_move==BLACK) fullmove_number--;
}

/* ========================= Evaluation ========================= */
/* Simple evaluation:
- Material
- Development (penalty for undeveloped minor pieces early; bonus for castling)
- Central control (attacking d4/e4/d5/e5)
- Mobility (pseudo-legal mobility for non-pawns)
- King safety: attackers to king ring (non-linear)
*/

static int attackers_to_square_by(int sq, int side){
int count = 0;
/* pawns */
if(side==WHITE){
int s1 = sq - 15, s2 = sq - 17;
if(SQ_OK(s1) && board[s1]==WP) count++;
if(SQ_OK(s2) && board[s2]==WP) count++;
}else{
int s1 = sq + 15, s2 = sq + 17;
if(SQ_OK(s1) && board[s1]==BP) count++;
if(SQ_OK(s2) && board[s2]==BP) count++;
}
/* knights */
for(int i=0;i<8;i++){
int s = sq + knight_dirs;
if(!SQ_OK(s)) continue;
int p = board[s];
if(p && color_of(p)==side && type_of(p)==2) count++;
}
/* bishops/queens */
for(int d=0;d<4;d++){
int dir = bishop_dirs[d];
int s = sq + dir;
while(SQ_OK(s)){
int p = board[s];
if(p){
if(color_of(p)==side){
int t=type_of(p);
if(t==3||t==5){ count++; }
}
break;
}
s += dir;
}
}
/* rooks/queens */
for(int d=0;d<4;d++){
int dir = rook_dirs[d];
int s = sq + dir;
while(SQ_OK(s)){
int p = board[s];
if(p){
if(color_of(p)==side){
int t=type_of(p);
if(t==4||t==5){ count++; }
}
break;
}
s += dir;
}
}
/* king */
for(int i=0;i<8;i++){
int s = sq + king_dirs;
if(!SQ_OK(s)) continue;
int p = board[s];
if(p && color_of(p)==side && type_of(p)==6) count++;
}
return count;
}

static int mobility_side(int side){
int score=0;
for(int sq=0;sq<128;sq++){
if(!SQ_OK(sq)){ sq=(sq|7); continue; }
int p = board[sq];
if(!p || color_of(p)!=side) continue;
int pt = type_of(p);
if(pt==2){ /* knight */
for(int i=0;i<8;i++){
int to = sq + knight_dirs; if(!SQ_OK(to)) continue;
int v = board[to];
if(v==EMPTY || color_of(v)!=side) score+=2;
}
}else if(pt==3 || pt==4 || pt==5){
const int* dirs = (pt==3)?bishop_dirs:(pt==4?rook_dirs:king_dirs);
int dir_count = (pt==3 || pt==4)?4:8;
for(int d=0;d<dir_count;d++){
int to = sq + dirs[d];
while(SQ_OK(to)){
int v = board[to];
if(v){ if(color_of(v)!=side) score+=3; break; }
score+=1;
to += dirs[d];
}
}
}
}
return score;
}

static int eval(void){
int mat[2]={0,0};
for(int sq=0;sq<128;sq++){
if(!SQ_OK(sq)){ sq=(sq|7); continue; }
int p = board[sq];
if(p){
mat[color_of(p)] += piece_value[type_of(p)];
}
}
int score = (mat[WHITE] - mat[BLACK]);

/* Development penalties early (first ~16 plies from start). Use fullmove_number approx */
int undeveloped_w = 0, undeveloped_b=0;
if(fullmove_number <= 20){
if(board[SQ(1,0)]==WN) undeveloped_w += 8;
if(board[SQ(6,0)]==WN) undeveloped_w += 8;
if(board[SQ(2,0)]==WB) undeveloped_w += 8;
if(board[SQ(5,0)]==WB) undeveloped_w += 8;
if(board[SQ(1,7)]==BN) undeveloped_b += 8;
if(board[SQ(6,7)]==BN) undeveloped_b += 8;
if(board[SQ(2,7)]==BB) undeveloped_b += 8;
if(board[SQ(5,7)]==BB) undeveloped_b += 8;
/* bonus for castled king */
if(king_sq[WHITE]==SQ(6,0) || king_sq[WHITE]==SQ(2,0)) score += 12;
if(king_sq[BLACK]==SQ(6,7) || king_sq[BLACK]==SQ(2,7)) score -= 12;
}
score -= undeveloped_w;
score += undeveloped_b;

/* Central control (d4,e4,d5,e5 attacks) */
int center_sqs[4] = { SQ(3,3), SQ(4,3), SQ(3,4), SQ(4,4) };
int ctrl_w=0, ctrl_b=0;
for(int i=0;i<4;i++){
ctrl_w += attackers_to_square_by(center_sqs, WHITE) ? 1 : 0;
ctrl_b += attackers_to_square_by(center_sqs[i], BLACK) ? 1 : 0;
}
score += (ctrl_w - ctrl_b)*8;

/* Mobility */
int mob_w = mobility_side(WHITE);
int mob_b = mobility_side(BLACK);
score += (mob_w - mob_b);

/* King safety (non-linear attackers to ring) */
int ring_w[8]; int rwc=0;
int ring_b[8]; int rbc=0;
/* Build ring squares around each king */
for(int i=0;i<8;i++){
int s = king_dirs[i];
int sw = king_sq[WHITE] + s;
if(SQ_OK(sw)) ring_w[rwc++] = sw;
int sb = king_sq[BLACK] + s;
if(SQ_OK(sb)) ring_b[rbc++] = sb;
}
int atk_w=0, atk_b=0;
for(int i=0;i<rbc;i++) if(attackers_to_square_by(ring_b[i], WHITE)) atk_w++;
for(int i=0;i<rwc;i++) if(attackers_to_square_by(ring_w[i], BLACK)) atk_b++;
score += (atk_w*atk_w*8) - (atk_b*atk_b*8);

/* Side to move perspective */
return (side_to_move==WHITE)? score : -score;
}

/* ========================= Quiescence ========================= */
static uint64_t nodes = 0;
static uint64_t start_time_ms = 0;
static uint64_t stop_time_ms = 0;
static int stop_search = 0;

static inline void check_time(void){
if(stop_time_ms && (now_ms() >= stop_time_ms)) stop_search = 1;
}

static int quiescence(int alpha, int beta, int ply){
if(stop_search) return alpha;
nodes++;

int stand = eval();
if(stand >= beta) return beta;
if(alpha < stand) alpha = stand;

/* If in check, we must generate full moves to resolve it */
if(in_check(side_to_move)){
MoveList ml; gen_moves_side(&ml, side_to_move, false);
for(int i=0;i<ml.count;i++){
Move m = ml.list[i].m;
if(!make_move(m)) continue;
int score = -quiescence(-beta, -alpha, ply+1);
unmake_move();
if(stop_search) return alpha;
if(score >= beta) return beta;
if(score > alpha) alpha = score;
}
return alpha;
}

/* normal quiescence: only captures and ep captures */
MoveList ml; gen_moves_side(&ml, side_to_move, true);
/* order captures by MVV-LVA score (already set) — do a simple insertion sort */
for(int i=1;i<ml.count;i++){
ScoredMove key = ml.list[i];
int j=i-1;
while(j>=0 && ml.list[j].score < key.score){
ml.list[j+1] = ml.list[j]; j--;
}
ml.list[j+1] = key;
}
for(int i=0;i<ml.count;i++){
Move m = ml.list[i].m;
if(!(MFLAGS(m) & FL_CAPTURE)) continue; /* ensure capture */
if(!make_move(m)) continue;
int score = -quiescence(-beta, -alpha, ply+1);
unmake_move();
if(stop_search) return alpha;
if(score >= beta) return beta;
if(score > alpha) alpha = score;
}
return alpha;
}

/* ========================= PV handling ========================= */
static Move pv_table[MAX_PLY][MAX_PLY];
static int pv_len[MAX_PLY];

static inline void pv_clear(void){
for(int i=0;i<MAX_PLY;i++){ pv_len[i]=0; }
}

/* ========================= Search (alpha-beta) ========================= */
static int search(int depth, int alpha, int beta, int ply){
pv_len[ply] = 0;
if(stop_search){ return alpha; }
if(ply >= MAX_PLY-1) return eval();

nodes++;
if((nodes & 2047ULL)==0) check_time();
if(stop_search) return alpha;

int is_in_check = in_check(side_to_move);
if(is_in_check) depth++; /* check extension */

/* draw checks */
if(halfmove_clock >= 100){
return 0;
}
/* repetition: check earlier positions every 2 plies back until last capture/pawn move */
for(int i=ply_history-2; i>=0 && i>ply_history - 2*halfmove_clock - 2; i-=2){
if(hash_history[i]==pos_key) return 0;
}

if(depth <= 0){
return quiescence(alpha, beta, ply);
}

/* Transposition table probe */
Move tt_move = 0;
TTEntry* e = tt_probe(pos_key);
if(e->key == pos_key && e->depth >= depth){
int tt_score = score_from_tt(e->score, ply);
if(e->flag==0) return tt_score;
else if(e->flag==1 && tt_score <= alpha) return alpha;
else if(e->flag==2 && tt_score >= beta) return beta;
tt_move = (Move)e->move;
}

MoveList ml; gen_moves_side(&ml, side_to_move, false);

/* simple move ordering: TT move first, then captures by MVV-LVA, then killers, then history */
/* score moves */
for(int i=0;i<ml.count;i++){
Move m = ml.list[i].m;
int score = 0;
if(m==tt_move) score = 1<<30;
else if(MFLAGS(m)&FL_CAPTURE){
int from = MFROM(m), to = MTO(m);
int attacker = board[from];
int victim;
int capflag = MFLAGS(m)&FL_EPCAP;
if(capflag){
victim = (side_to_move==WHITE)? BP : WP;
}else{
victim = board[to];
}
score = 1<<20 + mvv_lva(attacker, victim);
}else{
if(killer_moves[0][ply]==m) score = (1<<19);
else if(killer_moves[1][ply]==m) score = (1<<19)-1;
else score = history_heur[side_to_move][MFROM(m)][MTO(m)] & ((1<<18)-1);
}
ml.list[i].score = score;
}
/* sort (insertion sort) */
for(int i=1;i<ml.count;i++){
ScoredMove key = ml.list[i];
int j=i-1;
while(j>=0 && ml.list[j].score < key.score){
ml.list[j+1] = ml.list[j]; j--;
}
ml.list[j+1] = key;
}

int legal = 0;
Move best_move = 0;
int best_score = -INF;
int orig_alpha = alpha;

for(int i=0;i<ml.count;i++){
Move m = ml.list[i].m;
if(!make_move(m)) continue;
hash_history[ply_history++] = pos_key;
int score = -search(depth-1, -beta, -alpha, ply+1);
ply_history--;
unmake_move();
if(stop_search) return alpha;

if(score > best_score){
best_score = score;
best_move = m;
if(score > alpha){
alpha = score;
/* update PV: prefix best child PV with this move */
pv_table[ply][0] = m;
for(int j=0; j<pv_len[ply+1]; j++) pv_table[ply][j+1] = pv_table[ply+1][j];
pv_len[ply] = pv_len[ply+1] + 1;
}
if(alpha >= beta){
/* update killers/history for quiet moves */
if(!(MFLAGS(m)&FL_CAPTURE)){
if(killer_moves[0][ply] != m){
killer_moves[1][ply] = killer_moves[0][ply];
killer_moves[0][ply] = m;
}
history_heur[side_to_move][MFROM(m)][MTO(m)] += depth*depth;
}
break;
}
}
legal++;
}

if(legal==0){
if(is_in_check) return -MATE_IN(ply);
return 0; /* stalemate */
}

/* store TT */
int flag = 0;
if(best_score <= orig_alpha) flag=1;
else if(best_score >= beta) flag=2;
else flag=0;
tt_store(pos_key, depth, flag, best_score, best_move, ply);

return best_score;
}

/* ========================= UCI helpers ========================= */
static const char* startpos_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";

static void move_to_str(Move m, char out[8]){
int from = MFROM(m), to = MTO(m);
out[0] = 'a' + FILE_OF(from);
out[1] = '1' + RANK_OF(from);
out[2] = 'a' + FILE_OF(to);
out[3] = '1' + RANK_OF(to);
int promo = MPROM(m);
if(promo){
char pc='q';
if(promo==2) pc='n'; else if(promo==3) pc='b'; else if(promo==4) pc='r'; else pc='q';
/* adjust case for side (always lowercase for UCI) */
out[4] = pc;
out[5] = 0;
}else{
out[4] = 0;
}
}

static Move parse_move_uci(const char* s){
int len = (int)strlen(s);
if(len < 4) return 0;
int ff = s[0]-'a', fr = s[1]-'1';
int tf = s[2]-'a', tr = s[3]-'1';
if(ff<0||ff>7||tf<0||tf>7||fr<0||fr>7||tr<0||tr>7) return 0;
int from = SQ(ff,fr), to = SQ(tf,tr);
int want_promo=0;
if(len>=5){
char c = tolower((unsigned char)s[4]);
if(c=='q') want_promo=5;
else if(c=='r') want_promo=4;
else if(c=='b') want_promo=3;
else if(c=='n') want_promo=2;
}

MoveList ml; gen_moves_side(&ml, side_to_move, false);
/* find matching legal move */
for(int i=0;i<ml.count;i++){
Move m = ml.list[i].m;
if(MFROM(m)==from && MTO(m)==to){
if(MPROM(m) && want_promo && MPROM(m)!=want_promo) continue;
if(make_move(m)){
unmake_move();
return m;
}
}
}
return 0;
}

/* ========================= Iterative deepening & time control ========================= */
typedef struct {
int use_time; /* boolean */
int depth;
uint64_t movetime_ms;
uint64_t wtime, btime, winc, binc;
int movestogo;
} GoParams;

static void uci_info_line(int depth, int score, Move best, int pvlen){
uint64_t now = now_ms();
uint64_t elapsed = (now - start_time_ms);
if(elapsed==0) elapsed=1;
uint64_t nps = (nodes * 1000ULL) / elapsed;
char bestStr[8]; move_to_str(best, bestStr);
/* Build PV string */
char pvbuf[4096]; pvbuf[0]=0;
for(int i=0;i<pvlen && i<MAX_PLY;i++){
char s[8]; move_to_str(pv_table[0][i], s);
if(i) strcat(pvbuf, " ");
strcat(pvbuf, s);
}
if(abs(score) > MATE - 1000){
int mate_in = (score>0) ? (MATE - score + 1)/2 : - (MATE + score + 1)/2;
printf("info depth %d score mate %d time %" PRIu64 " nodes %" PRIu64 " nps %" PRIu64 " pv %s\n",
depth, mate_in, elapsed, nodes, nps, pvbuf);
}else{
printf("info depth %d score cp %d time %" PRIu64 " nodes %" PRIu64 " nps %" PRIu64 " pv %s\n",
depth, score, elapsed, nodes, nps, pvbuf);
}
fflush(stdout);
}

static uint64_t compute_move_budget_ms(const GoParams* gp){
if(gp->movetime_ms) return gp->movetime_ms;
if(gp->wtime==0 && gp->btime==0) return 0; /* no time control => search to depth only */
uint64_t mytime = (side_to_move==WHITE) ? gp->wtime : gp->btime;
uint64_t myinc = (side_to_move==WHITE) ? gp->winc : gp->binc;
int mtg = gp->movestogo ? gp->movestogo : 30;
uint64_t budget = mytime / (mtg+2) + myinc/2;
if(budget > mytime/3) budget = mytime/3;
if(budget < 10) budget = 10;
return budget;
}

static Move search_root(const GoParams* gp){
nodes = 0;
stop_search = 0;
pv_clear();
history_clear();

uint64_t budget = compute_move_budget_ms(gp);
start_time_ms = now_ms();
stop_time_ms = budget ? (start_time_ms + budget) : 0;

Move bestmove = 0;
int bestscore = -INF;

MoveList root; gen_moves_side(&root, side_to_move, false);
/* Ensure legal */
MoveList root_legal; ml_init(&root_legal);
for(int i=0;i<root.count;i++){
if(make_move(root.list[i].m)){
unmake_move();
ml_add(&root_legal, root.list[i].m, 0);
}
}

if(root_legal.count==0){
/* no moves */
return 0;
}

for(int depth=1; depth <= (gp->depth?gp->depth:64); depth++){
int alpha = -INF, beta = INF;
bestscore = -INF;
bestmove = root_legal.list[0].m;
pv_len[0]=0;

for(int i=0;i<root_legal.count;i++){
Move m = root_legal.list[i].m;
if(!make_move(m)) continue;
hash_history[ply_history++] = pos_key;
int score = -search(depth-1, -beta, -alpha, 1);
ply_history--;
unmake_move();
if(stop_search) break;
if(score > bestscore){
bestscore = score;
bestmove = m;
alpha = score;
/* build PV from child */
pv_table[0][0] = m;
for(int j=0; j<pv_len[1]; j++) pv_table[0][j+1] = pv_table[1][j];
pv_len[0] = pv_len[1] + 1;
}
}
if(stop_search) break;
uci_info_line(depth, bestscore, bestmove, pv_len[0]);
/* aspiration windows can be added later; keep brute force flavour */
}

return bestmove;
}

/* ========================= UCI loop ========================= */
static void uci_loop(void){
char line[4096];
printf("id name GPT-5 Pro Chess\n");
printf("id author OpenAI (GPT-5 Pro)\n");
printf("option name Hash type spin default 64 min 16 max 65536\n");
printf("option name Threads type spin default 1 min 1 max 32\n");
printf("uciok\n");
fflush(stdout);

GoParams gp = {0};
gp.depth = 0; gp.movetime_ms=0; gp.wtime=gp.btime=gp.winc=gp.binc=0; gp.movestogo=0;

while(fgets(line, sizeof(line), stdin)){
if(strncmp(line,"uci",3)==0){
printf("id name GPT-5 Pro Chess\n");
printf("id author OpenAI (GPT-5 Pro)\n");
printf("option name Hash type spin default 64 min 16 max 65536\n");
printf("option name Threads type spin default 1 min 1 max 32\n");
printf("uciok\n");
fflush(stdout);
}else if(strncmp(line,"isready",7)==0){
printf("readyok\n"); fflush(stdout);
}else if(strncmp(line,"ucinewgame",10)==0){
set_startpos();
if(tt) memset(tt,0,tt_size*sizeof(TTEntry));
}else if(strncmp(line,"position",8)==0){
char* p = line + 8;
while(*p==' ') p++;
if(strncmp(p,"startpos",8)==0){
set_startpos();
p += 8;
}else if(strncmp(p,"fen",3)==0){
p+=3; while(*p==' ') p++;
char fenbuf[256]; int idx=0;
/* read fen up to " moves" or end-of-line */
while(*p && *p!='\n'){
if(strncmp(p," moves",6)==0) break;
if(idx<255) fenbuf[idx++] = *p;
p++;
}
fenbuf[idx]=0;
if(!parse_fen(fenbuf)){
/* fallback */
set_startpos();
}
}else{
/* default to startpos */
set_startpos();
}
/* moves list */
char* movespos = strstr(line, "moves");
if(movespos){
movespos += 5;
while(*movespos){
while(*movespos==' ') movespos++;
if(!*movespos || *movespos=='\n') break;
char mv[8]={0};
int i=0;
while(*movespos && *movespos!=' ' && *movespos!='\n' && i<7){
mv[i++] = *movespos++;
}
mv[i]=0;
if(i>=4){
Move m = parse_move_uci(mv);
if(m) { make_move(m); hash_history[ply_history++] = pos_key; }
}
}
}
}else if(strncmp(line,"setoption",9)==0){
/* setoption name X value Y */
char* p = strstr(line, "name");
if(!p) continue;
p+=4; while(*p==' ') p++;
char name[64]={0}, value[64]={0};
int ni=0, vi=0;
while(*p && *p!=' ' && *p!='\n'){ if(ni<63) name[ni++]=*p; p++; }
char* v = strstr(line, "value");
if(v){ v+=5; while(*v==' ') v++; while(*v && *v!='\n'){ if(vi<63) value[vi++]=*v; v++; } }
name[ni]=0; value[vi]=0;
if(strcmp(name,"Hash")==0){
int mb = atoi(value);
if(mb<16) mb=16; if(mb>65536) mb=65536;
tt_resize_mb((size_t)mb);
}else if(strcmp(name,"Threads")==0){
/* present but not used (single-threaded) */
}
}else if(strncmp(line,"go",2)==0){
gp.depth = 0; gp.movetime_ms=0; gp.wtime=gp.btime=gp.winc=gp.binc=0; gp.movestogo=0;
char* p = line + 2;
while(*p){
while(*p==' ') p++;
if(!*p) break;
if(strncmp(p,"wtime",5)==0){ p+=5; gp.wtime = strtoull(p,&p,10); }
else if(strncmp(p,"btime",5)==0){ p+=5; gp.btime = strtoull(p,&p,10); }
else if(strncmp(p,"winc",4)==0){ p+=4; gp.winc = strtoull(p,&p,10); }
else if(strncmp(p,"binc",4)==0){ p+=4; gp.binc = strtoull(p,&p,10); }
else if(strncmp(p,"movestogo",9)==0){ p+=9; gp.movestogo = (int)strtoul(p,&p,10); }
else if(strncmp(p,"movetime",8)==0){ p+=8; gp.movetime_ms = strtoull(p,&p,10); }
else if(strncmp(p,"depth",5)==0){ p+=5; gp.depth = (int)strtoul(p,&p,10); }
else if(strncmp(p,"infinite",8)==0){ p+=8; gp.movetime_ms = 0; }
else { p++; }
}
Move bm = search_root(&gp);
char s[8]={0};
if(bm){ move_to_str(bm, s); printf("bestmove %s\n", s); }
else { printf("bestmove 0000\n"); }
fflush(stdout);
}else if(strncmp(line,"stop",4)==0){
stop_search = 1;
}else if(strncmp(line,"quit",4)==0){
break;
}else if(strncmp(line,"print",5)==0){
/* debug: print board */
for(int r=7;r>=0;r--){
for(int f=0;f<8;f++){
int p = board[SQ(f,r)];
char c = '.';
switch(p){
case WP:c='P';break; case WN:c='N';break; case WB:c='B';break; case WR:c='R';break; case WQ:c='Q';break; case WK:c='K';break;
case BP:c='p';break; case BN:c='n';break; case BB:c='b';break; case BR:c='r';break; case BQ:c='q';break; case BK:c='k';break;
default: c='.';
}
printf("%c ", c);
}
printf("\n");
}
fflush(stdout);
}
}
}

/* ========================= main ========================= */
int main(void){
init_zobrist();
tt_resize_mb(64); /* default */
set_startpos();
uci_loop();
if(tt) free(tt);
return 0;
}
User avatar
Bo Persson
Posts: 260
Joined: Sat Mar 11, 2006 8:31 am
Location: Malmö, Sweden
Full name: Bo Persson

Re: The Dawn of Skynet

Post by Bo Persson »

Let's start at the top:
Build (MSYS2 UCRT64 GCC):
gcc -O3 -pipe -DNDEBUG -std=c17 -march=native -pthread -o gpt5prochess.exe gpt5prochess.c
The current version of gcc defaults to the C23 standard, and GPT-15 specifically asks it to use an older version instead. How Intelligent is that?
smatovic
Posts: 3293
Joined: Wed Mar 10, 2010 10:18 pm
Location: Hamburg, Germany
Full name: Srdja Matovic

Re: The Dawn of Skynet

Post by smatovic »

Indeed, would be interesting to follow over time....it looks like the code formatting was lost, you can use the code tags in editor "</>" to post code and tabs will be kept, like quote tags but for code.

Code: Select all

	tab1
		tab2
			tab3
--
Srdja
jefk
Posts: 973
Joined: Sun Jul 25, 2010 10:07 pm
Location: the Netherlands
Full name: Jef Kaan

Re: The Dawn of Skynet

Post by jefk »

a single-file engine to make it easy to drop into Cursor and build.
interesting, but i got some compiler errors when compiling this with the Embarcadero C compiler
(with which i'm doing a similar thing btw, with another rudimentary engine source code)
My experience with Cgpt5 regarding C (and debugging eg compiler errors) was not good,
so i switched to Grok4 (expert mode) but only on the free on hr/day mode, so i'm
contemplating to have a look at deepseek especially their code version if i can find it.
PS maybe you also can ask the AI to insert some comments in the source code,
to explain some stuff, for the 'oldfashioned' human programmers ;)
Werewolf
Posts: 2014
Joined: Thu Sep 18, 2008 10:24 pm

Re: The Dawn of Skynet

Post by Werewolf »

Bo Persson wrote: Thu Aug 14, 2025 6:38 pm Let's start at the top:
Build (MSYS2 UCRT64 GCC):
gcc -O3 -pipe -DNDEBUG -std=c17 -march=native -pthread -o gpt5prochess.exe gpt5prochess.c
The current version of gcc defaults to the C23 standard, and GPT-15 specifically asks it to use an older version instead. How Intelligent is that?
Not very intelligent!

But just wait...

I will tell you a story, and it's 100% true.
Back in the 1990s I used to collect IQ puzzle books. The idea was you tried a ton of questions and then the book estimated your IQ.
Average IQ is 100, which corresponds to getting about 40% on the tests.

Back in January this year I found the smartest guy in our neighbourhood, he was a Bristol University Maths student. He got about 70% on the test. So I guess he'd be 130-140 in IQ. ChatGPT at that time scored 50%, but even that wasn't bad.
Now ChatGPT 5 Pro is getting 75%. That's in 7 months.

Of course the usual caveats apply: it's just a test, it's artificial etc etc. But it's improving.
Werewolf
Posts: 2014
Joined: Thu Sep 18, 2008 10:24 pm

Re: The Dawn of Skynet

Post by Werewolf »

jefk wrote: Thu Aug 14, 2025 8:51 pm
a single-file engine to make it easy to drop into Cursor and build.
interesting, but i got some compiler errors when compiling this with the Embarcadero C compiler
(with which i'm doing a similar thing btw, with another rudimentary engine source code)
My experience with Cgpt5 regarding C (and debugging eg compiler errors) was not good,
so i switched to Grok4 (expert mode) but only on the free on hr/day mode, so i'm
contemplating to have a look at deepseek especially their code version if i can find it.
PS maybe you also can ask the AI to insert some comments in the source code,
to explain some stuff, for the 'oldfashioned' human programmers ;)
Noted, thank you. I will do this next time.
Werewolf
Posts: 2014
Joined: Thu Sep 18, 2008 10:24 pm

Re: The Dawn of Skynet

Post by Werewolf »

smatovic wrote: Thu Aug 14, 2025 6:52 pm Indeed, would be interesting to follow over time....it looks like the code formatting was lost, you can use the code tags in editor "</>" to post code and tabs will be kept, like quote tags but for code.

Code: Select all

	tab1
		tab2
			tab3
--
Srdja
Ah...yes...that would help!
Aleks Peshkov
Posts: 901
Joined: Sun Nov 19, 2006 9:16 pm
Location: Russia
Full name: Aleks Peshkov

Re: The Dawn of Skynet

Post by Aleks Peshkov »

Werewolf wrote: Thu Aug 14, 2025 8:56 pm Not very intelligent!

But just wait...

I will tell you a story, and it's 100% true.
Back in the 1990s I used to collect IQ puzzle books. The idea was you tried a ton of questions and then the book estimated your IQ.
Average IQ is 100, which corresponds to getting about 40% on the tests.

Back in January this year I found the smartest guy in our neighbourhood, he was a Bristol University Maths student. He got about 70% on the test. So I guess he'd be 130-140 in IQ. ChatGPT at that time scored 50%, but even that wasn't bad.
Now ChatGPT 5 Pro is getting 75%. That's in 7 months.

Of course the usual caveats apply: it's just a test, it's artificial etc etc. But it's improving.
My personal observation: AI is an expert at 75% of programming topic, it can detect 95% of human bugs in code. 75% of time the suggested code is perfect, 95% cases the code is working but can be improved and AI improves its code under human guide. In remaining 5% AI does not understand the task, cannot progress with human interaction and suggests absurd not working solutions. AI does not know what it does not know and does not confess. Improving AI from 10 to 1% of failed cases is not 10 times progress. Without human supervision AI still cannot make professional job done.
Werewolf
Posts: 2014
Joined: Thu Sep 18, 2008 10:24 pm

Re: The Dawn of Skynet

Post by Werewolf »

After making a breakthrough in methodology overnight, I now claim to have a fully working UCI chess engine. Details to follow. Here is its first game:

[Event "15s/Move"]
[White "Skynet 001"]
[Black "MK12 Dedicated Chess Computer 1300 FIDE Elo"]


[pgn]1. Nf3 {15} d5 {6} 2. e3 {15} Nf6 {22} 3. b3 {15} Nc6 {25} 4. Bb5 {0} a6 {22} 5. Bxc6+ {17} bxc6 {10} 6. Ne5 {7} Bd7 {15} 7. O-O {21} e6 {22} 8. Nc3 {0} Bb4 {25} 9. Bb2 {19} O-O {39} 10. a4 {18} Bxc3 {42} 11. dxc3 {18} Rb8 {26} 12. Ba3 {18} Re8 {17} 13. f3 {0} g6 {26} 14. Qd3 {19} a5 {20} 15. Qd4 {0} g5 {25} 16. g4 {21} Rb6 {24} 17. f4 {20} gxf4 {47} 18. Qxf4 {19} Rb8 {29} 19. Qg5+ {19} Kh8 {22} 20. Nxf7# {0} 1-0[/pgn]
Werewolf
Posts: 2014
Joined: Thu Sep 18, 2008 10:24 pm

Re: The Dawn of Skynet

Post by Werewolf »

[d]2k2r2/1p1R4/p3r3/P1p1b1p1/B1P5/R2P4/P4PPP/2N3K1 b - - 0 1

Here is it black to play. Skynet 001 took 53 seconds to find black's win - how long do you take?