diff --git a/frontend/app/lib/hooks/useUserMode.ts b/frontend/app/lib/hooks/useUserMode.ts new file mode 100644 index 00000000..91fc2d3a --- /dev/null +++ b/frontend/app/lib/hooks/useUserMode.ts @@ -0,0 +1,22 @@ +import { useCallback } from "react"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; + +export type UserModeType = "viewer" | "player"; + +export function useUserMode(): [UserModeType, (mode: UserModeType) => void] { + const pathname = usePathname(); + const { replace } = useRouter(); + const searchParams = useSearchParams(); + const userMode = searchParams.get("mode") == "player" ? "player" : "viewer"; + + const setUserMode = useCallback( + (mode: UserModeType) => { + const params = new URLSearchParams(searchParams); + params.set("mode", mode); + replace(`${pathname}?${params.toString()}`); + }, + [searchParams, pathname, replace], + ); + + return [userMode, setUserMode]; +} diff --git a/frontend/app/pong/[id]/PongBoard.tsx b/frontend/app/pong/[id]/PongBoard.tsx index 60c1f7f8..d3e3d002 100644 --- a/frontend/app/pong/[id]/PongBoard.tsx +++ b/frontend/app/pong/[id]/PongBoard.tsx @@ -8,7 +8,7 @@ import { io } from "socket.io-client"; import type { Socket } from "socket.io-client"; import PongInformationBoard from "./PongInformationBoard"; import { useTheme } from "next-themes"; -import { useSearchParams } from "next/navigation"; +import { useUserMode } from "@/app/lib/hooks/useUserMode"; type setState = T | ((prevState: T) => T); @@ -34,12 +34,13 @@ interface HandleActionProps { const POINT_TO_WIN = 3; -function PongBoard({ id: id }: PongBoardProps) { +function PongBoard({ id }: PongBoardProps) { const [fps, setFps] = useStateCallback(0); const [speed, setSpeed] = useStateCallback(0); const [player1Position, setPlayer1Position] = useStateCallback(0); const [player2Position, setPlayer2Position] = useStateCallback(0); const [logs, setLogs] = useStateCallback([]); + const [userMode, setUserMode] = useUserMode(); const canvasRef = useRef(null); // only initialized once const gameRef = useRef(null); // only initialized once @@ -49,8 +50,6 @@ function PongBoard({ id: id }: PongBoardProps) { const [battleDisabled] = useState(true); const { resolvedTheme } = useTheme(); const defaultColor = "hsl(0, 0%, 0%)"; - const searchParams = useSearchParams(); - const isPlayer = searchParams.get("mode") == "player"; const getGame = useCallback(() => { const ctx = canvasRef.current?.getContext("2d"); @@ -66,16 +65,16 @@ function PongBoard({ id: id }: PongBoardProps) { setPlayer2Position, defaultColor, defaultColor, - isPlayer, + userMode, ); gameRef.current = game; return game; } return gameRef.current; - }, [setFps, setSpeed, setPlayer1Position, setPlayer2Position, isPlayer]); + }, [setFps, setSpeed, setPlayer1Position, setPlayer2Position, userMode]); const start = useCallback(() => { - if (!isPlayer) return; + if (!userMode) return; const game = getGame(); setStartDisabled(true); @@ -85,7 +84,12 @@ function PongBoard({ id: id }: PongBoardProps) { vx: -vx, vy: -vy, }); - }, [getGame, isPlayer]); + }, [getGame, userMode]); + + useEffect(() => { + const game = getGame(); + game.setUserMode(userMode); + }, [getGame, userMode]); useEffect(() => { // TODO: Use --foreground color from CSS @@ -130,7 +134,9 @@ function PongBoard({ id: id }: PongBoardProps) { }, [getGame]); useEffect(() => { - const socket = io("/pong", { query: { game_id: id, is_player: isPlayer } }); + const socket = io("/pong", { + query: { game_id: id, is_player: userMode === "player" }, + }); socketRef.current = socket; const game = getGame(); @@ -139,6 +145,10 @@ function PongBoard({ id: id }: PongBoardProps) { }; const handleLog = (log: string) => { + // TODO + if (log == "The game is full. You joined as a viewer.") { + setUserMode("viewer"); + } setLogs((logs) => [...logs, log]); }; const handleConnect = () => { @@ -154,7 +164,7 @@ function PongBoard({ id: id }: PongBoardProps) { }; const handleRight = ({ playerNumber }: HandleActionProps) => { - if (!isPlayer && playerNumber == 1) { + if (userMode !== "player" && playerNumber == 1) { game.movePlayer1Left(); } else { game.movePlayer2Left(); @@ -162,7 +172,7 @@ function PongBoard({ id: id }: PongBoardProps) { }; const handleLeft = ({ playerNumber }: HandleActionProps) => { - if (!isPlayer && playerNumber == 1) { + if (userMode !== "player" && playerNumber == 1) { game.movePlayer1Right(); } else { game.movePlayer2Right(); @@ -170,7 +180,7 @@ function PongBoard({ id: id }: PongBoardProps) { }; const handleBounce = ({ playerNumber }: HandleActionProps) => { - if (!isPlayer && playerNumber == 1) { + if (userMode !== "player" && playerNumber == 1) { game.bounceOffPaddlePlayer1(); } else { game.bounceOffPaddlePlayer2(); @@ -180,7 +190,7 @@ function PongBoard({ id: id }: PongBoardProps) { const handleCollide = (msg: HandleActionProps) => { const { playerNumber } = msg; console.log(msg); - if (isPlayer) { + if (userMode === "player") { const score = game.increaseScorePlayer1(); if (score != POINT_TO_WIN) { setTimeout(() => start(), 1000); @@ -241,7 +251,7 @@ function PongBoard({ id: id }: PongBoardProps) { socket.off("finish", handleFinish); socket.disconnect(); }; - }, [id, getGame, setLogs, start, isPlayer]); + }, [id, getGame, setLogs, start, userMode, setUserMode]); return (
@@ -275,7 +285,7 @@ function PongBoard({ id: id }: PongBoardProps) { player1Position={player1Position} player2Position={player2Position} logs={logs} - isPlayer={isPlayer} + userMode={userMode} />
diff --git a/frontend/app/pong/[id]/PongGame.ts b/frontend/app/pong/[id]/PongGame.ts index 1f21b3f4..05110032 100644 --- a/frontend/app/pong/[id]/PongGame.ts +++ b/frontend/app/pong/[id]/PongGame.ts @@ -13,6 +13,7 @@ import { type setFunction = (value: T | ((prevState: T) => T)) => void; type movingDirectionType = "none" | "left" | "right"; type onActionType = (action: string) => void; +type userModeType = "player" | "viewer"; export class PongGame { private ctx: CanvasRenderingContext2D; @@ -34,7 +35,7 @@ export class PongGame { onAction: onActionType | undefined; private paddleColor: string; private ballColor: string; - private isPlayer: boolean; + private userMode: userModeType; constructor( ctx: CanvasRenderingContext2D, @@ -44,7 +45,7 @@ export class PongGame { setPlayer2Position: setFunction, paddleColor: string, ballColor: string, - isPlayer: boolean, + userMode: userModeType, ) { this.ctx = ctx; this.ctx.textAlign = "center"; @@ -78,7 +79,7 @@ export class PongGame { this.setSpeed = setSpeed; this.setPlayer1Position = setPlayer1Position; this.setPlayer2Position = setPlayer2Position; - this.isPlayer = isPlayer; + this.userMode = userMode; } update_fps = () => { @@ -113,12 +114,12 @@ export class PongGame { // Draw objects this.ball.move(this.elapsed); if (this.player1.collide_with(this.ball)) { - if (this.isPlayer) { + if (this.userMode === "player") { this.ball.bounce_off_paddle(this.player1); this.onAction && this.onAction("bounce"); } } else if (this.ball.y + this.ball.radius * 2 >= CANVAS_HEIGHT) { - if (this.isPlayer) { + if (this.userMode === "player") { this.ball.reset(); this.score.player2++; this.onAction && this.onAction("collide"); @@ -148,7 +149,7 @@ export class PongGame { this.update_fps(); this.update_speed(this.ball.speed()); this.update_players(); - if (this.isPlayer) { + if (this.userMode === "player") { if (this.movingDirection === "left") { this.player1.clear(this.ctx); this.player1.move_left(); @@ -300,4 +301,8 @@ export class PongGame { this.player1.color = color; this.player2.color = color; } + + setUserMode(userMode: userModeType) { + this.userMode = userMode; + } } diff --git a/frontend/app/pong/[id]/PongInformationBoard.tsx b/frontend/app/pong/[id]/PongInformationBoard.tsx index 6d53a5a3..5d306949 100644 --- a/frontend/app/pong/[id]/PongInformationBoard.tsx +++ b/frontend/app/pong/[id]/PongInformationBoard.tsx @@ -6,7 +6,7 @@ interface PongInformationBoardProps { player1Position: number; player2Position: number; logs: string[]; - isPlayer: boolean; + userMode: "viewer" | "player"; } export default function PongInformationBoard({ @@ -15,11 +15,11 @@ export default function PongInformationBoard({ player1Position, player2Position, logs, - isPlayer, + userMode, }: PongInformationBoardProps) { return (
-
You are a {isPlayer ? "player" : "viewer"}
+
You are a {userMode}
FPS: {fps}
Speed: {speed}
diff --git a/frontend/app/pong/[id]/page.tsx b/frontend/app/pong/[id]/page.tsx index 1ceeedb1..580ed6ef 100644 --- a/frontend/app/pong/[id]/page.tsx +++ b/frontend/app/pong/[id]/page.tsx @@ -1,4 +1,3 @@ -"use client"; import PongBoard from "./PongBoard"; export default function Page({ params: { id } }: { params: { id: string } }) {