From db10ebe2207eae99f9fe12aca33fe7825876fc39 Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Thu, 2 Apr 2026 13:23:11 +0200 Subject: [PATCH] feat(games): add room types and game WS message schemas Co-Authored-By: Claude Opus 4.6 (1M context) --- api/src/games/types.ts | 54 +++++++++++++++++++++ shared/modules/dashboard/dashboard.types.ts | 5 ++ 2 files changed, 59 insertions(+) create mode 100644 api/src/games/types.ts diff --git a/api/src/games/types.ts b/api/src/games/types.ts new file mode 100644 index 0000000..e849b4d --- /dev/null +++ b/api/src/games/types.ts @@ -0,0 +1,54 @@ +import { z } from "zod"; + +// --- Room types --- + +export interface Room { + id: string; + gameSlug: string; + host: string; + players: string[]; + spectators: Set; + state: unknown; + status: "waiting" | "playing" | "finished"; + createdAt: number; +} + +export interface RoomSummary { + id: string; + gameSlug: string; + gameName: string; + host: string; + playerCount: number; + maxPlayers: number; + spectatorCount: number; + status: "waiting" | "playing" | "finished"; +} + +export interface PlayerInfo { + discordId: string; + username: string; +} + +// --- Client → Server messages --- + +export const GameWsClientSchema = z.discriminatedUnion("type", [ + z.object({ type: z.literal("CREATE_ROOM"), gameType: z.string() }), + z.object({ type: z.literal("JOIN_ROOM"), roomId: z.string(), as: z.enum(["player", "spectator"]) }), + z.object({ type: z.literal("LEAVE_ROOM"), roomId: z.string() }), + z.object({ type: z.literal("GAME_ACTION"), roomId: z.string(), action: z.record(z.unknown()) }), +]); + +export type GameWsClientMessage = z.infer; + +// --- Server → Client messages --- + +export type GameWsServerMessage = + | { type: "ROOM_LIST_UPDATE"; rooms: RoomSummary[] } + | { type: "GAME_STATE"; roomId: string; state: unknown } + | { type: "GAME_UPDATE"; roomId: string; state: unknown } + | { type: "PLAYER_JOINED"; roomId: string; player: PlayerInfo; as: "player" | "spectator" } + | { type: "PLAYER_LEFT"; roomId: string; playerId: string } + | { type: "GAME_STARTED"; roomId: string; state: unknown } + | { type: "GAME_ENDED"; roomId: string; winner: string | null; reason: string } + | { type: "ROOM_CREATED"; roomId: string; gameSlug: string } + | { type: "ERROR"; message: string }; diff --git a/shared/modules/dashboard/dashboard.types.ts b/shared/modules/dashboard/dashboard.types.ts index 948e0c9..966cf52 100644 --- a/shared/modules/dashboard/dashboard.types.ts +++ b/shared/modules/dashboard/dashboard.types.ts @@ -110,6 +110,11 @@ export const WsMessageSchema = z.discriminatedUnion("type", [ z.object({ type: z.literal("PONG") }), z.object({ type: z.literal("STATS_UPDATE"), data: DashboardStatsSchema }), z.object({ type: z.literal("NEW_EVENT"), data: RecentEventSchema }), + // Game messages (client → server) + z.object({ type: z.literal("CREATE_ROOM"), gameType: z.string() }), + z.object({ type: z.literal("JOIN_ROOM"), roomId: z.string(), as: z.enum(["player", "spectator"]) }), + z.object({ type: z.literal("LEAVE_ROOM"), roomId: z.string() }), + z.object({ type: z.literal("GAME_ACTION"), roomId: z.string(), action: z.record(z.unknown()) }), ]); export type WsMessage = z.infer;