From e57a376883681b2b85ad4bb7dcac6024525c58cc Mon Sep 17 00:00:00 2001 From: Tangent Wantwight Date: Sat, 13 Jan 2024 11:34:25 -0500 Subject: [PATCH] Breakup files --- island.ts | 191 +---------------------------------------------- island/grid.ts | 156 ++++++++++++++++++++++++++++++++++++++ island/render.ts | 37 +++++++++ 3 files changed, 195 insertions(+), 189 deletions(-) create mode 100644 island/grid.ts create mode 100644 island/render.ts diff --git a/island.ts b/island.ts index 86d7c29..740b5e3 100644 --- a/island.ts +++ b/island.ts @@ -1,5 +1,6 @@ +import { IslandGrid } from "./island/grid"; +import { renderIslands } from "./island/render"; import { canvas2d, h } from "./lib/html"; -import { Prng, mulberry32 } from "./lib/prng"; const BLOWUP = 4; const WIDTH = 240; @@ -7,194 +8,6 @@ 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) { - return ( - (((x % width) + width) % width) + - width * (((y % height) + height) % height) - ); - }; -} - -class IslandGrid { - data: number[]; - rng: Prng; - - 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 { - 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; - } - - 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 forestLobe = this.floodSearch(this.lobePos1, (tile) => tile > 1); - const forestTiles = forestLobe.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) { - 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; - case 4: - case 5: - case 6: - case 7: - case 8: - cx.fillStyle = "#666666"; - break; - default: - cx.fillStyle = "#88aaff"; - break; - } - cx.fillRect(x, y, 1, 1); - } - } -} - export function IslandApplet() { // STATE diff --git a/island/grid.ts b/island/grid.ts new file mode 100644 index 0000000..2207d99 --- /dev/null +++ b/island/grid.ts @@ -0,0 +1,156 @@ +import { Prng, mulberry32 } from "../lib/prng"; + +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) + ); + }; +} + +export class IslandGrid { + data: number[]; + rng: Prng; + + 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 { + 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; + } + + 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 forestLobe = this.floodSearch(this.lobePos1, (tile) => tile > 1); + const forestTiles = forestLobe.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); + } +} diff --git a/island/render.ts b/island/render.ts new file mode 100644 index 0000000..a7a3903 --- /dev/null +++ b/island/render.ts @@ -0,0 +1,37 @@ +import { IslandGrid } from "./grid"; + +export 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; + case 4: + case 5: + case 6: + case 7: + case 8: + cx.fillStyle = "#666666"; + break; + default: + cx.fillStyle = "#88aaff"; + break; + } + cx.fillRect(x, y, 1, 1); + } + } +}