2024.js/island/generators.ts

232 lines
6.9 KiB
TypeScript

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 */
export const SMALL_MOUNTAIN: LobeGeneratorConstructor =
(islands: IslandGrid, basePos: number) => () => {
const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER);
const edgeTiles = islandTiles.filter((pos) => islands.data[pos] <= BEACH);
islands.dropWithin(edgeTiles);
const mountainTiles = islands.floodSearch(
basePos,
(tile) => tile > MOUNTAIN
);
islands.dropWithin(mountainTiles);
return true;
};
/** 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 SMALL_BEACH: 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 low-lying beach that meets another island */
export const BIG_BEACH: LobeGeneratorConstructor = (
islands: IslandGrid,
basePos: number
) => {
let dropped = 0;
return () => {
const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER);
const shoreTiles = islandTiles.filter((pos) => islands.data[pos] <= WATER);
islands.dropWithin(shoreTiles);
dropped++;
const landTiles = islandTiles.filter((pos) => islands.data[pos] > WATER);
return landTiles.length > dropped;
};
};
/** form forested zone wherever available */
export const SCATTERED_FOREST: 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);
islands.dropWithin(beachTiles);
// expand forest
const forestLobe = islands.floodSearch(basePos, (tile) => tile > WATER);
const forestTiles = forestLobe.filter(
(pos) => islands.data[pos] == LIGHT_FOREST
);
islands.dropWithin(forestTiles);
return true;
};
/** form forested zone that tries to grow as one mass */
export const CONTIGUOUS_FOREST: 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);
// locate & expand central forest edge
const forestLobe = islands.floodSearch(basePos, (tile) => tile > BEACH);
const forestTiles = forestLobe.filter(
(pos) => islands.data[pos] == LIGHT_FOREST
);
islands.dropWithin(forestTiles);
islands.dropWithin(forestTiles);
return true;
};
/** form forested zone that can raise additional peaks */
export const HILLY_FOREST: 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);
// locate central forest
const centralForest = islands.floodSearch(basePos, (tile) => tile > BEACH);
islands.dropWithin(centralForest);
// expand edge
const edgeTiles = centralForest.filter(
(pos) => islands.data[pos] == LIGHT_FOREST
);
islands.dropWithin(edgeTiles);
const hillTiles = centralForest.filter(
(pos) => islands.data[pos] >= MOUNTAIN
);
return hillTiles.length > 10;
};
/** form low-lying beach with eroded sections */
export const ERODED_BEACH: 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;
};
/** form low-lying beach with eroded sections */
export const NO_ISLAND: LobeGeneratorConstructor =
(islands: IslandGrid, basePos: number) => () => {
return true;
};
const SINKHOLE_BURNOUT = 1500;
/** carve out elevation */
export const SINKHOLE: LobeGeneratorConstructor = (
islands: IslandGrid,
basePos: number
) => {
// emergency cutoff in case sinkhole ends up fighting a generator that terminates at a given elevation
let ticks = 0;
return () => {
if (ticks++ < SINKHOLE_BURNOUT) {
const sunk = islands.floodSearch(basePos, (tile) => tile < 0);
islands.sinkhole(islands.choose(sunk));
}
return true;
};
};
/** carve out elevation more aggressively */
export const WIDE_SINKHOLE: LobeGeneratorConstructor = (
islands: IslandGrid,
basePos: number
) => {
// emergency cutoff in case sinkhole ends up fighting a generator that terminates at a given elevation
let ticks = 0;
return () => {
if (ticks++ < SINKHOLE_BURNOUT) {
const sunk = islands.floodSearch(basePos, (tile) => tile < 0);
const sunkEdge = sunk.filter((pos) => islands.data[pos] >= WATER);
if (sunkEdge.length > 0) {
islands.sinkhole(islands.choose(sunkEdge));
} else {
islands.sinkhole(islands.choose(sunk));
}
}
return true;
};
};
export const BIG_ISLANDS = [BIG_MOUNTAIN, BIG_BEACH, HILLY_FOREST];
export const ROCKY_ISLANDS = [SMALL_MOUNTAIN, BIG_MOUNTAIN, HILLY_FOREST];
export const GREEN_ISLANDS = [
SCATTERED_FOREST,
CONTIGUOUS_FOREST,
HILLY_FOREST,
];
export const SMALL_ISLANDS = [SMALL_BEACH, SCATTERED_FOREST, ERODED_BEACH];
export const ALL_ISLANDS = [
SMALL_MOUNTAIN,
BIG_MOUNTAIN,
SMALL_BEACH,
BIG_BEACH,
SCATTERED_FOREST,
CONTIGUOUS_FOREST,
HILLY_FOREST,
ERODED_BEACH,
];
export const VOIDS = [NO_ISLAND, SINKHOLE, WIDE_SINKHOLE];