Get occasional updates about new fonts, designs and other interesting things.
import sequence from "/scratchpad/_lib/sequence.asset.mjs"
import palette from "/scratchpad/_lib/palette.asset.mjs"
import posterize from "/scratchpad/posterize/posterize.asset.mjs"
import * as random from "/scratchpad/_lib/random.asset.mjs"
const dpi = window.devicePixelRatio || 1
const debug = false
const last = (arr) => arr[arr.length - 1]
const moves = {
  N: { r: -1, c: +1 },
  E: { r: +0, c: +1 },
  S: { r: +1, c: -1 },
  W: { r: +0, c: -1 },
  X: { r: -1, c: +0 },
  Y: { r: +1, c: +0 },
}
// Array of direction keys
const directions = Object.keys(moves)
const move = ({ r, c }, dir) => {
  const offset = moves[dir]
  return { r: r + offset.r, c: c + offset.c }
}
const getAvailableCells = (cell, cells) => {
  return directions.reduce((m, dir) => {
    const q = move(cell, dir)
    const p = cells.find((p) => p.r === q.r && p.c === q.c)
    if (p) m.push(p)
    return m
  }, [])
}
export default async (canvas) => {
  canvas.width = canvas.offsetWidth * dpi
  canvas.height = canvas.offsetHeight * dpi
  const ctx = canvas.getContext("2d")
  const chanceToBranch = random.number(0.05, 0.15)
  let cells = []
  const cellWidth = random.integer(20, 28) * dpi
  const cellHeight = random.integer(16, 24) * dpi
  const cols = Math.floor(canvas.width / cellWidth) - 4
  const rows = Math.floor(canvas.height / cellHeight) - 4
  let offset = [
    0.5 * (canvas.width - (cols - 1) * cellWidth),
    0.5 * (canvas.height - (rows - 1) * cellHeight),
  ]
  const lineWidth = cellHeight * 0.2
  return sequence([
    // Fill canvas
    () => {
      ctx.fillStyle = palette.dark
      ctx.beginPath()
      ctx.rect(0, 0, canvas.width, canvas.height)
      ctx.fill()
      ctx.lineCap = "round"
      ctx.lineJoin = "round"
    },
    // Create cells
    () => {
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          cells.push({
            r,
            c,
            i: cells.length + 1,
          })
        }
      }
      cells.forEach((p) => {
        p.x = offset[0] + p.c * cellWidth
        p.y = offset[1] + p.r * cellHeight
      })
    },
    // Label cells
    () => {
      if (!debug) return
      const fs = 6 * dpi
      ctx.font = `${fs}px sans`
      ctx.textAlign = "center"
      ctx.fillStyle = palette.canvas
      cells.forEach(({ i, x, y }) => ctx.fillText(i, x, y + fs * 0.4))
    },
    // Draw grid
    () => {
      ctx.beginPath()
      cells.forEach((cell) => {
        ctx.moveTo(cell.x + cellWidth * 0, cell.y - cellHeight / 2)
        ctx.lineTo(cell.x + cellWidth * 1, cell.y - cellHeight / 2)
        ctx.moveTo(cell.x + cellWidth * 1, cell.y - cellHeight * 0.5)
        ctx.lineTo(cell.x + cellWidth * 0, cell.y + cellHeight * 0.5)
        if (cell.c <= 0) {
          ctx.moveTo(cell.x - cellWidth * 0, cell.y - cellHeight * 0.5)
          ctx.lineTo(cell.x - cellWidth * 1, cell.y + cellHeight * 0.5)
        }
        if (cell.c <= 0 || cell.r === rows - 1) {
          ctx.moveTo(cell.x - cellWidth * 1, cell.y + cellHeight / 2)
          ctx.lineTo(cell.x - cellWidth * 0, cell.y + cellHeight / 2)
        }
      })
      ctx.lineWidth = lineWidth / 2
      ctx.strokeStyle = palette.canvas
      ctx.stroke()
    },
    () => {
      const stack = [cells[0]]
      const remaining = [cells]
      let running = true
      ctx.beginPath()
      const promise = new Promise((res) => {
        const explore = () => {
          if (!running) return
          let cell
          if (random.maybe(chanceToBranch)) {
            cell = random.sample(stack.slice(0, -1)) || stack[0]
          } else {
            cell = last(stack)
          }
          const available = getAvailableCells(cell, remaining)
          if (available.length) {
            const next = random.sample(available)
            ctx.moveTo(cell.x, cell.y)
            ctx.lineTo(next.x, next.y)
            // Remove from remaining available tiles
            remaining.splice(remaining.indexOf(next), 1)
            stack.push(next)
          } else {
            stack.splice(stack.indexOf(cell), 1)
          }
          if (stack.length > 0) {
            explore()
          } else {
            res()
          }
        }
        explore()
      }).then(() => {
        ctx.stroke()
      })
      return {
        promise,
        cancel: () => {
          running = false
        },
      }
    },
    () => {
      ctx.lineWidth = lineWidth * 2
      ctx.strokeStyle = palette.dark
      ctx.stroke()
      ctx.lineWidth = lineWidth
      ctx.strokeStyle = palette.canvas
      ctx.stroke()
    },
    () => {
      posterize({
        ctx,
        radius: 5,
        ramp: 40,
        overshoot: 0.75,
      })
    },
  ])
}