twenty-one/frontend/clientGame.ts

92 lines
No EOL
3.2 KiB
TypeScript

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<GameAction>
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<string | null>(null)
const state = ref<GameState>(getUninitializedGameState())
const actions = reactive<GameAction[]>([])
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<void>((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()
}
})