summaryrefslogtreecommitdiff
path: root/src/core/chess.c
diff options
context:
space:
mode:
authorinfo@mode42.com <info@mode42.com>2026-08-09 11:54:17 +0000
committerinfo@mode42.com <info@mode42.com>2026-08-09 11:54:17 +0000
commita989bd16c40e04bba5a5404b0ef0cd22f19d2707 (patch)
tree604a0439c7d8c022cbd483d62bdc2cfc1c8d18f8 /src/core/chess.c
parenta72e5ea7558e5172c08d397d6f1e0e0f9273e694 (diff)
now including entertain/chess
Diffstat (limited to 'src/core/chess.c')
-rw-r--r--src/core/chess.c668
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]);
- }
-}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com