socket connection established and chess logic imported from old codebase

This commit is contained in:
Moon Patel
2023-06-29 02:03:06 +05:30
parent 8e91427c1c
commit 0a06890b81
34 changed files with 848 additions and 38 deletions
+21
View File
@@ -0,0 +1,21 @@
{
"files.associations": {
"*.tcc": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cstdint": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"exception": "cpp",
"initializer_list": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"ostream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"system_error": "cpp",
"type_traits": "cpp",
"typeinfo": "cpp"
}
}
+55 -16
View File
@@ -4,6 +4,7 @@ const cors = require("cors");
const authRoutes = require("./routes/auth"); const authRoutes = require("./routes/auth");
const userRoutes = require("./routes/user"); const userRoutes = require("./routes/user");
const mongoose = require("mongoose"); const mongoose = require("mongoose");
require("dotenv").config();
mongoose mongoose
.connect("mongodb://127.0.0.1:27017/test") .connect("mongodb://127.0.0.1:27017/test")
@@ -14,27 +15,65 @@ const app = express();
const http = require("http"); const http = require("http");
const server = http.createServer(app); const server = http.createServer(app);
const { Server } = require("socket.io"); const { Server } = require("socket.io");
const { sendEmail } = require("./mail");
const { User } = require("./models/user");
const io = new Server(server, { cors: { origin: "*" } }); const io = new Server(server, { cors: { origin: "*" } });
io.on("connection", (socket) => { const activeRooms = new Map();
// console.log("Connected: ", socket.data); const pendingChallenges = new Map();
socket.broadcast.emit("game", "emitting...");
socket.emit("game", "Welcome");
socket.on("join-room", async (data) => { io.on("connection", (socket) => {
if (data?.roomid) await socket.join(roomid); console.log("Client connected:", socket.id);
else socket.emit("error", "Room id not received");
// Handle join event
socket.on("join-room", async (roomID, playerUsername, challengedPlayerUsername) => {
// room exists
if (activeRooms.has(roomID)) {
socket.emit("room-full");
return;
} else if (pendingChallenges.has(roomID)) {
const challenge = pendingChallenges.get(roomID);
pendingChallenges.delete(roomID);
let newRoom = {
id: roomID,
players: {
challenger: {
id: challenge.challengerID,
name: challenge.challengerUsername,
},
challenged: {
id: socket.id,
name: playerUsername,
},
},
};
activeRooms.set(roomID, newRoom);
io.to(roomID).emit("room-created");
} else {
// no room on pending challenges found
const challenge = {
roomID,
challengerID: socket.id,
challengerUsername: playerUsername,
challengedUsername: challengedPlayerUsername,
};
pendingChallenges.set(roomID, challenge);
console.log(challengedPlayerUsername);
// notify the challenged player
const email = (await User.findOne({ username: challengedPlayerUsername })).email;
sendEmail(
email,
`Challenge from ${playerUsername}`,
`To accept the challenge follow the link: http://localhost:5173/game/friend/${roomID}`
);
socket.emit("challenge-pending");
}
}); });
socket.on("play", (data) => { socket.on("move", (roomID, moveData) => {
console.log(data); socket.to(roomID).emit("opponent-move", moveData);
let { fromRow, fromCol, toRow, toCol, room } = data;
fromRow = 7 - fromRow;
fromCol = 7 - fromCol;
toRow = 7 - toRow;
toCol = 7 - toCol;
socket.to(room).emit("play", { fromCol, fromRow, toCol, toRow });
socket.broadcast.emit("play", { fromCol, fromRow, toCol, toRow });
}); });
}); });
-1
View File
@@ -1 +0,0 @@
{"events":[{"title":"new event","image":"https://images.squarespace-cdn.com/content/v1/56d082a760b5e95c074d11a0/8ce30633-23f2-4dc9-b301-c251cbf26d02/Mercedes+Benz+Oscar+Party?format=2500w","date":"2023-05-03","description":"hello","id":"1f24eb47-5019-4a07-9c3c-1ba18b591800"},{"title":"concert","image":"https://business.twitter.com/content/dam/business-twitter/insights/may-2018/event-targeting.png.twimg.1920.png","date":"2023-05-04","description":"lorem","id":"2035d991-0e72-456c-b6df-1aa9774caaaa"}],"users":[{"email":"m@g.com","password":"$2a$12$.GW99UfbOfISG6mQHN.8MOXtYXePsWH592XudNWU9Df9tYEV.K.QG","id":"2a744c6d-da1a-4938-ab45-144b567d5187"},{"email":"moon@gmails.com","password":"$2a$12$o7p.OS1vA66yBlkOsh.xV.ifDsItbSeBOTaN5VgjYHIUVtDvP3nbu","id":"8f394056-592e-45fc-9808-e923e1a1d60a"},{"email":"moon@gmail.com","password":"$2a$12$3t8sENEvXNvC1XeKhMsJFOsaU0vVh27wnY4hHIPlXzzOIAwMiSSJC","id":"421e3728-ac58-4fc1-86d5-70b618d01f9b"},{"email":"moonpatel2003@gmail.com","password":"$2a$12$QxG6hh0vFudhlsX3MJvXLuVfnFKBAPd15Bei42MNB2v69BwwGT3oq","id":"d77a0a0b-2b82-489e-9782-ccac490db36d"}]}
+29
View File
@@ -0,0 +1,29 @@
const nodemailer = require("nodemailer");
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: "chessroyalemail@gmail.com",
pass: process.env.MAIL_SERVER_PASSWORD,
},
});
const sendEmail = (receiverEmail, subject, data) => {
console.log(process.env.MAIL_SERVER_PASSWORD);
let mailDetails = {
from: "chessroyalemail@gmail.com",
to: receiverEmail,
subject: subject,
text: data,
};
transporter.sendMail(mailDetails, function (err, data) {
if (err) {
console.log(err);
} else {
console.log("Email sent successfully");
}
});
};
module.exports = {
sendEmail,
};
+1 -1
View File
@@ -39,7 +39,7 @@ const userSchema = new Schema(
methods: { methods: {
async getFriends() { async getFriends() {
await this.populate("friends", "username"); await this.populate("friends", "username");
console.log(this.friends); // console.log(this.friends);
return this.friends.map(friend => friend.username); return this.friends.map(friend => friend.username);
}, },
}, },
+21
View File
@@ -12,9 +12,11 @@
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"mongoose": "^7.2.1", "mongoose": "^7.2.1",
"nodemailer": "^6.9.3",
"socket.io": "^4.6.1", "socket.io": "^4.6.1",
"uuid": "^9.0.0" "uuid": "^9.0.0"
} }
@@ -211,6 +213,17 @@
"npm": "1.2.8000 || >= 1.4.16" "npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"node_modules/dotenv": {
"version": "16.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/motdotla/dotenv?sponsor=1"
}
},
"node_modules/ecdsa-sig-formatter": { "node_modules/ecdsa-sig-formatter": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -774,6 +787,14 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/nodemailer": {
"version": "6.9.3",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.3.tgz",
"integrity": "sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+2
View File
@@ -14,9 +14,11 @@
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"mongoose": "^7.2.1", "mongoose": "^7.2.1",
"nodemailer": "^6.9.3",
"socket.io": "^4.6.1", "socket.io": "^4.6.1",
"uuid": "^9.0.0" "uuid": "^9.0.0"
} }
+82 -4
View File
@@ -15,7 +15,8 @@
"@tabler/icons-react": "^2.23.0", "@tabler/icons-react": "^2.23.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.14.0" "react-router-dom": "^6.14.0",
"socket.io-client": "^4.7.1"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.37", "@types/react": "^18.0.37",
@@ -1273,6 +1274,11 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"node_modules/@tabler/icons": { "node_modules/@tabler/icons": {
"version": "2.23.0", "version": "2.23.0",
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.23.0.tgz", "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.23.0.tgz",
@@ -1704,7 +1710,6 @@
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
}, },
@@ -1762,6 +1767,26 @@
"integrity": "sha512-r6dCgNpRhPwiWlxbHzZQ/d9swfPaEJGi8ekqRBwQYaR3WmA5VkqQfBWSDDjuJU1ntO+W9tHx8OHV/96Q8e0dVw==", "integrity": "sha512-r6dCgNpRhPwiWlxbHzZQ/d9swfPaEJGi8ekqRBwQYaR3WmA5VkqQfBWSDDjuJU1ntO+W9tHx8OHV/96Q8e0dVw==",
"dev": true "dev": true
}, },
"node_modules/engine.io-client": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.1.tgz",
"integrity": "sha512-hE5wKXH8Ru4L19MbM1GgYV/2Qo54JSMh1rlJbfpa40bEWkCKNo3ol2eOtGmowcr+ysgbI7+SGL+by42Q3pt/Ng==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.1.0",
"ws": "~8.11.0",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz",
"integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/error-ex": { "node_modules/error-ex": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -3016,8 +3041,7 @@
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"dev": true
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.6", "version": "3.3.6",
@@ -3687,6 +3711,32 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/socket.io-client": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.1.tgz",
"integrity": "sha512-Qk3Xj8ekbnzKu3faejo4wk2MzXA029XppiXtTF/PkbTg+fcwaTw1PlDrTrrrU4mKoYC4dvlApOnSeyLCKwek2w==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.5.7", "version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -4127,6 +4177,34 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true "dev": true
}, },
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+2 -1
View File
@@ -17,7 +17,8 @@
"@tabler/icons-react": "^2.23.0", "@tabler/icons-react": "^2.23.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.14.0" "react-router-dom": "^6.14.0",
"socket.io-client": "^4.7.1"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.37", "@types/react": "^18.0.37",
+4 -2
View File
@@ -17,8 +17,8 @@ import Play from './pages/Play/Play'
import AuthenticationPage, { loginAction, signupAction } from './pages/Authentication/Authentication' import AuthenticationPage, { loginAction, signupAction } from './pages/Authentication/Authentication'
import { getAuthToken } from '../utils/auth' import { getAuthToken } from '../utils/auth'
import { logoutAction } from './components/Logout' import { logoutAction } from './components/Logout'
import ChallengeFriend, { playFriendAction } from './pages/Play/ChallengeFriend'
import ChessGame from './pages/Chess/ChessGame'
const router = createBrowserRouter([{ const router = createBrowserRouter([{
path: '/', path: '/',
@@ -30,11 +30,13 @@ const router = createBrowserRouter([{
{ {
path: 'play', element: <PlayLayout />, children: [ path: 'play', element: <PlayLayout />, children: [
{ index: true, element: <Play /> }, { index: true, element: <Play /> },
{ path: 'friend/:friend_username', element: <ChallengeFriend />, action: playFriendAction },
{ path: 'friend', element: <PlayFriend /> }, { path: 'friend', element: <PlayFriend /> },
{ path: 'computer', element: <div>Computer</div> }, { path: 'computer', element: <div>Computer</div> },
{ path: 'online', element: <div>Online</div> } { path: 'online', element: <div>Online</div> }
] ]
}, },
{ path: "game/friend/:roomID", element: <ChessGame /> },
{ {
path: 'settings', element: <Settings />, children: [ path: 'settings', element: <Settings />, children: [
{ index: true, element: <Profile /> }, { index: true, element: <Profile /> },
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+44
View File
@@ -0,0 +1,44 @@
import React from 'react'
import Piece from './Piece';
import { socket } from '../socket';
import { Box, Flex } from '@mantine/core';
const Cell = ({ cellProps, selectedPiece, dispatch, myColor }) => {
const { row, col, piece, marked } = cellProps;
let bgColor = (selectedPiece?.row === row && selectedPiece?.col === col) ? 'bg-gray-100' : ((row + col) % 2 ? 'bg-stone-800' : 'bg-neutral-200');
bgColor = marked && piece ? `bg-red-300` : bgColor;
const handleClick = () => {
// if (!myTurn) return;
if (piece && piece.color === myColor) {
dispatch({ type: 'SELECT_PIECE', val: { row, col, color: piece.color } }) // select piece
}
else if (!piece && selectedPiece && marked) {
let payload = { fromRow: selectedPiece.row, fromCol: selectedPiece.col, toRow: row, toCol: col };
socket.emit('move', payload);
dispatch({ type: 'MOVE_PIECE', val: payload, }); // move piece
}
else if (piece && marked && piece.color !== myColor) {
let payload = { fromRow: selectedPiece.row, fromCol: selectedPiece.col, toRow: row, toCol: col, color: piece.color };
socket.emit('move', payload);
dispatch({ type: 'CAPTURE_PIECE', val: payload }) // capture piece
}
}
const content = marked ? <Mark /> : <Piece piece={piece} />;
return (
<Flex onClick={handleClick} w="75px" h="75px" bg={(row + col) % 2 ? 'gray' : 'white'} className={`w-12 h-12 md:w-20 md:h-20 ${bgColor} flex justify-center items-center relative`}>
{content}
</Flex>
)
}
export const Mark = () => {
return (
<Box w="33%" h="33%" sx={{ backgroundColor: 'gray', borderRadius: '100%' }} m="auto"></Box>
)
}
export default Cell
+53
View File
@@ -0,0 +1,53 @@
import { Avatar, Flex, Image, Loader, NavLink, Text, Title } from '@mantine/core';
import { Link } from 'react-router-dom';
import React, { useEffect, useState } from 'react';
const FriendsList = () => {
const [friends, setFriends] = useState(null);
useEffect(() => {
const abortController = new AbortController();
let response = null;
const fetchData = async () => {
let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/user1/friends`;
try {
response = await fetch(url, { signal: abortController.signal });
const data = await response.json();
if (data.success) {
setFriends(data.friends);
} else {
throw data;
}
} catch (error) {
if (error.message === 'The user aborted a request.');
else {
console.log('Error fetching data');
throw error;
}
}
}
fetchData();
return () => {
if (!response) {
abortController.abort();
}
}
}, []);
return (
<Flex sx={{ flexGrow: '1' }} height="100%" justify="start" my="md" align="start" direction="column">
<Title px="sm" pt="md" order={3}>Friends</Title>
{
friends ?
friends.map((friend, index) => <NavLink key={index} component={Link} to="/play/friend/moonpatel" p='5px' icon={<Avatar size='sm' color='blue' children="M" />} label={<Text fw={700}>{friend}</Text>} />)
:
<Loader m="20px" variant='dots' color='lime' />
}
</Flex>
)
}
export default FriendsList
+33
View File
@@ -0,0 +1,33 @@
import { Image } from '@mantine/core';
import React from 'react';
const Piece = ({ piece }) => {
if (piece === null) return null;
const { type, color } = piece;
let logo;
switch (type) {
case 'P':
logo = color === 'W' ? 'pawn_white' : 'pawn_black';
break;
case 'R':
logo = color === 'W' ? 'rook_white' : 'rook_black';
break;
case 'N':
logo = color === 'W' ? 'knight_white' : 'knight_black';
break;
case 'B':
logo = color === 'W' ? 'bishop_white' : 'bishop_black';
break;
case 'Q':
logo = color === 'W' ? 'queen_white' : 'queen_black';
break;
case 'K':
logo = color === 'W' ? 'king_white' : 'king_black';
break;
}
return (
<Image src={`/src/assets/${logo}.png`} />
)
}
export default Piece
@@ -8,7 +8,7 @@ const AuthenticationPage = (props) => {
const { isLogin } = props; const { isLogin } = props;
return ( return (
<Container maw="100%" bg="gray" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}> <Container maw="100%" sx={(theme) => ({ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', backgroundImage: theme.fn.gradient({ from: 'blue', to: 'teal' }) })}>
<Card shadow="md" p="lg" style={{ maxWidth: 400, width: '100%' }}> <Card shadow="md" p="lg" style={{ maxWidth: 400, width: '100%' }}>
<Text align="center" variant="h4" style={{ marginBottom: 20 }}> <Text align="center" variant="h4" style={{ marginBottom: 20 }}>
{isLogin ? 'Login' : 'Sign Up'} {isLogin ? 'Login' : 'Sign Up'}
+30
View File
@@ -0,0 +1,30 @@
import { Avatar, Flex, Image, NavLink, Text, Title } from '@mantine/core'
import React from 'react'
import ChessBoard from '../Play/ChessBoard'
const ChessGame = () => {
return (
<Flex gap="xl" justify='center' align='center' wrap='nowrap' direction='row'>
<Flex gap="xs" justify='center' align='start' wrap='nowrap' direction='column' >
<NavLink
p="2px"
label={"username"}
icon={<Avatar radius="3px" />}
description={"description"}
/>
<ChessBoard />
<NavLink
p="2px"
label={"username"}
icon={<Avatar radius="3px" />}
description={"description"}
/>
</Flex>
<Flex w="450px" bg='gray' h="600px" sx={{ borderRadius: '10px' }}>
<Title>Game Data</Title>
</Flex>
</Flex>
)
}
export default ChessGame
@@ -0,0 +1,49 @@
import { Avatar, Button, Card, Flex, Image, Select, Text, TextInput, Title } from '@mantine/core'
import React from 'react'
import FriendsList from '../../components/FriendsList'
import { IconSearch } from '@tabler/icons-react'
import { Form, Link, redirect, useParams, useSearchParams } from 'react-router-dom'
import { socket } from '../../socket'
const ChallengeFriend = () => {
const params = useParams();
let friend_username = params["friend_username"];
return (
<Card
sx={{
width: '450px',
height: '600px',
textAlign: 'center'
}}
>
<Flex align="center" direction="column" justify="center" gap="xs" my="lg">
<Title order={2}>Play vs {friend_username}</Title>
<Avatar mt="lg" color='lime' size="100px">{friend_username[0].toUpperCase()}</Avatar>
<Text>{friend_username}</Text>
</Flex>
<Select label="Time limit" placeholder='Time limit' value='10' data={['5', '10', '15', '30']} />
<Select value='W' onChange={(evt) => setColor(evt.target.value)} my="20px" label={<Text mx="auto" order={3}>I play as</Text>} placeholder='choose your color' data={[
{ value: 'W', label: 'White' },
{ value: 'B', label: 'Black' },
{ value: 'RANDOM', label: 'Random' }
]} />
<Form action={`/play/friend/${friend_username}`} method='POST'>
<Button color='lime' type='submit' >Challenge</Button>
</Form>
</Card>
)
}
export const playFriendAction = ({ request, params }) => {
let roomID = Math.floor(Math.random() * 1_000_000_000).toString();
// const req = new URL(request);
socket.connect();
socket.emit('join-room', roomID, 'user1', params.friend_username);
// socket.on('pending-challenge', () => {
// })
return redirect(`/game/friend/${roomID}`);
}
export default ChallengeFriend;
+104
View File
@@ -0,0 +1,104 @@
import React, { useEffect, useReducer, useRef } from 'react';
import { blackColor, chessBoardInit, getPieceHint, pieces, whiteColor } from '../../../utils/chess';
import Cell from '../../components/Cell';
import { socket } from '../../socket';
import { Flex } from '@mantine/core';
let myColor = 'W';
const reducer = (state, action) => {
if (state.capturedPieces.length && state.capturedPieces.at(-1).type === pieces.king) return state;
switch (action.type) {
case 'SELECT_PIECE':
{
let { row, col, color } = action.val;
let selectedPiece = { row, col, color };
let possibleMoves = getPieceHint(state.chessBoard, { row, col, color, type: state.chessBoard[row][col].type }, color).movePos;
return { ...state, selectedPiece, possibleMoves };
}
break;
case 'MOVE_PIECE':
{
let { fromRow, fromCol, toRow, toCol } = action.val;
let newChessBoard = state.chessBoard.map(row => row.slice());
let piece = state.chessBoard[fromRow][fromCol];
newChessBoard[toRow][toCol] = piece;
newChessBoard[fromRow][fromCol] = null;
return { ...state, chessBoard: newChessBoard, possibleMoves: [], selectedPiece: null, myTurn: !state.myTurn };
}
break;
case 'CAPTURE_PIECE':
{
let { fromRow, fromCol, toRow, toCol } = action.val;
let newChessBoard = state.chessBoard.map(row => row.slice());
let capturedPieces = [...state.capturedPieces, state.chessBoard[toRow][toCol]];
newChessBoard[toRow][toCol] = state.chessBoard[fromRow][fromCol];
newChessBoard[fromRow][fromCol] = null;
return { ...state, chessBoard: newChessBoard, capturedPieces, possibleMoves: [], selectedPiece: null, myTurn: !state.myTurn };
}
break;
default:
return state;
}
}
const ChessBoard = () => {
const [gameState, dispatch] = useReducer(reducer, {
chessBoard: chessBoardInit(myColor), selectedPiece: null, possibleMoves: [], capturedPieces: [], myTurn: myColor === whiteColor
});
const chessBoardRef = useRef(gameState.chessBoard);
chessBoardRef.current = gameState.chessBoard;
useEffect(() => {
function handleOpponentMove(data) {
let { fromCol, fromRow, toCol, toRow } = data;
if (chessBoardRef.current[toRow][toCol] === null) {
console.log('Moving piece: ', data)
dispatch({ type: 'MOVE_PIECE', val: { fromRow, fromCol, toRow, toCol } });
} else if (myColor === chessBoardRef.current[toRow][toCol].color) {
dispatch({ type: 'CAPTURE_PIECE', val: { fromRow, fromCol, toRow, toCol } });
}
}
socket.on('move', handleOpponentMove)
return () => {
socket.off('move', handleOpponentMove);
}
}, []);
return (
<React.Fragment>
<Flex w="600px">
<div>
{gameState.chessBoard.map((line, row) => {
return (
<Flex className='flex' key={row * 2}>
{line.map((cell, col) => {
let marked = null;
for (let k = 0; k < gameState.possibleMoves.length; k++) {
if (gameState.possibleMoves[k].row === row && gameState.possibleMoves[k].col === col) {
marked = true;
break;
}
}
let piece = cell ? { type: cell.type, color: cell.color } : null;
return <Cell
key={col * 3 + 1}
selectedPiece={gameState.selectedPiece}
cellProps={{ row, col, piece, marked }}
dispatch={dispatch}
myColor={myColor}
myTurn={gameState.myTurn}
/>
})}
</Flex>
)
})}
</div>
</Flex>
</React.Fragment>
)
}
export default ChessBoard
+2 -2
View File
@@ -6,7 +6,7 @@ const Play = () => {
return ( return (
<Card sx={{ <Card sx={{
width: '450px', width: '450px',
height: '100%', height: '600px',
textAlign: 'center' textAlign: 'center'
}}> }}>
<Flex gap="5px" px="20px" justify='center' align='center' wrap='nowrap' direction='column'> <Flex gap="5px" px="20px" justify='center' align='center' wrap='nowrap' direction='column'>
@@ -28,7 +28,7 @@ const CardItem = ({ label, description, src, to }) => {
label={label} label={label}
icon={<Image src={src} width={50} />} icon={<Image src={src} width={50} />}
description={description} description={description}
sx={{ backgroundColor: 'gray', borderRadius: '5px' }} sx={{ backgroundColor: 'black', borderRadius: '5px' }}
/> />
) )
} }
+7 -10
View File
@@ -1,8 +1,8 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { Button, Card, CopyButton, Flex, Group, Image, Modal, NativeSelect, NavLink, Select, Text, TextInput, Title } from '@mantine/core' import { Button, Card, CopyButton, Flex, Group, Image, Modal, NativeSelect, NavLink, Select, Text, TextInput, Title } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks' import { useDisclosure } from '@mantine/hooks'
import { IconSearch } from '@tabler/icons-react' import { IconSearch, IconUserCircle } from '@tabler/icons-react'
import { PersonIcon } from '@radix-ui/react-icons' import FriendsList from '../../components/FriendsList'
const createChallengeLink = (color) => { const createChallengeLink = (color) => {
let challengeLink = Math.floor(Math.random() * 100_000_000).toString(); let challengeLink = Math.floor(Math.random() * 100_000_000).toString();
@@ -46,10 +46,7 @@ const PlayFriend = () => {
<Title order={2}>Play a Friend</Title> <Title order={2}>Play a Friend</Title>
</Flex> </Flex>
<TextInput my="5px" placeholder="Search by email or username" icon={<IconSearch />} /> <TextInput my="5px" placeholder="Search by email or username" icon={<IconSearch />} />
<Flex sx={{ flexGrow: '1' }} height="100%" justify="start" align="start" direction="column"> <FriendsList />
<Title px="md" pt="md" order={3}>Friends</Title>
{friends.map((friend, index) => <NavLink key={index} icon={friend.avatar} label={<Text fw={700}>{friend.username}</Text>} />)}
</Flex>
<Flex direction='column' gap='10px'> <Flex direction='column' gap='10px'>
<Button color='lime' onClick={open}>Create Challenge Link</Button> <Button color='lime' onClick={open}>Create Challenge Link</Button>
<Button color='lime'>Join using Challenge Link</Button> <Button color='lime'>Join using Challenge Link</Button>
@@ -60,10 +57,10 @@ const PlayFriend = () => {
} }
const friends = [ const friends = [
{ avatar: <PersonIcon />, username: "friend", rating: 100 }, { avatar: <IconUserCircle />, username: "friend", rating: 100 },
{ avatar: <PersonIcon />, username: "friend", rating: 100 }, { avatar: <IconUserCircle />, username: "friend", rating: 100 },
{ avatar: <PersonIcon />, username: "friend", rating: 100 }, { avatar: <IconUserCircle />, username: "friend", rating: 100 },
{ avatar: <PersonIcon />, username: "friend", rating: 100 }, { avatar: <IconUserCircle />, username: "friend", rating: 100 },
] ]
export default PlayFriend export default PlayFriend
+3
View File
@@ -0,0 +1,3 @@
import { io } from "socket.io-client";
const url = import.meta.env.VITE_BACKEND_HOST
export const socket = io(url, { autoConnect: false });
+305
View File
@@ -0,0 +1,305 @@
const pawn = "P",
rook = "R",
knight = "N",
bishop = "B",
queen = "Q",
king = "K";
export const pieces = {
pawn,
rook,
knight,
bishop,
queen,
king,
};
export const whiteColor = "W",
blackColor = "B";
export function chessBoardInit(myColor) {
let opColor = myColor === whiteColor ? blackColor : whiteColor;
const chessBoardMatrix = [
[
{ color: opColor, type: rook },
{ color: opColor, type: knight },
{ color: opColor, type: bishop },
{ color: opColor, type: myColor === whiteColor ? queen : king },
{ color: opColor, type: myColor === whiteColor ? king : queen },
{ color: opColor, type: bishop },
{ color: opColor, type: knight },
{ color: opColor, type: rook },
],
Array(8).fill({ color: opColor, type: pawn }),
[null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null],
Array(8).fill({ color: myColor, type: pawn }),
[
{ color: myColor, type: rook },
{ color: myColor, type: knight },
{ color: myColor, type: bishop },
{ color: myColor, type: myColor === whiteColor ? queen : king },
{ color: myColor, type: myColor === whiteColor ? king : queen },
{ color: myColor, type: bishop },
{ color: myColor, type: knight },
{ color: myColor, type: rook },
],
];
return chessBoardMatrix;
}
function inBoard(i, j) {
if (i >= 0 && i < 8 && j >= 0 && j < 8) return true;
else return false;
}
function isBlocked(chessBoard, chessPiece, i, j) {
if (chessBoard[i][j] === null) return false;
else if (chessBoard[i][j].color === chessPiece.color) return true;
else return false;
}
function isAttacking(chessBoard, chessPiece, i, j) {
if (chessBoard[i][j] === null) return false;
else if (chessBoard[i][j].color !== chessPiece.color) return true;
else return false;
}
function getPawnHint(chessBoard, chessPiece, myColor) {
const { row, col } = chessPiece;
let movePos = [];
if (chessPiece.color === myColor) {
// for moving forward
if (inBoard(row - 1, col) && chessBoard[row - 1][col] === null && movePos.push({ row: row - 1, col })) {
chessPiece.row === 6 &&
chessBoard[row - 2][col] === null &&
inBoard(row - 2, col) &&
!isBlocked(chessBoard, chessPiece, row - 1, col) &&
movePos.push({ row: row - 2, col });
}
// for killing opponent piece
if (
inBoard(row - 1, col + 1) &&
chessBoard[row - 1][col + 1]?.type &&
chessBoard[row - 1][col + 1]?.color !== myColor
)
inBoard(row - 1, col + 1) && movePos.push({ row: row - 1, col: col + 1 });
if (
inBoard(row - 1, col - 1) &&
chessBoard[row - 1][col - 1]?.type &&
chessBoard[row - 1][col - 1]?.color !== myColor
)
inBoard(row - 1, col - 1) && movePos.push({ row: row - 1, col: col - 1 });
} else {
// for moving forward
if (inBoard(row + 1, col) && chessBoard[row + 1][col] === null && movePos.push({ row: row + 1, col })) {
chessPiece.row === 1 &&
chessBoard[row + 2][col] === null &&
inBoard(row + 2, col) &&
movePos.push({ row: row + 2, col });
}
// for killing opponent piece
if (
inBoard(row + 1, col + 1) &&
chessBoard[row + 1][col + 1]?.type &&
chessBoard[row + 1][col + 1]?.color === myColor
)
inBoard(row + 1, col + 1) && movePos.push({ row: row + 1, col: col + 1 });
if (
inBoard(row + 1, col - 1) &&
chessBoard[row + 1][col - 1]?.type &&
chessBoard[row + 1][col - 1]?.color === myColor
)
inBoard(row + 1, col - 1) && movePos.push({ row: row + 1, col: col - 1 });
}
return { movePos };
}
function getRookHint(chessBoard, chessPiece, myColor) {
const { row, col, color } = chessPiece;
let movePos = [];
let i = row,
j = col;
while (inBoard(++i, j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(--i, j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(i, ++j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(i, --j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
console.log(movePos);
return { movePos };
}
function getKnightHint(chessBoard, chessPiece, myColor) {
const { row, col, color } = chessPiece;
let movePos = [];
if (inBoard(row + 2, col + 1) && !isBlocked(chessBoard, chessPiece, row + 2, col + 1))
movePos.push({ row: row + 2, col: col + 1 });
if (inBoard(row + 2, col - 1) && !isBlocked(chessBoard, chessPiece, row + 2, col - 1))
movePos.push({ row: row + 2, col: col - 1 });
if (inBoard(row - 2, col + 1) && !isBlocked(chessBoard, chessPiece, row - 2, col + 1))
movePos.push({ row: row - 2, col: col + 1 });
if (inBoard(row - 2, col - 1) && !isBlocked(chessBoard, chessPiece, row - 2, col - 1))
movePos.push({ row: row - 2, col: col - 1 });
if (inBoard(row + 1, col + 2) && !isBlocked(chessBoard, chessPiece, row + 1, col + 2))
movePos.push({ row: row + 1, col: col + 2 });
if (inBoard(row - 1, col + 2) && !isBlocked(chessBoard, chessPiece, row - 1, col + 2))
movePos.push({ row: row - 1, col: col + 2 });
if (inBoard(row + 1, col - 2) && !isBlocked(chessBoard, chessPiece, row + 1, col - 2))
movePos.push({ row: row + 1, col: col - 2 });
if (inBoard(row - 1, col - 2) && !isBlocked(chessBoard, chessPiece, row - 1, col - 2))
movePos.push({ row: row - 1, col: col - 2 });
return { movePos };
}
function getBishopHint(chessBoard, chessPiece, myColor) {
const { row, col, color } = chessPiece;
let movePos = [];
let i = row,
j = col;
while (inBoard(++i, ++j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(++i, --j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(--i, ++j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(--i, --j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
return { movePos };
}
function getQueenHint(chessBoard, chessPiece, myColor) {
const { row, col, color } = chessPiece;
let movePos = [];
let i = row,
j = col;
while (inBoard(++i, j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(--i, j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(i, ++j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(i, --j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(++i, ++j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(++i, --j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(--i, ++j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
i = row;
j = col;
while (inBoard(--i, --j) && !isBlocked(chessBoard, chessPiece, i, j)) {
movePos.push({ row: i, col: j });
if (isAttacking(chessBoard, chessPiece, i, j)) break;
}
return { movePos };
}
function getKingHint(chessBoard, chessPiece, myColor) {
const { row, col } = chessPiece;
let movePos = [];
if (inBoard(row, col + 1) && !isBlocked(chessBoard, chessPiece, row, col + 1))
movePos.push({ row: row, col: col + 1 });
if (inBoard(row, col - 1) && !isBlocked(chessBoard, chessPiece, row, col - 1))
movePos.push({ row: row, col: col - 1 });
if (inBoard(row + 1, col + 1) && !isBlocked(chessBoard, chessPiece, row + 1, col + 1))
movePos.push({ row: row + 1, col: col + 1 });
if (inBoard(row + 1, col) && !isBlocked(chessBoard, chessPiece, row + 1, col))
movePos.push({ row: row + 1, col: col });
if (inBoard(row + 1, col - 1) && !isBlocked(chessBoard, chessPiece, row + 1, col - 1))
movePos.push({ row: row + 1, col: col - 1 });
if (inBoard(row - 1, col + 1) && !isBlocked(chessBoard, chessPiece, row - 1, col + 1))
movePos.push({ row: row - 1, col: col + 1 });
if (inBoard(row - 1, col) && !isBlocked(chessBoard, chessPiece, row - 1, col))
movePos.push({ row: row - 1, col: col });
if (inBoard(row - 1, col - 1) && !isBlocked(chessBoard, chessPiece, row - 1, col - 1))
movePos.push({ row: row - 1, col: col - 1 });
return { movePos };
}
export function getPieceHint(chessBoard, chessPiece, myColor) {
switch (chessPiece.type) {
case pawn:
return getPawnHint(chessBoard, chessPiece, myColor);
case rook:
return getRookHint(chessBoard, chessPiece, myColor);
case knight:
return getKnightHint(chessBoard, chessPiece, myColor);
case bishop:
return getBishopHint(chessBoard, chessPiece, myColor);
case queen:
return getQueenHint(chessBoard, chessPiece, myColor);
case king:
return getKingHint(chessBoard, chessPiece, myColor);
}
return [];
}