renamed BattleShipGame.tsx to BattleshipGame.tsx to match props name. using props interface for both instead of using commonProps
This commit is contained in:
147
src/games/battleship/BattleshipGame.tsx
Normal file
147
src/games/battleship/BattleshipGame.tsx
Normal 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 "Opponent’s 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user