renamed BattleShipGame.tsx to BattleshipGame.tsx to match props name. using props interface for both instead of using commonProps

This commit is contained in:
2025-12-04 19:22:07 +05:30
parent 06bdc92190
commit fc7cb8efb6
3 changed files with 15 additions and 9 deletions

View File

@@ -0,0 +1,147 @@
import React, { useMemo } from "react";
import { motion } from "framer-motion";
import { useNakama } from "../../providers/NakamaProvider";
import { PlayerModel } from "../../interfaces/models";
import PlacementGrid from "./placement/PlacementGrid";
import ShotGrid from "./battle/ShotGrid";
interface BattleBoardProps {
boards: Record<string, { grid: string[][] }>;
players: PlayerModel[];
myUserId: string | null;
turn: number;
winner: string | null;
gameOver: boolean | null;
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 BattleshipGame({
boards,
players,
myUserId,
turn,
winner,
gameOver,
metadata,
}: BattleBoardProps) {
const { sendMatchData, matchId } = useNakama();
const myIndex = players.findIndex((p) => p.user_id === myUserId);
const oppIndex = myIndex === 0 ? 1 : 0;
const phase = metadata["phase"] ?? "lobby";
const isMyTurn = phase === "battle" && turn === myIndex;
const myShips = boards[`p${myIndex}_ships`]?.grid ?? [[]];
const myShots = boards[`p${myIndex}_shots`] ?.grid ?? [[]];
const placed = metadata[`p${myIndex}_placed`] ?? 0;
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") {
sendMatchData(matchId!, 1, {
action: "place",
data: {
ship: ship,
row: r,
col: c,
dir,
}
});
}
// ------------------- SHOOT -------------------
function handleShoot(r: number, c: number) {
sendMatchData(matchId!, 1, {
action: "shoot",
data: {
row: r,
col: c,
}
});
}
// ------------------- STATUS LABEL -------------------
const status = useMemo(() => {
if (phase === "lobby") return `In Lobby`;
if (winner !== null) return `Winner: Player ${winner}`;
if (gameOver) return "Game over — draw";
if (phase === "placement") return `Place your ${nextShip ?? ""}`;
if (myIndex === -1) return "Spectating";
if (!isMyTurn) return "Opponents turn";
return "Your turn";
}, [winner, gameOver, phase, isMyTurn, myIndex, nextShip]);
return (
<motion.div
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
style={{ textAlign: "center" }}
>
<h2 style={{ marginBottom: 8 }}>{status}</h2>
{/* ---------------- PHASE 1: PLACEMENT ---------------- */}
{phase === "placement" && nextShip && (
<PlacementGrid
shipBoard={myShips}
shipName={nextShip}
shipSize={nextShipSize}
onPlace={handlePlace}
/>
)}
{/* ---------------- PHASE 2: BATTLE ---------------- */}
{phase === "battle" && (
<>
<h3>Your Shots</h3>
<ShotGrid
grid={myShots}
isMyTurn={isMyTurn}
gameOver={!!gameOver}
onShoot={handleShoot}
/>
<h3 style={{ marginTop: "18px" }}>Your Ships</h3>
<PlacementGrid
shipBoard={myShips}
shipName="readonly"
shipSize={0}
onPlace={() => {}}
/>
</>
)}
{/* ---------------- WINNER UI ---------------- */}
{winner !== null && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1, scale: [1, 1.05, 1] }}
transition={{ repeat: Infinity, duration: 1.4 }}
style={{
marginTop: 12,
fontSize: "20px",
fontWeight: "bold",
color: "#f1c40f",
}}
>
🎉 Player {winner} Wins! 🎉
</motion.div>
)}
</motion.div>
);
}