Skip to content

Commit

Permalink
test map
Browse files Browse the repository at this point in the history
  • Loading branch information
webdevcody committed Oct 17, 2024
1 parent fd5b30f commit 7ee9936
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 83 deletions.
278 changes: 207 additions & 71 deletions app/play/[level]/page.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<string | null>(null);
const [aiReasoning, setAiReasoning] = useState<string | null>(null);
const [showOriginalMap, setShowOriginalMap] = useState(true);

if (!map) {
return <div>Loading...</div>;
Expand Down Expand Up @@ -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 (
<div className="container mx-auto min-h-screen flex flex-col items-center py-12 pb-24 gap-8">
<Button variant="outline" asChild className="flex gap-2 items-center">
<Link href="/play" passHref>
<ChevronLeftIcon /> Play Different Night
</Link>
</Button>
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-center">Night #{level}</h1>
</div>
<div className="mb-4 flex justify-center gap-4">
<Button
onClick={() => handlePlacementModeChange("player")}
disabled={playerMap.some((row) => row.includes("P"))}
variant={placementMode === "player" ? "default" : "outline"}
>
Place Player
</Button>
<Button
onClick={() => handlePlacementModeChange("block")}
disabled={blockCount >= 2}
variant={placementMode === "block" ? "default" : "outline"}
>
Place Block ({2 - blockCount} left)
<div className="w-full flex justify-between items-center">
<Button variant="outline" asChild className="flex gap-2 items-center">
<Link href="/play" passHref>
<ChevronLeftIcon /> Play Different Night
</Link>
</Button>
{flags?.showTestPage && (
<Tabs
value={mode}
onValueChange={(value) => setMode(value as "play" | "test")}
>
<TabsList>
<TabsTrigger value="play">Play</TabsTrigger>
<TabsTrigger value="test">Test AI</TabsTrigger>
</TabsList>
</Tabs>
)}
</div>
<div className="mb-8 flex flex-col items-center">
<h2 className="text-xl font-semibold mb-4">
{isSimulating ? "Simulation Result" : "Place Your Player"}
</h2>
{isSimulating ? (
<>
<Visualizer
map={playerMap}
autoStart={true}
onSimulationEnd={handleSimulationEnd}
/>
{gameResult && (
<div
className={`mt-4 text-2xl font-bold ${gameResult === "WON" ? "text-green-500" : "text-red-500"}`}
>
{gameResult === "WON" ? "You Survived!" : "You Died!"}
</div>
)}
</>
) : (
<div className="relative">
<Map map={playerMap.length > 0 ? playerMap : map.grid} />
<div
className="absolute inset-0 grid"
style={{
gridTemplateColumns: `repeat(${mapWidth}, minmax(0, 1fr))`,
gridTemplateRows: `repeat(${mapHeight}, minmax(0, 1fr))`,
}}
<h1 className="text-3xl font-bold text-center">Night #{level}</h1>

{mode === "play" ? (
<>
<div className="mb-4 flex justify-center gap-4">
<Button
onClick={() => handlePlacementModeChange("player")}
disabled={playerMap.some((row) => row.includes("P"))}
variant={placementMode === "player" ? "default" : "outline"}
>
Place Player
</Button>
<Button
onClick={() => handlePlacementModeChange("block")}
disabled={blockCount >= 2}
variant={placementMode === "block" ? "default" : "outline"}
>
{(playerMap.length > 0 ? playerMap : map.grid).map((row, y) =>
row.map((_, x) => (
Place Block ({2 - blockCount} left)
</Button>
</div>
<div className="mb-8 flex flex-col items-center">
{isSimulating ? (
<>
<Visualizer
map={playerMap}
autoStart={true}
onSimulationEnd={handleSimulationEnd}
/>
{gameResult && (
<div
key={`${x}-${y}`}
className="cursor-pointer"
onClick={() => handleCellClick(x, y)}
/>
)),
)}
className={`mt-4 text-2xl font-bold ${
gameResult === "WON" ? "text-green-500" : "text-red-500"
}`}
>
{gameResult === "WON" ? "You Survived!" : "You Died!"}
</div>
)}
</>
) : (
<div className="relative">
<Map map={playerMap.length > 0 ? playerMap : map.grid} />
<div
className="absolute inset-0 grid"
style={{
gridTemplateColumns: `repeat(${playerMap[0]?.length || map.grid[0].length}, minmax(0, 1fr))`,
gridTemplateRows: `repeat(${playerMap.length || map.grid.length}, minmax(0, 1fr))`,
}}
>
{(playerMap.length > 0 ? playerMap : map.grid).map((row, y) =>
row.map((cell, x) => (
<div
key={`${x}-${y}`}
className={`
${cell === " " ? "cursor-pointer hover:border-2 hover:border-dashed hover:border-slate-500" : ""}
border border-transparent
`}
onClick={() => cell === " " && handleCellClick(x, y)}
/>
)),
)}
</div>
</div>
)}
</div>
<div className="flex justify-center gap-4">
{!isSimulating ? (
<>
<Button onClick={runSimulation}>Run Simulation</Button>
<Button onClick={handleClearMap} variant="outline">
Clear Map
</Button>
</>
) : (
<Button onClick={handleRetryClicked}>Retry</Button>
)}
</div>
</>
) : (
<div className="flex flex-col items-center gap-4 w-full">
{showOriginalMap && (
<div className="mb-8">
<Map map={map.grid} />
</div>
)}
<div className="flex gap-4 mb-4">
<Select value={selectedModel} onValueChange={setSelectedModel}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select AI model" />
</SelectTrigger>
<SelectContent>
{AI_MODELS.map((model) => (
<SelectItem key={model.model} value={model.model}>
{model.name}
</SelectItem>
))}
</SelectContent>
</Select>
<Button onClick={handleAITest} disabled={isSimulating}>
{isSimulating ? (
<>
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" />
Testing...
</>
) : (
"Test AI Model"
)}
</Button>
{!showOriginalMap && (
<Button onClick={handleReset} variant="outline">
Reset
</Button>
)}
</div>
)}
</div>
<div className="flex justify-center">
{!isSimulating && (
<Button onClick={runSimulation}>Run Simulation</Button>
)}
{isSimulating && <Button onClick={handleRetryClicked}>Retry</Button>}
</div>
{aiError && <div className="text-red-500 mt-4">{aiError}</div>}
{gameResult && (
<div className="mt-4 flex flex-col items-center gap-8 max-w-4xl mx-auto">
<Visualizer map={playerMap} autoStart={true} />
<div className="flex flex-col gap-4 flex-1">
<div
className={`text-2xl font-bold ${
gameResult === "WON" ? "text-green-500" : "text-red-500"
}`}
>
AI {gameResult}
</div>
{aiReasoning && (
<div className="p-4">
<h3 className="font-semibold mb-2">AI Reasoning:</h3>
<p>{aiReasoning}</p>
</div>
)}
</div>
</div>
)}
</div>
)}
</div>
);
}
47 changes: 35 additions & 12 deletions convex/maps.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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(
Expand All @@ -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";
Expand Down Expand Up @@ -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,
};
},
});

0 comments on commit 7ee9936

Please sign in to comment.