import { canvas2d } from "./lib/html"; import { Prng, mulberry32 } from "./lib/prng"; const BLOWUP = 4 * 5; const WIDTH = 240 / 5; const HEIGHT = 135 / 5; type Lookup2d = (x: number, y: number) => number; function dim(width: number, height: number): Lookup2d { return function xy(x: number, y: number) { return ( (((x % width) + width) % width) + width * (((y % height) + height) % height) ); }; } class IslandGrid { data: number[]; rng: Prng; xy: Lookup2d; constructor(public width: number, public height: number, seed: number) { this.data = Array(width * height).fill(0); this.rng = mulberry32(seed); this.xy = dim(width, height); } public get(x: number, y: number): number { return this.data[this.xy(x, y)]; } public set(x: number, y: number, tile: number) { this.data[this.xy(x, y)] = tile; console.log(x, y, this.xy(x, y), this.data[this.xy(x, y)]); } public floodSearch( startPos: number, shouldExpand: (tile: number) => boolean ): number[] { const len = this.data.length; const width = this.width; const seen = new Uint8Array(len); const hitPositions: number[] = []; function enqueue(pos: number) { if (!seen[pos]) { hitPositions.push(pos); seen[pos] = 1; } } enqueue(startPos); for (let i = 0; i < hitPositions.length; i++) { const pos = hitPositions[i]; if (shouldExpand(this.data[pos])) { enqueue((pos - width) % len); enqueue((pos - 1) % len); enqueue((pos + 1) % len); enqueue((pos + width) % len); } } return hitPositions; } } function renderIslands(islands: IslandGrid, cx: CanvasRenderingContext2D) { for (let y = 0; y < islands.height; y++) { for (let x = 0; x < islands.width; x++) { const tile = islands.data[islands.xy(x, y)]; switch (tile) { case 0: cx.fillStyle = "blue"; break; case 1: cx.fillStyle = "yellow"; break; case 2: cx.fillStyle = "#00ff00"; break; case 3: cx.fillStyle = "#008800"; break; default: cx.fillStyle = "#666666"; break; } cx.fillRect(x, y, 1, 1); } } } export function IslandApplet() { const [canvas, cx] = canvas2d({ width: WIDTH * BLOWUP, height: HEIGHT * BLOWUP, }); cx.scale(BLOWUP, BLOWUP); const islands = new IslandGrid(WIDTH, HEIGHT, 128); const len = islands.data.length; const basePos = len >> 1; const width = islands.width; function drop(pos: number) { const lowerNeighbors: number[] = []; function check(adjPos: number) { if (islands.data[adjPos] < islands.data[pos]) { lowerNeighbors.push(adjPos); } } check((pos - width) % len); check((pos - 1) % len); check((pos + 1) % len); check((pos + width) % len); if (lowerNeighbors.length > 0) { const downhill = lowerNeighbors[islands.rng() % lowerNeighbors.length]; drop(downhill); } else { islands.data[pos]++; } } function tick() { const islandTiles = islands.floodSearch(basePos, (tile) => tile > 0); const pos = islandTiles[islands.rng() % islandTiles.length]; drop(pos); renderIslands(islands, cx); } tick(); setInterval(tick, 1000 / 10); return [canvas]; } (globalThis as any).IslandApplet = IslandApplet;