Custom Code Experiment •
Maze: A* Algorithm
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Maze - A* Algorithm</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000000;
color: #e2e8f0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
padding: 16px;
}
.container {
max-width: 1200px;
margin: 0 auto;
width: 100%;
flex: 1;
display: flex;
flex-direction: column;
}
.header {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.controls {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.btn {
background: #1e293b;
border: 1px solid #334155;
color: #e2e8f0;
padding: 10px 16px;
font-family: inherit;
font-size: 14px;
font-weight: 500;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s;
}
.btn:hover {
background: #334155;
border-color: #475569;
}
.btn:active {
transform: scale(0.98);
}
.btn-primary {
background: #3b82f6;
border-color: #3b82f6;
color: white;
}
.btn-primary:hover {
background: #2563eb;
}
.status-bar {
background: #1a1a1a;
border-radius: 6px;
padding: 12px 16px;
margin-bottom: 20px;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.status {
color: #38bdf8;
font-weight: 500;
}
.stats {
display: flex;
gap: 16px;
}
.stat {
display: flex;
gap: 6px;
align-items: center;
}
.stat-label {
color: #94a3b8;
font-size: 12px;
}
.stat-value {
color: #e2e8f0;
font-weight: 500;
font-size: 14px;
}
.maze-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
background: #1a1a1a;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
overflow: hidden;
min-height: 500px;
}
.maze {
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
}
@media (max-width: 768px) {
body {
padding: 12px 16px;
}
.header {
justify-content: center;
margin-bottom: 15px;
}
.controls {
width: 100%;
gap: 8px;
}
.btn {
flex: 1;
min-width: 110px;
padding: 8px 12px;
font-size: 13px;
}
.status-bar {
flex-direction: column;
align-items: flex-start;
margin-bottom: 15px;
padding: 10px 12px;
}
.stats {
width: 100%;
justify-content: space-between;
gap: 12px;
}
.maze-container {
padding: 12px 15px;
margin-bottom: 15px;
min-height: 400px;
}
.stat-label, .stat-value {
font-size: 11px;
}
}
@media (max-width: 480px) {
body {
padding: 8px 12px;
}
.header {
margin-bottom: 12px;
}
.controls {
gap: 6px;
}
.btn {
min-width: 100px;
padding: 6px 10px;
font-size: 12px;
}
.status-bar {
margin-bottom: 12px;
padding: 8px 10px;
}
.maze-container {
padding: 8px 10px;
margin-bottom: 12px;
min-height: 350px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="controls">
<button class="btn btn-primary" onclick="generateMaze()">Generate Maze</button>
<button class="btn" onclick="solveMaze()">Find Path</button>
<button class="btn" onclick="clearSolution()">Clear Path</button>
<button class="btn" onclick="resetMaze()">Reset</button>
</div>
</div>
<div class="status-bar">
<div class="status" id="status">Ready - Generate a maze to begin</div>
<div class="stats">
<div class="stat">
<div class="stat-label">Size:</div>
<div class="stat-value" id="dimensions">100×100</div>
</div>
<div class="stat">
<div class="stat-label">Generated:</div>
<div class="stat-value" id="genTime">0.00s</div>
</div>
<div class="stat">
<div class="stat-label">Path:</div>
<div class="stat-value" id="solutionLength">-</div>
</div>
<div class="stat">
<div class="stat-label">Solved:</div>
<div class="stat-value" id="solveTime">0.00s</div>
</div>
</div>
</div>
<div class="maze-container">
<canvas id="mazeCanvas" class="maze"></canvas>
</div>
</div>
<script>
let gameState = {
maze: [],
solution: [],
isAnimating: false
};
const canvas = document.getElementById("mazeCanvas");
const ctx = canvas.getContext("2d");
const MAZE_SIZE = 100;
let CELL_SIZE = 0;
function calculateOptimalSize() {
const container = document.querySelector('.maze-container');
const containerWidth = container.clientWidth - 40;
const containerHeight = container.clientHeight - 40;
const maxCellSizeByWidth = Math.floor(containerWidth / MAZE_SIZE);
const maxCellSizeByHeight = Math.floor(containerHeight / MAZE_SIZE);
return Math.max(2, Math.min(maxCellSizeByWidth, maxCellSizeByHeight));
}
function resizeCanvas() {
CELL_SIZE = calculateOptimalSize();
canvas.width = MAZE_SIZE * CELL_SIZE;
canvas.height = MAZE_SIZE * CELL_SIZE;
if (gameState.maze && gameState.maze.length > 0) {
drawMaze();
}
}
let resizeTimeout;
window.addEventListener("resize", () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(resizeCanvas, 100);
});
window.addEventListener("load", () => {
resizeCanvas();
generateMaze();
});
function drawMaze() {
if (!ctx || !canvas) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (!gameState.maze || gameState.maze.length === 0) return;
for (let i = 0; i < MAZE_SIZE; i++) {
for (let j = 0; j < MAZE_SIZE; j++) {
const x = j * CELL_SIZE;
const y = i * CELL_SIZE;
switch (gameState.maze[i][j]) {
case 1:
ctx.fillStyle = "#334155";
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
break;
case 0:
ctx.fillStyle = "#1e293b";
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
break;
case 2:
ctx.fillStyle = "#10b981";
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
break;
case 3:
ctx.fillStyle = "#ef4444";
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
break;
case 4:
ctx.fillStyle = "#f59e0b";
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
break;
case 5:
ctx.fillStyle = "#3b82f6";
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
break;
case 6:
ctx.fillStyle = "#8b5cf6";
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
break;
}
}
}
}
async function generateMaze() {
if (gameState.isAnimating) return;
gameState.isAnimating = true;
const startTime = performance.now();
updateStatus("Generating maze...");
gameState.maze = Array(MAZE_SIZE)
.fill()
.map(() => Array(MAZE_SIZE).fill(1));
const stack = [];
const start = [1, 1];
gameState.maze[start[0]][start[1]] = 6;
stack.push(start);
const directions = [
[0, 2],
[2, 0],
[0, -2],
[-2, 0]
];
let steps = 0;
let cellsCarved = 1;
const totalCellsToCarve = Math.floor((MAZE_SIZE - 1) / 2) * Math.floor((MAZE_SIZE - 1) / 2);
const maxSteps = MAZE_SIZE * MAZE_SIZE * 2;
while (stack.length > 0 && steps < maxSteps) {
steps++;
const current = stack[stack.length - 1];
const neighbors = [];
for (const [dx, dy] of directions) {
const nx = current[0] + dx;
const ny = current[1] + dy;
if (
nx > 0 &&
nx < MAZE_SIZE - 1 &&
ny > 0 &&
ny < MAZE_SIZE - 1 &&
gameState.maze[nx][ny] === 1
) {
neighbors.push([nx, ny]);
}
}
if (neighbors.length > 0) {
const next = neighbors[Math.floor(Math.random() * neighbors.length)];
const wallX = (current[0] + next[0]) / 2;
const wallY = (current[1] + next[1]) / 2;
gameState.maze[wallX][wallY] = 6;
gameState.maze[next[0]][next[1]] = 6;
drawMaze();
await new Promise((resolve) => setTimeout(resolve, 0));
gameState.maze[wallX][wallY] = 0;
gameState.maze[next[0]][next[1]] = 0;
gameState.maze[current[0]][current[1]] = 0;
stack.push(next);
cellsCarved++;
const progress = Math.min(100, Math.floor((cellsCarved / totalCellsToCarve) * 100));
updateStatus(`Generating maze... (${progress}%)`);
} else {
gameState.maze[current[0]][current[1]] = 0;
stack.pop();
}
if (steps % 5 === 0) {
drawMaze();
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
let endX, endY;
let foundValidEnd = false;
for (let i = MAZE_SIZE - 2; i > 1 && !foundValidEnd; i--) {
for (let j = MAZE_SIZE - 2; j > 1 && !foundValidEnd; j--) {
if (gameState.maze[i][j] === 0) {
if (isConnectedToStart(i, j)) {
endX = i;
endY = j;
foundValidEnd = true;
}
}
}
}
if (!foundValidEnd && stack.length > 0) {
const lastCell = stack[stack.length - 1];
endX = lastCell[0];
endY = lastCell[1];
} else if (!foundValidEnd) {
endX = MAZE_SIZE - 2;
endY = MAZE_SIZE - 2;
gameState.maze[endX][endY] = 0;
}
gameState.maze[1][1] = 2;
gameState.maze[endX][endY] = 3;
const endTime = performance.now();
updateElement("genTime", ((endTime - startTime) / 1000).toFixed(2) + "s");
updateElement("solutionLength", "-");
updateElement("solveTime", "0.00s");
drawMaze();
updateStatus("Maze generated successfully");
gameState.isAnimating = false;
}
function isConnectedToStart(x, y) {
const visited = new Set();
const queue = [[1, 1]];
visited.add("1,1");
const directions = [
[0, 1], [1, 0], [0, -1], [-1, 0]
];
while (queue.length > 0) {
const [cx, cy] = queue.shift();
if (cx === x && cy === y) {
return true;
}
for (const [dx, dy] of directions) {
const nx = cx + dx;
const ny = cy + dy;
if (
nx >= 0 && nx < MAZE_SIZE &&
ny >= 0 && ny < MAZE_SIZE &&
gameState.maze[nx][ny] === 0 &&
!visited.has(`${nx},${ny}`)
) {
visited.add(`${nx},${ny}`);
queue.push([nx, ny]);
}
}
}
return false;
}
async function solveMaze() {
if (gameState.maze.length === 0 || gameState.isAnimating) return;
gameState.isAnimating = true;
const startTime = performance.now();
updateStatus("Finding path...");
clearSolution();
let start = [1, 1];
let end = null;
for (let i = 0; i < MAZE_SIZE; i++) {
for (let j = 0; j < MAZE_SIZE; j++) {
if (gameState.maze[i][j] === 3) {
end = [i, j];
break;
}
}
if (end) break;
}
if (!end) {
updateStatus("Error: No end point found");
gameState.isAnimating = false;
return;
}
const openSet = [start];
const cameFrom = new Map();
const gScore = new Map();
const fScore = new Map();
gScore.set(start.toString(), 0);
fScore.set(start.toString(), heuristic(start, end));
const directions = [
[0, 1],
[1, 0],
[0, -1],
[-1, 0]
];
let exploredCount = 0;
while (openSet.length > 0) {
let current = openSet[0];
let currentIndex = 0;
for (let i = 1; i < openSet.length; i++) {
if (fScore.get(openSet[i].toString()) < fScore.get(current.toString())) {
current = openSet[i];
currentIndex = i;
}
}
if (current[0] === end[0] && current[1] === end[1]) {
const path = [];
let temp = current;
while (cameFrom.has(temp.toString())) {
path.unshift(temp);
temp = cameFrom.get(temp.toString());
}
path.unshift(start);
await animateSolution(path);
const endTime = performance.now();
updateElement("solutionLength", path.length.toString());
updateElement(
"solveTime",
((endTime - startTime) / 1000).toFixed(2) + "s"
);
updateStatus(`Path found with ${path.length} steps`);
gameState.isAnimating = false;
return;
}
openSet.splice(currentIndex, 1);
for (const [dx, dy] of directions) {
const neighbor = [current[0] + dx, current[1] + dy];
if (
neighbor[0] < 0 ||
neighbor[0] >= MAZE_SIZE ||
neighbor[1] < 0 ||
neighbor[1] >= MAZE_SIZE ||
gameState.maze[neighbor[0]][neighbor[1]] === 1
) {
continue;
}
const tentativeGScore = gScore.get(current.toString()) + 1;
if (
!gScore.has(neighbor.toString()) ||
tentativeGScore < gScore.get(neighbor.toString())
) {
cameFrom.set(neighbor.toString(), current);
gScore.set(neighbor.toString(), tentativeGScore);
fScore.set(
neighbor.toString(),
tentativeGScore + heuristic(neighbor, end)
);
if (
!openSet.some(
(node) => node[0] === neighbor[0] && node[1] === neighbor[1]
)
) {
openSet.push(neighbor);
if (gameState.maze[neighbor[0]][neighbor[1]] === 0) {
gameState.maze[neighbor[0]][neighbor[1]] = 5;
exploredCount++;
drawMaze();
await new Promise((resolve) => setTimeout(resolve, 1));
}
}
}
}
}
updateStatus("No path found - maze is unsolvable");
gameState.isAnimating = false;
}
function heuristic(a, b) {
return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);
}
async function animateSolution(path) {
updateStatus("Visualizing path...");
for (let i = 1; i < path.length - 1; i++) {
const [row, col] = path[i];
gameState.maze[row][col] = 4;
drawMaze();
await new Promise((resolve) => setTimeout(resolve, 15));
}
drawMaze();
}
function clearSolution() {
if (gameState.isAnimating) return;
for (let i = 0; i < MAZE_SIZE; i++) {
for (let j = 0; j < MAZE_SIZE; j++) {
if (
gameState.maze[i] &&
(gameState.maze[i][j] === 4 || gameState.maze[i][j] === 5)
) {
gameState.maze[i][j] = 0;
}
}
}
for (let i = 0; i < MAZE_SIZE; i++) {
for (let j = 0; j < MAZE_SIZE; j++) {
if (gameState.maze[i][j] === 2) {
} else if (gameState.maze[i][j] === 3) {
}
}
}
drawMaze();
updateStatus("Path cleared");
updateElement("solutionLength", "-");
updateElement("solveTime", "0.00s");
}
function resetMaze() {
if (gameState.isAnimating) return;
gameState.maze = [];
gameState.solution = [];
ctx.clearRect(0, 0, canvas.width, canvas.height);
updateStatus("Reset - Generate a new maze");
updateElement("genTime", "0.00s");
updateElement("solutionLength", "-");
updateElement("solveTime", "0.00s");
}
function updateStatus(message) {
const statusElement = document.getElementById("status");
if (statusElement) {
statusElement.textContent = message;
}
}
function updateElement(id, value) {
const element = document.getElementById(id);
if (element) {
element.textContent = value;
}
}
</script>
</body>
</html>