{"spec_id":"maze-printable","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// maze-printable: Printable Maze Puzzle\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-05\n\n//# anyplot-orientation: square\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Maze generation: recursive-backtracker DFS on a grid graph ------------\n// A DFS spanning tree over the cell graph guarantees exactly one path\n// between any two cells (a \"perfect\" maze) — no loops, no isolated pockets.\nconst COLS = 20;\nconst ROWS = 20;\n\n// Tiny fixed-seed LCG — the browser has no seeded RNG.\nfunction makeRng(seed) {\n  let state = seed >>> 0;\n  return function () {\n    state = (Math.imul(state, 1103515245) + 12345) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst rng = makeRng(42);\n\nfunction shuffle(arr) {\n  for (let i = arr.length - 1; i > 0; i--) {\n    const j = Math.floor(rng() * (i + 1));\n    [arr[i], arr[j]] = [arr[j], arr[i]];\n  }\n  return arr;\n}\n\nconst visited = new Array(COLS * ROWS).fill(false);\nconst walls = Array.from({ length: COLS * ROWS }, () => ({\n  N: true,\n  S: true,\n  E: true,\n  W: true,\n}));\n\nconst stack = [[0, 0]];\nvisited[0] = true;\nwhile (stack.length > 0) {\n  const [col, row] = stack[stack.length - 1];\n  const candidates = shuffle([\n    [col, row - 1, \"N\", \"S\"],\n    [col, row + 1, \"S\", \"N\"],\n    [col - 1, row, \"W\", \"E\"],\n    [col + 1, row, \"E\", \"W\"],\n  ]).filter(\n    ([nc, nr]) => nc >= 0 && nc < COLS && nr >= 0 && nr < ROWS && !visited[nr * COLS + nc],\n  );\n\n  if (candidates.length === 0) {\n    stack.pop();\n    continue;\n  }\n  const [nextCol, nextRow, dir, opposite] = candidates[0];\n  walls[row * COLS + col][dir] = false;\n  walls[nextRow * COLS + nextCol][opposite] = false;\n  visited[nextRow * COLS + nextCol] = true;\n  stack.push([nextCol, nextRow]);\n}\n\n// --- Wall segments: remaining walls as (x1,y1)-(x2,y2) data-space lines ----\n// Cell (col, row) occupies x in [col, col+1], y in [ROWS-row-1, ROWS-row] so\n// row 0 (the start row) renders at the top of the chart.\nconst wallLines = [];\nfor (let row = 0; row < ROWS; row++) {\n  for (let col = 0; col < COLS; col++) {\n    const cell = walls[row * COLS + col];\n    if (cell.N) wallLines.push([col, ROWS - row, col + 1, ROWS - row]);\n    if (cell.W) wallLines.push([col, ROWS - row - 1, col, ROWS - row]);\n  }\n}\nfor (let col = 0; col < COLS; col++) {\n  if (walls[(ROWS - 1) * COLS + col].S) wallLines.push([col, 0, col + 1, 0]);\n}\nfor (let row = 0; row < ROWS; row++) {\n  if (walls[row * COLS + (COLS - 1)].E) wallLines.push([COLS, ROWS - row - 1, COLS, ROWS - row]);\n}\n\n// Start (top-left cell) and goal (bottom-right cell), at cell centers.\nconst startPoint = [{ x: 0.5, y: ROWS - 0.5 }];\nconst goalPoint = [{ x: COLS - 0.5, y: 0.5 }];\n\n// --- Wall plugin: draws the maze directly on the canvas 2D context, mapping\n// data-space coordinates through the chart's own linear scales. This is a\n// Chart.js-native technique (a plugin hooking chart lifecycle + scale API)\n// rather than a portable point/line-dataset trick.\nconst mazeWallsPlugin = {\n  id: \"mazeWalls\",\n  beforeDatasetsDraw(chart) {\n    const { ctx, scales } = chart;\n    ctx.save();\n    ctx.strokeStyle = t.ink;\n    ctx.lineWidth = 9;\n    ctx.lineCap = \"square\";\n    ctx.lineJoin = \"miter\";\n    ctx.beginPath();\n    for (const [x1, y1, x2, y2] of wallLines) {\n      ctx.moveTo(scales.x.getPixelForValue(x1), scales.y.getPixelForValue(y1));\n      ctx.lineTo(scales.x.getPixelForValue(x2), scales.y.getPixelForValue(y2));\n    }\n    ctx.stroke();\n    ctx.restore();\n  },\n};\n\n// --- Mount -------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart -----------------------------------------------------------------\nconst title = \"maze-printable · javascript · chartjs · anyplot.ai\";\n\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    datasets: [\n      {\n        label: \"Start\",\n        data: startPoint,\n        showLine: false,\n        pointStyle: \"triangle\",\n        pointRadius: 26,\n        backgroundColor: t.palette[0],\n        borderColor: t.palette[0],\n      },\n      {\n        label: \"Goal\",\n        data: goalPoint,\n        showLine: false,\n        pointStyle: \"star\",\n        pointRadius: 30,\n        backgroundColor: t.palette[1],\n        borderColor: t.palette[1],\n      },\n    ],\n  },\n  plugins: [mazeWallsPlugin],\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: 24 },\n    plugins: {\n      title: { display: true, text: title, color: t.ink, font: { size: 22 } },\n      legend: {\n        display: true,\n        position: \"bottom\",\n        labels: { color: t.ink, font: { size: 16 }, usePointStyle: true },\n      },\n    },\n    scales: {\n      x: { type: \"linear\", min: 0, max: COLS, display: false },\n      y: { type: \"linear\", min: 0, max: ROWS, display: false },\n    },\n  },\n});\n"}