/* * Entertain plugin — chess_cmd.c * /chess command handler (join/leave/list/move/resign/draw/clear/status/board). */ #include "entertain_chess.h" #include "hybbx/session.h" #include "hybbx/service.h" #include "hybbx/command.h" #include #include #include /* Forward declaration */ hybbx_result_t entertain_cmd_chess(struct hybbx_service *service, struct hybbx_session *session, const struct hybbx_parsed_command *cmd); static int ieq(const char *a, const char *b) { if (!a || !b) return 0; while (*a && *b) { char ca = (*a >= 'A' && *a <= 'Z') ? *a + 32 : *a; char cb = (*b >= 'A' && *b <= 'Z') ? *b + 32 : *b; if (ca != cb) return 0; a++; b++; } return *a == *b; } hybbx_result_t entertain_cmd_chess(struct hybbx_service *service, struct hybbx_session *session, const struct hybbx_parsed_command *cmd) { const char *sub; 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"; /* ── help ──────────────────────────────────────────────── */ if (ieq(sub, "help") || ieq(sub, "?")) { hybbx_session_write_line(session, "Chess commands:"); hybbx_session_write_line(session, " /chess new - Create game (you are White)"); hybbx_session_write_line(session, " /chess join [id] - Join game as Black"); hybbx_session_write_line(session, " /chess watch [id] - Spectate"); hybbx_session_write_line(session, " /chess board - Show board"); hybbx_session_write_line(session, " /chess move e2 e4 - Move (alias: /mv e2 e4)"); hybbx_session_write_line(session, " /chess resign - Resign"); hybbx_session_write_line(session, " /chess draw - Offer draw"); hybbx_session_write_line(session, " /chess list - List games + boards"); hybbx_session_write_line(session, " /chess leave - Stop watching"); hybbx_session_write_line(session, " /chess clear - Clear game history"); snprintf(line, sizeof(line), "Board refreshes every %ds for all.", CHESS_REFRESH_SEC); hybbx_session_write_line(session, line); return HYBBX_OK; } /* ── status ────────────────────────────────────────────── */ if (ieq(sub, "status")) { snprintf(line, sizeof(line), "Chess: %u max games", chess_max_games()); hybbx_session_write_line(session, line); return HYBBX_OK; } /* ── list ──────────────────────────────────────────────── */ if (ieq(sub, "list") || ieq(sub, "ls")) { unsigned found = 0; hybbx_session_write_line(session, "Chess games:"); for (unsigned i = 0; i < chess_max_games(); i++) { chess_game_t *g = chess_get_game(i); if (!g) continue; const char *st = (g->result != CHESS_RESULT_NONE) ? "finished" : (!g->black_session) ? "waiting" : "playing"; snprintf(line, sizeof(line), " [%s] %s(W) vs %s(B) %s move %u", g->game_id, g->white_name, g->black_session ? g->black_name : "...", st, g->move_number); hybbx_session_write_line(session, line); chess_send_board(g, session); if (g->spectator_count > 0) { int n = snprintf(line, sizeof(line), " Spectators (%u): ", g->spectator_count); for (unsigned j = 0; j < g->spectator_count; j++) { const char *nm = hybbx_session_display_name(g->spectators[j]); if (j > 0) n += snprintf(line+n, sizeof(line)-(size_t)n, ", "); n += snprintf(line+n, sizeof(line)-(size_t)n, "%s", nm ? nm : "?"); } 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; } /* ── new ───────────────────────────────────────────────── */ if (ieq(sub, "new") || ieq(sub, "create")) { for (unsigned i = 0; i < chess_max_games(); i++) { chess_game_t *g = chess_get_game(i); if (g && chess_is_player(g, session)) { hybbx_session_write_line(session, "Already in a game. /chess resign first."); return HYBBX_ERR_DENIED; } } chess_game_t *g = chess_create_game(session, hybbx_session_username(session)); if (!g) { hybbx_session_write_line(session, "No free game slots."); return HYBBX_ERR_DENIED; } snprintf(line, sizeof(line), "Game %s created. You are White.", g->game_id); hybbx_session_write_line(session, line); hybbx_session_write_line(session, "Waiting for Black to /chess join..."); chess_send_board(g, session); return HYBBX_OK; } /* ── join ──────────────────────────────────────────────── */ if (ieq(sub, "join")) { const char *id = (cmd->argc >= 2) ? cmd->argv[1] : NULL; for (unsigned i = 0; i < chess_max_games(); i++) { chess_game_t *g = chess_get_game(i); if (g && chess_is_player(g, session)) { hybbx_session_write_line(session, "Already in a game. /chess resign first."); return HYBBX_ERR_DENIED; } } chess_game_t *g = NULL; if (id) { g = chess_find_game_by_id(id); if (!g || !g->active) { hybbx_session_write_line(session, "Game not found."); return HYBBX_ERR_NOT_FOUND; } if (g->black_session) { hybbx_session_write_line(session, "Game is full."); return HYBBX_ERR_DENIED; } } else { g = chess_find_open_game(); if (!g) { hybbx_session_write_line(session, "No open games. /chess new"); return HYBBX_ERR_NOT_FOUND; } } chess_join_game(g, session, hybbx_session_username(session)); snprintf(line, sizeof(line), "Joined %s as Black. Game on!", g->game_id); hybbx_session_write_line(session, line); chess_send_board(g, g->white_session); chess_send_board(g, session); return HYBBX_OK; } /* ── watch ─────────────────────────────────────────────── */ if (ieq(sub, "watch") || ieq(sub, "spectate")) { const char *id = (cmd->argc >= 2) ? cmd->argv[1] : NULL; chess_game_t *g = NULL; if (id) { g = chess_find_game_by_id(id); } else { for (unsigned i = 0; i < chess_max_games(); i++) { chess_game_t *t = chess_get_game(i); if (t && t->black_session) { g = t; break; } } } if (!g || !g->active) { hybbx_session_write_line(session, "No game to watch."); return HYBBX_ERR_NOT_FOUND; } if (!chess_add_spectator(g, session)) { hybbx_session_write_line(session, "Spectator list full."); return HYBBX_ERR_DENIED; } snprintf(line, sizeof(line), "Watching %s. Board refreshes every %ds.", g->game_id, CHESS_REFRESH_SEC); hybbx_session_write_line(session, line); chess_send_board(g, session); return HYBBX_OK; } /* ── leave ─────────────────────────────────────────────── */ if (ieq(sub, "leave") || ieq(sub, "unwatch")) { for (unsigned i = 0; i < chess_max_games(); i++) { chess_game_t *g = chess_get_game(i); if (g && chess_is_spectator(g, session)) { chess_remove_spectator(g, session); hybbx_session_write_line(session, "Stopped watching."); return HYBBX_OK; } } hybbx_session_write_line(session, "Not watching any game."); return HYBBX_ERR_NOT_FOUND; } /* ── board ─────────────────────────────────────────────── */ if (ieq(sub, "board") || ieq(sub, "show")) { for (unsigned i = 0; i < chess_max_games(); i++) { chess_game_t *g = chess_get_game(i); if (g && (chess_is_player(g, session) || chess_is_spectator(g, session))) { chess_send_board(g, session); return HYBBX_OK; } } hybbx_session_write_line(session, "Not in a game. /chess new, join, or watch."); return HYBBX_ERR_NOT_FOUND; } /* ── move ──────────────────────────────────────────────── */ if (ieq(sub, "move") || ieq(sub, "mv") || ieq(sub, "m")) { if (cmd->argc < 3) { hybbx_session_write_line(session, "Usage: /chess move e2 e4"); return HYBBX_ERR_INVALID; } chess_game_t *game = NULL; for (unsigned i = 0; i < chess_max_games(); i++) { chess_game_t *g = chess_get_game(i); if (g && g->result == CHESS_RESULT_NONE && chess_is_player(g, session)) { if (chess_player_color(g, session) == g->turn) { game = g; break; } } } if (!game) { hybbx_session_write_line(session, "Not your turn or no active game."); return HYBBX_ERR_DENIED; } char err[64]; if (!chess_make_move(game, cmd->argv[1], cmd->argv[2], 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) chess_send_board(game, game->white_session); if (game->black_session) chess_send_board(game, game->black_session); for (unsigned j = 0; j < game->spectator_count; j++) chess_send_board(game, game->spectators[j]); /* Game over? */ if (game->result != CHESS_RESULT_NONE) { const char *res; 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 (unsigned j = 0; j < game->spectator_count; j++) hybbx_session_write_line(game->spectators[j], line); } return HYBBX_OK; } /* ── resign ────────────────────────────────────────────── */ if (ieq(sub, "resign")) { chess_game_t *game = NULL; for (unsigned i = 0; i < chess_max_games(); i++) { chess_game_t *g = chess_get_game(i); if (g && chess_is_player(g, session)) { game = g; break; } } if (!game) { hybbx_session_write_line(session, "Not in a game."); return HYBBX_ERR_NOT_FOUND; } 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); for (unsigned j = 0; j < game->spectator_count; j++) hybbx_session_write_line(game->spectators[j], line); return HYBBX_OK; } /* ── draw ──────────────────────────────────────────────── */ if (ieq(sub, "draw")) { chess_game_t *game = NULL; for (unsigned i = 0; i < chess_max_games(); i++) { chess_game_t *g = chess_get_game(i); if (g && chess_is_player(g, session)) { game = g; break; } } if (!game) { hybbx_session_write_line(session, "Not in a game."); return HYBBX_ERR_NOT_FOUND; } 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); for (unsigned j = 0; j < game->spectator_count; j++) hybbx_session_write_line(game->spectators[j], line); return HYBBX_OK; } /* ── clear ─────────────────────────────────────────────── */ if (ieq(sub, "clear") || ieq(sub, "reset")) { hybbx_session_write_line(session, "Chess has no persistent history to clear."); return HYBBX_OK; } hybbx_session_write_line(session, "Unknown chess command. /chess help"); return HYBBX_ERR_NOT_FOUND; }