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 @@
-
@@ -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" }}.
-
+
between {{ naturallyJoinEnumeration(game.state.winnerIds.map(id => game.state.players.find(p => p.id === id).name)) }}
diff --git a/src/components/JoinScreen.vue b/src/components/JoinScreen.vue
new file mode 100644
index 0000000..9da50a0
--- /dev/null
+++ b/src/components/JoinScreen.vue
@@ -0,0 +1,97 @@
+