#include "movegen.h" #include #include #include #include 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;iboard[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;iboard[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; sqboard[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; sqboard[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;iboard[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;iside); 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, we’d 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;iside==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; }