diff --git a/backend/controllers/roomController.js b/backend/controllers/roomController.js index 80a36ac..8a5bf2a 100644 --- a/backend/controllers/roomController.js +++ b/backend/controllers/roomController.js @@ -23,14 +23,8 @@ const createNewRoom = data => { return room; }; -const findPlayer = async sessionID => { - const player = await Room.findOne({ 'players.sessionID': sessionID }).exec(); - console.log(player); - return await Room.findOne({ 'players.sessionID': sessionID }).exec(); -}; - Room.watch().on('change', async data => { sendToPlayersData(await getRoom(data.documentKey._id)); }); -module.exports = { getRoom, getRooms, updateRoom, getJoinableRoom, createNewRoom, findPlayer }; +module.exports = { getRoom, getRooms, updateRoom, getJoinableRoom, createNewRoom }; diff --git a/backend/handlers/gameHandler.js b/backend/handlers/gameHandler.js index a13ff04..9ef9315 100644 --- a/backend/handlers/gameHandler.js +++ b/backend/handlers/gameHandler.js @@ -1,6 +1,5 @@ const { getRoom, updateRoom } = require('../controllers/roomController'); const { sendToPlayersRolledNumber } = require('../socket/emits'); -const { getPawnPositionAfterMove } = require('../utils/functions'); const { rollDice, isMoveValid } = require('./handlersFunctions'); module.exports = socket => { @@ -10,7 +9,7 @@ module.exports = socket => { const room = await getRoom(req.session.roomId); const pawn = room.getPawn(pawnId); if (isMoveValid(req.session, pawn, room)) { - const newPositionOfMovedPawn = getPawnPositionAfterMove(room.rolledNumber, pawn); + const newPositionOfMovedPawn = pawn.getPositionAfterMove(room.rolledNumber); room.changePositionOfPawn(pawn, newPositionOfMovedPawn); room.beatPawns(newPositionOfMovedPawn, req.session.color); room.changeMovingPlayer(); diff --git a/backend/handlers/roomHandler.js b/backend/handlers/roomHandler.js index bee90de..f99324c 100644 --- a/backend/handlers/roomHandler.js +++ b/backend/handlers/roomHandler.js @@ -1,5 +1,5 @@ const { getRooms, getRoom, updateRoom, createNewRoom } = require('../controllers/roomController'); -const { sendToOnePlayerRooms, sendToOnePlayerData, sendToPlayersData } = require('../socket/emits'); +const { sendToOnePlayerRooms, sendToOnePlayerData } = require('../socket/emits'); module.exports = socket => { const req = socket.request; diff --git a/backend/models/pawn.js b/backend/models/pawn.js index 3df2429..2680cad 100644 --- a/backend/models/pawn.js +++ b/backend/models/pawn.js @@ -2,8 +2,6 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; -const { getPawnPositionAfterMove } = require('../utils/functions'); - const PawnSchema = new Schema({ color: String, basePos: Number, @@ -15,10 +13,70 @@ PawnSchema.methods.canMove = function (rolledNumber) { return true; } // (if player's pawn is near finish line) if the move does not go beyond the win line - if (this.position !== getPawnPositionAfterMove(rolledNumber, this) && this.position !== this.basePos) { + if (this.position !== this.getPositionAfterMove(rolledNumber) && this.position !== this.basePos) { return true; } return false; }; +PawnSchema.methods.getPositionAfterMove = function (rolledNumber) { + const { position, color } = this; + switch (color) { + case 'red': + if (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 (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 (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 (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 = PawnSchema; diff --git a/backend/models/room.js b/backend/models/room.js index 2c7ca34..8c3f25a 100644 --- a/backend/models/room.js +++ b/backend/models/room.js @@ -1,6 +1,5 @@ const mongoose = require('mongoose'); const { colors } = require('../utils/constants'); -const { getPawnPositionAfterMove, getStartPositions } = require('../utils/functions'); const { makeRandomMove } = require('../handlers/handlersFunctions'); const PawnSchema = require('./pawn'); const PlayerSchema = require('./player'); @@ -16,7 +15,23 @@ const RoomSchema = new mongoose.Schema({ timeoutID: Number, rolledNumber: Number, players: [PlayerSchema], - pawns: { type: [PawnSchema], default: getStartPositions() }, + pawns: { + type: [PawnSchema], + default: () => { + 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; + }, + }, }); RoomSchema.methods.beatPawns = function (position, attackingPawnColor) { @@ -44,7 +59,7 @@ RoomSchema.methods.changeMovingPlayer = function () { }; RoomSchema.methods.movePawn = function (pawn) { - const newPositionOfMovedPawn = getPawnPositionAfterMove(this.rolledNumber, pawn); + const newPositionOfMovedPawn = pawn.getPositionAfterMove(this.rolledNumber); this.changePositionOfPawn(pawn, newPositionOfMovedPawn); this.beatPawns(newPositionOfMovedPawn, pawn.color); }; diff --git a/src/App.js b/src/App.js index 770998b..6853ef8 100644 --- a/src/App.js +++ b/src/App.js @@ -2,7 +2,7 @@ import React, { useEffect, useState, createContext } from 'react'; import { io } from 'socket.io-client'; import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom'; import ReactLoading from 'react-loading'; -import Gameboard from './components/Gameboard'; +import Gameboard from './components/Gameboard/Gameboard'; import LoginPage from './components/LoginPage/LoginPage'; export const PlayerDataContext = createContext(); diff --git a/src/components/game-board-components/Dice.jsx b/src/components/Gameboard/Dice/Dice.jsx similarity index 62% rename from src/components/game-board-components/Dice.jsx rename to src/components/Gameboard/Dice/Dice.jsx index b18131e..98a0751 100644 --- a/src/components/game-board-components/Dice.jsx +++ b/src/components/Gameboard/Dice/Dice.jsx @@ -1,24 +1,28 @@ -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'; -import four from '../../images/dice/4.png'; -import five from '../../images/dice/5.png'; -import six from '../../images/dice/6.png'; -import roll from '../../images/dice/roll.png'; +import React, { 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'; +import four from '../../../images/dice/4.png'; +import five from '../../../images/dice/5.png'; +import six from '../../../images/dice/6.png'; +import roll from '../../../images/dice/roll.png'; const Dice = ({ rolledNumberCallback, rolledNumber, nowMoving, color, movingPlayer }) => { const socket = useContext(SocketContext); - const [images] = useState([one, two, three, four, five, six, roll]); + + const images = [one, two, three, four, five, six, roll]; + const handleRoll = () => { socket.emit('game:roll'); }; + useEffect(() => { socket.on('game:roll', number => { rolledNumberCallback(number); }); }, []); + return (
{movingPlayer === color ? ( diff --git a/src/components/Gameboard.jsx b/src/components/Gameboard/Gameboard.jsx similarity index 88% rename from src/components/Gameboard.jsx rename to src/components/Gameboard/Gameboard.jsx index e0764d9..45f7ae8 100644 --- a/src/components/Gameboard.jsx +++ b/src/components/Gameboard/Gameboard.jsx @@ -1,17 +1,16 @@ -import React, { useState, useEffect, useContext, useCallback } from 'react'; +import React, { useState, useEffect, useContext } from 'react'; import ReactLoading from 'react-loading'; -import { PlayerDataContext, SocketContext } from '../App'; -import Map from './game-board-components/Map'; -import Navbar from './Navbar'; +import { PlayerDataContext, SocketContext } from '../../App'; +import Map from './Map/Map'; +import Navbar from '../Navbar/Navbar'; const Gameboard = () => { - // Context data const socket = useContext(SocketContext); const context = useContext(PlayerDataContext); - // Render data + const [pawns, setPawns] = useState([]); const [players, setPlayers] = useState([]); - // Game logic data + const [rolledNumber, setRolledNumber] = useState(null); const [time, setTime] = useState(); const [isReady, setIsReady] = useState(); @@ -19,7 +18,8 @@ const Gameboard = () => { const [started, setStarted] = useState(false); const [movingPlayer, setMovingPlayer] = useState('red'); - const checkWin = useCallback(() => { + + const checkWin = () => { // Player wins when all pawns with same color are inside end base if (pawns.filter(pawn => pawn.color === 'red' && pawn.position === 73).length === 4) { alert('Red Won'); @@ -30,7 +30,8 @@ const Gameboard = () => { } else if (pawns.filter(pawn => pawn.color === 'yellow' && pawn.position === 91).length === 4) { alert('Yellow Won'); } - }, [pawns]); + }; + useEffect(() => { socket.emit('room:data', context.roomId); socket.on('room:data', data => { @@ -59,9 +60,8 @@ const Gameboard = () => { setTime(data.nextMoveTime); setStarted(data.started); }); - }, []); + }, [socket]); - // Callback to handle dice rolling between dice and map component const rolledNumberCallback = number => { setRolledNumber(number); }; diff --git a/src/components/Gameboard/Map/Map.jsx b/src/components/Gameboard/Map/Map.jsx new file mode 100644 index 0000000..1af250e --- /dev/null +++ b/src/components/Gameboard/Map/Map.jsx @@ -0,0 +1,111 @@ +import React, { useEffect, useRef, useState, useContext } from 'react'; +import { PlayerDataContext, SocketContext } from '../../../App'; + +import mapImage from '../../../images/map.jpg'; +import positions from '../positions'; +import pawnImages from '../../../constants/pawnImages'; +import canPawnMove from './canPawnMove'; +import getPositionAfterMove from './getPositionAfterMove'; + +const Map = ({ pawns, nowMoving, rolledNumber }) => { + const player = useContext(PlayerDataContext); + const socket = useContext(SocketContext); + const canvasRef = useRef(null); + + const [hintPawn, setHintPawn] = useState(); + + const paintPawn = (context, x, y, color) => { + const touchableArea = new Path2D(); + touchableArea.arc(x, y, 12, 0, 2 * Math.PI); + const image = new Image(); + image.src = pawnImages[color]; + image.onload = function () { + context.drawImage(image, x - 17, y - 14, 35, 30); + }; + return touchableArea; + }; + + const handleCanvasClick = event => { + if (hintPawn) { + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); + const rect = canvas.getBoundingClientRect(), + cursorX = event.clientX - rect.left, + cursorY = event.clientY - rect.top; + for (const pawn of pawns) { + if (ctx.isPointInPath(pawn.touchableArea, cursorX, cursorY)) { + socket.emit('game:move', pawn._id); + } + } + setHintPawn(null); + } + }; + + const handleMouseMove = event => { + if (nowMoving && rolledNumber) { + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); + 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.touchableArea) { + if ( + ctx.isPointInPath(pawn.touchableArea, x, y) && + player.color === pawn.color && + canPawnMove(pawn, rolledNumber) + ) { + const pawnPosition = getPositionAfterMove(pawn, rolledNumber); + if (pawnPosition) { + canvas.style.cursor = 'pointer'; + setHintPawn({ id: pawn._id, position: pawnPosition, color: 'grey' }); + break; + } + } else { + setHintPawn(null); + } + } else { + setHintPawn(null); + } + } + } else { + setHintPawn(null); + } + }; + const rerenderCanvas = () => { + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); + const image = new Image(); + image.src = mapImage; + image.onload = function () { + ctx.drawImage(image, 0, 0); + pawns.forEach((pawn, index) => { + pawns[index].touchableArea = paintPawn( + ctx, + positions[pawn.position].x, + positions[pawn.position].y, + pawn.color + ); + }); + if (hintPawn) { + paintPawn(ctx, positions[hintPawn.position].x, positions[hintPawn.position].y, hintPawn.color); + } + }; + }; + useEffect(() => { + rerenderCanvas(); + }, [hintPawn, pawns, rerenderCanvas]); + + return ( + + ); +}; +export default Map; diff --git a/src/components/Gameboard/Map/canPawnMove.js b/src/components/Gameboard/Map/canPawnMove.js new file mode 100644 index 0000000..1a5de63 --- /dev/null +++ b/src/components/Gameboard/Map/canPawnMove.js @@ -0,0 +1,26 @@ +export default (pawn, rolledNumber) => { + // 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; + } +}; diff --git a/backend/utils/functions.js b/src/components/Gameboard/Map/getPositionAfterMove.js similarity index 79% rename from backend/utils/functions.js rename to src/components/Gameboard/Map/getPositionAfterMove.js index 81f5522..6b403ca 100644 --- a/backend/utils/functions.js +++ b/src/components/Gameboard/Map/getPositionAfterMove.js @@ -1,19 +1,4 @@ -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; -} -function getPawnPositionAfterMove(rolledNumber, pawn) { +export default (pawn, rolledNumber) => { const { position, color } = pawn; switch (color) { case 'red': @@ -71,5 +56,4 @@ function getPawnPositionAfterMove(rolledNumber, pawn) { return position; } } -} -module.exports = { getStartPositions, getPawnPositionAfterMove }; +}; diff --git a/src/components/game-board-components/positions.js b/src/components/Gameboard/positions.js similarity index 100% rename from src/components/game-board-components/positions.js rename to src/components/Gameboard/positions.js diff --git a/src/components/LoginPage/AddServer/AddServer.jsx b/src/components/LoginPage/AddServer/AddServer.jsx index 054bf54..4333c50 100644 --- a/src/components/LoginPage/AddServer/AddServer.jsx +++ b/src/components/LoginPage/AddServer/AddServer.jsx @@ -10,7 +10,6 @@ const AddServer = () => { useEffect(() => { socket.on('room:created', () => { - console.log('ewa'); socket.emit('room:rooms'); }); }, []); diff --git a/src/components/LoginPage/NameInput/NameInput.jsx b/src/components/LoginPage/NameInput/NameInput.jsx index e1212e4..307518a 100644 --- a/src/components/LoginPage/NameInput/NameInput.jsx +++ b/src/components/LoginPage/NameInput/NameInput.jsx @@ -1,13 +1,14 @@ import React, { useState, useContext, useEffect } from 'react'; import { SocketContext } from '../../../App'; +import useInput from '../../../hooks/useInput'; import './NameInput.css'; const NameInput = ({ isRoomPrivate, roomId }) => { const socket = useContext(SocketContext); - const [nickname, setNickname] = useState(''); - const [password, setPassword] = useState(''); + const nickname = useInput(''); + const password = useInput(''); const [isPasswordWrong, setIsPasswordWrong] = useState(false); const handleButtonClick = () => { - socket.emit('player:login', { name: nickname, password: password, roomId: roomId }); + socket.emit('player:login', { name: nickname.value, password: password.value, roomId: roomId }); }; useEffect(() => { socket.on('error:wrongPassword', () => { @@ -28,12 +29,12 @@ const NameInput = ({ isRoomPrivate, roomId }) => { return (
- setNickname(e.target.value)} /> + {isRoomPrivate ? ( setPassword(e.target.value)} + onChange={password.onChange} style={{ backgroundColor: isPasswordWrong ? 'red' : null }} /> ) : null} diff --git a/src/components/LoginPage/ServerList/ServerList.jsx b/src/components/LoginPage/ServerList/ServerList.jsx index 28e55a2..c23601e 100644 --- a/src/components/LoginPage/ServerList/ServerList.jsx +++ b/src/components/LoginPage/ServerList/ServerList.jsx @@ -12,7 +12,7 @@ const ServerList = () => { const [rooms, setRooms] = useState([]); const [joining, setJoining] = useState(false); const [clickedRoom, setClickedRoom] = useState(null); - useEffect(async () => { + useEffect(() => { socket.emit('room:rooms'); socket.on('room:rooms', data => { data = JSON.parse(data); @@ -70,9 +70,7 @@ const ServerList = () => {
)}
- {joining ? ( - - ) : null} + {joining ? : null}
); }; diff --git a/src/components/navbar-components/AnimatedOverlay.jsx b/src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.jsx similarity index 67% rename from src/components/navbar-components/AnimatedOverlay.jsx rename to src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.jsx index 5fc82fa..ba0558a 100644 --- a/src/components/navbar-components/AnimatedOverlay.jsx +++ b/src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.jsx @@ -1,13 +1,9 @@ -import React, { useState, useEffect } from 'react'; +import React, { useMemo } 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]); + const animationDelay = useMemo(() => 15 - Math.ceil((time - Date.now()) / 1000), [time]); return ( { return ( diff --git a/src/components/Navbar.css b/src/components/Navbar/Navbar.css similarity index 100% rename from src/components/Navbar.css rename to src/components/Navbar/Navbar.css diff --git a/src/components/Navbar.jsx b/src/components/Navbar/Navbar.jsx similarity index 82% rename from src/components/Navbar.jsx rename to src/components/Navbar/Navbar.jsx index 0c67c8a..1b05d12 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar/Navbar.jsx @@ -1,10 +1,10 @@ import React from 'react'; -import Dice from './game-board-components/Dice'; -import NameContainer from './navbar-components/NameContainer'; -import ReadyButton from './navbar-components/ReadyButton'; +import Dice from '../Gameboard/Dice/Dice'; +import NameContainer from './NameContainer/NameContainer'; +import ReadyButton from './ReadyButton/ReadyButton'; import './Navbar.css'; import { useContext } from 'react'; -import { PlayerDataContext } from '../App'; +import { PlayerDataContext } from '../../App'; const Navbar = ({ players, started, time, isReady, rolledNumber, nowMoving, rolledNumberCallback, movingPlayer }) => { const context = useContext(PlayerDataContext); const colors = ['red', 'blue', 'green', 'yellow']; diff --git a/src/components/navbar-components/ReadyButton.jsx b/src/components/Navbar/ReadyButton/ReadyButton.jsx similarity index 77% rename from src/components/navbar-components/ReadyButton.jsx rename to src/components/Navbar/ReadyButton/ReadyButton.jsx index 6addc94..6249a9e 100644 --- a/src/components/navbar-components/ReadyButton.jsx +++ b/src/components/Navbar/ReadyButton/ReadyButton.jsx @@ -1,19 +1,17 @@ import React, { useState, useContext, useEffect } from 'react'; -import { SocketContext } from '../../App'; +import { SocketContext } from '../../../App'; import Switch from '@material-ui/core/Switch'; import '../Navbar.css'; -import './TimerAnimation'; +import '../NameContainer/AnimatedOverlay/TimerAnimation'; const ReadyButton = ({ isReady }) => { const socket = useContext(SocketContext); - const [checked, setChecked] = useState(); + const [checked, setChecked] = useState(isReady); + const handleCheckboxChange = () => { socket.emit('player:ready'); setChecked(!checked); }; - useEffect(() => { - setChecked(isReady); - }); return (
diff --git a/src/components/game-board-components/Map.jsx b/src/components/game-board-components/Map.jsx deleted file mode 100644 index 0d93155..0000000 --- a/src/components/game-board-components/Map.jsx +++ /dev/null @@ -1,230 +0,0 @@ -import React, { useEffect, useRef, useState, useContext, useCallback } from 'react'; -import { PlayerDataContext, SocketContext } from '../../App'; -import positions from './positions'; -import bluePawn from '../../images/pawns/blue-pawn.png'; -import greenPawn from '../../images/pawns/green-pawn.png'; -import yellowPawn from '../../images/pawns/yellow-pawn.png'; -import redPawn from '../../images/pawns/red-pawn.png'; -import greyPawn from '../../images/pawns/grey-pawn.png'; -const Map = ({ pawns, nowMoving, rolledNumber }) => { - const context = useContext(PlayerDataContext); - const socket = useContext(SocketContext); - const [hintPawn, setHintPawn] = useState(); - const paintPawn = (context, x, y, color) => { - const circle = new Path2D(); - circle.arc(x, y, 12, 0, 2 * Math.PI); - const image = new Image(); - switch (color) { - case 'green': - image.src = greenPawn; - break; - case 'blue': - image.src = bluePawn; - break; - case 'red': - image.src = redPawn; - break; - case 'yellow': - image.src = yellowPawn; - break; - case 'grey': - image.src = greyPawn; - break; - } - context.drawImage(image, x - 17, y - 14, 35, 30); - 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; - } - } else { - return false; - } - }, - [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'); - const rect = canvas.getBoundingClientRect(), - x = event.clientX - rect.left, - y = event.clientY - rect.top; - for (const pawn of pawns) { - if (ctx.isPointInPath(pawn.circle, x, y)) { - socket.emit('game:move', pawn._id); - } - } - setHintPawn(null); - } - }; - const getHintPawnPosition = pawn => { - // Based on color (because specific color have specific base and end positions) - let { position } = pawn; - switch (context.color) { - case 'red': - // When in base - if (position >= 0 && position <= 3) { - return 16; - // 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; - } - case 'blue': - // When in base - 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) { - return position + rolledNumber + 20; - // Normal move - } else { - return position + rolledNumber; - } - case 'green': - // When in base - 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) { - return position + rolledNumber + 39; - // Normal move - } else { - return position + rolledNumber; - } - case 'yellow': - // When in base - 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) { - return position + rolledNumber + 58; - // Normal move - } else { - return position + rolledNumber; - } - default: - return position; - } - }; - 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) { - /* - This condition checks if mouse location is: - 1) on pawn - 2) is color of pawn same as player's - 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) - ) { - const pawnPosition = getHintPawnPosition(pawn); - // Checks if pawn can make a move - if (pawnPosition) { - canvas.style.cursor = 'pointer'; - setHintPawn({ id: pawn._id, position: pawnPosition, color: 'grey' }); - break; - } - } else { - setHintPawn(null); - } - } - } - } - }; - const rerenderCanvas = useCallback(() => { - const canvas = canvasRef.current; - const ctx = canvas.getContext('2d'); - const image = new Image(); - image.src = 'https://img-9gag-fun.9cache.com/photo/a8GdpYZ_460s.jpg'; - image.onload = function () { - ctx.drawImage(image, 0, 0); - pawns.forEach((pawn, index) => { - pawns[index].circle = paintPawn( - ctx, - positions[pawn.position].x, - positions[pawn.position].y, - pawn.color - ); - }); - if (hintPawn) { - paintPawn(ctx, positions[hintPawn.position].x, positions[hintPawn.position].y, hintPawn.color); - } - }; - }, [checkIfPawnCanMove, context.color, hintPawn, nowMoving, pawns, rolledNumber]); - - // Rerender canvas when pawns have changed - useEffect(() => { - rerenderCanvas(); - }, [hintPawn, pawns, rerenderCanvas]); - - useEffect(() => { - socket.on('game:move', () => { - setHintPawn(null); - }); - socket.on('game:roll', () => { - setHintPawn(null); - }); - }, [socket]); - return ( - - ); -}; -export default Map; diff --git a/src/constants/pawnImages.js b/src/constants/pawnImages.js new file mode 100644 index 0000000..e568106 --- /dev/null +++ b/src/constants/pawnImages.js @@ -0,0 +1,13 @@ +import bluePawn from '../images/pawns/blue-pawn.png'; +import greenPawn from '../images/pawns/green-pawn.png'; +import redPawn from '../images/pawns/red-pawn.png'; +import yellowPawn from '../images/pawns/yellow-pawn.png'; +import greyPawn from '../images/pawns/grey-pawn.png'; + +export default { + green: greenPawn, + blue: bluePawn, + red: redPawn, + yellow: yellowPawn, + grey: greyPawn, +}; diff --git a/src/constants/positions.js b/src/constants/positions.js new file mode 100644 index 0000000..c437f9f --- /dev/null +++ b/src/constants/positions.js @@ -0,0 +1,117 @@ +const positions = [ + // Red base + { x: 67, y: 67 }, // 0 + { x: 67, y: 116 }, + { x: 117, y: 67 }, + { x: 117, y: 116 }, + // Blue base + { x: 67, y: 343 }, + { x: 67, y: 392 }, + { x: 117, y: 343 }, + { x: 117, y: 392 }, + // Green base + { x: 343, y: 343 }, + { x: 392, y: 392 }, + { x: 392, y: 343 }, // 10 + { x: 343, y: 392 }, + // Yellow base + { x: 343, y: 67 }, + { x: 392, y: 116 }, + { x: 392, y: 67 }, + { x: 343, y: 116 }, + // Map - starting from red field + { x: 45, y: 200 }, + { x: 76, y: 200 }, + { x: 107, y: 200 }, + { x: 138, y: 200 }, + { x: 169, y: 200 }, // 20 + + { x: 200, y: 169 }, + { x: 200, y: 138 }, + { x: 200, y: 107 }, + { x: 200, y: 76 }, + { x: 200, y: 45 }, + { x: 200, y: 14 }, + // Top + { x: 230, y: 14 }, + { x: 261, y: 14 }, + { x: 261, y: 45 }, + { x: 261, y: 76 }, // 30 + { x: 261, y: 107 }, + { x: 261, y: 138 }, + { x: 261, y: 169 }, + + { x: 291, y: 200 }, + { x: 321, y: 200 }, + { x: 352, y: 200 }, + { x: 383, y: 200 }, + { x: 414, y: 200 }, + { x: 445, y: 200 }, + // Right + { x: 445, y: 230 }, // 40 + + { x: 445, y: 261 }, + { x: 414, y: 261 }, + { x: 383, y: 261 }, + { x: 352, y: 261 }, + { x: 321, y: 261 }, + { x: 291, y: 261 }, + + { x: 261, y: 291 }, + { x: 261, y: 322 }, + { x: 261, y: 353 }, + { x: 261, y: 384 }, // 50 + { x: 261, y: 414 }, + { x: 261, y: 445 }, + // Bottom + { x: 230, y: 445 }, + + { x: 200, y: 445 }, + { x: 200, y: 414 }, + { x: 200, y: 384 }, + { x: 200, y: 353 }, + { x: 200, y: 322 }, + { x: 200, y: 291 }, + + { x: 169, y: 261 }, // 60 + { x: 138, y: 261 }, + { x: 107, y: 261 }, + { x: 76, y: 261 }, + { x: 45, y: 261 }, + + { x: 15, y: 261 }, + // Left + { x: 15, y: 231 }, // 66 + // One behind red base + { x: 15, y: 200 }, //67 + // Red end + { x: 45, y: 231 }, // 68 + { x: 76, y: 231 }, + { x: 107, y: 231 }, + { x: 138, y: 231 }, + { x: 169, y: 231 }, + { x: 200, y: 231 }, // 73 + // Blue end + { x: 231, y: 414 }, // 74 + { x: 231, y: 384 }, + { x: 231, y: 353 }, + { x: 231, y: 322 }, + { x: 231, y: 291 }, + { x: 231, y: 260 }, // 79 + // Green end + { x: 414, y: 231 }, // 80 + { x: 383, y: 231 }, + { x: 352, y: 231 }, + { x: 321, y: 231 }, + { x: 290, y: 231 }, + { x: 259, y: 231 }, // 85 + // Yellow base + { x: 230, y: 45 }, // 86 + { x: 230, y: 76 }, + { x: 230, y: 107 }, + { x: 230, y: 138 }, + { x: 230, y: 169 }, + { x: 230, y: 200 }, // 91 +]; + +export default positions; diff --git a/src/hooks/useInput.js b/src/hooks/useInput.js new file mode 100644 index 0000000..1144f96 --- /dev/null +++ b/src/hooks/useInput.js @@ -0,0 +1,11 @@ +import { useState } from 'react'; +export default function useInput({ initialValue }) { + const [value, setValue] = useState(initialValue); + const handleChange = e => { + setValue(e.target.value); + }; + return { + value, + onChange: handleChange, + }; +} diff --git a/src/images/map.jpg b/src/images/map.jpg new file mode 100644 index 0000000..8c158fd Binary files /dev/null and b/src/images/map.jpg differ