oneshotlm
Compare all models on this prompt →

Maze generator & solver · grok-build-0.1

Generate a random maze with the recursive-backtracker algorithm on a canvas, then animate an A-star or BFS solver finding the path from start to finish.

⛨ sandboxed Open in new tab ↗
transcript · 12 messages
user

Generate a random maze with the recursive-backtracker algorithm on a canvas, then animate an A-star or BFS solver finding the path from start to finish.

assistant
thinking
The task is: "Build a single-page web app and write it ALL into one self-contained file named index.html in the current directory: CSS in a <style> tag, JS in a <script> tag, inline — do not create se...The task is to build a single-page web app in one index.html file with CSS in <style>, JS in <script>, no externals. It should generate a random maze with recursive-backtracker on a canvas and animate A-star or BFS solver from start to finish.

I need to implement:

1. Maze generation 2D grid, recursive backtracker.

2. Pathfinding A* or BFS.

3. Animate on canvas.

4. UI controls: generate, solve, size, etc.

5. All in one HTML file.

I'll sketch the code structure.

HTML:

<!DOCTYPE html>

<html>

<head>

<title>Maze Solver 2D</title>

<style> /* CSS here */ </style>

</head>

<body>

<canvas id="mazeCanvas" width="500" height="500"></canvas>

<div id="controls">

<button id="generateBtn">Generate New Maze</button>

<button id="solveBtn">Solve Maze</button (A*)

<select id="gridSize">

<option value="10">10x10</option>

<option value="20">20x20</option>

<option value="30">30x30</option>

</select>

<label>Speed: <input type="range" id="speedSlider" min="0" max="100" value="50"></label>

<div id="status">Ready</div>

</div>

<script> /* JS here */ </script>

</body>

</html>

CSS: simple, body {font-family: Arial; text-align:center;} canvas {border:1px solid black; background:white;} controls {margin-top:10px;}

For JS:

const canvas = document.getElementById('mazeCanvas');

const ctx = canvas.getContext('2d');

let cellSize = 20; // base, adjust with size

let rows, cols;

let cells = [];

let start = {x:0 y:0};

let goal = {x:cols-1, y:rows-1};

let mazeGenerated = false;

let solving = false;

let speed = 50; // delay ms

let animationTimeout = null;

let path = [];

let openSet = [];

let cameFrom = new Map();

let gScore = new Map();

let fScore = new Map();

let visited = new Set();

let searchSteps = []; // for animation

// dirs

const dirs = [

  {dx:0,dy:-1,wall:0,opp:2}, //N

  {dx:1,dy: 0,wall:1,opp:3}, //E

  {dx:0,dy:1,wall:2,opp:0}, //S

  {dx:-1,dy:0,wall:3,opp:1} //W

];

function initCanvas() {

  canvas.width = cols * cellSize;

  canvas.height = rows * cellSize;

}

function generateMaze() {

  rows = parseInt(document.getElementById('gridSize').value);

  cols = rows; // square

  cellSize = Math.max(10, 500 / rows); // fit

  canvas width = cols * cellSize etc.

  cells = [];

  for(let y=0; y<rows; y++) {

    cells[y] = [];

    for(let x=0; x<cols; x++) {

      cells[y][x] = {x, y, walls: [true,true,true true], visited:false};

    }

  }

  // recursive backtracker iterative

  let stack = [];

  let current = cells[0][0];

  current.visited = true;

  stack.push(current);

  while(stack.length >0) {

    let neighbors = [];

    for(let d of dirs) {

 let nx = current.x + d.dx;

  let ny = current.y + d.dy;

  if(nx>=0 && nx<cols && ny>=0 && ny<rows && !cells[ny][nx].visited) {

    neighbors.push({cell: cells[ny][nx], dir: d});

  }

  }

  if(neighbors.length >0) {

    let nextInfo = neighbors[Math.floor(Math.random()*neighbors.length)];

    removeWall(current, nextInfo.cell, nextInfo.dir);

    current.visited = true;

    stack.push(current);

    current = nextInfo.cell;

  } else {

    current = stack.pop();

  }

  }

  mazeGenerated = true;

  resetSolver();

  draw();

  document.getElementById('status').innerText = 'Maze generated';

}

