From b2255e212fbb2d500b32417f898a22cf6320f56e Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Wed, 16 Oct 2024 13:53:49 +0300 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 80e1c85bd68f080b6651ca33819d3fd172d3d372 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Wed, 16 Oct 2024 15:23:50 +0300 Subject: [PATCH 4/6] Implement central place for models --- convex/_generated/api.d.ts | 2 - convex/games.ts | 2 +- convex/maps.ts | 83 +++++++++++++++++++++++++++- convex/openai.ts | 109 ------------------------------------- convex/results.ts | 4 +- models/gpt-4o.ts | 56 +++++++++++++++++++ models/index.ts | 20 +++++++ 7 files changed, 161 insertions(+), 115 deletions(-) delete mode 100644 convex/openai.ts create mode 100644 models/gpt-4o.ts create mode 100644 models/index.ts diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 4f841d4..a4e3ff9 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -21,7 +21,6 @@ import type * as games from "../games.js"; import type * as http from "../http.js"; import type * as init from "../init.js"; import type * as maps from "../maps.js"; -import type * as openai from "../openai.js"; import type * as results from "../results.js"; import type * as scores from "../scores.js"; import type * as users from "../users.js"; @@ -41,7 +40,6 @@ declare const fullApi: ApiFromModules<{ http: typeof http; init: typeof init; maps: typeof maps; - openai: typeof openai; results: typeof results; scores: typeof scores; users: typeof users; diff --git a/convex/games.ts b/convex/games.ts index 34db7ed..a4aa84c 100644 --- a/convex/games.ts +++ b/convex/games.ts @@ -25,7 +25,7 @@ export const startNewGame = mutation({ throw new Error("No map found for level 1"); } - await ctx.scheduler.runAfter(0, internal.openai.playMapAction, { + await ctx.scheduler.runAfter(0, internal.maps.playMapAction, { gameId, modelId: args.modelId, level: 1, diff --git a/convex/maps.ts b/convex/maps.ts index 8312ef2..b6d2617 100644 --- a/convex/maps.ts +++ b/convex/maps.ts @@ -1,5 +1,12 @@ +import OpenAI from "openai"; +import { internalAction, internalMutation, query } from "./_generated/server"; import { v } from "convex/values"; -import { internalMutation, query } from "./_generated/server"; +import { z } from "zod"; +import { zodResponseFormat } from "openai/helpers/zod"; +import { Doc } from "./_generated/dataModel"; +import { ZombieSurvival } from "../simulators/zombie-survival"; +import { api, internal } from "./_generated/api"; +import { runModel } from "../models"; const MAPS = [ { @@ -98,3 +105,77 @@ export const getMapByLevel = query({ .first(); }, }); + +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_MODELS === "true") { + const existingMap = [...map.grid.map((row) => [...row])]; + + existingMap[0][0] = "P"; + existingMap[0][1] = "B"; + existingMap[0][2] = "B"; + + const game = new ZombieSurvival(existingMap); + + while (!game.finished()) { + game.step(); + } + + const isWin = !game.getPlayer().dead(); + + await ctx.runMutation(internal.results.updateResult, { + resultId, + isWin, + reasoning: "This is a mock response", + map: existingMap, + }); + + return; + } + + try { + const { solution, reasoning } = await runModel(args.modelId, map.grid); + const game = new ZombieSurvival(solution); + + while (!game.finished()) { + game.step(); + } + + const isWin = !game.getPlayer().dead(); + + await ctx.runMutation(internal.results.updateResult, { + resultId, + isWin, + reasoning, + map: solution, + }); + } catch (error) { + // todo: handle error + console.log(error); + } + }, +}); diff --git a/convex/openai.ts b/convex/openai.ts deleted file mode 100644 index 066a8d8..0000000 --- a/convex/openai.ts +++ /dev/null @@ -1,109 +0,0 @@ -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"; - -const ResponseSchema = z.object({ - map: z.array(z.array(z.string())), - reasoning: z.string(), - playerCoordinates: z.array(z.number()), - boxCoordinates: z.array(z.array(z.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_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, - reasoning: "This is a mock response", - playerCoordinates: [0, 0], - boxCoordinates: [], - }; - } - - // moving here for now so that I can get this deployed without a real key - const openai = new OpenAI(); - - try { - const completion = await openai.beta.chat.completions.parse({ - model: "gpt-4o-2024-08-06", - messages: [ - { - role: "system", - content: `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.`, - }, - { - role: "user", - content: JSON.stringify(map.grid), - }, - ], - response_format: zodResponseFormat(ResponseSchema, "game_map"), - }); - - 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(); - - await ctx.runMutation(internal.results.updateResult, { - resultId, - isWin, - reasoning: response.parsed.reasoning, - map: response.parsed.map, - }); - } else if (response.refusal) { - const refusal_res = response.refusal; - throw new Error(`Refusal: ${refusal_res}`); - } - } catch (error) { - // todo: handle error - console.log(error); - } - }, -}); diff --git a/convex/results.ts b/convex/results.ts index 912b045..3acb364 100644 --- a/convex/results.ts +++ b/convex/results.ts @@ -77,10 +77,10 @@ export const updateResult = internalMutation({ throw new Error("Next map not found"); } - await ctx.scheduler.runAfter(0, internal.openai.playMapAction, { + await ctx.scheduler.runAfter(0, internal.maps.playMapAction, { + gameId: result.gameId, modelId: game.modelId, level: result.level + 1, - gameId: result.gameId, }); } }, diff --git a/models/gpt-4o.ts b/models/gpt-4o.ts new file mode 100644 index 0000000..a2f53e2 --- /dev/null +++ b/models/gpt-4o.ts @@ -0,0 +1,56 @@ +import OpenAI from "openai"; +import { z } from "zod"; +import { zodResponseFormat } from "openai/helpers/zod"; +import { type ModelResult } from "."; + +const ResponseSchema = z.object({ + map: z.array(z.array(z.string())), + reasoning: z.string(), + playerCoordinates: z.array(z.number()), + boxCoordinates: z.array(z.array(z.number())), +}); + +export async function gpt4o(map: string[][]): Promise { + const openai = new OpenAI(); + + const prompt = `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.`; + + const completion = await openai.beta.chat.completions.parse({ + model: "gpt-4o-2024-08-06", + messages: [ + { + role: "system", + content: prompt, + }, + { + role: "user", + content: JSON.stringify(map), + }, + ], + response_format: zodResponseFormat(ResponseSchema, "game_map"), + }); + + const response = completion.choices[0].message; + + if (response.refusal) { + throw new Error(`Refusal: ${response.refusal}`); + } else if (!response.parsed) { + throw new Error("Failed to run model GPT-4o"); + } + + return { + solution: response.parsed.map, + reasoning: response.parsed.reasoning, + }; +} diff --git a/models/index.ts b/models/index.ts new file mode 100644 index 0000000..0393670 --- /dev/null +++ b/models/index.ts @@ -0,0 +1,20 @@ +import { gpt4o } from "./gpt-4o"; + +export interface ModelResult { + solution: string[][]; + reasoning: string; +} + +export async function runModel( + modelId: string, + map: string[][], +): Promise { + switch (modelId) { + case "gpt-4o": { + return gpt4o(map); + } + default: { + throw new Error(`Tried running unknown model '${modelId}'`); + } + } +} From c5305ba94b8c42e7272eddd341e7c66a1d0a7f52 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Wed, 16 Oct 2024 16:11:47 +0300 Subject: [PATCH 5/6] Convert Gemini model --- convex/constants.ts | 8 +-- convex/gemini.ts | 140 --------------------------------------- models/gemini-1.5-pro.ts | 87 ++++++++++++++++++++++++ models/index.ts | 4 ++ 4 files changed, 95 insertions(+), 144 deletions(-) delete mode 100644 convex/gemini.ts create mode 100644 models/gemini-1.5-pro.ts diff --git a/convex/constants.ts b/convex/constants.ts index f3009e7..299421d 100644 --- a/convex/constants.ts +++ b/convex/constants.ts @@ -1,12 +1,12 @@ export const AI_MODELS = [ - { - model: "gpt-4o", - name: "OpenAI - 4o Mini", - }, { model: "gemini-1.5-pro", name: "Gemini - 1.5 Pro", }, + { + model: "gpt-4o", + name: "OpenAI - 4o Mini", + }, ]; export const AI_MODEL_IDS = AI_MODELS.map((model) => model.model); diff --git a/convex/gemini.ts b/convex/gemini.ts deleted file mode 100644 index a83df75..0000000 --- a/convex/gemini.ts +++ /dev/null @@ -1,140 +0,0 @@ -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/models/gemini-1.5-pro.ts b/models/gemini-1.5-pro.ts new file mode 100644 index 0000000..7cc9fcc --- /dev/null +++ b/models/gemini-1.5-pro.ts @@ -0,0 +1,87 @@ +import { GoogleGenerativeAI, SchemaType } from "@google/generative-ai"; +import { type ModelResult } from "."; + +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"], +}; + +interface GeminiResponse { + map: string[][]; + reasoning: string; + playerCoordinates: number[]; + boxCoordinates: number[][]; +} + +export async function gemini15pro(map: string[][]): Promise { + const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!); + + const model = genAI.getGenerativeModel({ + model: "gemini-1.5-pro", + generationConfig: { + responseMimeType: "application/json", + responseSchema: schema, + }, + }); + + 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 GeminiResponse; + + return { + solution: parsedResponse.map, + reasoning: parsedResponse.reasoning, + }; +} diff --git a/models/index.ts b/models/index.ts index 0393670..0b994f7 100644 --- a/models/index.ts +++ b/models/index.ts @@ -1,3 +1,4 @@ +import { gemini15pro } from "./gemini-1.5-pro"; import { gpt4o } from "./gpt-4o"; export interface ModelResult { @@ -10,6 +11,9 @@ export async function runModel( map: string[][], ): Promise { switch (modelId) { + case "gemini-1.5-pro": { + return gemini15pro(map); + } case "gpt-4o": { return gpt4o(map); } From 2b6f9617200e104869e62cf229f52cdac8887328 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Wed, 16 Oct 2024 16:21:37 +0300 Subject: [PATCH 6/6] Fix mock documentation --- README.md | 5 +++-- convex/_generated/api.d.ts | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 25c60d7..c79518d 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,13 @@ Before you can run the project, you'll need to setup a This starter project works with [convex](https://www.convex.dev) so to run you need to use the [.env.example](.env.example) file to create your own .env file. -In convex, please add the following environment variables: +If you want to mock all models - in convex, please add the following environment variables: -- `npx convex env set MOCK_OPEN_AI true` +- `npx convex env set MOCK_MODELS true` Add optional environment variable/s for simulating real AI models without mockup responses(when mockup flags are set to FALSE): +- `npx convex env set GEMINI_API_KEY YOUR_API_KEY` - `npx convex env set OPENAI_API_KEY YOUR_API_KEY` also, you may need to run, but I think the initial setup does that. diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index f8c7441..a4e3ff9 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -18,7 +18,6 @@ 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,7 +37,6 @@ 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;