feat(battleship): add complete Battleship game UI with placement & battle phases

- Implement BattleshipBoard with phase-based rendering (placement/battle)
- Add PlacementGrid for ship placement interaction
- Add ShotGrid for firing UI with turn validation
- Integrate match metadata (pX_placed, pX_ready, phase)
- Connect UI to Nakama sendMatchData (place + shoot actions)
- Add real-time board rendering for ships and shots
- Add status line, turn handling, and winner display
- Ensure compatibility with new backend ApplyMove/ApplyPlacement logic
This commit is contained in:
2025-12-03 19:27:47 +05:30
parent 2b0af9fd1f
commit fe1cacb5ed
8 changed files with 882 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
import React from "react";
import { motion, AnimatePresence } from "framer-motion";
interface BattleStatusProps {
phase: string;
myIndex: number;
turn: number;
players: any[];
lastHit: boolean | null;
winner: string | null;
gameOver: boolean | null;
}
export default function BattleStatus({
phase,
myIndex,
turn,
players,
lastHit,
winner,
gameOver,
}: BattleStatusProps) {
const isMyTurn = turn === myIndex;
// -----------------------------
// STATUS TEXT
// -----------------------------
let statusText = "";
if (gameOver) {
statusText =
winner === players[myIndex]?.user_id ? "🎉 Victory!" : "💥 Defeat";
} else if (phase === "placement") {
statusText = "Place Your Fleet";
} else {
statusText = isMyTurn ? "Your Turn — FIRE!" : "Opponents Turn";
}
// -----------------------------
// Last hit/miss indicator
// -----------------------------
let hitText = null;
if (lastHit === true) hitText = "🔥 HIT!";
else if (lastHit === false) hitText = "💦 MISS";
return (
<motion.div
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
style={{
textAlign: "center",
marginBottom: 14,
color: "#eee",
fontFamily: "sans-serif",
}}
>
{/* MAIN STATUS */}
<motion.div
key={statusText}
initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
style={{
fontSize: 20,
fontWeight: 700,
marginBottom: 4,
}}
>
{statusText}
</motion.div>
{/* HIT / MISS FEEDBACK */}
<AnimatePresence mode="wait">
{hitText && !gameOver && (
<motion.div
key={hitText}
initial={{ opacity: 0, scale: 0.7 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.7 }}
transition={{ duration: 0.25 }}
style={{
color: lastHit ? "#e74c3c" : "#3498db",
fontSize: 18,
fontWeight: 600,
marginTop: 4,
}}
>
{hitText}
</motion.div>
)}
</AnimatePresence>
{/* PHASE */}
<div style={{ marginTop: 6, opacity: 0.55, fontSize: 14 }}>
Phase: <strong>{phase}</strong>
</div>
</motion.div>
);
}