function removeWall(a, b, dirInfo) {

  let dx = b.x - a.x;

  let dy = b .y - a.y;

  if(dx ==1) { a.walls[1] =false; b.walls[3]=false; } //E W

  else if(dx==-1) { a.walls[3]=false; b.walls[1]=false; }

  else if(d y==1) { a.walls[2]=false; b.walls[0]=false; }

  else if(dy==-1) { a.walls[0]=false; b.walls[2]=false; }

}

function draw() {

  ctx.clearRect(0,0,canvas.width ,canvas.height);

  ctx.fillStyle = '#ffffff';

  ctx.fillRect(0,0,canvas.width,canvas.height);

  // draw walls

  ctx.strokeStyle = '#000000';

  ctx.lineWidth = 2;

  for(let y=0; y<rows; y++) for(let x=0; x<cols; x++) {

    let cell = cells[y][x];

    let px = x*cellSize;

    let py = y*cellSize;

    if(cell.walls[0]) { ctx.beginPath(); ctx.moveTo(px,py); ctx.lineTo(px+cell ize,py); ctx.stroke(); }

    if(cell.walls[1]) { ctx.beginPath(); ctx.moveTo(px+cellSize,py); ctx.lineTo(px+cellSize,py+cellSize); ctx.stroke(); }

    if(cell.walls[2]) { ctx.beginPath(); ctx.moveTo (px,py+cellSize); ctx.lineTo(px+cellSize,py+cellSize); ctx.stroke(); }

    if(cell.walls[3]) { ctx.beginPath(); ctx.moveTo(px,py); ctx.lineTo(px,py+cellSize); ctx.stroke(); }

  }

  // draw start goal

  ctx.fillStyle = '#00ff00';

  ctx.beginPath();

  ctx.arc(start.x*cellSize + cellSize/2, start.y*cellSize + cellSize/2, cellSize/4,0,Math.PI*2);

  ctx.fill();

  ctx.fillStyle = '#ff0000';

  ctx.beginPath();

  ctx.arc(goal.x*cellSize + cellSize/2, goal.y*cellSize + cellSize/2, cellSize/4,0,Math.PI*2);

  ctx.fill();

  // if solving, draw visited etc

  if(s olving) {

    for(let step of searchSteps.slice(0, currentStep)) {

      ctx.fillStyle = '#ffff00';

      ctx.fillRect(step.x*cellSize, step.y*cellSize, cellSize, cellSize);

    }

  }

}

function resetSolver() {

  openSet = [cells[0][0]];

  cameFrom = new Map();

  gScore = new Map();

  fScore = new Map();

  visited = new Set();

  searchSteps = [];

  gScore.set(key(start),0);

  fScore.set(key(start), manhattan(start,goal));

  came From.set(key(start),null);

}

function key(c) { return c.x + ',' + c.y; }

function manhattan(a,b) { return Math.abs(a.x-b.x) + Math.abs(a.y-b.y); }

