mirror of
https://github.com/osmarks/random-stuff
synced 2024-11-09 13:59:55 +00:00
254 lines
9.5 KiB
HTML
254 lines
9.5 KiB
HTML
|
<style>
|
||
|
* { box-sizing: border-box;; }
|
||
|
#disp .cell {
|
||
|
width: 32px;
|
||
|
height: 32px;
|
||
|
display: inline-block;
|
||
|
font-family: monospace;
|
||
|
position: relative;
|
||
|
}
|
||
|
.cell img {
|
||
|
position: absolute;
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
top: 0;
|
||
|
left: 0;
|
||
|
image-rendering: crisp-edges;
|
||
|
image-rendering: pixelated;
|
||
|
}
|
||
|
.cell.selected {
|
||
|
background: yellow;
|
||
|
}
|
||
|
</style>
|
||
|
<div id="disp"></div>
|
||
|
<div id="controls">
|
||
|
<select id="interactmode">
|
||
|
<option selected>select</option>
|
||
|
<option>rotate</option>
|
||
|
<option>place drill</option>
|
||
|
<option>place belt</option>
|
||
|
<option>place inserter</option>
|
||
|
</select>
|
||
|
<button id="step">run step</button>
|
||
|
</div>
|
||
|
<script>
|
||
|
const noiseSeed = Math.random() * (2**32-1)
|
||
|
const hash = (str, seed = 0) => {
|
||
|
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed
|
||
|
for (let i = 0, ch; i < str.length; i++) {
|
||
|
ch = str.charCodeAt(i)
|
||
|
h1 = Math.imul(h1 ^ ch, 2654435761)
|
||
|
h2 = Math.imul(h2 ^ ch, 1597334677)
|
||
|
}
|
||
|
h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909)
|
||
|
h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909)
|
||
|
return 4294967296 * (2097151 & h2) + (h1>>>0)
|
||
|
}
|
||
|
|
||
|
|
||
|
const normalize = ([x, y]) => [x / Math.hypot(x, y), y / Math.hypot(x, y)]
|
||
|
const dot = ([a, b], [c, d]) => a*c+b*d
|
||
|
const cartesianProduct = (xs, ys) => xs.flatMap(x => ys.map(y => [x, y]))
|
||
|
|
||
|
const gradients = cartesianProduct([-1, -0.5, 0.5, 1], [-1, -0.5, 0.5, 1]).map(normalize)
|
||
|
const gradientFor = (x, y) => gradients[hash(x+"."+y, noiseSeed) % gradients.length]
|
||
|
|
||
|
const interpolate = (a0, a1, w) => (a1 - a0) * (3.0 - w * 2.0) * w * w + a0
|
||
|
const perlin = (x, y) => {
|
||
|
const i = Math.floor(x), j = Math.floor(y)
|
||
|
const u = x - i, v = y - j
|
||
|
const n00 = dot(gradientFor(i, j), [u, v])
|
||
|
const n01 = dot(gradientFor(i + 1, j), [u - 1, v])
|
||
|
const n10 = dot(gradientFor(i, j + 1), [u, v - 1])
|
||
|
const n11 = dot(gradientFor(i + 1, j + 1), [u - 1, v - 1])
|
||
|
return interpolate(interpolate(n00, n01, u), interpolate(n10, n11, u), v)
|
||
|
}
|
||
|
|
||
|
const disp = document.querySelector("#disp")
|
||
|
let interactMode = document.querySelector("#interactmode").value
|
||
|
document.querySelector("#interactmode").addEventListener("input", ev => {
|
||
|
interactMode = ev.target.value
|
||
|
console.log(interactMode)
|
||
|
})
|
||
|
const directions = {
|
||
|
0: [0, 1],
|
||
|
1: [-1, 0],
|
||
|
2: [0, -1],
|
||
|
3: [1, 0]
|
||
|
}
|
||
|
const arrows = "→↓←↑"
|
||
|
const grid = {}
|
||
|
const SIZE = 24
|
||
|
for (let x = 0; x < SIZE; x++) {
|
||
|
for (let y = 0; y < SIZE; y++) {
|
||
|
let thing = { type: "empty", direction: 0, x, y }
|
||
|
thing.mapNoise = perlin(x * 3 / SIZE, y * 3 / SIZE)
|
||
|
grid[x+"."+y] = thing
|
||
|
}
|
||
|
}
|
||
|
const noises = Object.values(grid).map(x => x.mapNoise)
|
||
|
noises.sort((a, b) => a - b)
|
||
|
const thresh1 = noises[Math.floor(noises.length * (6/7))]
|
||
|
const thresh2 = noises[Math.ceil(noises.length * (1/12))]
|
||
|
for (const cell of Object.values(grid)) {
|
||
|
if (cell.mapNoise > thresh1) {
|
||
|
cell.type = "ore1"
|
||
|
} else if (cell.mapNoise < thresh2) {
|
||
|
cell.type = "ore2"
|
||
|
}
|
||
|
}
|
||
|
const adjacentAt = (x, y, d) => {
|
||
|
const dir = directions[d]
|
||
|
const nx = x + dir[0], ny = y + dir[1]
|
||
|
return grid[nx+"."+ny]
|
||
|
}
|
||
|
let updateOrder
|
||
|
const itemHandlers = ["drill", "belt"]
|
||
|
const recomputeBelts = () => {
|
||
|
for (const cell of Object.values(grid)) {
|
||
|
delete cell.inext
|
||
|
delete cell.iprev
|
||
|
if (itemHandlers.includes(cell.structure)) {
|
||
|
cell.inext = adjacentAt(cell.x, cell.y, cell.direction)
|
||
|
if (cell.inext) cell.inext.iprev = cell
|
||
|
}
|
||
|
}
|
||
|
for (const cell of Object.values(grid)) {
|
||
|
if (cell.inext) {
|
||
|
if (!cell.iprev) { // root of tree thing
|
||
|
let order = 0
|
||
|
let visited = new Set()
|
||
|
let current = cell
|
||
|
while (current && !visited.has(current) && itemHandlers.includes(current.structure)) {
|
||
|
if (current.order === undefined || current.order < order) {
|
||
|
current.order = order
|
||
|
}
|
||
|
order++
|
||
|
visited.add(current)
|
||
|
current = current.inext
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
const iorder = []
|
||
|
for (const cell of Object.values(grid)) {
|
||
|
if (cell.order !== undefined) {
|
||
|
iorder[cell.order] ||= []
|
||
|
iorder[cell.order].push(cell)
|
||
|
}
|
||
|
}
|
||
|
updateOrder = [].concat(...iorder).reverse()
|
||
|
}
|
||
|
const update = () => {
|
||
|
recomputeBelts()
|
||
|
for (const cell of Object.values(grid)) {
|
||
|
if (cell.type === "inserter") {
|
||
|
|
||
|
}
|
||
|
}
|
||
|
for (const cell of updateOrder) {
|
||
|
if (cell.structure === "drill") {
|
||
|
cell.item = cell.type
|
||
|
}
|
||
|
if (cell.inext && !cell.inext.item) {
|
||
|
if (cell.item) {
|
||
|
cell.inext.item = cell.item
|
||
|
cell.item = null
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
render()
|
||
|
}
|
||
|
document.querySelector("#step").addEventListener("click", () => {
|
||
|
update()
|
||
|
})
|
||
|
const rotate = (d, e) => (d + e) % 4
|
||
|
const render = () => {
|
||
|
while (disp.firstChild) disp.removeChild(disp.firstChild)
|
||
|
for (let y = 0; y < SIZE; y++) {
|
||
|
for (let x = 0; x < SIZE; x++) {
|
||
|
const cell = document.createElement("span")
|
||
|
cell.id = x+"."+y
|
||
|
cell.classList.add("cell")
|
||
|
const thing = grid[x+"."+y]
|
||
|
const type = thing.type
|
||
|
const layers = []
|
||
|
layers.push("blank")
|
||
|
if (type === "empty") {
|
||
|
|
||
|
} else if (type.startsWith("ore")) {
|
||
|
layers.push({ sprite: type, rotation: hash(cell.id) % 4 })
|
||
|
if (thing.structure === "drill") {
|
||
|
layers.push("drill")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (thing.structure === "belt") {
|
||
|
const isInboundBelt = side => {
|
||
|
const newCell = adjacentAt(x, y, rotate(thing.direction, side))
|
||
|
return newCell && newCell.structure === "belt" && newCell.direction == rotate(thing.direction, rotate(side, 2))
|
||
|
}
|
||
|
const left = isInboundBelt(1), back = isInboundBelt(2), right = isInboundBelt(3)
|
||
|
if (left && !back) {
|
||
|
layers.push({ sprite: "belt-corner", reflect: true, rotation: 3 })
|
||
|
if (right) { layers.push("belt-conn") }
|
||
|
}
|
||
|
else if (right && !back) { layers.push({ sprite: "belt-corner", rotation: 1 }) }
|
||
|
else {
|
||
|
layers.push("belt")
|
||
|
if (right) { layers.push("belt-conn") }
|
||
|
if (left) { layers.push({ sprite: "belt-conn", reflect: true }) }
|
||
|
console.log(x, y, right, left)
|
||
|
}
|
||
|
if (thing.item) {
|
||
|
layers.push({ sprite: thing.item, scale: 0.5 })
|
||
|
}
|
||
|
}
|
||
|
if (thing.structure === "inserter") {
|
||
|
console.log("inserter")
|
||
|
layers.push("inserter-base")
|
||
|
layers.push("inserter-arm")
|
||
|
}
|
||
|
|
||
|
if (thing.selected) {
|
||
|
cell.classList.add("selected")
|
||
|
}
|
||
|
for (const layer of layers) {
|
||
|
const im = document.createElement("img")
|
||
|
im.src = `/sprites/${typeof layer === "string" ? layer : layer.sprite}.png`
|
||
|
const transforms = []
|
||
|
if (layer.rotation) { transforms.push(`rotate(${layer.rotation * 90}deg)`) }
|
||
|
if (layer.reflect) { transforms.push(`scaleX(-1)`) }
|
||
|
if (layer.scale) { transforms.push(`scale(${layer.scale})`) }
|
||
|
const transformString = transforms.join(" ")
|
||
|
if (transformString) { im.style.transform = transformString }
|
||
|
cell.appendChild(im)
|
||
|
}
|
||
|
cell.style.transform = `rotate(${thing.direction * 90}deg)`
|
||
|
disp.appendChild(cell)
|
||
|
}
|
||
|
disp.appendChild(document.createElement("br"))
|
||
|
}
|
||
|
}
|
||
|
disp.addEventListener("click", ev => {
|
||
|
const cell = ev.target.parentElement.id
|
||
|
console.log(grid[cell], cell)
|
||
|
if (interactMode === "select") {
|
||
|
grid[cell].selected ^= 1
|
||
|
} else if (interactMode === "place drill") {
|
||
|
if (grid[cell].type.startsWith("ore")) {
|
||
|
grid[cell].structure = "drill"
|
||
|
}
|
||
|
} else if (interactMode === "place belt") {
|
||
|
grid[cell].structure = "belt"
|
||
|
} else if (interactMode === "place inserter") {
|
||
|
grid[cell].structure = "inserter"
|
||
|
} else if (interactMode === "rotate") {
|
||
|
grid[cell].direction++
|
||
|
grid[cell].direction %= 4
|
||
|
}
|
||
|
ev.preventDefault()
|
||
|
render()
|
||
|
})
|
||
|
render()
|
||
|
</script>
|