#include "chess.h" #include #include #include #include static const int knight_offsets[8] = {15, 17, -15, -17, 10, -10, 6, -6}; static const int bishop_dirs[4] = {9, 7, -9, -7}; static const int rook_dirs[4] = {8, -8, 1, -1}; static const int king_dirs[8] = {8, -8, 1, -1, 9, 7, -9, -7}; static inline int file_of(int sq) { return sq % 8; } static inline int rank_of(int sq) { return sq / 8; } static inline bool on_board(int sq) { return sq >= 0 && sq < 64; } static inline bool same_color(char a, char b) { return (isupper(a) && isupper(b)) || (islower(a) && islower(b)); } static inline bool is_white(char p) { return isupper((unsigned char)p); } typedef struct { int from; int to; char promo; char captured; } MoveCandidate; void chess_init_start(Position *pos) { // a1..h1 (0..7) are white back rank; rank 8 at indexes 56..63 are black back rank const char *start = "RNBQKBNRPPPPPPPP................................pppppppprnbqkbnr"; for (int i = 0; i < 64; i++) pos->board[i] = start[i]; pos->white_to_move = true; pos->castle_wk = pos->castle_wq = pos->castle_bk = pos->castle_bq = true; pos->ep_square = -1; pos->halfmove_clock = 0; pos->fullmove_number = 1; } void chess_copy(Position *dst, const Position *src) { *dst = *src; } static bool is_empty(const Position *p, int sq) { return p->board[sq] == '.'; } bool chess_square_attacked(const Position *pos, int sq, bool by_white) { // pawns int r = rank_of(sq), f = file_of(sq); if (by_white) { int s1 = (r - 1) * 8 + (f - 1); if (f > 0 && r > 0 && on_board(s1) && pos->board[s1] == 'P') return true; int s2 = (r - 1) * 8 + (f + 1); if (f < 7 && r > 0 && on_board(s2) && pos->board[s2] == 'P') return true; } else { int s1 = (r + 1) * 8 + (f - 1); if (f > 0 && r < 7 && on_board(s1) && pos->board[s1] == 'p') return true; int s2 = (r + 1) * 8 + (f + 1); if (f < 7 && r < 7 && on_board(s2) && pos->board[s2] == 'p') return true; } // knights for (int i = 0; i < 8; i++) { int t = sq + knight_offsets[i]; if (!on_board(t)) continue; int df = file_of(t) - f; int dr = rank_of(t) - r; if (df < -2 || df > 2 || dr < -2 || dr > 2) continue; // edge wrap guard char pc = pos->board[t]; if (by_white && pc == 'N') return true; if (!by_white && pc == 'n') return true; } // bishops/queens for (int d = 0; d < 4; d++) { int off = bishop_dirs[d]; int t = sq + off; while (on_board(t) && abs(file_of(t) - f) == abs(rank_of(t) - r)) { char pc = pos->board[t]; if (pc != '.') { if (by_white && (pc == 'B' || pc == 'Q')) return true; if (!by_white && (pc == 'b' || pc == 'q')) return true; break; } t += off; } } // rooks/queens for (int d = 0; d < 4; d++) { int off = rook_dirs[d]; int t = sq + off; while (on_board(t) && (file_of(t) == f || rank_of(t) == r)) { char pc = pos->board[t]; if (pc != '.') { if (by_white && (pc == 'R' || pc == 'Q')) return true; if (!by_white && (pc == 'r' || pc == 'q')) return true; break; } t += off; } } // king for (int i = 0; i < 8; i++) { int t = sq + king_dirs[i]; if (!on_board(t)) continue; if (abs(file_of(t) - f) > 1 || abs(rank_of(t) - r) > 1) continue; char pc = pos->board[t]; if (by_white && pc == 'K') return true; if (!by_white && pc == 'k') return true; } return false; } bool chess_is_in_check(const Position *pos, bool white) { int ks = -1; char k = white ? 'K' : 'k'; for (int i = 0; i < 64; i++) if (pos->board[i] == k) { ks = i; break; } if (ks == -1) return false; // malformed return chess_square_attacked(pos, ks, !white); } static void add_move_if_legal(const Position *pos, MoveCandidate candidate, Move *out, size_t *n, size_t max) { if (*n >= max) return; Position tmp; chess_copy(&tmp, pos); char captured_piece = candidate.captured ? candidate.captured : pos->board[candidate.to]; Move m = {0}; m.from = candidate.from; m.to = candidate.to; m.promo = candidate.promo; m.moved = pos->board[candidate.from]; m.captured = captured_piece; int prev_ep = tmp.ep_square; m.prev_ep = prev_ep; m.prev_wk = tmp.castle_wk; m.prev_wq = tmp.castle_wq; m.prev_bk = tmp.castle_bk; m.prev_bq = tmp.castle_bq; m.prev_halfmove = tmp.halfmove_clock; if (!chess_make_move(&tmp, &m)) return; if (chess_is_in_check(&tmp, !tmp.white_to_move)) return; // after make, side switched out[(*n)++] = m; // store pseudo move with added flags from make_move } size_t chess_generate_legal_moves(const Position *pos, Move *out, size_t max) { size_t n = 0; bool white = pos->white_to_move; for (int sq = 0; sq < 64; sq++) { char p = pos->board[sq]; if (p == '.') continue; if (white != is_white(p)) continue; int f = file_of(sq), r = rank_of(sq); switch (tolower(p)) { case 'p': { int dir = white ? 8 : -8; int start_rank = white ? 1 : 6; int prom_rank = white ? 6 : 1; int one = sq + dir; if (on_board(one) && is_empty(pos, one)) { if (r == prom_rank) { const char *pr = white ? "QRBN" : "qrbn"; for (int i = 0; i < 4; i++) add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = one, .promo = pr[i]}, out, &n, max); } else { add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = one, .promo = 0}, out, &n, max); } // two int two = sq + 2 * dir; if (r == start_rank && is_empty(pos, two)) add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = two, .promo = 0}, out, &n, max); } // captures int caps[2] = {dir + 1, dir - 1}; for (int i = 0; i < 2; i++) { int t = sq + caps[i]; if (!on_board(t)) continue; if (abs(file_of(t) - f) != 1) continue; if (!is_empty(pos, t) && !same_color(pos->board[sq], pos->board[t])) { if (r == prom_rank) { const char *pr = white ? "QRBN" : "qrbn"; for (int j = 0; j < 4; j++) add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = pr[j]}, out, &n, max); } else add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n, max); } } // en passant if (pos->ep_square != -1) { int ep = pos->ep_square; if (abs(file_of(ep) - f) == 1 && (ep == sq + dir + 1 || ep == sq + dir - 1)) add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = ep, .promo = 0}, out, &n, max); } } break; case 'n': { for (int i = 0; i < 8; i++) { int t = sq + knight_offsets[i]; if (!on_board(t)) continue; if (abs(file_of(t) - f) > 2 || abs(rank_of(t) - r) > 2) continue; if (!is_empty(pos, t) && same_color(p, pos->board[t])) continue; add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n, max); } } break; case 'b': { for (int d = 0; d < 4; d++) { int off = bishop_dirs[d]; int t = sq + off; while (on_board(t) && abs(file_of(t) - f) == abs(rank_of(t) - r)) { if (!is_empty(pos, t)) { if (!same_color(p, pos->board[t])) add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n, max); break; } add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n, max); t += off; } } } break; case 'r': { for (int d = 0; d < 4; d++) { int off = rook_dirs[d]; int t = sq + off; while (on_board(t) && (file_of(t) == f || rank_of(t) == r)) { if (!is_empty(pos, t)) { if (!same_color(p, pos->board[t])) add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n, max); break; } add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n, max); t += off; } } } break; case 'q': { for (int d = 0; d < 4; d++) { int off = bishop_dirs[d]; int t = sq + off; while (on_board(t) && abs(file_of(t) - f) == abs(rank_of(t) - r)) { if (!is_empty(pos, t)) { if (!same_color(p, pos->board[t])) add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n, max); break; } add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n, max); t += off; } } for (int d = 0; d < 4; d++) { int off = rook_dirs[d]; int t = sq + off; while (on_board(t) && (file_of(t) == f || rank_of(t) == r)) { if (!is_empty(pos, t)) { if (!same_color(p, pos->board[t])) add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n, max); break; } add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n, max); t += off; } } } break; case 'k': { for (int i = 0; i < 8; i++) { int t = sq + king_dirs[i]; if (!on_board(t)) continue; if (abs(file_of(t) - f) > 1 || abs(rank_of(t) - r) > 1) continue; if (!is_empty(pos, t) && same_color(p, pos->board[t])) continue; add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n, max); } // castling if (white) { if (pos->castle_wk && pos->board[5] == '.' && pos->board[6] == '.' && !chess_square_attacked(pos, 4, false) && !chess_square_attacked(pos, 5, false) && !chess_square_attacked(pos, 6, false)) add_move_if_legal(pos, (MoveCandidate){.from = 4, .to = 6, .promo = 0}, out, &n, max); if (pos->castle_wq && pos->board[3] == '.' && pos->board[2] == '.' && pos->board[1] == '.' && !chess_square_attacked(pos, 4, false) && !chess_square_attacked(pos, 3, false) && !chess_square_attacked(pos, 2, false)) add_move_if_legal(pos, (MoveCandidate){.from = 4, .to = 2, .promo = 0}, out, &n, max); } else { if (pos->castle_bk && pos->board[61] == '.' && pos->board[62] == '.' && !chess_square_attacked(pos, 60, true) && !chess_square_attacked(pos, 61, true) && !chess_square_attacked(pos, 62, true)) add_move_if_legal(pos, (MoveCandidate){.from = 60, .to = 62, .promo = 0}, out, &n, max); if (pos->castle_bq && pos->board[59] == '.' && pos->board[58] == '.' && pos->board[57] == '.' && !chess_square_attacked(pos, 60, true) && !chess_square_attacked(pos, 59, true) && !chess_square_attacked(pos, 58, true)) add_move_if_legal(pos, (MoveCandidate){.from = 60, .to = 58, .promo = 0}, out, &n, max); } } break; default: break; } } return n; } bool chess_make_move(Position *pos, Move *m) { m->is_castle = false; m->is_enpassant = false; char p = pos->board[m->from]; char tgt = pos->board[m->to]; if (p == '.') return false; // handle special: en passant capture if (tolower(p) == 'p' && m->to == pos->ep_square && file_of(m->to) != file_of(m->from) && tgt == '.') { m->is_enpassant = true; int cap_sq = pos->white_to_move ? (m->to - 8) : (m->to + 8); m->captured = pos->board[cap_sq]; pos->board[cap_sq] = '.'; } // move piece pos->board[m->to] = p; pos->board[m->from] = '.'; // promotion if (tolower(p) == 'p' && m->promo) { pos->board[m->to] = m->promo; } // castling rook move if (tolower(p) == 'k') { int from = m->from, to = m->to; if (from == 4 && to == 6) { pos->board[5] = 'R'; pos->board[7] = '.'; m->is_castle = true; } else if (from == 4 && to == 2) { pos->board[3] = 'R'; pos->board[0] = '.'; m->is_castle = true; } else if (from == 60 && to == 62) { pos->board[61] = 'r'; pos->board[63] = '.'; m->is_castle = true; } else if (from == 60 && to == 58) { pos->board[59] = 'r'; pos->board[56] = '.'; m->is_castle = true; } } // update castling rights if (m->from == 0 || m->to == 0) pos->castle_wq = false; if (m->from == 7 || m->to == 7) pos->castle_wk = false; if (m->from == 56 || m->to == 56) pos->castle_bq = false; if (m->from == 63 || m->to == 63) pos->castle_bk = false; if (tolower(p) == 'k') { if (is_white(p)) { pos->castle_wk = pos->castle_wq = false; } else { pos->castle_bk = pos->castle_bq = false; } } // update ep square pos->ep_square = -1; if (tolower(p) == 'p') { int df = rank_of(m->to) - rank_of(m->from); if (df == 2 || df == -2) { pos->ep_square = (m->from + m->to) / 2; } } // halfmove clock if (tolower(p) == 'p' || tgt != '.') pos->halfmove_clock = 0; else pos->halfmove_clock++; // side to move pos->white_to_move = !pos->white_to_move; if (pos->white_to_move) pos->fullmove_number++; return true; } void chess_unmake_move(Position *pos, const Move *m) { pos->white_to_move = !pos->white_to_move; if (!pos->white_to_move) pos->fullmove_number--; // restore halfmove/flags pos->ep_square = m->prev_ep; pos->castle_wk = m->prev_wk; pos->castle_wq = m->prev_wq; pos->castle_bk = m->prev_bk; pos->castle_bq = m->prev_bq; pos->halfmove_clock = m->prev_halfmove; char p = m->moved; // undo promotions if (tolower(p) == 'p' && m->promo) { p = is_white(p) ? 'P' : 'p'; } pos->board[m->from] = p; pos->board[m->to] = m->captured ? m->captured : '.'; if (m->is_enpassant) { int cap_sq = pos->white_to_move ? (m->to - 8) : (m->to + 8); pos->board[m->to] = '.'; pos->board[cap_sq] = m->captured; } if (m->is_castle) { if (m->from == 4 && m->to == 6) { pos->board[7] = 'R'; pos->board[5] = '.'; } else if (m->from == 4 && m->to == 2) { pos->board[0] = 'R'; pos->board[3] = '.'; } else if (m->from == 60 && m->to == 62) { pos->board[63] = 'r'; pos->board[61] = '.'; } else if (m->from == 60 && m->to == 58) { pos->board[56] = 'r'; pos->board[59] = '.'; } } } void sq_to_coord(int sq, int *file, int *rank) { if (file) *file = file_of(sq); if (rank) *rank = rank_of(sq); } int coord_to_sq(int file, int rank) { return rank * 8 + file; } void move_to_uci(const Move *m, char buf[8]) { int f1 = file_of(m->from), r1 = rank_of(m->from), f2 = file_of(m->to), r2 = rank_of(m->to); buf[0] = (char)('a' + f1); buf[1] = (char)('1' + r1); buf[2] = (char)('a' + f2); buf[3] = (char)('1' + r2); int i = 4; if (m->promo) { buf[i++] = (char)tolower((unsigned char)m->promo); } buf[i] = '\0'; } bool parse_uci_move(const char *s, const Position *pos, Move *out) { if (!s || strlen(s) < 4) return false; int f1 = s[0] - 'a', r1 = s[1] - '1', f2 = s[2] - 'a', r2 = s[3] - '1'; if (f1 < 0 || f1 > 7 || f2 < 0 || f2 > 7 || r1 < 0 || r1 > 7 || r2 < 0 || r2 > 7) return false; int from = r1 * 8 + f1, to = r2 * 8 + f2; char promo = s[4] ? s[4] : 0; if (promo) promo = pos->white_to_move ? toupper((unsigned char)promo) : tolower((unsigned char)promo); Move list[MAX_MOVES]; size_t n = chess_generate_legal_moves(pos, list, MAX_MOVES); for (size_t i = 0; i < n; i++) { if (list[i].from == from && list[i].to == to) { *out = list[i]; out->promo = promo ? promo : list[i].promo; return true; } } return false; } bool chess_to_fen(const Position *pos, char *out, size_t outsz) { char buf[256]; int idx = 0; for (int r = 7; r >= 0; r--) { int empty = 0; for (int f = 0; f < 8; f++) { char p = pos->board[r * 8 + f]; if (p == '.') empty++; else { if (empty) { buf[idx++] = (char)('0' + empty); empty = 0; } buf[idx++] = p; } } if (empty) buf[idx++] = (char)('0' + empty); if (r) buf[idx++] = '/'; } buf[idx++] = ' '; buf[idx++] = pos->white_to_move ? 'w' : 'b'; buf[idx++] = ' '; int start = idx; if (pos->castle_wk) buf[idx++] = 'K'; if (pos->castle_wq) buf[idx++] = 'Q'; if (pos->castle_bk) buf[idx++] = 'k'; if (pos->castle_bq) buf[idx++] = 'q'; if (idx == start) buf[idx++] = '-'; buf[idx++] = ' '; if (pos->ep_square == -1) { buf[idx++] = '-'; } else { int f = file_of(pos->ep_square), r = rank_of(pos->ep_square); buf[idx++] = (char)('a' + f); buf[idx++] = (char)('1' + r); } idx += snprintf(buf + idx, sizeof(buf) - idx, " %d %d", pos->halfmove_clock, pos->fullmove_number); buf[idx] = '\0'; snprintf(out, outsz, "%s", buf); return true; }