{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.css b/src/components/LoginPage/AddServer/AddServer.css
new file mode 100644
index 0000000..4c64921
--- /dev/null
+++ b/src/components/LoginPage/AddServer/AddServer.css
@@ -0,0 +1,35 @@
+.refresh {
+ display: flex;
+ margin-left: auto;
+ justify-content: center;
+ align-items: center;
+ width: 40px;
+ height: 100%;
+ border: 1px solid white;
+}
+.refresh > img {
+ width: 20px;
+ height: 20px;
+ cursor: pointer;
+}
+
+form {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+}
+
+.private-container {
+ margin-left: 10px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ width: 100%;
+}
+input:disabled {
+ background-color: black;
+ color: #999;
+ border: 1px solid #ddd;
+}
diff --git a/src/components/LoginPage/AddServer/AddServer.jsx b/src/components/LoginPage/AddServer/AddServer.jsx
new file mode 100644
index 0000000..4333c50
--- /dev/null
+++ b/src/components/LoginPage/AddServer/AddServer.jsx
@@ -0,0 +1,57 @@
+import React, { useState, useContext, useEffect } from 'react';
+import './AddServer.css';
+import Switch from '@material-ui/core/Switch';
+import { SocketContext } from '../../../App';
+const AddServer = () => {
+ const socket = useContext(SocketContext);
+ const [isPrivate, setIsPrivate] = useState(false);
+ const [serverName, setServerName] = useState('');
+ const [password, setPassword] = useState('');
+
+ useEffect(() => {
+ socket.on('room:created', () => {
+ socket.emit('room:rooms');
+ });
+ }, []);
+
+ const handleButtonClick = e => {
+ e.preventDefault();
+ socket.emit('room:create', {
+ name: serverName,
+ private: isPrivate,
+ password: password,
+ });
+ };
+
+ return (
+
+ );
+};
+
+export default AddServer;
diff --git a/src/components/LoginPage/LoginPage.css b/src/components/LoginPage/LoginPage.css
new file mode 100644
index 0000000..3376c6f
--- /dev/null
+++ b/src/components/LoginPage/LoginPage.css
@@ -0,0 +1,53 @@
+.login-page-container {
+ display: flex;
+ flex-direction: row;
+
+ justify-content: center;
+ align-items: flex-start;
+ height: 50%;
+ width: 100%;
+}
+
+.lp-container {
+ margin: 50px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 500px;
+ padding: 20px;
+ color: white;
+}
+
+.title-container {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 40px;
+ border: 1px solid white;
+ border-radius: 2px;
+ transform: scaleX(1.02);
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+ padding-left: 10px;
+ text-align: center;
+}
+
+.title-container > h1 {
+ width: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+.content-container {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ padding: 10px;
+ background-color: rgba(0, 0, 0, 0.5);
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+ border-left: 1px solid black;
+ border-right: 1px solid black;
+ border-bottom: 1px solid black;
+}
diff --git a/src/components/LoginPage/LoginPage.jsx b/src/components/LoginPage/LoginPage.jsx
new file mode 100644
index 0000000..9532e2d
--- /dev/null
+++ b/src/components/LoginPage/LoginPage.jsx
@@ -0,0 +1,16 @@
+import './LoginPage.css';
+import AddServer from './AddServer/AddServer';
+import ServerList from './ServerList/ServerList';
+import NameInput from './NameInput/NameInput';
+const LoginPage = () => {
+ return (
+ <>
+
+ >
+ );
+};
+
+export default LoginPage;
diff --git a/src/components/LoginPage/NameInput/NameInput.css b/src/components/LoginPage/NameInput/NameInput.css
new file mode 100644
index 0000000..9f6282c
--- /dev/null
+++ b/src/components/LoginPage/NameInput/NameInput.css
@@ -0,0 +1,70 @@
+.name-input-container {
+ display: flex;
+ flex-direction: column;
+ padding: 10px 20px 60px 20px;
+ width: 300px;
+ background: radial-gradient(circle, rgba(0, 138, 255, 1) 5%, rgba(9, 9, 121, 1) 81%);
+ border: 1px solid white;
+ border-radius: 8px;
+ margin: 20px;
+}
+.name-input-container > button {
+ margin-top: 5px;
+ text-align: center;
+ width: 100px;
+ align-self: center;
+}
+.name-input-container > input {
+ margin-top: 10px;
+}
+.name-overlay {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 1;
+}
+input,
+button {
+ padding: 0;
+ border: none;
+ outline: none;
+ box-sizing: border-box;
+}
+
+input {
+ width: 100%;
+ padding: 12px;
+ font-size: 16px;
+ border-radius: 8px;
+ color: white;
+ border: 1px solid #ccc;
+ background-color: rgba(0, 0, 0, 0.2);
+ transition: border-color 0.3s ease-in-out, background-color 0.3s ease-in-out;
+}
+
+input:focus {
+ color: black;
+ border-color: #4a90e2;
+ background-color: #fff;
+}
+
+button {
+ padding: 12px 20px;
+ font-size: 16px;
+ border-radius: 8px;
+ border: none;
+ color: #fff;
+ background-color: rgba(0, 0, 0, 0.4);
+ cursor: pointer;
+ transition: background-color 0.3s ease-in-out;
+}
+
+button:hover {
+ background-color: rgba(0, 0, 0, 1);
+}
diff --git a/src/components/LoginPage/NameInput/NameInput.jsx b/src/components/LoginPage/NameInput/NameInput.jsx
new file mode 100644
index 0000000..307518a
--- /dev/null
+++ b/src/components/LoginPage/NameInput/NameInput.jsx
@@ -0,0 +1,47 @@
+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 = useInput('');
+ const password = useInput('');
+ const [isPasswordWrong, setIsPasswordWrong] = useState(false);
+ const handleButtonClick = () => {
+ socket.emit('player:login', { name: nickname.value, password: password.value, roomId: roomId });
+ };
+ useEffect(() => {
+ socket.on('error:wrongPassword', () => {
+ setIsPasswordWrong(true);
+ });
+ const keyDownHandler = event => {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ handleButtonClick();
+ }
+ };
+ document.addEventListener('keydown', keyDownHandler);
+ return () => {
+ document.removeEventListener('keydown', keyDownHandler);
+ };
+ }, []);
+
+ return (
+
+ );
+};
+
+export default NameInput;
diff --git a/src/components/LoginPage/ServerList/ServerList.css b/src/components/LoginPage/ServerList/ServerList.css
new file mode 100644
index 0000000..318a008
--- /dev/null
+++ b/src/components/LoginPage/ServerList/ServerList.css
@@ -0,0 +1,51 @@
+th {
+ text-align: left;
+}
+img {
+ margin-right: 5px;
+ width: 20px;
+ height: 20px;
+}
+th,
+td {
+ padding: 8px;
+ text-align: left;
+ height: 50px;
+}
+tr {
+ max-height: 50px;
+}
+
+table {
+ border-collapse: collapse;
+ width: 100%;
+}
+.server-container {
+ display: flex;
+ height: 500px;
+ overflow: scroll;
+}
+.room-name {
+ max-width: 150px;
+ overflow: hidden;
+}
+/* Firefox */
+* {
+ scrollbar-width: auto;
+ scrollbar-color: #ffffff rgba(0, 0, 0, 0.1);
+}
+
+/* Chrome, Edge, and Safari */
+*::-webkit-scrollbar {
+ background: rgba(0, 0, 0, 0);
+ width: 10px;
+}
+
+*::-webkit-scrollbar-track {
+ background: rgba(0, 0, 0, 0);
+}
+
+*::-webkit-scrollbar-thumb {
+ background-color: #ffffff;
+ border-radius: 10px;
+}
diff --git a/src/components/LoginPage/ServerList/ServerList.jsx b/src/components/LoginPage/ServerList/ServerList.jsx
new file mode 100644
index 0000000..c23601e
--- /dev/null
+++ b/src/components/LoginPage/ServerList/ServerList.jsx
@@ -0,0 +1,77 @@
+import React, { useContext, useEffect, useState } from 'react';
+import { SocketContext } from '../../../App';
+import lock from '../../../images/login-page/lock.png';
+import refresh from '../../../images/login-page/refresh.png';
+import ReactLoading from 'react-loading';
+
+import './ServerList.css';
+import NameInput from '../NameInput/NameInput';
+
+const ServerList = () => {
+ const socket = useContext(SocketContext);
+ const [rooms, setRooms] = useState([]);
+ const [joining, setJoining] = useState(false);
+ const [clickedRoom, setClickedRoom] = useState(null);
+ useEffect(() => {
+ socket.emit('room:rooms');
+ socket.on('room:rooms', data => {
+ data = JSON.parse(data);
+ setRooms(data);
+ });
+ }, []);
+
+ const getRooms = () => {
+ setRooms(null);
+ socket.emit('room:rooms');
+ };
+
+ const handleJoinClick = room => {
+ setClickedRoom(room);
+ setJoining(true);
+ };
+
+ return (
+
+
+
Server List
+
+

+
+
+
+ {rooms ? (
+
+
+
+ |
+ Server |
+ #/# |
+ Status |
+ |
+
+
+
+ {rooms.map((room, index) => (
+
+ {room.private ? : null} |
+ {room.name} |
+ {`${room.players.length}/4`} |
+ {room.isStarted ? 'started' : 'waiting'} |
+
+
+ |
+
+ ))}
+
+
+ ) : (
+
+
+
+ )}
+
+ {joining ?
: null}
+
+ );
+};
+export default ServerList;
diff --git a/src/components/NameInput.jsx b/src/components/NameInput.jsx
deleted file mode 100644
index 9f4d1eb..0000000
--- a/src/components/NameInput.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React, { useState, useContext } from "react";
-import { SocketContext } from "../App";
-
-const NameInput = () => {
- const socket = useContext(SocketContext);
- const [inputValue, setInputValue] = useState("");
- const handleInputChange = (e) => {
- setInputValue(e.target.value);
- };
- const handleButtonClick = () => {
- socket.emit("player:login", { name: inputValue });
- };
- return (
-
-
-
-
- );
-};
-
-export default NameInput;
diff --git a/src/components/navbar-components/AnimatedOverlay.jsx b/src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.jsx
similarity index 57%
rename from src/components/navbar-components/AnimatedOverlay.jsx
rename to src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.jsx
index 82b1507..ba0558a 100644
--- a/src/components/navbar-components/AnimatedOverlay.jsx
+++ b/src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.jsx
@@ -1,20 +1,16 @@
-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 (
diff --git a/src/components/navbar-components/TimerAnimation.js b/src/components/Navbar/NameContainer/AnimatedOverlay/TimerAnimation.js
similarity index 100%
rename from src/components/navbar-components/TimerAnimation.js
rename to src/components/Navbar/NameContainer/AnimatedOverlay/TimerAnimation.js
diff --git a/src/components/navbar-components/NameContainer.jsx b/src/components/Navbar/NameContainer/NameContainer.jsx
similarity index 89%
rename from src/components/navbar-components/NameContainer.jsx
rename to src/components/Navbar/NameContainer/NameContainer.jsx
index b0a68cc..14981ee 100644
--- a/src/components/navbar-components/NameContainer.jsx
+++ b/src/components/Navbar/NameContainer/NameContainer.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
-import AnimatedOverlay from './AnimatedOverlay';
+import AnimatedOverlay from './AnimatedOverlay/AnimatedOverlay';
const NameContainer = ({ player, time }) => {
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/login-page/lock.png b/src/images/login-page/lock.png
new file mode 100644
index 0000000..8e56993
Binary files /dev/null and b/src/images/login-page/lock.png differ
diff --git a/src/images/login-page/refresh.png b/src/images/login-page/refresh.png
new file mode 100644
index 0000000..25988e6
Binary files /dev/null and b/src/images/login-page/refresh.png differ
diff --git a/src/images/login-page/user.png b/src/images/login-page/user.png
new file mode 100644
index 0000000..0fa4040
Binary files /dev/null and b/src/images/login-page/user.png differ
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