testsAndMisc/C/opening_learner/chess.c

671 lines
21 KiB
C

#include "chess.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}