added computer mode

This commit is contained in:
Moon Patel
2023-10-11 22:43:37 +05:30
parent dcbd9477fe
commit f9639b5dc4
12 changed files with 307 additions and 81 deletions
+11 -5
View File
@@ -6,16 +6,22 @@ const engine = new Engine(
"C:\\Users\\MOON\\Downloads\\stockfish-windows-x86-64-avx2\\stockfish\\stockfish-windows-x86-64-avx2.exe"
);
engine.init().then(() => {
nextMove({ position: "r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3" });
});
engine
.init()
.then((eng) => {
return eng.setoption("UCI_LimitStrength", true);
})
.then((eng) => {
eng.setoption("UCI_Elo");
});
const nextMove = async ({ position }) => {
await engine.isready();
console.log("Chess engine ready");
engine.position(position);
const result = await engine.go({ depth: 22 });
const result = await engine.go({ depth: 10 });
console.log("Best move or position", position, "is", result.bestmove);
return result.bestmove;
};
module.exports = {};
module.exports = { nextMove };
+28
View File
@@ -3,6 +3,7 @@ const { SOCKET_EVENTS } = require("./constants");
const { Chess } = require("chess.js");
const { User } = require("./models/user");
const { Game } = require("./models/game");
const { nextMove } = require("./chessbot");
const {
CHESS_MOVE,
CHESS_OPPONENT_MOVE,
@@ -61,6 +62,33 @@ function socketIOServerInit(server) {
},
});
const ioBot = io.of("/chessbot");
ioBot.on("connection", (socket) => {
let id = socket.id;
console.log(id, "connected on /chessbot");
const chess = new Chess();
socket.onAny((evt) => {
console.log(evt);
});
socket.on("disconnect", (reason) => {
console.log(id, "disconnected due to", reason);
});
socket.on(CHESS_MOVE, async (roomID, moveData) => {
console.log("CHESS_MOVE");
chess.move(moveData);
let move = moveData.from + moveData.to;
console.log(move);
const botMove = await nextMove({ position: chess.fen() });
console.log({ from: botMove.substring(0, 2), to: botMove.substring(2) });
chess.move({ from: botMove.substring(0, 2), to: botMove.substring(2) });
socket.emit("CHESS_BOT_MOVE", { from: botMove.substring(0, 2), to: botMove.substring(2) });
});
});
io.on("connection", (socket) => {
let id = socket.id;
console.log(socket.id, "connected");
+8 -9
View File
@@ -11,10 +11,11 @@ import PlayFriend from './pages/Play/PlayFriend'
import Play from './pages/Play/Play'
import AuthenticationPage from './pages/Authentication/Authentication'
import ChallengeFriend, { playFriendAction } from './pages/Play/ChallengeFriend'
import ChessGame from './pages/Chess/ChessGame'
import Profile, { action as profileAction } from './pages/Settings/Profile'
import ChessGameContextProvider from './context/chess-game-context'
import { getAuthToken, getUserData } from './utils/auth'
import Computer from './pages/Play/Computer'
import ComputerGame from './pages/Play/ComputerGame'
import MultiplayerGame from './pages/Play/MultiplayerGame'
const router = createBrowserRouter([{
path: '/',
@@ -28,17 +29,15 @@ const router = createBrowserRouter([{
{ index: true, element: <Play /> },
{ path: 'friend/:friend_username', element: <ChallengeFriend />, action: playFriendAction },
{ path: 'friend', element: <PlayFriend /> },
{ path: 'computer', element: <div>Computer</div> },
{ path: 'computer', element: <Computer /> },
{ path: 'online', element: <div>Online</div> }
]
},
{
path: "game/friend/:roomID", element:
<div>
<ChessGameContextProvider>
<ChessGame />
</ChessGameContextProvider>
</div>
path: "game/friend/:roomID", element: <MultiplayerGame />
},
{
path: "game/computer", element: <ComputerGame />
},
{
path: 'settings', element: <Settings />, children: [
+4 -5
View File
@@ -9,9 +9,9 @@ import Piece from './Piece';
import { ChessGameContext } from '../context/chess-game-context';
import { SOCKET_EVENTS } from '../constants';
const { CHESS_MOVE } = SOCKET_EVENTS
const { CHESS_MOVE, GAME_END } = SOCKET_EVENTS
const Cell = ({ cell }) => {
const Cell = ({ cell, callbacks }) => {
let roomID = localStorage.getItem('roomID');
let { square, type } = cell;
const { getSquareColor, isSquareMarked, handleSquareClick } = useContext(ChessGameContext)
@@ -23,9 +23,8 @@ const Cell = ({ cell }) => {
let borderWidth = type ? '3px' : '5px'
const handleClick = () => {
handleSquareClick(square, (moveData) => {
// moveData contains fen string, from, to squares of the move
socket.emit(CHESS_MOVE, roomID, moveData);
handleSquareClick(square, callbacks.pieceClickCallback, () => {
socket.emit(GAME_END, roomID);
});
}
+35 -31
View File
@@ -2,7 +2,7 @@ import React, { createContext, useReducer, useRef, useState } from 'react'
import PropTypes from 'prop-types';
import { socket } from '../socket';
import { socket, socketBot } from '../socket';
import { ChessModified, chessInit } from '../utils/chess';
import { DISPATCH_EVENTS, SOCKET_EVENTS } from '../constants';
@@ -25,16 +25,18 @@ const reducer = (state, action) => {
let updatedGameHistory = state.gameHistory;
let { san, after } = newChessObj.move(action.val);
updatedGameHistory.push({ move: san, fen: after });
let newState;
if (newChessObj.isCheckmate()) {
socket.emit(GAME_END, localStorage.getItem('roomID'));
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'CHECKMATE' };
action.val.callback();
newState = { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'CHECKMATE' };
} else if (newChessObj.isStalemate()) {
socket.emit(GAME_END, localStorage.getItem('roomID'));
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'STALEMATE' };
action.val.callback();
newState = { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'STALEMATE' };
}
else {
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 };
newState = { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 };
}
return newState;
}
case CAPTURE_PIECE:
{
@@ -42,16 +44,18 @@ const reducer = (state, action) => {
let updatedGameHistory = state.gameHistory;
let { san, after } = newChessObj.move(action.val);
updatedGameHistory.push({ move: san, fen: after });
let newState;
if (newChessObj.isCheckmate()) {
socket.emit(GAME_END, localStorage.getItem('roomID'));
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'CHECKMATE' };
action.val.callback();
newState = { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'CHECKMATE' };
} else if (newChessObj.isStalemate()) {
socket.emit(GAME_END, localStorage.getItem('roomID'));
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'STALEMATE' };
action.val.callback();
newState = { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1, hasGameEnded: true, gameEndedReason: 'STALEMATE' };
}
else {
return { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 };
newState = { ...state, chess: newChessObj, chessBoard: newChessObj.getBoard(), moveHints: [], selected: null, gameHistory: updatedGameHistory, currentIndex: updatedGameHistory.length - 1 };
}
return newState;
}
case JUMP_TO:
{
@@ -120,21 +124,22 @@ const ChessGameContextProvider = ({ children }) => {
const checkAudioRef = useRef(null);
// data received through socket
function handleOpponentMove(data) {
function handleOpponentMove(data, callback) {
let { from, to } = data;
console.log(data);
if (!chessRef.current.get(to)) {
dispatch({ type: MOVE_PIECE, val: { from, to } });
moveAudioRef.current.play();
dispatch({ type: MOVE_PIECE, val: { from, to, callback } });
// moveAudioRef.current.play();
return;
} else {
dispatch({ type: CAPTURE_PIECE, val: { from, to } });
captureAudioRef.current.play();
dispatch({ type: CAPTURE_PIECE, val: { from, to, callback } });
// captureAudioRef.current.play();
return;
}
}
// called when user clicks a square
function handleSquareClick(square, emitToSocketCallback) {
function handleSquareClick(square, emitToSocketCallback, callback) {
let { type, color } = chessRef.current.get(square);
let marked = moveHintsRef.current.includes(square);
@@ -143,36 +148,35 @@ const ChessGameContextProvider = ({ children }) => {
return dispatch({ type: SELECT_PIECE, val: square });
}
if (!type && selectedRef.current && marked) {
dispatch({ type: MOVE_PIECE, val: { from: selected, to: square } })
dispatch({ type: MOVE_PIECE, val: { from: selected, to: square, callback } })
emitToSocketCallback({ from: selectedRef.current, to: square })
setIsTimerOn(false)
captureAudioRef.current.play();
moveAudioRef.current.play();
return;
}
if (type && marked) {
dispatch({ type: CAPTURE_PIECE, val: { from: selectedRef.current, to: square } })
console.log({ from: selectedRef.current, to: square })
dispatch({ type: CAPTURE_PIECE, val: { from: selectedRef.current, to: square, callback } })
emitToSocketCallback({ from: selectedRef.current, to: square })
setIsTimerOn(false);
moveAudioRef.current.play();
captureAudioRef.current.play();
return;
}
} else {
return;
}
}
function handleDrop(moveData) {
function handleDrop(moveData, emitToSocketCallback, callback) {
let { from, to } = moveData;
// console.log(from, to, ch ess.get(to), chess.ascii())
if (moveHintsRef.current.includes(to)) {
if (chessRef.current.get(to)) {
dispatch({ type: CAPTURE_PIECE, val: { from: from, to: to } }); // capture piece
dispatch({ type: CAPTURE_PIECE, val: { from: from, to: to, callback } }); // capture piece
captureAudioRef.current.play();
// setIsTimerOn(false)
socket.emit(CHESS_MOVE, roomID, moveData);
emitToSocketCallback(moveData);
} else {
dispatch({ type: MOVE_PIECE, val: { from: from, to: to } }); // move piece
dispatch({ type: MOVE_PIECE, val: { from: from, to: to, callback } }); // move piece
moveAudioRef.current.play();
// setIsTimerOn(false)
socket.emit(CHESS_MOVE, roomID, moveData);
console.log(moveData);
emitToSocketCallback(moveData);
}
}
}
+13 -19
View File
@@ -7,7 +7,7 @@ import Cell from '../../components/Cell';
import { ChessGameContext } from '../../context/chess-game-context';
import { socket } from '../../socket';
import { SOCKET_EVENTS } from '../../constants';
const { CHESS_OPPONENT_MOVE, CHESS_MOVE } = SOCKET_EVENTS
const { GAME_END } = SOCKET_EVENTS
const useStyles = createStyles((theme) => ({
chessboard: {
@@ -35,34 +35,28 @@ const useStyles = createStyles((theme) => ({
}
}))
const ChessBoard = () => {
const ChessBoard = ({ callbacks }) => {
const { classes } = useStyles();
const { getChessBoard, handleOpponentMove, handleDrop } = useContext(ChessGameContext)
const roomID = localStorage.getItem('roomID');
const { getChessBoard, handleDrop } = useContext(ChessGameContext)
const chessBoard = getChessBoard();
const myColor = localStorage.getItem('myColor')
useEffect(() => {
socket.on(CHESS_OPPONENT_MOVE, handleOpponentMove)
return () => {
socket.off(CHESS_OPPONENT_MOVE);
}
}, []);
const myColor = localStorage.getItem('myColor');
const roomID = localStorage.getItem('roomID');
if (myColor === 'w') {
return (
<DndContext onDragEnd={evt => {
let from = evt.active.id;
let to = evt.over.id;
handleDrop({ from, to });
handleDrop({ from, to }, callbacks.pieceDropCallback, () => {
socket.emit(GAME_END, roomID);
});
}}>
<Flex className={classes.chessboard} sx={{ userSelect: 'none' }}>
<div>
{chessBoard.map((row, rowIndex) => {
return (
<Flex className={classes.boardrow} key={rowIndex * 2}>
{row.map(cell => <Cell key={cell.square} cell={cell} />)}
{row.map(cell => <Cell callbacks={{ pieceClickCallback: callbacks.pieceClickCallback }} key={cell.square} cell={cell} />)}
</Flex>
)
})}
@@ -75,16 +69,16 @@ const ChessBoard = () => {
<DndContext onDragEnd={evt => {
let from = evt.active.id;
let to = evt.over.id;
handleDrop({ from, to }, (moveData) => {
socket.emit(CHESS_MOVE, roomID, moveData);
})
handleDrop({ from, to }, callbacks.pieceDropCallback, () => {
socket.emit(GAME_END, roomID);
});
}}>
<Flex className={classes.chessboard}>
<div>
{chessBoard.map((row, rowIndex) => {
return (
<Flex className={classes.boardrow} key={rowIndex * 2}>
{row.map(cell => <Cell key={cell.square} cell={cell} />).reverse()}
{row.map(cell => <Cell callbacks={{ pieceClickCallback: callbacks.pieceClickCallback }} key={cell.square} cell={cell} />).reverse()}
</Flex>
)
}).reverse()}
+17 -10
View File
@@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom'
import { useDisclosure } from '@mantine/hooks'
import { Avatar, Button, Flex, Image, MediaQuery, Modal, NavLink, Text, Title } from '@mantine/core'
import { socket } from '../../socket'
import { socket, socketBot } from '../../socket'
import { getUserData } from '../../utils/auth'
import { ChessGameContext } from '../../context/chess-game-context'
import ChessBoard from '../Chess/ChessBoard'
@@ -27,7 +27,6 @@ const ChessGame = () => {
const roomID = localStorage.getItem('roomID');
const navigate = useNavigate();
const opponent = localStorage.getItem('opponent');
let connected = socket.id;
const exitGame = () => {
// cleanup game related data
@@ -45,6 +44,14 @@ const ChessGame = () => {
endGame('RESIGN');
exitGame();
}
const pieceDropCallback = (moveData) => {
socket.emit(CHESS_MOVE, roomID, moveData);
}
const pieceClickCallback = (moveData) => {
// moveData contains fen string, from, to squares of the move
socket.emit(CHESS_MOVE, roomID, moveData);
}
useEffect(() => {
socket.connect();
@@ -71,7 +78,11 @@ const ChessGame = () => {
console.log('Socket disconnected due to', reason);
});
socket.on(CHESS_OPPONENT_MOVE, handleOpponentMove)
socket.on(CHESS_OPPONENT_MOVE, (data) => {
handleOpponentMove(data, () => {
socket.emit(GAME_END, roomID);
})
})
socket.on(USER_JOINED_ROOM, () => {
setIsWaiting(false);
@@ -83,14 +94,10 @@ const ChessGame = () => {
return () => {
socket.offAny();
socket.disconnect()
socket.disconnect();
}
}, []);
useEffect(() => {
console.log('Connection useEffect()');
}, [connected]);
if (!hasJoinedRoom) return (
<MainLoader />
)
@@ -110,7 +117,7 @@ const ChessGame = () => {
p="2px"
label={isWaiting ? "Waiting for opponent..." : opponent}
icon={<Avatar radius="3px" >
{opponent[0].toUpperCase}
{opponent?.at(0)?.toUpperCase}
</Avatar>}
description={"description"}
/>
@@ -128,7 +135,7 @@ const ChessGame = () => {
</MediaQuery>
</>
:
<ChessBoard />
<ChessBoard callbacks={{ pieceDropCallback, pieceClickCallback }} />
}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<NavLink
@@ -0,0 +1,132 @@
import { useDisclosure } from '@mantine/hooks';
import React, { useContext, useEffect } from 'react'
import { ChessGameContext } from '../../context/chess-game-context';
import { socketBot as socket } from '../../socket';
import { getUserData } from '../../utils/auth';
import { Avatar, Button, Flex, Image, MediaQuery, Modal, NavLink, Text, Title } from '@mantine/core';
import ChessBoard from '../Chess/ChessBoard';
import GameHistory from '../../components/GameHistory';
import { useNavigate } from 'react-router-dom';
import { SOCKET_EVENTS } from '../../constants';
const { CHESS_MOVE, GAME_END } = SOCKET_EVENTS
const ChessGameComputer = () => {
const { hasGameEnded, gameEndedReason, handleOpponentMove } = useContext(ChessGameContext);
const [gameEndedModalOpen, modalFunctions] = useDisclosure(true);
const navigate = useNavigate();
const user = getUserData();
let username = user.username;
let color = localStorage.getItem('myColor') || 'w';
const roomID = localStorage.getItem('roomID');
useEffect(() => {
socket.connect();
socket.onAny(evt => {
console.log("event", evt);
})
socket.on("CHESS_BOT_MOVE", (data) => {
handleOpponentMove(data, () => {
socket.emit(GAME_END, roomID);
})
});
return () => {
socket.offAny();
socket.disconnect();
}
}, []);
const exitGame = () => {
console.log("Ending game");
socket.disconnect();
navigate("/play/computer");
}
const pieceDropCallback = (moveData) => {
console.log("Hello");
socket.emit(CHESS_MOVE, roomID, moveData);
}
const pieceClickCallback = (moveData) => {
// moveData contains fen string, from, to squares of the move
socket.emit(CHESS_MOVE, roomID, moveData);
}
// const resign = () => {
// socket.emit(USER_RESIGNED);
// endGame('RESIGN');
// exitGame();
// }
return (
<React.Fragment>
<Modal onClose={modalFunctions.close} opened={hasGameEnded && gameEndedModalOpen} centered>
<Text>Game ended due to {gameEndedReason}</Text>
<Button color='lime' onClick={exitGame}>Go back</Button>
<Button mx='md' color='lime' onClick={modalFunctions.close}>OK</Button>
</Modal>
<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' >
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<NavLink
style={{ width: "500px" }}
p="2px"
// label={isWaiting ? "Waiting for opponent..." : opponent}
icon={<Avatar radius="3px" >
Computer
</Avatar>}
description={"description"}
/>
</div>
{
// TODO: handle isWaiting state
false ?
<>
<MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
<Image width={600} miw={480} src="/src/assets/images/chess_board.png" />
</MediaQuery>
<MediaQuery largerThan="sm" styles={{ display: 'none' }}>
<Image width="100%" maw={540} src="/src/assets/images/chess_board.png" />
</MediaQuery>
</>
:
<ChessBoard callbacks={{ pieceDropCallback, pieceClickCallback }} />
}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<NavLink
style={{ width: "500px" }}
p="2px"
label={username}
icon={<Avatar radius="3px" >
{username[0].toUpperCase()}
</Avatar>}
description={"description"}
/>
</div>
</Flex>
<MediaQuery smallerThan="lg" styles={{ display: 'none' }}>
<Flex maw={450} sx={{
width: '100%',
height: '100%',
textAlign: 'center',
borderRadius: '10px',
backgroundColor: '#272623'
}} bg='gray' justify='start' py='md' align='center' direction='column' h="600px">
<Title my='20px'>Game Data</Title>
<Flex direction='column' w='100%'>
<GameHistory />
</Flex>
<Flex>
<Button onClick={exitGame} color='red'>Exit </Button>
</Flex>
</Flex>
</MediaQuery>
</Flex>
</React.Fragment>
)
}
export default ChessGameComputer
+23 -1
View File
@@ -1,8 +1,30 @@
import { Button, Card, Flex, Image, TextInput, Title } from '@mantine/core'
import { IconSearch } from '@tabler/icons-react'
import React from 'react'
import { Link } from 'react-router-dom'
const Computer = () => {
return (
<div>Computer</div>
<Card
maw={450} sx={{
width: '100%',
height: '600px',
textAlign: 'center',
backgroundColor: '#262523'
}}
>
<Flex align="center" justify="center" gap="xs" my="lg">
<Image width="30px" src="https://www.chess.com/bundles/web/images/color-icons/computer.2318c3b4.svg" />
<Title order={2}>Play with Computer</Title>
</Flex>
<Flex direction='column' gap='10px'>
<Link to='/game/computer'>
<Button color='lime'>
Play
</Button>
</Link>
</Flex>
</Card>
)
}
+17
View File
@@ -0,0 +1,17 @@
import React from 'react'
import ChessGameContextProvider from '../../context/chess-game-context'
import ChessGameComputer from './ChessGameComputer'
const ComputerGame = () => {
return (
<div>
<ChessGameContextProvider>
<ChessGameComputer />
</ChessGameContextProvider>
</div>
)
}
export default ComputerGame
@@ -0,0 +1,17 @@
import React from 'react'
import ChessGameContextProvider from '../../context/chess-game-context'
import ChessGame from '../Chess/ChessGame'
const MultiplayerGame = () => {
return (
<div>
<ChessGameContextProvider>
<ChessGame multiplayer={false} />
</ChessGameContextProvider>
</div>
)
}
export default MultiplayerGame
+2 -1
View File
@@ -1,3 +1,4 @@
import { io } from "socket.io-client";
const url = import.meta.env.VITE_BACKEND_HOST
const url = import.meta.env.VITE_BACKEND_HOST;
export const socket = io(url, { autoConnect: false });
export const socketBot = io(url + "/chessbot", { autoConnect: false });