From de999481a6b8afe41c626539ee083baaf74623fa Mon Sep 17 00:00:00 2001 From: Ashutoshbind15 Date: Wed, 16 Oct 2024 13:22:47 +0530 Subject: [PATCH 01/10] add gemini model for the game simulation --- convex/_generated/api.d.ts | 2 + convex/constants.ts | 4 ++ convex/games.ts | 18 +++-- convex/gemini.ts | 140 +++++++++++++++++++++++++++++++++++++ convex/results.ts | 18 +++-- package.json | 1 + 6 files changed, 173 insertions(+), 10 deletions(-) create mode 100644 convex/gemini.ts diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 4f841d4..4169466 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -18,6 +18,7 @@ import type { import type * as auth from "../auth.js"; import type * as constants from "../constants.js"; import type * as games from "../games.js"; +import type * as gemini from "../gemini.js"; import type * as http from "../http.js"; import type * as init from "../init.js"; import type * as maps from "../maps.js"; @@ -38,6 +39,7 @@ declare const fullApi: ApiFromModules<{ auth: typeof auth; constants: typeof constants; games: typeof games; + gemini: typeof gemini; http: typeof http; init: typeof init; maps: typeof maps; diff --git a/convex/constants.ts b/convex/constants.ts index b03b955..f3009e7 100644 --- a/convex/constants.ts +++ b/convex/constants.ts @@ -3,6 +3,10 @@ export const AI_MODELS = [ model: "gpt-4o", name: "OpenAI - 4o Mini", }, + { + model: "gemini-1.5-pro", + name: "Gemini - 1.5 Pro", + }, ]; export const AI_MODEL_IDS = AI_MODELS.map((model) => model.model); diff --git a/convex/games.ts b/convex/games.ts index 34db7ed..73b31e9 100644 --- a/convex/games.ts +++ b/convex/games.ts @@ -25,11 +25,19 @@ export const startNewGame = mutation({ throw new Error("No map found for level 1"); } - await ctx.scheduler.runAfter(0, internal.openai.playMapAction, { - gameId, - modelId: args.modelId, - level: 1, - }); + if (args.modelId === "gpt-4o") { + await ctx.scheduler.runAfter(0, internal.openai.playMapAction, { + gameId, + modelId: args.modelId, + level: 1, + }); + } else if (args.modelId === "gemini-1.5-pro") { + await ctx.scheduler.runAfter(0, internal.gemini.playMapAction, { + gameId, + modelId: args.modelId, + level: 1, + }); + } return gameId; }, diff --git a/convex/gemini.ts b/convex/gemini.ts new file mode 100644 index 0000000..a83df75 --- /dev/null +++ b/convex/gemini.ts @@ -0,0 +1,140 @@ +import { GoogleGenerativeAI, SchemaType } from "@google/generative-ai"; +import { action, internalAction } from "./_generated/server"; +import { v } from "convex/values"; +import { api, internal } from "./_generated/api"; +import { Doc } from "./_generated/dataModel"; +import { ZombieSurvival } from "../simulators/zombie-survival"; + +const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY as string); + +const schema = { + description: "Game Round Results", + type: SchemaType.OBJECT, + properties: { + map: { + type: SchemaType.ARRAY, + items: { + type: SchemaType.ARRAY, + items: { + type: SchemaType.STRING, + }, + }, + description: "The resulting map after the placements", + }, + reasoning: { + type: SchemaType.STRING, + description: "The reasoning behind the move", + }, + playerCoordinates: { + type: SchemaType.ARRAY, + items: { + type: SchemaType.NUMBER, + }, + description: "The player's coordinates", + }, + boxCoordinates: { + type: SchemaType.ARRAY, + items: { + type: SchemaType.ARRAY, + items: { + type: SchemaType.NUMBER, + }, + }, + description: "The box coordinates", + }, + }, + required: ["map", "reasoning", "playerCoordinates", "boxCoordinates"], +}; + +const model = genAI.getGenerativeModel({ + model: "gemini-1.5-pro", + generationConfig: { + responseMimeType: "application/json", + responseSchema: schema, + }, +}); + +type playMapActionResponse = { + map: string[][]; + reasoning: string; + playerCoordinates: number[]; + boxCoordinates: number[][]; +}; + +export const playMapAction = internalAction({ + args: { + level: v.number(), + gameId: v.id("games"), + modelId: v.string(), + }, + handler: async (ctx, args) => { + const resultId = await ctx.runMutation( + internal.results.createInitialResult, + { + gameId: args.gameId, + level: args.level, + }, + ); + + const map: Doc<"maps"> | null = (await ctx.runQuery( + api.maps.getMapByLevel, + { + level: args.level, + }, + )) as any; + + if (!map) { + throw new Error("Map not found"); + } + + if (process.env.MOCK_GEMINI === "true") { + const existingMap = [...map.grid.map((row) => [...row])]; + existingMap[0][0] = "P"; + existingMap[0][1] = "B"; + existingMap[0][2] = "B"; + return { + map: existingMap, + reasoning: "This is a mock response", + playerCoordinates: [0, 0], + boxCoordinates: [], + }; + } + + const result = await model.generateContent( + `You're given a 2d grid of nums such that. + " " represents an empty space. + "Z" represents a zombie. Zombies move one Manhattan step every turn and aim to reach the player. + "R" represents rocks, which players can shoot over but zombies cannot pass through or break. + "P" represents the player, who cannot move. The player's goal is to shoot and kill zombies before they reach them. + "B" represents blocks that can be placed before the round begins to hinder the zombies. You can place up to two blocks on the map. + + Your goal is to place the player ("P") and two blocks ("B") in locations that maximize the player's survival by delaying the zombies' approach. + You can shoot any zombie regardless of where it is on the grid. + Returning a 2d grid with the player and blocks placed in the optimal locations, with the coordinates player ("P") and the blocks ("B"), also provide reasoning for the choices. + + You can't replace rocks R or zombies Z with blocks. If there is no room to place a block, do not place any. + + Grid: ${JSON.stringify(map)}`, + ); + + // todo: check if the response is valid acc to types and the player and box coordinates are valid, + // as sometimes the model returns a state that's erroring out in the simulator + + const parsedResponse = JSON.parse( + result.response.text(), + ) as playMapActionResponse; + + const game = new ZombieSurvival(parsedResponse.map); + while (!game.finished()) { + game.step(); + } + const isWin = !game.getPlayer().dead(); + + await ctx.runMutation(internal.results.updateResult, { + resultId, + isWin, + reasoning: parsedResponse.reasoning, + map: parsedResponse.map, + }); + }, +}); diff --git a/convex/results.ts b/convex/results.ts index 912b045..489d114 100644 --- a/convex/results.ts +++ b/convex/results.ts @@ -77,11 +77,19 @@ export const updateResult = internalMutation({ throw new Error("Next map not found"); } - await ctx.scheduler.runAfter(0, internal.openai.playMapAction, { - modelId: game.modelId, - level: result.level + 1, - gameId: result.gameId, - }); + if (game.modelId === "gpt-4o") { + await ctx.scheduler.runAfter(0, internal.openai.playMapAction, { + gameId: result.gameId, + modelId: game.modelId, + level: result.level + 1, + }); + } else if (game.modelId === "gemini-1.5-pro") { + await ctx.scheduler.runAfter(0, internal.gemini.playMapAction, { + gameId: result.gameId, + modelId: game.modelId, + level: result.level + 1, + }); + } } }, }); diff --git a/package.json b/package.json index 8af9657..cd82859 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "@auth/core": "^0.34.2", "@convex-dev/auth": "^0.0.71", + "@google/generative-ai": "^0.21.0", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-select": "^2.1.2", From 7da9a7547aed9a6a95af9fdc7efcd2a70611f546 Mon Sep 17 00:00:00 2001 From: Saga-sanga Date: Wed, 16 Oct 2024 14:12:39 +0530 Subject: [PATCH 02/10] feat: add theme toggle to header and set white background for light mode --- app/header.tsx | 20 +++++++++++++------- app/layout.tsx | 2 +- convex/openai.ts | 4 ++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/app/header.tsx b/app/header.tsx index 2be764a..5267dd1 100644 --- a/app/header.tsx +++ b/app/header.tsx @@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"; import { useAuthActions } from "@convex-dev/auth/react"; import { GitHubLogoIcon } from "@radix-ui/react-icons"; import { useConvexAuth } from "convex/react"; +import { ThemeToggle } from "@/components/ThemeToggle"; function SignInWithGitHub() { const { signIn } = useAuthActions(); @@ -25,14 +26,16 @@ export default function Header() { const { isAuthenticated } = useConvexAuth(); return ( -
+
Logo SurviveTheNight
- Synced using Convex + + Synced using Convex +
- {!isAuthenticated ? ( - - ) : ( - - )} +
+ + {!isAuthenticated ? ( + + ) : ( + + )} +
); } diff --git a/app/layout.tsx b/app/layout.tsx index f221e13..06ff44d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -29,7 +29,7 @@ export default function RootLayout({
- {children} +
{children}
diff --git a/convex/openai.ts b/convex/openai.ts index 066a8d8..1814db7 100644 --- a/convex/openai.ts +++ b/convex/openai.ts @@ -68,11 +68,11 @@ export const playMapAction = internalAction({ "R" represents rocks, which players can shoot over but zombies cannot pass through or break. "P" represents the player, who cannot move. The player's goal is to shoot and kill zombies before they reach them. "B" represents blocks that can be placed before the round begins to hinder the zombies. You can place up to two blocks on the map. - + Your goal is to place the player ("P") and two blocks ("B") in locations that maximize the player's survival by delaying the zombies' approach. You can shoot any zombie regardless of where it is on the grid. Returning a 2d grid with the player and blocks placed in the optimal locations, with the coordinates player ("P") and the blocks ("B"), also provide reasoning for the choices. - + You can't replace rocks R or zombies Z with blocks. If there is no room to place a block, do not place any.`, }, { From d71e0305d0b7adbd09a4f60d364ba0ec6415672f Mon Sep 17 00:00:00 2001 From: Saga-sanga Date: Wed, 16 Oct 2024 14:16:24 +0530 Subject: [PATCH 03/10] chore: invert github sign in button color in dark mode --- app/header.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/header.tsx b/app/header.tsx index 5267dd1..787bd43 100644 --- a/app/header.tsx +++ b/app/header.tsx @@ -15,6 +15,7 @@ function SignInWithGitHub() { variant="outline" type="button" onClick={() => void signIn("github", { redirectTo: "/" })} + className="dark:invert" > GitHub From 8bfa0fee05a7f2abdf137fd5f27b96aef9b197e8 Mon Sep 17 00:00:00 2001 From: Jaspal Singh Date: Wed, 16 Oct 2024 15:23:00 +0530 Subject: [PATCH 04/10] fix for the light mode --- app/globals.css | 69 +++++++++++++++++++++++++++----------- app/header.tsx | 20 +++++++---- components/ThemeToggle.tsx | 17 ++++++---- tailwind.config.ts | 51 ++++++++++++++++++++++------ 4 files changed, 114 insertions(+), 43 deletions(-) diff --git a/app/globals.css b/app/globals.css index 4679ba1..c5a40ec 100644 --- a/app/globals.css +++ b/app/globals.css @@ -4,6 +4,7 @@ /* To change the theme colors, change the values below or use the "Copy code" button at https://ui.shadcn.com/themes */ + @layer base { :root { --background: 0 0% 100%; @@ -32,27 +33,54 @@ --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; } - + .light { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 222.2 84% 4.9%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + --radius: 0.5rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + } + .dark { - --background: 20 14.3% 4.1%; - --foreground: 60 9.1% 97.8%; - --card: 20 14.3% 4.1%; - --card-foreground: 60 9.1% 97.8%; - --popover: 20 14.3% 4.1%; - --popover-foreground: 60 9.1% 97.8%; - --primary: 60 9.1% 97.8%; - --primary-foreground: 24 9.8% 10%; - --secondary: 12 6.5% 15.1%; - --secondary-foreground: 60 9.1% 97.8%; - --muted: 12 6.5% 15.1%; - --muted-foreground: 24 5.4% 63.9%; - --accent: 12 6.5% 15.1%; - --accent-foreground: 60 9.1% 97.8%; + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; - --destructive-foreground: 60 9.1% 97.8%; - --border: 12 6.5% 15.1%; - --input: 12 6.5% 15.1%; - --ring: 24 5.7% 82.9%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9; --chart-1: 220 70% 50%; --chart-2: 160 60% 45%; --chart-3: 30 80% 55%; @@ -68,4 +96,7 @@ body { @apply bg-gradient-to-b from-slate-950 to-slate-900 text-foreground; } + .light body { + @apply bg-gradient-to-b from-blue-300 to-blue-100; + } } diff --git a/app/header.tsx b/app/header.tsx index 2be764a..523c4b9 100644 --- a/app/header.tsx +++ b/app/header.tsx @@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"; import { useAuthActions } from "@convex-dev/auth/react"; import { GitHubLogoIcon } from "@radix-ui/react-icons"; import { useConvexAuth } from "convex/react"; +import { ThemeToggle } from "@/components/ThemeToggle"; function SignInWithGitHub() { const { signIn } = useAuthActions(); @@ -25,14 +26,14 @@ export default function Header() { const { isAuthenticated } = useConvexAuth(); return ( -
+
Logo SurviveTheNight
- Synced using Convex + Synced using Convex
- {!isAuthenticated ? ( - - ) : ( - - )} +
+
+ +
+ {!isAuthenticated ? ( + + ) : ( + + )} +
); } diff --git a/components/ThemeToggle.tsx b/components/ThemeToggle.tsx index 87468ef..3c4e681 100644 --- a/components/ThemeToggle.tsx +++ b/components/ThemeToggle.tsx @@ -7,13 +7,16 @@ import { useTheme } from "next-themes"; export function ThemeToggle() { const { theme, setTheme } = useTheme(); return ( - - - - - - - + + {theme == "light" ? ( + + + + ) : ( + + + + )} diff --git a/tailwind.config.ts b/tailwind.config.ts index 25a0c3d..0248b43 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,14 +1,14 @@ import type { Config } from "tailwindcss"; -const config = { - darkMode: "selector", +const config: Config = { + darkMode: "class", // Use 'class' strategy for toggling dark mode content: [ "./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}", ], - safelist: ["dark"], + safelist: ["dark", "light"], // Safelist the 'light' class prefix: "", theme: { container: { @@ -53,6 +53,42 @@ const config = { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))", }, + // Light mode colors + light: { + border: "hsl(var(--light-border))", + input: "hsl(var(--light-input))", + ring: "hsl(var(--light-ring))", + background: "hsl(var(--light-background))", + foreground: "hsl(var(--light-foreground))", + primary: { + DEFAULT: "hsl(var(--light-primary))", + foreground: "hsl(var(--light-primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--light-secondary))", + foreground: "hsl(var(--light-secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--light-destructive))", + foreground: "hsl(var(--light-destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--light-muted))", + foreground: "hsl(var(--light-muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--light-accent))", + foreground: "hsl(var(--light-accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--light-popover))", + foreground: "hsl(var(--light-popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--light-card))", + foreground: "hsl(var(--light-card-foreground))", + }, + }, }, borderRadius: { lg: "var(--radius)", @@ -69,13 +105,8 @@ const config = { to: { height: "0" }, }, }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, }, }, - plugins: [require("tailwindcss-animate")], -} satisfies Config; +}; -export default config; +export default config; \ No newline at end of file From 7706445324490dd863ee5fd3c69339ef06e19834 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Wed, 16 Oct 2024 13:30:36 +0300 Subject: [PATCH 05/10] Fix simulation with OpenAI mocked --- convex/openai.ts | 31 ++++++++++---------- simulators/zombie-survival/ZombieSurvival.ts | 10 +++++++ 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/convex/openai.ts b/convex/openai.ts index 066a8d8..ec28abc 100644 --- a/convex/openai.ts +++ b/convex/openai.ts @@ -1,11 +1,11 @@ import OpenAI from "openai"; -import { internalAction } from "./_generated/server"; import { v } from "convex/values"; import { z } from "zod"; import { zodResponseFormat } from "openai/helpers/zod"; -import { api, internal } from "./_generated/api"; import { Doc } from "./_generated/dataModel"; import { ZombieSurvival } from "../simulators/zombie-survival"; +import { api, internal } from "./_generated/api"; +import { internalAction } from "./_generated/server"; const ResponseSchema = z.object({ map: z.array(z.array(z.string())), @@ -42,15 +42,19 @@ export const playMapAction = internalAction({ if (process.env.MOCK_OPEN_AI === "true") { const existingMap = [...map.grid.map((row) => [...row])]; + existingMap[0][0] = "P"; existingMap[0][1] = "B"; existingMap[0][2] = "B"; - return { - map: existingMap, + + await ctx.runMutation(internal.results.updateResult, { + resultId, + isWin: ZombieSurvival.isWin(existingMap), reasoning: "This is a mock response", - playerCoordinates: [0, 0], - boxCoordinates: [], - }; + map: existingMap, + }); + + return; } // moving here for now so that I can get this deployed without a real key @@ -68,11 +72,11 @@ export const playMapAction = internalAction({ "R" represents rocks, which players can shoot over but zombies cannot pass through or break. "P" represents the player, who cannot move. The player's goal is to shoot and kill zombies before they reach them. "B" represents blocks that can be placed before the round begins to hinder the zombies. You can place up to two blocks on the map. - + Your goal is to place the player ("P") and two blocks ("B") in locations that maximize the player's survival by delaying the zombies' approach. You can shoot any zombie regardless of where it is on the grid. Returning a 2d grid with the player and blocks placed in the optimal locations, with the coordinates player ("P") and the blocks ("B"), also provide reasoning for the choices. - + You can't replace rocks R or zombies Z with blocks. If there is no room to place a block, do not place any.`, }, { @@ -84,16 +88,11 @@ export const playMapAction = internalAction({ }); const response = completion.choices[0].message; - if (response.parsed) { - const game = new ZombieSurvival(response.parsed.map); - while (!game.finished()) { - game.step(); - } - const isWin = !game.getPlayer().dead(); + if (response.parsed) { await ctx.runMutation(internal.results.updateResult, { resultId, - isWin, + isWin: ZombieSurvival.isWin(response.parsed.map), reasoning: response.parsed.reasoning, map: response.parsed.map, }); diff --git a/simulators/zombie-survival/ZombieSurvival.ts b/simulators/zombie-survival/ZombieSurvival.ts index 5c4a2a1..3b20433 100644 --- a/simulators/zombie-survival/ZombieSurvival.ts +++ b/simulators/zombie-survival/ZombieSurvival.ts @@ -17,6 +17,16 @@ export class ZombieSurvival { return new ZombieSurvival(config); } + public static isWin(config: string[][]): boolean { + const game = new ZombieSurvival(config); + + while (!game.finished()) { + game.step(); + } + + return !game.getPlayer().dead(); + } + public constructor(config: string[][]) { if (config.length === 0 || config[0].length == 0) { throw new Error("Config is empty"); From b2255e212fbb2d500b32417f898a22cf6320f56e Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Wed, 16 Oct 2024 13:53:49 +0300 Subject: [PATCH 06/10] Fix dead zombies killing player --- simulators/zombie-survival/ZombieSurvival.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/simulators/zombie-survival/ZombieSurvival.ts b/simulators/zombie-survival/ZombieSurvival.ts index 5c4a2a1..e76edba 100644 --- a/simulators/zombie-survival/ZombieSurvival.ts +++ b/simulators/zombie-survival/ZombieSurvival.ts @@ -134,6 +134,10 @@ export class ZombieSurvival { break; } + if (zombie.dead()) { + continue; + } + zombie.walk(); } } From a00b6366b0e1f21d9a0d1dc93a408a80af8a56bc Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Wed, 16 Oct 2024 14:11:08 +0300 Subject: [PATCH 07/10] Rollback and cover with tests --- .../zombie-survival/ZombieSurvival.spec.ts | 17 +++++++++++++++++ simulators/zombie-survival/ZombieSurvival.ts | 4 ---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/simulators/zombie-survival/ZombieSurvival.spec.ts b/simulators/zombie-survival/ZombieSurvival.spec.ts index 44e0ed8..509809f 100644 --- a/simulators/zombie-survival/ZombieSurvival.spec.ts +++ b/simulators/zombie-survival/ZombieSurvival.spec.ts @@ -360,3 +360,20 @@ test("player gets killed behind walls", () => { expect(game.finished()).toBeTruthy(); }); + +test("zombie dies after player killing it", () => { + const game = new ZombieSurvival([ + ["P", " "], + [" ", "Z"], + ]); + + game.step(); + game.step(); + + expect(game.getState()).toStrictEqual([ + ["P", " "], + [" ", " "], + ]); + + expect(game.finished()).toBeTruthy(); +}); diff --git a/simulators/zombie-survival/ZombieSurvival.ts b/simulators/zombie-survival/ZombieSurvival.ts index e76edba..5c4a2a1 100644 --- a/simulators/zombie-survival/ZombieSurvival.ts +++ b/simulators/zombie-survival/ZombieSurvival.ts @@ -134,10 +134,6 @@ export class ZombieSurvival { break; } - if (zombie.dead()) { - continue; - } - zombie.walk(); } } From a08c832115aed964491c4c02ecd667c658b0eda5 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Wed, 16 Oct 2024 14:47:17 +0300 Subject: [PATCH 08/10] Implement Pythagorean theorem for closest entity calculation --- simulators/zombie-survival/Position.ts | 4 -- .../zombie-survival/ZombieSurvival.spec.ts | 54 ++++++++++++++++++- .../zombie-survival/lib/closestEntity.ts | 17 +++--- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/simulators/zombie-survival/Position.ts b/simulators/zombie-survival/Position.ts index 61a08f9..09614ba 100644 --- a/simulators/zombie-survival/Position.ts +++ b/simulators/zombie-survival/Position.ts @@ -2,7 +2,3 @@ export interface Position { x: number; y: number; } - -export function positionAsNumber(position: Position): number { - return position.x + position.y; -} diff --git a/simulators/zombie-survival/ZombieSurvival.spec.ts b/simulators/zombie-survival/ZombieSurvival.spec.ts index 509809f..4851456 100644 --- a/simulators/zombie-survival/ZombieSurvival.spec.ts +++ b/simulators/zombie-survival/ZombieSurvival.spec.ts @@ -361,7 +361,7 @@ test("player gets killed behind walls", () => { expect(game.finished()).toBeTruthy(); }); -test("zombie dies after player killing it", () => { +test("player kills zombie and it doesn't hit afterwards", () => { const game = new ZombieSurvival([ ["P", " "], [" ", "Z"], @@ -377,3 +377,55 @@ test("zombie dies after player killing it", () => { expect(game.finished()).toBeTruthy(); }); + +test.only("player kills closest zombie", () => { + const game = new ZombieSurvival([ + [" ", " ", "R", " ", "Z"], + [" ", " ", " ", " ", "B"], + [" ", "P", " ", "R", " "], + ["Z", " ", " ", " ", " "], + [" ", " ", "B", " ", " "], + ]); + + game.step(); + + expect(game.getState()).toStrictEqual([ + [" ", " ", "R", "Z", " "], + [" ", " ", " ", " ", "B"], + [" ", "P", " ", "R", " "], + [" ", "Z", " ", " ", " "], + [" ", " ", "B", " ", " "], + ]); + + game.step(); + + expect(game.getState()).toStrictEqual([ + [" ", " ", "R", " ", " "], + [" ", " ", " ", "Z", "B"], + [" ", "P", " ", "R", " "], + [" ", " ", " ", " ", " "], + [" ", " ", "B", " ", " "], + ]); + + game.step(); + + expect(game.getState()).toStrictEqual([ + [" ", " ", "R", " ", " "], + [" ", " ", "Z", " ", "B"], + [" ", "P", " ", "R", " "], + [" ", " ", " ", " ", " "], + [" ", " ", "B", " ", " "], + ]); + + game.step(); + + expect(game.getState()).toStrictEqual([ + [" ", " ", "R", " ", " "], + [" ", " ", " ", " ", "B"], + [" ", "P", " ", "R", " "], + [" ", " ", " ", " ", " "], + [" ", " ", "B", " ", " "], + ]); + + expect(game.finished()).toBeTruthy(); +}); diff --git a/simulators/zombie-survival/lib/closestEntity.ts b/simulators/zombie-survival/lib/closestEntity.ts index 2facd5e..82d2267 100644 --- a/simulators/zombie-survival/lib/closestEntity.ts +++ b/simulators/zombie-survival/lib/closestEntity.ts @@ -1,13 +1,14 @@ import { Entity } from "../entities/Entity"; -import { positionAsNumber } from "../Position"; export interface ClosestEntityScore { - score: number; + distance: number; target: Entity; } export function closestEntity(entity: Entity, targets: Entity[]): Entity { - const entityPosition = positionAsNumber(entity.getPosition()); + const entityPosition = entity.getPosition(); + const x1 = entityPosition.x; + const y1 = entityPosition.y; const scores: ClosestEntityScore[] = []; for (const target of targets) { @@ -15,16 +16,18 @@ export function closestEntity(entity: Entity, targets: Entity[]): Entity { continue; } - const targetPosition = positionAsNumber(target.getPosition()); - const score = Math.abs(entityPosition - targetPosition); + const targetPosition = target.getPosition(); + const x2 = targetPosition.x; + const y2 = targetPosition.y; + const distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); - scores.push({ target, score }); + scores.push({ distance, target }); } if (scores.length === 0) { throw new Error("No alive targets found"); } - scores.sort((a, b) => a.score - b.score); + scores.sort((a, b) => a.distance - b.distance); return scores[0].target; } From c53f77f99b149a572a9a0909a131f395958b2bad Mon Sep 17 00:00:00 2001 From: Web Dev Cody Date: Wed, 16 Oct 2024 08:36:06 -0400 Subject: [PATCH 09/10] Revert "Light mode with theme toggle" --- app/header.tsx | 21 +++++++-------------- app/layout.tsx | 2 +- convex/openai.ts | 4 ++-- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/app/header.tsx b/app/header.tsx index 787bd43..2be764a 100644 --- a/app/header.tsx +++ b/app/header.tsx @@ -6,7 +6,6 @@ import { Button } from "@/components/ui/button"; import { useAuthActions } from "@convex-dev/auth/react"; import { GitHubLogoIcon } from "@radix-ui/react-icons"; import { useConvexAuth } from "convex/react"; -import { ThemeToggle } from "@/components/ThemeToggle"; function SignInWithGitHub() { const { signIn } = useAuthActions(); @@ -15,7 +14,6 @@ function SignInWithGitHub() { variant="outline" type="button" onClick={() => void signIn("github", { redirectTo: "/" })} - className="dark:invert" > GitHub @@ -27,16 +25,14 @@ export default function Header() { const { isAuthenticated } = useConvexAuth(); return ( -
+
Logo SurviveTheNight
- - Synced using Convex - + Synced using Convex
-
- - {!isAuthenticated ? ( - - ) : ( - - )} -
+ {!isAuthenticated ? ( + + ) : ( + + )}
); } diff --git a/app/layout.tsx b/app/layout.tsx index 06ff44d..f221e13 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -29,7 +29,7 @@ export default function RootLayout({
-
{children}
+ {children} diff --git a/convex/openai.ts b/convex/openai.ts index 1814db7..066a8d8 100644 --- a/convex/openai.ts +++ b/convex/openai.ts @@ -68,11 +68,11 @@ export const playMapAction = internalAction({ "R" represents rocks, which players can shoot over but zombies cannot pass through or break. "P" represents the player, who cannot move. The player's goal is to shoot and kill zombies before they reach them. "B" represents blocks that can be placed before the round begins to hinder the zombies. You can place up to two blocks on the map. - + Your goal is to place the player ("P") and two blocks ("B") in locations that maximize the player's survival by delaying the zombies' approach. You can shoot any zombie regardless of where it is on the grid. Returning a 2d grid with the player and blocks placed in the optimal locations, with the coordinates player ("P") and the blocks ("B"), also provide reasoning for the choices. - + You can't replace rocks R or zombies Z with blocks. If there is no room to place a block, do not place any.`, }, { From b12b01943eaacb6b85eb61fc20b267e7a05e1a99 Mon Sep 17 00:00:00 2001 From: Cody Seibert Date: Wed, 16 Oct 2024 08:39:26 -0400 Subject: [PATCH 10/10] update contribute --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 749eb45..bd6b4df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,9 @@ This is a community project for my discord, so please join if you want to participate. -You can still contribute without being part of discord, but it would be nice. +## How to Contribute -## Join the community +if you see an issue, leave a comment saying "I want to work on this", then I will assign it to you. -Want to help build on this project? +## Join the Discord -- Join the [WDC Discord](https://discord.gg/N2uEyp7Rfu) +The [WDC Discord](https://discord.gg/N2uEyp7Rfu) is where we are discussing this project. We recommend you join if you want to participate.