JSFiddle - React, Tailwind, and code Playground

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2D Cave Generator</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>

    <h1>Procedural Cave Generator</h1>
    <p>A simple visualization using Cellular Automata.</p>

    <!-- The Canvas is where the magic happens -->
    <canvas id="caveCanvas"></canvas>

    <div class="controls">
        <button id="generateButton">Generate New Cave</button>
        <p id="statusMessage">Press 'Generate' to start...</p>
    </div>

    <script src="script.js"></script>
</body>
</html>

CSS

body {
    font-family: sans-serif;
    display: flex;
    flex-direction: column;
    align-items: center;
    margin: 20px;
    background-color: #1e1e1e;
    color: #f0f0f0;
}

h1 {
    color: #4CAF50;
}

#caveCanvas {
    border: 3px solid #4CAF50;
    background-color: #000;
    margin-bottom: 20px;
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
}

.controls {
    display: flex;
    gap: 20px;
    align-items: center;
}

#generateButton {
    padding: 10px 20px;
    font-size: 16px;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color 0.2s;
}

#generateButton:hover {
    background-color: #45a049;
}

#statusMessage {
    margin: 0;
    font-style: italic;
}

JavaScript

// --- CONFIGURATION CONSTANTS ---
const CANVAS_SIZE = 800; // Width and Height of the visible canvas
const GRID_WIDTH = 60;   // Number of cells wide
const GRID_HEIGHT = 40;  // Number of cells high
const CELL_SIZE = CANVAS_SIZE / GRID_WIDTH;

// The state of the grid: 0 = Empty/Air, 1 = Wall/Solid
const grid = []; 

// --- DOM ELEMENTS ---
const canvas = document.getElementById('caveCanvas');
const ctx = canvas.getContext('2d');
const generateButton = document.getElementById('generateButton');
const statusMessage = document.getElementById('statusMessage');

// Set canvas dimensions
canvas.width = CANVAS_SIZE;
canvas.height = CANVAS_SIZE;

/**
 * 1. Initialization: Creates a random initial map (mostly solid, few holes).
 */
function initializeGrid() {
    for (let y = 0; y < GRID_HEIGHT; y++) {
        grid[y] = [];
        for (let x = 0; x < GRID_WIDTH; x++) {
            // Start with a random chance of being a wall (55% chance)
            grid[y][x] = Math.random() < 0.55 ? 1 : 0; 
        }
    }
}

/**
 * 2. Smoothing/Cave Generation (Cellular Automata Rule):
 * Iterates over the grid multiple times. A cell becomes a wall if it has 
 * 5 or more wall neighbors, OR a cell becomes air if it has 4 or more air neighbors.
 */
function smoothGrid(iterations = 5) {
    let tempGrid = [];

    for (let i = 0; i < iterations; i++) {
        // Deep copy the current grid state into tempGrid
        tempGrid = grid.map(row => [...row]);
        
        // Reset the main grid structure for the next calculation
        for (let y = 0; y < GRID_HEIGHT; y++) {
            grid[y] = [];
            for (let x = 0; x < GRID_WIDTH; x++) {
                let wallCount = 0;
                
                // Check all 8 neighbors (including diagonals)
                for (let dy = -1; dy <= 1; dy++) {
                    for (let dx = -1; dx <= 1; dx++) {
                        if (dx === 0 && dy === 0) continue; //...