added animated move timer
This commit is contained in:
parent
e702b912f7
commit
6fef82f36a
@ -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));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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));
|
||||
});
|
||||
|
||||
@ -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) {
|
||||
|
||||
27
package-lock.json
generated
27
package-lock.json
generated
@ -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": {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -40,9 +40,6 @@ function App() {
|
||||
<Gameboard />
|
||||
</PlayerDataContext.Provider>
|
||||
) : null}
|
||||
<a href='https://www.flaticon.com/free-icons/hand' title='hand icons'>
|
||||
Hand icons created by berkahicon - Flaticon
|
||||
</a>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
|
||||
@ -67,8 +67,8 @@ const Gameboard = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{players ? (
|
||||
<>
|
||||
{(players[0] && !started) || (time && started) ? (
|
||||
<div className='container'>
|
||||
<Navbar
|
||||
players={players}
|
||||
started={started}
|
||||
@ -80,7 +80,7 @@ const Gameboard = () => {
|
||||
rolledNumberCallback={rolledNumberCallback}
|
||||
/>
|
||||
<Map pawns={pawns} nowMoving={nowMoving} rolledNumber={rolledNumber} />
|
||||
</>
|
||||
</div>
|
||||
) : (
|
||||
<ReactLoading type='spinningBubbles' color='white' height={667} width={375} />
|
||||
)}
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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 (
|
||||
<div className='navbar-container'>
|
||||
<>
|
||||
{players.map((player, index) => (
|
||||
<div className={`player-container ${colors[index]}`} key={index}>
|
||||
<NameContainer player={player} time={time} />
|
||||
@ -18,10 +20,10 @@ const Navbar = ({ players, started, time, isReady, rolledNumber, nowMoving, roll
|
||||
color={colors[index]}
|
||||
rolledNumberCallback={rolledNumberCallback}
|
||||
/>
|
||||
{context.color !== player.color || started ? null : <ReadyButton isReady={isReady} />}
|
||||
</div>
|
||||
))}
|
||||
{started ? null : <ReadyButton isReady={isReady} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Navbar;
|
||||
|
||||
25
src/components/navbar-components/AnimatedOverlay.jsx
Normal file
25
src/components/navbar-components/AnimatedOverlay.jsx
Normal file
@ -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 (
|
||||
<CSSTransition
|
||||
in={true}
|
||||
timeout={0}
|
||||
classNames='overlay'
|
||||
style={{ 'animation-delay': `-${animationDelay}s` }}
|
||||
unmountOnExit
|
||||
>
|
||||
<div className='overlay'></div>
|
||||
</CSSTransition>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnimatedOverlay;
|
||||
@ -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 (
|
||||
<div
|
||||
className='name-container'
|
||||
style={player.ready ? { backgroundColor: player.color } : { backgroundColor: 'lightgrey' }}
|
||||
>
|
||||
<p>{player.name}</p>
|
||||
{player.nowMoving ? <div className='timer'> {remainingTime} </div> : null}
|
||||
{player.nowMoving ? <AnimatedOverlay time={time} /> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
|
||||
63
src/components/navbar-components/TimerAnimation.js
Normal file
63
src/components/navbar-components/TimerAnimation.js
Normal file
@ -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
|
||||
);
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user