From 06bdc92190e59b8b597c1692207176b5246f2fbf Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Thu, 4 Dec 2025 19:16:20 +0530 Subject: [PATCH] refactor(game): unify GameState, standardize board props, and rename game components - Replaced multiple App-level state fields with unified GameState - Added INITIAL_GAME_STATE and migrated App.tsx to use single game state - Introduced GameProps as shared base props for all turn-based board games - Created TicTacToeGameProps and BattleshipGameProps extending GameProps - Updated TicTacToe and Battleship components to use new props - Replaced verbose prop passing with spread {...commonProps} - Updated renderGameBoard to use game.metadata consistently - Renamed TicTacToeBoard -> TicTacToeGame for clarity - Renamed BattleShipBoard -> BattleShipGame for naming consistency - Updated all import paths to reflect new component names - Replaced MatchDataMessage with MatchDataModel - Moved GameState definition from models.ts to interfaces/states.ts - Removed old board-specific prop structures and per-field state management - Increased type safety and reduced duplication across the codebase This commit consolidates game state flow, introduces a clean component props architecture, and standardizes naming convention --- src/App.tsx | 103 ++++++++++-------- ...BattleShipBoard.tsx => BattleShipGame.tsx} | 2 +- src/games/battleship/props.ts | 13 +-- .../{TicTacToeBoard.tsx => TicTacToeGame.tsx} | 2 +- src/games/tictactoe/props.ts | 13 +-- src/interfaces/models.ts | 11 +- src/interfaces/props.ts | 16 ++- src/interfaces/states.ts | 15 +++ 8 files changed, 95 insertions(+), 80 deletions(-) rename src/games/battleship/{BattleShipBoard.tsx => BattleShipGame.tsx} (98%) rename src/games/tictactoe/{TicTacToeBoard.tsx => TicTacToeGame.tsx} (99%) diff --git a/src/App.tsx b/src/App.tsx index bc2edd2..b3e4365 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,57 +1,67 @@ import React, { useState, useEffect } from "react"; import { motion } from "framer-motion"; import { useNakama } from "./providers/NakamaProvider"; -import Player from "./Player"; -import { PlayerModel } from "./interfaces/models"; -import TicTacToeBoard from "./games/tictactoe/TicTacToeBoard"; -import BattleShipBoard from "./games/battleship/BattleShipBoard"; +import Player from "./Player"; +import TicTacToeGame from "./games/tictactoe/TicTacToeGame"; +import { TicTacToeGameProps } from "./games/tictactoe/props"; +import BattleShipGame from "./games/battleship/BattleShipGame"; +import { BattleshipGameProps } from "./games/battleship/props"; + +import { GameState } from "./interfaces/states"; +import { GameProps } from "./interfaces/props"; + +const INITIAL_GAME_STATE: GameState = { + boards: {}, + turn: 0, + winner: null, + gameOver: false, + players: [], + metadata: {}, +}; export default function App() { - // setting up a 2D game boards - const [boards, setBoards] = useState>({}); - const [turn, setTurn] = useState(0); - const [winner, setWinner] = useState(null); - const [gameOver, setGameOver] = useState(null); - const [players, setPlayers] = useState([]); - const [metadata, setMetadata] = useState>({}); - + // unified game state + const [game, setGame] = useState(INITIAL_GAME_STATE); const { sendMatchData, onMatchData, matchId, session } = useNakama(); - function renderGameBoard() { - if (!matchId || !metadata?.game) return null; + const commonProps: GameProps = { + boards: game.boards, + turn: game.turn, + winner: game.winner, + gameOver: game.gameOver, + players: game.players, + myUserId: session?.user_id ?? null, + }; - switch (metadata.game) { + // --------------------------------------------------- + // RENDER GAME BOARD + // --------------------------------------------------- + function renderGameBoard() { + if (!matchId || !game.metadata?.game) return null; + + switch (game.metadata.game) { case "tictactoe": return ( - ); case "battleship": return ( - ); default: - return
Unknown game: {metadata.game}
; + return
Unknown game: {game.metadata.game}
; } } + // ------------------------------------------ // MATCH DATA CALLBACK (from Player component) // ------------------------------------------ @@ -62,23 +72,21 @@ export default function App() { const state = msg.data; console.log("Match state:", state); - setBoards(state.boards); - setTurn(state.turn); - setGameOver(state.game_over); - if (state.winner >= 0) { - setWinner(state.players[state.winner].username); - // } else if (state.game_over) { - // // Game ended but winner = -1 → draw - // setWinner("draw"); - } else { - // Ongoing game, no winner - setWinner(null); - } - setPlayers(state.players || []); - setMetadata(state.metadata || {}); + setGame({ + boards: state.boards, + turn: state.turn, + gameOver: state.game_over, + winner: + state.winner >= 0 ? state.players[state.winner].username : null, + players: state.players ?? [], + metadata: state.metadata ?? {}, + }); } } + // --------------------------------------------------- + // EFFECTS + // --------------------------------------------------- useEffect(() => { document.body.style.overflow = "hidden"; return () => { @@ -96,9 +104,12 @@ export default function App() { function handleCellClick(row: number, col: number) { if (!matchId) return; - sendMatchData(matchId, 1, {data: {row, col}}); + sendMatchData(matchId, 1, { data: { row, col } }); } + // --------------------------------------------------- + // UI LAYOUT + // --------------------------------------------------- return (
= { }; const FLEET_ORDER = ["carrier", "battleship", "cruiser", "submarine", "destroyer"]; -export default function BattleShipBoard({ +export default function BattleShipGame({ boards, players, myUserId, diff --git a/src/games/battleship/props.ts b/src/games/battleship/props.ts index 0329f71..275881a 100644 --- a/src/games/battleship/props.ts +++ b/src/games/battleship/props.ts @@ -1,15 +1,8 @@ import { - Board, - PlayerModel, -} from '../../interfaces/models' + GameProps, +} from '../../interfaces/props' -export interface BattleShipBoardProps { - boards: Record; - turn: number; - winner: string | null; - gameOver: boolean | null; - players: PlayerModel[]; - myUserId: string | null; +export interface BattleshipGameProps extends GameProps { metadata: Record; } diff --git a/src/games/tictactoe/TicTacToeBoard.tsx b/src/games/tictactoe/TicTacToeGame.tsx similarity index 99% rename from src/games/tictactoe/TicTacToeBoard.tsx rename to src/games/tictactoe/TicTacToeGame.tsx index 3f8b4ac..4f69085 100644 --- a/src/games/tictactoe/TicTacToeBoard.tsx +++ b/src/games/tictactoe/TicTacToeGame.tsx @@ -14,7 +14,7 @@ interface BoardProps { onCellClick: (row: number, col: number) => void; } -export default function TicTacToeBoard({ +export default function TicTacToeGame({ boards, turn, winner, diff --git a/src/games/tictactoe/props.ts b/src/games/tictactoe/props.ts index bad6ef9..547fa00 100644 --- a/src/games/tictactoe/props.ts +++ b/src/games/tictactoe/props.ts @@ -1,15 +1,8 @@ import { - Board, - PlayerModel, -} from '../../interfaces/models' + GameProps, +} from '../../interfaces/props' -export interface TicTacToeBoardProps { - boards: Record; - turn: number; - winner: string | null; - gameOver: boolean | null; - players: PlayerModel[]; - myUserId: string | null; +export interface TicTacToeGameProps extends GameProps { onCellClick: (row: number, col: number) => void; } diff --git a/src/interfaces/models.ts b/src/interfaces/models.ts index 5da685c..5d58753 100644 --- a/src/interfaces/models.ts +++ b/src/interfaces/models.ts @@ -5,7 +5,7 @@ export interface PlayerModel { metadata: Record; // e.g. { symbol: "X" } } -export interface MatchDataMessage { +export interface MatchDataModel { opCode: number; data: T; userId: string | null; @@ -15,15 +15,6 @@ export interface Board { grid: string[][]; } -export interface GameState { - boards: Record; - turn: number; - winner: string | null; - gameOver: boolean; - players: PlayerModel[]; - metadata: Record; -} - export interface GameMetadata { game: string; mode: string; diff --git a/src/interfaces/props.ts b/src/interfaces/props.ts index 6ad6962..5453c53 100644 --- a/src/interfaces/props.ts +++ b/src/interfaces/props.ts @@ -1,7 +1,19 @@ import { - MatchDataMessage, + MatchDataModel, } from './models' +import { + GameState +} from "./states"; + export interface PlayerProps { - onMatchDataCallback: (msg:MatchDataMessage) => void; + onMatchDataCallback: (msg:MatchDataModel) => void; } + +export interface GameProps + extends Pick< + GameState, + "boards" | "turn" | "winner" | "gameOver" | "players" + > { + myUserId: string | null; +} \ No newline at end of file diff --git a/src/interfaces/states.ts b/src/interfaces/states.ts index aa144e8..d9816a5 100644 --- a/src/interfaces/states.ts +++ b/src/interfaces/states.ts @@ -3,9 +3,24 @@ import { Socket } from "@heroiclabs/nakama-js"; +import { + Board, + PlayerModel, +} from "./models" + + export interface NakamaProviderState { session: Session | null; socket: Socket | null; matchId: string | null; matchmakerTicket: string | null; +} + +export interface GameState { + boards: Record; + turn: number; + winner: string | null; + gameOver: boolean; + players: PlayerModel[]; + metadata: Record; } \ No newline at end of file