From 6fef82f36afb35d5fa249eeb280082e658034319 Mon Sep 17 00:00:00 2001 From: Wenszel Date: Tue, 21 Nov 2023 21:16:07 +0100 Subject: [PATCH] added animated move timer --- backend/handlers/playerHandler.js | 1 + backend/handlers/roomHandler.js | 2 + backend/schemas/room.js | 1 + package-lock.json | 27 ++++---- package.json | 1 + src/App.js | 3 - src/components/Gameboard.jsx | 6 +- src/components/Navbar.css | 69 ++++++++++++------- src/components/Navbar.jsx | 10 +-- .../navbar-components/AnimatedOverlay.jsx | 25 +++++++ .../navbar-components/NameContainer.jsx | 42 +---------- .../navbar-components/ReadyButton.jsx | 2 + .../navbar-components/TimerAnimation.js | 63 +++++++++++++++++ src/index.css | 47 ++++++++++--- 14 files changed, 205 insertions(+), 94 deletions(-) create mode 100644 src/components/navbar-components/AnimatedOverlay.jsx create mode 100644 src/components/navbar-components/TimerAnimation.js diff --git a/backend/handlers/playerHandler.js b/backend/handlers/playerHandler.js index 7640f16..fe8b453 100644 --- a/backend/handlers/playerHandler.js +++ b/backend/handlers/playerHandler.js @@ -115,6 +115,7 @@ module.exports = (io, socket) => { socket.join(room._id.toString()); // Sending data to the user, after which player will be redirected to the game socket.emit('player:data', JSON.stringify(req.session)); + socket.emit('room:data', JSON.stringify(updatedRoom)); }); }); } diff --git a/backend/handlers/roomHandler.js b/backend/handlers/roomHandler.js index 962bea7..9a4817c 100644 --- a/backend/handlers/roomHandler.js +++ b/backend/handlers/roomHandler.js @@ -27,6 +27,8 @@ module.exports = (io, socket) => { room.players[index + 1].nowMoving = true; } room.nextMoveTime = Date.now() + 15000; + if (this.timeoutID) clearTimeout(this.timeoutID); + this.timeoutID = null; RoomModel.findOneAndUpdate({ _id: req.session.roomId }, room, function (err, updatedRoom) { io.to(req.session.roomId).emit('room:data', JSON.stringify(updatedRoom)); }); diff --git a/backend/schemas/room.js b/backend/schemas/room.js index c6118e4..d6887e4 100644 --- a/backend/schemas/room.js +++ b/backend/schemas/room.js @@ -36,6 +36,7 @@ RoomSchema.methods.changeMovingPlayer = function () { this.nextMoveTime = Date.now() + 15000; this.rolledNumber = null; if (this.timeoutID) clearTimeout(this.timeoutID); + this.timeoutID = null; }; RoomSchema.methods.movePawn = function (pawn) { diff --git a/package-lock.json b/package-lock.json index 12ed4c5..b39a8bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "react-loading": "^2.0.3", "react-router-dom": "^5.2.0", "react-scripts": "^5.0.1", + "react-transition-group": "^4.4.5", "socket.io": "^4.5.1", "socket.io-client": "^4.5.1", "web-vitals": "^1.1.0" @@ -16735,9 +16736,9 @@ } }, "node_modules/react-transition-group": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", - "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -18664,16 +18665,16 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14.17" + "node": ">=4.2.0" } }, "node_modules/unbox-primitive": { @@ -31757,9 +31758,9 @@ } }, "react-transition-group": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", - "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "requires": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -33178,9 +33179,9 @@ } }, "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true }, "unbox-primitive": { diff --git a/package.json b/package.json index 354a129..a3b0784 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "react-loading": "^2.0.3", "react-router-dom": "^5.2.0", "react-scripts": "^5.0.1", + "react-transition-group": "^4.4.5", "socket.io": "^4.5.1", "socket.io-client": "^4.5.1", "web-vitals": "^1.1.0" diff --git a/src/App.js b/src/App.js index e69fe10..fa208b4 100644 --- a/src/App.js +++ b/src/App.js @@ -40,9 +40,6 @@ function App() { ) : null} - - Hand icons created by berkahicon - Flaticon - diff --git a/src/components/Gameboard.jsx b/src/components/Gameboard.jsx index 000f284..8847ba9 100644 --- a/src/components/Gameboard.jsx +++ b/src/components/Gameboard.jsx @@ -67,8 +67,8 @@ const Gameboard = () => { return ( <> - {players ? ( - <> + {(players[0] && !started) || (time && started) ? ( +
{ rolledNumberCallback={rolledNumberCallback} /> - +
) : ( )} diff --git a/src/components/Navbar.css b/src/components/Navbar.css index 5bd52da..1358d4b 100644 --- a/src/components/Navbar.css +++ b/src/components/Navbar.css @@ -1,26 +1,3 @@ -.red { - position: relative; - left: 176px; -} -.yellow { - position: relative; - flex-direction: row-reverse; - right: 170px; -} -.blue { - position: relative; - right: 28px; - top: 538px; -} -.green { - position: relative; - flex-direction: row-reverse; - top: 538px; - left: 36px; -} -.player-container { - display: flex; -} .dice-container { margin-left: 20px; margin-right: 20px; @@ -30,3 +7,49 @@ .roll { cursor: pointer; } +.ready-container { + display: flex; + width: 300px; + justify-content: center; + align-items: center; + flex-direction: column; + flex-flow: row-reverse; + background-color: grey; + border-radius: 10px; + border: 2px solid white; +} +.ready-container > label { + margin-left: 10px; + margin-right: 10px; + width: 100px; + color: white; +} +.player-container { + display: flex; + align-items: center; + flex-direction: row; + width: 100%; +} +.red { + margin-bottom: 50px; + grid-column: 1; + grid-row: 1; +} +.yellow { + margin-bottom: 50px; + flex-flow: row-reverse; + grid-column: 2; + grid-row: 1; +} +.blue { + margin-top: 50px; + grid-column: 1; + grid-row: 4; +} +.green { + margin-top: 50px; + flex-flow: row-reverse; + grid-column: 2; + grid-row: 4; +} +/* Styl dla overlay */ diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 4a27c2d..0c67c8a 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -3,11 +3,13 @@ import Dice from './game-board-components/Dice'; import NameContainer from './navbar-components/NameContainer'; import ReadyButton from './navbar-components/ReadyButton'; import './Navbar.css'; - +import { useContext } from 'react'; +import { PlayerDataContext } from '../App'; const Navbar = ({ players, started, time, isReady, rolledNumber, nowMoving, rolledNumberCallback, movingPlayer }) => { + const context = useContext(PlayerDataContext); const colors = ['red', 'blue', 'green', 'yellow']; return ( -
+ <> {players.map((player, index) => (
@@ -18,10 +20,10 @@ const Navbar = ({ players, started, time, isReady, rolledNumber, nowMoving, roll color={colors[index]} rolledNumberCallback={rolledNumberCallback} /> + {context.color !== player.color || started ? null : }
))} - {started ? null : } -
+ ); }; export default Navbar; diff --git a/src/components/navbar-components/AnimatedOverlay.jsx b/src/components/navbar-components/AnimatedOverlay.jsx new file mode 100644 index 0000000..82b1507 --- /dev/null +++ b/src/components/navbar-components/AnimatedOverlay.jsx @@ -0,0 +1,25 @@ +import React, { useState, useEffect } from 'react'; +import { CSSTransition } from 'react-transition-group'; +import './TimerAnimation.js'; + +const AnimatedOverlay = ({ time }) => { + const [animationDelay, setAnimationDelay] = useState(); + + useEffect(() => { + setAnimationDelay(15 - Math.ceil((time - Date.now()) / 1000)); + }, [time]); + + return ( + +
+
+ ); +}; + +export default AnimatedOverlay; diff --git a/src/components/navbar-components/NameContainer.jsx b/src/components/navbar-components/NameContainer.jsx index 53b17ff..b0a68cc 100644 --- a/src/components/navbar-components/NameContainer.jsx +++ b/src/components/navbar-components/NameContainer.jsx @@ -1,51 +1,15 @@ -import React, { useState, useEffect, useContext } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; -import { SocketContext } from '../../App'; - -/* - Component responsible for: - - displaying the player's name - - informing players about the readiness of other players by changing the color of container from gray to the player's color - - counting time to the end of the move - - Props: - - player (object): - The player to whom the container belongs - Player's properties used in this component: - - ready (boolean): - is the player ready for the start of the game, if so, change color from gray to the player's color - when the game is started all players are ready not matter if they clicked ready button before - - nowMoving (boolean) is this player move now, if true display timer - - name (string) - - time (number) - time remaining until the move is made in milliseconds -*/ +import AnimatedOverlay from './AnimatedOverlay'; const NameContainer = ({ player, time }) => { - const [remainingTime, setRemainingTime] = useState(); - const socket = useContext(SocketContext); - - // Function responsible for counting down to the end of time every second - const countdown = () => { - setRemainingTime(Math.ceil((time - Date.now()) / 1000)); - }; - - useEffect(() => { - // Starts the countdown from the beginning if the server returned information about skipping the turn - socket.on('game:skip', () => { - setRemainingTime(15); - }); - setRemainingTime(Math.ceil((time - Date.now()) / 1000)); - const interval = setInterval(countdown, 1000); - return () => clearInterval(interval); - }, [countdown]); - return (

{player.name}

- {player.nowMoving ?
{remainingTime}
: null} + {player.nowMoving ? : null}
); }; diff --git a/src/components/navbar-components/ReadyButton.jsx b/src/components/navbar-components/ReadyButton.jsx index 62ea815..6addc94 100644 --- a/src/components/navbar-components/ReadyButton.jsx +++ b/src/components/navbar-components/ReadyButton.jsx @@ -1,6 +1,8 @@ import React, { useState, useContext, useEffect } from 'react'; import { SocketContext } from '../../App'; import Switch from '@material-ui/core/Switch'; +import '../Navbar.css'; +import './TimerAnimation'; const ReadyButton = ({ isReady }) => { const socket = useContext(SocketContext); diff --git a/src/components/navbar-components/TimerAnimation.js b/src/components/navbar-components/TimerAnimation.js new file mode 100644 index 0000000..f8b44e1 --- /dev/null +++ b/src/components/navbar-components/TimerAnimation.js @@ -0,0 +1,63 @@ +const keyframes = []; +const steps = 86; + +let count = 0; +let s = 'polygon(50% 50%, 50% 0%, 50% 0%'; + +for (let i = 50; i <= 100; i += 5) { + s += `, ${i}% 0%`; + handle(); +} +for (let i = 0; i <= 100; i += 5) { + s += `, 100% ${i}%`; + handle(); +} +for (let i = 100; i >= 0; i -= 5) { + s += `, ${i}% 100%`; + handle(); +} + +for (let i = 100; i >= 0; i -= 5) { + s += `, 0% ${i}%`; + handle(); +} +for (let i = 0; i <= 50; i += 5) { + s += `, ${i}% 0%`; + handle(); +} + +function handle() { + const percentage = (count / steps) * 100; + let step; + if (percentage <= 75 && percentage >= 73) { + step = `${percentage}% { + background-color: orange; + clip-path: ${s}) + }`; + } else if (percentage > 97.5 && percentage < 100) { + step = `${percentage}% { + background-color: red; + clip-path: ${s}) + }`; + } else if (percentage > 0 && percentage < 2.5) { + step = `${percentage}% { + background-color: green; + clip-path: ${s}) + }`; + } else { + step = `${percentage}% { + clip-path: ${s}) + }`; + } + keyframes.push(step); + count++; +} + +const animation = document.styleSheets[0].insertRule( + ` + @keyframes timerAnimation { + ${keyframes.join('\n')} + } +`, + document.styleSheets[0].cssRules.length +); diff --git a/src/index.css b/src/index.css index 97dff06..1346031 100644 --- a/src/index.css +++ b/src/index.css @@ -6,13 +6,16 @@ body { rgba(0, 138, 255, 1) 16%, rgba(9, 9, 121, 1) 81% ); + overflow: hidden; +} +#root { display: flex; + justify-content: center; flex-direction: column; - align-items: center; -} -.canvas-container { - margin: 10px; + height: 100vh; + width: 100vw; } + canvas { border-radius: 15px; border: 2px solid black; @@ -25,12 +28,14 @@ canvas { display: flex; flex-direction: row; } -.navbar-container > div { - margin-right: 10px; -} + .name-container { - width: 100px; - height: 50px; + position: relative; + min-width: 100px; + min-height: 50px; + display: flex; + justify-content: center; + align-items: center; border: 2px solid white; border-radius: 5px; color: white; @@ -49,9 +54,33 @@ canvas { height: 20px; border-radius: 5px; } +.overlay { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + opacity: 0.9; + animation: timerAnimation 15s linear infinite; + transition-duration: 15s; +} #root { display: flex; flex-direction: column; align-items: center; justify-content: center; } + +.container { + display: grid; + align-items: center; + justify-items: center; + grid-template-columns: 230px 230px; + grid-template-rows: 50px 250px 250px 50px; +} + +.canvas-container { + place-self: center; + grid-column: 1 / span 2; + grid-row: 2 / span 2; +}