From 8f39b29377342806e8930e2e2f62523332431cb6 Mon Sep 17 00:00:00 2001 From: Moon Patel Date: Fri, 7 Jul 2023 19:10:48 +0530 Subject: [PATCH] New feature added: Games automatically saved in database after game ends in pgn format --- backend/constants.js | 1 + backend/socket.js | 60 ++++++++++++++++++--- backend/util/auth.js | 1 - frontend/src/components/Challenges.jsx | 16 +++--- frontend/src/constants.js | 1 + frontend/src/context/chess-game-context.jsx | 23 ++++++-- frontend/src/pages/Chess/ChessBoard.jsx | 6 +-- frontend/src/pages/Chess/ChessGame.jsx | 5 +- 8 files changed, 89 insertions(+), 24 deletions(-) diff --git a/backend/constants.js b/backend/constants.js index b6ed1c9..443bf30 100644 --- a/backend/constants.js +++ b/backend/constants.js @@ -8,6 +8,7 @@ const SOCKET_EVENTS = { CHESS_OPPONENT_MOVE: "opponent-move", USER_JOINED_ROOM: "user-joined-room", USER_RESIGNED: "user-resigned", + GAME_END: "game-end", }; module.exports = { diff --git a/backend/socket.js b/backend/socket.js index 754adf0..974d28e 100644 --- a/backend/socket.js +++ b/backend/socket.js @@ -1,5 +1,8 @@ const socket = require("socket.io"); const { SOCKET_EVENTS } = require("./constants"); +const { Chess } = require("chess.js"); +const { User } = require("./models/user"); +const { Game } = require("./models/game"); const { CHESS_MOVE, CHESS_OPPONENT_MOVE, @@ -10,8 +13,9 @@ const { ROOM_FULL, USER_JOINED_ROOM, USER_RESIGNED, + GAME_END, } = SOCKET_EVENTS; -// roomID => { timeLimit,gameHistory , players:[{username: {color}}] } +// roomID => { timeLimit,gameHistory , players:{'b': {username,userid}, 'w':{username,userid} } } let activeRooms = new Map(); function createRoom(roomID, timeLimit) { @@ -24,14 +28,18 @@ function getRoom(roomID) { return activeRooms.get(roomID); } -// structure of userDetails: {username,color} +function destoryRoom(roomID) { + activeRooms.delete(roomID); +} + +// structure of userDetails: {username,userid,color} function addUserToRoom(roomID, socket, userDetails) { console.log(userDetails); - let { username, color } = userDetails; + let { username, color, userid } = userDetails; let room = activeRooms.get(roomID); - if (room.players[username]) { - room.players[username] = { color, socket }; + if (room.players[color]) { + room.players[color] = { username, userid, socket }; return JOIN_ROOM_SUCCESS; } if (Object.keys(room.players).length > 1) { @@ -39,7 +47,7 @@ function addUserToRoom(roomID, socket, userDetails) { console.log(activeRooms); return ROOM_FULL; } else { - room.players[username] = { color, socket }; + room.players[color] = { username, userid, socket }; } console.log(activeRooms); @@ -69,7 +77,7 @@ function socketIOServerInit(server) { let result = addUserToRoom(roomID, socket, data); if (result === JOIN_ROOM_SUCCESS) { socket.join(roomID); - io.to(roomID).emit("new user joined the room"); + // io.to(roomID).emit("new user joined the room"); console.log(data, "joined"); let room = getRoom(roomID); io.to(roomID).emit(USER_JOINED_ROOM, data.username); @@ -89,9 +97,45 @@ function socketIOServerInit(server) { socket.to(roomID).emit(CHESS_OPPONENT_MOVE, moveData); }); - socket.on(USER_RESIGNED, (roomID, username) => { + socket.on(USER_RESIGNED, async (roomID, username) => { socket.to(roomID).emit(USER_RESIGNED, username); }); + + socket.on(GAME_END, async (roomID) => { + console.log("Ending game..."); + const room = getRoom(roomID); + const black = room.players["b"]; + const white = room.players["w"]; + io.socketsLeave(roomID); + black.socket.disconnect(); + white.socket.disconnect(); + + // generating pgn + let moves = room.gameHistory; + let chess = new Chess(); + for (let move of moves) { + let { from, to } = move; + chess.move({ from, to }); + } + let pgn = chess.pgn(); + + const blackPlayerDoc = await User.findById(black.userid); + const whitePlayerDoc = await User.findById(white.userid); + + let gameData = { + white: whitePlayerDoc.id, + black: blackPlayerDoc.id, + timeLimit: room.timeLimit, + roomID, + pgn, + }; + const gameDoc = await Game.create(gameData); + + blackPlayerDoc.games.push(gameDoc.id); + await blackPlayerDoc.save(); + whitePlayerDoc.games.push(gameDoc.id); + await whitePlayerDoc.save(); + }); }); } diff --git a/backend/util/auth.js b/backend/util/auth.js index 3300a00..0331e0b 100644 --- a/backend/util/auth.js +++ b/backend/util/auth.js @@ -25,7 +25,6 @@ function checkAuthMiddleware(req, res, next) { if (req.method === "OPTIONS") { return next(); } - console.log(req.headers) if (!req.headers.authorization) { console.log("NOT AUTH. AUTH HEADER MISSING."); return next(new NotAuthError("Not authenticated.")); diff --git a/frontend/src/components/Challenges.jsx b/frontend/src/components/Challenges.jsx index d8254ea..2d06a37 100644 --- a/frontend/src/components/Challenges.jsx +++ b/frontend/src/components/Challenges.jsx @@ -46,8 +46,8 @@ const Challenges = () => { } }, []) - const acceptChallengeHandler = async ({ challenger, roomID, color, timeLimit }) => { - return async () => { + const acceptChallengeHandler = ({ challenger, roomID, color, timeLimit }) => { + async function handler() { const res = await deleteChallenge(roomID, 'accept'); if (res?.success) { localStorage.setItem('myColor', color === 'b' ? 'w' : 'b'); @@ -57,25 +57,29 @@ const Challenges = () => { navigate(`/game/friend/${roomID}`); } } + return handler; } - const declineChallengeHandler = async ({ challenger, roomID, color, timeLimit }) => { - return async () => { + const declineChallengeHandler = ({ challenger, roomID, color, timeLimit }) => { + async function handler() { const res = await deleteChallenge(roomID, 'decline'); } + return handler; } const deleteChallenge = async (challengeID, response) => { try { let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/${userid}/challenges/${challengeID}?response=${response}` - let response = await fetch(url, { + console.log(url) + let res = await fetch(url, { method: 'DELETE', headers: { Authorization: `Bearer ${getAuthToken()}`, } }) - const resData = await response.json(); + const resData = await res.json(); return resData; } catch (err) { + console.log(err) setError(err) } } diff --git a/frontend/src/constants.js b/frontend/src/constants.js index a1c8e1a..c74f704 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -10,6 +10,7 @@ export const SOCKET_EVENTS = { CHESS_OPPONENT_MOVE: "opponent-move", USER_JOINED_ROOM: "user-joined-room", USER_RESIGNED: "user-resigned", + GAME_END: "game-end", }; export const DISPATCH_EVENTS = { diff --git a/frontend/src/context/chess-game-context.jsx b/frontend/src/context/chess-game-context.jsx index 23c64af..b56fe35 100644 --- a/frontend/src/context/chess-game-context.jsx +++ b/frontend/src/context/chess-game-context.jsx @@ -1,7 +1,9 @@ import React, { createContext, useReducer, useRef, useState } from 'react' import { ChessModified, chessInit } from '../../utils/chess'; -import { DISPATCH_EVENTS } from '../constants'; +import { DISPATCH_EVENTS, SOCKET_EVENTS } from '../constants'; +import { socket } from '../socket'; const { CAPTURE_PIECE, MOVE_PIECE, SELECT_PIECE, JUMP_TO, SET_GAME_HISTORY, END_GAME } = DISPATCH_EVENTS +const { GAME_END } = SOCKET_EVENTS; export const ChessGameContext = createContext(); // myColor: null, chess: null, chessBoard: null, moveHints: null, selected: null, dispatch: null, handleOpponentMove: null, handleSquareClick: null, getSquareColor: null, isSquareMarked: null, selectPiece: null, handleDrop: null @@ -23,8 +25,13 @@ const reducer = (state, action) => { let { san, after } = newChessObj.move(action.val); updatedGameHistory.push({ move: san, fen: after }); if (newChessObj.isCheckmate()) { + socket.emit(GAME_END, localStorage.getItem('roomID')); return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'CHECKMATE' }; - } else { + } else if (newChessObj.isStalemate()) { + socket.emit(GAME_END, localStorage.getItem('roomID')); + return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'STALEMATE' }; + } + else { return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 }; } } @@ -36,8 +43,13 @@ const reducer = (state, action) => { let { san, after } = newChessObj.move(action.val); updatedGameHistory.push({ move: san, fen: after }); if (newChessObj.isCheckmate()) { + socket.emit(GAME_END, localStorage.getItem('roomID')); return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'CHECKMATE' }; - } else { + } else if (newChessObj.isStalemate()) { + socket.emit(GAME_END, localStorage.getItem('roomID')); + return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'STALEMATE' }; + } + else { return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 }; } } @@ -83,6 +95,7 @@ function chessGameStateInit(myColor) { // some functions to update game state. const ChessGameContextProvider = ({ children }) => { let myColor = localStorage.getItem('myColor'); + let roomID = localStorage.getItem('roomID'); console.log('INSIDE CONTEXT PROVIDER'); const [{ chess, chessBoard, moveHints, selected, gameHistory, currentIndex, hasGameEnded, gameEndedReason }, dispatch] = useReducer(reducer, myColor, chessGameStateInit); const [isTimerOn, setIsTimerOn] = useState(true); @@ -182,6 +195,7 @@ const ChessGameContextProvider = ({ children }) => { if (currentIndex === -1 || gameHistory.length === 0) { return new ChessModified({ color: myColor }).getBoard(myColor); } else { + // console.log(chess); let currentChessBoard = new ChessModified({ prop: gameHistory[currentIndex].fen, color: myColor }).getBoard(myColor); return currentChessBoard; } @@ -206,13 +220,14 @@ const ChessGameContextProvider = ({ children }) => { function endGame(reason) { dispatch({ type: END_GAME, val: reason }) + socket.emit(GAME_END, roomID); } return ( {children}