diff --git a/src/WorldInit.ts b/src/WorldInit.ts new file mode 100644 index 0000000..34e02d1 --- /dev/null +++ b/src/WorldInit.ts @@ -0,0 +1,43 @@ +import { MAT_FOOD, MAT_HOME, MAT_SAND } from "./constants"; + +export function generateSideViewWorld(worldSize: number): Float32Array { + const data = new Float32Array(worldSize * worldSize * 4); + const sandHeight = Math.floor(worldSize * 0.6); + const surfaceRow = sandHeight - 1; + + // fill bottom 60% with sand + for (let y = 0; y < sandHeight; y++) { + for (let x = 0; x < worldSize; x++) { + const idx = (y * worldSize + x) * 4; + data[idx] = MAT_SAND; + } + } + // top 40% stays MAT_AIR (Float32Array is zero-initialized) + + // place home on surface near center + const centerX = Math.floor(worldSize / 2); + const homeIdx = (surfaceRow * worldSize + centerX) * 4; + data[homeIdx] = MAT_HOME; + + // scatter food on surface — deterministic spots spread across the row + const foodPositions = [ + Math.floor(worldSize * 0.15), + Math.floor(worldSize * 0.35), + Math.floor(worldSize * 0.65), + Math.floor(worldSize * 0.85), + ]; + for (const fx of foodPositions) { + // place a small cluster (3 cells wide) at each position + for (let dx = -1; dx <= 1; dx++) { + const x = fx + dx; + if (x >= 0 && x < worldSize) { + const idx = (surfaceRow * worldSize + x) * 4; + if (data[idx] !== MAT_HOME) { + data[idx] = MAT_FOOD; + } + } + } + } + + return data; +} diff --git a/src/__tests__/worldInit.test.ts b/src/__tests__/worldInit.test.ts new file mode 100644 index 0000000..f7498a7 --- /dev/null +++ b/src/__tests__/worldInit.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, test } from "bun:test"; +import { MAT_AIR, MAT_FOOD, MAT_HOME, MAT_SAND } from "../constants"; +import { generateSideViewWorld } from "../WorldInit"; + +const SIZE = 64; + +describe("generateSideViewWorld", () => { + test("output length is worldSize * worldSize * 4", () => { + const data = generateSideViewWorld(SIZE); + expect(data.length).toBe(SIZE * SIZE * 4); + }); + + test("bottom 60% of rows have R = MAT_SAND", () => { + const data = generateSideViewWorld(SIZE); + const sandHeight = Math.floor(SIZE * 0.6); + for (let y = 0; y < sandHeight; y++) { + for (let x = 0; x < SIZE; x++) { + const idx = (y * SIZE + x) * 4; + const mat = data[idx]; + // home and food are allowed on the surface row + if (y === sandHeight - 1) { + expect([MAT_SAND, MAT_HOME, MAT_FOOD]).toContain(mat); + } else { + expect(mat).toBe(MAT_SAND); + } + } + } + }); + + test("top 40% of rows have R = MAT_AIR", () => { + const data = generateSideViewWorld(SIZE); + const sandHeight = Math.floor(SIZE * 0.6); + for (let y = sandHeight; y < SIZE; y++) { + for (let x = 0; x < SIZE; x++) { + const idx = (y * SIZE + x) * 4; + expect(data[idx]).toBe(MAT_AIR); + } + } + }); + + test("G, B, A channels are 0 everywhere", () => { + const data = generateSideViewWorld(SIZE); + for (let i = 0; i < SIZE * SIZE; i++) { + expect(data[i * 4 + 1]).toBe(0); // G + expect(data[i * 4 + 2]).toBe(0); // B + expect(data[i * 4 + 3]).toBe(0); // A + } + }); + + test("exactly one cell has R = MAT_HOME on the surface row near center X", () => { + const data = generateSideViewWorld(SIZE); + const surfaceRow = Math.floor(SIZE * 0.6) - 1; + const centerX = Math.floor(SIZE / 2); + const tolerance = Math.floor(SIZE * 0.1); + + let homeCount = 0; + for (let i = 0; i < SIZE * SIZE; i++) { + if (data[i * 4] === MAT_HOME) { + homeCount++; + } + } + + expect(homeCount).toBe(1); + + // verify it's on the surface row — find the row + for (let x = 0; x < SIZE; x++) { + const idx = (surfaceRow * SIZE + x) * 4; + if (data[idx] === MAT_HOME) { + expect(Math.abs(x - centerX)).toBeLessThanOrEqual(tolerance); + return; + } + } + // if we reach here, home wasn't on the surface row + expect("home not on surface row").toBe(false); + }); + + test("some cells have R = MAT_FOOD on the surface row", () => { + const data = generateSideViewWorld(SIZE); + const surfaceRow = Math.floor(SIZE * 0.6) - 1; + + let foodCount = 0; + for (let x = 0; x < SIZE; x++) { + const idx = (surfaceRow * SIZE + x) * 4; + if (data[idx] === MAT_FOOD) { + foodCount++; + } + } + + expect(foodCount).toBeGreaterThan(0); + }); +});