summaryrefslogtreecommitdiff
path: root/plugins/entertain/chess_cmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/entertain/chess_cmd.c')
-rw-r--r--plugins/entertain/chess_cmd.c277
1 files changed, 277 insertions, 0 deletions
diff --git a/plugins/entertain/chess_cmd.c b/plugins/entertain/chess_cmd.c
new file mode 100644
index 0000000..3e57223
--- /dev/null
+++ b/plugins/entertain/chess_cmd.c
@@ -0,0 +1,277 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <strings.h>
+
+/* 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;
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com