From 0a06890b815bc2bbae9d3fd19374440d994c5bab Mon Sep 17 00:00:00 2001 From: Moon Patel Date: Thu, 29 Jun 2023 02:03:06 +0530 Subject: [PATCH] socket connection established and chess logic imported from old codebase --- .vscode/settings.json | 21 ++ backend/app.js | 71 +++- backend/events.json | 1 - backend/mail.js | 29 ++ backend/models/user.js | 2 +- backend/package-lock.json | 21 ++ backend/package.json | 2 + frontend/package-lock.json | 86 ++++- frontend/package.json | 3 +- frontend/src/App.jsx | 6 +- frontend/src/assets/bishop_black.png | Bin 0 -> 1395 bytes frontend/src/assets/bishop_white.png | Bin 0 -> 1643 bytes frontend/src/assets/king_black.png | Bin 0 -> 1453 bytes frontend/src/assets/king_white.png | Bin 0 -> 1707 bytes frontend/src/assets/knight_black.png | Bin 0 -> 1563 bytes frontend/src/assets/knight_white.png | Bin 0 -> 1682 bytes frontend/src/assets/pawn_black.png | Bin 0 -> 1168 bytes frontend/src/assets/pawn_white.png | Bin 0 -> 1183 bytes frontend/src/assets/queen_black.png | Bin 0 -> 1901 bytes frontend/src/assets/queen_white.png | Bin 0 -> 2171 bytes frontend/src/assets/rook_black.png | Bin 0 -> 1303 bytes frontend/src/assets/rook_white.png | Bin 0 -> 1490 bytes frontend/src/assets/sprites.png | Bin 0 -> 17013 bytes frontend/src/components/Cell.jsx | 44 +++ frontend/src/components/FriendsList.jsx | 53 +++ frontend/src/components/Piece.jsx | 33 ++ .../pages/Authentication/Authentication.jsx | 2 +- frontend/src/pages/Chess/ChessGame.jsx | 30 ++ frontend/src/pages/Play/ChallengeFriend.jsx | 49 +++ frontend/src/pages/Play/ChessBoard.jsx | 104 ++++++ frontend/src/pages/Play/Play.jsx | 4 +- frontend/src/pages/Play/PlayFriend.jsx | 17 +- frontend/src/socket.js | 3 + frontend/utils/chess.js | 305 ++++++++++++++++++ 34 files changed, 848 insertions(+), 38 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 backend/events.json create mode 100644 backend/mail.js create mode 100644 frontend/src/assets/bishop_black.png create mode 100644 frontend/src/assets/bishop_white.png create mode 100644 frontend/src/assets/king_black.png create mode 100644 frontend/src/assets/king_white.png create mode 100644 frontend/src/assets/knight_black.png create mode 100644 frontend/src/assets/knight_white.png create mode 100644 frontend/src/assets/pawn_black.png create mode 100644 frontend/src/assets/pawn_white.png create mode 100644 frontend/src/assets/queen_black.png create mode 100644 frontend/src/assets/queen_white.png create mode 100644 frontend/src/assets/rook_black.png create mode 100644 frontend/src/assets/rook_white.png create mode 100644 frontend/src/assets/sprites.png create mode 100644 frontend/src/components/Cell.jsx create mode 100644 frontend/src/components/FriendsList.jsx create mode 100644 frontend/src/components/Piece.jsx create mode 100644 frontend/src/pages/Chess/ChessGame.jsx create mode 100644 frontend/src/pages/Play/ChallengeFriend.jsx create mode 100644 frontend/src/pages/Play/ChessBoard.jsx create mode 100644 frontend/src/socket.js create mode 100644 frontend/utils/chess.js 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 0000000000000000000000000000000000000000..df2e407a2b6267f274f8cf18cf902a444a74b053 GIT binary patch literal 1395 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 BDSZF{ literal 0 HcmV?d00001 diff --git a/frontend/src/assets/knight_black.png b/frontend/src/assets/knight_black.png new file mode 100644 index 0000000000000000000000000000000000000000..e79df050f4ae7b6c4f43658194f7a818e16667ed GIT binary patch literal 1563 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 literal 0 HcmV?d00001 diff --git a/frontend/src/assets/pawn_black.png b/frontend/src/assets/pawn_black.png new file mode 100644 index 0000000000000000000000000000000000000000..bb3cfdf18aac84e9564553d5e72b3fbe98690808 GIT binary patch literal 1168 zcmV;B1aJF^P)Px#1ZP1_K>z@;j|==^1poj5Fi=cXMNDaNBqSsyB_$;#B_$;#B_$;#B_$;#B`ho} zcXV`SV`5fQQcX%pMM6S4IXNaKCbU?*3IG5A33O6UQve?~R(hkv;QMdqU~vEd1L;Xb zK~#9!?U_wa6Hyd~TiOas;zF0;M%|Ufn7G3j65Z8*fc}EIFmYKWZh*vDPx%GVKLDIo zwkj01CFHhSk;DpU8R*Ar?o2=CPAR=Tml&h-?#klHIcLttJ3}NADpaUYp+bcU73!bX z%?Ed`iS#f7p!Vb%NYSV8^xB;D3?02UMco6OgSA}YD}a|~sF;P;hMgm#f(2*Bh?Rn~ z>Y|7Wz^Mr;4(!`2 z%IN=VpdLuE!d|Q~wA79e{QVWlU|UJiV6R4I{RJxsVY2UUpy_*v zGDZF!ids16Whlxf_c1ct4Xh}HFuL2)Pq-D!OH|3sPmEb`Bx*;>GDt0G@Du!eC!+4#bO!@RX)8EFdrjb8?8liXtMD}JINA0?|~w}B;FIkpAaVyu$Q zqG8+0f%L}cgvI)@xw%FGR++_0DhsEbBXdhn_2yb({a~=h1KZx4Z{EgFpbzKOjy|9D@Re0ZEl5yr1uw=kYQYZ@8m#b8~YUr^l+vKNzgTOsm*o zvHemJfo?auUw zve|clh~;#rS~i$z>APbss(dHsmVT+XZbStyjLNm?+czbXf<-MS>)V6E@cTs8zX+=a z9hSoWihn_u*sm~=7@XeSE_y4BJ4W|$;k(;jk_&88xM2scqchD6tKt6yllRWh0 z84}T+X2HgBI4mq$*F3vPWi=1#iet?I8r1Q=-q#$Xv@wWz_`(rVU3+Y4R~YgvP}uF3lFeYo^&T>^UgPx#1ZP1_K>z@;j|==^1poj5JWxzjMNDaNK|w)7LqkMFL_|bHMMXtLMMXtLMgRZ* z`1ttm@9*N`;?vX9#Kgp}udkGplz@PMYiny&RaHhtMjcsvD*ylh2y{|TQve`5U45#` z;b6n+OaK4_?MXyIRCwC$o7>W>APj{o^%Mz;toMJ{eSuofw!26=|FCY`nSPUm5Ek_D zNJ>gdN=iyfN=iyfN=iyfN=i!lRb@&kmHiZw%1aC?bM+%($}22XZojxBa zXIrDS)~2;XE=8;C2>|E~)in)j+8zL)gcImY{emJQqSgVxQ_lBoOQgo=nQfI?;w+CwVR+>4~K1kfB(5pgRKE;Xd^h>A2@09pLX za?LrG0T6q81kjyPQN!E=Rs0fIZG23(fM6@*s_krxH5(JTmNZ zX-_3~PV~M=y9>pgpgS^n5U=!&P%P;|_~ZUm_l=&^Lo9`(4`_Fqs*e^V%t}=b=GJcG z>YR)x=tI>t^}&{CBmy>t>ew0K_yHS2MaM_7hBton7q()_ix2b%HGb==Z4F zh#f-VB3u&{_c708#p0UI@Jj9C<9H*gTKw*sip}EVh`Y}`TyBHKbE=q|b(Rk*fqskS zfO7nX$S_y|R&TI+nM2RYir8@3_~yf&akJ(Uuxd7gm!=I@D|sjEPuBgafsQpVSX|lb zoT%5u1=$=j?QqFvu^Mx@;c~I9@746&(k_=3e4tSQZhvrK1F>-B0NVP1%Su&OhE=fU zvoaeeWR8>oHV#Cy==6n53U{s)o5f(7ORPfI>^-kp41v(wbsJ8-eDE>Xpb2_b1Ya z%IRyW7nrvMi|poFpNzmOtP#w6sFk`kfo@3P8&uPMOLyl()ZAZMd_pynnfp4$b9!d_ z3DkHUbXR9pvlaLum48uuk9$+iVnzR>?v6P+fA!G+qaw258-50$r*EK$8W%i`#V}rd zLFLOU4;?J~EfrB8*aOJNV0}eJp`R#K{^8`8R77DtfXfHWpEQXqXbzhIRHTDuu>|@M zR764jI9xyHe7~huA=`rqLPa637oPxh7%UTTj<|d_DO2+~;OanJ0r#I`ZjpY!6#qV! x>jEh(R!IGItSYE};jghtNl8gbNlAZE{{SEr)R#jXkvjkY002ovPDHLkV1hhEAu9j? literal 0 HcmV?d00001 diff --git a/frontend/src/assets/queen_black.png b/frontend/src/assets/queen_black.png new file mode 100644 index 0000000000000000000000000000000000000000..a2ed748f4c0950e6560b28abdef1680717a43e61 GIT binary patch literal 1901 zcmV-z2a@=SP)Px#1ZP1_K>z@;j|==^1poj5Gf+%aMNDaNCMG5&B_$;#B_$;#B_$;#B_$;#B`z*4 zcXV`SV`ElRQcg@uMM6S6IyxpMCLbRk@5vey00009bW%=J02Vk}b(Fl|{jCoG+yDRu z!%0LzRCwC$o9mXVDhzL2I#9V;>`SZvlk390o zBab}tNGC8tR{C)Q@BKUtXDfGVn!Wchss6X1hOpo&Fmhnsc#QDVuYh%vM*>4JL;)T$ zwzp=VROO7;#--&)G9;-AvJrD@t}asPpP!#aYpsoc6UDF-bUDfJ~gEpDOJ)29Lnee)q+MuF4fY6!DkSaHa2jL$b^VWYinPxoV&E5d&c?|vJ~KHq1d7oTnlx3o`n0|0ZoVRTJVvK1U+*WmK1 zW+)W88!^_@b6+|4N2sjP1I2vi_E)WV_m9wJk}^p@){XF?%{&bRxY46ed$@gC82NK| zK&MYpY5>JM-KMK$^(z&D@&I{Z3gxzDZU_SQ=ZgCrK0c7UkTb=dRfbD4VX7 z`Lc%|5SA(WM01A4H+0}Dnl)84T`{xytSugJ)kXV6&}UeRY3K}-C-^qP(Ox}ajrr{O zc~OgD@%BTu=2Xm`B8>lBBTlg`F0deT0D`46D~|Ldie(urUyD`tF7L3S*UpR$SuEV! z7VpzJmSY+{$ew5U8dX3uCPCEnu*6m#0|A;?#!PAgp5Uptlf%$I*{#?@pNu&Yqw20UzjKTvL~ARg%?XAyDX< zU}ZfK>bEQRa;BrT!CdE9I#|&cHcVbOl~lQ|YCGF>QUR9cz3uyqt=2k|!QFRr;#m>y zw^Iu_)pi-%X%kSck(Tnnp0gNzGS}p(}B5SkZ}S+*q-opfYI- z;8k{e9~(s=%j{eoixsWt_e&8v&QA9+|}LGjy`_! zAJwz#Ys1+v(RP^1tjfB$i!&AJG1-yF(!kg-VPPtLbAPZeQ=D5{bPmh1|J}YpHtXQV z{@`5LW#i%iG-t!~hT?a6-4MULKd4H8HEil?v6RYeHoUwO$K~F{^(B_OR?O7Kh@Gn* z%aHfk-ii*)K1cS-eTU`B&fw2mj0Muh?{9N|7!}t8h?5C30NW^JUC+6AC!IsMA|36CF+y;~f*$?Xq1< zjCYhJb+1JxMp!Er9o{9zizXDjZ8P0>`kl(koevN9V}!M$(P8iIL5527BG+A~Kd3~n zz1>WV1}RJMjQ>_*%&4sF?U6f9pHqSG?UCz=F{iT9w?{4~MnvVr?`d3gdPLRWn&-6` zfvFl?m%5Y~F;$bxrxy|2&zD)ALY`9KJF!niHrt(Sc8l#;g=>695KEe7Q9h-iMXv+% zFKcpJ^j3!6ajy5)8=n?2W0!uBcn1i>{WuAADY*^s?96Nsup!7|X%MiYJsR!%9`fIVglNK4Q7V>Xmskx0b n`;qk{k390oBab}t|CRp$w(O*H3mkD=00000NkvXXu0mjf=a8y< literal 0 HcmV?d00001 diff --git a/frontend/src/assets/queen_white.png b/frontend/src/assets/queen_white.png new file mode 100644 index 0000000000000000000000000000000000000000..bdc6ff35005fb4148b65fe29659c2dbfc1b6e5f4 GIT binary patch literal 2171 zcmV->2!!{EP)Px#1ZP1_K>z@;j|==^1poj5PEbr#MNDaNKtMo4LqkMFL_|bHMMXtKL_|eJMMXtL zMMXvb|Nr>-`0?@a=;-L)-rm#G)5*!nyu7@xudkq>pplW0f`Wo@aByH?U{O&~Mn*D& zNGZ!ai+>ALq9$Ce%3Q#rY#$->)4 z-L6oRR(tZffGdY4H>Q?w%&Ai++Wg5K!1~>kOf{NWm)k z0E^gBR57f#PX}DyQ~d116uz#uw-mHiQe+e_rlXjWty4c_PFTK&15YnEFW3 z`>1!GfU9G(zo9^3=!qu+F-KpXP3rMj7z^Ekf^X{9B?qu-3DL=y(qHEKe0$pZ06Y9jtd?Nr$DJNpVIwaOI zEu1YGF&nj9BX_?qj3FVgiO;QP>z5V>dc@q4#r4|A49_Ot{>sDyr|^_m~$v96{;ejb6JZCG@Pq9aHURt5x0y$S9E8d8;> znL&3=Ku6fHNUdbEa`fQ%AO~H*2nBa5P#gKpT?m)%x$?+Z=V*4S-x>QE!CH3DDNwcw z)xkzkDLv7E9B526SfHH<9jv7V#xc|lLjD<5FODl){e3yL%&gW1uF?UEEvG1h_3v9V zug{8RYF3>^t>s5m#{0BsrUt=?Ej1DEE!OF8@XxSvGdJpGb)Q0|@@vsdEpg#;!m5SD zRaU#@tuX*9ww6LEt8;sVQKod0SvBi!#TD7rN!chJvDM9bwXP#ko9wRwSY9~N%TQ4C z>5fdH!q}|^JuB$dF)Ft1w_e&!jc^LDxMDXfDjYFfa!%`T!9hnQdgU}-tr03!6*A55&=UU0diMH=V7w(B=q$o-?o{$OFPExN29$%xBc#0l0~)9dlU z<^93b&Y~3;bs=zKUCMvwIF7lz6K6F!>Zy9iiX66DqQb$CH#6UA1asgL$6+zFo>_nm z@z_-&>2wAbfvD)V$?LPno4Y(ir}+ddF; zcfxy~TxTC@=QXRh<44cK#mLp$T{XI$09C$+8Wu5nhcL)-S>kN^JVF>C;oZb|(cVX% zBFJ%B>Ta*DCYB!^ekVqc5PH(#cVfIA!eHmav0FP_3!yh1{y>ZuQ8gmi`LLT9FC`YZ z1rXxeRv1)4FLL=3Bim4QA@ACF662OwVdyXrF&e0vu(wBih|xe5hz^4gV{O0^fp3po zCq@%hAN-!iRbn(z1*1b>Vx$1o4cDbE5u=&yLFv$akKh`rM%-{+>O3(rh$<)@UM5DE zR-x(eDluYL1*SvSwX7?y3QdO>h|vWqG##EMMklE7ba;vwDRYAgPlvACbt?qkG;P~; zy|O{sIO;!!zVExPZJHbD5D_EeZ8wb5EaVXr^E{11-!(Vh9*zp>WBDEHFiP_;E-_7f zbmZxqlRqDrnZH(B0@F6UW>o#^$2O{RziGQ(#oPF1m4*bi-F}>>fz#JnqkNde`J0nR zct^^)uU|e^_wFC)d7+w`m%lByGP7XiO5dyuV9O8mLc}qy)b#`W_}4+wRwKbT+d#L} z^Yu>V3;%fRv@Or*f`m~C{m~ndi7iNnlLkF6cprQw0b)3jur>#XVijPA(!Ltu+tkN_ zw!f`A2>vZ;9T*n(Z&cG$2h+!ouM=-rA~Y=ecfun6iZKgy5cMyBB_3E0tmSreAKHSX x{4Mt1gq0Te*suI=Ess6+*kg}9_SpZ+{s9?_0NMlHk)Z$p002ovPDHLkV1mV&9<2ZX literal 0 HcmV?d00001 diff --git a/frontend/src/assets/rook_black.png b/frontend/src/assets/rook_black.png new file mode 100644 index 0000000000000000000000000000000000000000..722db00de6d73199f553ab12450cf3dd339aa342 GIT binary patch literal 1303 zcmV+y1?c*TP)Px#1ZP1_K>z@;j|==^1poj5Fi=cXMNDaNB_$;#B_$>%CM6{$CMG5&B_)W4g?M#! zZEI>^UR_pGQcX)rMM6S3H#a6GCf#T*LI3~&2XsA>v7{-6Ic0d;qGIKssP{5vXC}>Fh4-i}QNI^oPK~O}gAfe_zKnPt!fl}Za zDS-|}3W+NU6fBDZi4g3gB(ZKsN3f;DzL#Qm&wIZ8m{}i$V&Cp&cb|El_iH{58f&bv z#u{s^v7@FOUg;W$2KM-qExjYLU0OXh))h%@x30$aqS5=`00I!1^xYRONVK(`|IK{v zXz=Du;L#~i99-YBOQAB0T3dMSYv9tYe(qO!g9{(tOnE5wH*6>qQ1)+q+ zb(hOaB>*S01Ox?Zl0sY8dW^BGUbb291dHX}E)VPKX9*TRB`%Md=-&^6yysYtRu#$^ zLkrHn*G2E!*a6yAW6JB`*2M7LJbslLD|)f^(^$)D@KRpB&v)&=#7oVJm-gcoR_bx( zwOfv*LGivL)~FOKPME$k)?5XacE*ycCrUyY0(#H7#&MGirAegNcivC@z(S$I5HP&w zTzYE6PG*T$8YB_b)}Xo=>s-dKZ*sp6sIJl`9gM^df*9(r=iOUEaDZ$nW2T=(V4&Og zDAO}iW*_+NG2d8E3rf;G=9uN9Q=WVBx4uQ9D6%MXSj+|ke?RB3rX>Y_H;$DIqVA!$ zA{ND`PaNGb$hNKB3#Yn@OdL8?4PIFrOs{ygvDkLtRro+gwAkdnI~WepsqB=d>r5lqB&?u%r&J znVma|{U!DMWlI(UJqupc;ngbOWy_jj;9uH>tONH}Sa}=_Jla?G0_7=boGF%+CF=}% zSwE~^X*+H`t=cYZU!}zz)F?;mi6u3Pd%ir8v_YLtE^sW7Sl>H7P}sK7YI6E^vOAQa z7cWJpQ!u%Kw$I&B=gP#ZZhRzt`-T%%=OJ)^ zW4nlT`;}E-^^OCU`l!cmH_DxG_9A;PPQ`Tk=H|@z{PY8uHB8<$M+P#Ac;@5b>8e~k z$9lV4;Vxqs{4wq7DXdTy797&KZS1pEzE?eQs|{sax&kY3ICG}w+^~w5M^Dz3Sg!x2 ziUZc{c+GMhYI$hv{r7v{2FrPUbSiB}Ec3tAxOdt8+s8E4SYwU(*+1Doh`A(7Y;6Dl N002ovPDHLkV1j$uUJ?KR literal 0 HcmV?d00001 diff --git a/frontend/src/assets/rook_white.png b/frontend/src/assets/rook_white.png new file mode 100644 index 0000000000000000000000000000000000000000..49b7d0f7bcf0fbbd0996eed0de769810cf60a2af GIT binary patch literal 1490 zcmV;@1ugoCP)Px#1ZP1_K>z@;j|==^1poj5KTu3mMNDaNK|w)7LqkMFL_|bHMMXtLMMXtLMgRZ* z`1ttm@9*N`;?vX9#l^+6w6vh0ppTD_dU|?eV`EcOQ$|KcFD0vP00008bW%=J03t?g zm%-rhIX^A4000FYNkl zpN+NM&3w5(J{C(vL;#>0eD&vWi2m9B8rnAiKr+&|O+;H`%te~N-SxIQRZ<}UM2uJq z$eF^tTm)3r0$5^gJEO7yIJwI=q(VKct21`M!8zu|68k#*se~994~t^}^*))teed`d zcn<^%!?H~a0CegUz-Eixx6Apd;}|6`A7lq~M*!AZBLH3xoy&(MEdbYb>@V+dag$79 z9RN6FG04zZwKLWlpncC9Tm02s-9&*=4{O|?uU{B|uKy%(&TNBwR~Qo@ofkn0PY+JB z4E>|(LF*8RnXslAPJx#zmS73%1<<679jq3NhvqK-+0NWQf%bB8=iQZ*SF8U#dm;Kq zoumvY;cEHkq@Mp%&Wt0wK%48q<(tTe1s`i!ttl3AJMjR>B$Kz+7$cY|ut-3RF}9sZ zDZnA?;4{6n1Ms$Z`xLx-4SltkGq!G0rV<}`g`*%Gsg)P&rWXEo_pfbNZ(` zXS-xFT9WK%wDb}p)*5f}Df1v|-vB^66(b@cQKJdBMlugdtRk&J3BJ9t(x;sI)XSTs@jNm%X2UYS`F zAh0Wqb+;a;=Ey_Yv2MBeAgnv1$dv05SP00C6Rwnn&jhs?3(ki)G2rI)Ik9fql*ZbH zVd=`mWpd|-8Lot`XtL*rTQ8Q!3a<#bs)Q(|))cF_69ey^T$3T=V!`wG8Tc$fNe=EL8@Aj!DR~NhZ}x*7PYP|wLDhRZZ?+1itFr5&k*ryC1T0b zV2#?A#M-6JD8v_nrdWRP)rwABrd)nlQwT=)#ALPbe^V>E+Vb;!QLOYG%g>0#r^4bx zu-Y|hwWY9Pv+0ViXo}_sJ__qWYBlK*-!JIMXiEsszu9k+_qe1O763i3ZA#cqAfjLL zgfgzMiB!w{9+^UucAZAF?Keg)NV3f0+rb6DU=WC9?2A9^F)T+fy+9-VN@u^2?YsNC z(|lN$cMGUasj(?lj2Jtt@rSK=hG5BN2LPSx*1TFIjcov6e_|Fa*|q>qC&gMbn+<@& z@jh3;u958y(4VIaFJaL+EBF%dI>vpqnpeR6oOHI(G(?$p)2|kI?^a{UAAtT~!n5A| z04cv2;M+avO^BC|oObl2m=_TNk0GHGg5{j#d0!BKh;tdROdGN5D$E?vnw3}*wZAFI zx_~GRz*6SLqGrL$OcnIJ@h`?RqDEhE=Tp09L>QnU%*&5rSo_ s69eRm*Ju04zT2!4RMkNB{r;07*qoM6N<$f@NaJp#T5? literal 0 HcmV?d00001 diff --git a/frontend/src/assets/sprites.png b/frontend/src/assets/sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..f2d313758e6e59f86d0e380cc99ed259bc29af17 GIT binary patch literal 17013 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 O0000 { + 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} + + setColor(evt.target.value)} my="20px" label={I play as} placeholder='choose your color' data={[ + { value: 'W', label: 'White' }, + { value: 'B', label: 'Black' }, + { value: 'RANDOM', label: 'Random' } + ]} /> +
+ +
+
+ ) +} + +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