2024.js/island/grid.ts

152 lines
3.8 KiB
TypeScript
Raw Normal View History

2024-01-13 11:34:25 -05:00
import { Prng, mulberry32 } from "../lib/prng";
2024-01-13 12:19:17 -05:00
import { BEACH, ICECAP } from "./data";
2024-01-13 12:55:17 -05:00
import {
ALL_ISLANDS,
BIG_ISLANDS,
LobeGenerator,
NO_ISLAND,
} from "./generators";
2024-01-13 11:34:25 -05:00
export class IslandGrid {
data: number[];
rng: Prng;
2024-01-13 12:10:37 -05:00
generators: LobeGenerator[] = [];
2024-01-13 11:34:25 -05:00
done = false;
constructor(public width: number, public height: number, seed: number) {
this.data = Array(width * height).fill(0);
this.rng = mulberry32(seed);
2024-01-13 12:55:17 -05:00
const islandBag = this.shuffle([
this.choose(BIG_ISLANDS),
this.choose(BIG_ISLANDS),
this.choose(ALL_ISLANDS),
this.choose(ALL_ISLANDS),
this.choose(ALL_ISLANDS),
this.choose(ALL_ISLANDS),
NO_ISLAND,
NO_ISLAND,
]);
const islandCount = islandBag.length;
const spacing = (Math.PI * 2) / islandCount;
for (let i = 0; i < islandCount; i++) {
const y = height / 2 + (Math.sin(spacing * i) * height) / 4;
const x = width / 2 + (Math.cos(spacing * i) * width) / 4;
console.log("xy", x | 0, y | 0);
this.generators.push(islandBag[i](this, this.xy(x | 0, y | 0)));
}
2024-01-13 11:34:25 -05:00
}
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;
}
}
2024-01-13 12:19:17 -05:00
public choose<T>(list: T[]) {
if (list.length == 0) {
throw new Error("Picking from empty list");
}
return list[this.rng() % list.length];
}
2024-01-13 12:55:17 -05:00
public shuffle<T>(list: T[]) {
const shuffled = list.slice();
for (let i = 0; i < shuffled.length - 1; i++) {
const swapIndex = (this.rng() % (shuffled.length - i)) + i;
[shuffled[i], shuffled[swapIndex]] = [shuffled[swapIndex], shuffled[i]];
}
return shuffled;
}
2024-01-13 11:34:25 -05:00
public dropWithin(tiles: number[]) {
if (tiles.length > 0) {
2024-01-13 12:19:17 -05:00
this.drop(this.choose(tiles));
2024-01-13 11:34:25 -05:00
}
}
public step() {
2024-01-13 12:10:37 -05:00
this.done = this.generators
.map((generator) => generator())
.every((done) => done);
2024-01-13 11:34:25 -05:00
}
}