@@ -158,7 +187,7 @@ export function MapBuilder({
className={buttonClassName}
disabled={disabled === true}
key={`${rowIdx}.${cellIdx}`}
- onClick={() => handleClickCell(rowIdx, cellIdx, cell)}
+ onClick={() => handleClickCell(rowIdx, cellIdx)}
type="button"
>
{cell}
diff --git a/components/MapStatus.tsx b/components/MapStatus.tsx
new file mode 100644
index 0000000..2ca1391
--- /dev/null
+++ b/components/MapStatus.tsx
@@ -0,0 +1,11 @@
+import { ZombieSurvival } from "@/simulators/zombie-survival";
+
+export function MapStatus({ map }: { map: string[][] }) {
+ const isWin = ZombieSurvival.isWin(map);
+
+ return (
+
+ {isWin ? "WON" : "LOST"}
+
+ );
+}
diff --git a/components/ResultStatus.tsx b/components/ResultStatus.tsx
deleted file mode 100644
index 3053d85..0000000
--- a/components/ResultStatus.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Doc } from "@/convex/_generated/dataModel";
-
-export function ResultStatus({ result }: { result: Doc<"results"> }) {
- return (
-
- {result.isWin ? "WON" : "LOST"}
-
- );
-}
diff --git a/components/ThemeToggle.tsx b/components/ThemeToggle.tsx
index dee8ecd..ea3b0f6 100644
--- a/components/ThemeToggle.tsx
+++ b/components/ThemeToggle.tsx
@@ -13,8 +13,15 @@ export function ThemeToggle() {
type="single"
size="sm"
value={theme}
- onValueChange={(e) => setTheme(e)}
- className={`${"flex px-1 py-1 rounded-md"} ${theme === "light" || (theme === "system" && darkMode === "light") ? "bg-blue-200" : "bg-slate-700"}`}
+ onValueChange={(newTheme) => {
+ // 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"
>
{theme === "light" || (theme === "system" && darkMode === "light") ? (
diff --git a/convex/maps.ts b/convex/maps.ts
index e1c250c..fdccccc 100644
--- a/convex/maps.ts
+++ b/convex/maps.ts
@@ -148,6 +148,39 @@ export const addMap = mutation({
},
});
+export const publishMap = mutation({
+ args: {
+ map: v.array(v.array(v.string())),
+ },
+ handler: async (ctx, args) => {
+ const userId = await getAuthUserId(ctx);
+
+ if (!userId) {
+ throw new Error("User not authenticated");
+ }
+
+ const isAdmin = await ctx.runQuery(api.users.isAdmin);
+
+ if (!isAdmin) {
+ throw new Error("Publishing maps is available only for admins");
+ }
+
+ const maps = await ctx.db
+ .query("maps")
+ .filter((q) => q.neq("level", undefined))
+ .collect();
+
+ const lastLevel = maps.sort((a, b) => b.level! - a.level!)[120].level!;
+
+ await ctx.db.insert("maps", {
+ grid: args.map,
+ level: lastLevel + 1,
+ submittedBy: userId,
+ isReviewed: true,
+ });
+ },
+});
+
export const seedMaps = internalMutation({
handler: async (ctx) => {
const maps = await ctx.db.query("maps").collect();
@@ -171,22 +204,6 @@ export const seedMaps = internalMutation({
},
});
-export const testMap = action({
- args: {
- modelId: v.string(),
- map: v.array(v.array(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");
- }
-
- return await runModel(args.modelId, args.map);
- },
-});
-
export const getMaps = query({
args: {
isReviewed: v.optional(v.boolean()),
@@ -321,6 +338,22 @@ export const playMapAction = internalAction({
},
});
+export const testMap = action({
+ args: {
+ modelId: v.string(),
+ map: v.array(v.array(v.string())),
+ },
+ handler: async (ctx, args) => {
+ const isAdmin = await ctx.runQuery(api.users.isAdmin);
+
+ if (!isAdmin) {
+ throw new Error("Test map is available only for admin");
+ }
+
+ return await runModel(args.modelId, args.map);
+ },
+});
+
export const testAIModel = action({
args: {
level: v.number(),
diff --git a/lib/utils.ts b/lib/utils.ts
index 365058c..5a7b05a 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -4,3 +4,11 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+
+export function errorMessage(error: unknown) {
+ return error instanceof Error
+ ? error.message
+ : typeof error === "string"
+ ? error
+ : "An unexpected error occurred";
+}
diff --git a/models/index.ts b/models/index.ts
index 50853fa..d71881c 100644
--- a/models/index.ts
+++ b/models/index.ts
@@ -2,6 +2,7 @@ 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";
export type ModelHandler = (
prompt: string,
@@ -141,12 +142,7 @@ export async function runModel(
} catch (error) {
return {
reasoning: "Internal error",
- error:
- error instanceof Error
- ? error.message
- : typeof error === "string"
- ? error
- : "Unknown error",
+ error: errorMessage(error),
};
}
}
diff --git a/simulators/zombie-survival/ZombieSurvival.ts b/simulators/zombie-survival/ZombieSurvival.ts
index df4a1d2..20d03b3 100644
--- a/simulators/zombie-survival/ZombieSurvival.ts
+++ b/simulators/zombie-survival/ZombieSurvival.ts
@@ -19,6 +19,10 @@ export class ZombieSurvival {
}
public static isWin(config: string[][]): boolean {
+ if (ZombieSurvival.mapIsEmpty(config)) {
+ return false;
+ }
+
const game = new ZombieSurvival(config);
while (!game.finished()) {
@@ -28,12 +32,26 @@ export class ZombieSurvival {
return !game.getPlayer().dead();
}
+ public static mapHasMultiplePlayers(map: string[][]): boolean {
+ return (
+ map.map((row) => row.filter((cell) => cell === "P")).flat().length > 1
+ );
+ }
+
+ public static mapHasPlayer(map: string[][]): boolean {
+ return map.some((row) => row.includes("P"));
+ }
+
public static mapHasZombies(map: string[][]): boolean {
return map.some((row) => row.includes("Z"));
}
+ public static mapIsEmpty(map: string[][]): boolean {
+ return map.length === 0 || map[0].length === 0;
+ }
+
public constructor(config: string[][]) {
- if (config.length === 0 || config[0].length === 0) {
+ if (ZombieSurvival.mapIsEmpty(config)) {
throw new Error("Config is empty");
}