/* ── Chess command handler ──────────────────────────────────── */ #include "hybbx/chess.h" #include "hybbx/session.h" #include "hybbx/service.h" #include "hybbx/hybbx.h" #include #include #include #include static int str_ieq(const char *a, const char *b) { if (a == NULL || b == NULL) return 0; while (*a != '\0' && *b != '\0') { char ca = (char)(*a >= 'A' && *a <= 'Z' ? *a + 32 : *a); char cb = (char)(*b >= 'A' && *b <= 'Z' ? *b + 32 : *b); if (ca != cb) return 0; a++; b++; } return *a == '\0' && *b == '\0'; } hybbx_result_t cmd_chess(hybbx_service_t *service, hybbx_session_t *session, const hybbx_parsed_command_t *cmd) { const char *sub; chess_game_t *game; char line[128]; (void)service; if (!hybbx_session_logged_in(session)) { hybbx_session_write_line(session, "Log in to play chess."); return HYBBX_ERR_DENIED; } sub = (cmd->argc >= 1) ? cmd->argv[0] : "help"; /* ── /chess help ───────────────────────────────────────── */ if (str_ieq(sub, "help") || str_ieq(sub, "?")) { hybbx_session_write_line(session, "Chess commands:"); hybbx_session_write_line(session, " /chess new - Create a new game (you are White)"); hybbx_session_write_line(session, " /chess join - Join an open game as Black"); hybbx_session_write_line(session, " /chess join G2 - Join a specific game by ID"); hybbx_session_write_line(session, " /chess watch - Watch a game as spectator"); hybbx_session_write_line(session, " /chess watch G2 - Watch a specific game"); hybbx_session_write_line(session, " /chess board - Show current board"); hybbx_session_write_line(session, " /chess move e2 e4 - Move piece (alias: /mv e2 e4)"); hybbx_session_write_line(session, " /chess resign - Resign the game"); hybbx_session_write_line(session, " /chess draw - Offer/accept draw"); hybbx_session_write_line(session, " /chess list - List games with board + spectators"); hybbx_session_write_line(session, " /chess leave - Stop watching"); hybbx_session_write_line(session, "Board auto-refreshes every 15 seconds for all."); return HYBBX_OK; } /* ── /chess list ───────────────────────────────────────── */ if (str_ieq(sub, "list") || str_ieq(sub, "ls")) { unsigned found = 0; unsigned i; for (i = 0; i < hybbx_chess_max_games(); i++) { game = hybbx_chess_get_game(i); if (game == NULL || !game->active) continue; /* Header: game id, players, state */ { const char *state = (game->result != CHESS_RESULT_NONE) ? "finished" : (game->black_session == NULL) ? "waiting for Black" : "playing"; snprintf(line, sizeof(line), "[%s] %s(W) vs %s(B) %s move %u", game->game_id, game->white_name, game->black_session ? game->black_name : "...", state, game->move_number); } hybbx_session_write_line(session, line); /* Board */ hybbx_chess_send_board(game, session); /* Spectators */ if (game->spectator_count > 0) { unsigned j; int n = snprintf(line, sizeof(line), " Spectators (%u): ", game->spectator_count); for (j = 0; j < game->spectator_count; j++) { const char *sname = hybbx_session_display_name( game->spectators[j]); if (j > 0) n += snprintf(line + n, sizeof(line) - (size_t)n, ", "); n += snprintf(line + n, sizeof(line) - (size_t)n, "%s", sname ? sname : "?"); } hybbx_session_write_line(session, line); } hybbx_session_write_line(session, ""); found++; } if (!found) hybbx_session_write_line(session, " No active games."); return HYBBX_OK; } /* ── /chess new ────────────────────────────────────────── */ if (str_ieq(sub, "new") || str_ieq(sub, "create")) { unsigned i; for (i = 0; i < hybbx_chess_max_games(); i++) { game = hybbx_chess_get_game(i); if (game && game->active && hybbx_chess_is_player(game, session)) { hybbx_session_write_line(session, "You are already in a game. /chess resign first."); return HYBBX_ERR_DENIED; } } game = hybbx_chess_create_game(session, hybbx_session_username(session)); if (game == NULL) { hybbx_session_write_line(session, "No free game slots."); return HYBBX_ERR_DENIED; } snprintf(line, sizeof(line), "Game %s created. You are White.", game->game_id); hybbx_session_write_line(session, line); hybbx_session_write_line(session, "Waiting for Black to /chess join..."); hybbx_chess_send_board(game, session); return HYBBX_OK; } /* ── /chess join [id] ──────────────────────────────────── */ if (str_ieq(sub, "join")) { const char *id = (cmd->argc >= 2) ? cmd->argv[1] : NULL; unsigned i; for (i = 0; i < hybbx_chess_max_games(); i++) { game = hybbx_chess_get_game(i); if (game && game->active && hybbx_chess_is_player(game, session)) { hybbx_session_write_line(session, "You are already in a game. /chess resign first."); return HYBBX_ERR_DENIED; } } if (id) { game = hybbx_chess_find_game_by_id(id); if (game == NULL || !game->active) { hybbx_session_write_line(session, "Game not found."); return HYBBX_ERR_NOT_FOUND; } if (game->black_session != NULL) { hybbx_session_write_line(session, "Game is full."); return HYBBX_ERR_DENIED; } } else { game = hybbx_chess_find_open_game(); if (game == NULL) { hybbx_session_write_line(session, "No open games. /chess new to create one."); return HYBBX_ERR_NOT_FOUND; } } hybbx_chess_join_game(game, session, hybbx_session_username(session)); snprintf(line, sizeof(line), "Joined %s as Black. Game on!", game->game_id); hybbx_session_write_line(session, line); hybbx_chess_send_board(game, game->white_session); hybbx_chess_send_board(game, session); return HYBBX_OK; } /* ── /chess watch [id] ─────────────────────────────────── */ if (str_ieq(sub, "watch") || str_ieq(sub, "spectate")) { const char *id = (cmd->argc >= 2) ? cmd->argv[1] : NULL; unsigned i; if (id) { game = hybbx_chess_find_game_by_id(id); } else { game = NULL; for (i = 0; i < hybbx_chess_max_games(); i++) { chess_game_t *g = hybbx_chess_get_game(i); if (g && g->active && g->black_session != NULL) { game = g; break; } } } if (game == NULL || !game->active) { hybbx_session_write_line(session, "No game to watch."); return HYBBX_ERR_NOT_FOUND; } if (!hybbx_chess_add_spectator(game, session)) { hybbx_session_write_line(session, "Spectator list full."); return HYBBX_ERR_DENIED; } snprintf(line, sizeof(line), "Watching %s. Board refreshes every %ds.", game->game_id, CHESS_REFRESH_SEC); hybbx_session_write_line(session, line); hybbx_chess_send_board(game, session); return HYBBX_OK; } /* ── /chess leave ──────────────────────────────────────── */ if (str_ieq(sub, "leave") || str_ieq(sub, "unwatch")) { unsigned i; for (i = 0; i < hybbx_chess_max_games(); i++) { game = hybbx_chess_get_game(i); if (game && game->active && hybbx_chess_is_spectator(game, session)) { hybbx_chess_remove_spectator(game, session); hybbx_session_write_line(session, "Stopped watching."); return HYBBX_OK; } } hybbx_session_write_line(session, "You are not watching any game."); return HYBBX_ERR_NOT_FOUND; } /* ── /chess board ──────────────────────────────────────── */ if (str_ieq(sub, "board") || str_ieq(sub, "show")) { unsigned i; game = NULL; for (i = 0; i < hybbx_chess_max_games(); i++) { chess_game_t *g = hybbx_chess_get_game(i); if (g && g->active && (hybbx_chess_is_player(g, session) || hybbx_chess_is_spectator(g, session))) { game = g; break; } } if (game == NULL) { hybbx_session_write_line(session, "Not in a game. /chess new, join, or watch."); return HYBBX_ERR_NOT_FOUND; } hybbx_chess_send_board(game, session); return HYBBX_OK; } /* ── /chess move e2 e4 ─────────────────────────────────── */ if (str_ieq(sub, "move") || str_ieq(sub, "mv") || str_ieq(sub, "m")) { const char *from, *to; char err[64]; unsigned i; if (cmd->argc < 3) { hybbx_session_write_line(session, "Usage: /chess move e2 e4"); return HYBBX_ERR_INVALID; } from = cmd->argv[1]; to = cmd->argv[2]; game = NULL; for (i = 0; i < hybbx_chess_max_games(); i++) { chess_game_t *g = hybbx_chess_get_game(i); if (g && g->active && g->result == CHESS_RESULT_NONE && hybbx_chess_is_player(g, session)) { chess_color_t color = hybbx_chess_player_color(g, session); if (color == g->turn) { game = g; break; } } } if (game == NULL) { hybbx_session_write_line(session, "Not your turn or no active game."); return HYBBX_ERR_DENIED; } if (!hybbx_chess_make_move(game, from, to, 0, err, sizeof(err))) { snprintf(line, sizeof(line), "Illegal: %s", err); hybbx_session_write_line(session, line); return HYBBX_ERR_INVALID; } /* Broadcast to all */ if (game->white_session) hybbx_chess_send_board(game, game->white_session); if (game->black_session) hybbx_chess_send_board(game, game->black_session); { unsigned j; for (j = 0; j < game->spectator_count; j++) hybbx_chess_send_board(game, game->spectators[j]); } if (game->result != CHESS_RESULT_NONE) { const char *res; unsigned j; switch (game->result) { case CHESS_WHITE_WINS: res = "White wins!"; break; case CHESS_BLACK_WINS: res = "Black wins!"; break; case CHESS_DRAW_STALEMATE: res = "Stalemate!"; break; case CHESS_DRAW_AGREEMENT: res = "Draw!"; break; default: res = "Game over."; break; } snprintf(line, sizeof(line), "[%s] %s", game->game_id, res); if (game->white_session) hybbx_session_write_line(game->white_session, line); if (game->black_session) hybbx_session_write_line(game->black_session, line); for (j = 0; j < game->spectator_count; j++) hybbx_session_write_line(game->spectators[j], line); } return HYBBX_OK; } /* ── /chess resign ─────────────────────────────────────── */ if (str_ieq(sub, "resign")) { unsigned i; game = NULL; for (i = 0; i < hybbx_chess_max_games(); i++) { chess_game_t *g = hybbx_chess_get_game(i); if (g && g->active && hybbx_chess_is_player(g, session)) { game = g; break; } } if (game == NULL) { hybbx_session_write_line(session, "You are not in a game."); return HYBBX_ERR_NOT_FOUND; } hybbx_chess_resign(game, session); snprintf(line, sizeof(line), "[%s] %s resigned.", game->game_id, hybbx_session_username(session)); if (game->white_session) hybbx_session_write_line(game->white_session, line); if (game->black_session) hybbx_session_write_line(game->black_session, line); { unsigned j; for (j = 0; j < game->spectator_count; j++) hybbx_session_write_line(game->spectators[j], line); } return HYBBX_OK; } /* ── /chess draw ───────────────────────────────────────── */ if (str_ieq(sub, "draw")) { unsigned i; game = NULL; for (i = 0; i < hybbx_chess_max_games(); i++) { chess_game_t *g = hybbx_chess_get_game(i); if (g && g->active && hybbx_chess_is_player(g, session)) { game = g; break; } } if (game == NULL) { hybbx_session_write_line(session, "You are not in a game."); return HYBBX_ERR_NOT_FOUND; } hybbx_chess_offer_draw(game, session); snprintf(line, sizeof(line), "[%s] Draw agreed.", game->game_id); if (game->white_session) hybbx_session_write_line(game->white_session, line); if (game->black_session) hybbx_session_write_line(game->black_session, line); { unsigned j; for (j = 0; j < game->spectator_count; j++) hybbx_session_write_line(game->spectators[j], line); } return HYBBX_OK; } hybbx_session_write_line(session, "Unknown chess command. /chess help for list."); return HYBBX_ERR_NOT_FOUND; }