Breakup files
This commit is contained in:
parent
9f4d96d710
commit
e57a376883
3 changed files with 195 additions and 189 deletions
191
island.ts
191
island.ts
|
@ -1,5 +1,6 @@
|
||||||
|
import { IslandGrid } from "./island/grid";
|
||||||
|
import { renderIslands } from "./island/render";
|
||||||
import { canvas2d, h } from "./lib/html";
|
import { canvas2d, h } from "./lib/html";
|
||||||
import { Prng, mulberry32 } from "./lib/prng";
|
|
||||||
|
|
||||||
const BLOWUP = 4;
|
const BLOWUP = 4;
|
||||||
const WIDTH = 240;
|
const WIDTH = 240;
|
||||||
|
@ -7,194 +8,6 @@ const HEIGHT = 135;
|
||||||
|
|
||||||
const DEFAULT_SEED = 128;
|
const DEFAULT_SEED = 128;
|
||||||
|
|
||||||
type Lookup2d = (x: number, y: number) => number;
|
|
||||||
function dim(width: number, height: number): Lookup2d {
|
|
||||||
return function xy(x: number, y: number) {
|
|
||||||
return (
|
|
||||||
(((x % width) + width) % width) +
|
|
||||||
width * (((y % height) + height) % height)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class IslandGrid {
|
|
||||||
data: number[];
|
|
||||||
rng: Prng;
|
|
||||||
|
|
||||||
xy: Lookup2d;
|
|
||||||
|
|
||||||
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.xy = dim(width, height);
|
|
||||||
|
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get(x: number, y: number): number {
|
|
||||||
return this.data[this.xy(x, y)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public set(x: number, y: number, tile: number) {
|
|
||||||
this.data[this.xy(x, y)] = tile;
|
|
||||||
console.log(x, y, this.xy(x, y), this.data[this.xy(x, y)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 == 9) {
|
|
||||||
this.done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public dropWithin(tiles: number[]) {
|
|
||||||
if (tiles.length > 0) {
|
|
||||||
this.drop(tiles[this.rng() % tiles.length]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public step() {
|
|
||||||
const lowlandTiles1 = this.floodSearch(this.lobePos1, (tile) => tile > 0);
|
|
||||||
const lowlandTiles2 = this.floodSearch(this.lobePos2, (tile) => tile > 0);
|
|
||||||
|
|
||||||
// grow shore
|
|
||||||
const shoreTiles1 = lowlandTiles1.filter((pos) => this.data[pos] == 0);
|
|
||||||
this.dropWithin(shoreTiles1);
|
|
||||||
const shoreTiles2 = lowlandTiles2.filter((pos) => this.data[pos] == 0);
|
|
||||||
this.dropWithin(shoreTiles2);
|
|
||||||
|
|
||||||
// seed beach
|
|
||||||
const beachTiles = lowlandTiles1.filter((pos) => this.data[pos] == 1);
|
|
||||||
this.dropWithin(beachTiles);
|
|
||||||
|
|
||||||
// expand forest
|
|
||||||
const forestLobe = this.floodSearch(this.lobePos1, (tile) => tile > 1);
|
|
||||||
const forestTiles = forestLobe.filter((pos) => this.data[pos] == 2);
|
|
||||||
this.dropWithin(forestTiles);
|
|
||||||
this.dropWithin(forestTiles);
|
|
||||||
this.dropWithin(forestTiles);
|
|
||||||
|
|
||||||
// form mountain
|
|
||||||
const mountainTiles = this.floodSearch(this.basePos, (tile) => tile > 4);
|
|
||||||
this.dropWithin(mountainTiles); // GENERATOR
|
|
||||||
|
|
||||||
// const erodePos = islandTiles[islands.rng() % islandTiles.length];
|
|
||||||
// islands.data[erodePos] = Math.max(islands.data[erodePos] - 1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderIslands(islands: IslandGrid, cx: CanvasRenderingContext2D) {
|
|
||||||
for (let y = 0; y < islands.height; y++) {
|
|
||||||
for (let x = 0; x < islands.width; x++) {
|
|
||||||
const tile = islands.data[islands.xy(x, y)];
|
|
||||||
switch (tile) {
|
|
||||||
case 0:
|
|
||||||
cx.fillStyle = "blue";
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
cx.fillStyle = "yellow";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
cx.fillStyle = "#00ff00";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
cx.fillStyle = "#008800";
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
case 5:
|
|
||||||
case 6:
|
|
||||||
case 7:
|
|
||||||
case 8:
|
|
||||||
cx.fillStyle = "#666666";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
cx.fillStyle = "#88aaff";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cx.fillRect(x, y, 1, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function IslandApplet() {
|
export function IslandApplet() {
|
||||||
// STATE
|
// STATE
|
||||||
|
|
||||||
|
|
156
island/grid.ts
Normal file
156
island/grid.ts
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
import { Prng, mulberry32 } from "../lib/prng";
|
||||||
|
|
||||||
|
type Lookup2d = (x: number, y: number) => number;
|
||||||
|
function dim(width: number, height: number): Lookup2d {
|
||||||
|
return function xy(x: number, y: number) {
|
||||||
|
return (
|
||||||
|
(((x % width) + width) % width) +
|
||||||
|
width * (((y % height) + height) % height)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IslandGrid {
|
||||||
|
data: number[];
|
||||||
|
rng: Prng;
|
||||||
|
|
||||||
|
xy: Lookup2d;
|
||||||
|
|
||||||
|
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.xy = dim(width, height);
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(x: number, y: number): number {
|
||||||
|
return this.data[this.xy(x, y)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public set(x: number, y: number, tile: number) {
|
||||||
|
this.data[this.xy(x, y)] = tile;
|
||||||
|
console.log(x, y, this.xy(x, y), this.data[this.xy(x, y)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == 9) {
|
||||||
|
this.done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public dropWithin(tiles: number[]) {
|
||||||
|
if (tiles.length > 0) {
|
||||||
|
this.drop(tiles[this.rng() % tiles.length]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public step() {
|
||||||
|
const lowlandTiles1 = this.floodSearch(this.lobePos1, (tile) => tile > 0);
|
||||||
|
const lowlandTiles2 = this.floodSearch(this.lobePos2, (tile) => tile > 0);
|
||||||
|
|
||||||
|
// grow shore
|
||||||
|
const shoreTiles1 = lowlandTiles1.filter((pos) => this.data[pos] == 0);
|
||||||
|
this.dropWithin(shoreTiles1);
|
||||||
|
const shoreTiles2 = lowlandTiles2.filter((pos) => this.data[pos] == 0);
|
||||||
|
this.dropWithin(shoreTiles2);
|
||||||
|
|
||||||
|
// seed beach
|
||||||
|
const beachTiles = lowlandTiles1.filter((pos) => this.data[pos] == 1);
|
||||||
|
this.dropWithin(beachTiles);
|
||||||
|
|
||||||
|
// expand forest
|
||||||
|
const forestLobe = this.floodSearch(this.lobePos1, (tile) => tile > 1);
|
||||||
|
const forestTiles = forestLobe.filter((pos) => this.data[pos] == 2);
|
||||||
|
this.dropWithin(forestTiles);
|
||||||
|
this.dropWithin(forestTiles);
|
||||||
|
this.dropWithin(forestTiles);
|
||||||
|
|
||||||
|
// form mountain
|
||||||
|
const mountainTiles = this.floodSearch(this.basePos, (tile) => tile > 4);
|
||||||
|
this.dropWithin(mountainTiles); // GENERATOR
|
||||||
|
|
||||||
|
// const erodePos = islandTiles[islands.rng() % islandTiles.length];
|
||||||
|
// islands.data[erodePos] = Math.max(islands.data[erodePos] - 1, 0);
|
||||||
|
}
|
||||||
|
}
|
37
island/render.ts
Normal file
37
island/render.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { IslandGrid } from "./grid";
|
||||||
|
|
||||||
|
export function renderIslands(
|
||||||
|
islands: IslandGrid,
|
||||||
|
cx: CanvasRenderingContext2D
|
||||||
|
) {
|
||||||
|
for (let y = 0; y < islands.height; y++) {
|
||||||
|
for (let x = 0; x < islands.width; x++) {
|
||||||
|
const tile = islands.data[islands.xy(x, y)];
|
||||||
|
switch (tile) {
|
||||||
|
case 0:
|
||||||
|
cx.fillStyle = "blue";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
cx.fillStyle = "yellow";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
cx.fillStyle = "#00ff00";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
cx.fillStyle = "#008800";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
cx.fillStyle = "#666666";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cx.fillStyle = "#88aaff";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cx.fillRect(x, y, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue