import { defineStore } from "pinia" import { EventBusKey, useEventBus } from "@vueuse/core" import type { GameAction } from "../shared/game/actions" import { computed, reactive, readonly, ref } from "vue" import { GameState, getNumberCardsSum, getUninitializedGameState, produceNewState } from "../shared/game/state" import { trpcClient } from "./trpc" import { useAuth } from "./auth" import type { SpecialCardId } from "../shared/game/cards" const gameActionsBusKey = Symbol() as EventBusKey const useGameActionsBus = () => useEventBus(gameActionsBusKey) export const useGameActionNotification = (listener: (action: GameAction) => unknown) => { const bus = useGameActionsBus() bus.on(listener) return { stop: () => bus.off(listener) } } export const useGame = defineStore("game", () => { const lobbyCode = ref(null) const state = ref(getUninitializedGameState()) const actions = reactive([]) const auth = useAuth() const actionsBus = useGameActionsBus() actionsBus.on(action => { actions.push(action) state.value = produceNewState(state.value, action) console.log(`⏩ ${action.type}`, action) }) function join(code: string) { 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 case "new_round": state.value = getUninitializedGameState() actions.splice(0, actions.length) join(code).catch(() => {}) } }, onError: error => { console.error("🔴", error) reject(error) } }) }) } const ownNumberCardsSum = computed(() => getNumberCardsSum(state.value.players.find(p => p.id === auth.requiredUser.id)!.numberCards)) const isForcedToHit = computed( () => state.value.activeSpecialCards.find(c => c.id === "force-hit" && c.ownerId !== auth.requiredUser.id) !== undefined ) return { lobbyCode: readonly(lobbyCode), isActive: computed(() => lobbyCode.value !== null), isOwnGame: computed(() => state.value.players.findIndex(p => p.id === (auth.authenticatedUser?.id ?? "")) === 0), isYourTurn: computed(() => state.value.activePlayerId === auth.requiredUser.id), isBust: computed(() => ownNumberCardsSum.value > state.value.targetSum), mayHit: computed(() => ownNumberCardsSum.value < state.value.targetSum || isForcedToHit.value), isForcedToHit, ownNumberCardsSum, state: readonly(state), actions: readonly(actions), join, async reset() { window.location.hash = "" lobbyCode.value = null state.value = getUninitializedGameState() actions.splice(0, actions.length) }, start: () => trpcClient.game.start.mutate({ lobbyCode: lobbyCode.value! }), hit: () => trpcClient.game.hit.mutate({ lobbyCode: lobbyCode.value! }), stay: () => trpcClient.game.stay.mutate({ lobbyCode: lobbyCode.value! }), useSpecialCard: (id: SpecialCardId) => trpcClient.game.useSpecialCard.mutate({ lobbyCode: lobbyCode.value!, specialCardId: id }), newRound: () => trpcClient.game.newRound.mutate({ lobbyCode: lobbyCode.value! }), create: () => trpcClient.createGame.mutate() } })