From d755f9e72ee2321022bddf3de3599316db68fe72 Mon Sep 17 00:00:00 2001 From: Tangent Wantwight Date: Sat, 13 Jan 2024 00:44:05 -0500 Subject: [PATCH] Move generator functions into proper class --- island.ts | 200 +++++++++++++++++++++++++++++------------------------- 1 file changed, 106 insertions(+), 94 deletions(-) diff --git a/island.ts b/island.ts index d278235..d7a3b95 100644 --- a/island.ts +++ b/island.ts @@ -5,6 +5,8 @@ const BLOWUP = 4; const WIDTH = 240; const HEIGHT = 135; +const DEFAULT_SEED = 128; + type Lookup2d = (x: number, y: number) => number; function dim(width: number, height: number): Lookup2d { return function xy(x: number, y: number) { @@ -21,10 +23,26 @@ class IslandGrid { xy: Lookup2d; + basePos: number; + lobePos1: number; + lobePos2: number; + + done = false; + 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); + + this.basePos = this.data.length >> 1; + this.lobePos1 = this.xy( + (WIDTH >> 1) + (this.rng() % 48) - 24, + (HEIGHT >> 1) + (this.rng() % 48) - 24 + ); + this.lobePos2 = this.xy( + (WIDTH >> 1) + (this.rng() % 48) - 24, + (HEIGHT >> 1) + (this.rng() % 48) - 24 + ); } public get(x: number, y: number): number { @@ -67,6 +85,80 @@ class IslandGrid { return hitPositions; } + + public drop(pos: number): void { + const lowerNeighbors: number[] = []; + + const check = (adjPos: number) => { + if (this.data[adjPos] < this.data[pos]) { + lowerNeighbors.push(adjPos); + } + }; + + // try to roll in cardinal directions + + check((pos - this.width) % this.data.length); + check((pos - 1) % this.data.length); + check((pos + 1) % this.data.length); + check((pos + this.width) % this.data.length); + + if (lowerNeighbors.length > 0) { + const downhill = lowerNeighbors[this.rng() % lowerNeighbors.length]; + return this.drop(downhill); + } + + // try to roll in diagonal directions + + check((pos - this.width - 1) % this.data.length); + check((pos - this.width + 1) % this.data.length); + check((pos + this.width - 1) % this.data.length); + check((pos + this.width + 1) % this.data.length); + + if (lowerNeighbors.length > 0) { + const downhill = lowerNeighbors[this.rng() % lowerNeighbors.length]; + return this.drop(downhill); + } + + // flat, increase elevation + const newValue = ++this.data[pos]; + if (newValue == 9) { + this.done = true; + } + } + + public dropWithin(tiles: number[]) { + if (tiles.length > 0) { + this.drop(tiles[this.rng() % tiles.length]); + } + } + + public step() { + const lowlandTiles1 = this.floodSearch(this.lobePos1, (tile) => tile > 0); + const lowlandTiles2 = this.floodSearch(this.lobePos2, (tile) => tile > 0); + + // grow shore + const shoreTiles1 = lowlandTiles1.filter((pos) => this.data[pos] == 0); + this.dropWithin(shoreTiles1); + const shoreTiles2 = lowlandTiles2.filter((pos) => this.data[pos] == 0); + this.dropWithin(shoreTiles2); + + // seed beach + const beachTiles = lowlandTiles1.filter((pos) => this.data[pos] == 1); + this.dropWithin(beachTiles); + + // expand forest + const forestTiles = lowlandTiles1.filter((pos) => this.data[pos] == 2); + this.dropWithin(forestTiles); + this.dropWithin(forestTiles); + this.dropWithin(forestTiles); + + // form mountain + const mountainTiles = this.floodSearch(this.basePos, (tile) => tile > 4); + this.dropWithin(mountainTiles); // GENERATOR + + // const erodePos = islandTiles[islands.rng() % islandTiles.length]; + // islands.data[erodePos] = Math.max(islands.data[erodePos] - 1, 0); + } } function renderIslands(islands: IslandGrid, cx: CanvasRenderingContext2D) { @@ -109,103 +201,13 @@ export function IslandApplet() { }); cx.scale(BLOWUP, BLOWUP); - const seedInput = h("input", { type: "number", valueAsNumber: 128 }); + const seedInput = h("input", { type: "number", valueAsNumber: DEFAULT_SEED }); const seedLabel = h("label", {}, "Seed:", seedInput); // STATE let timerId: number; - let islands = new IslandGrid(WIDTH, HEIGHT, 128); - - // GENERATOR - - const len = islands.data.length; - const basePos = len >> 1; - const lobePos1 = islands.xy( - (WIDTH >> 1) + (islands.rng() % 48) - 24, - (HEIGHT >> 1) + (islands.rng() % 48) - 24 - ); - const lobePos2 = islands.xy( - (WIDTH >> 1) + (islands.rng() % 48) - 24, - (HEIGHT >> 1) + (islands.rng() % 48) - 24 - ); - 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); - } - } - - // try to roll in cardinal directions - - 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]; - return drop(downhill); - } - - // try to roll in diagonal directions - - check((pos - width - 1) % len); - check((pos - width + 1) % len); - check((pos + width - 1) % len); - check((pos + width + 1) % len); - - if (lowerNeighbors.length > 0) { - const downhill = lowerNeighbors[islands.rng() % lowerNeighbors.length]; - return drop(downhill); - } - - // flat, increase elevation - const newValue = ++islands.data[pos]; - if (newValue == 9) { - clearInterval(timerId); - } - } - - function dropWithin(tiles: number[]) { - if (tiles.length > 0) { - drop(tiles[islands.rng() % tiles.length]); - } - } - - function tick() { - const lowlandTiles1 = islands.floodSearch(lobePos1, (tile) => tile > 0); - const lowlandTiles2 = islands.floodSearch(lobePos2, (tile) => tile > 0); - - // grow shore - const shoreTiles1 = lowlandTiles1.filter((pos) => islands.data[pos] == 0); - dropWithin(shoreTiles1); - const shoreTiles2 = lowlandTiles2.filter((pos) => islands.data[pos] == 0); - dropWithin(shoreTiles2); - - // seed beach - const beachTiles = lowlandTiles1.filter((pos) => islands.data[pos] == 1); - dropWithin(beachTiles); - - // expand forest - const forestTiles = lowlandTiles1.filter((pos) => islands.data[pos] == 2); - dropWithin(forestTiles); - dropWithin(forestTiles); - dropWithin(forestTiles); - - // form mountain - const mountainTiles = islands.floodSearch(basePos, (tile) => tile > 4); - dropWithin(mountainTiles); - - // const erodePos = islandTiles[islands.rng() % islandTiles.length]; - // islands.data[erodePos] = Math.max(islands.data[erodePos] - 1, 0); - - renderIslands(islands, cx); - } + let islands = new IslandGrid(WIDTH, HEIGHT, DEFAULT_SEED); // CONTROLS @@ -214,9 +216,19 @@ export function IslandApplet() { { onclick: () => { clearInterval(timerId); - console.log(seedInput.valueAsNumber); + islands = new IslandGrid(WIDTH, HEIGHT, seedInput.valueAsNumber); - timerId = setInterval(tick, 1000 / 30); + + timerId = setInterval(function tick() { + islands.step(); + islands.step(); + islands.step(); + if (islands.done) { + clearInterval(timerId); + } + + renderIslands(islands, cx); + }, 1000 / 30); }, }, "Generate"