92 lines
No EOL
3.2 KiB
TypeScript
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()
|
|
}
|
|
}) |