diff --git a/.eslintrc.json b/.eslintrc.json index e5ace1d..095995b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,7 +6,7 @@ "next/core-web-vitals", "plugin:react/recommended" ], - "plugins": ["react", "react-hooks"], + "plugins": ["react"], "env": { "browser": true, "node": true, diff --git a/next.config.mjs b/next.config.mjs index 4f2edee..4d2ddd3 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -9,6 +9,16 @@ const nextConfig = { }, ] },*/ + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "images.unsplash.com", + port: "", + pathname: "/**", + }, + ], + }, } export default nextConfig diff --git a/socket/src/index.js b/socket/src/index.js index 1323832..efa5962 100644 --- a/socket/src/index.js +++ b/socket/src/index.js @@ -2,8 +2,10 @@ import { Server } from "socket.io" import { GAME_STATE_INIT } from "./quizz.config.js" import Manager from "./roles/manager.js" import Player from "./roles/player.js" +import { abortCooldown } from "./utils/cooldown.js" +import deepClone from "./utils/deepClone.js" -let gameState = GAME_STATE_INIT +let gameState = deepClone(GAME_STATE_INIT) const io = new Server({ cors: { @@ -25,8 +27,8 @@ io.on("connection", (socket) => { Player.join(gameState, io, socket, player), ) - socket.on("manager:createRoom", () => - Manager.createRoom(gameState, io, socket), + socket.on("manager:createRoom", (password) => + Manager.createRoom(gameState, io, socket, password), ) socket.on("manager:kickPlayer", (playerId) => Manager.kickPlayer(gameState, io, socket, playerId), @@ -48,9 +50,21 @@ io.on("connection", (socket) => { socket.on("disconnect", () => { console.log(`user disconnected ${socket.id}`) - /*if (gameState.manager === socket.id) { + if (gameState.manager === socket.id) { console.log("Reset game") - gameState = gameStateInit - }*/ + io.to(gameState.room).emit("game:reset") + gameState.started = false + gameState = deepClone(GAME_STATE_INIT) + + abortCooldown() + return + } + + const player = gameState.players.find((p) => p.id === socket.id) + + if (player) { + gameState.players = gameState.players.filter((p) => p.id !== socket.id) + socket.to(gameState.manager).emit("manager:removePlayer", player.id) + } }) }) diff --git a/socket/src/quizz.config.js b/socket/src/quizz.config.js index 1c9195b..46b7528 100644 --- a/socket/src/quizz.config.js +++ b/socket/src/quizz.config.js @@ -1,11 +1,13 @@ export const GAME_STATE_INIT = { started: false, + password: "PASSWORD", players: [], playersAnswer: [], manager: null, room: null, currentQuestion: 0, roundStartTime: 0, + subject: "Adobe", questions: [ { question: "Who are the founders of Adobe?", @@ -15,21 +17,24 @@ export const GAME_STATE_INIT = { "Jhon Jonse and Charles Geskie", "Bill Gate", ], - image: "/question/Adobe-Logo.png", solution: 1, + cooldow: 5, time: 15, }, { question: "What is Adobe's most famous software?", answers: ["Encore", "AfterEffect", "Creative Cloud", "Photoshop"], - image: "/question/Adobe-Packages.webp", + image: + "https://images.unsplash.com/photo-1626785774573-4b799315345d?q=80&w=500&auto=webp", solution: 3, + cooldown: 5, time: 15, }, { question: "When was Adobe created?", answers: ["2000", "1982", "2003", "1987"], solution: 1, + cooldown: 5, time: 15, }, { @@ -40,8 +45,8 @@ export const GAME_STATE_INIT = { "DowTown, Texas", "Tokyo, Japan", ], - image: "/question/Adobe_World_Headquarters.jpg", solution: 0, + cooldown: 5, time: 15, }, { @@ -52,8 +57,10 @@ export const GAME_STATE_INIT = { "25,988 employees", "5,073 employees", ], - image: "/question/000012204568_Large.jpg", + image: + "https://images.unsplash.com/photo-1504384308090-c894fdcc538d?q=80&w=500&auto=webp", solution: 2, + cooldown: 5, time: 15, }, { @@ -64,8 +71,10 @@ export const GAME_STATE_INIT = { "Mark Java", "Shantanu Narayen", ], - image: "/question/guess-the-person.png", + image: + "https://images.unsplash.com/photo-1435348773030-a1d74f568bc2?q=80&w=500&auto=webp", solution: 3, + cooldown: 5, time: 15, }, { @@ -76,7 +85,10 @@ export const GAME_STATE_INIT = { "Logistics software", "Other", ], + image: + "https://images.unsplash.com/photo-1582736317407-371893d9e146?q=80&w=500&auto=webp", solution: 0, + cooldown: 5, time: 15, }, ], diff --git a/socket/src/roles/manager.js b/socket/src/roles/manager.js index ee52fb8..8a933fc 100644 --- a/socket/src/roles/manager.js +++ b/socket/src/roles/manager.js @@ -2,11 +2,17 @@ import { GAME_STATE_INIT } from "../quizz.config.js" import { startRound } from "../utils/round.js" import generateRoomId from "../utils/generateRoomId.js" import { cooldown, sleep } from "../utils/cooldown.js" +import deepClone from "../utils/deepClone.js" const Manager = { - createRoom: (game, io, socket) => { + createRoom: (game, io, socket, password) => { + if (game.password !== password) { + io.to(socket.id).emit("game:errorMessage", "Bad Password") + return + } + if (game.manager || game.room) { - io.to(socket.id).emit("message", "Already manager") + io.to(socket.id).emit("game:errorMessage", "Already manager") return } @@ -28,6 +34,7 @@ const Manager = { const player = game.players.find((p) => p.id === playerId) game.players = game.players.filter((p) => p.id !== playerId) + io.in(playerId).socketsLeave(game.room) io.to(player.id).emit("game:kick") io.to(game.manager).emit("manager:playerKicked", player.id) }, @@ -72,18 +79,19 @@ const Manager = { showLoaderboard: (game, io, socket) => { if (!game.questions[game.currentQuestion + 1]) { - io.to(socket).emit("game:status", { + socket.emit("game:status", { name: "FINISH", data: { - winners: game.players.slice(0, 3).sort((a, b) => b.points - a.points), + subject: game.subject, + top: game.players.slice(0, 3).sort((a, b) => b.points - a.points), }, }) - game = GAME_STATE_INIT + game = deepClone(GAME_STATE_INIT) return } - io.to(socket.id).emit("game:status", { + socket.emit("game:status", { name: "SHOW_LEADERBOARD", data: { leaderboard: game.players diff --git a/socket/src/roles/player.js b/socket/src/roles/player.js index 5b7c20f..aed2b31 100644 --- a/socket/src/roles/player.js +++ b/socket/src/roles/player.js @@ -51,7 +51,7 @@ const Player = { game.players.push(playerData) - io.to(socket.id).emit("game:successJoin") + socket.emit("game:successJoin") }, selectedAnswer: (game, io, socket, answerKey) => { @@ -72,7 +72,7 @@ const Player = { points: convertTimeToPoint(game.roundStartTime, question.time), }) - io.to(socket.id).emit("game:status", { + socket.emit("game:status", { name: "WAIT", data: { text: "Waiting for the players to answer" }, }) diff --git a/socket/src/utils/deepClone.js b/socket/src/utils/deepClone.js new file mode 100644 index 0000000..b169b29 --- /dev/null +++ b/socket/src/utils/deepClone.js @@ -0,0 +1,20 @@ +const deepClone = (obj) => { + if (obj === null || typeof obj !== "object") { + return obj + } + + if (Array.isArray(obj)) { + return obj.map(deepClone) + } + + const clonedObj = {} + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + clonedObj[key] = deepClone(obj[key]) + } + } + + return clonedObj +} + +export default deepClone diff --git a/socket/src/utils/round.js b/socket/src/utils/round.js index 63612ef..3c27d7f 100644 --- a/socket/src/utils/round.js +++ b/socket/src/utils/round.js @@ -3,6 +3,8 @@ import { cooldown, sleep } from "./cooldown.js" export const startRound = async (game, io, socket) => { const question = game.questions[game.currentQuestion] + if (!game.started) return + io.to(game.room).emit("game:updateQuestion", { current: game.currentQuestion + 1, total: game.questions.length, @@ -16,19 +18,22 @@ export const startRound = async (game, io, socket) => { }, }) - await sleep(4) + await sleep(2) + + if (!game.started) return io.to(game.room).emit("game:status", { name: "SHOW_QUESTION", data: { question: question.question, - number: game.currentQuestion + 1, image: question.image, - cooldown: 6, + cooldown: question.cooldown, }, }) - await sleep(6) + await sleep(question.cooldown) + + if (!game.started) return game.roundStartTime = Date.now() @@ -45,6 +50,8 @@ export const startRound = async (game, io, socket) => { await cooldown(question.time, io, game.room) + if (!game.started) return + game.players.map(async (player) => { let playerAnswer = await game.playersAnswer.find((p) => p.id === player.id) @@ -55,32 +62,30 @@ export const startRound = async (game, io, socket) => { let points = (isCorrect && Math.round(playerAnswer && playerAnswer.points)) || 0 - game.players.find((p) => p.id === player.id).points += points + player.points += points - setTimeout(() => { - let sortPlayers = game.players.sort((a, b) => b.points - a.points) + let sortPlayers = game.players.sort((a, b) => b.points - a.points) - let rank = sortPlayers.findIndex((p) => p.id === player.id) + 1 - let aheadPlayer = sortPlayers[rank - 2] + let rank = sortPlayers.findIndex((p) => p.id === player.id) + 1 + let aheadPlayer = sortPlayers[rank - 2] - io.to(player.id).emit("game:status", { - name: "SHOW_RESULT", - data: { - correct: isCorrect, - message: isCorrect ? "Nice !" : "Too bad", - points: points, - myPoints: player.points, - rank, - aheadOfMe: aheadPlayer ? aheadPlayer.username : null, - }, - }) - }, 200) + io.to(player.id).emit("game:status", { + name: "SHOW_RESULT", + data: { + correct: isCorrect, + message: isCorrect ? "Nice !" : "Too bad", + points: points, + myPoints: player.points, + rank, + aheadOfMe: aheadPlayer ? aheadPlayer.username : null, + }, + }) }) - let totalParType = {} + let totalType = {} game.playersAnswer.forEach(({ answer }) => { - totalParType[answer] = (totalParType[answer] || 0) + 1 + totalType[answer] = (totalType[answer] || 0) + 1 }) // Manager @@ -88,7 +93,7 @@ export const startRound = async (game, io, socket) => { name: "SHOW_RESPONSES", data: { question: game.questions[game.currentQuestion].question, - responses: totalParType, + responses: totalType, correct: game.questions[game.currentQuestion].solution, answers: game.questions[game.currentQuestion].answers, image: game.questions[game.currentQuestion].image, diff --git a/src/components/ManagerPassword.jsx b/src/components/ManagerPassword.jsx new file mode 100644 index 0000000..5674cfe --- /dev/null +++ b/src/components/ManagerPassword.jsx @@ -0,0 +1,47 @@ +import Image from "next/image" +import { usePlayerContext } from "@/context/player" +import Form from "@/components/Form" +import Button from "@/components/Button" +import Input from "@/components/Input" +import { useEffect, useState } from "react" +import { socket } from "@/context/socket" +import logo from "@/assets/logo.svg" +import toast from "react-hot-toast" + +export default function ManagerPassword() { + const [loading, setLoading] = useState(false) + const [password, setPassword] = useState("") + + const handleCreate = () => { + socket.emit("manager:createRoom", password) + } + + useEffect(() => { + socket.on("game:errorMessage", (message) => { + toast.error(message) + }) + + return () => { + socket.off("game:errorMessage") + } + }, []) + + return ( +
+
+
+
+
+ + logo + +
+ setPassword(e.target.value)} + placeholder="Manager password" + /> + +
+
+ ) +} diff --git a/src/components/Toaster.jsx b/src/components/Toaster.jsx index 1d14263..48b0da7 100644 --- a/src/components/Toaster.jsx +++ b/src/components/Toaster.jsx @@ -1,4 +1,3 @@ -import clsx from "clsx" import { Toaster as ToasterRaw, ToastBar } from "react-hot-toast" const Toaster = () => ( diff --git a/src/components/game/join/Room.jsx b/src/components/game/join/Room.jsx index d1cd28c..1d51291 100644 --- a/src/components/game/join/Room.jsx +++ b/src/components/game/join/Room.jsx @@ -7,7 +7,6 @@ import { socket } from "@/context/socket" export default function Room() { const { player, dispatch } = usePlayerContext() - const [loading, setLoading] = useState(false) const [roomId, setRoomId] = useState("") const handleLogin = () => { @@ -25,20 +24,12 @@ export default function Room() { }, []) return ( - <> - {loading && ( -
- -
- )} - -
- setRoomId(e.target.value)} - placeholder="PIN Code here" - /> - -
- +
+ setRoomId(e.target.value)} + placeholder="PIN Code here" + /> + +
) } diff --git a/src/components/game/join/Username.jsx b/src/components/game/join/Username.jsx index b82fc58..325e3dc 100644 --- a/src/components/game/join/Username.jsx +++ b/src/components/game/join/Username.jsx @@ -9,7 +9,6 @@ import { useRouter } from "next/router" export default function Username() { const { socket } = useSocketContext() const { player, dispatch } = usePlayerContext() - const [loading, setLoading] = useState(false) const router = useRouter() const [username, setUsername] = useState("") @@ -33,20 +32,12 @@ export default function Username() { }, [username]) return ( - <> - {loading && ( -
- -
- )} - -
- setUsername(e.target.value)} - placeholder="Usernname here" - /> - -
- +
+ setUsername(e.target.value)} + placeholder="Usernname here" + /> + +
) } diff --git a/src/components/game/states/Answers.jsx b/src/components/game/states/Answers.jsx index 9191a7f..faa3149 100644 --- a/src/components/game/states/Answers.jsx +++ b/src/components/game/states/Answers.jsx @@ -1,6 +1,5 @@ import AnswerButton from "../../AnswerButton" import { useSocketContext } from "@/context/socket" -import Image from "next/image" import { useEffect, useRef, useState } from "react" import clsx from "clsx" import { ANSWERS_COLORS, ANSWERS_ICONS } from "@/constants" @@ -66,7 +65,9 @@ export default function Answers({ {question} - {/**/} + {!!image && !responses && ( + + )} {responses && (
{ + if (top.length < 3) { + setApparition(4) + } + + const interval = setInterval(() => { + if (apparition > 4) { + clearInterval(interval) + return + } + setApparition((value) => value + 1) + }, 2000) + + return () => clearInterval(interval) + }, []) + + return ( +
+

+ {subject} +

+ +
+ {top[1] && ( +
= 2 }, + )} + > +

+ {top[1].username} +

+
+

+ 2 +

+

+ {top[1].points} +

+
+
+ )} + +
= 3, + }, + )} + > +

= 4 }, + )} + > + {top[0].username} +

+
+

+ 1 +

+

+ {top[0].points} +

+
+
+ + {top[2] && ( +
= 1, + }, + )} + > +

+ {top[2].username} +

+
+

+ 3 +

+ +

+ {top[2].points} +

+
+
+ )} +
+
+ ) +} diff --git a/src/components/game/states/Question.jsx b/src/components/game/states/Question.jsx index 21b3a11..35feff3 100644 --- a/src/components/game/states/Question.jsx +++ b/src/components/game/states/Question.jsx @@ -1,19 +1,23 @@ import { useRef } from "react" -export default function Question({ data: { question } }) { +export default function Question({ data: { question, image, cooldown } }) { const barRef = useRef(null) return (
-
+

{question}

+ + {!!image && ( + + )}
) diff --git a/src/components/game/states/Room.jsx b/src/components/game/states/Room.jsx index 01bb275..29a7036 100644 --- a/src/components/game/states/Room.jsx +++ b/src/components/game/states/Room.jsx @@ -10,12 +10,17 @@ export default function Room({ data: { text, inviteCode } }) { setPlayerList([...playerList, player]) }) + socket.on("manager:removePlayer", (playerId) => { + setPlayerList(playerList.filter((p) => p.id !== playerId)) + }) + socket.on("manager:playerKicked", (playerId) => { setPlayerList(playerList.filter((p) => p.id !== playerId)) }) return () => { socket.off("manager:newPlayer") + socket.off("manager:removePlayer") socket.off("manager:playerKicked") } }, [playerList]) diff --git a/src/components/icons/Pentagon.jsx b/src/components/icons/Pentagon.jsx new file mode 100644 index 0000000..0ee5149 --- /dev/null +++ b/src/components/icons/Pentagon.jsx @@ -0,0 +1,39 @@ +export default function Pentagon({ className, fill, stroke }) { + return ( + + + + + + + {" "} + + + + + + + + {" "} + + + + + ) +} diff --git a/src/constants.js b/src/constants.js index 733c28b..073b5fa 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,15 +1,53 @@ +import Answers from "@/components/game/states/Answers" +import Leaderboard from "@/components/game/states/Leaderboard" +import Prepared from "@/components/game/states/Prepared" +import Question from "@/components/game/states/Question" +import Result from "@/components/game/states/Result" +import Room from "./components/game/states/Room" +import Podium from "./components/game/states/Podium" +import Wait from "@/components/game/states/Wait" +import Start from "@/components/game/states/Start" + import Circle from "@/components/icons/Circle" import Triangle from "@/components/icons/Triangle" import Square from "@/components/icons/Square" import Rhombus from "@/components/icons/Rhombus" -export const WEBSOCKET_URL = "http://localhost:5157" ///"https://rahoot.ralex.app" +export const WEBSOCKET_URL = "http://localhost:5157" export const ANSWERS_COLORS = [ - "bg-red-500", - "bg-blue-500", - "bg-yellow-500", - "bg-green-500", - ] - + "bg-red-500", + "bg-blue-500", + "bg-yellow-500", + "bg-green-500", +] + export const ANSWERS_ICONS = [Triangle, Rhombus, Circle, Square] + +export const GAME_STATES = { + status: { + name: "WAIT", + data: { text: "Waiting for the players" }, + }, + question: { + current: 1, + total: null, + }, +} + +export const GAME_STATE_COMPONENTS = { + SELECT_ANSWER: Answers, + SHOW_QUESTION: Question, + WAIT: Wait, + SHOW_START: Start, + SHOW_RESULT: Result, + SHOW_PREPARED: Prepared, +} + +export const GAME_STATE_COMPONENTS_MANAGER = { + ...GAME_STATE_COMPONENTS, + SHOW_ROOM: Room, + SHOW_RESPONSES: Answers, + SHOW_LEADERBOARD: Leaderboard, + FINISH: Podium, +} diff --git a/src/pages/api/hello.js b/src/pages/api/hello.js deleted file mode 100644 index 9346c88..0000000 --- a/src/pages/api/hello.js +++ /dev/null @@ -1,5 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction - -export default function handler(req, res) { - res.status(200).json({ name: "John Doe" }) -} diff --git a/src/pages/game.jsx b/src/pages/game.jsx index a8f4205..80e4e80 100644 --- a/src/pages/game.jsx +++ b/src/pages/game.jsx @@ -1,30 +1,16 @@ import GameWrapper from "@/components/game/GameWrapper" -import Answers from "@/components/game/states/Answers" -import Leaderboard from "@/components/game/states/Leaderboard" -import Prepared from "@/components/game/states/Prepared" -import Question from "@/components/game/states/Question" -import Result from "@/components/game/states/Result" -import Wait from "@/components/game/states/Wait" -import Start from "@/components/game/states/Start" +import { GAME_STATES, GAME_STATE_COMPONENTS } from "@/constants" import { usePlayerContext } from "@/context/player" import { useSocketContext } from "@/context/socket" import { useRouter } from "next/router" import { createElement, useEffect, useState } from "react" - -const gameStateComponent = { - SELECT_ANSWER: Answers, - SHOW_QUESTION: Question, - WAIT: Wait, - SHOW_START: Start, - SHOW_RESULT: Result, - SHOW_PREPARED: Prepared, -} +import toast from "react-hot-toast" export default function Game() { const router = useRouter() const { socket } = useSocketContext() - const { player } = usePlayerContext() + const { player, dispatch } = usePlayerContext() useEffect(() => { if (!player) { @@ -32,16 +18,7 @@ export default function Game() { } }, []) - const [state, setState] = useState({ - status: { - name: "WAIT", - data: { text: "Waiting for the players" }, - }, - question: { - current: 1, - total: null, - }, - }) + const [state, setState] = useState(GAME_STATES) useEffect(() => { socket.on("game:status", (status) => { @@ -55,15 +32,25 @@ export default function Game() { }) }) + socket.on("game:reset", () => { + router.replace("/") + + dispatch({ type: "LOGOUT" }) + setState(GAME_STATES) + + toast("The game has been reset by the host") + }) + return () => { socket.off("game:status") + socket.off("game:reset") } }, [state]) return ( - {gameStateComponent[state.status.name] && - createElement(gameStateComponent[state.status.name], { + {GAME_STATE_COMPONENTS[state.status.name] && + createElement(GAME_STATE_COMPONENTS[state.status.name], { data: state.status.data, })} diff --git a/src/pages/index.js b/src/pages/index.js index 54b63ef..e155838 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -13,8 +13,6 @@ import { useSocketContext } from "@/context/socket" import toast from "react-hot-toast" export default function Home() { - const [loading, setLoading] = useState(false) - const { player, dispatch } = usePlayerContext() const { socket } = useSocketContext() @@ -35,12 +33,6 @@ export default function Home() {
- {loading && ( -
- -
- )} - logo {!player ? : } diff --git a/src/pages/manager.jsx b/src/pages/manager.jsx index ae03b97..4d8db09 100644 --- a/src/pages/manager.jsx +++ b/src/pages/manager.jsx @@ -1,41 +1,21 @@ import Button from "@/components/Button" import GameWrapper from "@/components/game/GameWrapper" -import Answers from "@/components/game/states/Answers" -import Leaderboard from "@/components/game/states/Leaderboard" -import Prepared from "@/components/game/states/Prepared" -import Question from "@/components/game/states/Question" -import Room from "@/components/game/states/Room" -import Start from "@/components/game/states/Start" -import Wait from "@/components/game/states/Wait" +import ManagerPassword from "@/components/ManagerPassword" +import { GAME_STATES, GAME_STATE_COMPONENTS_MANAGER } from "@/constants" import { usePlayerContext } from "@/context/player" import { useSocketContext } from "@/context/socket" import { useRouter } from "next/router" import { createElement, useEffect, useState } from "react" -const gameStateComponent = { - SHOW_ROOM: Room, - SHOW_START: Start, - SELECT_ANSWER: Answers, - SHOW_QUESTION: Question, - WAIT: Wait, - SHOW_RESPONSES: Answers, - SHOW_LEADERBOARD: Leaderboard, - SHOW_PREPARED: Prepared, -} - export default function Manager() { const { socket } = useSocketContext() const [nextText, setNextText] = useState("Start") const [state, setState] = useState({ - created: false, + ...GAME_STATES, status: { + ...GAME_STATES.status, name: "SHOW_ROOM", - data: { text: "Waiting for the players" }, - }, - question: { - current: 1, - total: null, }, }) @@ -97,13 +77,13 @@ export default function Manager() { <> {!state.created ? (
- +
) : ( <> - {gameStateComponent[state.status.name] && - createElement(gameStateComponent[state.status.name], { + {GAME_STATE_COMPONENTS_MANAGER[state.status.name] && + createElement(GAME_STATE_COMPONENTS_MANAGER[state.status.name], { data: state.status.data, })} diff --git a/src/pages/managerold.jsx b/src/pages/managerold.jsx deleted file mode 100644 index 6d12aba..0000000 --- a/src/pages/managerold.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import GameWrapper from "@/components/game/GameWrapper" -import Answers from "@/components/game/states/Answers" -import Leaderboard from "@/components/game/states/Leaderboard" -import Question from "@/components/game/states/Question" -import Wait from "@/components/game/states/Wait" -import { useSocketContext } from "@/context/socket" -import { createElement, useEffect, useState } from "react" - -export default function Manager({ children }) { - const { socket } = useSocketContext() - - const [gameState, setGameState] = useState({ - status: { - name: "PENDING", - }, - cooldown: 0, - answers: [], - }) - - socket.on("game:status", (status) => { - setGameState({ - ...gameState, - status: status, - cooldown: 0, - }) - }) - - return ( - <> -

{JSON.stringify(gameState)}

-
-
- - - - - - - -
- - ) -}