-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(engine): populate
api.otherPlayers
, ensure bots can eat other b…
…ots, and fix crash that can sometimes happen when the food with the latest index is eaten (#12) ## How does this PR impact the user? This PR fixes the following issues: - [x] `api.otherPlayers` was always empty - [x] bots weren't able to eat other bots - [x] world collision computation can crash when the bot eats multiple foods and on of them has the latest index ### Now – `api.otherPlayers` is correctly populated and the bots can eat each other https://github.com/move-fast-and-break-things/aibyss/assets/4187729/b7c8a438-b3a1-43ae-be11-c8df36e45797 ### Before – `api.otherPlayers` is always empty, and the second bot is stuck https://github.com/move-fast-and-break-things/aibyss/assets/4187729/9725cc6b-46d4-4222-aeb2-c9a87aa9c0b3 ## Description - [x] move bot code preparation logic from `engine.ts` to `prepareBotCode.ts` - [x] fix bugs - [x] add tests to prevent regressions ## Limitations N/A ## Checklist - [x] my PR is focused and contains one wholistic change - [x] I have added screenshots or screen recordings to show the changes
- Loading branch information
1 parent
b640f1b
commit a0e9030
Showing
7 changed files
with
249 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||
|
||
exports[`prepares bot code correctly when there are three bots and some food in the world 1`] = ` | ||
" | ||
global._player = {"x":0,"y":0,"radius":5}; | ||
global._otherPlayers = [{"x":10,"y":10,"radius":5},{"x":20,"y":20,"radius":5}]; | ||
global._food = [{"x":30,"y":30,"radius":5},{"x":40,"y":40,"radius":5}]; | ||
global._worldWidth = 100; | ||
global._worldHeight = 100; | ||
console.log('hello world'); | ||
console.log('hello bot api'); | ||
" | ||
`; | ||
|
||
exports[`prepares bot code correctly when there are two bots in the world 1`] = ` | ||
" | ||
global._player = {"x":0,"y":0,"radius":5}; | ||
global._otherPlayers = [{"x":10,"y":10,"radius":5}]; | ||
global._food = []; | ||
global._worldWidth = 100; | ||
global._worldHeight = 100; | ||
console.log('hello world'); | ||
console.log('hello bot api'); | ||
" | ||
`; | ||
|
||
exports[`prepares bot code correctly when there is one bot in the world 1`] = ` | ||
" | ||
global._player = {"x":0,"y":0,"radius":5}; | ||
global._otherPlayers = []; | ||
global._food = []; | ||
global._worldWidth = 100; | ||
global._worldHeight = 100; | ||
console.log('hello world'); | ||
console.log('hello bot api'); | ||
" | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import EventEmitter from "node:events"; | ||
|
||
interface Bot { | ||
export interface Bot { | ||
id: string; | ||
code: string; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { it, expect } from "vitest"; | ||
import prepareBotCode from "./prepareBotCode"; | ||
|
||
it("prepares bot code correctly when there is one bot in the world", () => { | ||
const bot = { | ||
id: "1", | ||
code: "console.log('hello world');", | ||
}; | ||
|
||
const state = { | ||
bots: new Map([["1", { x: 0, y: 0, radius: 5, id: "1", color: "#00FF00" }]]), | ||
food: [], | ||
width: 100, | ||
height: 100, | ||
}; | ||
|
||
const botApi = "console.log('hello bot api');"; | ||
|
||
const preparedCode = prepareBotCode({ bot, state, botApi }); | ||
|
||
expect(preparedCode).toMatchSnapshot(); | ||
}); | ||
|
||
it("prepares bot code correctly when there are two bots in the world", () => { | ||
const bot = { | ||
id: "1", | ||
code: "console.log('hello world');", | ||
}; | ||
|
||
const state = { | ||
bots: new Map([ | ||
["1", { x: 0, y: 0, radius: 5, id: "1", color: "#00FF00" }], | ||
["2", { x: 10, y: 10, radius: 5, id: "2", color: "#00FF00" }], | ||
]), | ||
food: [], | ||
width: 100, | ||
height: 100, | ||
}; | ||
|
||
const botApi = "console.log('hello bot api');"; | ||
|
||
const preparedCode = prepareBotCode({ bot, state, botApi }); | ||
|
||
expect(preparedCode).toMatchSnapshot(); | ||
}); | ||
|
||
it("prepares bot code correctly when there are three bots and some food in the world", () => { | ||
const bot = { | ||
id: "1", | ||
code: "console.log('hello world');", | ||
}; | ||
|
||
const state = { | ||
bots: new Map([ | ||
["1", { x: 0, y: 0, radius: 5, id: "1", color: "#00FF00" }], | ||
["2", { x: 10, y: 10, radius: 5, id: "2", color: "#00FF00" }], | ||
["3", { x: 20, y: 20, radius: 5, id: "3", color: "#00FF00" }], | ||
]), | ||
food: [{ x: 30, y: 30, radius: 5 }, { x: 40, y: 40, radius: 5 }], | ||
width: 100, | ||
height: 100, | ||
}; | ||
|
||
const botApi = "console.log('hello bot api');"; | ||
|
||
const preparedCode = prepareBotCode({ bot, state, botApi }); | ||
|
||
expect(preparedCode).toMatchSnapshot(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import type { Bot } from "~/utils/botstore"; | ||
import type { WorldState } from "~/utils/world"; | ||
|
||
type PrepareBotCodeArgs = { | ||
bot: Bot; | ||
state: WorldState; | ||
botApi: string; | ||
}; | ||
|
||
export default function prepareBotCode({ bot, state, botApi }: PrepareBotCodeArgs): string | undefined { | ||
const { code } = bot; | ||
|
||
const botObject = state.bots.get(bot.id); | ||
if (!botObject) { | ||
// TODO(yurij): handle this better | ||
console.error(`Bot with id ${bot.id} not found in the world`); | ||
return; | ||
} | ||
|
||
const me = { x: botObject.x, y: botObject.y, radius: botObject.radius }; | ||
const otherPlayers = [...state.bots.values()] | ||
.filter(b => b.id !== bot.id) | ||
.map(b => ({ x: b.x, y: b.y, radius: b.radius })); | ||
const food = state.food.map(f => ({ x: f.x, y: f.y, radius: f.radius })); | ||
|
||
const preparedCode = ` | ||
global._player = ${JSON.stringify(me)}; | ||
global._otherPlayers = ${JSON.stringify(otherPlayers)}; | ||
global._food = ${JSON.stringify(food)}; | ||
global._worldWidth = ${state.width}; | ||
global._worldHeight = ${state.height}; | ||
${code} | ||
${botApi} | ||
`; | ||
|
||
return preparedCode; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { it, expect } from "vitest"; | ||
import World, { type BotSprite, type Sprite } from "./world"; | ||
|
||
it("correctly removes the smaller bot when the bigger bot eats it", () => { | ||
const world = new World({ width: 100, height: 100 }); | ||
world.addBot("1"); | ||
world.addBot("2"); | ||
|
||
// @ts-expect-error - world.bots is private | ||
const bot1 = world.bots.get("1") as BotSprite; | ||
bot1.radius = 10; | ||
bot1.x = 10; | ||
bot1.y = 10; | ||
|
||
// @ts-expect-error - world.bots is private | ||
const bot2 = world.bots.get("2") as BotSprite; | ||
bot2.radius = 5; | ||
bot2.x = 10; | ||
bot2.y = 10; | ||
|
||
world.checkCollisions("1"); | ||
|
||
// @ts-expect-error - world.bots is private | ||
expect(world.bots.has("1")).toBe(true); | ||
// @ts-expect-error - world.bots is private | ||
expect(world.bots.has("2")).toBe(false); | ||
}); | ||
|
||
it("correctly removes food when the bot eats it", () => { | ||
const world = new World({ width: 100, height: 100 }); | ||
world.addBot("1"); | ||
|
||
// @ts-expect-error - world.bots is private | ||
const bot = world.bots.get("1") as BotSprite; | ||
bot.x = 10; | ||
bot.y = 10; | ||
|
||
// @ts-expect-error - world.food is private | ||
expect(world.food.length).toBe(100); | ||
|
||
// @ts-expect-error - world.food is private | ||
const food = world.food[0] as Sprite; | ||
food.x = 10; | ||
food.y = 10; | ||
|
||
world.checkCollisions("1"); | ||
|
||
// @ts-expect-error - world.food is private | ||
expect(world.food.length).toBeLessThan(100); | ||
}); | ||
|
||
it("shouldn't crash when bot eats two food items and one of them with the last index", () => { | ||
const world = new World({ width: 100, height: 100 }); | ||
world.addBot("1"); | ||
|
||
// @ts-expect-error - world.bots is private | ||
const bot = world.bots.get("1") as BotSprite; | ||
bot.x = 10; | ||
bot.y = 10; | ||
|
||
// @ts-expect-error - world.food is private | ||
expect(world.food.length).toBe(100); | ||
|
||
// @ts-expect-error - world.food is private | ||
const food1 = world.food[0] as Sprite; | ||
food1.x = 10; | ||
food1.y = 10; | ||
|
||
// @ts-expect-error - world.food is private | ||
const food2 = world.food[99] as Sprite; | ||
food2.x = 10; | ||
food2.y = 10; | ||
|
||
world.checkCollisions("1"); | ||
|
||
// @ts-expect-error - world.food is private | ||
expect(world.food.length).toBeLessThan(100); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters