| | import { Server, Socket } from 'socket.io'; |
| | import { RoomManager } from '../rooms/RoomManager.js'; |
| | import type { |
| | ServerToClientEvents, |
| | ClientToServerEvents, |
| | CardId, |
| | CardInstance, |
| | PendingAction, |
| | } from '../../../shared/types.js'; |
| | import { CARD_DATABASE } from '../../../shared/types.js'; |
| |
|
| | type GameSocket = Socket<ClientToServerEvents, ServerToClientEvents>; |
| | type GameServer = Server<ClientToServerEvents, ServerToClientEvents>; |
| |
|
| | const KING_RA_TIMEOUT = 5000; |
| |
|
| | export function setupSocketHandlers( |
| | io: GameServer, |
| | socket: GameSocket, |
| | roomManager: RoomManager |
| | ): void { |
| | |
| | |
| | socket.on('getRooms', () => { |
| | socket.emit('roomList', roomManager.getRoomList()); |
| | }); |
| |
|
| | socket.on('createRoom', (playerName: string, roomName: string) => { |
| | const room = roomManager.createRoom(socket.id, playerName, roomName); |
| | socket.join(room.id); |
| | socket.emit('roomJoined', { |
| | id: room.id, |
| | name: room.name, |
| | hostId: room.hostId, |
| | players: room.players, |
| | maxPlayers: room.maxPlayers, |
| | gameState: room.gameState, |
| | }); |
| | io.emit('roomList', roomManager.getRoomList()); |
| | }); |
| |
|
| | socket.on('joinRoom', (playerName: string, roomId: string) => { |
| | const room = roomManager.joinRoom(socket.id, playerName, roomId); |
| | if (!room) { |
| | socket.emit('error', 'Could not join room. Room may be full or in progress.'); |
| | return; |
| | } |
| | |
| | socket.join(room.id); |
| | socket.emit('roomJoined', { |
| | id: room.id, |
| | name: room.name, |
| | hostId: room.hostId, |
| | players: room.players, |
| | maxPlayers: room.maxPlayers, |
| | gameState: room.gameState, |
| | }); |
| | |
| | io.to(room.id).emit('roomUpdated', { |
| | id: room.id, |
| | name: room.name, |
| | hostId: room.hostId, |
| | players: room.players, |
| | maxPlayers: room.maxPlayers, |
| | gameState: room.gameState, |
| | }); |
| | |
| | io.emit('roomList', roomManager.getRoomList()); |
| | }); |
| |
|
| | socket.on('leaveRoom', () => { |
| | const { room, wasHost } = roomManager.leaveRoom(socket.id); |
| | socket.emit('roomLeft'); |
| | |
| | if (room) { |
| | io.to(room.id).emit('roomUpdated', { |
| | id: room.id, |
| | name: room.name, |
| | hostId: room.hostId, |
| | players: room.players, |
| | maxPlayers: room.maxPlayers, |
| | gameState: room.gameState, |
| | }); |
| | } |
| | |
| | io.emit('roomList', roomManager.getRoomList()); |
| | }); |
| |
|
| | socket.on('setReady', (ready: boolean) => { |
| | const room = roomManager.setPlayerReady(socket.id, ready); |
| | if (room) { |
| | io.to(room.id).emit('roomUpdated', { |
| | id: room.id, |
| | name: room.name, |
| | hostId: room.hostId, |
| | players: room.players, |
| | maxPlayers: room.maxPlayers, |
| | gameState: room.gameState, |
| | }); |
| | } |
| | }); |
| |
|
| | socket.on('startGame', () => { |
| | const playerRoom = roomManager.getPlayerRoom(socket.id); |
| | if (!playerRoom || playerRoom.hostId !== socket.id) { |
| | socket.emit('error', 'Only the host can start the game.'); |
| | return; |
| | } |
| | |
| | if (playerRoom.players.length < 2) { |
| | socket.emit('error', 'Need at least 2 players to start.'); |
| | return; |
| | } |
| | |
| | const room = roomManager.startGame(playerRoom.id); |
| | if (!room || !room.game || !room.gameState) { |
| | socket.emit('error', 'Could not start game.'); |
| | return; |
| | } |
| | |
| | |
| | for (const player of room.players) { |
| | const hand = room.game.getPlayerHand(player.id); |
| | io.to(player.id).emit('gameStarted', room.gameState, hand); |
| | } |
| | |
| | |
| | const currentPlayer = room.game.getCurrentPlayer(); |
| | if (currentPlayer) { |
| | io.to(room.id).emit('turnStarted', currentPlayer.id, room.gameState.turnsRemaining); |
| | } |
| | |
| | io.emit('roomList', roomManager.getRoomList()); |
| | }); |
| |
|
| | |
| |
|
| | socket.on('playCard', async (cardInstanceId: string, targetPlayerId?: string, additionalData?: any) => { |
| | const room = roomManager.getPlayerRoom(socket.id); |
| | if (!room?.game) return; |
| | |
| | const game = room.game; |
| | |
| | |
| | if (!game.isPlayerTurn(socket.id)) { |
| | socket.emit('error', "It's not your turn!"); |
| | return; |
| | } |
| | |
| | |
| | const card = game.findCardInstance(socket.id, cardInstanceId); |
| | if (!card) { |
| | socket.emit('error', 'Card not found in your hand.'); |
| | return; |
| | } |
| | |
| | const cardDef = CARD_DATABASE[card.cardId]; |
| | |
| | |
| | if (cardDef.isHalf) { |
| | const count = game.countCards(socket.id, card.cardId); |
| | if (count < 2) { |
| | socket.emit('error', 'Half cards require 2 copies to play!'); |
| | return; |
| | } |
| | } |
| | |
| | |
| | if (card.cardId === 'king_ra_says_no') { |
| | socket.emit('error', 'King Ra Says NO can only be played reactively!'); |
| | return; |
| | } |
| | |
| | |
| | if (card.cardId === 'take_a_lap') { |
| | socket.emit('error', 'Take a Lap is used automatically when drawing a mummy!'); |
| | return; |
| | } |
| | |
| | |
| | game.removeCardFromHand(socket.id, cardInstanceId); |
| | game.discardCard(card); |
| | |
| | |
| | let secondCard: CardInstance | null = null; |
| | if (cardDef.isHalf) { |
| | const hand = game.getPlayerHand(socket.id); |
| | const secondInstance = hand.find(c => c.cardId === card.cardId); |
| | if (secondInstance) { |
| | secondCard = game.removeCardFromHand(socket.id, secondInstance.instanceId); |
| | if (secondCard) game.discardCard(secondCard); |
| | } |
| | } |
| | |
| | |
| | socket.emit('handUpdated', game.getPlayerHand(socket.id)); |
| | |
| | |
| | io.to(room.id).emit('gameStateUpdated', game.getGameState()); |
| | |
| | |
| | io.to(room.id).emit('cardPlayed', socket.id, card.cardId, targetPlayerId); |
| | |
| | |
| | const nonCancellableCards = [ |
| | 'shuffle_it', |
| | 'sharp_eye', |
| | 'wait_a_sec', |
| | 'flip_the_table' |
| | ]; |
| | |
| | |
| | if (!nonCancellableCards.includes(card.cardId)) { |
| | |
| | const otherPlayers = game.getOtherAlivePlayers(socket.id); |
| | |
| | if (otherPlayers.length > 0) { |
| | |
| | game.pendingAction = { |
| | action: { type: 'PLAY_CARD', playerId: socket.id, cardInstanceId, targetId: targetPlayerId, additionalData }, |
| | cardPlayed: card.cardId, |
| | playerId: socket.id, |
| | targetId: targetPlayerId, |
| | kingRaResponses: new Map(), |
| | kingRaCount: 0, |
| | timeoutAt: Date.now() + KING_RA_TIMEOUT, |
| | }; |
| | |
| | |
| | socket.emit('actionPending', card.cardId, KING_RA_TIMEOUT); |
| | |
| | |
| | for (const player of otherPlayers) { |
| | io.to(player.id).emit('kingRaPrompt', socket.id, card.cardId, KING_RA_TIMEOUT); |
| | } |
| | |
| | |
| | setTimeout(() => { |
| | if (game.pendingAction && game.pendingAction.cardPlayed === card.cardId) { |
| | resolvePendingAction(io, room.id, game, roomManager); |
| | } |
| | }, KING_RA_TIMEOUT); |
| | |
| | return; |
| | } |
| | } |
| | |
| | |
| | await executeCardEffect(io, socket, room.id, game, card.cardId, targetPlayerId, additionalData, roomManager); |
| | }); |
| |
|
| | socket.on('kingRaResponse', (useKingRa: boolean) => { |
| | console.log('kingRaResponse received:', { socketId: socket.id, useKingRa }); |
| | |
| | const room = roomManager.getPlayerRoom(socket.id); |
| | if (!room?.game?.pendingAction) { |
| | console.log('No pending action found'); |
| | return; |
| | } |
| | |
| | const game = room.game; |
| | const pending = game.pendingAction; |
| | |
| | |
| | if (!pending) { |
| | console.log('Pending is null'); |
| | return; |
| | } |
| | |
| | console.log('Pending action:', { cardPlayed: pending.cardPlayed, kingRaCount: pending.kingRaCount }); |
| | |
| | |
| | pending.kingRaResponses.set(socket.id, true); |
| | |
| | const hasKingRaCard = game.hasCard(socket.id, 'king_ra_says_no'); |
| | console.log('Player has King Ra card:', hasKingRaCard); |
| | |
| | if (useKingRa && hasKingRaCard) { |
| | console.log('Using King Ra card!'); |
| | |
| | const hand = game.getPlayerHand(socket.id); |
| | const kingRaCard = hand.find(c => c.cardId === 'king_ra_says_no'); |
| | if (kingRaCard) { |
| | console.log('Found King Ra card, removing it'); |
| | game.removeCardFromHand(socket.id, kingRaCard.instanceId); |
| | game.discardCard(kingRaCard); |
| | pending.kingRaCount++; |
| | |
| | console.log('Emitting kingRaResponse'); |
| | io.to(room.id).emit('kingRaResponse', socket.id, true); |
| | io.to(socket.id).emit('handUpdated', game.getPlayerHand(socket.id)); |
| | |
| | |
| | const otherPlayersWithKingRa = game.getPlayersWithKingRa(socket.id) |
| | .filter((id: string) => !pending.kingRaResponses.has(id)); |
| | |
| | if (otherPlayersWithKingRa.length > 0) { |
| | |
| | pending.timeoutAt = Date.now() + KING_RA_TIMEOUT; |
| | pending.kingRaResponses.clear(); |
| | |
| | for (const playerId of otherPlayersWithKingRa) { |
| | io.to(playerId).emit('kingRaPrompt', socket.id, 'king_ra_says_no', KING_RA_TIMEOUT); |
| | } |
| | |
| | setTimeout(() => { |
| | if (game.pendingAction === pending) { |
| | resolvePendingAction(io, room.id, game, roomManager); |
| | } |
| | }, KING_RA_TIMEOUT); |
| | |
| | return; |
| | } else { |
| | |
| | resolvePendingAction(io, room.id, game, roomManager); |
| | } |
| | } |
| | } |
| | |
| | |
| | }); |
| |
|
| | socket.on('drawCard', async () => { |
| | const room = roomManager.getPlayerRoom(socket.id); |
| | if (!room?.game) return; |
| | |
| | const game = room.game; |
| | |
| | if (!game.isPlayerTurn(socket.id)) { |
| | socket.emit('error', "It's not your turn!"); |
| | return; |
| | } |
| | |
| | |
| | if (game.pendingAction?.waitingForArrange) { |
| | socket.emit('error', 'Please wait for card arrangement to complete!'); |
| | return; |
| | } |
| | |
| | await handleDrawCard(io, socket, room.id, game, roomManager); |
| | }); |
| |
|
| | socket.on('placeMummy', (position: number) => { |
| | const room = roomManager.getPlayerRoom(socket.id); |
| | if (!room?.game) return; |
| | |
| | const game = room.game; |
| | |
| | |
| | const mummyCard: CardInstance = { |
| | instanceId: `mummy_${Date.now()}`, |
| | cardId: 'mummified', |
| | }; |
| | |
| | game.insertCardInDeck(mummyCard, position); |
| | |
| | |
| | game.endTurn(); |
| | roomManager.updateGameState(room.id); |
| | |
| | io.to(room.id).emit('gameStateUpdated', game.getGameState()); |
| | |
| | const currentPlayer = game.getCurrentPlayer(); |
| | if (currentPlayer) { |
| | io.to(room.id).emit('turnStarted', currentPlayer.id, game.getGameState().turnsRemaining); |
| | } |
| | }); |
| |
|
| | socket.on('spellboundRequest', (targetId: string, requestedCardId: CardId) => { |
| | const room = roomManager.getPlayerRoom(socket.id); |
| | if (!room?.game) return; |
| | |
| | const game = room.game; |
| | |
| | |
| | if (game.hasCard(targetId, requestedCardId)) { |
| | |
| | const targetHand = game.getPlayerHand(targetId); |
| | const cardToTake = targetHand.find(c => c.cardId === requestedCardId); |
| | |
| | if (cardToTake) { |
| | game.removeCardFromHand(targetId, cardToTake.instanceId); |
| | game.addCardToHand(socket.id, cardToTake); |
| | |
| | socket.emit('spellboundResult', true, requestedCardId); |
| | io.to(socket.id).emit('handUpdated', game.getPlayerHand(socket.id)); |
| | io.to(targetId).emit('handUpdated', game.getPlayerHand(targetId)); |
| | } |
| | } else { |
| | |
| | socket.emit('spellboundResult', false); |
| | |
| | handleDrawCard(io, socket, room.id, game, roomManager, true); |
| | } |
| | |
| | roomManager.updateGameState(room.id); |
| | io.to(room.id).emit('gameStateUpdated', game.getGameState()); |
| | }); |
| |
|
| | socket.on('blindStealSelect', (position: number) => { |
| | const room = roomManager.getPlayerRoom(socket.id); |
| | if (!room?.game) return; |
| | |
| | const game = room.game; |
| | const pending = game.pendingAction; |
| | |
| | if (!pending || pending.action.type !== 'PLAY_CARD') return; |
| | |
| | const targetId = pending.targetId; |
| | if (!targetId) return; |
| | |
| | const stolenCard = game.getRandomCard(targetId, position); |
| | if (stolenCard) { |
| | game.removeCardFromHand(targetId, stolenCard.instanceId); |
| | game.addCardToHand(socket.id, stolenCard); |
| | |
| | io.to(socket.id).emit('handUpdated', game.getPlayerHand(socket.id)); |
| | io.to(targetId).emit('handUpdated', game.getPlayerHand(targetId)); |
| | } |
| | |
| | game.pendingAction = null; |
| | roomManager.updateGameState(room.id); |
| | io.to(room.id).emit('gameStateUpdated', game.getGameState()); |
| | }); |
| |
|
| | socket.on('arrangeHand', (newOrder: string[]) => { |
| | const room = roomManager.getPlayerRoom(socket.id); |
| | if (!room?.game) return; |
| | |
| | const game = room.game; |
| | game.rearrangeHand(socket.id, newOrder); |
| | io.to(socket.id).emit('handUpdated', game.getPlayerHand(socket.id)); |
| | |
| | |
| | if (game.pendingAction?.waitingForArrange && game.pendingAction?.cardPlayed === 'criminal_mummy') { |
| | game.pendingAction.waitingForArrange = false; |
| | const thiefId = game.pendingAction.playerId; |
| | const currentHand = game.getPlayerHand(socket.id); |
| | io.to(thiefId).emit('blindStealStart', thiefId, socket.id, currentHand.length); |
| | } |
| | }); |
| |
|
| | socket.on('burnCard', (cardInstanceId: string) => { |
| | const room = roomManager.getPlayerRoom(socket.id); |
| | if (!room?.game) return; |
| | |
| | const game = room.game; |
| | const pending = game.pendingAction; |
| | |
| | if (!pending || pending.action.type !== 'PLAY_CARD') return; |
| | |
| | const targetId = pending.targetId; |
| | if (!targetId) return; |
| | |
| | const card = game.removeCardFromHand(targetId, cardInstanceId); |
| | if (card) { |
| | game.discardCard(card); |
| | |
| | io.to(targetId).emit('handUpdated', game.getPlayerHand(targetId)); |
| | } |
| | |
| | game.pendingAction = null; |
| | roomManager.updateGameState(room.id); |
| | io.to(room.id).emit('gameStateUpdated', game.getGameState()); |
| | }); |
| |
|
| | socket.on('rearrangeTopCards', (newOrder: string[]) => { |
| | const room = roomManager.getPlayerRoom(socket.id); |
| | if (!room?.game) return; |
| | |
| | room.game.rearrangeTopCards(newOrder); |
| | room.game.pendingAction = null; |
| | |
| | roomManager.updateGameState(room.id); |
| | io.to(room.id).emit('gameStateUpdated', room.game.getGameState()); |
| | }); |
| | } |
| |
|
| | |
| |
|
| | async function handleDrawCard( |
| | io: GameServer, |
| | socket: GameSocket, |
| | roomId: string, |
| | game: any, |
| | roomManager: RoomManager, |
| | isPenalty: boolean = false |
| | ): Promise<void> { |
| | const card = game.drawTopCard(); |
| | if (!card) { |
| | socket.emit('error', 'Deck is empty!'); |
| | return; |
| | } |
| | |
| | if (card.cardId === 'mummified') { |
| | |
| | io.to(roomId).emit('mummyDrawn', socket.id); |
| | |
| | if (game.canDefuse(socket.id)) { |
| | |
| | game.useDefuse(socket.id); |
| | io.to(roomId).emit('mummyDefused', socket.id); |
| | |
| | socket.emit('handUpdated', game.getPlayerHand(socket.id)); |
| | |
| | |
| | const mummyCard: CardInstance = { |
| | instanceId: `mummy_${Date.now()}`, |
| | cardId: 'mummified', |
| | }; |
| | const randomPosition = Math.floor(Math.random() * (game.getDeckSize() + 1)); |
| | game.insertCardInDeck(mummyCard, randomPosition); |
| | |
| | |
| | game.endTurn(); |
| | roomManager.updateGameState(roomId); |
| | io.to(roomId).emit('gameStateUpdated', game.getGameState()); |
| | |
| | const currentPlayer = game.getCurrentPlayer(); |
| | if (currentPlayer) { |
| | io.to(roomId).emit('turnStarted', currentPlayer.id, game.getGameState().turnsRemaining); |
| | } |
| | } else { |
| | |
| | game.eliminatePlayer(socket.id); |
| | |
| | io.to(roomId).emit('playerEliminated', socket.id); |
| | |
| | roomManager.updateGameState(roomId); |
| | const gameState = game.getGameState(); |
| | io.to(roomId).emit('gameStateUpdated', gameState); |
| | |
| | if (gameState.phase === 'game_over' && gameState.winnerId) { |
| | const winner = game.getPlayer(gameState.winnerId); |
| | io.to(roomId).emit('gameOver', gameState.winnerId, winner?.name ?? 'Unknown'); |
| | } else { |
| | const currentPlayer = game.getCurrentPlayer(); |
| | if (currentPlayer) { |
| | io.to(roomId).emit('turnStarted', currentPlayer.id, gameState.turnsRemaining); |
| | } |
| | } |
| | } |
| | } else { |
| | |
| | game.addCardToHand(socket.id, card); |
| | socket.emit('handUpdated', game.getPlayerHand(socket.id)); |
| | |
| | if (!isPenalty) { |
| | |
| | game.endTurn(); |
| | } |
| | |
| | roomManager.updateGameState(roomId); |
| | io.to(roomId).emit('gameStateUpdated', game.getGameState()); |
| | |
| | if (!isPenalty) { |
| | const currentPlayer = game.getCurrentPlayer(); |
| | if (currentPlayer) { |
| | io.to(roomId).emit('turnStarted', currentPlayer.id, game.getGameState().turnsRemaining); |
| | } |
| | } |
| | } |
| | } |
| |
|
| | function resolvePendingAction( |
| | io: GameServer, |
| | roomId: string, |
| | game: any, |
| | roomManager: RoomManager |
| | ): void { |
| | const pending = game.pendingAction; |
| | if (!pending) { |
| | console.log('resolvePendingAction: No pending action'); |
| | return; |
| | } |
| | |
| | const cancelled = pending.kingRaCount % 2 === 1; |
| | const cardId = pending.cardPlayed as keyof typeof CARD_DATABASE; |
| | |
| | console.log('resolvePendingAction:', { cancelled, kingRaCount: pending.kingRaCount, cardId }); |
| | |
| | if (cancelled) { |
| | console.log('ACTION CANCELLED - King Ra count odd'); |
| | io.to(roomId).emit('actionCancelled', pending.cardPlayed, pending.kingRaCount); |
| | game.pendingAction = null; |
| | } else { |
| | |
| | io.to(roomId).emit('actionResolved', pending.cardPlayed); |
| | |
| | |
| | game.pendingAction = null; |
| | |
| | |
| | const playerSocket = io.sockets.sockets.get(pending.playerId); |
| | if (playerSocket) { |
| | executeCardEffect( |
| | io, |
| | playerSocket as GameSocket, |
| | roomId, |
| | game, |
| | pending.cardPlayed, |
| | pending.targetId, |
| | pending.action.type === 'PLAY_CARD' ? pending.action.additionalData : undefined, |
| | roomManager |
| | ); |
| | } |
| | } |
| | } |
| |
|
| | async function executeCardEffect( |
| | io: GameServer, |
| | socket: GameSocket, |
| | roomId: string, |
| | game: any, |
| | cardId: CardId, |
| | targetId?: string, |
| | additionalData?: any, |
| | roomManager?: RoomManager |
| | ): Promise<void> { |
| | const playerId = socket.id; |
| | |
| | switch (cardId) { |
| | case 'sharp_eye': { |
| | |
| | const topCards = game.peekTopCards(3); |
| | socket.emit('peekCards', topCards); |
| | break; |
| | } |
| | |
| | case 'wait_a_sec': { |
| | |
| | game.endTurn(); |
| | roomManager?.updateGameState(roomId); |
| | io.to(roomId).emit('gameStateUpdated', game.getGameState()); |
| | |
| | const currentPlayer = game.getCurrentPlayer(); |
| | if (currentPlayer) { |
| | io.to(roomId).emit('turnStarted', currentPlayer.id, game.getGameState().turnsRemaining); |
| | } |
| | break; |
| | } |
| | |
| | case 'me_or_you': { |
| | |
| | if (!targetId) { |
| | socket.emit('error', 'Must select an opponent for duel!'); |
| | return; |
| | } |
| | |
| | const card1 = game.drawTopCard(); |
| | const card2 = game.drawTopCard(); |
| | |
| | if (!card1 || !card2) { |
| | socket.emit('error', 'Not enough cards in deck for duel!'); |
| | return; |
| | } |
| | |
| | const value1 = CARD_DATABASE[card1.cardId as keyof typeof CARD_DATABASE].value; |
| | const value2 = CARD_DATABASE[card2.cardId as keyof typeof CARD_DATABASE].value; |
| | |
| | let winnerId: string; |
| | |
| | if (value1 > value2) { |
| | winnerId = playerId; |
| | game.addCardToHand(playerId, card1); |
| | game.addCardToHand(playerId, card2); |
| | } else if (value2 > value1) { |
| | winnerId = targetId; |
| | game.addCardToHand(targetId, card1); |
| | game.addCardToHand(targetId, card2); |
| | } else { |
| | |
| | game.insertCardInDeck(card1, Math.floor(Math.random() * game.getDeckSize())); |
| | game.insertCardInDeck(card2, Math.floor(Math.random() * game.getDeckSize())); |
| | game.shuffleDeck(); |
| | break; |
| | } |
| | |
| | |
| | io.to(playerId).emit('duelResult', |
| | { playerId, card: card1 }, |
| | { playerId: targetId, card: card2 }, |
| | winnerId |
| | ); |
| | io.to(targetId).emit('duelResult', |
| | { playerId, card: card1 }, |
| | { playerId: targetId, card: card2 }, |
| | winnerId |
| | ); |
| | |
| | |
| | const wonCards = winnerId === playerId ? [card1, card2] : [card1, card2]; |
| | for (const wonCard of wonCards) { |
| | if (wonCard.cardId === 'mummified') { |
| | io.to(roomId).emit('mummyDrawn', winnerId); |
| | |
| | if (game.canDefuse(winnerId)) { |
| | game.useDefuse(winnerId); |
| | io.to(roomId).emit('mummyDefused', winnerId); |
| | game.removeCardFromHand(winnerId, wonCard.instanceId); |
| | |
| | |
| | const mummyCard: CardInstance = { |
| | instanceId: `mummy_${Date.now()}`, |
| | cardId: 'mummified', |
| | }; |
| | const randomPosition = Math.floor(Math.random() * (game.getDeckSize() + 1)); |
| | game.insertCardInDeck(mummyCard, randomPosition); |
| | } else { |
| | game.eliminatePlayer(winnerId); |
| | io.to(roomId).emit('playerEliminated', winnerId); |
| | } |
| | } |
| | } |
| | |
| | io.to(playerId).emit('handUpdated', game.getPlayerHand(playerId)); |
| | io.to(targetId).emit('handUpdated', game.getPlayerHand(targetId)); |
| | break; |
| | } |
| | |
| | case 'spellbound': { |
| | |
| | |
| | if (!targetId) { |
| | socket.emit('error', 'Must select a target player!'); |
| | return; |
| | } |
| | |
| | break; |
| | } |
| | |
| | case 'criminal_mummy': { |
| | |
| | if (!targetId) { |
| | socket.emit('error', 'Must select a target player!'); |
| | return; |
| | } |
| | |
| | const targetHand = game.getPlayerHand(targetId); |
| | if (targetHand.length === 0) { |
| | socket.emit('error', 'Target has no cards!'); |
| | return; |
| | } |
| | |
| | |
| | game.pendingAction = { |
| | action: { type: 'PLAY_CARD', playerId, cardInstanceId: '', targetId }, |
| | cardPlayed: 'criminal_mummy', |
| | playerId, |
| | targetId, |
| | kingRaResponses: new Map(), |
| | kingRaCount: 0, |
| | timeoutAt: Date.now() + 15000, |
| | waitingForArrange: true, |
| | }; |
| | |
| | |
| | io.to(targetId).emit('arrangeHandPrompt', playerId); |
| | |
| | |
| | setTimeout(() => { |
| | if (game.pendingAction?.waitingForArrange && game.pendingAction?.cardPlayed === 'criminal_mummy') { |
| | |
| | game.pendingAction.waitingForArrange = false; |
| | const currentHand = game.getPlayerHand(targetId); |
| | io.to(playerId).emit('blindStealStart', playerId, targetId, currentHand.length); |
| | } |
| | }, 15000); |
| | |
| | break; |
| | } |
| | |
| | case 'give_and_take': { |
| | |
| | if (!targetId) { |
| | socket.emit('error', 'Must select a target player!'); |
| | return; |
| | } |
| | |
| | const lowestCard = game.getLowestValueCard(playerId); |
| | const highestCard = game.getHighestValueCard(targetId); |
| | |
| | if (!lowestCard || !highestCard) { |
| | socket.emit('error', 'Not enough cards to swap!'); |
| | return; |
| | } |
| | |
| | |
| | game.removeCardFromHand(playerId, lowestCard.instanceId); |
| | game.removeCardFromHand(targetId, highestCard.instanceId); |
| | game.addCardToHand(playerId, highestCard); |
| | game.addCardToHand(targetId, lowestCard); |
| | |
| | const playerName = game.getPlayer(playerId)?.name ?? 'A player'; |
| | const targetName = game.getPlayer(targetId)?.name ?? 'someone'; |
| | |
| | |
| | io.to(playerId).emit('swapResult', lowestCard, highestCard, targetName); |
| | io.to(targetId).emit('swapResult', highestCard, lowestCard, playerName); |
| | |
| | io.to(playerId).emit('handUpdated', game.getPlayerHand(playerId)); |
| | io.to(targetId).emit('handUpdated', game.getPlayerHand(targetId)); |
| | break; |
| | } |
| | |
| | case 'shuffle_it': { |
| | |
| | game.shuffleDeck(); |
| | break; |
| | } |
| | |
| | case 'safe_travels': { |
| | |
| | game.setNextPlayerTurns(2); |
| | roomManager?.updateGameState(roomId); |
| | |
| | const currentPlayer = game.getCurrentPlayer(); |
| | if (currentPlayer) { |
| | io.to(roomId).emit('turnStarted', currentPlayer.id, 2); |
| | } |
| | |
| | io.to(roomId).emit('gameStateUpdated', game.getGameState()); |
| | break; |
| | } |
| | |
| | case 'all_or_nothing': { |
| | |
| | if (!targetId) { |
| | socket.emit('error', 'Must select a target player!'); |
| | return; |
| | } |
| | |
| | const targetHand = game.getPlayerHand(targetId); |
| | |
| | |
| | game.pendingAction = { |
| | action: { type: 'PLAY_CARD', playerId, cardInstanceId: '', targetId }, |
| | cardPlayed: 'all_or_nothing', |
| | playerId, |
| | targetId, |
| | kingRaResponses: new Map(), |
| | kingRaCount: 0, |
| | timeoutAt: Date.now() + 30000, |
| | }; |
| | |
| | |
| | socket.emit('viewHand', targetId, targetHand); |
| | break; |
| | } |
| | |
| | case 'flip_the_table': { |
| | |
| | const topCards = game.peekTopCards(5); |
| | |
| | |
| | game.pendingAction = { |
| | action: { type: 'PLAY_CARD', playerId, cardInstanceId: '' }, |
| | cardPlayed: 'flip_the_table', |
| | playerId, |
| | kingRaResponses: new Map(), |
| | kingRaCount: 0, |
| | timeoutAt: Date.now() + 30000, |
| | }; |
| | |
| | socket.emit('flipTableCards', topCards); |
| | break; |
| | } |
| | |
| | case 'this_is_on_you': { |
| | |
| | if (!targetId) { |
| | socket.emit('error', 'Must select a target player!'); |
| | return; |
| | } |
| | |
| | |
| | const targetSocket = io.sockets.sockets.get(targetId); |
| | if (targetSocket && roomManager) { |
| | await handleDrawCard(io, targetSocket as GameSocket, roomId, game, roomManager, true); |
| | } |
| | break; |
| | } |
| | } |
| | |
| | |
| | roomManager?.updateGameState(roomId); |
| | io.to(roomId).emit('gameStateUpdated', game.getGameState()); |
| | } |
| |
|