Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display avatar in pong #280

Merged
merged 10 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
!/public/avatar/shongou.jpg
!/public/avatar/kakiba.jpg
!/public/avatar/thara.jpg
!/public/avatar/undefined.jpg

# Logs
logs
Expand Down
Binary file added backend/public/avatar/undefined.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 29 additions & 9 deletions backend/src/events/events.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,14 @@ export class EventsGateway implements OnGatewayDisconnect {
client.join(gameId);

if (!isPlayer) {
this.emitUpdateStatus(client, 'joined-as-viewer');
this.emitUpdateStatus(client, 'joined-as-viewer', {
players: Object.keys(this.players[gameId] || {}).map(
(socketId, playerNumber) => ({
playerNumber,
user: this.users[socketId],
}),
),
});
return;
}

Expand All @@ -127,10 +134,16 @@ export class EventsGateway implements OnGatewayDisconnect {
return;
}
addPlayer(this.players, gameId, client.id);
this.emitUpdateStatusToRoomId(client, gameId, 'friend-joined');
this.emitUpdateStatusToRoomId(client, gameId, 'friend-joined', {
playerNumber: this.players[gameId][client.id],
user,
});
this.emitUpdateStatus(client, 'joined-as-player');
if (Object.keys(this.players[gameId]).length == 2) {
this.emitUpdateStatus(client, 'ready');
const opponent = getOpponent(this.players, gameId, client.id);
if (opponent) {
this.emitUpdateStatus(client, 'ready', {
user: this.users[opponent],
});
}
this.lostPoints[client.id] = 0;
return;
Expand All @@ -144,7 +157,9 @@ export class EventsGateway implements OnGatewayDisconnect {
delete this.users[client.id];

if (isPlayer(this.players, roomId, client.id)) {
this.emitUpdateStatusToRoomId(client, roomId, 'friend-left');
this.emitUpdateStatusToRoomId(client, roomId, 'friend-left', {
playerNumber: this.players[roomId][client.id],
});
removePlayer(this.players, roomId, client.id);
delete this.lostPoints[client.id];
}
Expand Down Expand Up @@ -296,12 +311,17 @@ export class EventsGateway implements OnGatewayDisconnect {
else socket.to(roomId).emit(eventName);
}

emitUpdateStatus(socket: Socket, status: Status) {
socket.emit('update-status', status);
emitUpdateStatus(socket: Socket, status: Status, payload: any = null) {
socket.emit('update-status', { status, payload });
}

emitUpdateStatusToRoomId(socket: Socket, roomId: string, status: Status) {
socket.to(roomId).emit('update-status', status);
emitUpdateStatusToRoomId(
socket: Socket,
roomId: string,
status: Status,
payload: any = null,
) {
socket.to(roomId).emit('update-status', { status, payload });
}

async createHistory(socket: Socket) {
Expand Down
24 changes: 17 additions & 7 deletions frontend/app/pong/GameList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { useToast } from "@/components/ui/use-toast";
import { useCallback, useEffect, useMemo, useState } from "react";
import { IoRefresh } from "react-icons/io5";
import { io } from "socket.io-client";
import { GameCard } from "./GameCard";
import { GameCard } from "../ui/pong/GameCard";
import { useRouter } from "next/navigation";

export default function GameList() {
const socket = useMemo(() => io("/pong", { forceNew: true }), []);
const router = useRouter();

const [games, setGames] = useState([]);
const { toast } = useToast();
Expand All @@ -34,19 +36,27 @@ export default function GameList() {
<CardHeader>
<CardTitle>Ongoing games</CardTitle>
</CardHeader>
<CardContent className="flex flex-col items-center">
<CardContent className="flex flex-col items-center gap-5">
<Button variant="ghost" onClick={requestListingGames}>
<IoRefresh />
</Button>
{games.length === 0 ? (
<p>No ongoing games</p>
) : (
games.map((game: any, index: number) => (
<GameCard
key={game.roomId}
roomId={game.roomId}
players={game.players}
/>
<div key={game.roomID} className="flex flex-col align-middle">
<GameCard
leftPlayer={game.players[0]}
rightPlayer={game.players[1]}
/>
<Button
onClick={() =>
router.push(`/pong/${game.roomId}?mode=viewer`)
}
>
View
</Button>
</div>
))
)}
</CardContent>
Expand Down
65 changes: 56 additions & 9 deletions frontend/app/pong/[id]/PongBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { io } from "socket.io-client";
import { PongGame } from "./PongGame";
import PongInformationBoard from "./PongInformationBoard";
import { CANVAS_HEIGHT, CANVAS_WIDTH, TARGET_FRAME_MS } from "./const";
import { PublicUserEntity } from "@/app/lib/dtos";

type Status =
| "too-many-players"
Expand Down Expand Up @@ -84,13 +85,29 @@ function PongBoard({ id }: PongBoardProps) {
const gameRef = useRef<PongGame | null>(null); // only initialized once
const socketRef = useRef<Socket | null>(null); // updated on `id` change
const [startDisabled, setStartDisabled] = useState(true);
const [practiceDisabled, setPracticeDisabled] = useState(true);
const [battleDisabled] = useState(true);
const { resolvedTheme } = useTheme();
const defaultColor = "hsl(0, 0%, 0%)";

const { currentUser } = useAuthContext();

const [leftPlayer, setLeftPlayer] = useState<PublicUserEntity | undefined>(
() => (userMode === "player" ? currentUser : undefined),
);
const [rightPlayer, setRightPlayer] = useState<PublicUserEntity | undefined>(
undefined,
);

const getPlayerSetterFromPlayerNumber = useCallback(
(playerNumber: number) => {
return userMode == "player"
? setRightPlayer
: playerNumber == 1
? setLeftPlayer
: setRightPlayer;
},
[userMode],
);

const getGame = useCallback(() => {
const ctx = canvasRef.current?.getContext("2d");
if (!ctx) {
Expand Down Expand Up @@ -127,7 +144,7 @@ function PongBoard({ id }: PongBoardProps) {
}, [getGame, userMode]);

const runSideEffectForStatusUpdate = useCallback(
(status: Status) => {
(status: Status, payload: any) => {
const game = getGame();

switch (status) {
Expand All @@ -140,17 +157,39 @@ function PongBoard({ id }: PongBoardProps) {
setUserMode("viewer");
break;
case "friend-joined":
const { playerNumber, user } = payload;
const setter = getPlayerSetterFromPlayerNumber(playerNumber);
setter(user);
currentUser && setStartDisabled(false);
setPracticeDisabled(true);
game.resetPlayerPosition();
break;
case "friend-left":
setStartDisabled(true);
setPracticeDisabled(false);
{
const { playerNumber } = payload;
const setter = getPlayerSetterFromPlayerNumber(playerNumber);
setter(undefined);
setStartDisabled(true);
}
break;
case "joined-as-viewer":
{
const { players } = payload;
players.forEach(({ playerNumber, user }: any) => {
const setter = getPlayerSetterFromPlayerNumber(playerNumber);
setter(user);
});
}
break;
case "ready": {
{
const { user } = payload;
setRightPlayer(user);
}
break;
}
}
},
[currentUser, setUserMode, getGame],
[currentUser, setUserMode, getGame, getPlayerSetterFromPlayerNumber],
);

useEffect(() => {
Expand Down Expand Up @@ -212,8 +251,14 @@ function PongBoard({ id }: PongBoardProps) {
socket.emit(action);
};

const handleUpdateStatus = (status: Status) => {
runSideEffectForStatusUpdate(status);
const handleUpdateStatus = ({
status,
payload,
}: {
status: Status;
payload: any;
}) => {
runSideEffectForStatusUpdate(status, payload);
const log = getLogFromStatus(status);
setLogs((logs) => [...logs, log]);
};
Expand Down Expand Up @@ -325,6 +370,8 @@ function PongBoard({ id }: PongBoardProps) {
player2Position={player2Position}
logs={logs}
userMode={userMode}
leftPlayer={leftPlayer}
rightPlayer={rightPlayer}
/>
</div>
</div>
Expand Down
8 changes: 8 additions & 0 deletions frontend/app/pong/[id]/PongInformationBoard.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
"use client";

import { PublicUserEntity } from "@/app/lib/dtos";
import { GameCard } from "@/app/ui/pong/GameCard";

interface PongInformationBoardProps {
fps: number;
speed: number;
player1Position: number;
player2Position: number;
logs: string[];
userMode: "viewer" | "player";
leftPlayer?: PublicUserEntity;
rightPlayer?: PublicUserEntity;
}

export default function PongInformationBoard({
Expand All @@ -16,9 +21,12 @@ export default function PongInformationBoard({
player2Position,
logs,
userMode,
leftPlayer,
rightPlayer,
}: PongInformationBoardProps) {
return (
<div className="overflow-hidden flex-grow flex flex-col gap-1">
<GameCard leftPlayer={leftPlayer} rightPlayer={rightPlayer}></GameCard>
<div>You are a {userMode}</div>
<div id="fps">FPS: {fps}</div>
<div id="speed">Speed: {speed}</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,45 @@ import { Avatar } from "@/app/ui/user/avatar";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { useRouter } from "next/navigation";
import { PublicUserEntity } from "../lib/dtos";
import { PublicUserEntity } from "../../lib/dtos";

function UserCard({ user }: { user: PublicUserEntity }) {
function UserCard({ user }: { user?: PublicUserEntity }) {
const name = user?.name || "";
const avatarUrl = user?.avatarURL || "/avatar/undefined.jpg";
const href = user ? `/user/${user.id}` : undefined;
const id = user?.id;
return (
<>
<div className="flex gap-4 items-center">
{user.name}
{name}
<Avatar
avatarURL={user.avatarURL}
avatarURL={avatarUrl}
size="medium"
href={`/user/${user.id}`}
alt={user.name}
id={user.id}
href={href}
alt={name}
id={id}
/>
</div>
</>
);
}

export function GameCard({
roomId,
players,
leftPlayer,
rightPlayer,
}: {
roomId: string;
players: PublicUserEntity[];
leftPlayer?: PublicUserEntity;
rightPlayer?: PublicUserEntity;
}) {
const router = useRouter();

const handleView = () => {
router.push(`/pong/${roomId}?mode=viewer`);
};

return (
<Card className="w-[350px] p-4 flex flex-col gap-2 items-center">
<div className="flex gap-10 items-center justify-evenly">
<TooltipProvider delayDuration={0}>
<UserCard user={players[0]} />
<UserCard user={leftPlayer} />
{" vs "}
<UserCard user={players[1]} />
<UserCard user={rightPlayer} />
</TooltipProvider>
</div>
<Button onClick={handleView}>View</Button>
</Card>
);
}
Loading