diff --git a/package.json b/package.json index 3161d45..12cc769 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "bufferutil": "^4.0.7", "cookie": "^0.5.0", "cookie-parser": "^1.4.6", + "date-fns": "^2.29.3", "eventemitter3": "^5.0.0", "express": "^4.18.2", "immer": "^10.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be836c5..537306a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,7 @@ importers: bufferutil: ^4.0.7 cookie: ^0.5.0 cookie-parser: ^1.4.6 + date-fns: ^2.29.3 eventemitter3: ^5.0.0 express: ^4.18.2 immer: ^10.0.1 @@ -56,6 +57,7 @@ importers: bufferutil: 4.0.7 cookie: 0.5.0 cookie-parser: 1.4.6 + date-fns: 2.29.3 eventemitter3: 5.0.0 express: 4.18.2 immer: 10.0.1 @@ -1320,6 +1322,11 @@ packages: /csstype/2.6.21: resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} + /date-fns/2.29.3: + resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} + engines: {node: '>=0.11'} + dev: false + /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: diff --git a/schema.prisma b/schema.prisma index 76d1e2b..06acf76 100644 --- a/schema.prisma +++ b/schema.prisma @@ -7,32 +7,9 @@ generator client { provider = "prisma-client-js" } -model Game { - id String @id @default(cuid()) - lobbyCodeIfActive String? @unique - - actions GameAction[] -} - model User { - id String @id @default(cuid()) - name String - token String @unique - - gameActions GameAction[] -} - -model GameAction { - id String @id @default(cuid()) - - index Int - gameId String - game Game @relation(references: [id], fields: [gameId], onDelete: Cascade) - - playerId String? // null → the server or a deleted user - player User? @relation(references: [id], fields: [playerId], onDelete: SetNull) - - data String - - @@unique([gameId, index]) + id String @id @default(cuid()) + name String + token String @unique + lastActivityDate DateTime @default(now()) } diff --git a/src/App.vue b/src/App.vue index 529a6d1..4a50731 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,13 +2,13 @@
-
- - +
+
+ Loading… +
+ + +
@@ -67,24 +67,20 @@ diff --git a/src/auth.ts b/src/auth.ts index da067c7..a5c147b 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,5 +1,6 @@ import { defineStore } from "pinia" import { computed, ref } from "vue" +import { trpcClient } from "./trpc" export const useAuth = defineStore("auth", () => { const authenticatedUser = ref<{ id: string; name: string } | null>(null) @@ -7,6 +8,16 @@ export const useAuth = defineStore("auth", () => { return { authenticatedUser, - requiredUser + requiredUser, + async fetchSelf() { + authenticatedUser.value = (await trpcClient.getSelf.query()).user + }, + async login(username: string) { + const { id } = await trpcClient.login.mutate({ name: username }) + authenticatedUser.value = { + id, + name: username + } + } } }) \ No newline at end of file diff --git a/src/clientGame.ts b/src/clientGame.ts index def0b49..6c903a5 100644 --- a/src/clientGame.ts +++ b/src/clientGame.ts @@ -1,9 +1,11 @@ import { defineStore } from "pinia" import { EventBusKey, useEventBus } from "@vueuse/core" -import type { GameAction } from "./shared/game/gameActions" -import { reactive, readonly, ref } from "vue" +import type { GameAction } from "./shared/game/actions" +import { computed, reactive, readonly, ref } from "vue" import { GameState, getUninitializedGameState, produceNewState } from "./shared/game/state" import { trpcClient } from "./trpc" +import { useAuth } from "./auth" +import { read } from "fs" const gameActionsBusKey = Symbol() as EventBusKey const useGameActionsBus = () => useEventBus(gameActionsBusKey) @@ -18,10 +20,12 @@ export const useGameActionNotification = (listener: (action: GameAction) => unkn } export const useGame = defineStore("game", () => { - const isActive = ref(false) + const lobbyCode = ref(null) const state = ref(getUninitializedGameState()) const actions = reactive([]) + const auth = useAuth() + const actionsBus = useGameActionsBus() actionsBus.on(action => { actions.push(action) @@ -30,18 +34,37 @@ export const useGame = defineStore("game", () => { }) return { - isActive: readonly(isActive), + lobbyCode: readonly(lobbyCode), + isActive: computed(() => lobbyCode.value !== null), + isOwnGame: computed(() => state.value.players.findIndex(p => p.id === (auth.authenticatedUser?.id ?? "")) === 0), state: readonly(state), actions: readonly(actions), join(code: string) { - trpcClient.join.subscribe({ code: "game" }, { - onData: actionsBus.emit + return new Promise((resolve, reject) => { + trpcClient.join.subscribe({ lobbyCode: code }, { + onStarted: () => { + lobbyCode.value = code + resolve() + }, + onData: event => { + switch (event.type) { + case "action": + actionsBus.emit(event.action) + break + } + }, + onError: error => { + console.error("🔴", error) + reject(error) + } + }) }) - - isActive.value = true }, - start: () => trpcClient.game.start.mutate(), - hit: () => trpcClient.game.hit.mutate(), - stay: () => trpcClient.game.stay.mutate() + async create() { + return await trpcClient.createGame.mutate() + }, + start: () => trpcClient.game.start.mutate({ lobbyCode: lobbyCode.value! }), + hit: () => trpcClient.game.hit.mutate({ lobbyCode: lobbyCode.value! }), + stay: () => trpcClient.game.stay.mutate({ lobbyCode: lobbyCode.value! }) } }) \ No newline at end of file diff --git a/src/components/GameEndModal.vue b/src/components/GameEndModal.vue index 5e13bea..9714a33 100644 --- a/src/components/GameEndModal.vue +++ b/src/components/GameEndModal.vue @@ -14,7 +14,7 @@ {{ getNumberCardsSum(singleWinner.numberCards) }} point{{ getNumberCardsSum(singleWinner.numberCards) === 1 ? "" : "s" }}. -