socket connection established and chess logic imported from old codebase
@@ -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 /> },
|
||||
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -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
|
||||
@@ -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
|
||||
@@ -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'}
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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 });
|
||||