Skip to content

Commit

Permalink
Display avatar in pong (#280)
Browse files Browse the repository at this point in the history
* Separate game card and view button

* Move GameCard to common dir

* Take players separately in GameCard

* Remove unnecessary code

* Handle undefined player in GameCard

* Add payload in update-status message

* Handle user join

* Handle user leave

* Handle viewer join

* Handle player join
  • Loading branch information
takumihara authored Feb 22, 2024
1 parent c5c6495 commit 753cfc4
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 46 deletions.
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>
);
}

0 comments on commit 753cfc4

Please sign in to comment.