From e9f39c572d780b1152945fee2ddba078acc177d3 Mon Sep 17 00:00:00 2001 From: jessy-david-dev Date: Sat, 11 Apr 2026 13:47:05 +0200 Subject: [PATCH] feat(game): add configurable rounds per room --- app/api/rooms/[code]/route.ts | 4 +- app/api/rooms/route.ts | 9 +- app/components/HomeScreen.tsx | 250 +++++++++++++++++++------------- app/components/LobbyScreen.tsx | 2 +- app/components/ScreenRouter.tsx | 8 + app/page.tsx | 6 +- lib/useGameHandlers.ts | 6 +- lib/useMultiGame.ts | 4 +- 8 files changed, 175 insertions(+), 114 deletions(-) diff --git a/app/api/rooms/[code]/route.ts b/app/api/rooms/[code]/route.ts index c80d245..8987e73 100644 --- a/app/api/rooms/[code]/route.ts +++ b/app/api/rooms/[code]/route.ts @@ -65,8 +65,8 @@ export async function PATCH( if (!playerName || typeof playerName !== "string" || playerName.trim() === "") { return Response.json({ error: "Pseudo invalide" }, { status: 400 }); } - if (room.players.length >= 8) { - return Response.json({ error: "Salle pleine (8 joueurs max)" }, { status: 409 }); + if (room.players.length >= room.maxPlayers) { + return Response.json({ error: `Salle pleine (${room.maxPlayers} joueurs max)` }, { status: 409 }); } if (room.phase !== "waiting" && room.phase !== "results") { return Response.json({ error: "Partie en cours, attends la prochaine manche" }, { status: 409 }); diff --git a/app/api/rooms/route.ts b/app/api/rooms/route.ts index 9d3348e..eed2bf7 100644 --- a/app/api/rooms/route.ts +++ b/app/api/rooms/route.ts @@ -21,6 +21,7 @@ export type Room = { phase: "waiting" | "countdown" | "playing" | "results"; round: number; totalRounds: number; + maxPlayers: number; startArticle: string; targetArticle: string; roundWinner: string | null; // player id @@ -75,12 +76,15 @@ function pruneOldRooms(rooms: Map) { // Response: { room: Room, playerId: string } export async function POST(request: NextRequest) { const body = await request.json(); - const { playerName } = body as { playerName: string }; + const { playerName, maxPlayers, totalRounds } = body as { playerName: string; maxPlayers?: number; totalRounds?: number }; if (!playerName || typeof playerName !== "string" || playerName.trim() === "") { return Response.json({ error: "Pseudo invalide" }, { status: 400 }); } + const clampedMax = Math.min(Math.max(typeof maxPlayers === "number" ? Math.floor(maxPlayers) : 16, 2), 16); + const clampedRounds = Math.min(Math.max(typeof totalRounds === "number" ? Math.floor(totalRounds) : 3, 1), 10); + const rooms = getRooms(); pruneOldRooms(rooms); @@ -109,7 +113,8 @@ export async function POST(request: NextRequest) { ], phase: "waiting", round: 0, - totalRounds: 3, + totalRounds: clampedRounds, + maxPlayers: clampedMax, startArticle: "", targetArticle: "", roundWinner: null, diff --git a/app/components/HomeScreen.tsx b/app/components/HomeScreen.tsx index 60c9be8..e1125a4 100644 --- a/app/components/HomeScreen.tsx +++ b/app/components/HomeScreen.tsx @@ -30,6 +30,10 @@ type HomeScreenProps = { setPlayerName: (v: string) => void; joinCode: string; setJoinCode: (v: string) => void; + maxPlayers: number; + setMaxPlayers: (v: number) => void; + totalRounds: number; + setTotalRounds: (v: number) => void; error: string | null; setError: (v: string | null) => void; loading: boolean; @@ -49,6 +53,10 @@ export function HomeScreen({ setPlayerName, joinCode, setJoinCode, + maxPlayers, + setMaxPlayers, + totalRounds, + setTotalRounds, error, setError, loading, @@ -94,120 +102,154 @@ export function HomeScreen({ {/* Hero */}
-
- WikiRush -

- Navigue entre les articles Wikipedia pour atteindre la cible en - premier ! -

- {today !== null && ( -

- {todayEmoji(today)}{" "} - - {today.toLocaleString("fr-FR")} - {" "} - partie{today > 1 ? "s" : ""} jouée{today > 1 ? "s" : ""} au total +

+ WikiRush +

+ Navigue entre les articles Wikipedia pour atteindre la cible en + premier !

- )} -
+ {today !== null && ( +

+ {todayEmoji(today)}{" "} + + {today.toLocaleString("fr-FR")} + {" "} + partie{today > 1 ? "s" : ""} jouée{today > 1 ? "s" : ""} au total +

+ )} +
- {/* Form */} -
- {error && ( -
- {error} - + {/* Form */} +
+ {error && ( +
+ {error} + +
+ )} + + setPlayerName(e.target.value)} + maxLength={20} + onKeyDown={(e) => e.key === "Enter" && onCreateRoom()} + /> + + {/* Multijoueur */} +
+

+ Multijoueur +

+
+ +
+ + Max + + +
+
+ + Manches + + +
+
+ {/* Join row - colonne sur mobile, ligne sur sm+ */} +
+ + setJoinCode(e.target.value.toUpperCase().slice(0, 4)) + } + maxLength={4} + onKeyDown={(e) => e.key === "Enter" && onJoinRoom()} + /> + +
- )} - setPlayerName(e.target.value)} - maxLength={20} - onKeyDown={(e) => e.key === "Enter" && onCreateRoom()} - /> + {/* Divider */} +
+ ou +
+
- {/* Multijoueur */} -
-

- Multijoueur -

- - {/* Join row - colonne sur mobile, ligne sur sm+ */} -
- - setJoinCode(e.target.value.toUpperCase().slice(0, 4)) - } - maxLength={4} - onKeyDown={(e) => e.key === "Enter" && onJoinRoom()} - /> + {/* Solo */} +
+

