socket connection established and chess logic imported from old codebase

This commit is contained in:
Moon Patel
2023-06-29 02:03:06 +05:30
parent 8e91427c1c
commit 0a06890b81
34 changed files with 848 additions and 38 deletions
+4 -2
View File
@@ -17,8 +17,8 @@ import Play from './pages/Play/Play'
import AuthenticationPage, { loginAction, signupAction } from './pages/Authentication/Authentication'
import { getAuthToken } from '../utils/auth'
import { logoutAction } from './components/Logout'
import ChallengeFriend, { playFriendAction } from './pages/Play/ChallengeFriend'
import ChessGame from './pages/Chess/ChessGame'
const router = createBrowserRouter([{
path: '/',
@@ -30,11 +30,13 @@ const router = createBrowserRouter([{
{
path: 'play', element: <PlayLayout />, children: [
{ index: true, element: <Play /> },
{ path: 'friend/:friend_username', element: <ChallengeFriend />, action: playFriendAction },
{ path: 'friend', element: <PlayFriend /> },
{ path: 'computer', element: <div>Computer</div> },
{ path: 'online', element: <div>Online</div> }
]
},
{ path: "game/friend/:roomID", element: <ChessGame /> },
{
path: 'settings', element: <Settings />, children: [
{ index: true, element: <Profile /> },
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+44
View File
@@ -0,0 +1,44 @@
import React from 'react'
import Piece from './Piece';
import { socket } from '../socket';
import { Box, Flex } from '@mantine/core';
const Cell = ({ cellProps, selectedPiece, dispatch, myColor }) => {
const { row, col, piece, marked } = cellProps;
let bgColor = (selectedPiece?.row === row && selectedPiece?.col === col) ? 'bg-gray-100' : ((row + col) % 2 ? 'bg-stone-800' : 'bg-neutral-200');
bgColor = marked && piece ? `bg-red-300` : bgColor;
const handleClick = () => {
// if (!myTurn) return;
if (piece && piece.color === myColor) {
dispatch({ type: 'SELECT_PIECE', val: { row, col, color: piece.color } }) // select piece
}
else if (!piece && selectedPiece && marked) {
let payload = { fromRow: selectedPiece.row, fromCol: selectedPiece.col, toRow: row, toCol: col };
socket.emit('move', payload);
dispatch({ type: 'MOVE_PIECE', val: payload, }); // move piece
}
else if (piece && marked && piece.color !== myColor) {
let payload = { fromRow: selectedPiece.row, fromCol: selectedPiece.col, toRow: row, toCol: col, color: piece.color };
socket.emit('move', payload);
dispatch({ type: 'CAPTURE_PIECE', val: payload }) // capture piece
}
}
const content = marked ? <Mark /> : <Piece piece={piece} />;
return (
<Flex onClick={handleClick} w="75px" h="75px" bg={(row + col) % 2 ? 'gray' : 'white'} className={`w-12 h-12 md:w-20 md:h-20 ${bgColor} flex justify-center items-center relative`}>
{content}
</Flex>
)
}
export const Mark = () => {
return (
<Box w="33%" h="33%" sx={{ backgroundColor: 'gray', borderRadius: '100%' }} m="auto"></Box>
)
}
export default Cell
+53
View File
@@ -0,0 +1,53 @@
import { Avatar, Flex, Image, Loader, NavLink, Text, Title } from '@mantine/core';
import { Link } from 'react-router-dom';
import React, { useEffect, useState } from 'react';
const FriendsList = () => {
const [friends, setFriends] = useState(null);
useEffect(() => {
const abortController = new AbortController();
let response = null;
const fetchData = async () => {
let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/user1/friends`;
try {
response = await fetch(url, { signal: abortController.signal });
const data = await response.json();
if (data.success) {
setFriends(data.friends);
} else {
throw data;
}
} catch (error) {
if (error.message === 'The user aborted a request.');
else {
console.log('Error fetching data');
throw error;
}
}
}
fetchData();
return () => {
if (!response) {
abortController.abort();
}
}
}, []);
return (
<Flex sx={{ flexGrow: '1' }} height="100%" justify="start" my="md" align="start" direction="column">
<Title px="sm" pt="md" order={3}>Friends</Title>
{
friends ?
friends.map((friend, index) => <NavLink key={index} component={Link} to="/play/friend/moonpatel" p='5px' icon={<Avatar size='sm' color='blue' children="M" />} label={<Text fw={700}>{friend}</Text>} />)
:
<Loader m="20px" variant='dots' color='lime' />
}
</Flex>
)
}
export default FriendsList
+33
View File
@@ -0,0 +1,33 @@
import { Image } from '@mantine/core';
import React from 'react';
const Piece = ({ piece }) => {
if (piece === null) return null;
const { type, color } = piece;
let logo;
switch (type) {
case 'P':
logo = color === 'W' ? 'pawn_white' : 'pawn_black';
break;
case 'R':
logo = color === 'W' ? 'rook_white' : 'rook_black';
break;
case 'N':
logo = color === 'W' ? 'knight_white' : 'knight_black';
break;
case 'B':
logo = color === 'W' ? 'bishop_white' : 'bishop_black';
break;
case 'Q':
logo = color === 'W' ? 'queen_white' : 'queen_black';
break;
case 'K':
logo = color === 'W' ? 'king_white' : 'king_black';
break;
}
return (
<Image src={`/src/assets/${logo}.png`} />
)
}
export default Piece
@@ -8,7 +8,7 @@ const AuthenticationPage = (props) => {
const { isLogin } = props;
return (
<Container maw="100%" bg="gray" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<Container maw="100%" sx={(theme) => ({ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', backgroundImage: theme.fn.gradient({ from: 'blue', to: 'teal' }) })}>
<Card shadow="md" p="lg" style={{ maxWidth: 400, width: '100%' }}>
<Text align="center" variant="h4" style={{ marginBottom: 20 }}>
{isLogin ? 'Login' : 'Sign Up'}
+30
View File
@@ -0,0 +1,30 @@
import { Avatar, Flex, Image, NavLink, Text, Title } from '@mantine/core'
import React from 'react'
import ChessBoard from '../Play/ChessBoard'
const ChessGame = () => {
return (
<Flex gap="xl" justify='center' align='center' wrap='nowrap' direction='row'>
<Flex gap="xs" justify='center' align='start' wrap='nowrap' direction='column' >
<NavLink
p="2px"
label={"username"}
icon={<Avatar radius="3px" />}
description={"description"}
/>
<ChessBoard />
<NavLink
p="2px"
label={"username"}
icon={<Avatar radius="3px" />}
description={"description"}
/>
</Flex>
<Flex w="450px" bg='gray' h="600px" sx={{ borderRadius: '10px' }}>
<Title>Game Data</Title>
</Flex>
</Flex>
)
}
export default ChessGame
@@ -0,0 +1,49 @@
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 { socket } from '../../socket'
const ChallengeFriend = () => {
const params = useParams();
let friend_username = params["friend_username"];
return (
<Card
sx={{
width: '450px',
height: '600px',
textAlign: 'center'
}}
>
<Flex align="center" direction="column" justify="center" gap="xs" my="lg">
<Title order={2}>Play vs {friend_username}</Title>
<Avatar mt="lg" color='lime' size="100px">{friend_username[0].toUpperCase()}</Avatar>
<Text>{friend_username}</Text>
</Flex>
<Select label="Time limit" placeholder='Time limit' value='10' data={['5', '10', '15', '30']} />
<Select value='W' onChange={(evt) => setColor(evt.target.value)} my="20px" label={<Text mx="auto" order={3}>I play as</Text>} placeholder='choose your color' data={[
{ value: 'W', label: 'White' },
{ value: 'B', label: 'Black' },
{ value: 'RANDOM', label: 'Random' }
]} />
<Form action={`/play/friend/${friend_username}`} method='POST'>
<Button color='lime' type='submit' >Challenge</Button>
</Form>
</Card>
)
}
export const playFriendAction = ({ request, params }) => {
let roomID = Math.floor(Math.random() * 1_000_000_000).toString();
// const req = new URL(request);
socket.connect();
socket.emit('join-room', roomID, 'user1', params.friend_username);
// socket.on('pending-challenge', () => {
// })
return redirect(`/game/friend/${roomID}`);
}
export default ChallengeFriend;
+104
View File
@@ -0,0 +1,104 @@
import React, { useEffect, useReducer, useRef } from 'react';
import { blackColor, chessBoardInit, getPieceHint, pieces, whiteColor } from '../../../utils/chess';
import Cell from '../../components/Cell';
import { socket } from '../../socket';
import { Flex } from '@mantine/core';
let myColor = 'W';
const reducer = (state, action) => {
if (state.capturedPieces.length && state.capturedPieces.at(-1).type === pieces.king) return state;
switch (action.type) {
case 'SELECT_PIECE':
{
let { row, col, color } = action.val;
let selectedPiece = { row, col, color };
let possibleMoves = getPieceHint(state.chessBoard, { row, col, color, type: state.chessBoard[row][col].type }, color).movePos;
return { ...state, selectedPiece, possibleMoves };
}
break;
case 'MOVE_PIECE':
{
let { fromRow, fromCol, toRow, toCol } = action.val;
let newChessBoard = state.chessBoard.map(row => row.slice());
let piece = state.chessBoard[fromRow][fromCol];
newChessBoard[toRow][toCol] = piece;
newChessBoard[fromRow][fromCol] = null;
return { ...state, chessBoard: newChessBoard, possibleMoves: [], selectedPiece: null, myTurn: !state.myTurn };
}
break;
case 'CAPTURE_PIECE':
{
let { fromRow, fromCol, toRow, toCol } = action.val;
let newChessBoard = state.chessBoard.map(row => row.slice());
let capturedPieces = [...state.capturedPieces, state.chessBoard[toRow][toCol]];
newChessBoard[toRow][toCol] = state.chessBoard[fromRow][fromCol];
newChessBoard[fromRow][fromCol] = null;
return { ...state, chessBoard: newChessBoard, capturedPieces, possibleMoves: [], selectedPiece: null, myTurn: !state.myTurn };
}
break;
default:
return state;
}
}
const ChessBoard = () => {
const [gameState, dispatch] = useReducer(reducer, {
chessBoard: chessBoardInit(myColor), selectedPiece: null, possibleMoves: [], capturedPieces: [], myTurn: myColor === whiteColor
});
const chessBoardRef = useRef(gameState.chessBoard);
chessBoardRef.current = gameState.chessBoard;
useEffect(() => {
function handleOpponentMove(data) {
let { fromCol, fromRow, toCol, toRow } = data;
if (chessBoardRef.current[toRow][toCol] === null) {
console.log('Moving piece: ', data)
dispatch({ type: 'MOVE_PIECE', val: { fromRow, fromCol, toRow, toCol } });
} else if (myColor === chessBoardRef.current[toRow][toCol].color) {
dispatch({ type: 'CAPTURE_PIECE', val: { fromRow, fromCol, toRow, toCol } });
}
}
socket.on('move', handleOpponentMove)
return () => {
socket.off('move', handleOpponentMove);
}
}, []);
return (
<React.Fragment>
<Flex w="600px">
<div>
{gameState.chessBoard.map((line, row) => {
return (
<Flex className='flex' key={row * 2}>
{line.map((cell, col) => {
let marked = null;
for (let k = 0; k < gameState.possibleMoves.length; k++) {
if (gameState.possibleMoves[k].row === row && gameState.possibleMoves[k].col === col) {
marked = true;
break;
}
}
let piece = cell ? { type: cell.type, color: cell.color } : null;
return <Cell
key={col * 3 + 1}
selectedPiece={gameState.selectedPiece}
cellProps={{ row, col, piece, marked }}
dispatch={dispatch}
myColor={myColor}
myTurn={gameState.myTurn}
/>
})}
</Flex>
)
})}
</div>
</Flex>
</React.Fragment>
)
}
export default ChessBoard
+2 -2
View File
@@ -6,7 +6,7 @@ const Play = () => {
return (
<Card sx={{
width: '450px',
height: '100%',
height: '600px',
textAlign: 'center'
}}>
<Flex gap="5px" px="20px" justify='center' align='center' wrap='nowrap' direction='column'>
@@ -28,7 +28,7 @@ const CardItem = ({ label, description, src, to }) => {
label={label}
icon={<Image src={src} width={50} />}
description={description}
sx={{ backgroundColor: 'gray', borderRadius: '5px' }}
sx={{ backgroundColor: 'black', borderRadius: '5px' }}
/>
)
}
+7 -10
View File
@@ -1,8 +1,8 @@
import React, { useState } from 'react'
import { Button, Card, CopyButton, Flex, Group, Image, Modal, NativeSelect, NavLink, Select, Text, TextInput, Title } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { IconSearch } from '@tabler/icons-react'
import { PersonIcon } from '@radix-ui/react-icons'
import { IconSearch, IconUserCircle } from '@tabler/icons-react'
import FriendsList from '../../components/FriendsList'
const createChallengeLink = (color) => {
let challengeLink = Math.floor(Math.random() * 100_000_000).toString();
@@ -46,10 +46,7 @@ const PlayFriend = () => {
<Title order={2}>Play a Friend</Title>
</Flex>
<TextInput my="5px" placeholder="Search by email or username" icon={<IconSearch />} />
<Flex sx={{ flexGrow: '1' }} height="100%" justify="start" align="start" direction="column">
<Title px="md" pt="md" order={3}>Friends</Title>
{friends.map((friend, index) => <NavLink key={index} icon={friend.avatar} label={<Text fw={700}>{friend.username}</Text>} />)}
</Flex>
<FriendsList />
<Flex direction='column' gap='10px'>
<Button color='lime' onClick={open}>Create Challenge Link</Button>
<Button color='lime'>Join using Challenge Link</Button>
@@ -60,10 +57,10 @@ const PlayFriend = () => {
}
const friends = [
{ avatar: <PersonIcon />, username: "friend", rating: 100 },
{ avatar: <PersonIcon />, username: "friend", rating: 100 },
{ avatar: <PersonIcon />, username: "friend", rating: 100 },
{ avatar: <PersonIcon />, username: "friend", rating: 100 },
{ avatar: <IconUserCircle />, username: "friend", rating: 100 },
{ avatar: <IconUserCircle />, username: "friend", rating: 100 },
{ avatar: <IconUserCircle />, username: "friend", rating: 100 },
{ avatar: <IconUserCircle />, username: "friend", rating: 100 },
]
export default PlayFriend
+3
View File
@@ -0,0 +1,3 @@
import { io } from "socket.io-client";
const url = import.meta.env.VITE_BACKEND_HOST
export const socket = io(url, { autoConnect: false });