Skip to content

Commit

Permalink
Merge pull request #356 from giripatel/Feat/Add_Exit_Game
Browse files Browse the repository at this point in the history
Feat/Add Exit Game Functionality
  • Loading branch information
siinghd authored Jun 19, 2024
2 parents 80464c0 + 57d6609 commit 6be6745
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 38 deletions.
1 change: 1 addition & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@types/express-session": "^1.18.0",
"cookie-session": "^2.1.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-session": "^1.18.0",
"jsonwebtoken": "^9.0.2",
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-slot": "^1.0.2",
Expand Down
39 changes: 39 additions & 0 deletions apps/frontend/src/components/ExitGameModel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
AlertDialogAction,
AlertDialogCancel,
AlertDialogTitle,
AlertDialogHeader,
AlertDialogFooter,
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogDescription,
} from './ui/alert-dialog';

const ExitGameModel = ({ onClick } : {onClick : () => void}) => {

return (
<AlertDialog>
<AlertDialogTrigger className='w-24 h-12 bg-stone-800 rounded-md font-semibold text-white'>Exit</AlertDialogTrigger>
<AlertDialogContent className='bg-stone-800 border-stone-800'>
<AlertDialogHeader>
<AlertDialogTitle className='text-white font-mono'>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription className='text-white font-mono'>
This action cannot be undone. This will be considered as a loss.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className='font-mono bg-[#739552] text-white font-semibold hover:bg-[#b2e084] hover:text-gray-700 border-none'>Continue</AlertDialogCancel>
<AlertDialogAction
onClick={onClick}
className='bg-[#e2e6aa] min-w-20 text-gray-900 hover:text-slate-100 hover:bg-[#bbc259] font-semibold'
>
Exit
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

export default ExitGameModel;
139 changes: 139 additions & 0 deletions apps/frontend/src/components/ui/alert-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import * as React from 'react';
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';

import { cn } from '@/lib/utils';
import { buttonVariants } from '@/components/ui/button';

const AlertDialog = AlertDialogPrimitive.Root;

const AlertDialogTrigger = AlertDialogPrimitive.Trigger;

const AlertDialogPortal = AlertDialogPrimitive.Portal;

const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className,
)}
{...props}
ref={ref}
/>
));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;

const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className,
)}
{...props}
/>
</AlertDialogPortal>
));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;

const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-2 text-center sm:text-left',
className,
)}
{...props}
/>
);
AlertDialogHeader.displayName = 'AlertDialogHeader';

const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className,
)}
{...props}
/>
);
AlertDialogFooter.displayName = 'AlertDialogFooter';

const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold', className)}
{...props}
/>
));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;

const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName;

const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;

const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: 'outline' }),
'mt-2 sm:mt-0',
className,
)}
{...props}
/>
));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;