+ Solo +

+ +
- - {/* Divider */} -
- ou -
-
- - {/* Solo */} -
-

- Solo -

- - - -
-
); diff --git a/app/components/LobbyScreen.tsx b/app/components/LobbyScreen.tsx index 70b784a..d1a1f60 100644 --- a/app/components/LobbyScreen.tsx +++ b/app/components/LobbyScreen.tsx @@ -62,7 +62,7 @@ export function LobbyScreen({ {/* Players */}

- Joueurs ({room.players.length}/8) + Joueurs ({room.players.length}/{room.maxPlayers})

    {room.players.map((p) => ( diff --git a/app/components/ScreenRouter.tsx b/app/components/ScreenRouter.tsx index 1982066..8a3b976 100644 --- a/app/components/ScreenRouter.tsx +++ b/app/components/ScreenRouter.tsx @@ -28,6 +28,10 @@ type Props = { setPlayerName: Dispatch>; joinCode: string; setJoinCode: Dispatch>; + maxPlayers: number; + setMaxPlayers: Dispatch>; + totalRounds: number; + setTotalRounds: Dispatch>; error: string | null; setError: Dispatch>; loading: boolean; @@ -38,6 +42,8 @@ type Props = { export function ScreenRouter({ screen, setScreen, session, solo, multi, handlers, playerName, setPlayerName, joinCode, setJoinCode, + maxPlayers, setMaxPlayers, + totalRounds, setTotalRounds, error, setError, loading, showAuth, setShowAuth, }: Props) { if (screen === "profile") return ( @@ -61,6 +67,8 @@ export function ScreenRouter({ ("home"); const [playerName, setPlayerName] = useState(""); const [joinCode, setJoinCode] = useState(""); + const [maxPlayers, setMaxPlayers] = useState(16); + const [totalRounds, setTotalRounds] = useState(3); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const [showAuth, setShowAuth] = useState(false); @@ -20,7 +22,7 @@ export default function WikiRush() { const multi = useMultiGame(); const { session } = useGameEffects({ solo, multi, screen, setScreen, setPlayerName }); - const handlers = useGameHandlers({ solo, multi, playerName, joinCode, setScreen, setError, setLoading }); + const handlers = useGameHandlers({ solo, multi, playerName, joinCode, maxPlayers, totalRounds, setScreen, setError, setLoading }); return ( diff --git a/lib/useGameHandlers.ts b/lib/useGameHandlers.ts index 24f3e2f..456185b 100644 --- a/lib/useGameHandlers.ts +++ b/lib/useGameHandlers.ts @@ -11,18 +11,20 @@ type Handlers = { multi: UseMultiGame; playerName: string; joinCode: string; + maxPlayers: number; + totalRounds: number; setScreen: Dispatch>; setError: Dispatch>; setLoading: Dispatch>; }; export function useGameHandlers({ - solo, multi, playerName, joinCode, setScreen, setError, setLoading, + solo, multi, playerName, joinCode, maxPlayers, totalRounds, setScreen, setError, setLoading, }: Handlers) { async function handleCreateRoom() { if (!playerName.trim()) { setError("Entre ton pseudo !"); return; } setLoading(true); setError(null); - const { error: err } = await multi.createRoom(playerName.trim()); + const { error: err } = await multi.createRoom(playerName.trim(), maxPlayers, totalRounds); setLoading(false); if (err) { setError(err); return; } setScreen("lobby"); diff --git a/lib/useMultiGame.ts b/lib/useMultiGame.ts index c63f6af..b7def49 100644 --- a/lib/useMultiGame.ts +++ b/lib/useMultiGame.ts @@ -152,11 +152,11 @@ export function useMultiGame() { // Room actions - async function createRoom(playerName: string): Promise<{ error?: string }> { + async function createRoom(playerName: string, maxPlayers = 16, totalRounds = 3): Promise<{ error?: string }> { const res = await fetch("/api/rooms", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ playerName }), + body: JSON.stringify({ playerName, maxPlayers, totalRounds }), }); const data = await res.json() as { room?: Room; playerId?: string; error?: string }; if (!res.ok) return { error: data.error ?? "Erreur" };