matchId check in Board instead of TicTacToe.tsx
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { useNakama } from "./providers/NakamaProvider";
|
||||||
|
|
||||||
interface BoardProps {
|
interface BoardProps {
|
||||||
board: string[][];
|
board: string[][];
|
||||||
@@ -20,6 +21,9 @@ export default function Board({
|
|||||||
}: BoardProps) {
|
}: BoardProps) {
|
||||||
const myIndex = players.indexOf(myUserId ?? "");
|
const myIndex = players.indexOf(myUserId ?? "");
|
||||||
const gameReady = players.length === 2;
|
const gameReady = players.length === 2;
|
||||||
|
const {
|
||||||
|
matchId
|
||||||
|
} = useNakama();
|
||||||
|
|
||||||
const mySymbol =
|
const mySymbol =
|
||||||
myIndex === 0 ? "X" : myIndex === 1 ? "O" : null;
|
myIndex === 0 ? "X" : myIndex === 1 ? "O" : null;
|
||||||
@@ -44,134 +48,138 @@ export default function Board({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<>
|
||||||
initial={{ opacity: 0, y: 10 }}
|
{matchId && (
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.35 }}
|
|
||||||
>
|
|
||||||
<motion.h2
|
|
||||||
key={status}
|
|
||||||
initial={{ opacity: 0, y: -6 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.25 }}
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
>
|
|
||||||
{status}
|
|
||||||
</motion.h2>
|
|
||||||
|
|
||||||
{gameReady && mySymbol && (
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
animate={{ opacity: 0.75 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
style={{ marginBottom: 8, fontSize: 14 }}
|
transition={{ duration: 0.35 }}
|
||||||
>
|
>
|
||||||
You: <strong>{mySymbol}</strong> — Opponent:{" "}
|
<motion.h2
|
||||||
<strong>{opponentSymbol}</strong>
|
key={status}
|
||||||
|
initial={{ opacity: 0, y: -6 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.25 }}
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
>
|
||||||
|
{status}
|
||||||
|
</motion.h2>
|
||||||
|
|
||||||
|
{gameReady && mySymbol && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.75 }}
|
||||||
|
style={{ marginBottom: 8, fontSize: 14 }}
|
||||||
|
>
|
||||||
|
You: <strong>{mySymbol}</strong> — Opponent:{" "}
|
||||||
|
<strong>{opponentSymbol}</strong>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* -------------------------
|
||||||
|
BOARD
|
||||||
|
-------------------------- */}
|
||||||
|
<motion.div
|
||||||
|
layout
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(3, 80px)",
|
||||||
|
gap: "10px",
|
||||||
|
marginTop: "6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{board.map((row, rIdx) =>
|
||||||
|
row.map((cell, cIdx) => {
|
||||||
|
const disabled =
|
||||||
|
!!cell ||
|
||||||
|
!!winner ||
|
||||||
|
!gameReady ||
|
||||||
|
myIndex === -1 ||
|
||||||
|
!isMyTurn;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.button
|
||||||
|
key={`${rIdx}-${cIdx}-${cell}`} // rerender when cell changes
|
||||||
|
layout
|
||||||
|
whileHover={
|
||||||
|
!disabled
|
||||||
|
? {
|
||||||
|
scale: 1.1,
|
||||||
|
boxShadow: "0px 0px 10px rgba(255,255,255,0.4)",
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
whileTap={!disabled ? { scale: 0.85 } : {}}
|
||||||
|
onClick={() => !disabled && onCellClick(rIdx, cIdx)}
|
||||||
|
style={{
|
||||||
|
width: "80px",
|
||||||
|
height: "80px",
|
||||||
|
fontSize: "2rem",
|
||||||
|
borderRadius: "10px",
|
||||||
|
border: "2px solid #333",
|
||||||
|
background: "#111",
|
||||||
|
color: "white",
|
||||||
|
cursor: disabled ? "not-allowed" : "pointer",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AnimatePresence>
|
||||||
|
{cell && (
|
||||||
|
<motion.span
|
||||||
|
key="symbol"
|
||||||
|
initial={{ scale: 0.3, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
exit={{ scale: 0.3, opacity: 0 }}
|
||||||
|
transition={{ type: "spring", stiffness: 200, damping: 12 }}
|
||||||
|
style={{
|
||||||
|
color:
|
||||||
|
winner === cell
|
||||||
|
? "#f1c40f" // highlight winning symbol
|
||||||
|
: "white",
|
||||||
|
textShadow:
|
||||||
|
winner === cell
|
||||||
|
? "0 0 12px rgba(241,196,15,0.8)"
|
||||||
|
: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cell}
|
||||||
|
</motion.span>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</motion.button>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Winner pulse animation */}
|
||||||
|
{winner && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: [1, 1.06, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
repeat: Infinity,
|
||||||
|
duration: 1.4,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
color: "#f1c40f",
|
||||||
|
fontSize: "20px",
|
||||||
|
marginTop: "14px",
|
||||||
|
fontWeight: 700,
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🎉 {winner} Wins! 🎉
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
{/* -------------------------
|
)
|
||||||
BOARD
|
|
||||||
-------------------------- */}
|
|
||||||
<motion.div
|
|
||||||
layout
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(3, 80px)",
|
|
||||||
gap: "10px",
|
|
||||||
marginTop: "6px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{board.map((row, rIdx) =>
|
|
||||||
row.map((cell, cIdx) => {
|
|
||||||
const disabled =
|
|
||||||
!!cell ||
|
|
||||||
!!winner ||
|
|
||||||
!gameReady ||
|
|
||||||
myIndex === -1 ||
|
|
||||||
!isMyTurn;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.button
|
|
||||||
key={`${rIdx}-${cIdx}-${cell}`} // rerender when cell changes
|
|
||||||
layout
|
|
||||||
whileHover={
|
|
||||||
!disabled
|
|
||||||
? {
|
|
||||||
scale: 1.1,
|
|
||||||
boxShadow: "0px 0px 10px rgba(255,255,255,0.4)",
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
whileTap={!disabled ? { scale: 0.85 } : {}}
|
|
||||||
onClick={() => !disabled && onCellClick(rIdx, cIdx)}
|
|
||||||
style={{
|
|
||||||
width: "80px",
|
|
||||||
height: "80px",
|
|
||||||
fontSize: "2rem",
|
|
||||||
borderRadius: "10px",
|
|
||||||
border: "2px solid #333",
|
|
||||||
background: "#111",
|
|
||||||
color: "white",
|
|
||||||
cursor: disabled ? "not-allowed" : "pointer",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AnimatePresence>
|
|
||||||
{cell && (
|
|
||||||
<motion.span
|
|
||||||
key="symbol"
|
|
||||||
initial={{ scale: 0.3, opacity: 0 }}
|
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
|
||||||
exit={{ scale: 0.3, opacity: 0 }}
|
|
||||||
transition={{ type: "spring", stiffness: 200, damping: 12 }}
|
|
||||||
style={{
|
|
||||||
color:
|
|
||||||
winner === cell
|
|
||||||
? "#f1c40f" // highlight winning symbol
|
|
||||||
: "white",
|
|
||||||
textShadow:
|
|
||||||
winner === cell
|
|
||||||
? "0 0 12px rgba(241,196,15,0.8)"
|
|
||||||
: "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{cell}
|
|
||||||
</motion.span>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</motion.button>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Winner pulse animation */}
|
|
||||||
{winner && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{
|
|
||||||
opacity: 1,
|
|
||||||
scale: [1, 1.06, 1],
|
|
||||||
}}
|
|
||||||
transition={{
|
|
||||||
repeat: Infinity,
|
|
||||||
duration: 1.4,
|
|
||||||
ease: "easeInOut",
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
color: "#f1c40f",
|
|
||||||
fontSize: "20px",
|
|
||||||
marginTop: "14px",
|
|
||||||
fontWeight: 700,
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
🎉 {winner} Wins! 🎉
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,16 +80,14 @@ export default function TicTacToe() {
|
|||||||
onMatchDataCallback={onMatchDataCallback}
|
onMatchDataCallback={onMatchDataCallback}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{matchId && (
|
<Board
|
||||||
<Board
|
board={board}
|
||||||
board={board}
|
turn={turn}
|
||||||
turn={turn}
|
winner={winner}
|
||||||
winner={winner}
|
players={players}
|
||||||
players={players}
|
myUserId={session?.user_id ?? null}
|
||||||
myUserId={session?.user_id ?? null}
|
onCellClick={handleCellClick}
|
||||||
onCellClick={handleCellClick}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user