#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 #include #include #include 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]); } }