diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9ae6313
--- /dev/null
+++ b/.vscode/settings.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/backend/app.js b/backend/app.js
index 3343cfe..59a5613 100644
--- a/backend/app.js
+++ b/backend/app.js
@@ -4,6 +4,7 @@ const cors = require("cors");
const authRoutes = require("./routes/auth");
const userRoutes = require("./routes/user");
const mongoose = require("mongoose");
+require("dotenv").config();
mongoose
.connect("mongodb://127.0.0.1:27017/test")
@@ -14,27 +15,65 @@ const app = express();
const http = require("http");
const server = http.createServer(app);
const { Server } = require("socket.io");
+const { sendEmail } = require("./mail");
+const { User } = require("./models/user");
const io = new Server(server, { cors: { origin: "*" } });
-io.on("connection", (socket) => {
- // console.log("Connected: ", socket.data);
- socket.broadcast.emit("game", "emitting...");
- socket.emit("game", "Welcome");
+const activeRooms = new Map();
+const pendingChallenges = new Map();
- socket.on("join-room", async (data) => {
- if (data?.roomid) await socket.join(roomid);
- else socket.emit("error", "Room id not received");
+io.on("connection", (socket) => {
+ console.log("Client connected:", socket.id);
+
+ // 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) => {
- console.log(data);
- 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 });
+ socket.on("move", (roomID, moveData) => {
+ socket.to(roomID).emit("opponent-move", moveData);
});
});
diff --git a/backend/events.json b/backend/events.json
deleted file mode 100644
index 210b635..0000000
--- a/backend/events.json
+++ /dev/null
@@ -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"}]}
\ No newline at end of file
diff --git a/backend/mail.js b/backend/mail.js
new file mode 100644
index 0000000..8cfa8f8
--- /dev/null
+++ b/backend/mail.js
@@ -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,
+};
diff --git a/backend/models/user.js b/backend/models/user.js
index 26f7e91..f467b4d 100644
--- a/backend/models/user.js
+++ b/backend/models/user.js
@@ -39,7 +39,7 @@ const userSchema = new Schema(
methods: {
async getFriends() {
await this.populate("friends", "username");
- console.log(this.friends);
+ // console.log(this.friends);
return this.friends.map(friend => friend.username);
},
},
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 4cdb6cc..67d84dc 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -12,9 +12,11 @@
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
+ "dotenv": "^16.3.1",
"express": "^4.18.2",
"jsonwebtoken": "^8.5.1",
"mongoose": "^7.2.1",
+ "nodemailer": "^6.9.3",
"socket.io": "^4.6.1",
"uuid": "^9.0.0"
}
@@ -211,6 +213,17 @@
"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": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -774,6 +787,14 @@
"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": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
diff --git a/backend/package.json b/backend/package.json
index 4330c71..10a5959 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -14,9 +14,11 @@
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
+ "dotenv": "^16.3.1",
"express": "^4.18.2",
"jsonwebtoken": "^8.5.1",
"mongoose": "^7.2.1",
+ "nodemailer": "^6.9.3",
"socket.io": "^4.6.1",
"uuid": "^9.0.0"
}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 7d74cda..b40262d 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -15,7 +15,8 @@
"@tabler/icons-react": "^2.23.0",
"react": "^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": {
"@types/react": "^18.0.37",
@@ -1273,6 +1274,11 @@
"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": {
"version": "2.23.0",
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.23.0.tgz",
@@ -1704,7 +1710,6 @@
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
"dependencies": {
"ms": "2.1.2"
},
@@ -1762,6 +1767,26 @@
"integrity": "sha512-r6dCgNpRhPwiWlxbHzZQ/d9swfPaEJGi8ekqRBwQYaR3WmA5VkqQfBWSDDjuJU1ntO+W9tHx8OHV/96Q8e0dVw==",
"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": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -3016,8 +3041,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/nanoid": {
"version": "3.3.6",
@@ -3687,6 +3711,32 @@
"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": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -4127,6 +4177,34 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"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": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 5c5b2f5..875c587 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -17,7 +17,8 @@
"@tabler/icons-react": "^2.23.0",
"react": "^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": {
"@types/react": "^18.0.37",
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 460419e..123da4f 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -17,8 +17,8 @@ import Play from './pages/Play/Play'
import AuthenticationPage, { loginAction, signupAction } from './pages/Authentication/Authentication'
import { getAuthToken } from '../utils/auth'
import { logoutAction } from './components/Logout'
-
-
+import ChallengeFriend, { playFriendAction } from './pages/Play/ChallengeFriend'
+import ChessGame from './pages/Chess/ChessGame'
const router = createBrowserRouter([{
path: '/',
@@ -30,11 +30,13 @@ const router = createBrowserRouter([{
{
path: 'play', element: , children: [
{ index: true, element: },
+ { path: 'friend/:friend_username', element: , action: playFriendAction },
{ path: 'friend', element: },
{ path: 'computer', element:
Computer
},
{ path: 'online', element: Online
}
]
},
+ { path: "game/friend/:roomID", element: },
{
path: 'settings', element: , children: [
{ index: true, element: },
diff --git a/frontend/src/assets/bishop_black.png b/frontend/src/assets/bishop_black.png
new file mode 100644
index 0000000..df2e407
Binary files /dev/null and b/frontend/src/assets/bishop_black.png differ
diff --git a/frontend/src/assets/bishop_white.png b/frontend/src/assets/bishop_white.png
new file mode 100644
index 0000000..5e26a38
Binary files /dev/null and b/frontend/src/assets/bishop_white.png differ
diff --git a/frontend/src/assets/king_black.png b/frontend/src/assets/king_black.png
new file mode 100644
index 0000000..35637f5
Binary files /dev/null and b/frontend/src/assets/king_black.png differ
diff --git a/frontend/src/assets/king_white.png b/frontend/src/assets/king_white.png
new file mode 100644
index 0000000..44ded88
Binary files /dev/null and b/frontend/src/assets/king_white.png differ
diff --git a/frontend/src/assets/knight_black.png b/frontend/src/assets/knight_black.png
new file mode 100644
index 0000000..e79df05
Binary files /dev/null and b/frontend/src/assets/knight_black.png differ
diff --git a/frontend/src/assets/knight_white.png b/frontend/src/assets/knight_white.png
new file mode 100644
index 0000000..e382675
Binary files /dev/null and b/frontend/src/assets/knight_white.png differ
diff --git a/frontend/src/assets/pawn_black.png b/frontend/src/assets/pawn_black.png
new file mode 100644
index 0000000..bb3cfdf
Binary files /dev/null and b/frontend/src/assets/pawn_black.png differ
diff --git a/frontend/src/assets/pawn_white.png b/frontend/src/assets/pawn_white.png
new file mode 100644
index 0000000..6d1b1fe
Binary files /dev/null and b/frontend/src/assets/pawn_white.png differ
diff --git a/frontend/src/assets/queen_black.png b/frontend/src/assets/queen_black.png
new file mode 100644
index 0000000..a2ed748
Binary files /dev/null and b/frontend/src/assets/queen_black.png differ
diff --git a/frontend/src/assets/queen_white.png b/frontend/src/assets/queen_white.png
new file mode 100644
index 0000000..bdc6ff3
Binary files /dev/null and b/frontend/src/assets/queen_white.png differ
diff --git a/frontend/src/assets/rook_black.png b/frontend/src/assets/rook_black.png
new file mode 100644
index 0000000..722db00
Binary files /dev/null and b/frontend/src/assets/rook_black.png differ
diff --git a/frontend/src/assets/rook_white.png b/frontend/src/assets/rook_white.png
new file mode 100644
index 0000000..49b7d0f
Binary files /dev/null and b/frontend/src/assets/rook_white.png differ
diff --git a/frontend/src/assets/sprites.png b/frontend/src/assets/sprites.png
new file mode 100644
index 0000000..f2d3137
Binary files /dev/null and b/frontend/src/assets/sprites.png differ
diff --git a/frontend/src/components/Cell.jsx b/frontend/src/components/Cell.jsx
new file mode 100644
index 0000000..381f79d
--- /dev/null
+++ b/frontend/src/components/Cell.jsx
@@ -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 ? : ;
+
+ return (
+
+ {content}
+
+ )
+}
+
+export const Mark = () => {
+ return (
+
+ )
+}
+
+
+export default Cell
\ No newline at end of file
diff --git a/frontend/src/components/FriendsList.jsx b/frontend/src/components/FriendsList.jsx
new file mode 100644
index 0000000..829939d
--- /dev/null
+++ b/frontend/src/components/FriendsList.jsx
@@ -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 (
+
+ Friends
+
+ {
+ friends ?
+ friends.map((friend, index) => } label={{friend}} />)
+ :
+
+ }
+
+ )
+}
+
+export default FriendsList
\ No newline at end of file
diff --git a/frontend/src/components/Piece.jsx b/frontend/src/components/Piece.jsx
new file mode 100644
index 0000000..6833801
--- /dev/null
+++ b/frontend/src/components/Piece.jsx
@@ -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 (
+
+ )
+}
+
+export default Piece
\ No newline at end of file
diff --git a/frontend/src/pages/Authentication/Authentication.jsx b/frontend/src/pages/Authentication/Authentication.jsx
index 721837a..c649932 100644
--- a/frontend/src/pages/Authentication/Authentication.jsx
+++ b/frontend/src/pages/Authentication/Authentication.jsx
@@ -8,7 +8,7 @@ const AuthenticationPage = (props) => {
const { isLogin } = props;
return (
-
+ ({ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', backgroundImage: theme.fn.gradient({ from: 'blue', to: 'teal' }) })}>
{isLogin ? 'Login' : 'Sign Up'}
diff --git a/frontend/src/pages/Chess/ChessGame.jsx b/frontend/src/pages/Chess/ChessGame.jsx
new file mode 100644
index 0000000..cc40e38
--- /dev/null
+++ b/frontend/src/pages/Chess/ChessGame.jsx
@@ -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 (
+
+
+ }
+ description={"description"}
+ />
+
+ }
+ description={"description"}
+ />
+
+
+ Game Data
+
+
+ )
+}
+
+export default ChessGame
\ No newline at end of file
diff --git a/frontend/src/pages/Play/ChallengeFriend.jsx b/frontend/src/pages/Play/ChallengeFriend.jsx
new file mode 100644
index 0000000..071131c
--- /dev/null
+++ b/frontend/src/pages/Play/ChallengeFriend.jsx
@@ -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 (
+
+
+ Play vs {friend_username}
+ {friend_username[0].toUpperCase()}
+ {friend_username}
+
+
+
+ )
+}
+
+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;
\ No newline at end of file
diff --git a/frontend/src/pages/Play/ChessBoard.jsx b/frontend/src/pages/Play/ChessBoard.jsx
new file mode 100644
index 0000000..974ed2a
--- /dev/null
+++ b/frontend/src/pages/Play/ChessBoard.jsx
@@ -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 (
+
+
+
+ {gameState.chessBoard.map((line, row) => {
+ return (
+
+ {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 |
+ })}
+
+ )
+ })}
+
+
+
+ )
+}
+
+export default ChessBoard
\ No newline at end of file
diff --git a/frontend/src/pages/Play/Play.jsx b/frontend/src/pages/Play/Play.jsx
index b5dcf4a..cee76fc 100644
--- a/frontend/src/pages/Play/Play.jsx
+++ b/frontend/src/pages/Play/Play.jsx
@@ -6,7 +6,7 @@ const Play = () => {
return (
@@ -28,7 +28,7 @@ const CardItem = ({ label, description, src, to }) => {
label={label}
icon={}
description={description}
- sx={{ backgroundColor: 'gray', borderRadius: '5px' }}
+ sx={{ backgroundColor: 'black', borderRadius: '5px' }}
/>
)
}
diff --git a/frontend/src/pages/Play/PlayFriend.jsx b/frontend/src/pages/Play/PlayFriend.jsx
index 318e5fe..3c57ac5 100644
--- a/frontend/src/pages/Play/PlayFriend.jsx
+++ b/frontend/src/pages/Play/PlayFriend.jsx
@@ -1,8 +1,8 @@
import React, { useState } from 'react'
import { Button, Card, CopyButton, Flex, Group, Image, Modal, NativeSelect, NavLink, Select, Text, TextInput, Title } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
-import { IconSearch } from '@tabler/icons-react'
-import { PersonIcon } from '@radix-ui/react-icons'
+import { IconSearch, IconUserCircle } from '@tabler/icons-react'
+import FriendsList from '../../components/FriendsList'
const createChallengeLink = (color) => {
let challengeLink = Math.floor(Math.random() * 100_000_000).toString();
@@ -46,10 +46,7 @@ const PlayFriend = () => {
Play a Friend
} />
-
- Friends
- {friends.map((friend, index) => {friend.username}} />)}
-
+
@@ -60,10 +57,10 @@ const PlayFriend = () => {
}
const friends = [
- { avatar: , username: "friend", rating: 100 },
- { avatar: , username: "friend", rating: 100 },
- { avatar: , username: "friend", rating: 100 },
- { avatar: , username: "friend", rating: 100 },
+ { avatar: , username: "friend", rating: 100 },
+ { avatar: , username: "friend", rating: 100 },
+ { avatar: , username: "friend", rating: 100 },
+ { avatar: , username: "friend", rating: 100 },
]
export default PlayFriend
\ No newline at end of file
diff --git a/frontend/src/socket.js b/frontend/src/socket.js
new file mode 100644
index 0000000..c8aaa64
--- /dev/null
+++ b/frontend/src/socket.js
@@ -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 });
diff --git a/frontend/utils/chess.js b/frontend/utils/chess.js
new file mode 100644
index 0000000..3f967dd
--- /dev/null
+++ b/frontend/utils/chess.js
@@ -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 [];
+}
\ No newline at end of file