diff --git a/frontend/src/clientDispatch.ts b/frontend/src/clientDispatch.ts new file mode 100644 index 0000000..3562aa6 --- /dev/null +++ b/frontend/src/clientDispatch.ts @@ -0,0 +1,127 @@ +import { GameEvent, GameEventTypes } from '../../backend/src/types'; +import { GameState } from './contexts/GameContext'; +import assert from 'assert'; + +export const clientDispatch = ( + gameState: GameState, + event: GameEvent +): GameState => { + let newGameState: GameState; + + switch (event.type) { + case GameEventTypes.JOIN_GAME: + newGameState = handleJoinGame(gameState, event); + break; + case GameEventTypes.LEAVE_GAME: + newGameState = handleLeaveGame(gameState, event); + break; + case GameEventTypes.DRAW_CARD: + newGameState = handleDrawCard(gameState, event); + break; + case GameEventTypes.THROW_CARD: + newGameState = handleThrowCard(gameState, event); + break; + case GameEventTypes.STATE_SYNC: + newGameState = handleStateSync(gameState, event); + break; + default: + throw new Error(`Unhandled event type: ${event}`); + } + return newGameState; +}; + +const handleJoinGame = (gameState: GameState, event: GameEvent): GameState => { + if (event.type !== GameEventTypes.JOIN_GAME) { + throw new Error(`Invalid event type for handleJoinGame: ${event.type}`); + } + assert(event.data !== undefined, 'Unknown Player'); + const { playerId, data } = event; + const playerName = data.joinedPlayer.name; + + return { + ...gameState, + players: [ + ...gameState.players, + { id: playerId, name: playerName, cards: [] }, + ], + }; +}; +const handleLeaveGame = (gameState: GameState, event: GameEvent): GameState => { + if (event.type !== GameEventTypes.LEAVE_GAME) { + throw new Error( + `Invalid event type for handleLeaveGame: ${event.type}` + ); + } + const { playerId } = event; + return { + ...gameState, + players: gameState.players.filter((player) => player.id !== playerId), + }; +}; + +const handleDrawCard = (gameState: GameState, event: GameEvent): GameState => { + if (event.type !== GameEventTypes.DRAW_CARD) { + throw new Error(`Invalid event type for handleDrawCard: ${event.type}`); + } + const { playerId } = event; + + // Find the player who drew the card + const updatedPlayers = gameState.players.map((player) => { + if (player.id === playerId) { + const updatedCards = [...player.cards]; + return { ...player, cards: updatedCards }; + } + return player; + }); + + return { + ...gameState, + players: updatedPlayers, + }; +}; + +const handleThrowCard = (gameState: GameState, event: GameEvent): GameState => { + if (event.type !== GameEventTypes.THROW_CARD) { + throw new Error( + `Invalid event type for handleThrowCard: ${event.type}` + ); + } + const { playerId } = event; + const { cardId } = event.data; + return { + ...gameState, + players: gameState.players.map((player) => + player.id === playerId + ? { ...player, cards: player.cards.filter((c) => c !== cardId) } + : player + ), + lastThrownCard: + gameState.cardDeck.find((card) => card.id === cardId) || null, + }; +}; + +const handleStateSync = (gameState: GameState, event: GameEvent): GameState => { + if (event.type !== GameEventTypes.STATE_SYNC) { + throw new Error( + `Invalid event type for handleStateSync: ${event.type}` + ); + } + const { data } = event; + const mappedPlayers = data.players.map((player) => ({ + id: player.id, + name: player.name, + cards: player.cards, + })); + + return { + ...gameState, + players: mappedPlayers, + cardDeck: data.cardDeck, + thrownCards: data.thrownCards, + currentPlayerIndex: data.currentPlayerIndex, + lastThrownCard: data.lastThrownCard, + direction: data.direction, + status: data.status, + runningEvents: data.runningEvents, + }; +}; diff --git a/frontend/src/contexts/GameContext.tsx b/frontend/src/contexts/GameContext.tsx index 42c0627..7adce8f 100644 --- a/frontend/src/contexts/GameContext.tsx +++ b/frontend/src/contexts/GameContext.tsx @@ -5,14 +5,18 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { useAuth } from './AuthContext'; import { useToast } from '../library/toast/toast-context'; import * as channel from '../channel'; +import { clientDispatch } from '../clientDispatch'; import { APIPlayer, GameStatus, RunningEvents, UNOCard, + AppEvent, + GameEvent, + GameEventTypes, } from '../../../backend/src/types'; -interface GameState { +export interface GameState { id: string; cardDeck: UNOCard[]; thrownCards: UNOCard[]; @@ -56,7 +60,14 @@ export const GameProvider = () => { const toast = useToast(); const auth = useAuth(); const backendUrl = process.env.REACT_APP_BACKEND_URL; - + const dispatchGameEvent = (event: AppEvent) => { + if (event.type in GameEventTypes) { + setGameState((prevState) => + clientDispatch(prevState as GameState, event as GameEvent) + ); + } + // Handle ChatEvents + }; useEffect(() => { if (!auth.isLoggedIn()) { navigate('/login' + location.search); @@ -125,12 +136,7 @@ export const GameProvider = () => { useEffect(() => { // add event listener to listen for the game state changes channel.setGameEventsDispatcher((event) => { - // todo: this callback will be replaced by the event dispatcher - console.log('Handling event:', event); - if (event.type === 'STATE_SYNC') { - console.log(event.data); - setGameState(event.data); - } + dispatchGameEvent(event); }); }, [gameState]);