refactored frontend

This commit is contained in:
Wenszel 2023-12-08 17:25:51 +01:00
parent f318afe071
commit f87938ae08
26 changed files with 407 additions and 313 deletions

View File

@ -23,14 +23,8 @@ const createNewRoom = data => {
return room; 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 => { Room.watch().on('change', async data => {
sendToPlayersData(await getRoom(data.documentKey._id)); sendToPlayersData(await getRoom(data.documentKey._id));
}); });
module.exports = { getRoom, getRooms, updateRoom, getJoinableRoom, createNewRoom, findPlayer }; module.exports = { getRoom, getRooms, updateRoom, getJoinableRoom, createNewRoom };

View File

@ -1,6 +1,5 @@
const { getRoom, updateRoom } = require('../controllers/roomController'); const { getRoom, updateRoom } = require('../controllers/roomController');
const { sendToPlayersRolledNumber } = require('../socket/emits'); const { sendToPlayersRolledNumber } = require('../socket/emits');
const { getPawnPositionAfterMove } = require('../utils/functions');
const { rollDice, isMoveValid } = require('./handlersFunctions'); const { rollDice, isMoveValid } = require('./handlersFunctions');
module.exports = socket => { module.exports = socket => {
@ -10,7 +9,7 @@ module.exports = socket => {
const room = await getRoom(req.session.roomId); const room = await getRoom(req.session.roomId);
const pawn = room.getPawn(pawnId); const pawn = room.getPawn(pawnId);
if (isMoveValid(req.session, pawn, room)) { if (isMoveValid(req.session, pawn, room)) {
const newPositionOfMovedPawn = getPawnPositionAfterMove(room.rolledNumber, pawn); const newPositionOfMovedPawn = pawn.getPositionAfterMove(room.rolledNumber);
room.changePositionOfPawn(pawn, newPositionOfMovedPawn); room.changePositionOfPawn(pawn, newPositionOfMovedPawn);
room.beatPawns(newPositionOfMovedPawn, req.session.color); room.beatPawns(newPositionOfMovedPawn, req.session.color);
room.changeMovingPlayer(); room.changeMovingPlayer();

View File

@ -1,5 +1,5 @@
const { getRooms, getRoom, updateRoom, createNewRoom } = require('../controllers/roomController'); const { getRooms, getRoom, updateRoom, createNewRoom } = require('../controllers/roomController');
const { sendToOnePlayerRooms, sendToOnePlayerData, sendToPlayersData } = require('../socket/emits'); const { sendToOnePlayerRooms, sendToOnePlayerData } = require('../socket/emits');
module.exports = socket => { module.exports = socket => {
const req = socket.request; const req = socket.request;

View File

@ -2,8 +2,6 @@ const mongoose = require('mongoose');
const Schema = mongoose.Schema; const Schema = mongoose.Schema;
const { getPawnPositionAfterMove } = require('../utils/functions');
const PawnSchema = new Schema({ const PawnSchema = new Schema({
color: String, color: String,
basePos: Number, basePos: Number,
@ -15,10 +13,70 @@ PawnSchema.methods.canMove = function (rolledNumber) {
return true; return true;
} }
// (if player's pawn is near finish line) if the move does not go beyond the win line // (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 true;
} }
return false; 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; module.exports = PawnSchema;

View File

@ -1,6 +1,5 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const { colors } = require('../utils/constants'); const { colors } = require('../utils/constants');
const { getPawnPositionAfterMove, getStartPositions } = require('../utils/functions');
const { makeRandomMove } = require('../handlers/handlersFunctions'); const { makeRandomMove } = require('../handlers/handlersFunctions');
const PawnSchema = require('./pawn'); const PawnSchema = require('./pawn');
const PlayerSchema = require('./player'); const PlayerSchema = require('./player');
@ -16,7 +15,23 @@ const RoomSchema = new mongoose.Schema({
timeoutID: Number, timeoutID: Number,
rolledNumber: Number, rolledNumber: Number,
players: [PlayerSchema], 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) { RoomSchema.methods.beatPawns = function (position, attackingPawnColor) {
@ -44,7 +59,7 @@ RoomSchema.methods.changeMovingPlayer = function () {
}; };
RoomSchema.methods.movePawn = function (pawn) { RoomSchema.methods.movePawn = function (pawn) {
const newPositionOfMovedPawn = getPawnPositionAfterMove(this.rolledNumber, pawn); const newPositionOfMovedPawn = pawn.getPositionAfterMove(this.rolledNumber);
this.changePositionOfPawn(pawn, newPositionOfMovedPawn); this.changePositionOfPawn(pawn, newPositionOfMovedPawn);
this.beatPawns(newPositionOfMovedPawn, pawn.color); this.beatPawns(newPositionOfMovedPawn, pawn.color);
}; };

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState, createContext } from 'react';
import { io } from 'socket.io-client'; import { io } from 'socket.io-client';
import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom'; import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom';
import ReactLoading from 'react-loading'; import ReactLoading from 'react-loading';
import Gameboard from './components/Gameboard'; import Gameboard from './components/Gameboard/Gameboard';
import LoginPage from './components/LoginPage/LoginPage'; import LoginPage from './components/LoginPage/LoginPage';
export const PlayerDataContext = createContext(); export const PlayerDataContext = createContext();

View File

@ -1,24 +1,28 @@
import React, { useState, useEffect, useContext } from 'react'; import React, { useEffect, useContext } from 'react';
import { SocketContext } from '../../App'; import { SocketContext } from '../../../App';
import one from '../../images/dice/1.png'; import one from '../../../images/dice/1.png';
import two from '../../images/dice/2.png'; import two from '../../../images/dice/2.png';
import three from '../../images/dice/3.png'; import three from '../../../images/dice/3.png';
import four from '../../images/dice/4.png'; import four from '../../../images/dice/4.png';
import five from '../../images/dice/5.png'; import five from '../../../images/dice/5.png';
import six from '../../images/dice/6.png'; import six from '../../../images/dice/6.png';
import roll from '../../images/dice/roll.png'; import roll from '../../../images/dice/roll.png';
const Dice = ({ rolledNumberCallback, rolledNumber, nowMoving, color, movingPlayer }) => { const Dice = ({ rolledNumberCallback, rolledNumber, nowMoving, color, movingPlayer }) => {
const socket = useContext(SocketContext); 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 = () => { const handleRoll = () => {
socket.emit('game:roll'); socket.emit('game:roll');
}; };
useEffect(() => { useEffect(() => {
socket.on('game:roll', number => { socket.on('game:roll', number => {
rolledNumberCallback(number); rolledNumberCallback(number);
}); });
}, []); }, []);
return ( return (
<div className={`dice-container dice-${color}`}> <div className={`dice-container dice-${color}`}>
{movingPlayer === color ? ( {movingPlayer === color ? (

View File

@ -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 ReactLoading from 'react-loading';
import { PlayerDataContext, SocketContext } from '../App'; import { PlayerDataContext, SocketContext } from '../../App';
import Map from './game-board-components/Map'; import Map from './Map/Map';
import Navbar from './Navbar'; import Navbar from '../Navbar/Navbar';
const Gameboard = () => { const Gameboard = () => {
// Context data
const socket = useContext(SocketContext); const socket = useContext(SocketContext);
const context = useContext(PlayerDataContext); const context = useContext(PlayerDataContext);
// Render data
const [pawns, setPawns] = useState([]); const [pawns, setPawns] = useState([]);
const [players, setPlayers] = useState([]); const [players, setPlayers] = useState([]);
// Game logic data
const [rolledNumber, setRolledNumber] = useState(null); const [rolledNumber, setRolledNumber] = useState(null);
const [time, setTime] = useState(); const [time, setTime] = useState();
const [isReady, setIsReady] = useState(); const [isReady, setIsReady] = useState();
@ -19,7 +18,8 @@ const Gameboard = () => {
const [started, setStarted] = useState(false); const [started, setStarted] = useState(false);
const [movingPlayer, setMovingPlayer] = useState('red'); const [movingPlayer, setMovingPlayer] = useState('red');
const checkWin = useCallback(() => {
const checkWin = () => {
// Player wins when all pawns with same color are inside end base // Player wins when all pawns with same color are inside end base
if (pawns.filter(pawn => pawn.color === 'red' && pawn.position === 73).length === 4) { if (pawns.filter(pawn => pawn.color === 'red' && pawn.position === 73).length === 4) {
alert('Red Won'); alert('Red Won');
@ -30,7 +30,8 @@ const Gameboard = () => {
} else if (pawns.filter(pawn => pawn.color === 'yellow' && pawn.position === 91).length === 4) { } else if (pawns.filter(pawn => pawn.color === 'yellow' && pawn.position === 91).length === 4) {
alert('Yellow Won'); alert('Yellow Won');
} }
}, [pawns]); };
useEffect(() => { useEffect(() => {
socket.emit('room:data', context.roomId); socket.emit('room:data', context.roomId);
socket.on('room:data', data => { socket.on('room:data', data => {
@ -59,9 +60,8 @@ const Gameboard = () => {
setTime(data.nextMoveTime); setTime(data.nextMoveTime);
setStarted(data.started); setStarted(data.started);
}); });
}, []); }, [socket]);
// Callback to handle dice rolling between dice and map component
const rolledNumberCallback = number => { const rolledNumberCallback = number => {
setRolledNumber(number); setRolledNumber(number);
}; };

View File

@ -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 (
<canvas
className='canvas-container'
width={460}
height={460}
ref={canvasRef}
onClick={handleCanvasClick}
onMouseMove={handleMouseMove}
/>
);
};
export default Map;

View File

@ -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;
}
};

View File

@ -1,19 +1,4 @@
const { colors } = require('./constants'); export default (pawn, rolledNumber) => {
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) {
const { position, color } = pawn; const { position, color } = pawn;
switch (color) { switch (color) {
case 'red': case 'red':
@ -71,5 +56,4 @@ function getPawnPositionAfterMove(rolledNumber, pawn) {
return position; return position;
} }
} }
} };
module.exports = { getStartPositions, getPawnPositionAfterMove };

View File

@ -10,7 +10,6 @@ const AddServer = () => {
useEffect(() => { useEffect(() => {
socket.on('room:created', () => { socket.on('room:created', () => {
console.log('ewa');
socket.emit('room:rooms'); socket.emit('room:rooms');
}); });
}, []); }, []);

View File

@ -1,13 +1,14 @@
import React, { useState, useContext, useEffect } from 'react'; import React, { useState, useContext, useEffect } from 'react';
import { SocketContext } from '../../../App'; import { SocketContext } from '../../../App';
import useInput from '../../../hooks/useInput';
import './NameInput.css'; import './NameInput.css';
const NameInput = ({ isRoomPrivate, roomId }) => { const NameInput = ({ isRoomPrivate, roomId }) => {
const socket = useContext(SocketContext); const socket = useContext(SocketContext);
const [nickname, setNickname] = useState(''); const nickname = useInput('');
const [password, setPassword] = useState(''); const password = useInput('');
const [isPasswordWrong, setIsPasswordWrong] = useState(false); const [isPasswordWrong, setIsPasswordWrong] = useState(false);
const handleButtonClick = () => { 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(() => { useEffect(() => {
socket.on('error:wrongPassword', () => { socket.on('error:wrongPassword', () => {
@ -28,12 +29,12 @@ const NameInput = ({ isRoomPrivate, roomId }) => {
return ( return (
<div className='name-overlay'> <div className='name-overlay'>
<div className='name-input-container' style={{ height: isRoomPrivate ? '100px' : '50px' }}> <div className='name-input-container' style={{ height: isRoomPrivate ? '100px' : '50px' }}>
<input placeholder='Nickname' type='text' onChange={e => setNickname(e.target.value)} /> <input placeholder='Nickname' type='text' onChange={nickname.onChange} />
{isRoomPrivate ? ( {isRoomPrivate ? (
<input <input
placeholder='Room password' placeholder='Room password'
type='text' type='text'
onChange={e => setPassword(e.target.value)} onChange={password.onChange}
style={{ backgroundColor: isPasswordWrong ? 'red' : null }} style={{ backgroundColor: isPasswordWrong ? 'red' : null }}
/> />
) : null} ) : null}

View File

@ -12,7 +12,7 @@ const ServerList = () => {
const [rooms, setRooms] = useState([]); const [rooms, setRooms] = useState([]);
const [joining, setJoining] = useState(false); const [joining, setJoining] = useState(false);
const [clickedRoom, setClickedRoom] = useState(null); const [clickedRoom, setClickedRoom] = useState(null);
useEffect(async () => { useEffect(() => {
socket.emit('room:rooms'); socket.emit('room:rooms');
socket.on('room:rooms', data => { socket.on('room:rooms', data => {
data = JSON.parse(data); data = JSON.parse(data);
@ -70,9 +70,7 @@ const ServerList = () => {
</div> </div>
)} )}
</div> </div>
{joining ? ( {joining ? <NameInput roomId={clickedRoom._id} isRoomPrivate={clickedRoom.private} /> : null}
<NameInput roomId={clickedRoom._id} isRoomPrivate={clickedRoom.private} />
) : null}
</div> </div>
); );
}; };

View File

@ -1,13 +1,9 @@
import React, { useState, useEffect } from 'react'; import React, { useMemo } from 'react';
import { CSSTransition } from 'react-transition-group'; import { CSSTransition } from 'react-transition-group';
import './TimerAnimation.js'; import './TimerAnimation.js';
const AnimatedOverlay = ({ time }) => { const AnimatedOverlay = ({ time }) => {
const [animationDelay, setAnimationDelay] = useState(); const animationDelay = useMemo(() => 15 - Math.ceil((time - Date.now()) / 1000), [time]);
useEffect(() => {
setAnimationDelay(15 - Math.ceil((time - Date.now()) / 1000));
}, [time]);
return ( return (
<CSSTransition <CSSTransition

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import AnimatedOverlay from './AnimatedOverlay'; import AnimatedOverlay from './AnimatedOverlay/AnimatedOverlay';
const NameContainer = ({ player, time }) => { const NameContainer = ({ player, time }) => {
return ( return (

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import Dice from './game-board-components/Dice'; import Dice from '../Gameboard/Dice/Dice';
import NameContainer from './navbar-components/NameContainer'; import NameContainer from './NameContainer/NameContainer';
import ReadyButton from './navbar-components/ReadyButton'; import ReadyButton from './ReadyButton/ReadyButton';
import './Navbar.css'; import './Navbar.css';
import { useContext } from 'react'; import { useContext } from 'react';
import { PlayerDataContext } from '../App'; import { PlayerDataContext } from '../../App';
const Navbar = ({ players, started, time, isReady, rolledNumber, nowMoving, rolledNumberCallback, movingPlayer }) => { const Navbar = ({ players, started, time, isReady, rolledNumber, nowMoving, rolledNumberCallback, movingPlayer }) => {
const context = useContext(PlayerDataContext); const context = useContext(PlayerDataContext);
const colors = ['red', 'blue', 'green', 'yellow']; const colors = ['red', 'blue', 'green', 'yellow'];

View File

@ -1,19 +1,17 @@
import React, { useState, useContext, useEffect } from 'react'; import React, { useState, useContext, useEffect } from 'react';
import { SocketContext } from '../../App'; import { SocketContext } from '../../../App';
import Switch from '@material-ui/core/Switch'; import Switch from '@material-ui/core/Switch';
import '../Navbar.css'; import '../Navbar.css';
import './TimerAnimation'; import '../NameContainer/AnimatedOverlay/TimerAnimation';
const ReadyButton = ({ isReady }) => { const ReadyButton = ({ isReady }) => {
const socket = useContext(SocketContext); const socket = useContext(SocketContext);
const [checked, setChecked] = useState(); const [checked, setChecked] = useState(isReady);
const handleCheckboxChange = () => { const handleCheckboxChange = () => {
socket.emit('player:ready'); socket.emit('player:ready');
setChecked(!checked); setChecked(!checked);
}; };
useEffect(() => {
setChecked(isReady);
});
return ( return (
<div className='ready-container'> <div className='ready-container'>
<Switch onChange={handleCheckboxChange} checked={checked || false} /> <Switch onChange={handleCheckboxChange} checked={checked || false} />

View File

@ -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 (
<canvas
className='canvas-container'
width={460}
height={460}
ref={canvasRef}
onClick={handleCanvasClick}
onMouseMove={handleMouseMove}
/>
);
};
export default Map;

View File

@ -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,
};

117
src/constants/positions.js Normal file
View File

@ -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;

11
src/hooks/useInput.js Normal file
View File

@ -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,
};
}

BIN
src/images/map.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB