From fd390ac486b58ee24e317ffca0f645fdba3a1957 Mon Sep 17 00:00:00 2001 From: Nischay-loq Date: Mon, 27 Oct 2025 22:29:40 +0530 Subject: [PATCH] Added path to exit. --- projects/maze/index.html | 9 +- projects/maze/main.js | 237 ++++++++++++++++++++++++++++++++++++++- projects/maze/styles.css | 36 ++++++ 3 files changed, 277 insertions(+), 5 deletions(-) diff --git a/projects/maze/index.html b/projects/maze/index.html index 75b35c2..fc024a8 100644 --- a/projects/maze/index.html +++ b/projects/maze/index.html @@ -10,8 +10,15 @@
-

Maze

+

Maze

+
+ + + Ready +
+

Contribute: generator, solver, keyboard navigation.

+

Instructions: Toggle "Draw to Exit Mode", then click/touch and drag across the maze to draw a path from the entrance (top-left) to the exit (bottom-right). Use Clear Path to reset.

diff --git a/projects/maze/main.js b/projects/maze/main.js index 8a1f5ad..3f2e196 100644 --- a/projects/maze/main.js +++ b/projects/maze/main.js @@ -1,4 +1,233 @@ -const c = document.getElementById('maze'); const ctx = c.getContext('2d'); -// TODO: implement maze generation and basic player movement -ctx.fillStyle = '#17171c'; ctx.fillRect(0, 0, c.width, c.height); -ctx.fillStyle = '#6ee7b7'; ctx.fillRect(8, 8, 24, 24); +const canvas = document.getElementById('maze'); +const ctx = canvas.getContext('2d'); + +// Maze configuration +const cols = 16; +const rows = 16; +const cellSize = Math.floor(canvas.width / cols); + +// grid: each cell has walls: top, right, bottom, left +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 = []; +for (let y = 0; y < rows; y++) { + const row = []; + for (let x = 0; x < cols; x++) row.push(new Cell(x, y)); + grid.push(row); +} + +function index(x, y) { + if (x < 0 || y < 0 || x >= cols || y >= rows) return null; + return grid[y][x]; +} + +// Recursive backtracker maze generator +function generateMaze() { + const stack = []; + const start = grid[0][0]; + start.visited = true; + stack.push(start); + + while (stack.length) { + const current = stack[stack.length - 1]; + const { x, y } = current; + const neighbors = []; + const dirs = [ [0,-1,'top','bottom'], [1,0,'right','left'], [0,1,'bottom','top'], [-1,0,'left','right'] ]; + for (const [dx,dy,wall,opp] of dirs) { + const n = index(x+dx, y+dy); + if (n && !n.visited) neighbors.push({cell:n,wall,opp}); + } + if (neighbors.length) { + const pick = neighbors[Math.floor(Math.random()*neighbors.length)]; + // remove wall between + current.walls[pick.wall] = false; + pick.cell.walls[pick.opp] = false; + pick.cell.visited = true; + stack.push(pick.cell); + } else { + stack.pop(); + } + } + // open entrance and exit + grid[0][0].walls.left = false; + grid[rows-1][cols-1].walls.right = false; +} + +function drawMaze() { + ctx.clearRect(0,0,canvas.width,canvas.height); + ctx.fillStyle = '#17171c'; + ctx.fillRect(0,0,canvas.width,canvas.height); + + ctx.strokeStyle = '#9aa3b3'; + ctx.lineWidth = 2; + for (let y=0;y= cols || cy >= rows) return null; + return grid[cy][cx]; +} + +function cellsAreNeighbors(a,b) { + const dx = b.x - a.x, dy = b.y - a.y; + if (dx === 1 && dy === 0) return ['right','left']; + if (dx === -1 && dy === 0) return ['left','right']; + if (dx === 0 && dy === 1) return ['bottom','top']; + if (dx === 0 && dy === -1) return ['top','bottom']; + return null; +} + +function pointerDown(e) { + if (!drawMode) return; + isDrawing = true; + canvas.setPointerCapture(e.pointerId); + const cell = cellFromEvent(e); + if (cell) { + pathCells = [cell]; + render(); + } +} + +function pointerMove(e) { + if (!isDrawing) return; + const cell = cellFromEvent(e); + if (!cell) return; + const last = pathCells[pathCells.length-1]; + if (!last || (last.x === cell.x && last.y === cell.y)) return; + + // Check if move is valid (adjacent and no wall between) + const neigh = cellsAreNeighbors(last, cell); + if (!neigh) return; // not adjacent, skip + const [fromSide, toSide] = neigh; + if (last.walls[fromSide] || cell.walls[toSide]) { + // Wall blocking, don't add this cell + return; + } + + pathCells.push(cell); + render(); +}function pointerUp(e) { + if (isDrawing) { + isDrawing = false; + tryValidatePath(); + } + try { canvas.releasePointerCapture(e.pointerId); } catch (err) {} +} + +function tryValidatePath() { + if (!pathCells.length) { setStatus('No path drawn'); return; } + const start = grid[0][0]; + const exit = grid[rows-1][cols-1]; + const first = pathCells[0]; + const last = pathCells[pathCells.length-1]; + if (first.x !== start.x || first.y !== start.y) { setStatus('Path must start at the entrance'); return; } + if (last.x !== exit.x || last.y !== exit.y) { setStatus('Path must end at the exit'); return; } + + // ensure each step moves to neighbor and there's no wall between + for (let i=0;i toggleDrawMode()); +drawToggle.addEventListener('keydown', (e)=>{ if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); toggleDrawMode(); }}); +clearBtn.addEventListener('click', clearPath); + +canvas.addEventListener('pointerdown', pointerDown); +canvas.addEventListener('pointermove', pointerMove); +window.addEventListener('pointerup', pointerUp); + +// initialize +generateMaze(); +drawMaze(); + +// expose for debugging +window._maze = { grid, render, clearPath, toggleDrawMode }; diff --git a/projects/maze/styles.css b/projects/maze/styles.css index a3ef54d..72bdbd7 100644 --- a/projects/maze/styles.css +++ b/projects/maze/styles.css @@ -19,6 +19,42 @@ canvas { border-radius: .5rem } +.controls { + display: flex; + gap: .5rem; + margin-bottom: .5rem; + align-items: center; +} + +.controls button { + background: #23232a; + color: #eef1f8; + border: 1px solid #37373f; + padding: .4rem .6rem; + border-radius: .35rem; + cursor: pointer; +} + +.controls button[aria-pressed="true"] { + background: linear-gradient(90deg,#2b6cb0,#2b9cf0); + color: #fff; + border-color: #1f6fb5; +} + +.status { + margin-left: .5rem; + color: #9fb4c8; + font-size: .95rem; +} + +.instructions { + color: #9aa3b3; + font-size: .9rem; + margin-top: .5rem; +} + +canvas { touch-action: none; } + .notes { color: #a6adbb; font-size: .9rem