feature added: checkmate detection and ending game on checkmate

This commit is contained in:
Moon Patel
2023-07-05 21:25:28 +05:30
parent 313906eb5d
commit 128e86ecb4
6 changed files with 118 additions and 59 deletions
+6 -2
View File
@@ -1,7 +1,7 @@
import { Button, Group, Stack, Text, Title } from '@mantine/core'; import { Button, Group, Stack, Text, Title } from '@mantine/core';
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { getUserData } from '../../utils/auth' import { getAuthToken, getUserData } from '../../utils/auth'
const Challenges = () => { const Challenges = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -15,7 +15,11 @@ const Challenges = () => {
const fetchData = async () => { const fetchData = async () => {
let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/${username}/challenges`; let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/${username}/challenges`;
try { try {
response = await fetch(url, { signal: abortController.signal }) response = await fetch(url, {
signal: abortController.signal, headers: {
'Authorization': `Bearer ${getAuthToken()}`
}
})
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
setChallenges(data.challenges); setChallenges(data.challenges);
+2
View File
@@ -1,4 +1,6 @@
export const SOCKET_EVENTS = { export const SOCKET_EVENTS = {
CONNECT:'connect',
DISCONNECT:'disconnect',
CONNECTION: "connection", CONNECTION: "connection",
JOIN_ROOM: "join-room", JOIN_ROOM: "join-room",
JOIN_ROOM_SUCCESS: "join-room-success", JOIN_ROOM_SUCCESS: "join-room-success",
+17 -5
View File
@@ -22,7 +22,11 @@ const reducer = (state, action) => {
let updatedGameHistory = state.gameHistory; let updatedGameHistory = state.gameHistory;
let { san, after } = newChessObj.move(action.val); let { san, after } = newChessObj.move(action.val);
updatedGameHistory.push({ move: san, fen: after }); 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 }; if (newChessObj.isCheckmate()) {
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'CHECKMATE' };
} else {
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 };
}
} }
case CAPTURE_PIECE: case CAPTURE_PIECE:
{ {
@@ -31,7 +35,11 @@ const reducer = (state, action) => {
let updatedGameHistory = state.gameHistory; let updatedGameHistory = state.gameHistory;
let { san, after } = newChessObj.move(action.val); let { san, after } = newChessObj.move(action.val);
updatedGameHistory.push({ move: san, fen: after }); 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 }; if (newChessObj.isCheckmate()) {
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'CHECKMATE' };
} else {
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 };
}
} }
case JUMP_TO: case JUMP_TO:
{ {
@@ -61,8 +69,10 @@ function chessGameStateInit(myColor) {
let gameHistory = []; let gameHistory = [];
let selected = null; let selected = null;
let currentIndex = -1; let currentIndex = -1;
let hasGameEnded = false;
let gameEndedReason = "";
return { chess, chessBoard, moveHints, selected, gameHistory, currentIndex }; return { chess, chessBoard, moveHints, selected, gameHistory, currentIndex, hasGameEnded, gameEndedReason };
} }
// the ChessGameContextProvider seperates the game logic from the ChessBoard component and exposes // the ChessGameContextProvider seperates the game logic from the ChessBoard component and exposes
@@ -70,7 +80,7 @@ function chessGameStateInit(myColor) {
const ChessGameContextProvider = ({ children }) => { const ChessGameContextProvider = ({ children }) => {
let myColor = localStorage.getItem('myColor'); let myColor = localStorage.getItem('myColor');
console.log('INSIDE CONTEXT PROVIDER'); console.log('INSIDE CONTEXT PROVIDER');
const [{ chess, chessBoard, moveHints, selected, gameHistory, currentIndex }, dispatch] = useReducer(reducer, myColor, chessGameStateInit); const [{ chess, chessBoard, moveHints, selected, gameHistory, currentIndex, hasGameEnded, gameEndedReason }, dispatch] = useReducer(reducer, myColor, chessGameStateInit);
const [isTimerOn, setIsTimerOn] = useState(true); const [isTimerOn, setIsTimerOn] = useState(true);
console.log(gameHistory); console.log(gameHistory);
@@ -192,7 +202,9 @@ const ChessGameContextProvider = ({ children }) => {
return ( return (
<ChessGameContext.Provider value={{ <ChessGameContext.Provider value={{
myColor, chessBoard, moveHints, selected, handleOpponentMove, handleSquareClick, getSquareColor, isSquareMarked, selectPiece, handleDrop, gameHistory, jumpTo, getChessBoard, currentIndex, goAhead, goBack, setGameHistory, isTimerOn myColor, chessBoard, moveHints, selected, handleOpponentMove, handleSquareClick, getSquareColor, isSquareMarked,
selectPiece, handleDrop, gameHistory, jumpTo, getChessBoard, currentIndex, goAhead, goBack, setGameHistory,
isTimerOn, hasGameEnded, gameEndedReason
}}> }}>
{children} {children}
<audio src='/src/assets/move-self.mp3' ref={moveAudioRef} /> <audio src='/src/assets/move-self.mp3' ref={moveAudioRef} />
+7
View File
@@ -0,0 +1,7 @@
import React from 'react'
const useQuery = () => {
}
export default useQuery
+7 -1
View File
@@ -34,10 +34,16 @@ const useStyles = createStyles((theme) => ({
const ChessBoard = () => { const ChessBoard = () => {
const { classes } = useStyles(); const { classes } = useStyles();
const { getChessBoard, handleOpponentMove, handleDrop } = useContext(ChessGameContext) const { getChessBoard, handleOpponentMove, handleDrop,hasGameEnded,gameEndedReason } = useContext(ChessGameContext)
let roomID = localStorage.getItem('roomID'); let roomID = localStorage.getItem('roomID');
const chessBoard = getChessBoard(); const chessBoard = getChessBoard();
if(hasGameEnded) {
console.log('Game ended due to',gameEndedReason)
} else {
console.log('Game not ended yet')
}
useEffect(() => { useEffect(() => {
socket.on(CHESS_OPPONENT_MOVE, handleOpponentMove) socket.on(CHESS_OPPONENT_MOVE, handleOpponentMove)
+79 -51
View File
@@ -1,4 +1,4 @@
import { Avatar, Button, Flex, Group, Image, Loader, MediaQuery, NavLink, Text, Title } from '@mantine/core' import { Avatar, Button, Flex, Group, Image, Loader, MediaQuery, Modal, NavLink, Text, Title } from '@mantine/core'
import React, { useContext, useEffect, useState } from 'react' import React, { useContext, useEffect, useState } from 'react'
import ChessBoard from '../Chess/ChessBoard' import ChessBoard from '../Chess/ChessBoard'
import { useNavigate, useParams } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'
@@ -7,9 +7,14 @@ import { getUserData } from '../../../utils/auth'
import { ChessGameContext } from '../../context/chess-game-context' import { ChessGameContext } from '../../context/chess-game-context'
import GameHistory from '../../components/GameHistory' import GameHistory from '../../components/GameHistory'
import Timer from './Timer' import Timer from './Timer'
import { useDisclosure } from '@mantine/hooks'
import { SOCKET_EVENTS } from '../../constants'
const { CONNECT, DISCONNECT, CHESS_MOVE, CHESS_OPPONENT_MOVE, CONNECTION, JOIN_ROOM, JOIN_ROOM_ERROR, JOIN_ROOM_SUCCESS, ROOM_FULL, USER_JOINED_ROOM } = SOCKET_EVENTS;
const ChessGame = () => { const ChessGame = () => {
const { gameHistory, setGameHistory, isTimerOn,setIsTimerOn } = useContext(ChessGameContext); const { setGameHistory, isTimerOn, setIsTimerOn, hasGameEnded, gameEndedReason } = useContext(ChessGameContext);
const [gameEndedModalOpen, modalFunctions] = useDisclosure(true);
const user = getUserData(); const user = getUserData();
let username = user.username; let username = user.username;
let color = localStorage.getItem('myColor') let color = localStorage.getItem('myColor')
@@ -31,37 +36,40 @@ const ChessGame = () => {
useEffect(() => { useEffect(() => {
socket.connect(); socket.connect();
socket.on(CONNECT, () => {
socket.on('connect', () => {
console.log('Connected'); console.log('Connected');
}); });
socket.on('join-room-success', (fetchedGameHistory) => { socket.on(JOIN_ROOM_SUCCESS, (fetchedGameHistory) => {
console.log('Room joined:', roomID); console.log('Room joined:', roomID);
setGameHistory(fetchedGameHistory); setGameHistory(fetchedGameHistory);
setHasJoinedRoom(true); setHasJoinedRoom(true);
}); });
socket.on('room-full', () => { socket.on(ROOM_FULL, () => {
console.log('Room is full'); console.log('Room is full');
}) })
socket.on("join-room-error", (err) => { socket.on(JOIN_ROOM_ERROR, (err) => {
console.error("Error:", err); console.error("Error:", err);
}) })
console.log('JOINING ROOM') console.log('JOINING ROOM')
socket.emit("join-room", roomID, { username, color }) socket.emit(JOIN_ROOM, roomID, { username, color })
socket.on('disconnect', (reason) => { socket.on(DISCONNECT, (reason) => {
console.log('Socket disconnected due to', reason); console.log('Socket disconnected due to', reason);
}) });
socket.on('opponent-move', (data) => { socket.on(CHESS_OPPONENT_MOVE, (data) => {
console.log(data); console.log(data);
// setIsTimerOn(true); // setIsTimerOn(true);
}) })
socket.on(USER_JOINED_ROOM, () => {
setIsWaiting(false);
});
}, []); }, []);
if (!hasJoinedRoom) return ( if (!hasJoinedRoom) return (
@@ -69,47 +77,67 @@ const ChessGame = () => {
) )
return ( return (
<Flex gap="xl" miw={360} justify='center' align='center' wrap='nowrap' mt={{ base: '50px', sm: '0px' }} direction={{ base: 'column', lg: 'row' }}> <>
<Flex gap="xs" justify='center' align='start' wrap='nowrap' direction='column' > <Modal onClose={modalFunctions.close} opened={hasGameEnded && gameEndedModalOpen} centered>
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center'}}> <Text>Game ended due to {gameEndedReason}</Text>
<NavLink <Button color='lime' onClick={exitGame}>Go back</Button>
style={{width:"500px"}} <Button mx='md' color='lime' onClick={modalFunctions.close}>OK</Button>
p="2px" </Modal>
label={opponent} <Flex gap="xl" miw={360} justify='center' align='center' wrap='nowrap' mt={{ base: '50px', sm: '0px' }} direction={{ base: 'column', lg: 'row' }}>
icon={<Avatar radius="3px" children={opponent[0].toUpperCase()} />} <Flex gap="xs" justify='center' align='start' wrap='nowrap' direction='column' >
description={"description"} <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
/> <NavLink
{/* <Timer on={!isTimerOn} /> */} style={{ width: "500px" }}
</div> p="2px"
<ChessBoard /> label={isWaiting ? "Waiting for opponent..." : opponent}
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center'}}> icon={<Avatar radius="3px" children={opponent[0].toUpperCase()} />}
<NavLink description={"description"}
style={{width:"500px"}} />
p="2px" {/* <Timer on={!isTimerOn} /> */}
label={username} </div>
icon={<Avatar radius="3px" children={username[0].toUpperCase()} />} {
description={"description"} // TODO: handle isWaiting state
/> false ?
{/* <Timer on={isTimerOn} /> */} <>
</div> <MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
</Flex> <Image width={600} miw={480} src="/src/assets/chess_board.png" />
<MediaQuery smallerThan="lg" styles={{ display: 'none' }}> </MediaQuery>
<Flex maw={450} sx={{ <MediaQuery largerThan="sm" styles={{ display: 'none' }}>
width: '100%', <Image width="100%" maw={540} src="/src/assets/chess_board.png" />
height: '600px', </MediaQuery>
textAlign: 'center', </>
borderRadius: '10px' :
}} bg='gray' p="10px" justify='start' align='center' direction='column' h="600px"> <ChessBoard />
<Title>Game Data</Title> }
<Flex direction='column'> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<GameHistory /> <NavLink
</Flex> style={{ width: "500px" }}
<Flex> p="2px"
<Button onClick={exitGame} color='red'>Exit Game</Button> label={username}
</Flex> icon={<Avatar radius="3px" children={username[0].toUpperCase()} />}
description={"description"}
/>
{/* <Timer on={isTimerOn} /> */}
</div>
</Flex> </Flex>
</MediaQuery> <MediaQuery smallerThan="lg" styles={{ display: 'none' }}>
</Flex> <Flex maw={450} sx={{
width: '100%',
height: '600px',
textAlign: 'center',
borderRadius: '10px'
}} bg='gray' p="10px" justify='start' align='center' direction='column' h="600px">
<Title>Game Data</Title>
<Flex direction='column'>
<GameHistory />
</Flex>
<Flex>
<Button onClick={exitGame} color='red'>Exit Game</Button>
</Flex>
</Flex>
</MediaQuery>
</Flex>
</>
) )
} }