diff --git a/app/play/[level]/page.tsx b/app/play/[level]/page.tsx index bb92566..4e6860c 100644 --- a/app/play/[level]/page.tsx +++ b/app/play/[level]/page.tsx @@ -1,13 +1,22 @@ "use client"; import { useState } from "react"; -import { useQuery } from "convex/react"; +import { useQuery, useAction } from "convex/react"; import { api } from "@/convex/_generated/api"; import { Button } from "@/components/ui/button"; import { Visualizer } from "../../visualizer"; import { Map } from "@/app/map"; import Link from "next/link"; -import { ChevronLeftIcon } from "@radix-ui/react-icons"; +import { ChevronLeftIcon, ReloadIcon } from "@radix-ui/react-icons"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { AI_MODELS } from "@/convex/constants"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; export default function PlayLevelPage({ params, @@ -23,6 +32,13 @@ export default function PlayLevelPage({ "player", ); const [blockCount, setBlockCount] = useState(0); + const [selectedModel, setSelectedModel] = useState(AI_MODELS[0].model); + const testAIModel = useAction(api.maps.testAIModel); + const flags = useQuery(api.flags.getFlags); + const [mode, setMode] = useState<"play" | "test">("play"); + const [aiError, setAiError] = useState(null); + const [aiReasoning, setAiReasoning] = useState(null); + const [showOriginalMap, setShowOriginalMap] = useState(true); if (!map) { return
Loading...
; @@ -87,84 +103,204 @@ export default function PlayLevelPage({ setGameResult(isWin ? "WON" : "LOST"); }; - const mapWidth = - playerMap.length > 0 ? playerMap[0].length : map.grid[0].length; - const mapHeight = playerMap.length > 0 ? playerMap.length : map.grid.length; + const handleAITest = async () => { + if (!map) return; + + setIsSimulating(true); + setGameResult(null); + setAiError(null); + setAiReasoning(null); + setShowOriginalMap(false); + + try { + const result = await testAIModel({ + level, + modelId: selectedModel, + }); + + setPlayerMap(result.map); + setGameResult(result.isWin ? "WON" : "LOST"); + setAiReasoning(result.reasoning); + } catch (error) { + console.error("Error testing AI model:", error); + setAiError( + error instanceof Error ? error.message : "An unexpected error occurred", + ); + } finally { + setIsSimulating(false); + } + }; + + const handleReset = () => { + setShowOriginalMap(true); + setPlayerMap([]); + setGameResult(null); + setAiError(null); + setAiReasoning(null); + }; + + const handleClearMap = () => { + setPlayerMap(map.grid.map((row) => [...row])); + setBlockCount(0); + setPlacementMode("player"); + }; return (
- -
-

Night #{level}

-
-
- - + {flags?.showTestPage && ( + setMode(value as "play" | "test")} + > + + Play + Test AI + + + )}
-
-

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

- {isSimulating ? ( - <> - - {gameResult && ( -
- {gameResult === "WON" ? "You Survived!" : "You Died!"} -
- )} - - ) : ( -
- 0 ? playerMap : map.grid} /> -
Night #{level} + + {mode === "play" ? ( + <> +
+ + +
+
+ {isSimulating ? ( + <> + + {gameResult && (
handleCellClick(x, y)} - /> - )), - )} + className={`mt-4 text-2xl font-bold ${ + gameResult === "WON" ? "text-green-500" : "text-red-500" + }`} + > + {gameResult === "WON" ? "You Survived!" : "You Died!"} +
+ )} + + ) : ( +
+ 0 ? playerMap : map.grid} /> +
+ {(playerMap.length > 0 ? playerMap : map.grid).map((row, y) => + row.map((cell, x) => ( +
cell === " " && handleCellClick(x, y)} + /> + )), + )} +
+
+ )} +
+
+ {!isSimulating ? ( + <> + + + + ) : ( + + )} +
+ + ) : ( +
+ {showOriginalMap && ( +
+
+ )} +
+ + + {!showOriginalMap && ( + + )}
- )} -
-
- {!isSimulating && ( - - )} - {isSimulating && } -
+ {aiError &&
{aiError}
} + {gameResult && ( +
+ +
+
+ AI {gameResult} +
+ {aiReasoning && ( +
+

AI Reasoning:

+

{aiReasoning}

+
+ )} +
+
+ )} +
+ )}
); } diff --git a/convex/maps.ts b/convex/maps.ts index e0907af..b4ff3fd 100644 --- a/convex/maps.ts +++ b/convex/maps.ts @@ -1,4 +1,9 @@ -import { internalAction, internalMutation, query } from "./_generated/server"; +import { + internalAction, + internalMutation, + query, + action, +} from "./_generated/server"; import { v } from "convex/values"; import { ZombieSurvival } from "../simulators/zombie-survival"; import { api, internal } from "./_generated/api"; @@ -73,9 +78,9 @@ export const getMapByLevel = query({ export const playMapAction = internalAction({ args: { - level: v.number(), gameId: v.id("games"), modelId: v.string(), + level: v.number(), }, handler: async (ctx, args) => { const resultId = await ctx.runMutation( @@ -95,7 +100,7 @@ export const playMapAction = internalAction({ } if (process.env.MOCK_MODELS === "true") { - const existingMap = [...map.grid.map((row) => [...row])]; + const existingMap = [...map.grid.map((row: string[]) => [...row])]; existingMap[0][0] = "P"; existingMap[0][1] = "B"; @@ -128,22 +133,40 @@ export const playMapAction = internalAction({ ? error : "Unexpected error happened"; - // await ctx.runMutation(internal.results.failResult, { - // resultId, - // error: errorMessage, - // }); await ctx.runMutation(internal.results.updateResult, { resultId, isWin: false, reasoning: errorMessage, error: errorMessage, }); + } + }, +}); - // await ctx.runMutation(internal.leaderboard.updateRankings, { - // modelId: args.modelId, - // level: args.level, - // isWin: false, - // }); +export const testAIModel = action({ + args: { + level: v.number(), + modelId: v.string(), + }, + handler: async (ctx, args) => { + const flags = await ctx.runQuery(api.flags.getFlags); + if (!flags.showTestPage) { + throw new Error("Test page is not enabled"); } + + const map = await ctx.runQuery(api.maps.getMapByLevel, { + level: args.level, + }); + + if (!map) { + throw new Error("Map not found"); + } + + const { solution, reasoning } = await runModel(args.modelId, map.grid); + return { + map: solution, + isWin: ZombieSurvival.isWin(solution), + reasoning, + }; }, });