| import { create } from 'zustand'; |
| import type { Room, GameState, CardInstance, Player, CardId } from '@shared/types'; |
|
|
| export type Screen = 'menu' | 'lobby' | 'room' | 'game'; |
|
|
| export type ModalType = |
| | 'peek-cards' |
| | 'target-select' |
| | 'card-type-select' |
| | 'blind-steal' |
| | 'arrange-hand' |
| | 'view-hand' |
| | 'flip-table' |
| | 'place-mummy' |
| | 'king-ra-prompt' |
| | 'duel-result' |
| | 'swap-result' |
| | 'game-over' |
| | null; |
|
|
| export interface Toast { |
| id: string; |
| message: string; |
| type: 'info' | 'warning' | 'success' | 'danger'; |
| } |
|
|
| export interface MummyEvent { |
| type: 'drawn' | 'defused' | 'eliminated'; |
| playerName: string; |
| } |
|
|
| export interface ModalData { |
| peekCards?: CardInstance[]; |
| targetPlayers?: Player[]; |
| blindStealCount?: number; |
| viewHandCards?: CardInstance[]; |
| viewHandTargetId?: string; |
| flipTableCards?: CardInstance[]; |
| deckSize?: number; |
| kingRaPlayerId?: string; |
| kingRaCardPlayed?: CardId; |
| kingRaTimeout?: number; |
| duelChallenger?: { playerId: string; card: CardInstance }; |
| duelOpponent?: { playerId: string; card: CardInstance }; |
| duelWinnerId?: string; |
| spellboundTargetId?: string; |
| swapGaveCard?: CardInstance; |
| swapReceivedCard?: CardInstance; |
| swapOtherPlayer?: string; |
| winnerId?: string; |
| winnerName?: string; |
| } |
|
|
| interface GameStore { |
| |
| playerId: string | null; |
| playerName: string; |
| isConnected: boolean; |
| |
| |
| currentScreen: Screen; |
| |
| |
| currentRoom: Room | null; |
| roomList: { id: string; name: string; playerCount: number; maxPlayers: number; isPlaying: boolean }[]; |
| |
| |
| gameState: GameState | null; |
| myHand: CardInstance[]; |
| selectedCards: string[]; |
| |
| |
| isMyTurn: boolean; |
| currentTurnPlayerId: string | null; |
| turnsRemaining: number; |
| reactionWindowActive: boolean; |
| lastActionResolvedAt: number; |
| |
| |
| activeModal: ModalType; |
| modalData: ModalData; |
| toasts: Toast[]; |
| mummyEvent: MummyEvent | null; |
| |
| |
| setPlayerId: (id: string | null) => void; |
| setPlayerName: (name: string) => void; |
| setConnected: (connected: boolean) => void; |
| setScreen: (screen: Screen) => void; |
| setRoom: (room: Room | null) => void; |
| setRoomList: (rooms: { id: string; name: string; playerCount: number; maxPlayers: number; isPlaying: boolean }[]) => void; |
| setGameState: (state: GameState | null) => void; |
| setMyHand: (hand: CardInstance[]) => void; |
| toggleCardSelection: (instanceId: string) => void; |
| clearSelection: () => void; |
| setTurnInfo: (playerId: string, turnsRemaining: number) => void; |
| setReactionWindowActive: (active: boolean) => void; |
| setLastActionResolvedAt: (timestamp: number) => void; |
| openModal: (type: ModalType, data?: ModalData) => void; |
| closeModal: () => void; |
| addToast: (message: string, type: Toast['type']) => void; |
| removeToast: (id: string) => void; |
| setMummyEvent: (event: MummyEvent | null) => void; |
| reset: () => void; |
| } |
|
|
| export const useGameStore = create<GameStore>((set, get) => ({ |
| |
| playerId: null, |
| playerName: '', |
| isConnected: false, |
| currentScreen: 'menu', |
| currentRoom: null, |
| roomList: [], |
| gameState: null, |
| myHand: [], |
| selectedCards: [], |
| isMyTurn: false, |
| currentTurnPlayerId: null, |
| turnsRemaining: 1, |
| reactionWindowActive: false, |
| lastActionResolvedAt: 0, |
| activeModal: null, |
| modalData: {}, |
| toasts: [], |
| mummyEvent: null, |
| |
| |
| setPlayerId: (id) => set({ playerId: id }), |
| |
| setPlayerName: (name) => set({ playerName: name }), |
| |
| setConnected: (connected) => set({ isConnected: connected }), |
| |
| setScreen: (screen) => set({ currentScreen: screen }), |
| |
| setRoom: (room) => set({ currentRoom: room }), |
| |
| setRoomList: (rooms) => set({ roomList: rooms }), |
| |
| setGameState: (state) => { |
| const { playerId } = get(); |
| const isMyTurn = state?.players[state.currentPlayerIndex]?.id === playerId; |
| set({ |
| gameState: state, |
| isMyTurn, |
| currentTurnPlayerId: state?.players[state.currentPlayerIndex]?.id ?? null, |
| turnsRemaining: state?.turnsRemaining ?? 1, |
| }); |
| }, |
| |
| setMyHand: (hand) => set({ myHand: hand }), |
| |
| toggleCardSelection: (instanceId) => { |
| const { selectedCards } = get(); |
| if (selectedCards.includes(instanceId)) { |
| set({ selectedCards: selectedCards.filter(id => id !== instanceId) }); |
| } else { |
| set({ selectedCards: [...selectedCards, instanceId] }); |
| } |
| }, |
| |
| clearSelection: () => set({ selectedCards: [] }), |
| |
| setTurnInfo: (playerId, turnsRemaining) => { |
| const { playerId: myId } = get(); |
| set({ |
| currentTurnPlayerId: playerId, |
| turnsRemaining, |
| isMyTurn: playerId === myId, |
| }); |
| }, |
| |
| setReactionWindowActive: (active) => set({ reactionWindowActive: active }), |
| |
| setLastActionResolvedAt: (timestamp) => set({ lastActionResolvedAt: timestamp }), |
| |
| openModal: (type, data = {}) => { |
| const state = get(); |
| |
| if (state.activeModal === type) return; |
| |
| |
| |
| const resultModals = ['duel-result', 'swap-result', 'peek-cards', 'view-hand', 'blind-steal']; |
| if (resultModals.includes(type as string)) { |
| set({ activeModal: type, modalData: data, reactionWindowActive: false }); |
| } else { |
| set({ activeModal: type, modalData: data }); |
| } |
| }, |
| |
| closeModal: () => set({ activeModal: null, modalData: {} }), |
| |
| addToast: (message, type) => { |
| const id = Date.now().toString(); |
| set((state) => ({ |
| toasts: [...state.toasts, { id, message, type }], |
| })); |
| |
| setTimeout(() => { |
| get().removeToast(id); |
| }, 4000); |
| }, |
| |
| removeToast: (id) => { |
| set((state) => ({ |
| toasts: state.toasts.filter(t => t.id !== id), |
| })); |
| }, |
| |
| setMummyEvent: (event) => { |
| set({ mummyEvent: event }); |
| |
| if (event) { |
| setTimeout(() => { |
| const current = get().mummyEvent; |
| |
| if (current?.type === event.type && current?.playerName === event.playerName) { |
| set({ mummyEvent: null }); |
| } |
| }, 3000); |
| } |
| }, |
| |
| reset: () => set({ |
| currentScreen: 'menu', |
| currentRoom: null, |
| gameState: null, |
| myHand: [], |
| selectedCards: [], |
| isMyTurn: false, |
| currentTurnPlayerId: null, |
| turnsRemaining: 1, |
| reactionWindowActive: false, |
| lastActionResolvedAt: 0, |
| activeModal: null, |
| modalData: {}, |
| mummyEvent: null, |
| }), |
| })); |
|
|