feat(ui/battleship): integrate BattleshipBoard and metadata-driven placement/battle flow

- Added metadata state to App and wired incoming match metadata.
- Added Fleet + FLEET_ORDER in BattleShipBoard to drive ship placement order.
- Added nextShip + nextShipSize calculation for guided placement.
- Updated handlePlace and handleShoot to send structured payloads (action + data).
- Added lobby/placement/battle status messages.
- Updated grids to use shipBoard + shipName/shipSize props instead of generic grid.
- Fixed metadata access (state.Metadata vs state.metadata).
- Consolidated PlacementGrid usage and disabled it during battle phase.
- Added logging for debugging incoming battleship boards.
This commit is contained in:
2025-12-03 21:01:41 +05:30
parent fe1cacb5ed
commit 81a54aa93e

View File

@@ -16,6 +16,15 @@ interface BattleBoardProps {
metadata: Record<string, any>; metadata: Record<string, any>;
} }
const Fleet: Record<string, number> = {
carrier: 5,
battleship: 4,
cruiser: 3,
submarine: 3,
destroyer: 2,
};
const FLEET_ORDER = ["carrier", "battleship", "cruiser", "submarine", "destroyer"];
export default function BattleShipBoard({ export default function BattleShipBoard({
boards, boards,
players, players,
@@ -25,49 +34,55 @@ export default function BattleShipBoard({
gameOver, gameOver,
metadata, metadata,
}: BattleBoardProps) { }: BattleBoardProps) {
const { matchId, sendMatchData } = useNakama(); const { sendMatchData, matchId } = useNakama();
const myIndex = players.findIndex((p) => p.user_id === myUserId); const myIndex = players.findIndex((p) => p.user_id === myUserId);
const oppIndex = myIndex === 0 ? 1 : 0; const oppIndex = myIndex === 0 ? 1 : 0;
const phase = metadata["phase"] ?? "placement"; console.log(metadata, "metadata");
const phase = metadata["phase"] ?? "lobby";
const isMyTurn = phase === "battle" && turn === myIndex; const isMyTurn = phase === "battle" && turn === myIndex;
const myShips = boards[`p${myIndex}_ships`]?.grid ?? []; const myShips = boards[`p${myIndex}_ships`]?.grid ?? [[]];
const myShots = boards[`p${myIndex}_shots`]?.grid ?? []; const myShots = boards[`p${myIndex}_shots`] ?.grid ?? [[]];
const placed = metadata[`p${myIndex}_placed`] ?? 0; const placed = metadata[`p${myIndex}_placed`] ?? 0;
// ----------- SEND PLACE MESSAGE ----------- const nextShip = FLEET_ORDER[placed] || null;
const nextShipSize = nextShip ? Fleet[nextShip] : null;
// ------------------- PLACE SHIP -------------------
function handlePlace(ship: string, r: number, c: number, dir: "h" | "v") { function handlePlace(ship: string, r: number, c: number, dir: "h" | "v") {
sendMatchData(matchId!, 2, { sendMatchData(matchId!, 1, {
action: "place", action: "place",
ship, data: {
row: r, ship: ship,
col: c, row: r,
dir, col: c,
dir,
}
}); });
} }
// ----------- SEND SHOT MESSAGE ----------- // ------------------- SHOOT -------------------
function handleShoot(r: number, c: number) { function handleShoot(r: number, c: number) {
sendMatchData(matchId!, 2, { sendMatchData(matchId!, 1, {
action: "shoot", action: "shoot",
row: r, row: r,
col: c, col: c,
}); });
} }
// ------------------- STATUS LINE --------------------- // ------------------- STATUS LABEL -------------------
const status = useMemo(() => { const status = useMemo(() => {
if (winner) return `Winner: Player ${winner}`; if (phase === "lobby") return `In Lobby`;
if (winner !== null) return `Winner: Player ${winner}`;
if (gameOver) return "Game over — draw"; if (gameOver) return "Game over — draw";
if (phase === "placement") return "Place your ships"; if (phase === "placement") return `Place your ${nextShip ?? ""}`;
if (myIndex === -1) return "Spectating"; if (myIndex === -1) return "Spectating";
if (!isMyTurn) return "Opponent's turn"; if (!isMyTurn) return "Opponents turn";
return "Your turn"; return "Your turn";
}, [winner, gameOver, phase, isMyTurn, myIndex]); }, [winner, gameOver, phase, isMyTurn, myIndex, nextShip]);
return ( return (
<motion.div <motion.div
@@ -79,20 +94,20 @@ export default function BattleShipBoard({
<h2 style={{ marginBottom: 8 }}>{status}</h2> <h2 style={{ marginBottom: 8 }}>{status}</h2>
{/* ---------------- PHASE 1: PLACEMENT ---------------- */} {/* ---------------- PHASE 1: PLACEMENT ---------------- */}
{phase === "placement" && ( {phase === "placement" && nextShip && (
<div style={{ display: "flex", justifyContent: "center" }}> <PlacementGrid
<PlacementGrid shipBoard={myShips}
grid={myShips} shipName={nextShip}
placed={placed} shipSize={nextShipSize}
onPlace={handlePlace} onPlace={handlePlace}
/> />
</div>
)} )}
{/* ---------------- PHASE 2: BATTLE ---------------- */} {/* ---------------- PHASE 2: BATTLE ---------------- */}
{phase === "battle" && ( {phase === "battle" && (
<> <>
<h3>Your Shots</h3> <h3>Your Shots</h3>
<ShotGrid <ShotGrid
grid={myShots} grid={myShots}
isMyTurn={isMyTurn} isMyTurn={isMyTurn}
@@ -102,9 +117,10 @@ export default function BattleShipBoard({
<h3 style={{ marginTop: "18px" }}>Your Ships</h3> <h3 style={{ marginTop: "18px" }}>Your Ships</h3>
<PlacementGrid <PlacementGrid
grid={myShips} shipBoard={myShips}
placed={999} // show ships but disable placement shipName="readonly"
readOnly shipSize={0}
onPlace={() => {}}
/> />
</> </>
)} )}