commit #7
This commit is contained in:
parent
2975b2820b
commit
26772d9d30
17 changed files with 216 additions and 45 deletions
|
@ -2,10 +2,11 @@ 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, getUninitializedGameState, produceNewState } from "./shared/game/state"
|
||||
import { GameState, getNumberCardsSum, getUninitializedGameState, produceNewState } from "./shared/game/state"
|
||||
import { trpcClient } from "./trpc"
|
||||
import { useAuth } from "./auth"
|
||||
import { read } from "fs"
|
||||
import type { SpecialCardId } from "./shared/game/cards"
|
||||
|
||||
const gameActionsBusKey = Symbol() as EventBusKey<GameAction>
|
||||
const useGameActionsBus = () => useEventBus(gameActionsBusKey)
|
||||
|
@ -63,6 +64,8 @@ export const useGame = defineStore("game", () => {
|
|||
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(() => getNumberCardsSum(state.value.players.find(p => p.id === auth.requiredUser.id)!.numberCards) > state.value.targetSum),
|
||||
state: readonly(state),
|
||||
actions: readonly(actions),
|
||||
join,
|
||||
|
@ -75,6 +78,7 @@ export const useGame = defineStore("game", () => {
|
|||
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()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="flex-shrink-0 rounded-lg shadow-xl bg-gradient-to-br w-35 h-45" :class="$style.root">
|
||||
<component :is="asButton ? 'button' : 'div'" class="block flex-shrink-0 rounded-lg shadow-xl bg-gradient-to-br" :class="$style.root">
|
||||
<div class="relative flex flex-col items-center justify-center h-full">
|
||||
<div class="absolute top-2 right-2 bg-dark-800 rounded-full flex gap-2 items-center px-2">
|
||||
<div v-for="tag in tags" :key="tag.label" :title="tag.label">
|
||||
|
@ -8,7 +8,7 @@
|
|||
</div>
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
|
@ -25,6 +25,10 @@
|
|||
background: url("../assets/noise.png") repeat;
|
||||
opacity: 10%;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@ -38,6 +42,7 @@
|
|||
icon: FunctionalComponent
|
||||
}>>,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
asButton: Boolean
|
||||
})
|
||||
</script>
|
|
@ -13,18 +13,18 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<div class="font-fat opacity-20 text-3xl pr-2">
|
||||
<template v-if="isBust">You are bust.</template>
|
||||
<template v-if="game.isBust">You are bust.</template>
|
||||
</div>
|
||||
<BigButton
|
||||
class="bg-gradient-to-br from-red-800 to-red-900 uppercase"
|
||||
:disabled="!isYourTurn || isBust"
|
||||
:disabled="!game.isYourTurn || game.isBust"
|
||||
@click="game.hit()"
|
||||
>
|
||||
Hit
|
||||
</BigButton>
|
||||
<BigButton
|
||||
class="bg-gradient-to-br from-blue-800 to-blue-900 uppercase"
|
||||
:disabled="!isYourTurn"
|
||||
:disabled="!game.isYourTurn"
|
||||
@click="game.stay()"
|
||||
>
|
||||
Stay
|
||||
|
@ -50,14 +50,12 @@
|
|||
|
||||
const game = useGame()
|
||||
const auth = useAuth()
|
||||
const isYourTurn = computed(() => game.state.activePlayerId === auth.requiredUser.id)
|
||||
const isBust = computed(() => getNumberCardsSum(game.state.players.find(p => p.id === auth.requiredUser.id)!.numberCards) > game.state.targetSum)
|
||||
|
||||
const reorderedPlayerIds = computed(() => {
|
||||
const ids = game.state.players.map(p => p.id)
|
||||
const selfIndex = ids.findIndex(id => id === auth.requiredUser.id)
|
||||
let before = ids.slice(0, selfIndex)
|
||||
let rest = ids.slice(selfIndex)
|
||||
return [...rest, ...before]
|
||||
let front = ids.slice(0, selfIndex + 1)
|
||||
let back = ids.slice(selfIndex + 1)
|
||||
return [...back, ...front]
|
||||
})
|
||||
</script>
|
|
@ -42,7 +42,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch, watchEffect } from "vue"
|
||||
import { useGame } from "../clientGame"
|
||||
import { LOBBY_CODE_LENGTH } from "../shared/lobbyCode"
|
||||
import { LOBBY_CODE_LENGTH } from "../shared/constants"
|
||||
import { useBrowserLocation } from "@vueuse/core"
|
||||
|
||||
const location = useBrowserLocation()
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, watchEffect } from "vue"
|
||||
import { useGame } from "../clientGame"
|
||||
import { LOBBY_CODE_LENGTH } from "../shared/lobbyCode"
|
||||
import { LOBBY_CODE_LENGTH } from "../shared/constants"
|
||||
import { useBrowserLocation } from "@vueuse/core"
|
||||
import { useAuth } from "../auth"
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<Card
|
||||
class="w-35 h-45"
|
||||
:class="isUnknown ? 'bg-gradient-to-br from-gray-700 to-gray-800' : 'bg-gradient-to-br from-yellow-600 to-yellow-900'"
|
||||
:tags="tags"
|
||||
>
|
||||
|
|
|
@ -19,6 +19,15 @@
|
|||
:is-own="isYou"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="isYou" class="p-4 border border-dark-200 rounded-xl flex gap-5 flex-wrap w-full">
|
||||
<template v-for="(count, id) in playerState.specialCardCountByType" :key="id">
|
||||
<SpecialCard
|
||||
v-if="count > 0"
|
||||
:id="id"
|
||||
:count="count"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="font-fat opacity-30 text-3xl">
|
||||
{{ getNumberCardsSum(playerState.numberCards) }}
|
||||
<template v-if="!isYou && game.state.phase !== 'end' && playerState.numberCards.some(c => c.isCovert)">+ ?</template>
|
||||
|
@ -38,6 +47,7 @@
|
|||
import { useAuth } from "../auth"
|
||||
import { getNumberCardsSum } from "../shared/game/state"
|
||||
import CrownIcon from "virtual:icons/ph/crown-simple-bold"
|
||||
import SpecialCard from "./SpecialCard.vue"
|
||||
|
||||
const props = defineProps<{
|
||||
playerId: string
|
||||
|
|
46
src/components/SpecialCard.vue
Normal file
46
src/components/SpecialCard.vue
Normal file
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<Card
|
||||
class="bg-gradient-to-br w-50 h-32"
|
||||
:class="meta_.type === 'permanent' ? 'from-green-800 to-green-900' : 'from-blue-700 to-blue-800'"
|
||||
as-button
|
||||
:tags="[{ label: `${count}x`, icon: numberCircleIcons[count] }]"
|
||||
:disabled="!game.isYourTurn"
|
||||
@click="use()"
|
||||
>
|
||||
<div class="flex justify-center items-center text-5xl opacity-90 flex-grow">
|
||||
<component :is="specialCardIcons[id]"/>
|
||||
</div>
|
||||
<div class="px-3 pb-2 text-sm flex items-end flex-grow">
|
||||
<div>
|
||||
{{ meta_.description }}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Card from "./Card.vue"
|
||||
import type { SpecialCardId } from "../shared/game/cards"
|
||||
import { numberCircleIcons, specialCardIcons } from "../icons"
|
||||
import { specialCardsMeta } from "../shared/game/cards"
|
||||
import { computed } from "vue"
|
||||
import { useGame } from "../clientGame"
|
||||
import { useThrottleFn } from "@vueuse/core"
|
||||
|
||||
const props = defineProps<{
|
||||
id: SpecialCardId
|
||||
count: number
|
||||
}>()
|
||||
|
||||
const game = useGame()
|
||||
|
||||
const meta_ = computed(() => specialCardsMeta[props.id])
|
||||
|
||||
const use = useThrottleFn(() => {
|
||||
game.useSpecialCard(props.id)
|
||||
}, 1000)
|
||||
</script>
|
30
src/icons.ts
Normal file
30
src/icons.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import type { SpecialCardId } from "./shared/game/cards"
|
||||
import type { Component } from "vue"
|
||||
import ArrowArcRightIcon from "virtual:icons/ph/arrow-arc-right-bold"
|
||||
import PlusCircleIcon from "virtual:icons/ph/plus-circle"
|
||||
import NumberCircleOneIcon from "virtual:icons/ph/number-circle-one"
|
||||
import NumberCircleTwoIcon from "virtual:icons/ph/number-circle-two"
|
||||
import NumberCircleThreeIcon from "virtual:icons/ph/number-circle-three"
|
||||
import NumberCircleFourIcon from "virtual:icons/ph/number-circle-four"
|
||||
import NumberCircleFiveIcon from "virtual:icons/ph/number-circle-five"
|
||||
import NumberCircleSixIcon from "virtual:icons/ph/number-circle-six"
|
||||
import NumberCircleSevenIcon from "virtual:icons/ph/number-circle-seven"
|
||||
import NumberCircleEightIcon from "virtual:icons/ph/number-circle-eight"
|
||||
import NumberCircleNineIcon from "virtual:icons/ph/number-circle-nine"
|
||||
|
||||
export const specialCardIcons: Record<SpecialCardId, Component> = {
|
||||
"return-last-opponent": ArrowArcRightIcon,
|
||||
"increase-target-by-2": PlusCircleIcon
|
||||
}
|
||||
|
||||
export const numberCircleIcons = {
|
||||
1: NumberCircleOneIcon,
|
||||
2: NumberCircleTwoIcon,
|
||||
3: NumberCircleThreeIcon,
|
||||
4: NumberCircleFourIcon,
|
||||
5: NumberCircleFiveIcon,
|
||||
6: NumberCircleSixIcon,
|
||||
7: NumberCircleSevenIcon,
|
||||
8: NumberCircleEightIcon,
|
||||
9: NumberCircleNineIcon
|
||||
}
|
|
@ -6,7 +6,8 @@ import type { RemoveKey } from "../shared/RemoveKey"
|
|||
import { random } from "lodash-es"
|
||||
import type { DeepReadonly } from "vue"
|
||||
import { customAlphabet as createNanoIdWithCustomAlphabet } from "nanoid/non-secure"
|
||||
import { LOBBY_CODE_LENGTH } from "../shared/lobbyCode"
|
||||
import { LOBBY_CODE_LENGTH, SPECIAL_CARD_PROBABILITY } from "../shared/constants"
|
||||
import { SpecialCardId, weightedSpecialCardIds } from "../shared/game/cards"
|
||||
|
||||
const generateLobbyCode = createNanoIdWithCustomAlphabet("ABCDEFGHKMNPQRSTUVWXYZ", LOBBY_CODE_LENGTH)
|
||||
|
||||
|
@ -35,6 +36,16 @@ function redactGameAction<T extends GameAction>(action: T, receiverId: string):
|
|||
number: 0
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case "deal-special":
|
||||
if (action.toPlayerId === receiverId) return action
|
||||
|
||||
return {
|
||||
...action,
|
||||
cardId: undefined
|
||||
}
|
||||
}
|
||||
|
||||
return NO_REDACTION
|
||||
|
@ -118,18 +129,23 @@ export class Game extends EventEmitter<Events> {
|
|||
|
||||
for (const player of players) {
|
||||
this.dealNumberTo(player.id, true)
|
||||
this.dealSpecialTo(player.id)
|
||||
}
|
||||
}
|
||||
|
||||
hit() {
|
||||
const playerId = this.state.activePlayerId
|
||||
|
||||
this.addAction({
|
||||
type: "hit",
|
||||
initiatingPlayerId: playerId
|
||||
})
|
||||
if (Math.random() > SPECIAL_CARD_PROBABILITY) {
|
||||
this.dealNumberTo(playerId, false)
|
||||
|
||||
this.dealNumberTo(playerId, false)
|
||||
this.addAction({
|
||||
type: "hit",
|
||||
initiatingPlayerId: playerId
|
||||
})
|
||||
}
|
||||
|
||||
else this.dealSpecialTo(playerId)
|
||||
}
|
||||
|
||||
stay() {
|
||||
|
@ -140,9 +156,18 @@ export class Game extends EventEmitter<Events> {
|
|||
initiatingPlayerId: playerId
|
||||
})
|
||||
|
||||
if (this.state.players.every(p => p.stayed)) {
|
||||
this.end()
|
||||
}
|
||||
if (this.state.players.every(p => p.stayed)) this.end()
|
||||
}
|
||||
|
||||
useSpecialCard(cardId: SpecialCardId) {
|
||||
const player = this.state.players.find(p => p.id === this.state.activePlayerId)!
|
||||
if (player.specialCardCountByType[cardId] <= 0) throw new Error("The player does not have this card")
|
||||
|
||||
this.addAction({
|
||||
type: "use-special",
|
||||
initiatingPlayerId: player.id,
|
||||
cardId
|
||||
})
|
||||
}
|
||||
|
||||
end() {
|
||||
|
@ -197,6 +222,14 @@ export class Game extends EventEmitter<Events> {
|
|||
})
|
||||
}
|
||||
|
||||
private dealSpecialTo(playerId: string) {
|
||||
this.addAction({
|
||||
type: "deal-special",
|
||||
toPlayerId: playerId,
|
||||
cardId: weightedSpecialCardIds[random(0, weightedSpecialCardIds.length - 1)]
|
||||
})
|
||||
}
|
||||
|
||||
private addAction<T extends RemoveKey<GameAction, "index">>(action: T): T & { index: number } {
|
||||
const fullAction = {
|
||||
...action,
|
||||
|
|
|
@ -2,6 +2,7 @@ import { requireAuthentication, t } from "./base"
|
|||
import { getNumberCardsSum } from "../../shared/game/state"
|
||||
import { z } from "zod"
|
||||
import { createGame, getGameByLobbyCode } from "../game"
|
||||
import { SpecialCardId, specialCardIds } from "../../shared/game/cards"
|
||||
|
||||
const gameProcedure = t.procedure
|
||||
.use(requireAuthentication)
|
||||
|
@ -41,6 +42,15 @@ export const gameRouter = t.router({
|
|||
await ctx.game.stay()
|
||||
}),
|
||||
|
||||
useSpecialCard: gameProcedure
|
||||
.input(z.object({
|
||||
specialCardId: z.string().refine(a => specialCardIds.includes(a as any)).transform(a => a as SpecialCardId)
|
||||
}))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
if (ctx.game.state.activePlayerId !== ctx.user.id) throw new Error("It is not the player’s turn")
|
||||
await ctx.game.useSpecialCard(input.specialCardId)
|
||||
}),
|
||||
|
||||
newRound: gameProcedure
|
||||
.mutation(async ({ ctx }) => {
|
||||
if (ctx.game.state.phase !== "end") throw new Error(`Cannot start a new round in this phase: ${ctx.game.state.phase}`)
|
||||
|
|
2
src/shared/constants.ts
Normal file
2
src/shared/constants.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export const LOBBY_CODE_LENGTH = 5
|
||||
export const SPECIAL_CARD_PROBABILITY = 0.2
|
|
@ -15,7 +15,7 @@ type PlayerAction = {
|
|||
}
|
||||
| {
|
||||
type: "use-special"
|
||||
cardType: SpecialCardId
|
||||
cardId: SpecialCardId
|
||||
}
|
||||
| {
|
||||
type: "leave"
|
||||
|
@ -38,7 +38,7 @@ type ServerAction = {
|
|||
| {
|
||||
type: "deal-special"
|
||||
toPlayerId: string
|
||||
cardType?: SpecialCardId // undefined if redacted
|
||||
cardId?: SpecialCardId // undefined if redacted
|
||||
}
|
||||
| {
|
||||
type: "end"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
interface SpecialCardMeta {
|
||||
type: "single-use" | "permanent"
|
||||
description: string
|
||||
weight: number
|
||||
}
|
||||
|
||||
// const specialCardTypesObject = {
|
||||
|
@ -15,13 +16,31 @@ interface SpecialCardMeta {
|
|||
// "get-11": true
|
||||
// }
|
||||
|
||||
export type SpecialCardId = "return-last-opponent"
|
||||
export type SpecialCardId = "return-last-opponent" | "increase-target-by-2"
|
||||
|
||||
export const specialCardsMeta: Record<SpecialCardId, SpecialCardMeta> = {
|
||||
"return-last-opponent": {
|
||||
type: "single-use",
|
||||
description: "Return the last card your opponent drew to the stack"
|
||||
description: "Return the last card your opponent drew to the stack.",
|
||||
weight: 5
|
||||
},
|
||||
"increase-target-by-2": {
|
||||
type: "permanent",
|
||||
description: "Increases the target card value by two.",
|
||||
weight: 1
|
||||
}
|
||||
}
|
||||
|
||||
export const specialCardTypes = Object.keys(specialCardsMeta)
|
||||
export const specialCardIds = Object.keys(specialCardsMeta) as SpecialCardId[]
|
||||
|
||||
const weightedSpecialCardIds: SpecialCardId[] = []
|
||||
|
||||
for (const id of specialCardIds) {
|
||||
const meta = specialCardsMeta[id]
|
||||
|
||||
for (let i = 0; i < meta.weight; i++) {
|
||||
weightedSpecialCardIds.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
export { weightedSpecialCardIds }
|
|
@ -1,7 +1,7 @@
|
|||
import type { SpecialCardId } from "./cards"
|
||||
import type { GameAction } from "./actions"
|
||||
import { produce } from "immer"
|
||||
import { specialCardTypes } from "./cards"
|
||||
import { specialCardIds, specialCardsMeta } from "./cards"
|
||||
import { cloneDeep, tail, without } from "lodash-es"
|
||||
|
||||
type SpecialCardCountByType = Record<SpecialCardId, number>
|
||||
|
@ -33,14 +33,18 @@ export interface GameState {
|
|||
winnerIds: string[] | null
|
||||
}
|
||||
|
||||
const ALL_ZERO_SPECIAL_CARD_COUNTS: SpecialCardCountByType = Object.fromEntries(specialCardTypes.map(t => [t, 0])) as SpecialCardCountByType
|
||||
const ALL_ZERO_SPECIAL_CARD_COUNTS: SpecialCardCountByType = Object.fromEntries(specialCardIds.map(t => [t, 0])) as SpecialCardCountByType
|
||||
|
||||
const ALL_ZERO_PERMANENT_SPECIAL_CARD_COUNTS: SpecialCardCountByType = Object.fromEntries(
|
||||
specialCardIds.filter(id => specialCardsMeta[id].type === "permanent").map(t => [t, 0])
|
||||
) as SpecialCardCountByType
|
||||
|
||||
const UNINITIALIZED_GAME_STATE: GameState = {
|
||||
phase: "pre-start",
|
||||
players: [],
|
||||
activePlayerId: "",
|
||||
targetSum: 0,
|
||||
activeSpecialCardCountByType: ALL_ZERO_SPECIAL_CARD_COUNTS,
|
||||
activeSpecialCardCountByType: ALL_ZERO_PERMANENT_SPECIAL_CARD_COUNTS,
|
||||
numberCardsStack: [],
|
||||
actualNumberCardsStackSize: 0,
|
||||
winnerIds: null
|
||||
|
@ -49,6 +53,13 @@ const UNINITIALIZED_GAME_STATE: GameState = {
|
|||
export const getUninitializedGameState = () => cloneDeep(UNINITIALIZED_GAME_STATE)
|
||||
export const getFullNumbersCardStack = () => tail([...Array(12).keys()])
|
||||
|
||||
const getPreviousPlayer = (state: GameState) => {
|
||||
const activePlayerIndex = state.players.findIndex(p => p.id === state.activePlayerId)
|
||||
let index = (activePlayerIndex - 1) % state.players.length
|
||||
if (index < 0) index = state.players.length + index
|
||||
return state.players[index]
|
||||
}
|
||||
|
||||
export const produceNewState = (oldState: GameState, action: GameAction) => produce(oldState, state => {
|
||||
const activateNextPlayer = () => {
|
||||
const activePlayerIndex = state.players.findIndex(p => p.id === state.activePlayerId)
|
||||
|
@ -72,7 +83,7 @@ export const produceNewState = (oldState: GameState, action: GameAction) => prod
|
|||
state.phase = "running"
|
||||
state.activePlayerId = state.players[0].id
|
||||
state.targetSum = action.targetSum
|
||||
state.activeSpecialCardCountByType = ALL_ZERO_SPECIAL_CARD_COUNTS
|
||||
state.activeSpecialCardCountByType = ALL_ZERO_PERMANENT_SPECIAL_CARD_COUNTS
|
||||
state.numberCardsStack = getFullNumbersCardStack()
|
||||
state.actualNumberCardsStackSize = state.numberCardsStack.length
|
||||
|
||||
|
@ -96,9 +107,9 @@ export const produceNewState = (oldState: GameState, action: GameAction) => prod
|
|||
break
|
||||
|
||||
case "deal-special":
|
||||
if (action.cardType !== undefined) {
|
||||
if (action.cardId !== undefined) {
|
||||
const p2 = state.players.find(p => p.id === action.toPlayerId)!
|
||||
p2.specialCardCountByType[action.cardType]++
|
||||
p2.specialCardCountByType[action.cardId]++
|
||||
}
|
||||
|
||||
break
|
||||
|
@ -117,7 +128,7 @@ export const produceNewState = (oldState: GameState, action: GameAction) => prod
|
|||
|
||||
case "use-special":
|
||||
const p3 = state.players.find(p => p.id === action.initiatingPlayerId)!
|
||||
applySpecialCardUsage(state, action.cardType, p3)
|
||||
applySpecialCardUsage(state, action.cardId, p3)
|
||||
break
|
||||
|
||||
case "end":
|
||||
|
@ -130,11 +141,21 @@ export const produceNewState = (oldState: GameState, action: GameAction) => prod
|
|||
})
|
||||
|
||||
function applySpecialCardUsage(state: GameState, type: SpecialCardId, player: GameStatePlayer) {
|
||||
player.specialCardCountByType[type]--
|
||||
player.specialCardCountByType[type] = Math.max(player.specialCardCountByType[type] - 1, 0)
|
||||
|
||||
switch (type) {
|
||||
case "return-last-opponent":
|
||||
// TODO
|
||||
const previousPlayer = getPreviousPlayer(state)
|
||||
const removedCard = previousPlayer.numberCards.pop()
|
||||
if (removedCard !== undefined) {
|
||||
state.numberCardsStack.push(removedCard.number)
|
||||
state.actualNumberCardsStackSize++
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case "increase-target-by-2":
|
||||
state.targetSum += 2
|
||||
break
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export const LOBBY_CODE_LENGTH = 5
|
|
@ -1,7 +0,0 @@
|
|||
import type { SpecialCardId } from "./shared/game/cards"
|
||||
import type { Component } from "vue"
|
||||
import ArrowArcRightIcon from "virtual:icons/*"
|
||||
|
||||
export const specialCardIcons: Record<SpecialCardId, Component> = {
|
||||
"return-last-opponent": ArrowArcRightIcon
|
||||
}
|
Loading…
Add table
Reference in a new issue