From e334f79e1a2f00529b50098d20b5a8bcb84e814d Mon Sep 17 00:00:00 2001 From: Yuto Nishida Date: Sun, 7 Dec 2025 18:11:54 -0800 Subject: [PATCH] [C][2nub] Use context for socket handlers --- experimental/2nub/src/client/App.tsx | 149 ++++++++++-------- .../2nub/src/client/components/GameBoard.tsx | 7 +- .../2nub/src/client/components/Onub.tsx | 7 +- .../2nub/src/client/components/OnubLobby.tsx | 5 +- .../client/contexts/SocketEventContext.tsx | 37 +++++ .../2nub/src/client/hooks/useSocketEvents.ts | 5 + 6 files changed, 131 insertions(+), 79 deletions(-) create mode 100644 experimental/2nub/src/client/contexts/SocketEventContext.tsx create mode 100644 experimental/2nub/src/client/hooks/useSocketEvents.ts diff --git a/experimental/2nub/src/client/App.tsx b/experimental/2nub/src/client/App.tsx index 7ce4cb1..d75613c 100644 --- a/experimental/2nub/src/client/App.tsx +++ b/experimental/2nub/src/client/App.tsx @@ -3,6 +3,7 @@ import { GameBoard } from './components/GameBoard'; import { CreateGame } from './components/CreateGame'; import { GameList } from './components/GameList'; import { useGameEvents } from './hooks/useGameEvents'; +import { SocketEventProvider } from './contexts/SocketEventContext'; import { GameState, GameStateClient, WebSocketMessage, StateLobby } from '../types'; import { buildApiUrl } from './config/api'; import { sessionStorage } from './utils/sessionStorage'; @@ -86,7 +87,7 @@ const App: React.FC = () => { } }, [currentGame, currentPlayerId]); - const { isConnected, forceDisconnectPlayer, connect, authenticatePlayer, updateRuleset } = useGameEvents({ + const { isConnected, forceDisconnectPlayer, connect, disconnect, authenticatePlayer, updateRuleset } = useGameEvents({ onGameState, onGameCreated, onGameDeleted, @@ -217,82 +218,92 @@ const App: React.FC = () => { // Authentication is now handled automatically in useWebSocket hook + const socketEventHandlers = { + isConnected, + forceDisconnectPlayer, + connect, + disconnect, + authenticatePlayer, + updateRuleset, + }; + return ( -
-
-

2nub Game Boilerplate

-
-
+
+
+

2nub Game Boilerplate

+
- {isConnected ? 'Connected' : 'Disconnected'} -
- {!isConnected && ( - + )} + {view === 'game' && ( + - )} - {view === 'game' && ( - - )} -
-
+ }}> + Leave Game + + )} +
+
- {view === 'list' ? ( -
- - -
- ) : ( - - )} -
+ {view === 'list' ? ( +
+ + +
+ ) : ( + + )} + + ); }; diff --git a/experimental/2nub/src/client/components/GameBoard.tsx b/experimental/2nub/src/client/components/GameBoard.tsx index 0c33b50..7901df9 100644 --- a/experimental/2nub/src/client/components/GameBoard.tsx +++ b/experimental/2nub/src/client/components/GameBoard.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { GameStateClient, StateLobby } from '../../types'; +import { GameStateClient } from '../../types'; import { Onub } from './Onub'; interface GameBoardProps { @@ -8,10 +8,9 @@ interface GameBoardProps { currentPlayerId: string | null; onLeave: () => void; onForceDisconnect: (playerId: string) => void; - updateRuleset: (ruleset: StateLobby["ruleset"]) => void; } -export const GameBoard: React.FC = ({ game, gameId, currentPlayerId, onLeave, onForceDisconnect, updateRuleset }) => { +export const GameBoard: React.FC = ({ game, gameId, currentPlayerId, onLeave, onForceDisconnect }) => { console.debug("Rendering GameBoard with game:", game); const [disconnectingPlayerId, setDisconnectingPlayerId] = useState(null); @@ -231,7 +230,7 @@ export const GameBoard: React.FC = ({ game, gameId, currentPlaye )} - {currentPlayerId && } + {currentPlayerId && } ); }; \ No newline at end of file diff --git a/experimental/2nub/src/client/components/Onub.tsx b/experimental/2nub/src/client/components/Onub.tsx index de90f52..1110f7c 100644 --- a/experimental/2nub/src/client/components/Onub.tsx +++ b/experimental/2nub/src/client/components/Onub.tsx @@ -1,14 +1,13 @@ import React, { useState } from 'react'; -import { GameStateClient, StateLobby } from '../../types'; +import { GameStateClient } from '../../types'; import { OnubLobby } from './OnubLobby'; interface OnubProps { game: GameStateClient; currentPlayerId: string; - updateRuleset: (ruleset: StateLobby["ruleset"]) => void; } -export const Onub: React.FC = ({ game, currentPlayerId, updateRuleset }) => { +export const Onub: React.FC = ({ game, currentPlayerId }) => { let content =

This is where the actual game would be implemented. The boilerplate provides: @@ -32,7 +31,7 @@ export const Onub: React.FC = ({ game, currentPlayerId, updateRuleset switch (game.state.state) { case 'lobby': - content = ; + content = ; default: //throw new Error(`Onub component: Unsupported game state "${game.state.state}"`); } diff --git a/experimental/2nub/src/client/components/OnubLobby.tsx b/experimental/2nub/src/client/components/OnubLobby.tsx index f479fc9..e3fcff5 100644 --- a/experimental/2nub/src/client/components/OnubLobby.tsx +++ b/experimental/2nub/src/client/components/OnubLobby.tsx @@ -1,14 +1,15 @@ import React from 'react'; import { GameStateClient, RoleId, ROLES, StateLobby } from '../../types'; +import { useSocketEvents } from '../hooks/useSocketEvents'; interface OnubLobbyProps { stateLobby: StateLobby; playerCount: number; currentPlayerId: string; - updateRuleset: (ruleset: StateLobby["ruleset"]) => void; } -export const OnubLobby: React.FC = ({ stateLobby, playerCount, currentPlayerId, updateRuleset }) => { +export const OnubLobby: React.FC = ({ stateLobby, playerCount, currentPlayerId }) => { + const { updateRuleset } = useSocketEvents(); console.debug("Player count: ", playerCount); function makeRoleAddHandler(roleId: RoleId): React.MouseEventHandler { diff --git a/experimental/2nub/src/client/contexts/SocketEventContext.tsx b/experimental/2nub/src/client/contexts/SocketEventContext.tsx new file mode 100644 index 0000000..77fa8c8 --- /dev/null +++ b/experimental/2nub/src/client/contexts/SocketEventContext.tsx @@ -0,0 +1,37 @@ +import React, { createContext, useContext, ReactNode } from 'react'; +import { StateLobby } from '../../types'; + +export interface SocketEventContextType { + isConnected: boolean; + forceDisconnectPlayer: (gameId: string, playerId: string) => Promise; + connect: () => void; + disconnect: () => void; + authenticatePlayer: (gameId: string, playerId: string) => void; + updateRuleset: (ruleset: StateLobby["ruleset"]) => void; +} + +const SocketEventContext = createContext(null); + +interface SocketEventProviderProps { + children: ReactNode; + eventHandlers: SocketEventContextType; +} + +export const SocketEventProvider: React.FC = ({ + children, + eventHandlers +}) => { + return ( + + {children} + + ); +}; + +export const useSocketEventContext = (): SocketEventContextType => { + const context = useContext(SocketEventContext); + if (!context) { + throw new Error('useSocketEventContext must be used within a SocketEventProvider'); + } + return context; +}; \ No newline at end of file diff --git a/experimental/2nub/src/client/hooks/useSocketEvents.ts b/experimental/2nub/src/client/hooks/useSocketEvents.ts new file mode 100644 index 0000000..da47158 --- /dev/null +++ b/experimental/2nub/src/client/hooks/useSocketEvents.ts @@ -0,0 +1,5 @@ +import { useSocketEventContext } from '../contexts/SocketEventContext'; + +export const useSocketEvents = () => { + return useSocketEventContext(); +}; \ No newline at end of file -- 2.51.2