diff options
Diffstat (limited to 'src/core/chess.c')
| -rw-r--r-- | src/core/chess.c | 668 |
1 files changed, 0 insertions, 668 deletions
diff --git a/src/core/chess.c b/src/core/chess.c deleted file mode 100644 index 94d35a6..0000000 --- a/src/core/chess.c +++ /dev/null @@ -1,668 +0,0 @@ -#if defined(__linux__) -#define _DEFAULT_SOURCE -#endif - -#include "hybbx/chess.h" -#include "hybbx/session.h" -#include "hybbx/service.h" -#include "hybbx/log.h" -#include "hybbx/util.h" -#include "hybbx/config.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <ctype.h> - -static chess_game_t g_games[CHESS_GAME_MAX]; -static unsigned g_max_games = CHESS_DEFAULT_MAX; - -/* ── Board init ────────────────────────────────────────────── */ - -static void board_init(chess_board_t *b) -{ - static const char back[] = "RNBQKBNR"; - unsigned f; - - memset(b, 0, sizeof(*b)); - for (f = 0; f < 8; f++) { - b->sq[0][f] = back[f]; - b->sq[1][f] = 'P'; - b->sq[6][f] = 'p'; - b->sq[7][f] = (char)tolower((unsigned char)back[f]); - } - for (unsigned r = 2; r < 6; r++) - for (f = 0; f < 8; f++) - b->sq[r][f] = CHESS_EMPTY; -} - -void hybbx_chess_init(void) -{ - memset(g_games, 0, sizeof(g_games)); - g_max_games = CHESS_DEFAULT_MAX; -} - -void hybbx_chess_shutdown(void) -{ - unsigned i; - for (i = 0; i < g_max_games; i++) - g_games[i].active = 0; -} - -void hybbx_chess_config_apply(const struct hybbx_config *config) -{ - unsigned val; - - if (config != NULL) { - val = hybbx_config_get_uint(config, "chess", "max_games", - CHESS_DEFAULT_MAX, 1, CHESS_GAME_MAX); - } else { - val = CHESS_DEFAULT_MAX; - } - g_max_games = val; - hybbx_log_info("[chess] max_games=%u", g_max_games); -} - -unsigned hybbx_chess_max_games(void) -{ - return g_max_games; -} - -/* ── Helpers ───────────────────────────────────────────────── */ - -static int parse_square(const char *s, unsigned *rank, unsigned *file) -{ - if (s == NULL || strlen(s) < 2) - return 0; - if (s[0] < 'a' || s[0] > 'h' || s[1] < '1' || s[1] > '8') - return 0; - *file = (unsigned)(s[0] - 'a'); - *rank = (unsigned)(s[1] - '1'); - return 1; -} - -static chess_color_t piece_color(char p) -{ - if (p == CHESS_EMPTY) return CHESS_COLOR_NONE; - return (p >= 'A' && p <= 'Z') ? CHESS_WHITE : CHESS_BLACK; -} - -static char piece_upper(char p) -{ - return (char)toupper((unsigned char)p); -} - -static int in_bounds(unsigned r, unsigned f) -{ - return r < 8 && f < 8; -} - -/* ── Check detection ───────────────────────────────────────── */ - -static int square_attacked_by(const chess_board_t *b, unsigned rank, - unsigned file, chess_color_t by) -{ - unsigned r, f; - int dr, df; - char pawn, knight, bishop, rook, queen, king; - - if (by == CHESS_WHITE) { - pawn = 'P'; knight = 'N'; bishop = 'B'; - rook = 'R'; queen = 'Q'; king = 'K'; - } else { - pawn = 'p'; knight = 'n'; bishop = 'b'; - rook = 'r'; queen = 'q'; king = 'k'; - } - - /* Pawn attacks */ - if (by == CHESS_WHITE) { - if (rank > 0 && file > 0 && b->sq[rank - 1][file - 1] == pawn) return 1; - if (rank > 0 && file < 7 && b->sq[rank - 1][file + 1] == pawn) return 1; - } else { - if (rank < 7 && file > 0 && b->sq[rank + 1][file - 1] == pawn) return 1; - if (rank < 7 && file < 7 && b->sq[rank + 1][file + 1] == pawn) return 1; - } - - /* Knight */ - { - static const int kr[] = {2,2,-2,-2,1,1,-1,-1}; - static const int kf[] = {1,-1,1,-1,2,-2,2,-2}; - for (unsigned i = 0; i < 8; i++) { - int nr = (int)rank + kr[i], nf = (int)file + kf[i]; - if (nr >= 0 && nr < 8 && nf >= 0 && nf < 8) - if (b->sq[nr][nf] == knight) return 1; - } - } - - /* Sliding: bishop/queen diagonals */ - { - static const int drs[] = {1,1,-1,-1}; - static const int dfs[] = {1,-1,1,-1}; - for (unsigned d = 0; d < 4; d++) { - dr = drs[d]; df = dfs[d]; - r = rank; f = file; - while (1) { - r = (unsigned)((int)r + dr); - f = (unsigned)((int)f + df); - if (!in_bounds(r, f)) break; - if (b->sq[r][f] != CHESS_EMPTY) { - if (b->sq[r][f] == bishop || b->sq[r][f] == queen) return 1; - break; - } - } - } - } - - /* Sliding: rook/queen straights */ - { - static const int drs[] = {1,-1,0,0}; - static const int dfs[] = {0,0,1,-1}; - for (unsigned d = 0; d < 4; d++) { - dr = drs[d]; df = dfs[d]; - r = rank; f = file; - while (1) { - r = (unsigned)((int)r + dr); - f = (unsigned)((int)f + df); - if (!in_bounds(r, f)) break; - if (b->sq[r][f] != CHESS_EMPTY) { - if (b->sq[r][f] == rook || b->sq[r][f] == queen) return 1; - break; - } - } - } - } - - /* King */ - { - static const int drs[] = {1,1,1,0,0,-1,-1,-1}; - static const int dfs[] = {1,0,-1,1,-1,1,0,-1}; - for (unsigned d = 0; d < 8; d++) { - int nr = (int)rank + drs[d], nf = (int)file + dfs[d]; - if (nr >= 0 && nr < 8 && nf >= 0 && nf < 8) - if (b->sq[nr][nf] == king) return 1; - } - } - - return 0; -} - -static int find_king(const chess_board_t *b, chess_color_t color, - unsigned *kr, unsigned *kf) -{ - char king = (color == CHESS_WHITE) ? 'K' : 'k'; - for (unsigned r = 0; r < 8; r++) - for (unsigned f = 0; f < 8; f++) - if (b->sq[r][f] == king) { *kr = r; *kf = f; return 1; } - return 0; -} - -static int is_in_check(const chess_board_t *b, chess_color_t side) -{ - unsigned kr, kf; - chess_color_t opp = (side == CHESS_WHITE) ? CHESS_BLACK : CHESS_WHITE; - if (!find_king(b, side, &kr, &kf)) return 0; - return square_attacked_by(b, kr, kf, opp); -} - -/* ── Move legality ─────────────────────────────────────────── */ - -static int pseudo_legal_pawn(const chess_board_t *b, unsigned fr, - unsigned ff, unsigned tr, unsigned tf, - chess_color_t side) -{ - int dir = (side == CHESS_WHITE) ? 1 : -1; - int start_rank = (side == CHESS_WHITE) ? 1 : 6; - int dr = (int)tr - (int)fr; - int df = (int)tf - (int)ff; - - if (df == 0) { - if (dr == dir && b->sq[tr][tf] == CHESS_EMPTY) return 1; - if (dr == 2 * dir && (int)fr == start_rank && - b->sq[fr + (unsigned)dir][ff] == CHESS_EMPTY && - b->sq[tr][tf] == CHESS_EMPTY) return 1; - } - if ((df == 1 || df == -1) && dr == dir) { - if (b->sq[tr][tf] != CHESS_EMPTY) return 1; - } - return 0; -} - -static int pseudo_legal_knight(unsigned fr, unsigned ff, unsigned tr, - unsigned tf) -{ - int dr = abs((int)tr - (int)fr); - int df = abs((int)tf - (int)ff); - return (dr == 2 && df == 1) || (dr == 1 && df == 2); -} - -static int sliding_clear(const chess_board_t *b, unsigned fr, unsigned ff, - unsigned tr, unsigned tf) -{ - int dr = 0, df = 0; - unsigned r, f; - - if ((int)tr > (int)fr) dr = 1; - else if ((int)tr < (int)fr) dr = -1; - if ((int)tf > (int)ff) df = 1; - else if ((int)tf < (int)ff) df = -1; - - r = (unsigned)((int)fr + dr); - f = (unsigned)((int)ff + df); - while (r != tr || f != tf) { - if (!in_bounds(r, f) || b->sq[r][f] != CHESS_EMPTY) return 0; - r = (unsigned)((int)r + dr); - f = (unsigned)((int)f + df); - } - return 1; -} - -static int pseudo_legal(const chess_board_t *b, unsigned fr, unsigned ff, - unsigned tr, unsigned tf, chess_color_t side) -{ - char piece = b->sq[fr][ff]; - int dr = abs((int)tr - (int)fr); - int df = abs((int)tf - (int)ff); - chess_color_t target_color; - - if (piece == CHESS_EMPTY) return 0; - if (piece_color(piece) != side) return 0; - - target_color = piece_color(b->sq[tr][tf]); - if (target_color == side) return 0; - - switch (piece_upper(piece)) { - case 'P': return pseudo_legal_pawn(b, fr, ff, tr, tf, side); - case 'N': return pseudo_legal_knight(fr, ff, tr, tf); - case 'B': - if (dr != df || dr == 0) return 0; - return sliding_clear(b, fr, ff, tr, tf); - case 'R': - if (fr != tr && ff != tf) return 0; - return sliding_clear(b, fr, ff, tr, tf); - case 'Q': - if (fr != tr && ff != tf && dr != df) return 0; - return sliding_clear(b, fr, ff, tr, tf); - case 'K': - return dr <= 1 && df <= 1 && (dr + df > 0); - } - return 0; -} - -static int has_any_legal_move(chess_board_t *b, chess_color_t side); - -static int is_legal_move(chess_board_t *b, unsigned fr, unsigned ff, - unsigned tr, unsigned tf, chess_color_t side) -{ - chess_board_t tmp; - - if (!pseudo_legal(b, fr, ff, tr, tf, side)) return 0; - - memcpy(&tmp, b, sizeof(tmp)); - tmp.sq[tr][tf] = tmp.sq[fr][ff]; - tmp.sq[fr][ff] = CHESS_EMPTY; - - /* Auto-promote to queen */ - if (piece_upper(tmp.sq[tr][tf]) == 'P') { - if ((side == CHESS_WHITE && tr == 7) || - (side == CHESS_BLACK && tr == 0)) { - tmp.sq[tr][tf] = (side == CHESS_WHITE) ? 'Q' : 'q'; - } - } - - return !is_in_check(&tmp, side); -} - -static int has_any_legal_move(chess_board_t *b, chess_color_t side) -{ - for (unsigned fr = 0; fr < 8; fr++) - for (unsigned ff = 0; ff < 8; ff++) { - if (piece_color(b->sq[fr][ff]) != side) continue; - for (unsigned tr = 0; tr < 8; tr++) - for (unsigned tf = 0; tf < 8; tf++) - if (is_legal_move(b, fr, ff, tr, tf, side)) - return 1; - } - return 0; -} - -/* ── SAN notation (simplified) ─────────────────────────────── */ - -static void make_san(char *out, size_t outlen, char piece, - unsigned ff, unsigned tr, unsigned tf, - int capture, int check, int mate, char promote) -{ - char *p = out; - size_t room = outlen; - - if (piece_upper(piece) == 'P') { - if (capture) { - int n = snprintf(p, room, "%cx%c%d", 'a' + ff, 'a' + tf, tr + 1); - p += n; room -= (size_t)n; - } else { - int n = snprintf(p, room, "%c%d", 'a' + tf, tr + 1); - p += n; room -= (size_t)n; - } - } else { - int n = snprintf(p, room, "%c%c%d", piece_upper(piece), - 'a' + tf, tr + 1); - p += n; room -= (size_t)n; - } - if (promote) { - snprintf(p, room, "=%c", piece_upper(promote)); - } else if (mate) { - snprintf(p, room, "#"); - } else if (check) { - snprintf(p, room, "+"); - } -} - -/* ── Game management ───────────────────────────────────────── */ - -chess_game_t *hybbx_chess_get_game(unsigned index) -{ - if (index >= CHESS_GAME_MAX) return NULL; - return &g_games[index]; -} - -chess_game_t *hybbx_chess_create_game(struct hybbx_session *white, - const char *white_name) -{ - unsigned i; - for (i = 0; i < g_max_games; i++) { - if (!g_games[i].active) { - memset(&g_games[i], 0, sizeof(g_games[i])); - g_games[i].active = 1; - hybbx_strlcpy(g_games[i].white_name, white_name, - sizeof(g_games[i].white_name)); - g_games[i].white_session = white; - board_init(&g_games[i].board); - g_games[i].turn = CHESS_WHITE; - g_games[i].move_number = 1; - snprintf(g_games[i].game_id, sizeof(g_games[i].game_id), - "G%u", i + 1); - hybbx_log_info("[chess] game %s created by %s", - g_games[i].game_id, white_name); - return &g_games[i]; - } - } - return NULL; -} - -chess_game_t *hybbx_chess_find_open_game(void) -{ - unsigned i; - for (i = 0; i < g_max_games; i++) - if (g_games[i].active && g_games[i].black_session == NULL) - return &g_games[i]; - return NULL; -} - -chess_game_t *hybbx_chess_find_game_by_id(const char *id) -{ - unsigned i; - if (id == NULL) return NULL; - for (i = 0; i < g_max_games; i++) - if (g_games[i].active && strcasecmp(g_games[i].game_id, id) == 0) - return &g_games[i]; - return NULL; -} - -int hybbx_chess_join_game(chess_game_t *game, struct hybbx_session *black, - const char *black_name) -{ - if (game == NULL || game->black_session != NULL) return 0; - game->black_session = black; - hybbx_strlcpy(game->black_name, black_name, sizeof(game->black_name)); - hybbx_log_info("[chess] %s joined game %s as black", black_name, - game->game_id); - return 1; -} - -int hybbx_chess_add_spectator(chess_game_t *game, - struct hybbx_session *spec) -{ - unsigned i; - if (game == NULL || spec == NULL) return 0; - if (hybbx_chess_is_player(game, spec)) return 0; - for (i = 0; i < game->spectator_count; i++) - if (game->spectators[i] == spec) return 1; - if (game->spectator_count >= CHESS_SPECTATOR_MAX) return 0; - game->spectators[game->spectator_count++] = spec; - return 1; -} - -void hybbx_chess_remove_spectator(chess_game_t *game, - struct hybbx_session *spec) -{ - unsigned i; - if (game == NULL || spec == NULL) return; - for (i = 0; i < game->spectator_count; i++) { - if (game->spectators[i] == spec) { - game->spectators[i] = game->spectators[--game->spectator_count]; - return; - } - } -} - -int hybbx_chess_is_player(const chess_game_t *game, - const struct hybbx_session *session) -{ - if (game == NULL || session == NULL) return 0; - return game->white_session == session || game->black_session == session; -} - -int hybbx_chess_is_spectator(const chess_game_t *game, - const struct hybbx_session *session) -{ - unsigned i; - if (game == NULL || session == NULL) return 0; - for (i = 0; i < game->spectator_count; i++) - if (game->spectators[i] == session) return 1; - return 0; -} - -chess_color_t hybbx_chess_player_color(const chess_game_t *game, - const struct hybbx_session *session) -{ - if (game == NULL || session == NULL) return CHESS_COLOR_NONE; - if (game->white_session == session) return CHESS_WHITE; - if (game->black_session == session) return CHESS_BLACK; - return CHESS_COLOR_NONE; -} - -/* ── Move execution ────────────────────────────────────────── */ - -int hybbx_chess_make_move(chess_game_t *game, const char *from, - const char *to, char promote, char *err, - size_t errlen) -{ - unsigned fr, ff, tr, tf; - char piece, captured; - int check, mate; - chess_color_t side, opp; - - if (game == NULL || game->result != CHESS_RESULT_NONE) { - if (err) snprintf(err, errlen, "Game is over."); - return 0; - } - - side = game->turn; - - if (!parse_square(from, &fr, &ff)) { - if (err) snprintf(err, errlen, "Invalid square: %s", from); - return 0; - } - if (!parse_square(to, &tr, &tf)) { - if (err) snprintf(err, errlen, "Invalid square: %s", to); - return 0; - } - - piece = game->board.sq[fr][ff]; - if (piece_color(piece) != side) { - if (err) snprintf(err, errlen, "Not your piece."); - return 0; - } - - if (!is_legal_move(&game->board, fr, ff, tr, tf, side)) { - if (err) snprintf(err, errlen, "Illegal move."); - return 0; - } - - captured = game->board.sq[tr][tf]; - game->board.sq[tr][tf] = piece; - game->board.sq[fr][ff] = CHESS_EMPTY; - - /* Pawn promotion */ - if (piece_upper(piece) == 'P') { - if ((side == CHESS_WHITE && tr == 7) || - (side == CHESS_BLACK && tr == 0)) { - char pp = promote ? (char)toupper((unsigned char)promote) : 'Q'; - game->board.sq[tr][tf] = (side == CHESS_WHITE) ? pp : - (char)tolower((unsigned char)pp); - } - } - - opp = (side == CHESS_WHITE) ? CHESS_BLACK : CHESS_WHITE; - check = is_in_check(&game->board, opp); - mate = check && !has_any_legal_move(&game->board, opp); - - make_san(game->last_move_san, sizeof(game->last_move_san), piece, - ff, tr, tf, captured != CHESS_EMPTY, check, mate, promote); - - if (mate) { - game->result = (side == CHESS_WHITE) ? CHESS_WHITE_WINS : - CHESS_BLACK_WINS; - } else if (!has_any_legal_move(&game->board, opp)) { - game->result = CHESS_DRAW_STALEMATE; - } - - game->turn = opp; - if (opp == CHESS_WHITE) game->move_number++; - game->tick_counter = 0; - - hybbx_log_info("[chess] %s: %s %s", game->game_id, - (side == CHESS_WHITE) ? game->white_name : game->black_name, - game->last_move_san); - return 1; -} - -/* ── Resign / Draw ─────────────────────────────────────────── */ - -void hybbx_chess_resign(chess_game_t *game, struct hybbx_session *who) -{ - if (game == NULL || game->result != CHESS_RESULT_NONE) return; - if (game->white_session == who) - game->result = CHESS_BLACK_WINS; - else if (game->black_session == who) - game->result = CHESS_WHITE_WINS; -} - -void hybbx_chess_offer_draw(chess_game_t *game, struct hybbx_session *who) -{ - (void)who; - if (game == NULL || game->result != CHESS_RESULT_NONE) return; - game->result = CHESS_DRAW_AGREEMENT; -} - -/* ── Board rendering ───────────────────────────────────────── */ - -void hybbx_chess_render_board(const chess_board_t *board, - struct hybbx_session *session, - const char *label) -{ - static const char *glyph = ".KQRBNPkqrbnp"; - char line[96]; - unsigned r, f; - - if (label && label[0]) - hybbx_session_write_line(session, label); - - hybbx_session_write_line(session, - " +---+---+---+---+---+---+---+---+"); - - for (r = 0; r < 8; r++) { - unsigned rank = 7 - r; - int n = snprintf(line, sizeof(line), "%u |", rank + 1); - for (f = 0; f < 8; f++) { - char p = board->sq[rank][f]; - const char *ch; - switch (p) { - case 'K': ch = "K"; break; case 'Q': ch = "Q"; break; - case 'R': ch = "R"; break; case 'B': ch = "B"; break; - case 'N': ch = "N"; break; case 'P': ch = "P"; break; - case 'k': ch = "k"; break; case 'q': ch = "q"; break; - case 'r': ch = "r"; break; case 'b': ch = "b"; break; - case 'n': ch = "n"; break; case 'p': ch = "p"; break; - default: ch = "."; break; - } - n += snprintf(line + n, sizeof(line) - (size_t)n, " %s |", ch); - } - hybbx_session_write_line(session, line); - hybbx_session_write_line(session, - " +---+---+---+---+---+---+---+---+"); - } - hybbx_session_write_line(session, - " a b c d e f g h"); -} - -void hybbx_chess_send_board(chess_game_t *game, - struct hybbx_session *session) -{ - char label[96]; - const char *turn_name; - - if (game == NULL || session == NULL) return; - - turn_name = (game->turn == CHESS_WHITE) ? game->white_name : - game->black_name; - - if (game->result != CHESS_RESULT_NONE) { - const char *result_str; - switch (game->result) { - case CHESS_WHITE_WINS: result_str = "1-0 White wins"; break; - case CHESS_BLACK_WINS: result_str = "0-1 Black wins"; break; - case CHESS_DRAW_STALEMATE: result_str = "1/2-1/2 Stalemate"; break; - case CHESS_DRAW_AGREEMENT: result_str = "1/2-1/2 Draw"; break; - default: result_str = "Game over"; break; - } - snprintf(label, sizeof(label), "[%s] %s vs %s %s Move %u", - game->game_id, game->white_name, game->black_name, - result_str, game->move_number); - } else { - snprintf(label, sizeof(label), - "[%s] %s(W) vs %s(B) %s to move %s Move %u", - game->game_id, game->white_name, game->black_name, - turn_name, - game->last_move_san[0] ? game->last_move_san : "", - game->move_number); - } - - hybbx_chess_render_board(&game->board, session, label); -} - -/* ── 15-second broadcast tick ──────────────────────────────── */ - -void hybbx_chess_tick(struct hybbx_service *service) -{ - unsigned i, j; - - (void)service; - - for (i = 0; i < g_max_games; i++) { - chess_game_t *g = &g_games[i]; - - if (!g->active) continue; - if (g->black_session == NULL) continue; - - g->tick_counter++; - if (g->tick_counter < CHESS_REFRESH_SEC) continue; - g->tick_counter = 0; - - if (g->white_session) - hybbx_chess_send_board(g, g->white_session); - if (g->black_session) - hybbx_chess_send_board(g, g->black_session); - for (j = 0; j < g->spectator_count; j++) - hybbx_chess_send_board(g, g->spectators[j]); - } -} |
