THIS IS A MAJOR MILESTONE

Socket connection re-implemented. Everything is working fine now.
All thats left to do is to make the app robust and handle some edge cases.
TODO
- The friends list is maybe hardcoded or whatever I dont know, but the problem is
  it shows the same username no matter who the signed in user is. I need to fix that.
- Whenever the pawn reaches to tha last square for promotion an 'Invalid move' error is
  thrown by the Chess class. Need to fix that too.
- Need to disable the chessboard (so user cannot make any moves) until the opponent has joined the game.
- Set timers for the game.
- Need to show the loading states in various places in the app.
- Improve the backend such that a user cannot create more than one room at a time.
- Remove hardcoded values from various places.
- Make the app responsive. The UI is good on desktop but it is very bad on mobile.
- Add a feature to search and add friends.
- Destroying the rooms when the match ends and set timers for auto destruction after a certain time.
- Need some logic to save games in the history.
- Add some tests.
This commit is contained in:
Moon Patel
2023-07-03 01:12:47 +05:30
parent 40acca7dd1
commit 4853e5edd9
8 changed files with 113 additions and 61 deletions
+12 -7
View File
@@ -10,24 +10,29 @@ const pendingChallenges = new Map();
// and vice versa is not true
router.post("/create", checkAuth, async (req, res, next) => {
console.log(req.body);
// challenger and challenged are username
const { challenger, challenged } = req.body;
// challenger and challenged are username, color is the color played by challenger, timeLimit is the timeLimit for one player
const { challenger, challenged, color, timeLimit } = req.body;
const challengedEmail = (await User.findOne({ username: challenged })).email;
console.log(challengedEmail);
// get email of the challenged person
// const challengedEmail = (await User.findOne({ username: challenged })).email;
// console.log(challengedEmail);
const roomID = uuid.v4();
createRoom(roomID, req.body.timeLimit);
createRoom(roomID, timeLimit);
// create a challenge and add it to pendingChallenges to notify the challenged user
// structure of challenge: {challenger,roomID,color,timeLimit}
if (pendingChallenges.has(challenged)) {
let challenges = pendingChallenges.get(challenged);
challenges.push(challenger);
challenges.push({ challenger, roomID, color, timeLimit });
} else {
pendingChallenges.set(challenged, [{ challenger, roomID }]);
// color is the choosed by the challenger
pendingChallenges.set(challenged, [{ challenger, roomID, color, timeLimit }]);
}
console.log("Pending challenges", pendingChallenges);
// STOP SENDING EMAILS FOR NOW
// sendEmail(
// challengedEmail,
// `Challenge from ${challenger}`,
+24 -14
View File
@@ -1,37 +1,43 @@
const socket = require("socket.io");
// roomID => { timeLimit, 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: {} });
console.log("Currently active rooms", activeRooms.size);
}
// structure of userDetails: {username,color}
function addUserToRoom(roomID, userDetails) {
console.log(userDetails);
let { username, color } = userDetails;
let room = activeRooms.get(roomID);
if (room.players) {
// room is full
if (Object.keys(room.players).length > 1) {
return "room-full";
} else {
// only one user in room
room.players[username].color = color;
}
} else {
// add player in the room
room.players = {};
room.players[username].color = color;
if (room.players[username]) {
room.players[username] = { color };
return "join-room-success";
}
if (Object.keys(room.players).length > 1) {
// room is full
console.log(activeRooms);
return "room-full";
} else {
room.players[username] = { color };
}
console.log(activeRooms);
return "join-room-success";
}
// initialize the socket server with the given http server instance
function socketIOServerInit(server) {
const io = new socket.Server(server);
const io = new socket.Server(server, {
cors: {
origin: process.env.CORS_ALLOWED_HOST,
},
});
io.on("connection", (socket) => {
let id = socket.id;
@@ -57,6 +63,10 @@ function socketIOServerInit(server) {
socket.emit("join-room-error", "room does not exist");
}
});
socket.on("move", (roomID, moveData) => {
socket.to(roomID).emit("opponent-move", moveData);
});
});
}
+2 -1
View File
@@ -23,4 +23,5 @@ dist-ssr
*.sln
*.sw?
.env
.env
test.jsx
-1
View File
@@ -10,7 +10,6 @@ const Cell = ({ cell, chess, marked, dispatch }) => {
const [isDropped, setIsDropped] = useState(false);
let squareColor = chess.squareColor(square) === 'light' ? "w" : "b";
console.log(chess.turn() !== localStorage.getItem('my_color'))
const handleClick = () => {
if (chess.turn() !== localStorage.getItem('my_color')) return;
if (chess.myColor === color) {
+9 -6
View File
@@ -5,10 +5,6 @@ import { getUserData } from '../../utils/auth'
const Challenges = () => {
const navigate = useNavigate();
const dummyChallenges = [
{ challenger: 'moonpatel', roomID: 'sgnkjsdbnojsnvjsdnkl' },
{ challenger: 'user1', roomID: 'sgnkjsdbnojsnvjsdnkl' }
]
const [challenges, setChallenges] = useState([]);
const { username } = getUserData()
@@ -58,12 +54,19 @@ const Challenges = () => {
<Title mt="20px" mb="10px" order={3}>Challenges</Title>
<Stack>
{
challenges.map(({ challenger, roomID }) => {
challenges.map(({ challenger, roomID, color, timeLimit }) => {
console.log(challenger, roomID, color, timeLimit);
return (
<Group position='apart'>
<Text>Challenge by {challenger}</Text>
<Group position='center'>
<Button color='lime' onClick={() => navigate(`/game/friend/${roomID}`)}>Accept</Button>
<Button color='lime' onClick={() => {
localStorage.setItem('myColor', color === 'b' ? 'w' : 'b');
localStorage.setItem('roomID', roomID);
localStorage.setItem('opponent', challenger);
localStorage.setItem('timeLimit', timeLimit);
navigate(`/game/friend/${roomID}`);
}}>Accept</Button>
<Button color='gray'>Decline</Button>
</Group>
</Group>
+55 -21
View File
@@ -8,41 +8,75 @@ import { getUserData } from '../../../utils/auth'
const ChessGame = () => {
const user = getUserData();
let username = user.username;
let color = localStorage.getItem('myColor')
const [hasJoinedRoom, setHasJoinedRoom] = useState(localStorage.getItem('socketid'));
const [isWaiting, setIsWaiting] = useState(true);
const roomID = useParams()['roomID'];
const roomID = localStorage.getItem('roomID')
const navigate = useNavigate();
const exitGame = () => {
localStorage.removeItem('socketid');
localStorage.removeItem('roomID');
localStorage.removeItem('opponent');
localStorage.removeItem('my_color');
localStorage.removeItem('time_limit');
localStorage.removeItem('myColor');
localStorage.removeItem('timeLimit');
socket.disconnect();
navigate('/play/friend');
}
useEffect(() => {
if (hasJoinedRoom) {
} else {
console.log('Connecting');
socket.connect();
socket.on('connect', () => {
localStorage.setItem('socketid', socket.id);
console.log('Connected');
});
socket.emit('join-room', roomID, username, localStorage.getItem('opponent'), { color: localStorage.getItem('my_color'), timeLimit: localStorage.getItem('time_limit') });
socket.on('joined-room', () => {
setHasJoinedRoom(true);
});
socket.on('room-created', () => {
console.log('Room is created')
setIsWaiting(false);
});
}
socket.connect();
socket.on('connect', () => {
console.log('Connected');
});
socket.on('join-room-success', () => {
console.log('Room joined:', roomID);
setHasJoinedRoom(true);
});
socket.on('room-full', () => {
console.log('Room is full');
})
socket.on("join-room-error", (err) => {
console.error("Error:", err);
})
console.log('JOINING ROOM')
socket.emit("join-room", roomID, { username, color })
socket.on('disconnect', (reason) => {
console.log('Socket disconnected due to', reason);
})
socket.on('opponent-move', (data) => {
console.log(data)
})
}, []);
// useEffect(() => {
// if (hasJoinedRoom) {
// } else {
// console.log('Connecting');
// socket.connect();
// socket.on('connect', () => {
// localStorage.setItem('socketid', socket.id);
// console.log('Connected');
// });
// socket.emit('join-room', roomID, username, localStorage.getItem('opponent'), { color: localStorage.getItem('myColor'), timeLimit: localStorage.getItem('timeLimit') });
// socket.on('joined-room', () => {
// setHasJoinedRoom(true);
// });
// socket.on('room-created', () => {
// console.log('Room is created')
// setIsWaiting(false);
// });
// }
// }, []);
if (!hasJoinedRoom) return (
<Loader variant='bars' />
)
@@ -56,7 +90,7 @@ const ChessGame = () => {
icon={<Avatar radius="3px" />}
description={"description"}
/>
<ChessBoard color={localStorage.getItem('my_color')} />
<ChessBoard color={localStorage.getItem('myColor')} />
<NavLink
p="2px"
label={username}
+7 -8
View File
@@ -1,8 +1,6 @@
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 { Form, redirect, useParams } from 'react-router-dom'
import { getAuthToken, getUserData } from '../../../utils/auth'
const ChallengeFriend = () => {
@@ -23,7 +21,7 @@ const ChallengeFriend = () => {
<Avatar mt="lg" color='lime' size="100px">{friend_username[0].toUpperCase()}</Avatar>
<Text>{friend_username}</Text>
</Flex>
<Select label="Time limit" placeholder='Time limit' name='time_limit' defaultValue='10' data={['5', '10', '15', '30']} />
<Select label="Time limit" placeholder='Time limit' name='timeLimit' defaultValue='10' data={['5', '10', '15', '30']} />
<Select defaultValue='w' my="20px" color='lime' name='color' label={<Text mx="auto" order={3}>I play as</Text>} placeholder='choose your color' data={[
{ value: 'w', label: 'White' },
{ value: 'b', label: 'Black' },
@@ -40,13 +38,13 @@ const ChallengeFriend = () => {
export const playFriendAction = async ({ request, params }) => {
const formData = await request.formData();
let color = formData.get('color');
let timeLimit = formData.get('time_limit');
let timeLimit = formData.get('timeLimit');
let username = getUserData().username;
let challenged = params.friend_username;
console.log(color, timeLimit, username, challenged);
let roomIDURL = `${import.meta.env.VITE_BACKEND_HOST}/api/room/create`;
let reqBody = { challenger: username, challenged }
let reqBody = { challenger: username, challenged, timeLimit, color }
try {
console.log(reqBody);
@@ -61,8 +59,9 @@ export const playFriendAction = async ({ request, params }) => {
const { roomID } = resJSON;
console.log('Room ID:', roomID);
localStorage.setItem('roomID', roomID)
localStorage.setItem('my_color', color);
// set properties of the game
localStorage.setItem('roomID', roomID);
localStorage.setItem('myColor', color);
localStorage.setItem('timeLimit', timeLimit);
localStorage.setItem('opponent', challenged);
+4 -3
View File
@@ -11,7 +11,7 @@ const reducer = (state, action) => {
switch (action.type) {
case 'SELECT_PIECE':
{
if (state.chess.turn() !== localStorage.getItem('my_color')) return state;
if (state.chess.turn() !== localStorage.getItem('myColor')) return state;
state.chess.select(action.val.square);
return { ...state, moveHints: state.chess.getMoves(state.chess.selected) };
}
@@ -20,14 +20,14 @@ const reducer = (state, action) => {
console.log('Moving', action.val, state.chess.turn());
let newChessObj = new ChessModified({ prop: state.chess.fen(), color: state.chess.myColor })
newChessObj.move(action.val);
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('my_color')), moveHints: [] };
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [] };
}
case 'CAPTURE_PIECE':
{
console.log('Capture', action.val, state.chess.turn())
let newChessObj = new ChessModified({ prop: state.chess.fen(), color: state.chess.myColor });
newChessObj.move(action.val);
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('my_color')), moveHints: [] };
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [] };
}
default:
return state;
@@ -52,6 +52,7 @@ const ChessBoard = ({ color }) => {
useEffect(() => {
function handleOpponentMove(data) {
let { from, to } = data;
console.log(from + to)
if (!gameState.chess.get(to)) {
console.log('Moving piece: ', data)
dispatch({ type: 'MOVE_PIECE', val: { from, to } });