diff --git a/app/games/[gameId]/visualizer.tsx b/app/games/[gameId]/visualizer.tsx index 2f41a5e..3dcec0c 100644 --- a/app/games/[gameId]/visualizer.tsx +++ b/app/games/[gameId]/visualizer.tsx @@ -1,16 +1,60 @@ import { Button } from "@/components/ui/button"; import { ZombieSurvival } from "@/simulators/zombie-survival"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; const REPLAY_SPEED = 600; -export function Visualizer({ map }: { map: string[][] }) { +export function Visualizer({ + map, + autoStart = false, + onSimulationEnd, +}: { + map: string[][]; + autoStart?: boolean; + onSimulationEnd?: (isWin: boolean) => void; +}) { const simulator = useRef(null); const interval = useRef(null); const [isRunning, setIsRunning] = useState(false); const [mapState, setMapState] = useState(map); const [needsReset, setNeedsReset] = useState(false); + const startSimulation = () => { + setNeedsReset(true); + const clonedMap = JSON.parse(JSON.stringify(map)); + simulator.current = new ZombieSurvival(clonedMap); + setMapState(simulator.current!.getState()); + setIsRunning(true); + interval.current = setInterval(() => { + if (simulator.current!.finished()) { + clearInterval(interval.current!); + interval.current = null; + setIsRunning(false); + if (onSimulationEnd) { + console.log("here"); + onSimulationEnd(!simulator.current!.getPlayer().dead()); + } + return; + } + simulator.current!.step(); + setMapState(simulator.current!.getState()); + }, REPLAY_SPEED); + }; + + useEffect(() => { + if (autoStart) { + startSimulation(); + } + }, [autoStart]); + + useEffect(() => { + return () => { + if (interval.current) { + clearInterval(interval.current); + } + }; + }, []); + return (
{mapState.map((row, y) => ( @@ -26,27 +70,8 @@ export function Visualizer({ map }: { map: string[][] }) {
))}
- + + + + + + +
Synced using Convex Convex -
-
-
- +
+
{!isAuthenticated ? ( diff --git a/app/play/[level]/page.tsx b/app/play/[level]/page.tsx new file mode 100644 index 0000000..9448788 --- /dev/null +++ b/app/play/[level]/page.tsx @@ -0,0 +1,134 @@ +"use client"; + +import { useState } from "react"; +import { useQuery } from "convex/react"; +import { api } from "@/convex/_generated/api"; +import { Button } from "@/components/ui/button"; +import { Visualizer } from "@/app/games/[gameId]/visualizer"; +import { Map } from "@/app/map"; +import Link from "next/link"; +import { ChevronLeftIcon } from "@radix-ui/react-icons"; + +export default function PlayLevelPage({ + params, +}: { + params: { level: string }; +}) { + const level = parseInt(params.level, 10); + const map = useQuery(api.maps.getMapByLevel, { level }); + const [playerMap, setPlayerMap] = useState([]); + const [isSimulating, setIsSimulating] = useState(false); + const [gameResult, setGameResult] = useState<"WON" | "LOST" | null>(null); + + if (!map) { + return
Loading...
; + } + + function handleRetryClicked() { + setIsSimulating(false); + setGameResult(null); + if (map) { + setPlayerMap(map.grid); + } + } + + const handleCellClick = (x: number, y: number) => { + if (isSimulating) return; + + const newMap = + playerMap.length > 0 ? [...playerMap] : map.grid.map((row) => [...row]); + + // Remove existing player if any + for (let i = 0; i < newMap.length; i++) { + for (let j = 0; j < newMap[i].length; j++) { + if (newMap[i][j] === "P") { + newMap[i][j] = " "; + } + } + } + + // Place new player + if (newMap[y][x] === " ") { + newMap[y][x] = "P"; + } + + setPlayerMap(newMap); + }; + + const runSimulation = () => { + if (!playerMap.some((row) => row.includes("P"))) { + alert( + "Please place a player (P) on the map before running the simulation.", + ); + return; + } + setIsSimulating(true); + setGameResult(null); + }; + + const handleSimulationEnd = (isWin: boolean) => { + setGameResult(isWin ? "WON" : "LOST"); + }; + + return ( +
+
+ +

Level {level}

+
{/* Spacer for alignment */} +
+
+

+ {isSimulating ? "Simulation Result" : "Place Your Player"} +

+ {isSimulating ? ( + <> + + {gameResult && ( +
+ {gameResult === "WON" ? "You Survived!" : "You Died!"} +
+ )} + + ) : ( +
+ 0 ? playerMap : map.grid} /> +
+ {(playerMap.length > 0 ? playerMap : map.grid).map((row, y) => + row.map((_, x) => ( +
handleCellClick(x, y)} + /> + )), + )} +
+
+ )} +
+
+ {!isSimulating && ( + + )} + {isSimulating && } +
+
+ ); +} diff --git a/app/play/page.tsx b/app/play/page.tsx new file mode 100644 index 0000000..6cdfa07 --- /dev/null +++ b/app/play/page.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { useQuery } from "convex/react"; +import { api } from "@/convex/_generated/api"; +import { Map } from "@/app/map"; +import { Button } from "@/components/ui/button"; +import Link from "next/link"; + +export default function PlayPage() { + const maps = useQuery(api.maps.getMaps); + + if (!maps) { + return
Loading...
; + } + + return ( +
+

Choose a Level

+
+ {maps.map((map) => ( +
+
+

Level {map.level}

+ +
+ + + +
+ ))} +
+
+ ); +}