diff options
Diffstat (limited to 'src/core/chess_cmd.c')
| -rw-r--r-- | src/core/chess_cmd.c | 397 |
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; +} |
