133 lines
3.2 KiB
TypeScript
133 lines
3.2 KiB
TypeScript
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;
|
|
|
|
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(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.generators.push(
|
|
BEACH_LOBE(
|
|
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 dropWithin(tiles: number[]) {
|
|
if (tiles.length > 0) {
|
|
this.drop(tiles[this.rng() % tiles.length]);
|
|
}
|
|
}
|
|
|
|
public step() {
|
|
this.done = this.generators
|
|
.map((generator) => generator())
|
|
.every((done) => done);
|
|
}
|
|
}
|