diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index ff5f988..7dc79f5 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,2 +1,3 @@ export * from "./constants.js"; export * from "./schemas/game.js"; +export * from "./schemas/lobby.js"; diff --git a/packages/shared/src/schemas/lobby.ts b/packages/shared/src/schemas/lobby.ts index 93d5563..1e92a6f 100644 --- a/packages/shared/src/schemas/lobby.ts +++ b/packages/shared/src/schemas/lobby.ts @@ -1,6 +1,7 @@ import * as z from "zod"; import { LOBBY_STATUSES } from "../constants.js"; +import { GameQuestionSchema } from "./game.js"; export const LobbyPlayerSchema = z.object({ lobbyId: z.uuid(), @@ -19,4 +20,103 @@ export const LobbySchema = z.object({ createdAt: z.iso.datetime(), players: z.array(LobbyPlayerSchema), }); + export type Lobby = z.infer; + +export const JoinLobbyResponseSchema = LobbySchema; + +export type JoinLobbyResponse = z.infer; + +// ---------------------------------------------------------------------------- +// WebSocket: Client → Server +// ---------------------------------------------------------------------------- + +export const WsLobbyJoinSchema = z.object({ + type: z.literal("lobby:join"), + code: z.string().min(1).max(10), +}); + +export type WsLobbyJoin = z.infer; + +export const WsLobbyLeaveSchema = z.object({ + type: z.literal("lobby:leave"), + lobbyId: z.uuid(), +}); + +export type WsLobbyLeave = z.infer; + +export const WsLobbyStartSchema = z.object({ + type: z.literal("lobby:start"), + lobbyId: z.uuid(), +}); + +export type WsLobbyStart = z.infer; + +export const WsGameAnswerSchema = z.object({ + type: z.literal("game:answer"), + lobbyId: z.uuid(), + questionId: z.uuid(), + selectedOptionId: z.number().int().min(0).max(3), +}); + +export type WsGameAnswer = z.infer; + +export const WsClientMessageSchema = z.discriminatedUnion("type", [ + WsLobbyJoinSchema, + WsLobbyLeaveSchema, + WsLobbyStartSchema, + WsGameAnswerSchema, +]); +export type WsClientMessage = z.infer; + +// ---------------------------------------------------------------------------- +// WebSocket: Server → Client +// ---------------------------------------------------------------------------- + +export const WsLobbyStateSchema = z.object({ + type: z.literal("lobby:state"), + lobby: LobbySchema, +}); + +export type WsLobbyState = z.infer; + +export const WsGameQuestionSchema = z.object({ + type: z.literal("game:question"), + question: GameQuestionSchema, + questionNumber: z.number().int().min(1), + totalQuestions: z.number().int().min(1), +}); + +export type WsGameQuestion = z.infer; + +export const WsGameAnswerResultSchema = z.object({ + type: z.literal("game:answer_result"), + correctOptionId: z.number().int().min(0).max(3), + results: z.array( + z.object({ + userId: z.string(), + selectedOptionId: z.number().int().min(0).max(3).nullable(), + isCorrect: z.boolean(), + }), + ), + players: z.array(LobbyPlayerSchema), +}); + +export type WsGameAnswerResult = z.infer; + +export const WsGameFinishedSchema = z.object({ + type: z.literal("game:finished"), + players: z.array(LobbyPlayerSchema), + winnerIds: z.array(z.string()), +}); + +export type WsGameFinished = z.infer; + +export const WsServerMessageSchema = z.discriminatedUnion("type", [ + WsLobbyStateSchema, + WsGameQuestionSchema, + WsGameAnswerResultSchema, + WsGameFinishedSchema, +]); + +export type WsServerMessage = z.infer;