2024.js/island/grid.ts

154 lines
4 KiB
TypeScript
Raw Normal View History

2024-01-13 11:34:25 -05:00
import { Prng, mulberry32 } from "../lib/prng";
2024-01-13 11:44:36 -05:00
import { BEACH, ICECAP, LIGHT_FOREST, MOUNTAIN, WATER } from "./data";
2024-01-13 11:34:25 -05:00
export class IslandGrid {
data: number[];
rng: Prng;
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.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
);
}
2024-01-13 11:34:37 -05:00
public xy(x: number, y: number): number {
return (
(((x % this.width) + this.width) % this.width) +
this.width * (((y % this.height) + this.height) % this.height)
);
}
2024-01-13 11:34:25 -05:00
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];
2024-01-13 11:44:36 -05:00
if (newValue == ICECAP) {
2024-01-13 11:34:25 -05:00
this.done = true;
}
}
public dropWithin(tiles: number[]) {
if (tiles.length > 0) {
this.drop(tiles[this.rng() % tiles.length]);
}
}
public step() {
2024-01-13 11:44:36 -05:00
const lowlandTiles1 = this.floodSearch(
this.lobePos1,
(tile) => tile > WATER
);
const lowlandTiles2 = this.floodSearch(
this.lobePos2,
(tile) => tile > WATER
);
2024-01-13 11:34:25 -05:00
// grow shore
2024-01-13 11:44:36 -05:00
const shoreTiles1 = lowlandTiles1.filter((pos) => this.data[pos] == WATER);
2024-01-13 11:34:25 -05:00
this.dropWithin(shoreTiles1);
2024-01-13 11:44:36 -05:00
const shoreTiles2 = lowlandTiles2.filter((pos) => this.data[pos] == WATER);
2024-01-13 11:34:25 -05:00
this.dropWithin(shoreTiles2);
// seed beach
2024-01-13 11:44:36 -05:00
const beachTiles = lowlandTiles1.filter((pos) => this.data[pos] == BEACH);
2024-01-13 11:34:25 -05:00
this.dropWithin(beachTiles);
// expand forest
2024-01-13 11:44:36 -05:00
const forestLobe = this.floodSearch(this.lobePos1, (tile) => tile > BEACH);
const forestTiles = forestLobe.filter(
(pos) => this.data[pos] == LIGHT_FOREST
);
2024-01-13 11:34:25 -05:00
this.dropWithin(forestTiles);
this.dropWithin(forestTiles);
this.dropWithin(forestTiles);
// form mountain
2024-01-13 11:44:36 -05:00
const mountainTiles = this.floodSearch(
this.basePos,
(tile) => tile > MOUNTAIN
);
2024-01-13 11:34:25 -05:00
this.dropWithin(mountainTiles); // GENERATOR
// const erodePos = islandTiles[islands.rng() % islandTiles.length];
// islands.data[erodePos] = Math.max(islands.data[erodePos] - 1, 0);
}
}