Last active
August 21, 2025 23:33
-
-
Save greggman/802656cfbbd28d751c110d17030e085c to your computer and use it in GitHub Desktop.
Tiny-ish maze code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:root { | |
color-scheme: light dark; | |
} | |
html, body { | |
margin: 0; | |
height: 100%; | |
display: flex; | |
flex-direction: column; | |
} | |
html, input { | |
font-family: monospace; | |
} | |
input { | |
width: 4em; | |
} | |
#ui, input { | |
display: inline-block; | |
border: none; | |
} | |
#maze { | |
flex: 1 1 auto; | |
min-height: 0; | |
} | |
#error { | |
display: inline-block; | |
font-weight: bold; | |
color: red; | |
} | |
canvas { | |
image-rendering: pixelated; | |
display: block; | |
width: 100%; | |
height: 100%; | |
background-color: black; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div> | |
<fieldset id="ui"> | |
<label>width:<input id="width" type="number" placeholder="width" value="91"></label> | |
<label>height:<input id="height" type="number" placeholder="height" value="49"></label> | |
<label>speed:<input id="speed" type="number" placeholdr="speed" value="25"></label> | |
</fieldset> | |
<button id="go" type="button">Go!</button> | |
<div id="error"></div> | |
</div> | |
<div id="maze"><canvas width="91" height="49"></canvas></div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// converted from https://archive.org/details/1981-12-compute-magazine/page/58/mode/2up | |
const kEdge = 0xFF0000FF; | |
const kWall = 0xFFFF0000; | |
const kHall = 0xFFFFFFFF; | |
const kMaxSize = 2048; | |
// random integer from 0 to max - 1 | |
const rnd = max => Math.random() * max | 0; | |
const uiElem = document.querySelector('#ui'); | |
const widthElem = document.querySelector('#width'); | |
const heightElem = document.querySelector('#height'); | |
const speedElem = document.querySelector('#speed'); | |
const goElem = document.querySelector('#go'); | |
const errorElem = document.querySelector('#error'); | |
const canvas = document.querySelector('canvas'); | |
let running = false; | |
goElem.addEventListener('click', go); | |
function getValue(elem, min, max, name) { | |
const width = parseInt(elem.value); | |
if (width >= min && width <= max) { | |
return width; | |
} | |
errorElem.textContent = `${name} must be >= ${min} and <= ${max}`; | |
return undefined; | |
} | |
async function go() { | |
if (running) { | |
running = false | |
} else { | |
running = true; | |
errorElem.textContent = ''; | |
const width = getValue(widthElem, 1, kMaxSize, 'width'); | |
const height = getValue(heightElem, 1, kMaxSize, 'height'); | |
const speed = getValue(speedElem, 0, kMaxSize, 'speed'); | |
if (width === undefined || height === undefined || speed === undefined) { | |
return; | |
} | |
canvas.width = width * 2 + 1; | |
canvas.height = height * 2 + 1; | |
goElem.textContent = 'Stop!'; | |
await generateMaze(canvas, speed); | |
goElem.textContent = 'Go!'; | |
running = false; | |
} | |
} | |
go(); | |
async function generateMaze(canvas, speed) { | |
const { width, height } = canvas; | |
if (width % 2 !== 1 || height % 2 !== 1) { | |
throw new Error(`canvas must be odd size`); | |
} | |
const ctx = canvas.getContext('2d'); | |
const imgData = new ImageData(width, height); | |
const data = new Uint32Array(imgData.data.buffer); | |
const show = (() => { | |
let count = 0; | |
return async function show(force) { | |
if (force || (speed > 0 && (count++) % speed === 0)) { | |
ctx.putImageData(imgData, 0, 0); | |
await new Promise(resolve => requestAnimationFrame(resolve)); | |
} | |
} | |
})(); | |
prep(data, width, height); | |
await makeMaze(data, width, height); | |
async function makeMaze(data, width, height) { | |
const dirOffset = [2, -width * 2, -2, width * 2]; | |
const startX = rnd((width - 1) / 2) * 2 + 1; | |
const startY = rnd((height - 1) / 2) * 2 + 1; | |
let offset = startY * width + startX; | |
data[offset] = 5; | |
while (true) { | |
// 220 | |
let dir = rnd(4); | |
let startDir = dir; | |
// 230 | |
while(true) { | |
if (!running) { | |
return; | |
} | |
let newOffset = offset + dirOffset[dir]; | |
if (data[newOffset] === kWall) { | |
data[newOffset] = dir + 1; | |
data[offset + dirOffset[dir] / 2] = kHall; | |
offset = newOffset; | |
await show() | |
break; | |
} else { | |
dir = (dir + 1) % 4; | |
if (dir !== startDir) { | |
continue; | |
} else { | |
dir = data[offset]; | |
data[offset] = kHall; | |
await show(); | |
if (dir < 5) { | |
offset = offset - dirOffset[dir - 1]; | |
break; | |
} else { | |
await show(true); | |
return; | |
} | |
} | |
} | |
} | |
} | |
} | |
function prep(data, width, height) { | |
data.fill(kWall); | |
data.fill(kEdge, 0, width); // Set top wall | |
const bottomOff = (height - 1) * width; | |
data.fill(kEdge, bottomOff, bottomOff + width); // Set the bottom wall | |
for (let y = 0; y < height - 1; ++y) { | |
data[y * width] = kEdge; // Set left wall | |
data[(y + 1) * width - 1] = kEdge; // Set right wall | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"name":"Tiny-ish maze code","settings":{},"filenames":["index.html","index.css","index.js"]} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment