Skip to content

Instantly share code, notes, and snippets.

@taesiri
Last active December 28, 2024 22:58
Show Gist options
  • Save taesiri/401e85a386fa86057784333624c9bab6 to your computer and use it in GitHub Desktop.
Save taesiri/401e85a386fa86057784333624c9bab6 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<style>
.container {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding: 20px;
font-family: Arial, sans-serif;
}
.maze {
border: 2px solid #333;
cursor: pointer;
}
.controls {
display: flex;
flex-direction: column;
gap: 15px;
align-items: center;
}
.control-group {
display: flex;
gap: 15px;
align-items: center;
}
button {
padding: 8px 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
label {
font-weight: bold;
}
input[type="range"] {
width: 200px;
}
.value-display {
min-width: 30px;
text-align: left;
}
.position-controls {
display: flex;
gap: 20px;
margin-top: 10px;
}
.position-group {
display: flex;
flex-direction: column;
gap: 5px;
align-items: center;
}
.coordinate-inputs {
display: flex;
gap: 10px;
}
.coordinate-input {
width: 50px;
}
.instructions {
font-style: italic;
color: #666;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="container">
<h2>Random Maze Generator</h2>
<canvas id="mazeCanvas" class="maze"></canvas>
<div class="controls">
<div class="control-group">
<label>Width:</label>
<input type="range" id="widthSlider" min="5" max="40" value="20">
<span id="widthValue" class="value-display">20</span>
</div>
<div class="control-group">
<label>Height:</label>
<input type="range" id="heightSlider" min="5" max="40" value="20">
<span id="heightValue" class="value-display">20</span>
</div>
<div class="control-group">
<label>Complexity:</label>
<input type="range" id="complexitySlider" min="0" max="100" value="100">
<span id="complexityValue" class="value-display">100%</span>
</div>
<div class="position-controls">
<div class="position-group">
<label style="color: #4CAF50;">Start Position:</label>
<div class="coordinate-inputs">
<input type="number" id="startX" class="coordinate-input" min="0" value="0">
<input type="number" id="startY" class="coordinate-input" min="0" value="0">
</div>
</div>
<div class="position-group">
<label style="color: #f44336;">End Position:</label>
<div class="coordinate-inputs">
<input type="number" id="endX" class="coordinate-input" min="0" value="19">
<input type="number" id="endY" class="coordinate-input" min="0" value="19">
</div>
</div>
</div>
<p class="instructions">Click on the maze to set start (first click) and end (second click) positions</p>
<button onclick="generateNewMaze()">Generate New Maze</button>
</div>
</div>
<script>
const canvas = document.getElementById('mazeCanvas');
const ctx = canvas.getContext('2d');
const cellSize = 20;
let width, height, complexity;
let startPos = { x: 0, y: 0 };
let endPos = { x: 19, y: 19 };
let clickMode = 'none';
// Setup slider and input events
const widthSlider = document.getElementById('widthSlider');
const heightSlider = document.getElementById('heightSlider');
const complexitySlider = document.getElementById('complexitySlider');
const startXInput = document.getElementById('startX');
const startYInput = document.getElementById('startY');
const endXInput = document.getElementById('endX');
const endYInput = document.getElementById('endY');
function updateStartEndInputs() {
startXInput.value = startPos.x;
startYInput.value = startPos.y;
endXInput.value = endPos.x;
endYInput.value = endPos.y;
// Update max values
startXInput.max = width - 1;
startYInput.max = height - 1;
endXInput.max = width - 1;
endYInput.max = height - 1;
}
widthSlider.addEventListener('input', function() {
document.getElementById('widthValue').textContent = this.value;
updateCanvasSize();
});
heightSlider.addEventListener('input', function() {
document.getElementById('heightValue').textContent = this.value;
updateCanvasSize();
});
complexitySlider.addEventListener('input', function() {
document.getElementById('complexityValue').textContent = this.value + '%';
});
[startXInput, startYInput, endXInput, endYInput].forEach(input => {
input.addEventListener('change', function() {
const value = Math.min(Math.max(0, parseInt(this.value) || 0), parseInt(this.max));
this.value = value;
if (this.id.startsWith('start')) {
startPos.x = startXInput.value;
startPos.y = startYInput.value;
} else {
endPos.x = endXInput.value;
endPos.y = endYInput.value;
}
drawMaze();
});
});
canvas.addEventListener('click', function(event) {
const rect = canvas.getBoundingClientRect();
const x = Math.floor((event.clientX - rect.left) / cellSize);
const y = Math.floor((event.clientY - rect.top) / cellSize);
if (x >= 0 && x < width && y >= 0 && y < height) {
if (clickMode === 'none' || clickMode === 'end') {
startPos = { x, y };
clickMode = 'start';
} else {
endPos = { x, y };
clickMode = 'end';
}
updateStartEndInputs();
drawMaze();
}
});
function updateCanvasSize() {
width = parseInt(widthSlider.value);
height = parseInt(heightSlider.value);
complexity = parseInt(complexitySlider.value);
canvas.width = width * cellSize;
canvas.height = height * cellSize;
// Ensure start and end positions are within bounds
startPos.x = Math.min(startPos.x, width - 1);
startPos.y = Math.min(startPos.y, height - 1);
endPos.x = Math.min(endPos.x, width - 1);
endPos.y = Math.min(endPos.y, height - 1);
updateStartEndInputs();
}
let maze = [];
function initializeMaze() {
maze = [];
for (let y = 0; y < height; y++) {
const row = [];
for (let x = 0; x < width; x++) {
row.push({
x,
y,
walls: { top: true, right: true, bottom: true, left: true },
visited: false
});
}
maze.push(row);
}
}
function getNeighbors(cell, unvisitedOnly = true) {
const neighbors = [];
const { x, y } = cell;
if (y > 0) neighbors.push({ cell: maze[y-1][x], direction: 'top' });
if (x < width-1) neighbors.push({ cell: maze[y][x+1], direction: 'right' });
if (y < height-1) neighbors.push({ cell: maze[y+1][x], direction: 'bottom' });
if (x > 0) neighbors.push({ cell: maze[y][x-1], direction: 'left' });
return unvisitedOnly ? neighbors.filter(n => !n.cell.visited) : neighbors;
}
function removeWalls(current, next, direction) {
current.walls[direction] = false;
const oppositeWalls = {
top: 'bottom',
right: 'left',
bottom: 'top',
left: 'right'
};
next.walls[oppositeWalls[direction]] = false;
}
function generateMaze() {
initializeMaze();
// Start generation from the start position
const stack = [];
const start = maze[startPos.y][startPos.x];
start.visited = true;
stack.push(start);
while (stack.length > 0) {
const current = stack[stack.length - 1];
const neighbors = getNeighbors(current);
if (neighbors.length === 0) {
stack.pop();
continue;
}
const { cell: next, direction } = neighbors[Math.floor(Math.random() * neighbors.length)];
removeWalls(current, next, direction);
next.visited = true;
stack.push(next);
}
// Add additional paths based on complexity
const maxExtraPaths = Math.floor((width * height) * (1 - complexity / 100));
let addedPaths = 0;
while (addedPaths < maxExtraPaths) {
const x = Math.floor(Math.random() * width);
const y = Math.floor(Math.random() * height);
const cell = maze[y][x];
const neighbors = getNeighbors(cell, false);
if (neighbors.length > 0) {
const { cell: next, direction } = neighbors[Math.floor(Math.random() * neighbors.length)];
if (cell.walls[direction]) { // Only if wall exists
removeWalls(cell, next, direction);
addedPaths++;
}
}
}
}
function drawMaze() {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const cell = maze[y][x];
const startX = x * cellSize;
const startY = y * cellSize;
ctx.beginPath();
if (cell.walls.top) {
ctx.moveTo(startX, startY);
ctx.lineTo(startX + cellSize, startY);
}
if (cell.walls.right) {
ctx.moveTo(startX + cellSize, startY);
ctx.lineTo(startX + cellSize, startY + cellSize);
}
if (cell.walls.bottom) {
ctx.moveTo(startX, startY + cellSize);
ctx.lineTo(startX + cellSize, startY + cellSize);
}
if (cell.walls.left) {
ctx.moveTo(startX, startY);
ctx.lineTo(startX, startY + cellSize);
}
ctx.stroke();
}
}
// Draw start position (green)
ctx.fillStyle = '#4CAF50';
ctx.fillRect(
startPos.x * cellSize + 2,
startPos.y * cellSize + 2,
cellSize - 4,
cellSize - 4
);
// Draw end position (red)
ctx.fillStyle = '#f44336';
ctx.fillRect(
endPos.x * cellSize + 2,
endPos.y * cellSize + 2,
cellSize - 4,
cellSize - 4
);
}
function generateNewMaze() {
updateCanvasSize();
generateMaze();
drawMaze();
}
// Initial generation
generateNewMaze();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment