| import React, { createContext, useReducer, useRef, useState } from 'react' |
|
|
| import PropTypes from 'prop-types'; |
|
|
| import { socket } from '../socket'; |
| import { ChessModified, chessInit } from '../utils/chess'; |
|
|
| import { DISPATCH_EVENTS, SOCKET_EVENTS } from '../constants'; |
| const { MOVE_PIECE, SELECT_PIECE, JUMP_TO, SET_GAME_HISTORY, END_GAME } = DISPATCH_EVENTS |
| const { GAME_END } = SOCKET_EVENTS; |
|
|
| export const ChessGameContext = createContext(); |
|
|
| const reducer = (state, action) => { |
| try { |
| switch (action.type) { |
| case SELECT_PIECE: |
| { |
| if (state.chess.turn() === state.chess.myColor && state.currentIndex < state.gameHistory.length - 1) return { ...state, currentIndex: state.gameHistory.length - 1 } |
| return { ...state, moveHints: state.chess.getMoves(action.val), selected: action.val }; |
| } |
| case MOVE_PIECE: |
| { |
| let newChessObj = new ChessModified(state.chess.fen()); |
| let updatedGameHistory = state.gameHistory; |
| let { san, after } = newChessObj.move(action.val); |
| updatedGameHistory.push({ move: san, fen: after }); |
| let newState; |
| if (newChessObj.isCheckmate()) { |
| action.val.callback(); |
| action.val.playAudioCallback("CHECKMATE"); |
| newState = { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'CHECKMATE' }; |
| } else if(newChessObj.isCheck() || newChessObj.inCheck()) { |
| action.val.playAudioCallback("CHECK"); |
| newState = { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 }; |
| } else if (newChessObj.isStalemate()) { |
| action.val.callback(); |
| action.val.playAudioCallback("STALEMATE"); |
| newState = { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'STALEMATE' }; |
| } |
| else { |
| if(!state.chess.get(action.val.to)) { |
| action.val.playAudioCallback("MOVE"); |
| } else { |
| action.val.playAudioCallback("CAPTURE"); |
| } |
| newState = { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 }; |
| } |
| return newState; |
| } |
| case JUMP_TO: |
| { |
| let index = action.val; |
| return { ...state, currentIndex: index } |
| } |
| case SET_GAME_HISTORY: |
| { |
| let fetchedGameHistory = action.val; |
| let newChessObj = new ChessModified(); |
| 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(), gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 } |
| } |
| case END_GAME: |
| { |
| return { ...state, hasGameEnded: true, gameEndedReason: action.val } |
| } |
| default: |
| return state; |
| } |
| } catch (err) { |
| console.error(err); |
| return state; |
| } |
| } |
|
|
| function chessGameStateInit(myColor) { |
| let chess = chessInit(myColor); |
| let chessBoard = chess.getBoard(); |
| let moveHints = []; |
| let gameHistory = []; |
| let selected = null; |
| let currentIndex = -1; |
| let hasGameEnded = false; |
| let gameEndedReason = ""; |
|
|
| return { chess, chessBoard, moveHints, selected, gameHistory, currentIndex, hasGameEnded, gameEndedReason }; |
| } |
|
|
| |
| |
| const ChessGameContextProvider = ({ children }) => { |
| let myColor = localStorage.getItem('myColor'); |
| let roomID = localStorage.getItem('roomID'); |
| const [{ chess, chessBoard, moveHints, selected, gameHistory, currentIndex, hasGameEnded, gameEndedReason }, dispatch] = useReducer(reducer, myColor, chessGameStateInit); |
| const [isTimerOn, setIsTimerOn] = useState(true); |
|
|
| const chessRef = useRef(chess); |
| const moveHintsRef = useRef(moveHints); |
| const selectedRef = useRef(selected); |
| const gameHistoryRef = useRef(gameHistory); |
| const currentIndexRef = useRef(currentIndex); |
| chessRef.current = chess; |
| selectedRef.current = selected; |
| moveHintsRef.current = moveHints; |
| gameHistoryRef.current = gameHistory; |
| currentIndexRef.current = currentIndex; |
|
|
| const moveAudioRef = useRef(null); |
| const captureAudioRef = useRef(null); |
| const gameEndAudioRef = useRef(null); |
| const checkAudioRef = useRef(null); |
|
|
| function playAudioCallback(action) { |
| switch(action) { |
| case "MOVE": |
| moveAudioRef.current.play(); |
| break; |
| case "CAPTURE": |
| captureAudioRef.current.play(); |
| break; |
| case "CHECK": |
| checkAudioRef.current.play(); |
| break; |
| case "CHECKMATE": |
| case "STALEMATE": |
| case "GAME_END": |
| gameEndAudioRef.current.play(); |
| break; |
| case "CASTLE": |
| |
| break; |
| default: |
| break; |
| } |
| } |
|
|
| |
| function handleOpponentMove(data, callback) { |
| let { from, to } = data; |
| console.log("Opponent move:",from,to); |
| dispatch({type:MOVE_PIECE,val: { from, to, callback,playAudioCallback }}); |
| } |
|
|
| |
| function handleSquareClick(square, emitToSocketCallback, callback) { |
| let { type, color } = chessRef.current.get(square); |
| let marked = moveHintsRef.current.includes(square); |
|
|
| if (chessRef.current.turn() === myColor) { |
| if (type && color === myColor) { |
| selectPiece({square,color}); |
| return; |
| } else if(marked) { |
| dispatch({ type: MOVE_PIECE, val: { from: selectedRef.current, to: square, callback,playAudioCallback } }) |
| console.log("Move:",{ from: selectedRef.current, to: square }) |
| emitToSocketCallback({ from: selectedRef.current, to: square }) |
| } |
| } |
| } |
|
|
| function handleDrop(moveData, emitToSocketCallback, callback) { |
| let { from, to } = moveData; |
| if (moveHintsRef.current.includes(to)) { |
| dispatch({ type: MOVE_PIECE, val: { from: from, to: to, callback,playAudioCallback } }); |
| console.log("Move:",{ from,to }) |
| emitToSocketCallback(moveData); |
| } |
| } |
|
|
| function selectPiece({ square, color: pieceColor }) { |
| if (pieceColor === myColor && myColor === chessRef.current.turn()) { |
| dispatch({ type: SELECT_PIECE, val: square }); |
| } |
| } |
|
|
| function getSquareColor(square) { |
| return chessRef.current.squareColor(square) === 'light' ? "w" : "b"; |
| } |
|
|
| function isSquareMarked(square) { |
| return moveHintsRef.current.includes(square); |
| } |
|
|
| function isLastMoveSquare(square) { |
| |
| if (currentIndexRef.current < 0) |
| return false; |
| let [lastMove] = chessRef.current.history({verbose: true}).slice(-1); |
| if (square != lastMove.to && square!=lastMove.from) |
| return false; |
| return true; |
| } |
|
|
| function jumpTo(index) { |
| dispatch({ type: JUMP_TO, val: index }) |
| } |
|
|
| function getChessBoard() { |
| if (currentIndexRef.current === -1 || gameHistoryRef.current.length === 0) { |
| return new ChessModified().getBoard(); |
| } else { |
| |
| let currentChessBoard = new ChessModified(gameHistoryRef.current[currentIndexRef.current].fen).getBoard(); |
| return currentChessBoard; |
| } |
| } |
|
|
| function goBack() { |
| if (currentIndexRef.current > 0) { |
| jumpTo(currentIndexRef.current - 1); |
| } |
| } |
|
|
| function goAhead() { |
| if (currentIndexRef.current < gameHistoryRef.current.length - 1) { |
| jumpTo(currentIndexRef.current + 1); |
| } |
| } |
|
|
| |
| function setGameHistory(fetchedGameHistory) { |
| dispatch({ type: SET_GAME_HISTORY, val: fetchedGameHistory }) |
| } |
|
|
| function endGame(reason) { |
| dispatch({ type: END_GAME, val: reason }) |
| socket.emit(GAME_END, roomID); |
| } |
|
|
| function getPieceColor(square) { |
| return chessRef.current.get(square).color |
| } |
|
|
| return ( |
| <ChessGameContext.Provider value={{ |
| myColor, chess, chessBoard, moveHints, selected, handleOpponentMove, handleSquareClick, getSquareColor, isSquareMarked, isLastMoveSquare, |
| selectPiece, handleDrop, gameHistory, jumpTo, getChessBoard, currentIndex, goAhead, goBack, setGameHistory, |
| isTimerOn, hasGameEnded, gameEndedReason, endGame,getPieceColor |
| }}> |
| {children} |
| <audio src='/assets/audio/move-self.mp3' ref={moveAudioRef} /> |
| <audio src='/assets/audio/capture.mp3' ref={captureAudioRef} /> |
| <audio src='/assets/audio/game-end.webm' ref={gameEndAudioRef} /> |
| <audio src='/assets/audio/move-check.mp3' ref={checkAudioRef} /> |
| </ChessGameContext.Provider> |
| ) |
| } |
|
|
| ChessGameContextProvider.propTypes = { |
| children: PropTypes.object |
| } |
|
|
| export default ChessGameContextProvider |