import { Prng, mulberry32 } from "../lib/prng"; import { BEACH, ICECAP } from "./data"; import { ALL_ISLANDS, BIG_ISLANDS, LobeGenerator } from "./generators"; export class IslandGrid { data: number[]; rng: Prng; generators: LobeGenerator[] = []; done = false; constructor(public width: number, public height: number, seed: number) { this.data = Array(width * height).fill(0); this.rng = mulberry32(seed); this.generators.push(this.choose(BIG_ISLANDS)(this, this.data.length >> 1)); this.generators.push( this.choose(ALL_ISLANDS)( this, this.xy( (width >> 1) + (this.rng() % 48) - 24, (height >> 1) + (this.rng() % 48) - 24 ) ) ); this.generators.push( this.choose(ALL_ISLANDS)( this, this.xy( (width >> 1) + (this.rng() % 48) - 24, (height >> 1) + (this.rng() % 48) - 24 ) ) ); } public xy(x: number, y: number): number { return ( (((x % this.width) + this.width) % this.width) + this.width * (((y % this.height) + this.height) % this.height) ); } 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 == ICECAP) { this.done = true; } } public choose(list: T[]) { if (list.length == 0) { throw new Error("Picking from empty list"); } return list[this.rng() % list.length]; } public dropWithin(tiles: number[]) { if (tiles.length > 0) { this.drop(this.choose(tiles)); } } public step() { this.done = this.generators .map((generator) => generator()) .every((done) => done); } }