import { z } from "zod"; import { createController, createEndpoint } from "./controller"; import { getCurrentGame, getGame, getGames, getTotalGamesPlayed, parseGameState, upsertGameState, } from "../repositories/gameRepository"; import { serverToClientGame, type ServerGame } from "../../shared/game"; import crypto from "crypto"; import { game } from "../entities/game"; import { UnauthorizedError } from "../errors/UnauthorizedError"; import { emit, emitToWS } from "../events"; import { serverGame } from "../../shared/gameType"; import { pickRandom } from "../../shared/utils"; import { addGems } from "../repositories/gemsRepository"; import { getCollection } from "../repositories/collectionRepository"; const safeGameState = (gameState: ServerGame) => { return gameState.finished ? gameState : serverToClientGame(gameState); }; export const gameController = createController({ getGameState: createEndpoint(z.string(), async (uuid, ctx) => { const game = await getGame(ctx.db, uuid); const parsed = parseGameState(game.gameState); const gameState = await serverGame.parseAsync(parsed); return safeGameState(gameState); }), createGame: createEndpoint(z.null(), async (_, { user, db }) => { if (!user) throw new UnauthorizedError("Unauthorized"); const uuid = crypto.randomUUID() as string; const collection = await getCollection(db, user); const newGame: ServerGame = game.createGame({ uuid, user: user, mines: 2, width: 4, height: 4, theme: pickRandom(collection.entries.filter((e) => e.selected)).id, }); upsertGameState(db, newGame); emit({ type: "new", user, }); emit({ type: "updateStage", game: uuid, stage: newGame.stage, started: newGame.started, }); return newGame; }), reveal: createEndpoint( z.object({ x: z.number(), y: z.number() }), async ({ x, y }, { db, user, ws }) => { if (!user) throw new UnauthorizedError("Unauthorized"); const dbGame = await getCurrentGame(db, user); const serverGame = parseGameState(dbGame.gameState); const ts = serverGame.finished; game.reveal(serverGame, x, y, true); await upsertGameState(db, serverGame); emit({ type: "updateGame", game: dbGame.uuid, gameState: safeGameState(serverGame), }); if (ts === 0 && serverGame.finished !== 0) { emit({ type: "loss", stage: serverGame.stage, user, time: serverGame.finished - serverGame.started, }); const reward = game.getRewards(serverGame); emitToWS( { type: "gemsRewarded", stage: serverGame.stage, gems: reward, }, ws, ); await addGems(db, user, reward); } }, ), placeFlag: createEndpoint( z.object({ x: z.number(), y: z.number() }), async ({ x, y }, { db, user, ws }) => { if (!user) throw new UnauthorizedError("Unauthorized"); const dbGame = await getCurrentGame(db, user); const serverGame = parseGameState(dbGame.gameState); const ts = serverGame.finished; game.placeFlag(serverGame, x, y); await upsertGameState(db, serverGame); emit({ type: "updateGame", game: dbGame.uuid, gameState: safeGameState(serverGame), }); if (ts === 0 && serverGame.finished !== 0) { emit({ type: "loss", stage: serverGame.stage, user, time: serverGame.finished - serverGame.started, }); const reward = game.getRewards(serverGame); emitToWS( { type: "gemsRewarded", stage: serverGame.stage, gems: reward, }, ws, ); await addGems(db, user, reward); } }, ), placeQuestionMark: createEndpoint( z.object({ x: z.number(), y: z.number() }), async ({ x, y }, { db, user }) => { if (!user) throw new UnauthorizedError("Unauthorized"); const dbGame = await getCurrentGame(db, user); const serverGame = parseGameState(dbGame.gameState); game.placeQuestionMark(serverGame, x, y); await upsertGameState(db, serverGame); emit({ type: "updateGame", game: dbGame.uuid, gameState: safeGameState(serverGame), }); }, ), clearTile: createEndpoint( z.object({ x: z.number(), y: z.number() }), async ({ x, y }, { db, user, ws }) => { if (!user) throw new UnauthorizedError("Unauthorized"); const dbGame = await getCurrentGame(db, user); const serverGame = parseGameState(dbGame.gameState); const ts = serverGame.finished; game.clearTile(serverGame, x, y); upsertGameState(db, serverGame); emit({ type: "updateGame", game: dbGame.uuid, gameState: safeGameState(serverGame), }); if (ts === 0 && serverGame.finished !== 0) { emit({ type: "loss", stage: serverGame.stage, user, time: serverGame.finished - serverGame.started, }); const reward = game.getRewards(serverGame); emitToWS( { type: "gemsRewarded", stage: serverGame.stage, gems: reward, }, ws, ); await addGems(db, user, reward); } }, ), getGames: createEndpoint( z.object({ page: z.number().default(0), user: z.string(), }), async ({ page, user }, { db }) => { const perPage = 20; const offset = page * perPage; const games = await getGames(db, user); const parsedGames = games .slice(offset, offset + perPage) .map((game) => parseGameState(game.gameState)); const isLastPage = games.length <= offset + perPage; return { data: parsedGames, nextPage: isLastPage ? undefined : page + 1, }; }, ), getTotalGamesPlayed: createEndpoint( z.object({ user: z.string().optional(), }), async ({ user }, { db }) => { const total = await getTotalGamesPlayed(db, user); return total; }, ), });