export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
};
86 changes: 49 additions & 37 deletions apps/frontend/src/screens/Game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const GAME_ADDED = 'game_added';
export const USER_TIMEOUT = 'user_timeout';
export const GAME_TIME = 'game_time';
export const GAME_ENDED = 'game_ended';
export const EXIT_GAME = 'exit_game';
export enum Result {
WHITE_WINS = 'WHITE_WINS',
BLACK_WINS = 'BLACK_WINS',
Expand All @@ -33,7 +34,6 @@ export interface GameResult {
by: string;
}


const GAME_TIME_MS = 10 * 60 * 1000;

import { useRecoilValue, useSetRecoilState } from 'recoil';
Expand All @@ -42,6 +42,7 @@ import { movesAtom, userSelectedMoveIndexAtom } from '@repo/store/chessBoard';
import GameEndModal from '@/components/GameEndModal';
import { Waitopponent } from '@/components/ui/waitopponent';
import { ShareGame } from '../components/ShareGame';
import ExitGameModel from '@/components/ExitGameModel';

const moveAudio = new Audio(MoveSound);

Expand All @@ -62,10 +63,7 @@ export const Game = () => {
const [added, setAdded] = useState(false);
const [started, setStarted] = useState(false);
const [gameMetadata, setGameMetadata] = useState<Metadata | null>(null);
const [result, setResult] = useState<
GameResult
| null
>(null);
const [result, setResult] = useState<GameResult | null>(null);
const [player1TimeConsumed, setPlayer1TimeConsumed] = useState(0);
const [player2TimeConsumed, setPlayer2TimeConsumed] = useState(0);
const [gameID,setGameID] = useState("");
Expand Down Expand Up @@ -133,25 +131,25 @@ export const Game = () => {
break;

case GAME_ENDED:
const wonBy = message.payload.status === 'COMPLETED' ?
message.payload.result !== 'DRAW' ? 'CheckMate' : 'Draw' : 'Timeout';
let wonBy;
switch (message.payload.status) {
case 'COMPLETED':
wonBy = message.payload.result !== 'DRAW' ? 'CheckMate' : 'Draw';
break;
case 'PLAYER_EXIT':
wonBy = 'Player Exit';
break;
default:
wonBy = 'Timeout';
}
setResult({
result: message.payload.result,
by: wonBy,
});
chess.reset();
setMoves(() => {
message.payload.moves.map((curr_move: Move) => {
chess.move(curr_move as Move);
});
return message.payload.moves;
});
setGameMetadata({
blackPlayer: message.payload.blackPlayer,
whitePlayer: message.payload.whitePlayer,
});


setStarted(false);
setAdded(false);

break;

case USER_TIMEOUT:
Expand Down Expand Up @@ -228,6 +226,19 @@ export const Game = () => {
);
};

const handleExit = () => {
socket?.send(
JSON.stringify({
type: EXIT_GAME,
payload: {
gameId,
},
}),
);
setMoves([]);
navigate('/');
};

if (!socket) return <div>Connecting...</div>;

return (
Expand Down Expand Up @@ -255,26 +266,24 @@ export const Game = () => {
<div>
{started && (
<div className="mb-4">
<div className="flex justify-between">
<UserAvatar
name={
<div className="flex justify-between">
<UserAvatar
name={
user.id === gameMetadata?.whitePlayer?.id
? gameMetadata?.blackPlayer?.name
: gameMetadata?.whitePlayer?.name ?? ''
}
/>
{getTimer(
user.id === gameMetadata?.whitePlayer?.id
? gameMetadata?.blackPlayer?.name
: gameMetadata?.whitePlayer?.name ?? ''
}
/>
{getTimer(
user.id === gameMetadata?.whitePlayer?.id
? player2TimeConsumed
: player1TimeConsumed,
)}
</div>
? player2TimeConsumed
: player1TimeConsumed,
)}
</div>
</div>
)}
<div>
<div
className={`w-full flex justify-center text-white`}
>
<div className={`w-full flex justify-center text-white`}>
<ChessBoard
started={started}
gameId={gameId ?? ''}
Expand Down Expand Up @@ -308,14 +317,13 @@ export const Game = () => {
</div>
</div>
<div className="rounded-md pt-2 bg-bgAuxiliary3 flex-1 overflow-auto h-[95vh] overflow-y-scroll no-scrollbar">
{!started && (
{!started ? (
<div className="pt-8 flex justify-center w-full">
{added ? (
<div className='flex flex-col items-center space-y-4 justify-center'>
<div className="text-white"><Waitopponent/></div>
<ShareGame gameId={gameID}/>
</div>

) : (
gameId === 'random' && (
<Button
Expand All @@ -332,6 +340,10 @@ export const Game = () => {
)
)}
</div>
) : (
<div className="p-8 flex justify-center w-full">
<ExitGameModel onClick={() => handleExit()} />
</div>
)}
<div>
<MovesTable />
Expand Down
6 changes: 5 additions & 1 deletion apps/ws/src/Game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { db } from './db';
import { randomUUID } from 'crypto';
import { socketManager, User } from './SocketManager';

type GAME_STATUS = 'IN_PROGRESS' | 'COMPLETED' | 'ABANDONED' | 'TIME_UP';
type GAME_STATUS = 'IN_PROGRESS' | 'COMPLETED' | 'ABANDONED' | 'TIME_UP' | 'PLAYER_EXIT';
type GAME_RESULT = "WHITE_WINS" | "BLACK_WINS" | "DRAW";

const GAME_TIME_MS = 10 * 60 * 60 * 1000;
Expand Down Expand Up @@ -313,6 +313,10 @@ export class Game {
}, timeLeft);
}

async exitGame(user : User) {
this.endGame('PLAYER_EXIT', user.userId === this.player2UserId ? 'WHITE_WINS' : 'BLACK_WINS');
}

async endGame(status: GAME_STATUS, result: GAME_RESULT) {
const updatedGame = await db.game.update({
data: {
Expand Down
Loading

0 comments on commit 6be6745

Please sign in to comment.