Chess game logic move to chess-game-context.jsx

Improves code clarity as logic is handled by context provider and
application is handled by the component itself
This commit is contained in:
Moon Patel
2023-07-03 22:37:02 +05:30
parent 4e6bc1f198
commit 8c99021395
7 changed files with 178 additions and 141 deletions
+1
View File
@@ -65,6 +65,7 @@ function socketIOServerInit(server) {
});
socket.on("move", (roomID, moveData) => {
console.log(moveData);
socket.to(roomID).emit("opponent-move", moveData);
});
});
-17
View File
@@ -1,17 +0,0 @@
import { createContext } from "react";
const AuthContext = createContext();
import React from 'react'
const AuthContextProvider = ({ children }) => {
return (
<AuthContext.Provider value={{
}}>
{children}
</AuthContext.Provider>
)
}
export default auth - context
+12 -23
View File
@@ -1,43 +1,32 @@
import React, { useState } from 'react'
import React, { useContext, useState } from 'react'
import Piece from './Piece';
import { socket } from '../socket';
import { Box, Flex } from '@mantine/core';
import { useDroppable } from '@dnd-kit/core'
import { ChessGameContext } from '../context/chess-game-context';
const Cell = ({ cell, chess, marked, dispatch,selected }) => {
const { square, type, color } = cell;
const { isOver, setNodeRef } = useDroppable({ id: square });
const Cell = ({ cell }) => {
let { square, type, color } = cell;
const { getSquareColor, isSquareMarked, handleSquareClick } = useContext(ChessGameContext)
const [isDropped, setIsDropped] = useState(false);
let squareColor = chess.squareColor(square) === 'light' ? "w" : "b";
const { isOver, setNodeRef } = useDroppable({ id: square });
let squareColor = getSquareColor(square);
let marked = isSquareMarked(square);
const handleClick = () => {
console.log(!type, selected, marked)
if (chess.turn() !== localStorage.getItem('myColor')) return;
if (chess.myColor === color) {
if (type && chess.turn() === chess.myColor) {
return dispatch({ type: 'SELECT_PIECE', val: square });
}
if (!type && selected && marked) {
console.log(square)
dispatch({ type: 'MOVE_PIECE', val: { from: selected, to: square } })
}
if (type && marked) {
dispatch({ type: 'CAPTURE_PIECE', val: { from: selected, to: square } })
}
}
handleSquareClick(square);
}
let content;
content = marked ? <Mark /> : <Piece cell={cell} dispatch={dispatch} />;
let content = marked ? <Mark /> : <Piece cell={cell} />;
return (
<Flex ref={setNodeRef} style={{aspectRatio:'1/1'}} onClick={handleClick} bg={squareColor === 'w' ? "white" : "gray"} >
<Flex ref={setNodeRef} style={{ aspectRatio: '1/1' }} onClick={handleClick} bg={squareColor === 'w' ? "white" : "gray"} >
{content}
</Flex>
)
}
export const Mark = () => {
const Mark = () => {
return (
<Box w="33%" h="33%" sx={{ backgroundColor: '#77777777', borderRadius: '100%' }} m="auto"></Box>
)
+10 -5
View File
@@ -1,8 +1,10 @@
import { Image } from '@mantine/core';
import React, { useEffect } from 'react';
import React, { useContext, useEffect } from 'react';
import { useDraggable } from '@dnd-kit/core'
import { ChessGameContext } from '../context/chess-game-context';
const Piece = ({ cell, dispatch }) => {
const Piece = ({ cell }) => {
const { selectPiece } = useContext(ChessGameContext)
let { square, type, color } = cell;
let logo = null;
switch (type) {
@@ -31,6 +33,7 @@ const Piece = ({ cell, dispatch }) => {
...cell
}
});
const style = transform ? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
cursor: isDragging ? 'grabbing' : 'pointer',
@@ -38,19 +41,21 @@ const Piece = ({ cell, dispatch }) => {
aspectRatio: '1',
touchAction: 'none'
} : undefined;
useEffect(() => {
if (isDragging) {
dispatch({ type: 'SELECT_PIECE', val: cell });
selectPiece(cell);
}
}, [isDragging])
if (logo) {
return (
<Image ref={setNodeRef} style={style} sx={{ cursor: 'pointer' }} {...listeners} {...attributes} src={`/src/assets/${logo}.png`} />
)
} else {
return (
<div style={{width:'100%'}}>
</div>
<div style={{ width: '100%' }}></div>
)
}
}
+138
View File
@@ -0,0 +1,138 @@
import React, { createContext, useReducer, useRef } from 'react'
import { ChessModified, chessInit } from '../../utils/chess';
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
const reducer = (state, action) => {
console.log(state.chess.myColor)
switch (action.type) {
case 'SELECT_PIECE':
{
console.log('SELECTING...', action.val)
return { ...state, moveHints: state.chess.getMoves(action.val), selected: action.val };
}
case 'MOVE_PIECE':
{
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('myColor')), moveHints: [], selected: null };
}
case 'CAPTURE_PIECE':
{
console.log('Capture', action.val, state.chess.turn())
let newChessObj = new ChessModified({ prop: state.chess.fen(), color: state.chess.myColor, selected: null });
newChessObj.move(action.val);
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [] };
}
default:
return state;
}
}
function chessGameStateInit(myColor) {
let chess = chessInit(myColor);
let chessBoard = chess.getBoard(myColor);
let moveHints = [];
let selected = null;
return { chess, chessBoard, moveHints, selected }
}
// the ChessGameContextProvider seperates the game logic from the ChessBoard component and exposes
// some functions to update game state.
const ChessGameContextProvider = ({ children }) => {
let myColor = localStorage.getItem('myColor');
const [{ chess, chessBoard, moveHints, selected }, dispatch] = useReducer(reducer, myColor, chessGameStateInit);
const moveAudioRef = useRef(null);
const captureAudioRef = useRef(null);
const gameEndAudioRef = useRef(null);
const checkAudioRef = useRef(null);
// data received through socket
function handleOpponentMove(data) {
let { from, to } = data;
console.log(from + to)
if (!chess.get(to)) {
console.log('Moving piece: ', data)
dispatch({ type: 'MOVE_PIECE', val: { from, to } });
moveAudioRef.current.play();
return;
} else {
console.log('Capturing piece');
dispatch({ type: 'CAPTURE_PIECE', val: { from, to } });
captureAudioRef.current.play();
return;
}
}
// called when user clicks a square
function handleSquareClick(square) {
let { type, color } = chess.get(square);
let marked = moveHints.includes(square);
console.log('handleSquareClick', square)
console.log(!type, selected, marked)
if (chess.turn() === myColor) {
if (type && color === myColor) {
return dispatch({ type: 'SELECT_PIECE', val: square });
}
if (!type && selected && marked) {
dispatch({ type: 'MOVE_PIECE', val: { from: selected, to: square } })
}
if (type && marked) {
dispatch({ type: 'CAPTURE_PIECE', val: { from: selected, to: square } })
}
} else {
return;
}
}
function handleDrop(moveData, emitToSocketCallback) {
let { from, to } = moveData;
if (moveHints.includes(to)) {
console.log(chess.get(from))
if (chess.get(to)) {
dispatch({ type: 'CAPTURE_PIECE', val: { from: from, to: to } }); // capture piece
captureAudioRef.current.play();
emitToSocketCallback(moveData);
} else {
dispatch({ type: 'MOVE_PIECE', val: { from: from, to: to } }); // move piece
moveAudioRef.current.play();
emitToSocketCallback(moveData);
}
}
}
function selectPiece({ square, type, color: pieceColor }) {
if (pieceColor === myColor && myColor === chess.turn()) {
console.log(square, type, pieceColor)
dispatch({ type: 'SELECT_PIECE', val: square });
}
}
function getSquareColor(square) {
return chess.squareColor(square) === 'light' ? "w" : "b";
}
function isSquareMarked(square) {
return moveHints.includes(square);
}
return (
<ChessGameContext.Provider value={{
myColor, chess, chessBoard, moveHints, selected, dispatch, handleOpponentMove, handleSquareClick, getSquareColor, isSquareMarked, selectPiece, handleDrop
}}>
{children}
<audio src='/src/assets/move-self.mp3' ref={moveAudioRef} />
<audio src='/src/assets/capture.mp3' ref={captureAudioRef} />
<audio src='/src/assets/game-end.webm.mp3' ref={gameEndAudioRef} />
<audio src='/src/assets/move-check.mp3' ref={checkAudioRef} />
</ChessGameContext.Provider>
)
}
export default ChessGameContextProvider
+13 -95
View File
@@ -1,10 +1,9 @@
import React, { useEffect, useReducer, useRef } from 'react';
import { ChessModified, chess, chessInit } from '../../../utils/chess';
import React, { useContext, useEffect, useReducer, useRef } from 'react';
import Cell from '../../components/Cell';
import { socket } from '../../socket';
import { Flex, createStyles } from '@mantine/core';
import { DndContext } from '@dnd-kit/core'
import { useElementSize } from '@mantine/hooks';
import { ChessGameContext } from '../../context/chess-game-context';
const useStyles = createStyles((theme) => ({
chessboard: {
@@ -32,120 +31,39 @@ const useStyles = createStyles((theme) => ({
}
}))
const reducer = (state, action) => {
console.log(state.chess.myColor)
switch (action.type) {
case 'SELECT_PIECE':
{
if (state.chess.turn() !== localStorage.getItem('myColor')) return state;
return { ...state, moveHints: state.chess.getMoves(action.val.square), selected: action.val.square };
}
case 'MOVE_PIECE':
{
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('myColor')), moveHints: [], selected: null };
}
case 'CAPTURE_PIECE':
{
console.log('Capture', action.val, state.chess.turn())
let newChessObj = new ChessModified({ prop: state.chess.fen(), color: state.chess.myColor, selected: null });
newChessObj.move(action.val);
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(localStorage.getItem('myColor')), moveHints: [] };
}
default:
return state;
}
}
const ChessBoard = ({ color }) => {
const ref = useRef()
const { height, width } = useElementSize(ref)
const { classes } = useStyles();
const moveAudioRef = useRef(null);
const captureAudioRef = useRef(null);
const gameEndAudioRef = useRef(null);
const checkAudioRef = useRef(null);
const { chessBoard, handleOpponentMove, handleDrop } = useContext(ChessGameContext)
let roomID = localStorage.getItem('roomID');
const [gameState, dispatch] = useReducer(reducer, {
chess: chessInit(color), chessBoard: chess.getBoard(color), moveHints: [], selected: null
});
const chessBoardRef = useRef(gameState.chessBoard);
chessBoardRef.current = gameState.chessBoard;
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 } });
moveAudioRef.current.play();
return;
} else {
console.log('Capturing piece');
dispatch({ type: 'CAPTURE_PIECE', val: { from, to } });
captureAudioRef.current.play();
return;
}
}
socket.on('opponent-move', handleOpponentMove)
return () => {
socket.off('move', handleOpponentMove);
socket.off('opponent-move');
}
}, []);
return (
<DndContext onDragEnd={evt => {
let srcSquare = evt.active.id;
let destSquare = evt.over.id;
if (gameState.moveHints.includes(destSquare)) {
console.log(gameState.chess.get(srcSquare))
if (gameState.chess.get(destSquare)) {
captureAudioRef.current.play();
dispatch({ type: 'CAPTURE_PIECE', val: { from: srcSquare, to: destSquare } }); // capture piece
socket.emit('move', roomID, { from: srcSquare, to: destSquare })
} else {
moveAudioRef.current.play();
dispatch({ type: 'MOVE_PIECE', val: { from: srcSquare, to: destSquare } }); // move piece
socket.emit('move', roomID, { from: srcSquare, to: destSquare })
}
}
let from = evt.active.id;
let to = evt.over.id;
handleDrop({ from, to }, (moveData) => {
socket.emit('move', roomID, moveData);
})
}}>
<Flex ref={ref} className={
classes.chessboard
}>
<Flex className={classes.chessboard}>
<div>
{gameState.chessBoard.map((row, rowIndex) => {
{chessBoard.map((row, rowIndex) => {
return (
<Flex className={classes.boardrow} key={rowIndex * 2}>
{row.map((cell) => {
return (
<Cell
key={cell.square}
cell={cell}
chess={chess}
marked={gameState.moveHints.includes(cell.square)}
dispatch={dispatch}
selected={gameState.selected}
/>)
})}
{row.map(cell => <Cell key={cell.square} cell={cell} />)}
</Flex>
)
})}
</div>
</Flex>
<audio src='/src/assets/move-self.mp3' ref={moveAudioRef} />
<audio src='/src/assets/capture.mp3' ref={captureAudioRef} />
<audio src='/src/assets/game-end.webm.mp3' ref={gameEndAudioRef} />
<audio src='/src/assets/move-check.mp3' ref={checkAudioRef} />
</DndContext >
</DndContext>
)
}
+4 -1
View File
@@ -4,6 +4,7 @@ import ChessBoard from '../Chess/ChessBoard'
import { useNavigate, useParams } from 'react-router-dom'
import { socket } from '../../socket'
import { getUserData } from '../../../utils/auth'
import ChessGameContextProvider from '../../context/chess-game-context'
const ChessGame = () => {
const user = getUserData();
@@ -71,7 +72,9 @@ const ChessGame = () => {
icon={<Avatar radius="3px" children={opponent[0].toUpperCase()} />}
description={"description"}
/>
<ChessBoard color={localStorage.getItem('myColor')} />
<ChessGameContextProvider>
<ChessBoard />
</ChessGameContextProvider>
<NavLink
p="2px"
label={username}