From e295992ed984b36c6bd2873c3114c44dc4f7625c Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Fri, 18 Oct 2024 18:58:50 +0300 Subject: [PATCH 01/12] A function to clone map --- components/MapBuilder.tsx | 2 +- convex/maps.ts | 2 +- simulators/zombie-survival/ZombieSurvival.ts | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/components/MapBuilder.tsx b/components/MapBuilder.tsx index b07826d..c869ebd 100644 --- a/components/MapBuilder.tsx +++ b/components/MapBuilder.tsx @@ -42,7 +42,7 @@ export function MapBuilder({ newValue = initialValue === " " ? "Z" : initialValue === "Z" ? "R" : " "; } - const newMap = [...map.map((row) => [...row])]; + const newMap = ZombieSurvival.cloneMap(map); newMap[row][cell] = newValue; onChange(newMap); } diff --git a/convex/maps.ts b/convex/maps.ts index 199d982..6e80fa7 100644 --- a/convex/maps.ts +++ b/convex/maps.ts @@ -284,7 +284,7 @@ export const playMapAction = internalAction({ } if (process.env.MOCK_MODELS === "true") { - const existingMap = [...map.grid.map((row: string[]) => [...row])]; + const existingMap = ZombieSurvival.cloneMap(map.grid); existingMap[0][0] = "P"; existingMap[0][1] = "B"; diff --git a/simulators/zombie-survival/ZombieSurvival.ts b/simulators/zombie-survival/ZombieSurvival.ts index 20d03b3..b99615c 100644 --- a/simulators/zombie-survival/ZombieSurvival.ts +++ b/simulators/zombie-survival/ZombieSurvival.ts @@ -13,6 +13,10 @@ export class ZombieSurvival { private player: Player; private zombies: Zombie[]; + public static cloneMap(map: string[][]): string[][] { + return [...map.map((row) => [...row])]; + } + public static fromSnapshot(snapshot: string): ZombieSurvival { const config = snapshot.split(".").map((it) => it.split("")); return new ZombieSurvival(config); From 79b302422d498e8f3006f5426f839e162adcb68e Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Fri, 18 Oct 2024 18:59:59 +0300 Subject: [PATCH 02/12] Use ZombieSurvival.cloneMap in models/index.ts --- models/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/models/index.ts b/models/index.ts index d71881c..4aec09b 100644 --- a/models/index.ts +++ b/models/index.ts @@ -3,6 +3,7 @@ import { gpt4o } from "./gpt-4o"; import { claude35sonnet } from "./claude-3-5-sonnet"; import { perplexityModel } from "./perplexity-llama"; import { errorMessage } from "../lib/utils"; +import { ZombieSurvival } from "@/simulators/zombie-survival"; export type ModelHandler = ( prompt: string, @@ -110,7 +111,8 @@ export async function runModel( throw new Error(`Tried running unknown model '${modelId}'`); } } - const originalMap = JSON.parse(JSON.stringify(map)); + + const originalMap = ZombieSurvival.cloneMap(map); const [playerRow, playerCol] = result.playerCoordinates; if (originalMap[playerRow][playerCol] !== " ") { From cb87ddfd71120ee5487ee376a55671691e84b424 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Fri, 18 Oct 2024 20:09:21 +0300 Subject: [PATCH 03/12] Fix convex compilation --- models/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/index.ts b/models/index.ts index 4aec09b..24cec33 100644 --- a/models/index.ts +++ b/models/index.ts @@ -1,9 +1,9 @@ +import { ZombieSurvival } from "../simulators/zombie-survival"; import { gemini15pro } from "./gemini-1.5-pro"; import { gpt4o } from "./gpt-4o"; import { claude35sonnet } from "./claude-3-5-sonnet"; import { perplexityModel } from "./perplexity-llama"; import { errorMessage } from "../lib/utils"; -import { ZombieSurvival } from "@/simulators/zombie-survival"; export type ModelHandler = ( prompt: string, From 583dca84acc61ed8490eb2549f5947e908f8f672 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Fri, 18 Oct 2024 20:10:09 +0300 Subject: [PATCH 04/12] Allow users to edit map --- app/playground/page.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/playground/page.tsx b/app/playground/page.tsx index 4e709ea..aaa78b7 100644 --- a/app/playground/page.tsx +++ b/app/playground/page.tsx @@ -199,18 +199,18 @@ export default function PlaygroundPage() { {simulating ? "Simulating..." : "Play With AI"} )} - {(solution !== null || userPlaying) && ( - - )} )} + {(solution !== null || userPlaying) && ( + + )} {solution === null && !simulating && !userPlaying && (

* Click on a cell to place entity

From b1e08b35b6f344c38e570367f49a32d2077605d8 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Fri, 18 Oct 2024 21:10:00 +0300 Subject: [PATCH 09/12] Remove unnecessary console logs --- components/ThemeToggle.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/ThemeToggle.tsx b/components/ThemeToggle.tsx index ea3b0f6..51db0c6 100644 --- a/components/ThemeToggle.tsx +++ b/components/ThemeToggle.tsx @@ -17,8 +17,6 @@ export function ThemeToggle() { // This check is needed because if the user clicks on a button twice the button gets unselected and the newTheme is undefined if (newTheme) { setTheme(newTheme); - } else { - console.log("No theme selected, keeping current theme:", theme); } }} className="flex px-1 py-1 rounded-md bg-blue-200 dark:bg-slate-700" From b2d1db23b445cdecfad75b2e2b19e4e9f9d2e344 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Fri, 18 Oct 2024 21:27:37 +0300 Subject: [PATCH 10/12] Improve Watch page layout --- app/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 6bc535e..9d0a7ef 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -37,10 +37,10 @@ export default function GamePage() { } return ( -
+

Recent Games

-
+
{results.map((result) => ( ))} From e155ec3ad58589c8e71a98f4639654a7fd98193b Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Fri, 18 Oct 2024 21:27:54 +0300 Subject: [PATCH 11/12] Improve Visualizer performance --- components/Visualizer.tsx | 146 +++++++++++++++++++------------------- lib/utils.ts | 14 ++++ 2 files changed, 88 insertions(+), 72 deletions(-) diff --git a/components/Visualizer.tsx b/components/Visualizer.tsx index d28e955..ae1360a 100644 --- a/components/Visualizer.tsx +++ b/components/Visualizer.tsx @@ -1,6 +1,6 @@ +import React from "react"; import { Button } from "@/components/ui/button"; import { EntityType, ZombieSurvival } from "@/simulators/zombie-survival"; -import { useEffect, useRef, useState } from "react"; import { getCellImage } from "@/components/Map"; const AUTO_REPLAY_SPEED = 1_500; @@ -19,57 +19,60 @@ export function Visualizer({ controls?: boolean; cellSize?: string; map: string[][]; - onSimulationEnd?: (isWin: boolean) => Promise; + onSimulationEnd?: (isWin: boolean) => unknown; }) { - const simulator = useRef(null); - const interval = useRef(null); - const timeout = useRef(null); - const [isRunning, setIsRunning] = useState(false); - const [mapState, setMapState] = useState(map); - const [needsReset, setNeedsReset] = useState(false); + const simulator = React.useRef(new ZombieSurvival(map)); + const interval = React.useRef | null>(null); + const timeout = React.useRef | null>(null); + const ref = React.useRef(null); + const [running, setRunning] = React.useState(false); + const [startedAt, setStartedAt] = React.useState(Date.now()); + const [, setRenderedAt] = React.useState(Date.now()); - const startSimulation = () => { - setNeedsReset(true); - const clonedMap = JSON.parse(JSON.stringify(map)); - simulator.current = new ZombieSurvival(clonedMap); - setMapState(simulator.current!.getState()); - setIsRunning(true); + function stepSimulation() { + if (simulator.current === null) { + return; + } - interval.current = setInterval(async () => { - if (simulator.current!.finished()) { - clearInterval(interval.current!); - interval.current = null; + if (!simulator.current.finished()) { + simulator.current.step(); + setRenderedAt(Date.now()); + return; + } - if (autoReplay) { - timeout.current = setTimeout(() => { - timeout.current = null; - startSimulation(); - }, AUTO_REPLAY_SPEED); + clearInterval(interval.current!); + interval.current = null; - return; - } + if (autoReplay) { + timeout.current = setTimeout(() => { + timeout.current = null; + startSimulation(); + }, AUTO_REPLAY_SPEED); - setIsRunning(false); + return; + } - if (onSimulationEnd) { - await onSimulationEnd(!simulator.current!.getPlayer().dead()); - } + setRunning(false); - return; - } + if (onSimulationEnd) { + onSimulationEnd(!simulator.current.getPlayer().dead()); + } + } - simulator.current!.step(); - setMapState(simulator.current!.getState()); - }, REPLAY_SPEED); - }; + function startSimulation() { + simulator.current = new ZombieSurvival(map); + setStartedAt(Date.now()); + setRunning(true); + interval.current = setInterval(stepSimulation, REPLAY_SPEED); + } - useEffect(() => { + React.useEffect(() => { if (autoStart) { startSimulation(); } }, [autoStart]); - useEffect(() => { + React.useEffect(() => { return () => { if (interval.current) { clearInterval(interval.current); @@ -80,43 +83,44 @@ export function Visualizer({ }; }, []); + const entities = simulator.current.getAllEntities() ?? []; + const cellSizeNum = Number.parseInt(cellSize, 10); + return ( <> -
+
Background Map -
- {mapState.map((row, y) => ( -
- {row.map((cell, x) => ( -
{ - const entity = simulator.current?.getEntityAt({ - x, - y, - }); - if ( - entity?.getType() === EntityType.Zombie && - entity.getHealth() === 1 - ) { - return 0.5; - } - return 1; - })(), - }} - className={`border flex items-center justify-center`} - > - {getCellImage(cell)} -
- ))} +
+ {entities.map((entity, idx) => ( +
+ {getCellImage(entity.toConfig())}
))}
@@ -124,16 +128,14 @@ export function Visualizer({
{controls && (
-