summaryrefslogtreecommitdiff
path: root/src/core/chess_cmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/chess_cmd.c')
-rw-r--r--src/core/chess_cmd.c397
1 files changed, 397 insertions, 0 deletions
diff --git a/src/core/chess_cmd.c b/src/core/chess_cmd.c
new file mode 100644
index 0000000..6ad1058
--- /dev/null
+++ b/src/core/chess_cmd.c
@@ -0,0 +1,397 @@
+/* ── Chess command handler ──────────────────────────────────── */
+
+#include "hybbx/chess.h"
+#include "hybbx/session.h"
+#include "hybbx/service.h"
+#include "hybbx/hybbx.h"
+
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <ctype.h>
+
+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;
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com