166 lines
4.3 KiB
TypeScript
166 lines
4.3 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
|
import { motion } from "framer-motion";
|
|
import { useNakama } from "./providers/NakamaProvider";
|
|
|
|
import Player from "./Player";
|
|
import TicTacToeGame from "./games/tictactoe/TicTacToeGame";
|
|
import { TicTacToeGameProps } from "./games/tictactoe/props";
|
|
import BattleshipGame from "./games/battleship/BattleshipGame"
|
|
import { BattleshipGameProps } from "./games/battleship/props";
|
|
|
|
import { GameState } from "./interfaces/states";
|
|
import { GameProps } from "./interfaces/props";
|
|
|
|
const INITIAL_GAME_STATE: GameState = {
|
|
boards: {},
|
|
turn: 0,
|
|
winner: null,
|
|
gameOver: false,
|
|
players: [],
|
|
metadata: {},
|
|
};
|
|
|
|
export default function App() {
|
|
// unified game state
|
|
const [game, setGame] = useState<GameState>(INITIAL_GAME_STATE);
|
|
const { onMatchData, matchId, session } = useNakama();
|
|
|
|
const commonProps: GameProps = {
|
|
boards: game.boards,
|
|
turn: game.turn,
|
|
winner: game.winner,
|
|
gameOver: game.gameOver,
|
|
players: game.players,
|
|
myUserId: session?.user_id ?? null,
|
|
};
|
|
const ticTacToeProps: TicTacToeGameProps = {
|
|
...commonProps,
|
|
};
|
|
const battleshipProps: BattleshipGameProps = {
|
|
...commonProps,
|
|
metadata: game.metadata,
|
|
};
|
|
|
|
// ---------------------------------------------------
|
|
// RENDER GAME BOARD
|
|
// ---------------------------------------------------
|
|
function renderGameBoard() {
|
|
if (!matchId || !game.metadata?.game) return null;
|
|
|
|
switch (game.metadata.game) {
|
|
case "tictactoe":
|
|
return (
|
|
<TicTacToeGame
|
|
{...ticTacToeProps}
|
|
/>
|
|
);
|
|
|
|
case "battleship":
|
|
return (
|
|
<BattleshipGame
|
|
{...battleshipProps}
|
|
/>
|
|
);
|
|
|
|
default:
|
|
return <div>Unknown game: {game.metadata.game}</div>;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------
|
|
// MATCH DATA CALLBACK (from Player component)
|
|
// ------------------------------------------
|
|
function onMatchDataCallback(msg: { opCode: number; data: any }) {
|
|
console.log("[Match Data]", msg);
|
|
|
|
if (msg.opCode === 2) {
|
|
const state = msg.data;
|
|
console.log("Match state:", state);
|
|
|
|
setGame({
|
|
boards: state.boards,
|
|
turn: state.turn,
|
|
gameOver: state.game_over,
|
|
winner:
|
|
state.winner >= 0 ? state.players[state.winner].username : null,
|
|
players: state.players ?? [],
|
|
metadata: state.metadata ?? {},
|
|
});
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------
|
|
// EFFECTS
|
|
// ---------------------------------------------------
|
|
useEffect(() => {
|
|
document.body.style.overflow = "hidden";
|
|
return () => {
|
|
document.body.style.overflow = "auto";
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
onMatchData(onMatchDataCallback);
|
|
}, [onMatchData]);
|
|
|
|
// ---------------------------------------------------
|
|
// UI LAYOUT
|
|
// ---------------------------------------------------
|
|
return (
|
|
<div
|
|
style={{
|
|
height: "100vh",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
background: "#060606",
|
|
color: "white",
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
{/* ---------------- HEADER (always fixed at top) ---------------- */}
|
|
<header
|
|
style={{
|
|
padding: "16px 20px",
|
|
background: "rgba(255,255,255,0.04)",
|
|
borderBottom: "1px solid rgba(255,255,255,0.08)",
|
|
backdropFilter: "blur(6px)",
|
|
textAlign: "center",
|
|
fontSize: "26px",
|
|
fontWeight: 700,
|
|
letterSpacing: "1px",
|
|
}}
|
|
>
|
|
Games
|
|
</header>
|
|
|
|
{/* ---------------- MAIN CONTENT (scrolls) ---------------- */}
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
style={{
|
|
flex: 1,
|
|
overflowY: "auto",
|
|
padding: "20px",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
alignItems: "center",
|
|
}}
|
|
>
|
|
<Player onMatchDataCallback={onMatchDataCallback} />
|
|
|
|
<div
|
|
style={{
|
|
padding: "20px",
|
|
background: "rgba(255,255,255,0.03)",
|
|
borderRadius: "20px",
|
|
boxShadow: "0 6px 20px rgba(0,0,0,0.4)",
|
|
minWidth: "300px",
|
|
}}
|
|
>
|
|
{renderGameBoard()}
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
);
|
|
}
|