diff --git a/island/generators.ts b/island/generators.ts new file mode 100644 index 0000000..b8d6a31 --- /dev/null +++ b/island/generators.ts @@ -0,0 +1,74 @@ +import { BEACH, ICECAP, LIGHT_FOREST, MOUNTAIN, WATER } from "./data"; +import { IslandGrid } from "./grid"; + +export type IsDone = boolean; +export type LobeGenerator = () => IsDone; +export type LobeGeneratorConstructor = ( + islands: IslandGrid, + basePos: number +) => LobeGenerator; + +/** form mountain with icecap */ +export const BIG_MOUNTAIN: LobeGeneratorConstructor = + (islands: IslandGrid, basePos: number) => () => { + const mountainTiles = islands.floodSearch( + basePos, + (tile) => tile > MOUNTAIN + ); + islands.dropWithin(mountainTiles); + + return mountainTiles.some((pos) => islands.data[pos] == ICECAP); + }; + +/** form low-lying beach */ +export const BEACH_LOBE: LobeGeneratorConstructor = + (islands: IslandGrid, basePos: number) => () => { + const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER); + + const shoreTiles = islandTiles.filter((pos) => islands.data[pos] == WATER); + islands.dropWithin(shoreTiles); + + return true; + }; + +/** form forested zone */ +export const FOREST_LOBE: LobeGeneratorConstructor = + (islands: IslandGrid, basePos: number) => () => { + const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER); + + // grow shore + const shoreTiles = islandTiles.filter((pos) => islands.data[pos] == WATER); + islands.dropWithin(shoreTiles); + + // seed beach + const beachTiles = islandTiles.filter((pos) => islands.data[pos] == BEACH); + islands.dropWithin(beachTiles); + + // expand forest + const forestLobe = islands.floodSearch(basePos, (tile) => tile > BEACH); + const forestTiles = forestLobe.filter( + (pos) => islands.data[pos] == LIGHT_FOREST + ); + islands.dropWithin(forestTiles); + islands.dropWithin(forestTiles); + islands.dropWithin(forestTiles); + + return true; + }; + +/** form low-lying beach with eroded sections */ +export const ERODED_LOBE: LobeGeneratorConstructor = + (islands: IslandGrid, basePos: number) => () => { + const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER); + + const shoreTiles = islandTiles.filter((pos) => islands.data[pos] == WATER); + islands.dropWithin(shoreTiles); + + // erode + if (islandTiles.length > 1) { + const erodePos = islandTiles[islands.rng() % islandTiles.length]; + islands.data[erodePos] = Math.max(islands.data[erodePos] - 1, WATER); + } + + return true; + }; diff --git a/island/grid.ts b/island/grid.ts index 4b7b916..50e662b 100644 --- a/island/grid.ts +++ b/island/grid.ts @@ -1,13 +1,17 @@ import { Prng, mulberry32 } from "../lib/prng"; import { BEACH, ICECAP, LIGHT_FOREST, MOUNTAIN, WATER } from "./data"; +import { + BEACH_LOBE, + BIG_MOUNTAIN, + FOREST_LOBE, + LobeGenerator, +} from "./generators"; export class IslandGrid { data: number[]; rng: Prng; - basePos: number; - lobePos1: number; - lobePos2: number; + generators: LobeGenerator[] = []; done = false; @@ -15,14 +19,24 @@ export class IslandGrid { this.data = Array(width * height).fill(0); this.rng = mulberry32(seed); - this.basePos = this.data.length >> 1; - this.lobePos1 = this.xy( - (width >> 1) + (this.rng() % 48) - 24, - (height >> 1) + (this.rng() % 48) - 24 + this.generators.push(BIG_MOUNTAIN(this, this.data.length >> 1)); + this.generators.push( + FOREST_LOBE( + this, + 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 + this.generators.push( + BEACH_LOBE( + this, + this.xy( + (width >> 1) + (this.rng() % 48) - 24, + (height >> 1) + (this.rng() % 48) - 24 + ) + ) ); } @@ -112,44 +126,8 @@ export class IslandGrid { } public step() { - const lowlandTiles1 = this.floodSearch( - this.lobePos1, - (tile) => tile > WATER - ); - const lowlandTiles2 = this.floodSearch( - this.lobePos2, - (tile) => tile > WATER - ); - - // grow shore - const shoreTiles1 = lowlandTiles1.filter((pos) => this.data[pos] == WATER); - this.dropWithin(shoreTiles1); - const shoreTiles2 = lowlandTiles2.filter((pos) => this.data[pos] == WATER); - this.dropWithin(shoreTiles2); - - // seed beach - const beachTiles = lowlandTiles1.filter((pos) => this.data[pos] == BEACH); - this.dropWithin(beachTiles); - - // expand forest - const forestLobe = this.floodSearch(this.lobePos1, (tile) => tile > BEACH); - const forestTiles = forestLobe.filter( - (pos) => this.data[pos] == LIGHT_FOREST - ); - this.dropWithin(forestTiles); - this.dropWithin(forestTiles); - this.dropWithin(forestTiles); - - // form mountain - const mountainTiles = this.floodSearch( - this.basePos, - (tile) => tile > MOUNTAIN - ); - this.dropWithin(mountainTiles); // GENERATOR - - if (lowlandTiles2.length > 1) { - const erodePos = lowlandTiles2[this.rng() % lowlandTiles2.length]; - this.data[erodePos] = Math.max(this.data[erodePos] - 1, 0); - } + this.done = this.generators + .map((generator) => generator()) + .every((done) => done); } }