function solve() {

  if(!mazeGenerated) return;

  solving = true ;

  currentStep = 0;

  searchSteps = [];

  resetSolver();

  let open = [cells[0][0]];

  while(open.length >0) {

    let curr = open[0];

    for(let i=1; i<open.length; i++) if(fScore.get(key (open[i])) < fScore.get(key(curr))) curr = open[i];

    open.splice(open.indexOf(curr),1);

    searchSteps.push(curr);

    if(curr.x == goal.x && curr.y == goal.y) break;

    let neighs = getNeigh(curr);

    for(let n of neigh s) {

      let tent = gScore.get(key(curr)) +1;

      if(!gScore.has(key(n)) || tent < gScore.get(key(n))) {

        cameFrom.set(key(n), key(curr));

        gScore.set(key(n),tent);

        fScore.set(key(n),tent + manh (n,goal));

        if(!open.includes(n)) open.push(n);

      }

    }

  }

  // reconstruct path

  path = [];

  let currK = key(goal);

  while(currK) {

    let [x,y] = currK.split(',').map(Number);

    path.unshift ({x,y});

    currK = cameFrom.get(currK);

  }

  solving = false;

  draw();

  document.getElementById('status').
bash {"command":"ls -la /tmp/ws"}
toolResult

total 0 drwxr-sr-x. 2 node node 6 Aug 1 20:18 . drwxrwsrwx. 3 root node 16 Aug 1 20:18 ..

assistant
thinking
The task is: "Generate a random maze with the recursive-backtracker algorithm on a canvas, then animate an A-star or BFS solver finding the path from start to finish."
First, I need to implement a recursive backtracker maze generator in JavaScript that runs on a canvas.

I'll represent the maze as a grid of cells, each with walls.

For the canvas, I'll draw the maze with lines for walls.

Then, for solving: BFS or A* to find path from start to end 0,0 to rows-1,cols-1.

Animate by highlighting cells as visited in order.

Use setTimeout for animation speed.

Make it self-contained: all JS in <script>, CSS in <style>.

Let's sketch the code.

HTML structure:

<!DOCTYPE html>
<html>
<head>
<title>Maze Generator 2</title>
<style>
body { font-family: Arial; text-align: center; }
canvas { border: 1px solid black; }
.controls { margin: 10px; }
button, input { margin: 5px; }
</style>
</head>
<body>
<h1>Maze Generator & 2D Solver</h1>
<div class="controls">
  <label>Rows: <input type="number" id="rows" min="5" max="50" value="20"></label>
  <label>Cols: <input type="number" id="cols" min="5" max=" 50" value="20"></label>
  <label>Solver: <select id="solver">
    <option value="bfs">BFS</option>
    <option value="astar">A*</option>
  </select></label>
  <label>Speed: <input type="range" id="speed " min="1" max="10" value="5"></label>
  <button id="gen">Generate Maze</button>
  <button id="solve">Solve</button>
</div>
<canvas id="mazeCanvas" width="600" height="600"></canvas>
<script>
// JS code here </script>
</body>
</html>

Now, in script:

const canvas = document.getElementById('mazeCanvas');
const ctx = canvas.getContext('2d');
let rows = 20;
let cols = 20;
let cellSize = 600 / Math.max(rows,cols); wait, 600 / rows if square? No, make canvas size based on rows cols.

Better: set canvas width = cols * cellSize, but to fixed, let's calculate cellSize = 25, canvas width = cols*25, height = rows*25, but to make it fit, use fixed 600, cell 600/max.

For simplicity, set cellSize = 25, and set canvas width = cols*25, height = rows*25, but since rows cols variable, need to update canvas size.

In code, when gen, set canvas.width = cols * cellSize; etc.

Let's set cellSize = 25 fixed, and if too big, but for max 50, 1250 too big, so use dynamic cellSize.

let cellSize;

function resizeCanvas() {

  cellSize = Math.min(600/rows, 600/cols);

  canvas.width = cols * cellSize;

  canvas.height = rows * cellSize;

}

Call when change rows cols.

But for simplicity, default 20x20, cellSize=30, canvas 600x600.

I'll fix to 20x20 for now, later add sliders.

No, use inputs, and resize.

Let's write code.

First, class Cell {

  constructor(x, y) {

     this.x = x;

     this.y = y;

     this.walls = {

       top: true,

       right: true,

       bottom: true,

       left: true

     };

     this.visited = false;

   }

 }

let grid = [];

function initGrid() {

  grid = [];

  for (let y = 0; y < rows; y++) {

    let row = [];

    for (let x = 0; x < cols; x++) {

      row.push(new Cell(x, y));

    }

    grid.push(row);

  }

 }

function getUn visitedNeighbors(cell) {

  let neighbors = [];

  const {x, y} = cell;

  const neighborsList = [

    {dx:0, dy:-1, wall:'top', opp:'bottom'},

    {dx:1, dy:0, wall:'right', opp:'left'},

 {dx:0, dy:1, wall:'bottom', opp:'top'},

    {dx:-1, dy:0, wall:'left', opp:'right'}

  ];

  for (let dir of neighborsList) {

    let nx = x + dir.dx;

    let ny = y + dir .dy;

    if (nx >=0 && nx <cols && ny >=0 && ny <rows && !grid[ny][nx].visited) {

      neighbors.push(grid[ny][nx]);

    }

  }

  return neighbors;

 }

function removeWalls(current, next) {

  let dx = next.x - current.x;

  let dy = next.y - current.y;

  if (dx === 1) {

    current.walls.right = false;

    next.walls.left = false;

  } else if (dx === -1) {

    current.walls.left = false;

    next.walls.right = false;

  } else if (dy === 1) {

    current.walls.bottom = false;

    next.walls.top = false;

  } else if (dy === -1) {

    current.walls.top = false;

    next.walls.bottom = false;

  }

 }

function recursiveBacktracker() {

  initGrid();

  let stack = [];

  let current = grid[0][0];

  current.visited = true;

  while (true) {

    let neighbors = getUnvisitedNeighbors(current);

    if (neighbors.length > 0) {

      let next = neighbors[Math.floor(Math.random() * neighbors.length)];

      removeWalls(current, next);

      stack.push(current);

      current = next;

      current.visited = true;

    } else if (stack.length > 0) {

      current = stack.pop();

    } else {

      break;

    }

  }

 }

function drawMaze() {

  ctx .clearRect(0,0,canvas.width,canvas.height);

  let cs = cellSize;

  // draw cells

  for (let y = 0; y < rows; y++) {

    for (let x = 0; x < cols; x++) {

      let cell = grid[y ][x];

      let px = x * cs;

      let py = y * cs;

      // draw walls

      ctx.strokeStyle = 'black';

      ctx.lineWidth = 2;

      ctx.beginPath();

      if (cell.walls.top) ctx.moveTo(px, py) , ctx.lineTo (px + cs, py);

      if (cell.walls.right) ctx.moveTo(px + cs, py) , ctx.lineTo (px + cs, py + cs);

      if (cell.walls.bottom) ctx.moveTo(px, py + cs) , ctx.lineTo (px + cs, py + cs);

      if (cell.walls.left) ctx.moveTo(px, py) , ctx.lineTo (px, py + cs);

      ctx.stroke();

    }

  }

  // start and end

  ctx.fillStyle = 'green';

  ctx.fillRect(0,0,cs,cs);

 ctx.fillStyle = 'red';

  ctx.fillRect((cols-1)*cs, (rows-1)*cs, cs, cs);

 }

function animateSolve() {

  // first compute path

  let path = findPath();

  if (!path.length) return;

  // animate

  let i = 0;

  let interval = setInterval(() => {

    if (i < path.length) {

      let cell = path[i];

      let px = cell.x * cellSize;

      let py = cell.y * cellSize;

      ctx.fillStyle = 'yellow';

      ctx.fillRect(px, py, cell Size, cellSize);

      i++;

    } else {

      clearInterval(interval);

    }

  }, parseInt(document.getElementById('speed').value) * 100);

 }

function findPath() {

  // implement BFS or A*

  // return array of cells in path

  // for BFS

 let queue = [];

  let visited = new Set();

  let cameFrom = new Map();

  let start = {x:0, y:0};

  queue.push(start);

  visited.add('0,0');

  cameFrom.set('0,0', null);

  let goal = {x:cols -1, y:rows-1};

  let found = false;

  while (queue.length > 0) {

    let curr = queue.shift();

    if (curr.x === goal.x && curr.y === goal.y) {

      found = true;

      break;

    }

    let dirs = [[0 ,-1], [1,0], [0,1], [-1,0]];

    for (let [dx, dy] of dirs) {

      let nx = curr.x + dx;

      let ny = curr.y + dy;

      if (nx >=0 && nx <cols && ny >= 0 && ny <rows) {

        let cell = grid[ny][curr.y wait curr.y ? grid[ny][nx];

        // check if wall

        // to check wall, need to see if adjacent wall

        // for example if dx=1, if cell.walls.left ? No, from curr to next, if curr.walls.right or next.walls.left

        if ( (dx ===1 && curr.walls.right) || (dx === -1 && curr.walls.left) || (dy ===1 && curr.walls.bottom) || (dy === -1 && curr.walls.top) ) continue;

        let key = `${nx},${ny}`;

        if (!visited.has(key)) {

          visited.add(key);

          queue.push({x:nx, y:ny});

          cameFrom.
write /tmp/ws/index.html (18013 bytes)
toolResult

Successfully wrote 18013 bytes to /tmp/ws/index.html

assistant
thinking
The file is created. I need to verify it works correctly by testing it. Since it's a self-contained HTML, I can use bash to check if the file exists and perhaps use a simple way to validate it, but th...
bash {"command":"ls -la /tmp/ws/index.html && head -c 200 /tmp/ws/index.html && echo -e \"\\n... [file truncated for display]\" && tail -c 100 /t
toolResult

-rw-r--r--. 1 node node 18035 Aug 1 20:21 /tmp/ws/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Maze • Recursive Backtracker + Solver</title> <st ... [file truncated for display] ion.visitedOrder.length}`); } // Boot window.onload = init; </script> </body> </html>

assistant
thinking
The file has been successfully created. It's a single self-contained HTML file with embedded CSS and JS. No external dependencies.
bash {"command":"cd /tmp/ws && wc -l index.html && echo \"---\" && grep -c \"recursive backtracker\\|solveAStar\\|solveBFS\\|getUnvisitedNeighbor
toolResult

624 index.html --- 7

assistant
thinking
The file is created successfully. It's a complete single-file HTML app with:
bash {"command":"test -f /tmp/ws/index.html && echo \"✓ index.html present and ready\" || echo \"✗ missing\""}
toolResult

✓ index.html present and ready

assistant
thinking
The file has been created successfully. It's a fully self-contained single HTML file with:

Done! A complete single-file `index.html` has been created at `/tmp/ws/index.html`. **Features:** - **Recursive backtracker** maze generation (standard stack-based implementation) - **A*** (Manhattan heuristic) or **BFS** solver, selectable at runtime - Canvas rendering at 600×600 px (adjustable grid 5×5–40×40) - **Animated** step-by-step pathfinding with controllable speed - **Instant** solution mode (shows full explored set + final path immediately) - Start (green/S) and End (red/E) markers - Visual legend for explored cells vs. final path - Stop button + keyboard shortcuts (G/S/I/Esc) Open `index.html` directly in any browser — no network, no external files.

TAKE IT TO CODE

A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.

RUN

modelgrok-build-0.1
vendorxAI · Grok
harnesspi-agent
generated2026-08-01

EVALUATION

4/5 working

Recursive backtracker maze generates correctly and A* solver animates blue explored cells, though pointer inputs don't trigger it, keys do.

Vision grade · anthropic/claude-sonnet-5

MEASUREMENTS

Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.

idle motion0.0%
on click0.0%
on drag0.0%
on wheel0.0%
on enter + space27.7%
on arrow keys27.8%
on w/a/s/d5.8%
frame spread23.2 / 255
console errors0
js errors none

METRICS

runtime152.5s
tokens in6.4k
tokens out27k
cached25k
cost / run$0.0655