diff --git a/README.md b/README.md index 4bb9ca7..8b2cb32 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,9 @@ node server.js - Express-session - MongoDB, Mongoose - MongoDB sessions store +- SocketIO ### Frontend - React - Axios - Material UI -- Canvas -## ToDo -- Redis -- SocketIO -- Add more game logic \ No newline at end of file +- Canvas \ No newline at end of file diff --git a/backend/handlers/gameHandler.js b/backend/handlers/gameHandler.js new file mode 100644 index 0000000..68b8676 --- /dev/null +++ b/backend/handlers/gameHandler.js @@ -0,0 +1,56 @@ +const RoomModel = require('../schemas/room'); +const { getPositionAfterMove } = require('../utils/functions'); +/* + Function handle all requests + file constains functions: + 1. roll + 2. move +*/ +module.exports = (io, socket) => { + const req = socket.request; + const roll = () => { + const rolledNumber = Math.ceil(Math.random() * 6); + req.session.reload(err => { + if (err) return socket.disconnect(); + // Saving session data + req.session.rolledNumber = rolledNumber; + req.session.save(); + socket.emit('game:roll', rolledNumber); + }); + }; + + const move = ({ pawnId }) => { + RoomModel.findOne({ _id: req.session.roomId }, function (err, room) { + if (!room) return err; + const pawnIndex = room.pawns.findIndex(pawn => pawn._id == pawnId); + room.pawns[pawnIndex].position = getPositionAfterMove(req.session.rolledNumber, room.pawns[pawnIndex]); + const pawnsOnPos = room.pawns.filter(pawn => pawn.position == room.pawns[pawnIndex].position); + pawnsOnPos.forEach(pawn => { + if (pawn.color !== req.session.color) { + const index = room.pawns.findIndex(i => i._id === pawn._id); + room.pawns[index].position = room.pawns[index].basePos; + } + }); + // Updating moving player + const playerIndex = room.players.findIndex(player => player.nowMoving === true); + const roomSize = room.players.length; + room.players[playerIndex].nowMoving = false; + if (playerIndex + 1 === roomSize) { + room.players[0].nowMoving = true; + } else { + room.players[playerIndex + 1].nowMoving = true; + } + // Updating timer + room.nextMoveTime = Date.now() + 15000; + // Pushing above data to database + RoomModel.findOneAndUpdate({ _id: req.session.roomId }, room, (err, updatedRoom) => { + if (!updatedRoom) return err; + io.to(req.session.roomId).emit('room:data', JSON.stringify(updatedRoom)); + socket.emit('room:move'); + }); + }); + }; + + socket.on('game:roll', roll); + socket.on('game:move', move); +}; diff --git a/backend/routes/game.js b/backend/routes/game.js index d40cfc3..a0168f1 100644 --- a/backend/routes/game.js +++ b/backend/routes/game.js @@ -42,64 +42,6 @@ router.post('/move', function (req, res){ }); }); -function getPosition(rolledNumber, pawn){ - const { position, color } = pawn; - switch (color){ - case 'red': - if(pawn.position + rolledNumber <= 73){ - if(position >= 0 && position <= 3) { - return 16; - }else if(position <= 66 && position + rolledNumber >= 67){ - return position + rolledNumber + 1; - }else{ - return position + rolledNumber; - } - }else{ - return position; - } - case 'blue': - if(pawn.position + rolledNumber <= 79){ - if(position >= 4 && position <= 7){ - return 55; - }else if(position <= 67 && position + rolledNumber > 67){ - return position + rolledNumber - 52; - }else if(position <= 53 && position + rolledNumber >= 54){ - return position + rolledNumber + 20; - }else{ - return position + rolledNumber; - } - }else{ - return position; - } - case 'green': - if(pawn.position + rolledNumber <= 85){ - if(position >= 8 && position <= 11){ - return 42; - }else if(position <= 67 && position + rolledNumber > 67){ - return position + rolledNumber - 52; - }else if(position <= 40 && position + rolledNumber >= 41){ - return position + rolledNumber + 39; - }else{ - return position + rolledNumber; - } - }else{ - return position; - } - case 'yellow': - if(pawn.position + rolledNumber <= 85){ - if(position >= 12 && position <= 15){ - return 29; - }else if(position <= 67 && position + rolledNumber > 67){ - return position + rolledNumber - 52; - }else if(position <= 27 && position + rolledNumber >= 28){ - return position + rolledNumber + 58; - }else{ - return position + rolledNumber; - } - }else{ - return position; - } - } -}; + module.exports = router; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index e4b8d6d..1c0bb69 100644 --- a/backend/server.js +++ b/backend/server.js @@ -4,6 +4,7 @@ const cookieParser = require('cookie-parser'); const { sessionMiddleware, wrap } = require('./controllers/serverController'); const registerPlayerHandlers = require('./handlers/playerHandler'); const registerRoomHandlers = require('./handlers/roomHandler'); +const registerGameHandlers = require('./handlers/gameHandler'); const PORT = 8080; const mongoose = require('mongoose'); const CONNECTION_URI = require('./credentials.js'); @@ -75,6 +76,7 @@ io.use(wrap(sessionMiddleware)); io.on('connection', socket => { registerPlayerHandlers(io, socket); registerRoomHandlers(io, socket); + registerGameHandlers(io, socket); if (socket.request.session.roomId) { socket.join(socket.request.session.roomId); socket.emit('player:data', JSON.stringify(socket.request.session)); @@ -82,13 +84,6 @@ io.on('connection', socket => { } }); -//ROUTES CONFIG -const playerRoutes = require('./routes/player'); -const gameRoutes = require('./routes/game'); - -app.use('/player', playerRoutes); -app.use('/game', gameRoutes); - if (process.env.NODE_ENV === 'production') { app.use(express.static('/app/build')); app.get('/', (req, res) => { diff --git a/backend/utils/functions.js b/backend/utils/functions.js index 234ca59..a799e05 100644 --- a/backend/utils/functions.js +++ b/backend/utils/functions.js @@ -1,16 +1,75 @@ -const { colors } = require("./constants"); +const { colors } = require('./constants'); function getStartPositions() { - const startPositions = []; - for (let i = 0; i < 16; i++) { - let pawn = {}; - pawn.basePos = i; - pawn.position = i; - if (i < 4) pawn.color = colors[0]; - else if (i < 8) pawn.color = colors[1]; - else if (i < 12) pawn.color = colors[2]; - else if (i < 16) pawn.color = colors[3]; - startPositions.push(pawn); - } - return startPositions; + const startPositions = []; + for (let i = 0; i < 16; i++) { + let pawn = {}; + pawn.basePos = i; + pawn.position = i; + if (i < 4) pawn.color = colors[0]; + else if (i < 8) pawn.color = colors[1]; + else if (i < 12) pawn.color = colors[2]; + else if (i < 16) pawn.color = colors[3]; + startPositions.push(pawn); + } + return startPositions; } -module.exports = { getStartPositions }; +function getPositionAfterMove(rolledNumber, pawn) { + const { position, color } = pawn; + switch (color) { + case 'red': + if (pawn.position + rolledNumber <= 73) { + if (position >= 0 && position <= 3) { + return 16; + } else if (position <= 66 && position + rolledNumber >= 67) { + return position + rolledNumber + 1; + } else { + return position + rolledNumber; + } + } else { + return position; + } + case 'blue': + if (pawn.position + rolledNumber <= 79) { + if (position >= 4 && position <= 7) { + return 55; + } else if (position <= 67 && position + rolledNumber > 67) { + return position + rolledNumber - 52; + } else if (position <= 53 && position + rolledNumber >= 54) { + return position + rolledNumber + 20; + } else { + return position + rolledNumber; + } + } else { + return position; + } + case 'green': + if (pawn.position + rolledNumber <= 85) { + if (position >= 8 && position <= 11) { + return 42; + } else if (position <= 67 && position + rolledNumber > 67) { + return position + rolledNumber - 52; + } else if (position <= 40 && position + rolledNumber >= 41) { + return position + rolledNumber + 39; + } else { + return position + rolledNumber; + } + } else { + return position; + } + case 'yellow': + if (pawn.position + rolledNumber <= 85) { + if (position >= 12 && position <= 15) { + return 29; + } else if (position <= 67 && position + rolledNumber > 67) { + return position + rolledNumber - 52; + } else if (position <= 27 && position + rolledNumber >= 28) { + return position + rolledNumber + 58; + } else { + return position + rolledNumber; + } + } else { + return position; + } + } +} +module.exports = { getStartPositions, getPositionAfterMove }; diff --git a/src/components/Gameboard.jsx b/src/components/Gameboard.jsx index 8539781..ca22bcc 100644 --- a/src/components/Gameboard.jsx +++ b/src/components/Gameboard.jsx @@ -1,6 +1,5 @@ import React, { useState, useEffect, useContext, useCallback } from 'react'; import { PlayerDataContext, SocketContext } from '../App'; -import axios from 'axios'; import Map from './game-board-components/Map'; import Dice from './game-board-components/Dice'; import Navbar from './Navbar'; diff --git a/src/components/game-board-components/Dice.jsx b/src/components/game-board-components/Dice.jsx index e9f4e9e..3b5d011 100644 --- a/src/components/game-board-components/Dice.jsx +++ b/src/components/game-board-components/Dice.jsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import axios from 'axios'; +import React, { useState, useEffect, useContext } from 'react'; +import { SocketContext } from '../../App'; import one from '../../images/dice/1.png'; import two from '../../images/dice/2.png'; import three from '../../images/dice/3.png'; @@ -8,20 +8,28 @@ import five from '../../images/dice/5.png'; import six from '../../images/dice/6.png'; const Dice = ({ rolledNumberCallback, nowMoving }) => { + const socket = useContext(SocketContext); const [rolledNumber, setRolledNumber] = useState(); const [images] = useState([one, two, three, four, five, six]); const handleRoll = () => { - axios.get('/game/roll', {withCredentials: true}).then(response => { - const utterance = new SpeechSynthesisUtterance(response.data.number); + socket.emit('game:roll'); + }; + useEffect(() => { + socket.on('game:roll', number => { + const utterance = new SpeechSynthesisUtterance(number); speechSynthesis.speak(utterance); - setRolledNumber(response.data.number); - rolledNumberCallback(response.data.number); - }) - } - return( -
- {rolledNumber ? {rolledNumber} : nowMoving ? : null} + setRolledNumber(number); + rolledNumberCallback(number); + }); + }, []); + return ( +
+ {rolledNumber ? ( + {rolledNumber} + ) : nowMoving ? ( + + ) : null}
- ) -} -export default Dice; \ No newline at end of file + ); +}; +export default Dice; diff --git a/src/components/game-board-components/Map.jsx b/src/components/game-board-components/Map.jsx index 3abde79..07ca402 100644 --- a/src/components/game-board-components/Map.jsx +++ b/src/components/game-board-components/Map.jsx @@ -1,13 +1,13 @@ import React, { useEffect, useRef, useState, useContext, useCallback } from 'react'; -import { PlayerDataContext } from '../../App'; -import axios from 'axios'; +import { PlayerDataContext, SocketContext } from '../../App'; import positions from './positions'; const Map = ({ pawns, nowMoving, rolledNumber }) => { const context = useContext(PlayerDataContext); + const socket = useContext(SocketContext); const [hintPawn, setHintPawn] = useState(); const [blinking, setBlinking] = useState(false); - const paintPawn = (context, x, y, color) =>{ + const paintPawn = (context, x, y, color) => { const circle = new Path2D(); circle.arc(x, y, 12, 0, 2 * Math.PI); context.strokeStyle = 'black'; @@ -15,129 +15,128 @@ const Map = ({ pawns, nowMoving, rolledNumber }) => { context.fillStyle = color; context.fill(circle); return circle; - } + }; const canvasRef = useRef(null); // Return true when pawn can move - const checkIfPawnCanMove = useCallback(pawn => { - // If is in base - if((rolledNumber === 1 || rolledNumber === 6) && pawn.position === pawn.basePos){ - return true; - // Other situations: pawn is on map or pawn is in end positions - }else if(pawn.position !== pawn.basePos){ - switch (pawn.color){ - case 'red': - if(pawn.position + rolledNumber <= 73) return true; - break; - case 'blue': - if(pawn.position + rolledNumber <= 79) return true; - break; - case 'green': - if(pawn.position + rolledNumber <= 85) return true; - break; - case 'yellow': - if(pawn.position + rolledNumber <= 91) return true; - break; - default: - return false; + const checkIfPawnCanMove = useCallback( + pawn => { + // If is in base + if ((rolledNumber === 1 || rolledNumber === 6) && pawn.position === pawn.basePos) { + return true; + // Other situations: pawn is on map or pawn is in end positions + } else if (pawn.position !== pawn.basePos) { + switch (pawn.color) { + case 'red': + if (pawn.position + rolledNumber <= 73) return true; + break; + case 'blue': + if (pawn.position + rolledNumber <= 79) return true; + break; + case 'green': + if (pawn.position + rolledNumber <= 85) return true; + break; + case 'yellow': + if (pawn.position + rolledNumber <= 91) return true; + break; + default: + return false; + } + } else { + return false; } - }else{ - return false; - } - },[rolledNumber]); + }, + [rolledNumber] + ); const handleCanvasClick = event => { - // If hint pawn exist it means that pawn can move - if(hintPawn){ - const canvas = canvasRef.current - const ctx = canvas.getContext('2d') + // If hint pawn exist it means that pawn can move + if (hintPawn) { + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); const rect = canvas.getBoundingClientRect(), - x = event.clientX - rect.left, - y = event.clientY - rect.top; - for(const pawn of pawns){ + x = event.clientX - rect.left, + y = event.clientY - rect.top; + for (const pawn of pawns) { if (ctx.isPointInPath(pawn.circle, x, y)) { - axios.post('/game/move', {pawnId: pawn._id}, {withCredentials: true, mode: 'cors'}) - .then(() => { - setHintPawn(null); - }); - + socket.emit('game:move', { pawnId: pawn._id }); } } } - } + }; const getHintPawnPosition = pawn => { // Based on color (because specific color have specific base and end positions) let { position } = pawn; - switch (context.color){ - case 'red': + switch (context.color) { + case 'red': // When in base - if(position >= 0 && position <= 3) { + if (position >= 0 && position <= 3) { return 16; - // Next to end - }else if(position <= 66 && position + rolledNumber >= 67){ + // Next to end + } else if (position <= 66 && position + rolledNumber >= 67) { return position + rolledNumber + 1; // 1 is difference between last position on map and first on end - // Normal move - }else{ - return position + rolledNumber; + // Normal move + } else { + return position + rolledNumber; } - case 'blue': + case 'blue': // When in base - if(position >= 4 && position <= 7){ + if (position >= 4 && position <= 7) { return 55; - // Next to red base - }else if(position <= 67 && position + rolledNumber > 67){ - return position + rolledNumber - 52; - // Next to base - }else if(position <= 53 && position + rolledNumber >= 54){ + // Next to red base + } else if (position <= 67 && position + rolledNumber > 67) { + return position + rolledNumber - 52; + // Next to base + } else if (position <= 53 && position + rolledNumber >= 54) { return position + rolledNumber + 20; - // Normal move - }else{ + // Normal move + } else { return position + rolledNumber; } - case 'green': + case 'green': // When in base - if(position >= 8 && position <= 11){ + if (position >= 8 && position <= 11) { return 42; - // Next to red base - }else if(position <= 67 && position + rolledNumber > 67){ - return position + rolledNumber - 52; - // Next to base - }else if(position <= 40 && position + rolledNumber >= 41){ + // Next to red base + } else if (position <= 67 && position + rolledNumber > 67) { + return position + rolledNumber - 52; + // Next to base + } else if (position <= 40 && position + rolledNumber >= 41) { return position + rolledNumber + 39; - // Normal move - }else{ + // Normal move + } else { return position + rolledNumber; } - case 'yellow': + case 'yellow': // When in base - if(position >= 12 && position <= 15){ + if (position >= 12 && position <= 15) { return 29; - // Next to red base - }else if(position <= 67 && position + rolledNumber > 67){ - return position + rolledNumber - 52; - // Next to base - }else if(position <= 27 && position + rolledNumber >= 28){ + // Next to red base + } else if (position <= 67 && position + rolledNumber > 67) { + return position + rolledNumber - 52; + // Next to base + } else if (position <= 27 && position + rolledNumber >= 28) { return position + rolledNumber + 58; - // Normal move - }else{ + // Normal move + } else { return position + rolledNumber; } - default: + default: return position; } }; - const handleMouseMove = event => { - if(nowMoving && rolledNumber){ + const handleMouseMove = event => { + if (nowMoving && rolledNumber) { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); // Gets x and y cords of mouse on canvas const rect = canvas.getBoundingClientRect(), - x = event.clientX - rect.left, - y = event.clientY - rect.top; - canvas.style.cursor = "default"; - for (const pawn of pawns){ - if(pawn.circle){ + x = event.clientX - rect.left, + y = event.clientY - rect.top; + canvas.style.cursor = 'default'; + for (const pawn of pawns) { + if (pawn.circle) { /* This condition checks if mouse location is: 1) on pawn @@ -145,19 +144,23 @@ const Map = ({ pawns, nowMoving, rolledNumber }) => { 3) if pawn can move And then sets cursor to pointer and paints hint pawn - where will be pawn after click */ - if (ctx.isPointInPath(pawn.circle, x, y) && context.color === pawn.color && checkIfPawnCanMove(pawn)) { + if ( + ctx.isPointInPath(pawn.circle, x, y) && + context.color === pawn.color && + checkIfPawnCanMove(pawn) + ) { const pawnPosition = getHintPawnPosition(pawn); setBlinking(false); // Checks if pawn can make a move - if(pawnPosition){ - canvas.style.cursor = "pointer"; - setHintPawn({id: pawn._id, position: pawnPosition, color: 'grey'}); + if (pawnPosition) { + canvas.style.cursor = 'pointer'; + setHintPawn({ id: pawn._id, position: pawnPosition, color: 'grey' }); break; } - }else{ + } else { setHintPawn(null); } - } + } } } }; @@ -166,35 +169,51 @@ const Map = ({ pawns, nowMoving, rolledNumber }) => { const ctx = canvas.getContext('2d'); const image = new Image(); image.src = 'https://img-9gag-fun.9cache.com/photo/a8GdpYZ_460s.jpg'; - image.onload = function() { + image.onload = function () { ctx.drawImage(image, 0, 0); - pawns.forEach( (pawn, index) => { - if(nowMoving && rolledNumber && blinking && pawn.color === context.color && checkIfPawnCanMove(pawn)){ - pawns[index].circle = paintPawn(ctx, positions[pawn.position].x, positions[pawn.position].y, 'white'); - }else{ - pawns[index].circle = paintPawn(ctx, positions[pawn.position].x, positions[pawn.position].y, pawn.color); + pawns.forEach((pawn, index) => { + if (nowMoving && rolledNumber && blinking && pawn.color === context.color && checkIfPawnCanMove(pawn)) { + pawns[index].circle = paintPawn( + ctx, + positions[pawn.position].x, + positions[pawn.position].y, + 'white' + ); + } else { + pawns[index].circle = paintPawn( + ctx, + positions[pawn.position].x, + positions[pawn.position].y, + pawn.color + ); } setBlinking(!blinking); }); - if (hintPawn){ + if (hintPawn) { paintPawn(ctx, positions[hintPawn.position].x, positions[hintPawn.position].y, hintPawn.color); } - } - },[ checkIfPawnCanMove, context.color, hintPawn, nowMoving, pawns, rolledNumber]); + }; + }, [checkIfPawnCanMove, context.color, hintPawn, nowMoving, pawns, rolledNumber]); + // Rerender canvas when pawns have changed useEffect(() => { - rerenderCanvas(); + rerenderCanvas(); }, [hintPawn, pawns, rerenderCanvas]); - return( - { + socket.on('game:move', () => { + setHintPawn(null); + }); + }, [socket]); + return ( + - ) -} -export default Map; \ No newline at end of file + ); +}; +export default Map;