snapshot
This commit is contained in:
parent
3064dc4437
commit
87b4aca21f
1 changed files with 247 additions and 0 deletions
247
snapshots/2024-01-12-island.html
Normal file
247
snapshots/2024-01-12-island.html
Normal file
|
@ -0,0 +1,247 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>ProcGen Island</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
"use strict";
|
||||
(() => {
|
||||
// lib/html.ts
|
||||
function h(name, props, ...children) {
|
||||
const element = Object.assign(document.createElement(name), props);
|
||||
element.append(...children);
|
||||
return element;
|
||||
}
|
||||
function canvas2d(props) {
|
||||
const canvas = h("canvas", props);
|
||||
const cx = canvas.getContext("2d");
|
||||
if (!cx) {
|
||||
throw new Error("2D rendering context not supported");
|
||||
}
|
||||
return [canvas, cx];
|
||||
}
|
||||
|
||||
// lib/prng.ts
|
||||
function mulberry32(state) {
|
||||
return function () {
|
||||
let t = (state += 1831565813);
|
||||
t = Math.imul(t ^ (t >>> 15), t | 1);
|
||||
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
||||
return (t ^ (t >>> 14)) >>> 0;
|
||||
};
|
||||
}
|
||||
|
||||
// island.ts
|
||||
var BLOWUP = 4;
|
||||
var WIDTH = 240;
|
||||
var HEIGHT = 135;
|
||||
var DEFAULT_SEED = 128;
|
||||
function dim(width, height) {
|
||||
return function xy(x, y) {
|
||||
return (
|
||||
(((x % width) + width) % width) +
|
||||
width * (((y % height) + height) % height)
|
||||
);
|
||||
};
|
||||
}
|
||||
var IslandGrid = class {
|
||||
constructor(width, height, seed) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
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
|
||||
);
|
||||
}
|
||||
data;
|
||||
rng;
|
||||
xy;
|
||||
basePos;
|
||||
lobePos1;
|
||||
lobePos2;
|
||||
done = false;
|
||||
get(x, y) {
|
||||
return this.data[this.xy(x, y)];
|
||||
}
|
||||
set(x, y, tile) {
|
||||
this.data[this.xy(x, y)] = tile;
|
||||
console.log(x, y, this.xy(x, y), this.data[this.xy(x, y)]);
|
||||
}
|
||||
floodSearch(startPos, shouldExpand) {
|
||||
const len = this.data.length;
|
||||
const width = this.width;
|
||||
const seen = new Uint8Array(len);
|
||||
const hitPositions = [];
|
||||
function enqueue(pos) {
|
||||
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;
|
||||
}
|
||||
drop(pos) {
|
||||
const lowerNeighbors = [];
|
||||
const check = (adjPos) => {
|
||||
if (this.data[adjPos] < this.data[pos]) {
|
||||
lowerNeighbors.push(adjPos);
|
||||
}
|
||||
};
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
const newValue = ++this.data[pos];
|
||||
if (newValue == 9) {
|
||||
this.done = true;
|
||||
}
|
||||
}
|
||||
dropWithin(tiles) {
|
||||
if (tiles.length > 0) {
|
||||
this.drop(tiles[this.rng() % tiles.length]);
|
||||
}
|
||||
}
|
||||
step() {
|
||||
const lowlandTiles1 = this.floodSearch(
|
||||
this.lobePos1,
|
||||
(tile) => tile > 0
|
||||
);
|
||||
const lowlandTiles2 = this.floodSearch(
|
||||
this.lobePos2,
|
||||
(tile) => tile > 0
|
||||
);
|
||||
const shoreTiles1 = lowlandTiles1.filter(
|
||||
(pos) => this.data[pos] == 0
|
||||
);
|
||||
this.dropWithin(shoreTiles1);
|
||||
const shoreTiles2 = lowlandTiles2.filter(
|
||||
(pos) => this.data[pos] == 0
|
||||
);
|
||||
this.dropWithin(shoreTiles2);
|
||||
const beachTiles = lowlandTiles1.filter(
|
||||
(pos) => this.data[pos] == 1
|
||||
);
|
||||
this.dropWithin(beachTiles);
|
||||
const forestTiles = lowlandTiles1.filter(
|
||||
(pos) => this.data[pos] == 2
|
||||
);
|
||||
this.dropWithin(forestTiles);
|
||||
this.dropWithin(forestTiles);
|
||||
this.dropWithin(forestTiles);
|
||||
const mountainTiles = this.floodSearch(
|
||||
this.basePos,
|
||||
(tile) => tile > 4
|
||||
);
|
||||
this.dropWithin(mountainTiles);
|
||||
}
|
||||
};
|
||||
function renderIslands(islands, cx) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
function IslandApplet() {
|
||||
const [canvas, cx] = canvas2d({
|
||||
width: WIDTH * BLOWUP,
|
||||
height: HEIGHT * BLOWUP,
|
||||
});
|
||||
cx.scale(BLOWUP, BLOWUP);
|
||||
const seedInput = h("input", {
|
||||
type: "number",
|
||||
valueAsNumber: DEFAULT_SEED,
|
||||
});
|
||||
const seedLabel = h("label", {}, "Seed:", seedInput);
|
||||
let timerId;
|
||||
let islands = new IslandGrid(WIDTH, HEIGHT, DEFAULT_SEED);
|
||||
const generateButton = h(
|
||||
"button",
|
||||
{
|
||||
onclick: () => {
|
||||
clearInterval(timerId);
|
||||
islands = new IslandGrid(
|
||||
WIDTH,
|
||||
HEIGHT,
|
||||
seedInput.valueAsNumber
|
||||
);
|
||||
timerId = setInterval(function tick() {
|
||||
islands.step();
|
||||
islands.step();
|
||||
islands.step();
|
||||
if (islands.done) {
|
||||
clearInterval(timerId);
|
||||
}
|
||||
renderIslands(islands, cx);
|
||||
}, 1e3 / 30);
|
||||
},
|
||||
},
|
||||
"Generate"
|
||||
);
|
||||
renderIslands(islands, cx);
|
||||
return [canvas, seedLabel, generateButton];
|
||||
}
|
||||
globalThis.IslandApplet = IslandApplet;
|
||||
})();
|
||||
|
||||
document.body.append(...IslandApplet());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue