2d-map-gen.html
<!DOCTYPE html>
<html>
<head>
<title>Procedural Map</title>
<script src="https://cdn.jsdelivr.net/npm/noisejs@2.1.0/index.min.js"></script>
</head>
<body>
<canvas id="gameCanvas" width="450" height="450"></canvas>
<pre id="data-export" style="text-wrap: wrap;"></pre>

<script>
  class MapGenerator {
    static perlinNoise(map, scale = 0.1, threshold = 0.5) {
        const mapSize = map.length;

        // Initialize Perlin noise function (you'll need a library like simplex-noise.js)
        const noise = new Noise(); // Or your preferred Perlin/Simplex noise library
        const val = Math.floor(Math.random() * 65536) + 1;
        noise.seed(val);

        for (let y = 1; y < mapSize - 1; y++) {
            for (let x = 1; x < mapSize - 1; x++) {
                // const value = noise.noise2D(x * scale, y * scale); // Sample noise
                const value = noise.simplex2(x * scale, y * scale);

                // Apply threshold to create binary map (0 or 1)
                map[y][x] = value > threshold? 1: 0;
            }
        }

        return map;
    }

    static cellularAutomata(map, iterations) {
        for (let i = 0; i < iterations; i++) {
            const newMap = map.map(row => row.slice()); // Copy the map
            for (let y = 1; y < mapSize - 1; y++) {
                for (let x = 1; x < mapSize - 1; x++) {
                    let neighbors = 0;
                    for (let ny = -1; ny <= 1; ny++) {
                        for (let nx = -1; nx <= 1; nx++) {
                            if (ny === 0 && nx === 0) continue; // Skip self
                            neighbors += map[y + ny][x + nx];
                        }
                    }
                    if (neighbors > 4) {
                        newMap[y][x] = 1;
                    } else if (neighbors < 4) {
                        newMap[y][x] = 0;
                    }
                    //If neighbors == 4, the tile stays as it was
                }
            }
            map = newMap; // Update the map for the next iteration
        }
        return map;
    }

    static bsp(map) {
        const mapSize = map.length; // Get mapSize from the passed map

        function partition(x1, y1, x2, y2) {
            const width = x2 - x1;
            const height = y2 - y1;

            if (width < 5 || height < 5) { // Minimum room size
                return;
            }

            const splitHorizontal = Math.random() < 0.5;

            if (splitHorizontal) {
                const splitY = Math.floor(y1 + height * (0.3 + Math.random() * 0.4)); // 30%-70% of height
                for (let x = x1 + 1; x < x2; x++) {
                    map[splitY][x] = 1; // Create wall
                }
                partition(x1, y1, x2, splitY);
                partition(x1, splitY + 1, x2, y2);
            } else {
                const splitX = Math.floor(x1 + width * (0.3 + Math.random() * 0.4));
                for (let y = y1 + 1; y < y2; y++) {
                    map[y][splitX] = 1;
                }
                partition(x1, y1, splitX, y2);
                partition(splitX + 1, y1, x2, y2);
            }
        }

        partition(1, 1, mapSize - 2, mapSize - 2); // Start partitioning

        return map;
    }

    static randomWalk(map) {
        const mapSize = map.length; // Get mapSize from the passed map
        let x = Math.floor(mapSize / 2);
        let y = Math.floor(mapSize / 2);
        const steps = mapSize * mapSize * 0.4; // Adjust number of steps

        for (let i = 0; i < steps; i++) {
            const direction = Math.floor(Math.random() * 4); // 0: up, 1: right, 2: down, 3: left

            const dx = [0, 1, 0, -1][direction];
            const dy = [-1, 0, 1, 0][direction];

            const newX = Math.max(1, Math.min(mapSize - 2, x + dx));
            const newY = Math.max(1, Math.min(mapSize - 2, y + dy));

            map[newY][newX] = 1; // Carve path
            x = newX;
            y = newY;
        }

        return map;
    }
  }

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

  let mapSize = 75;
  let tileSize;

  function resizeCanvas() {
    tileSize = canvas.width / mapSize;
    canvas.height = canvas.width;
  }

  function generateMap() {
    let map = [];
    for (let y = 0; y < mapSize; y++) {
      map[y] = []; // Initialize each row
      for (let x = 0; x < mapSize; x++) {
        map[y][x] = (x === 0 || x === mapSize - 1 || y === 0 || y === mapSize - 1) ? 1 : 0;
      }
    }

    // Initialize interior to 0s
    for (let y = 1; y < mapSize - 1; y++) {
      for (let x = 1; x < mapSize - 1; x++) {
        map[y][x] = 0; // Explicitly set to 0
      }
    }

    // Now randomize after initialization
    for (let y = 1; y < mapSize - 1; y++) {
      for (let x = 1; x < mapSize - 1; x++) {
        map[y][x] = Math.random() < 0.4 ? 1 : 0;
      }
    }

    map = MapGenerator.cellularAutomata(map, 5);
    map = MapGenerator.bsp(map); // Use BSP
    map = MapGenerator.perlinNoise(map, 0.09, 0.02); // Adjust scale and threshold as needed
    map = MapGenerator.randomWalk(map);
    return map;
  }

  function renderMap(map) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    for (let y = 0; y < mapSize; y++) {
      for (let x = 0; x < mapSize; x++) {
        ctx.fillStyle = map[y][x] === 1 ? 'black' : 'white';
        ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
      }
    }
  }

  function updateExport(map) {
    const pre = document.getElementById('data-export');
    pre.innerHTML = JSON.stringify(map);
  }

  function initialize() {
    canvas.width = 1000;
    resizeCanvas();
    let map = generateMap();
    renderMap(map);
    updateExport(map)

    canvas.addEventListener('click', function() {
      map = generateMap();
      renderMap(map);
      updateExport(map)
    });
  }

  initialize();
</script>
</body>
</html>