diff --git a/socket/src/index.js b/socket/src/index.js index 2cf6ea0..3114155 100644 --- a/socket/src/index.js +++ b/socket/src/index.js @@ -17,6 +17,10 @@ io.listen(5157) io.on("connection", (socket) => { console.log(`A user connected ${socket.id}`) + socket.on("player:checkRoom", (roomId) => + Player.checkRoom(gameState, io, socket, roomId), + ) + socket.on("player:join", (player) => Player.join(gameState, io, socket, player), ) diff --git a/socket/src/roles/manager.js b/socket/src/roles/manager.js index 9014f61..ee52fb8 100644 --- a/socket/src/roles/manager.js +++ b/socket/src/roles/manager.js @@ -1,6 +1,7 @@ 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" const Manager = { createRoom: (game, io, socket) => { @@ -31,13 +32,24 @@ const Manager = { io.to(game.manager).emit("manager:playerKicked", player.id) }, - startGame: (game, io, socket) => { + startGame: async (game, io, socket) => { if (game.started || !game.room) { return } game.started = true - io.to(game.room).emit("startGame", game.room) + io.to(game.room).emit("game:status", { + name: "SHOW_START", + data: { + time: 3, + subject: "Adobe", + }, + }) + + await sleep(3) + io.to(game.room).emit("game:startCooldown") + + await cooldown(3, io, game.room) startRound(game, io, socket) }, diff --git a/socket/src/roles/player.js b/socket/src/roles/player.js index 1a23cd0..3218c65 100644 --- a/socket/src/roles/player.js +++ b/socket/src/roles/player.js @@ -1,7 +1,17 @@ import convertTimeToPoint from "../utils/convertTimeToPoint.js" -import { abortCooldown } from "../utils/round.js" +import { abortCooldown } from "../utils/cooldown.js" const Player = { + checkRoom: (game, io, socket, roomId) => { + if (!game.room || roomId !== game.room) { + io.to(socket.id).emit("game:errorMessage", "Room not found") + console.log("zaza") + return + } + + io.to(socket.id).emit("game:successRoom", roomId) + }, + join: (game, io, socket, player) => { if (!player.room || !player.room || game.started) { return diff --git a/socket/src/utils/cooldown.js b/socket/src/utils/cooldown.js new file mode 100644 index 0000000..de2dcd3 --- /dev/null +++ b/socket/src/utils/cooldown.js @@ -0,0 +1,26 @@ +let cooldownTimeout +let cooldownResolve + +export const abortCooldown = () => { + clearInterval(cooldownTimeout) + cooldownResolve() +} + +export const cooldown = (ms, io, room) => { + let count = ms - 1 + + return new Promise((resolve) => { + cooldownResolve = resolve + + cooldownTimeout = setInterval(() => { + if (!count) { + clearInterval(cooldownTimeout) + resolve() + } + io.to(room).emit("game:cooldown", count) + count -= 1 + }, 1000) + }) +} + +export const sleep = (sec) => new Promise((r) => setTimeout(r, sec * 1000)) \ No newline at end of file diff --git a/socket/src/utils/round.js b/socket/src/utils/round.js index 7aa8c50..63612ef 100644 --- a/socket/src/utils/round.js +++ b/socket/src/utils/round.js @@ -1,29 +1,4 @@ -let cooldownTimeout -let cooldownResolve - -export const abortCooldown = () => { - clearInterval(cooldownTimeout) - cooldownResolve() -} - -function cooldown(ms, io, room) { - let count = ms - 1 - - return new Promise((resolve) => { - cooldownResolve = resolve - - cooldownTimeout = setInterval(() => { - if (!count) { - clearInterval(cooldownTimeout) - resolve() - } - io.to(room).emit("game:cooldown", count) - count -= 1 - }, 1000) - }) -} - -const sleep = (sec) => new Promise((r) => setTimeout(r, sec * 1000)) +import { cooldown, sleep } from "./cooldown.js" export const startRound = async (game, io, socket) => { const question = game.questions[game.currentQuestion] diff --git a/src/components/game/GameWrapper.jsx b/src/components/game/GameWrapper.jsx index 11b1781..9b7de9e 100644 --- a/src/components/game/GameWrapper.jsx +++ b/src/components/game/GameWrapper.jsx @@ -3,7 +3,7 @@ import Button from "@/components/Button" import background from "@/assets/background.webp" import { usePlayerContext } from "@/context/player" import { useSocketContext } from "@/context/socket" -import { useState } from "react" +import { useEffect, useState } from "react" export default function GameWrapper({ children, textNext, onNext, manager }) { const { socket } = useSocketContext() @@ -11,12 +11,18 @@ export default function GameWrapper({ children, textNext, onNext, manager }) { const [questionState, setQuestionState] = useState() - socket.on("game:updateQuestion", ({ current, total }) => { - setQuestionState({ - current, - total, + useEffect(() => { + socket.on("game:updateQuestion", ({ current, total }) => { + setQuestionState({ + current, + total, + }) }) - }) + + return () => { + socket.off("game:updateQuestion") + } + }, []) return (
@@ -24,6 +30,7 @@ export default function GameWrapper({ children, textNext, onNext, manager }) { background @@ -47,7 +54,7 @@ export default function GameWrapper({ children, textNext, onNext, manager }) { {children} {!manager && ( -
+

{!!player && player.username}

{!!player && player.points} diff --git a/src/components/game/join/Room.jsx b/src/components/game/join/Room.jsx index 8cdad4c..ac89326 100644 --- a/src/components/game/join/Room.jsx +++ b/src/components/game/join/Room.jsx @@ -2,17 +2,28 @@ import { usePlayerContext } from "@/context/player" import Form from "@/components/Form" import Button from "@/components/Button" import Input from "@/components/Input" -import { useState } from "react" +import { useEffect, useMemo, useState } from "react" +import { socket } from "@/context/socket" export default function Room() { const { player, dispatch } = usePlayerContext() const [loading, setLoading] = useState(false) - const [roomId, setRoomId] = useState("207223") + const [roomId, setRoomId] = useState("") const handleLogin = () => { - dispatch({ type: "JOIN", payload: roomId }) + socket.emit("player:checkRoom", roomId) } + useEffect(() => { + socket.on("game:successRoom", (roomId) => { + dispatch({ type: "JOIN", payload: roomId }) + }) + + return () => { + socket.off("game:successRoom") + } + }, []) + return ( <> {loading && ( diff --git a/src/components/game/states/Answers.jsx b/src/components/game/states/Answers.jsx index bf2f00b..9191a7f 100644 --- a/src/components/game/states/Answers.jsx +++ b/src/components/game/states/Answers.jsx @@ -1,21 +1,9 @@ -import Circle from "@/components/icons/Circle" -import Triangle from "@/components/icons/Triangle" -import Square from "@/components/icons/Square" -import Rhombus from "@/components/icons/Rhombus" import AnswerButton from "../../AnswerButton" import { useSocketContext } from "@/context/socket" import Image from "next/image" import { useEffect, useRef, useState } from "react" import clsx from "clsx" - -const answersColors = [ - "bg-red-500", - "bg-blue-500", - "bg-yellow-500", - "bg-green-500", -] - -const answersIcons = [Triangle, Rhombus, Circle, Square] +import { ANSWERS_COLORS, ANSWERS_ICONS } from "@/constants" const calculatePercentages = (objectResponses) => { const keys = Object.keys(objectResponses) @@ -36,8 +24,6 @@ const calculatePercentages = (objectResponses) => { result[key] = ((objectResponses[key] / totalSum) * 100).toFixed() + "%" }) - console.log(result) - return result } @@ -58,13 +44,20 @@ export default function Answers({ setPercentages(calculatePercentages(responses)) }, [responses]) - socket.on("game:cooldown", (sec) => { - setCooldown(sec) - }) + useEffect(() => { + socket.on("game:cooldown", (sec) => { + setCooldown(sec) + }) - socket.on("game:playerAnswer", (count) => { - setTotalAnswer(count) - }) + socket.on("game:playerAnswer", (count) => { + setTotalAnswer(count) + }) + + return () => { + socket.off("game:cooldown") + socket.off("game:playerAnswer") + } + }, []) return (
@@ -84,7 +77,7 @@ export default function Answers({ key={key} className={clsx( "flex flex-col justify-end self-end overflow-hidden rounded-md", - answersColors[key], + ANSWERS_COLORS[key], )} style={{ height: percentages[key] }} > @@ -115,10 +108,10 @@ export default function Answers({ {answers.map((answer, key) => ( socket.emit("player:selectedAnswer", key)} > {answer} diff --git a/src/components/game/states/Prepared.jsx b/src/components/game/states/Prepared.jsx index 04c2359..4862f5c 100644 --- a/src/components/game/states/Prepared.jsx +++ b/src/components/game/states/Prepared.jsx @@ -1,27 +1,25 @@ -import Circle from "@/components/icons/Circle" -import Rhombus from "@/components/icons/Rhombus" -import Square from "@/components/icons/Square" -import Triangle from "@/components/icons/Triangle" +import { ANSWERS_COLORS, ANSWERS_ICONS } from "@/constants" +import clsx from "clsx" +import { createElement } from "react" export default function Prepared({ data: { totalAnswers, questionNumber } }) { return (
-

+

Question #{questionNumber}

-
-
- -
-
- -
-
- -
-
- -
+
+ {[...Array(totalAnswers)].map((_, key) => ( +
+ {createElement(ANSWERS_ICONS[key], { className: "h-10 md:h-14" })} +
+ ))}
) diff --git a/src/components/game/states/Question.jsx b/src/components/game/states/Question.jsx index 3d67765..21b3a11 100644 --- a/src/components/game/states/Question.jsx +++ b/src/components/game/states/Question.jsx @@ -6,7 +6,7 @@ export default function Question({ data: { question } }) { return (
-

+

{question}

diff --git a/src/components/game/states/Room.jsx b/src/components/game/states/Room.jsx new file mode 100644 index 0000000..c46b1bf --- /dev/null +++ b/src/components/game/states/Room.jsx @@ -0,0 +1,43 @@ +import { useSocketContext } from "@/context/socket" +import { useEffect, useState } from "react" + +export default function Room({ data: { text, inviteCode } }) { + const { socket } = useSocketContext() + const [playerList, setPlayerList] = useState([]) + + useEffect(() => { + socket.on("manager:newPlayer", (player) => { + setPlayerList([...playerList, player]) + }) + + return () => { + socket.off("manager:newPlayer") + } + }, [playerList]) + + return ( +
+
+ {inviteCode} +
+ +

+ {text} +

+ +
+ {playerList.map((player) => ( +
socket.emit("manager:kickPlayer", player.id)} + > + + {player.username} + +
+ ))} +
+
+ ) +} diff --git a/src/components/game/states/Start.jsx b/src/components/game/states/Start.jsx index 49c6829..8ee7b81 100644 --- a/src/components/game/states/Start.jsx +++ b/src/components/game/states/Start.jsx @@ -1,37 +1,48 @@ import { useSocketContext } from "@/context/socket" -import { useState } from "react" +import clsx from "clsx" +import { useEffect, useState } from "react" -export default function Start({ data: { text, inviteCode } }) { +export default function Start({ data: { time, subject } }) { const { socket } = useSocketContext() - const [playerList, setPlayerList] = useState([]) + const [showTitle, setShowTitle] = useState(true) + const [cooldown, setCooldown] = useState(time) - socket.on("manager:newPlayer", (player) => { - setPlayerList([...playerList, player]) - }) + useEffect(() => { + socket.on("game:startCooldown", () => { + setShowTitle(false) + }) + + socket.on("game:cooldown", (sec) => { + setCooldown(sec) + }) + + return () => { + socket.off("game:startCooldown") + socket.off("game:cooldown") + } + }, []) return ( -
-
- {inviteCode} -
- -

- {text} -

- -
- {playerList.map((player) => ( +
+ {showTitle ? ( +

+ {subject} +

+ ) : ( + <>
socket.emit("manager:kickPlayer", player.id)} - > - - {player.username} - -
- ))} -
+ className={clsx( + `anim-show aspect-square h-32 bg-primary transition-all md:h-60`, + )} + style={{ + transform: `rotate(${45 * (time - cooldown)}deg)`, + }} + >
+ + {cooldown} + + + )}
) } diff --git a/src/components/game/states/Wait.jsx b/src/components/game/states/Wait.jsx index cfde245..d37ea74 100644 --- a/src/components/game/states/Wait.jsx +++ b/src/components/game/states/Wait.jsx @@ -4,7 +4,7 @@ export default function Wait({ data: { text } }) { return (
-

+

{text}

diff --git a/src/constants.js b/src/constants.js index af3d000..733c28b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1 +1,15 @@ +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 ANSWERS_COLORS = [ + "bg-red-500", + "bg-blue-500", + "bg-yellow-500", + "bg-green-500", + ] + +export const ANSWERS_ICONS = [Triangle, Rhombus, Circle, Square] diff --git a/src/pages/game.jsx b/src/pages/game.jsx index 4e361c3..6dd24e3 100644 --- a/src/pages/game.jsx +++ b/src/pages/game.jsx @@ -5,15 +5,17 @@ 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 { usePlayerContext } from "@/context/player" import { useSocketContext } from "@/context/socket" import { useRouter } from "next/router" -import { createElement, useState } from "react" +import { createElement, useMemo, useState } from "react" const gameStateComponent = { SELECT_ANSWER: Answers, SHOW_QUESTION: Question, WAIT: Wait, + SHOW_START: Start, SHOW_RESULT: Result, SHOW_PREPARED: Prepared, } @@ -40,16 +42,22 @@ export default function Game() { }, }) - socket.on("game:status", (status) => { - setState({ - ...state, - status: status, - question: { - ...state.question, - current: status.question, - }, + useMemo(() => { + socket.on("game:status", (status) => { + setState({ + ...state, + status: status, + question: { + ...state.question, + current: status.question, + }, + }) }) - }) + + return () => { + socket.off("game:status") + } + }, [state]) return ( diff --git a/src/pages/index.js b/src/pages/index.js index cdb1095..e233b43 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -4,16 +4,28 @@ import Form from "@/components/Form" import Button from "@/components/Button" import Input from "@/components/Input" import logo from "@/assets/logo.svg" -import { useState } from "react" +import { useEffect, useMemo, useState } from "react" import Loader from "@/components/Loader" import { usePlayerContext } from "@/context/player" import Room from "@/components/game/join/Room" import Username from "@/components/game/join/Username" +import { useSocketContext } from "@/context/socket" export default function Home() { const [loading, setLoading] = useState(false) - const { player } = usePlayerContext() + const { player, dispatch } = usePlayerContext() + const { socket } = useSocketContext() + + useMemo(() => { + socket.on("game:errorMessage", (message) => { + console.log(message) + }) + + return () => { + socket.off("game:errorMessage") + } + }, []) return (
@@ -28,7 +40,7 @@ export default function Home() { )} - + logo {!player ? : }
diff --git a/src/pages/manager.jsx b/src/pages/manager.jsx index 247b47d..ae03b97 100644 --- a/src/pages/manager.jsx +++ b/src/pages/manager.jsx @@ -4,14 +4,16 @@ 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 { usePlayerContext } from "@/context/player" import { useSocketContext } from "@/context/socket" import { useRouter } from "next/router" -import { createElement, useState } from "react" +import { createElement, useEffect, useState } from "react" const gameStateComponent = { + SHOW_ROOM: Room, SHOW_START: Start, SELECT_ANSWER: Answers, SHOW_QUESTION: Question, @@ -23,13 +25,12 @@ const gameStateComponent = { export default function Manager() { const { socket } = useSocketContext() - const { player } = usePlayerContext() const [nextText, setNextText] = useState("Start") const [state, setState] = useState({ created: false, status: { - name: "SHOW_START", + name: "SHOW_ROOM", data: { text: "Waiting for the players" }, }, question: { @@ -38,30 +39,37 @@ export default function Manager() { }, }) - socket.on("game:status", (status) => { - setState({ - ...state, - status: status, - question: { - ...state.question, - current: status.question, - }, - }) - }) - - socket.on("manager:inviteCode", (inviteCode) => { - setState({ - ...state, - created: true, - status: { - ...state.status, - data: { - ...state.status.data, - inviteCode: inviteCode, + useEffect(() => { + socket.on("game:status", (status) => { + setState({ + ...state, + status: status, + question: { + ...state.question, + current: status.question, }, - }, + }) }) - }) + + socket.on("manager:inviteCode", (inviteCode) => { + setState({ + ...state, + created: true, + status: { + ...state.status, + data: { + ...state.status.data, + inviteCode: inviteCode, + }, + }, + }) + }) + + return () => { + socket.off("game:status") + socket.off("manager:inviteCode") + } + }, [state]) const handleCreate = () => { socket.emit("manager:createRoom") @@ -71,7 +79,7 @@ export default function Manager() { setNextText("Skip") switch (state.status.name) { - case "SHOW_START": + case "SHOW_ROOM": socket.emit("manager:startGame") break diff --git a/src/styles/globals.css b/src/styles/globals.css index 60cc50a..d014b46 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -48,6 +48,7 @@ } .anim-quizz .button { + box-shadow: rgba(0, 0, 0, 0.25) -4px -4px inset; animation: quizzButton 0.8s ease-out; } @@ -94,7 +95,14 @@ 0% { transform: scale(0) perspective(1200px) rotateY(-60deg) rotateX(60deg) translateZ(100px); - box-shadow: 40px 10px 0 rgba(20, 24, 29, 1); + } + 60% { + transform: scale(1) perspective(1200px) rotateY(-15deg) rotateX(15deg) + translateZ(100px); + } + 80% { + transform: scale(0.8) perspective(1200px) rotateY(-15deg) rotateX(15deg) + translateZ(100px); } 100% { transform: scale(1) perspective(1200px) rotateY(-15deg) rotateX(15deg) @@ -106,6 +114,12 @@ 0% { transform: scale(0); } + 60% { + transform: scale(1); + } + 80% { + transform: scale(0.8); + } 100% { transform: scale(1); } diff --git a/tailwind.config.js b/tailwind.config.js index f2c382e..0bd9511 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -13,6 +13,7 @@ module.exports = { }, }, }, - safelist: ["grid-cols-4", "grid-cols-3", "grid-cols-2"], + safelist: ["grid-cols-4", "grid-cols-3", "grid-cols-2", { + pattern: /bg-(red|blue|yellow|green)/}], plugins: [], }