feat(games): implement chess game plugin with full UI
Some checks failed
Deploy to Production / test (push) Failing after 32s
Some checks failed
Deploy to Production / test (push) Failing after 32s
Add chess as the first game plugin using the existing multiplayer framework. Server-side game logic uses chess.js with server-authoritative clock management. Client uses react-chessboard v5 with cburnett piece set, drag-and-drop + click-to-move, configurable time controls (bullet/blitz/rapid/classical/none), draw offers, resignation, and timeout detection. Extends the game framework with room creation options to support per-game configuration. Includes 57 tests covering all code paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -117,7 +117,7 @@ export class GameServer {
|
||||
|
||||
switch (msg.type) {
|
||||
case "CREATE_ROOM": {
|
||||
const result = this.roomManager.createRoom(msg.gameType, discordId);
|
||||
const result = this.roomManager.createRoom(msg.gameType, discordId, msg.options);
|
||||
if (!result.ok) {
|
||||
ws.send(JSON.stringify({ type: "ERROR", message: result.error }));
|
||||
return;
|
||||
|
||||
@@ -33,7 +33,7 @@ export class RoomManager {
|
||||
private cleanupTimers = new Map<string, Timer>();
|
||||
readonly emitter = mitt<RoomEvents>();
|
||||
|
||||
createRoom(gameSlug: string, hostId: string): CreateResult {
|
||||
createRoom(gameSlug: string, hostId: string, options?: Record<string, unknown>): CreateResult {
|
||||
const plugin = gameRegistry.get(gameSlug);
|
||||
if (!plugin) return { ok: false, error: `Unknown game type: ${gameSlug}` };
|
||||
|
||||
@@ -47,6 +47,7 @@ export class RoomManager {
|
||||
state: null,
|
||||
status: "waiting",
|
||||
createdAt: Date.now(),
|
||||
options,
|
||||
};
|
||||
|
||||
this.rooms.set(id, room);
|
||||
@@ -85,7 +86,7 @@ export class RoomManager {
|
||||
room.players.push(playerId);
|
||||
|
||||
if (room.players.length >= plugin.maxPlayers) {
|
||||
room.state = plugin.createInitialState(room.players);
|
||||
room.state = plugin.createInitialState(room.players, room.options);
|
||||
room.status = "playing";
|
||||
this.scheduleCleanup(roomId, ROOM_CONFIG.PLAYING_MAX_MS);
|
||||
|
||||
@@ -185,7 +186,7 @@ export class RoomManager {
|
||||
room.players.push(adminId);
|
||||
}
|
||||
|
||||
room.state = plugin.createInitialState(room.players);
|
||||
room.state = plugin.createInitialState(room.players, room.options);
|
||||
room.status = "playing";
|
||||
this.scheduleCleanup(roomId, ROOM_CONFIG.PLAYING_MAX_MS);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface Room {
|
||||
state: unknown;
|
||||
status: "waiting" | "playing" | "finished";
|
||||
createdAt: number;
|
||||
options?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface RoomSummary {
|
||||
@@ -28,7 +29,7 @@ export interface PlayerInfo {
|
||||
}
|
||||
|
||||
export const GameWsClientSchema = z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal("CREATE_ROOM"), gameType: z.string() }),
|
||||
z.object({ type: z.literal("CREATE_ROOM"), gameType: z.string(), options: z.looseObject({}).optional() }),
|
||||
z.object({
|
||||
type: z.literal("JOIN_ROOM"),
|
||||
roomId: z.string(),
|
||||
|
||||
@@ -18,6 +18,11 @@ import type { WsConnectionData } from "./games/GameServer";
|
||||
import { getSession } from "./routes/auth.routes";
|
||||
import { GameWsClientSchema } from "./games/types";
|
||||
|
||||
// Register game plugins
|
||||
import { gameRegistry } from "@shared/games/registry";
|
||||
import { chessPlugin } from "@shared/games/chess/chess.plugin";
|
||||
gameRegistry.register(chessPlugin);
|
||||
|
||||
const WS_CONFIG = {
|
||||
MAX_CONNECTIONS: 200,
|
||||
MAX_PAYLOAD_BYTES: 16384,
|
||||
|
||||
Reference in New Issue
Block a user