diff --git a/backend/package-lock.json b/backend/package-lock.json index 67d84dc..e88a6f1 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "bcryptjs": "^2.4.3", "body-parser": "^1.20.2", + "chess.js": "^1.0.0-beta.6", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", @@ -144,6 +145,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chess.js": { + "version": "1.0.0-beta.6", + "resolved": "https://registry.npmjs.org/chess.js/-/chess.js-1.0.0-beta.6.tgz", + "integrity": "sha512-sqBfX1VL3csSyqVM5ogbKA+aRlZyWDh276ruWXphwI0lDUMs7iYjZs29BOi49f7mXeunJE7cdfnIZhihsyLnsA==" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", diff --git a/backend/package.json b/backend/package.json index 10a5959..9c4282f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,6 +13,7 @@ "dependencies": { "bcryptjs": "^2.4.3", "body-parser": "^1.20.2", + "chess.js": "^1.0.0-beta.6", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", diff --git a/backend/socket.js b/backend/socket.js index ff9d18d..c67ac5d 100644 --- a/backend/socket.js +++ b/backend/socket.js @@ -2,15 +2,19 @@ const socket = require("socket.io"); const { SOCKET_EVENTS } = require("./constants"); const { CHESS_MOVE, CHESS_OPPONENT_MOVE, CONNECTION, JOIN_ROOM, JOIN_ROOM_ERROR, JOIN_ROOM_SUCCESS, ROOM_FULL } = SOCKET_EVENTS; -// roomID => { timeLimit, players:[{username: {color}}] } +// roomID => { timeLimit,gameHistory , players:[{username: {color}}] } let activeRooms = new Map(); function createRoom(roomID, timeLimit) { console.log(roomID, "created"); - activeRooms.set(roomID, { timeLimit, players: {} }); + activeRooms.set(roomID, { timeLimit, players: {}, gameHistory: [] }); console.log("Currently active rooms", activeRooms.size); } +function getRoom(roomID) { + return activeRooms.get(roomID); +} + // structure of userDetails: {username,color} function addUserToRoom(roomID, userDetails) { console.log(userDetails); @@ -57,7 +61,9 @@ function socketIOServerInit(server) { if (result === JOIN_ROOM_SUCCESS) { socket.join(roomID); io.to(roomID).emit("new user joined the room"); - socket.emit(result); // room joined successfully + console.log(data, "joined"); + let room = getRoom(roomID); + socket.emit(result, room.gameHistory); // room joined successfully } else { socket.emit(result); // room is full } @@ -68,6 +74,8 @@ function socketIOServerInit(server) { socket.on(CHESS_MOVE, (roomID, moveData) => { console.log(moveData); + let room = activeRooms.get(roomID); + room.gameHistory.push(moveData); socket.to(roomID).emit(CHESS_OPPONENT_MOVE, moveData); }); }); diff --git a/frontend/src/components/Cell.jsx b/frontend/src/components/Cell.jsx index 3d436c9..a05cf01 100644 --- a/frontend/src/components/Cell.jsx +++ b/frontend/src/components/Cell.jsx @@ -1,11 +1,14 @@ import React, { useContext, useState } from 'react' import Piece from './Piece'; import { socket } from '../socket'; -import { Box, Flex } from '@mantine/core'; +import { Box, Flex, Modal } from '@mantine/core'; import { useDroppable } from '@dnd-kit/core' import { ChessGameContext } from '../context/chess-game-context'; +import { SOCKET_EVENTS } from '../constants'; +const { CHESS_MOVE } = SOCKET_EVENTS const Cell = ({ cell }) => { + let roomID = localStorage.getItem('roomID'); let { square, type, color } = cell; const { getSquareColor, isSquareMarked, handleSquareClick } = useContext(ChessGameContext) const [isDropped, setIsDropped] = useState(false); @@ -14,7 +17,10 @@ const Cell = ({ cell }) => { let marked = isSquareMarked(square); const handleClick = () => { - handleSquareClick(square); + handleSquareClick(square, (moveData) => { + // moveData contains fen string, from, to squares of the move + socket.emit(CHESS_MOVE, roomID, moveData); + }); } let content = marked ? : ; diff --git a/frontend/src/constants.js b/frontend/src/constants.js index c85cdaf..c9548bc 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -14,4 +14,5 @@ export const DISPATCH_EVENTS = { MOVE_PIECE: "MOVE_PIECE", CAPTURE_PIECE: "CAPTURE_PIECE", JUMP_TO: "JUMP_TO", + SET_GAME_HISTORY: "SET_GAME_HISTORY", }; diff --git a/frontend/src/context/chess-game-context.jsx b/frontend/src/context/chess-game-context.jsx index 94f8f37..1bf9732 100644 --- a/frontend/src/context/chess-game-context.jsx +++ b/frontend/src/context/chess-game-context.jsx @@ -2,7 +2,7 @@ import React, { createContext, useReducer, useRef } from 'react' import { ChessModified, chessInit } from '../../utils/chess'; import { DISPATCH_EVENTS } from '../constants'; import ChessBoard from '../pages/Chess/ChessBoard'; -const { CAPTURE_PIECE, MOVE_PIECE, SELECT_PIECE, JUMP_TO } = DISPATCH_EVENTS +const { CAPTURE_PIECE, MOVE_PIECE, SELECT_PIECE, JUMP_TO, SET_GAME_HISTORY } = DISPATCH_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 @@ -20,8 +20,8 @@ const reducer = (state, action) => { console.log('Moving', action.val, state.chess.turn()); let newChessObj = new ChessModified({ prop: state.chess.fen(), color: state.chess.myColor }); let updatedGameHistory = state.gameHistory; - let move = newChessObj.move(action.val); - updatedGameHistory.push({ move: move.san, fen: newChessObj.fen() }); + let { san, after } = newChessObj.move(action.val); + updatedGameHistory.push({ move: san, fen: after }); return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 }; } case CAPTURE_PIECE: @@ -29,8 +29,8 @@ const reducer = (state, action) => { console.log('Capture', action.val, state.chess.turn()); let newChessObj = new ChessModified({ prop: state.chess.fen(), color: state.chess.myColor }); let updatedGameHistory = state.gameHistory; - let move = newChessObj.move(action.val); - updatedGameHistory.push({ move: move.san, fen: newChessObj.fen() }); + let { san, after } = newChessObj.move(action.val); + updatedGameHistory.push({ move: san, fen: after }); return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 }; } case JUMP_TO: @@ -38,6 +38,17 @@ const reducer = (state, action) => { let index = action.val; return { ...state, currentIndex: index } } + case SET_GAME_HISTORY: + { + let fetchedGameHistory = action.val; + let newChessObj = new ChessModified({ color: state.chess.myColor }); + let updatedGameHistory = []; + for (let i = 0; i < fetchedGameHistory.length; i++) { + let { san, after } = newChessObj.move(fetchedGameHistory[i]); + updatedGameHistory.push({ fen: after, move: san }) + } + return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(state.chess.myColor), gameHistory: updatedGameHistory } + } default: return state; } @@ -60,7 +71,7 @@ const ChessGameContextProvider = ({ children }) => { let myColor = localStorage.getItem('myColor'); console.log('INSIDE CONTEXT PROVIDER'); const [{ chess, chessBoard, moveHints, selected, gameHistory, currentIndex }, dispatch] = useReducer(reducer, myColor, chessGameStateInit); - console.log(gameHistory) + console.log(gameHistory); console.log(gameHistory); @@ -71,7 +82,7 @@ const ChessGameContextProvider = ({ children }) => { // data received through socket function handleOpponentMove(data) { - let { from, to } = data; + let { from, to, fen } = data; console.log(from + to); if (!chess.get(to)) { console.log('Moving piece: ', data); @@ -87,7 +98,7 @@ const ChessGameContextProvider = ({ children }) => { } // called when user clicks a square - function handleSquareClick(square) { + function handleSquareClick(square, emitToSocketCallback) { let { type, color } = chess.get(square); let marked = moveHints.includes(square); console.log('handleSquareClick', square) @@ -99,9 +110,13 @@ const ChessGameContextProvider = ({ children }) => { } if (!type && selected && marked) { dispatch({ type: MOVE_PIECE, val: { from: selected, to: square } }) + emitToSocketCallback({ from: selected, to: square }) + captureAudioRef.current.play(); } if (type && marked) { dispatch({ type: CAPTURE_PIECE, val: { from: selected, to: square } }) + emitToSocketCallback({ from: selected, to: square }) + moveAudioRef.current.play(); } } else { return; @@ -165,9 +180,14 @@ const ChessGameContextProvider = ({ children }) => { } } + // fetchedGameHistory is an array of objects of the form {from,to} + function setGameHistory(fetchedGameHistory) { + dispatch({ type: SET_GAME_HISTORY, val: fetchedGameHistory }) + } + return ( {children}