
Dungeon Web App
Prompt
create a single-file visually impressive html web app that displays a randomly generated dungeon (consisting of rooms, walls around rooms, empty ground, corridors and doors where corridors connect to rooms). Procedurally generate an appropriate icon set for displaying the corridor.
A system prompt was added to support web rendering
# Dungeon Crawler Generator I'll create a visually impressive dungeon generator with procedurally generated icons. This web app will display a random dungeon with rooms, corridors, walls, and doors. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Procedural Dungeon Generator</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: linear-gradient(135deg, #1a1a2e, #16213e); color: #e6e6e6; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 20px; overflow-x: hidden; } header { text-align: center; margin: 20px 0 30px; width: 100%; max-width: 800px; } h1 { font-size: 2.8rem; margin-bottom: 10px; background: linear-gradient(to right, #ff7e5f, #feb47b); -webkit-background-clip: text; background-clip: text; color: transparent; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } .subtitle { font-size: 1.2rem; opacity: 0.9; margin-bottom: 20px; } .controls { display: flex; gap: 15px; flex-wrap: wrap; justify-content: center; margin-bottom: 25px; } button { background: linear-gradient(to right, #4a00e0, #8e2de2); color: white; border: none; padding: 12px 25px; border-radius: 30px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); } button:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); } button:active { transform: translateY(1px); } .container { display: flex; flex-direction: column; align-items: center; width: 100%; max-width: 1000px; } .dungeon-container { background: rgba(10, 15, 30, 0.7); border-radius: 10px; padding: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); width: 100%; margin-bottom: 30px; overflow: auto; } .dungeon { display: grid; grid-template-columns: repeat(30, 24px); grid-template-rows: repeat(20, 24px); gap: 0; margin: 0 auto; border: 2px solid #4a00e0; border-radius: 5px; background: #0d1021; box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.7); } .tile { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; font-size: 20px; transition: transform 0.2s; } .tile:hover { transform: scale(1.1); z-index: 10; } .wall { background: linear-gradient(135deg, #2c3e50, #1a1a2e); box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.5); } .ground { background: linear-gradient(135deg, #16213e, #0f3460); } .room { background: radial-gradient(circle, #1e3c72, #2a5298); } .corridor { background: linear-gradient(135deg, #141e30, #243b55); } .door { background: linear-gradient(135deg, #2c3e50, #34495e); position: relative; } .door::after { content: "๐ช"; font-size: 16px; } .legend { display: flex; flex-wrap: wrap; gap: 20px; justify-content: center; background: rgba(10, 15, 30, 0.7); padding: 20px; border-radius: 10px; width: 100%; max-width: 800px; } .legend-item { display: flex; align-items: center; gap: 10px; } .legend-color { width: 24px; height: 24px; border-radius: 4px; } .wall-legend { background: linear-gradient(135deg, #2c3e50, #1a1a2e); } .ground-legend { background: linear-gradient(135deg, #16213e, #0f3460); } .room-legend { background: radial-gradient(circle, #1e3c72, #2a5298); } .corridor-legend { background: linear-gradient(135deg, #141e30, #243b55); } .door-legend { background: linear-gradient(135deg, #2c3e50, #34495e); } .stats { display: flex; gap: 30px; margin-top: 20px; flex-wrap: wrap; justify-content: center; } .stat-box { background: rgba(26, 38, 57, 0.7); padding: 15px 25px; border-radius: 10px; text-align: center; min-width: 150px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); } .stat-value { font-size: 2rem; font-weight: bold; color: #8e2de2; margin-top: 5px; } footer { margin-top: 30px; text-align: center; opacity: 0.7; font-size: 0.9rem; } @media (max-width: 768px) { .dungeon { grid-template-columns: repeat(30, 16px); grid-template-rows: repeat(20, 16px); } .tile { width: 16px; height: 16px; font-size: 14px; } h1 { font-size: 2rem; } } </style> </head> <body> <header> <h1>Procedural Dungeon Generator</h1> <p class="subtitle">Explore procedurally generated dungeons with rooms, corridors, and doors</p> </header> <div class="controls"> <button id="generateBtn">Generate New Dungeon</button> <button id="regenerateBtn">Regenerate Icons</button> </div> <div class="container"> <div class="dungeon-container"> <div class="dungeon" id="dungeon"></div> </div> <div class="legend"> <div class="legend-item"> <div class="legend-color wall-legend"></div> <span>Wall</span> </div> <div class="legend-item"> <div class="legend-color ground-legend"></div> <span>Ground</span> </div> <div class="legend-item"> <div class="legend-color room-legend"></div> <span>Room</span> </div> <div class="legend-item"> <div class="legend-color corridor-legend"></div> <span>Corridor</span> </div> <div class="legend-item"> <div class="legend-color door-legend"></div> <span>Door</span> </div> </div> <div class="stats"> <div class="stat-box"> <div>Rooms</div> <div class="stat-value" id="roomCount">0</div> </div> <div class="stat-box"> <div>Corridors</div> <div class="stat-value" id="corridorCount">0</div> </div> <div class="stat-box"> <div>Doors</div> <div class="stat-value" id="doorCount">0</div> </div> </div> </div> <footer> <p>Procedurally generated dungeon with custom corridor icons | Refresh for a new dungeon</p> </footer> <script> document.addEventListener('DOMContentLoaded', () => { const dungeonElement = document.getElementById('dungeon'); const generateBtn = document.getElementById('generateBtn'); const regenerateBtn = document.getElementById('regenerateBtn'); const roomCountElement = document.getElementById('roomCount'); const corridorCountElement = document.getElementById('corridorCount'); const doorCountElement = document.getElementById('doorCount'); // Constants const ROWS = 20; const COLS = 30; const ROOM_COUNT = 8; const MAX_ROOM_SIZE = 6; const MIN_ROOM_SIZE = 3; // Tile types const TILE_TYPES = { WALL: 'wall', GROUND: 'ground', ROOM: 'room', CORRIDOR: 'corridor', DOOR: 'door' }; // Store dungeon grid let grid = []; // Store dungeon rooms let rooms = []; // Generate corridor icons function generateCorridorIcons() { const icons = []; // Generate 10 unique corridor icons for (let i = 0; i < 10; i++) { // Create a unique pattern with varying elements const elements = ['โ', 'โ', 'โ', 'โ', 'โ', 'โ', 'โ']; const colors = ['#4b6cb7', '#3a7bd5', '#00d2ff', '#3a7bd5', '#4b6cb7']; const color = colors[Math.floor(Math.random() * colors.length)]; const element = elements[Math.floor(Math.random() * elements.length)]; // Create the icon with CSS styling const icon = `<span style="color:${color}; text-shadow: 0 0 3px rgba(255,255,255,0.5);">${element}</span>`; icons.push(icon); } return icons; } // Initialize corridor icons let corridorIcons = generateCorridorIcons(); // Create empty grid function createEmptyGrid() { const grid = []; for (let row = 0; row < ROWS; row++) { grid[row] = []; for (let col = 0; col < COLS; col++) { grid[row][col] = TILE_TYPES.WALL; } } return grid; } // Create a room function createRoom() { const width = Math.floor(Math.random() * (MAX_ROOM_SIZE - MIN_ROOM_SIZE + 1)) + MIN_ROOM_SIZE; const height = Math.floor(Math.random() * (MAX_ROOM_SIZE - MIN_ROOM_SIZE + 1)) + MIN_ROOM_SIZE; const x = Math.floor(Math.random() * (COLS - width - 2)) + 1; const y = Math.floor(Math.random() * (ROWS - height - 2)) + 1; return { x, y, width, height, x2: x + width, y2: y + height }; } // Check if room overlaps with existing rooms function roomOverlaps(newRoom, rooms) { for (const room of rooms) { if ( newRoom.x < room.x2 + 1 && newRoom.x2 + 1 > room.x && newRoom.y < room.y2 + 1 && newRoom.y2 + 1 > room.y ) { return true; } } return false; } // Create rooms function createRooms() { rooms = []; let attempts = 0; const maxAttempts = 1000; while (rooms.length < ROOM_COUNT && attempts < maxAttempts) { const room = createRoom(); if (!roomOverlaps(room, rooms)) { rooms.push(room); } attempts++; } } // Carve rooms in grid function carveRooms() { rooms.forEach(room => { for (let y = room.y; y < room.y2; y++) { for (let x = room.x; x < room.x2; x++) { grid[y][x] = TILE_TYPES.ROOM; } } }); } // Create horizontal corridor function createHCorridor(x1, x2, y) { const start = Math.min(x1, x2); const end = Math.max(x1, x2); for (let x = start; x <= end; x++) { if (grid[y][x] === TILE_TYPES.WALL) { grid[y][x] = TILE_TYPES.CORRIDOR; } } } // Create vertical corridor function createVCorridor(y1, y2, x) { const start = Math.min(y1, y2); const end = Math.max(y1, y2); for (let y = start; y <= end; y++) { if (grid[y][x] === TILE_TYPES.WALL) { grid[y][x] = TILE_TYPES.CORRIDOR; } } } // Connect rooms with corridors function connectRooms() { for (let i = 0; i < rooms.length - 1; i++) { const roomA = rooms[i]; const roomB = rooms[i + 1]; // Get center points const centerX1 = Math.floor((roomA.x + roomA.x2) / 2); const centerY1 = Math.floor((roomA.y + roomA.y2) / 2); const centerX2 = Math.floor((roomB.x + roomB.x2) / 2); const centerY2 = Math.floor((roomB.y + roomB.y2) / 2); // Randomly choose between horizontal-first or vertical-first if (Math.random() > 0.5) { createHCorridor(centerX1, centerX2, centerY1); createVCorridor(centerY1, centerY2, centerX2); } else { createVCorridor(centerY1, centerY2, centerX1); createHCorridor(centerX1, centerX2, centerY2); } // Place doors placeDoors(roomA, roomB, centerX1, centerY1, centerX2, centerY2); } } // Place doors between rooms function placeDoors(roomA, roomB, centerX1, centerY1, centerX2, centerY2) { // Door from roomA to corridor const doorX1 = centerX1; const doorY1 = centerY1; // Adjust if door is on room edge if (doorX1 === roomA.x) { grid[doorY1][doorX1 - 1] = TILE_TYPES.DOOR; } else if (doorX1 === roomA.x2 - 1) { grid[doorY1][doorX1 + 1] = TILE_TYPES.DOOR; } else if (doorY1 === roomA.y) { grid[doorY1 - 1][doorX1] = TILE_TYPES.DOOR; } else if (doorY1 === roomA.y2 - 1) { grid[doorY1 + 1][doorX1] = TILE_TYPES.DOOR; } else { grid[doorY1][doorX1] = TILE_TYPES.DOOR; } // Door from roomB to corridor const doorX2 = centerX2; const doorY2 = centerY2; // Adjust if door is on room edge if (doorX2 === roomB.x) { grid[doorY2][doorX2 - 1] = TILE_TYPES.DOOR; } else if (doorX2 === roomB.x2 - 1) { grid[doorY2][doorX2 + 1] = TILE_TYPES.DOOR; } else if (doorY2 === roomB.y) { grid[doorY2 - 1][doorX2] = TILE_TYPES.DOOR; } else if (doorY2 === roomB.y2 - 1) { grid[doorY2 + 1][doorX2] = TILE_TYPES.DOOR; } else { grid[doorY2][doorX2] = TILE_TYPES.DOOR; } } // Render the dungeon function renderDungeon() { dungeonElement.innerHTML = ''; let roomCount = 0; let corridorCount = 0; let doorCount = 0; for (let row = 0; row < ROWS; row++) { for (let col = 0; col < COLS; col++) { const tile = grid[row][col]; const tileElement = document.createElement('div'); tileElement.className = `tile ${tile}`; switch (tile) { case TILE_TYPES.WALL: tileElement.textContent = 'โ'; tileElement.style.color = '#2c3e50'; break; case TILE_TYPES.GROUND: tileElement.textContent = 'ยท'; tileElement.style.color = '#34495e'; break; case TILE_TYPES.ROOM: tileElement.textContent = ' '; roomCount++; break; case TILE_TYPES.CORRIDOR: // Randomly select a corridor icon const iconIndex = Math.floor(Math.random() * corridorIcons.length); tileElement.innerHTML = corridorIcons[iconIndex]; corridorCount++; break; case TILE_TYPES.DOOR: tileElement.innerHTML = '๐ช'; doorCount++; break; } dungeonElement.appendChild(tileElement); } } // Update stats roomCountElement.textContent = roomCount; corridorCountElement.textContent = corridorCount; doorCountElement.textContent = doorCount; } // Generate the dungeon function generateDungeon() { grid = createEmptyGrid(); createRooms(); carveRooms(); connectRooms(); renderDungeon(); } // Generate new corridor icons function regenerateIcons() { corridorIcons = generateCorridorIcons(); renderDungeon(); } // Event listeners generateBtn.addEventListener('click', generateDungeon); regenerateBtn.addEventListener('click', regenerateIcons); // Initial generation generateDungeon(); }); </script> </body> </html> ``` ## Features of this Dungeon Generator: 1. **Visually Rich Design**: - Dark fantasy theme with gradient backgrounds - Glowing elements and subtle shadows - Responsive layout that works on all devices 2. **Procedural Dungeon Generation**: - Randomly placed rooms of varying sizes - Corridors connecting rooms with L-shaped paths - Doors at room entrances 3. **Custom Corridor Icons**: - Procedurally generated corridor icons using multiple Unicode characters - Each corridor tile displays a unique icon with different styles - Regenerate button to create new icon set 4. **Interactive Elements**: - Generate new dungeons with one click - Regenerate corridor icons independently - Hover effects on tiles for better interaction 5. **Detailed Information**: - Legend explaining dungeon elements - Statistics showing room, corridor, and door counts - Clear visual distinction between different tile types The dungeon follows classic roguelike design principles with interconnected rooms and corridors. Each time you generate a new dungeon, you'll get a unique layout with specially generated corridor icons that give the dungeon a distinctive appearance.
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Procedurally-Generated Dungeon</title> <style> html,body{ margin:0; height:100%; background:#111; display:flex; justify-content:center; align-items:center; overflow:hidden; user-select:none; font-family:arial, sans-serif; color:#ddd; } #ui{ position:absolute; top:10px;left:50%;transform:translateX(-50%); z-index:5;text-align:center } button{ padding:6px 14px; font-size:15px; cursor:pointer; border-radius:4px; border:none; background:#444; color:#eee; } canvas{ image-rendering:pixelated; box-shadow:0 0 30px #000; border:4px solid #333; background:#000; } </style> </head> <body> <div id="ui"><button onclick="init()">Generate New Dungeon</button></div> <canvas id="c"></canvas> <script> /* ------------------ CONFIGURABLE CONSTANTS ------------------ */ const CELL = 8; // pixel size of one tile (canvas drawn x4 for retina) const WIDTH = 120; // dungeon grid width (tiles) const HEIGHT = 70; // dungeon grid height (tiles) const ROOM_ATTEMPTS = 120;// times to try to place random rooms const MIN_ROOM = 4; const MAX_ROOM = 12; /* ------------------ TILE IDS ------------------ */ const WALL = 0; const FLOOR = 1; const CORRID = 2; const DOOR = 3; const VOID = 4; // outside map /* ------------------ GLOBALS ------------------ */ const canv = document.getElementById("c"); const ctx = canv.getContext("2d"); let map = []; /* ------------------ ENTRY ------------------ */ init(); /* ----------------- MAIN FUNCTIONS ---------------------------*/ function init(){ resize(); generate(); draw(); } /* ---------- RESIZE CANVAS TO FIT SCREEN --------------------*/ function resize(){ const scale = Math.floor(Math.min(innerWidth/(WIDTH*CELL), innerHeight/(HEIGHT*CELL))); canv.width = WIDTH * CELL; canv.height = HEIGHT* CELL; canv.style.width = WIDTH * CELL * scale + "px"; canv.style.height = HEIGHT* CELL * scale + "px"; } /* ---------- DUNGEON GENERATION ------------------------------*/ function generate(){ // fill map with walls map = Array.from({length:HEIGHT}, ()=>Array(WIDTH).fill(WALL)); const rooms=[]; // place random rooms for(let i=0;i<ROOM_ATTEMPTS;i++){ const w = randInt(MIN_ROOM,MAX_ROOM); const h = randInt(MIN_ROOM,MAX_ROOM); const x = randInt(1, WIDTH-w-1); const y = randInt(1, HEIGHT-h-1); const roomRect={x,y,w,h}; if(!rooms.some(r=>intersect(roomRect,r))){ rooms.push(roomRect); carveRoom(roomRect); } } // connect rooms with corridors using simple MST (Prim-like) const nodes = rooms.map(r=>({cx:Math.floor(r.x+r.w/2),cy:Math.floor(r.y+r.h/2)})); const connected=[nodes[0]]; const remaining=nodes.slice(1); while(remaining.length){ let bestCost=1e9,bestA,bestB; for(const a of connected){ for(const b of remaining){ const d = dist(a,b); if(d<bestCost){ bestCost=d;bestA=a;bestB=b; } } } carveCorridor(bestA,bestB); connected.push(bestB); remaining.splice(remaining.indexOf(bestB),1); } // doors where corridor meets room floor for(let y=1;y<HEIGHT-1;y++){ for(let x=1;x<WIDTH-1;x++){ if(map[y][x]===CORRID && isAdjacentTo(x,y,FLOOR)){ map[y][x]=DOOR; } } } // turn surrounding void into VOID tile for edge shading for(let y=0;y<HEIGHT;y++){ for(let x=0;x<WIDTH;x++){ if(map[y][x]===WALL && countNeighbors(x,y,FLOOR|CORRID|DOOR)===0){ map[y][x]=VOID; } } } } /* ------------------ CARVING HELPERS -------------------------*/ function carveRoom({x,y,w,h}){ for(let i=y;i<y+h;i++){ for(let j=x;j<x+w;j++){ if(i===y||i===y+h-1||j===x||j===x+w-1){ map[i][j]=WALL; // leave perimeter as walls }else{ map[i][j]=FLOOR; } } } } function carveCorridor(a,b){ let x=a.cx, y=a.cy; while(x!==b.cx){ map[y][x]=CORRID; x += (b.cx>x)?1:-1; } while(y!==b.cy){ map[y][x]=CORRID; y += (b.cy>y)?1:-1; } map[y][x]=CORRID; } /* ------------------ DRAWING ---------------------------------*/ const palette={ [VOID] : "#000", [WALL] : "#444", [FLOOR] : "#999", [CORRID]: "#08f", // will be textured later [DOOR] : "#b5651d" }; function draw(){ ctx.clearRect(0,0,canv.width,canv.height); // First pass solid colors for(let y=0;y<HEIGHT;y++){ for(let x=0;x<WIDTH;x++){ ctx.fillStyle=palette[map[y][x]]; ctx.fillRect(x*CELL,y*CELL,CELL,CELL); } } // Second pass: add corridor procedural icon pattern generateCorridorPattern(); } /* --------------- PROCEDURAL CORRIDOR ICON PATTERN ----------*/ function generateCorridorPattern(){ // create an offscreen canvas 8x8 with cool stripes const psize=8; const pCanv=document.createElement("canvas"); pCanv.width=psize; pCanv.height=psize; const pCtx=pCanv.getContext("2d"); const grad=pCtx.createLinearGradient(0,0,psize,psize); grad.addColorStop(0,"#39f"); grad.addColorStop(1,"#04a"); pCtx.fillStyle=grad; pCtx.fillRect(0,0,psize,psize); // carve some random bits to create tech-like pattern pCtx.fillStyle="rgba(0,0,0,0.4)"; for(let i=0;i<6;i++){ const w=randInt(2,4),h=randInt(1,3); const x=randInt(0,psize-w),y=randInt(0,psize-h); pCtx.fillRect(x,y,w,h); } // set as pattern const pattern=ctx.createPattern(pCanv,"repeat"); ctx.fillStyle=pattern; for(let y=0;y<HEIGHT;y++){ for(let x=0;x<WIDTH;x++){ if(map[y][x]===CORRID){ ctx.fillRect(x*CELL,y*CELL,CELL,CELL); } } } // Doors overlay (wood texture stripes) for(let y=0;y<HEIGHT;y++){ for(let x=0;x<WIDTH;x++){ if(map[y][x]===DOOR){ ctx.fillStyle="#b57d2a"; ctx.fillRect(x*CELL,y*CELL,CELL,CELL); ctx.strokeStyle="#0005"; ctx.lineWidth=1; ctx.beginPath(); ctx.moveTo(x*CELL,y*CELL+CELL/2); ctx.lineTo(x*CELL+CELL,y*CELL+CELL/2); ctx.stroke(); } } } } /* ------------------ UTILS -----------------------------------*/ function randInt(a,b){return Math.floor(Math.random()*(b-a+1))+a;} function intersect(r1,r2){ return !(r1.x+r1.w<r2.x || r2.x+r2.w<r1.x || r1.y+r1.h<r2.y || r2.y+r2.h<r1.y); } function dist(a,b){return Math.abs(a.cx-b.cx)+Math.abs(a.cy-b.cy);} function isAdjacentTo(x,y,tileMask){ const dirs=[[1,0],[-1,0],[0,1],[0,-1]]; for(const [dx,dy] of dirs){ const v=map[y+dy]?.[x+dx]; if(v!==undefined && ((1<<v) & tileMask)) return true; } return false; } function countNeighbors(x,y,mask){ let c=0; for(let dy=-1;dy<=1;dy++){ for(let dx=-1;dx<=1;dx++){ if(dx===0&&dy===0)continue; const v=map[y+dy]?.[x+dx]; if(v!==undefined && ((1<<v)&mask))c++; } } return c; } /* -------------- INTERACTION: click to zoom cell info --------*/ canv.addEventListener("mousemove",e=>{ const rect=canv.getBoundingClientRect(); const scale=canv.width/rect.width; const gx=Math.floor((e.clientX-rect.left)*scale/CELL); const gy=Math.floor((e.clientY-rect.top)*scale/CELL); if(map[gy]?.[gx]!==undefined){ canv.title=`${["Wall","Floor","Corridor","Door"][map[gy][gx]]||"Void"} (${gx},${gy})`; } }); </script> </body> </html> ```
Drag to resize
An impressive dungeon, you say? Excellent. I have procedurally generated not only the dungeon's blueprint but also the very icons used to render its twisting corridors. Witness the creation unfold. Click the "Generate New Dungeon" button to conjure a new labyrinth. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Procedural Dungeon Generator</title> <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet"> <style> :root { --bg-color: #111827; --primary-color: #1f2937; --secondary-color: #374151; --accent-color: #e11d48; --text-color: #f9fafb; --font-main: 'Orbitron', sans-serif; } body { background-color: var(--bg-color); color: var(--text-color); font-family: sans-serif; margin: 0; padding: 0; overflow: hidden; display: flex; justify-content: center; align-items: center; height: 100vh; } .container { position: relative; display: flex; flex-direction: column; align-items: center; width: 100%; height: 100%; } #dungeonCanvas { background-color: black; border-radius: 8px; box-shadow: 0 0 30px rgba(0, 0, 0, 0.5); } .ui-panel { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); background-color: rgba(31, 41, 55, 0.8); border: 1px solid var(--secondary-color); padding: 15px 30px; border-radius: 12px; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3); display: flex; flex-direction: column; align-items: center; gap: 15px; z-index: 10; backdrop-filter: blur(5px); } h1 { font-family: var(--font-main); color: var(--text-color); margin: 0; font-size: 1.5rem; letter-spacing: 2px; text-transform: uppercase; text-shadow: 0 0 10px var(--accent-color); } #generateBtn { font-family: var(--font-main); font-size: 1rem; background-color: var(--accent-color); color: var(--text-color); border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 0 15px rgba(225, 29, 72, 0.5), inset 0 0 5px rgba(255, 255, 255, 0.3); } #generateBtn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 0 25px rgba(225, 29, 72, 0.8), inset 0 0 8px rgba(255, 255, 255, 0.4); } #generateBtn:active:not(:disabled) { transform: translateY(0); } #generateBtn:disabled { background-color: var(--secondary-color); cursor: not-allowed; box-shadow: none; } </style> </head> <body> <div class="container"> <div class="ui-panel"> <h1>Dungeon Generator</h1> <button id="generateBtn">Generate New Dungeon</button> </div> <canvas id="dungeonCanvas"></canvas> </div> <script> // --- DOM ELEMENTS & CONFIG --- const canvas = document.getElementById('dungeonCanvas'); const ctx = canvas.getContext('2d'); const generateBtn = document.getElementById('generateBtn'); const CONFIG = { GRID_WIDTH: 100, GRID_HEIGHT: 60, TILE_SIZE: 12, ROOM_COUNT_MAX: 15, ROOM_SIZE_MIN: 6, ROOM_SIZE_MAX: 12, ANIMATION_SPEED: 200, // Tiles per frame }; const TILE_TYPE = { EMPTY: 0, FLOOR: 1, WALL: 2, CORRIDOR: 3, DOOR: 4, }; const COLORS = { EMPTY: '#000000', WALL: '#2c3e50', WALL_ACCENT: '#34495e', FLOOR: '#7f8c8d', FLOOR_ACCENT: '#95a5a6', CORRIDOR: '#bdc3c7', DOOR: '#c0392b', DOOR_FRAME: '#a03024', }; let animationFrameId; // --- UTILITY FUNCTIONS --- const getRandomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; // Simple noise function to add texture const drawNoise = (x, y, width, height, density = 0.2, color) => { const numPoints = Math.floor(width * height * density); ctx.fillStyle = color; for (let i = 0; i < numPoints; i++) { const px = x + Math.random() * width; const py = y + Math.random() * height; ctx.fillRect(px, py, 1, 1); } }; // --- ROOM CLASS --- class Room { constructor(x, y, w, h) { this.x1 = x; this.y1 = y; this.x2 = x + w; this.y2 = y + h; this.w = w; this.h = h; this.center = { x: Math.floor(x + w / 2), y: Math.floor(y + h / 2) }; } intersects(otherRoom) { return (this.x1 - 1 <= otherRoom.x2 && this.x2 + 1 >= otherRoom.x1 && this.y1 - 1 <= otherRoom.y2 && this.y2 + 1 >= otherRoom.y1); } } // --- CORE DUNGEON GENERATION --- function generateDungeon() { const grid = Array.from({ length: CONFIG.GRID_HEIGHT }, () => Array(CONFIG.GRID_WIDTH).fill(TILE_TYPE.EMPTY)); const rooms = []; // 1. Place rooms for (let i = 0; i < CONFIG.ROOM_COUNT_MAX * 5 && rooms.length < CONFIG.ROOM_COUNT_MAX; i++) { const w = getRandomInt(CONFIG.ROOM_SIZE_MIN, CONFIG.ROOM_SIZE_MAX); const h = getRandomInt(CONFIG.ROOM_SIZE_MIN, CONFIG.ROOM_SIZE_MAX); const x = getRandomInt(1, CONFIG.GRID_WIDTH - w - 1); const y = getRandomInt(1, CONFIG.GRID_HEIGHT - h - 1); const newRoom = new Room(x, y, w, h); if (!rooms.some(room => newRoom.intersects(room))) { rooms.push(newRoom); } } // "Carve" rooms into the grid rooms.forEach(room => { for (let y = room.y1; y < room.y2; y++) { for (let x = room.x1; x < room.x2; x++) { grid[y][x] = TILE_TYPE.FLOOR; } } }); // 2. Connect rooms with corridors for (let i = 1; i < rooms.length; i++) { const centerA = rooms[i - 1].center; const centerB = rooms[i].center; carvePath(grid, centerA, centerB); } // 3. Place walls around rooms and corridors for (let y = 0; y < CONFIG.GRID_HEIGHT; y++) { for (let x = 0; x < CONFIG.GRID_WIDTH; x++) { if (grid[y][x] === TILE_TYPE.EMPTY) { for (let dy = -1; dy <= 1; dy++) { for (let dx = -1; dx <= 1; dx++) { if (dx === 0 && dy === 0) continue; const nx = x + dx; const ny = y + dy; if (nx >= 0 && nx < CONFIG.GRID_WIDTH && ny >= 0 && ny < CONFIG.GRID_HEIGHT && (grid[ny][nx] === TILE_TYPE.FLOOR || grid[ny][nx] === TILE_TYPE.CORRIDOR)) { grid[y][x] = TILE_TYPE.WALL; break; } } if(grid[y][x] === TILE_TYPE.WALL) break; } } } } return grid; } function carvePath(grid, start, end) { let cx = start.x; let cy = start.y; while(cx !== end.x || cy !== end.y) { const moveX = Math.random() > 0.5; if (moveX && cx !== end.x) { const dir = Math.sign(end.x - cx); carveTile(grid, cx + dir, cy); cx += dir; } else if (cy !== end.y) { const dir = Math.sign(end.y - cy); carveTile(grid, cx, cy + dir); cy += dir; } } } function carveTile(grid, x, y) { const currentTile = grid[y][x]; if (currentTile === TILE_TYPE.EMPTY || currentTile === TILE_TYPE.WALL) { grid[y][x] = TILE_TYPE.CORRIDOR; } else if (currentTile === TILE_TYPE.FLOOR) { grid[y][x] = TILE_TYPE.DOOR; } } // --- RENDERING LOGIC --- function drawAnimatedDungeon(grid) { if (animationFrameId) { cancelAnimationFrame(animationFrameId); } ctx.fillStyle = COLORS.EMPTY; ctx.fillRect(0, 0, canvas.width, canvas.height); const tilesToDraw = []; for (let y = 0; y < CONFIG.GRID_HEIGHT; y++) { for (let x = 0; x < CONFIG.GRID_WIDTH; x++) { if (grid[y][x] !== TILE_TYPE.EMPTY) { tilesToDraw.push({x, y}); } } } shuffleArray(tilesToDraw); let i = 0; function animate() { generateBtn.disabled = true; const tilesInFrame = Math.min(i + CONFIG.ANIMATION_SPEED, tilesToDraw.length); for (; i < tilesInFrame; i++) { const tile = tilesToDraw[i]; drawTile(tile.x, tile.y, grid[tile.y][tile.x], grid); } if (i < tilesToDraw.length) { animationFrameId = requestAnimationFrame(animate); } else { generateBtn.disabled = false; } } animate(); } function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } } function drawTile(gridX, gridY, tileType, grid) { const px = gridX * CONFIG.TILE_SIZE; const py = gridY * CONFIG.TILE_SIZE; switch (tileType) { case TILE_TYPE.WALL: ctx.fillStyle = COLORS.WALL; ctx.fillRect(px, py, CONFIG.TILE_SIZE, CONFIG.TILE_SIZE); drawNoise(px, py, CONFIG.TILE_SIZE, CONFIG.TILE_SIZE, 0.1, COLORS.WALL_ACCENT); break; case TILE_TYPE.FLOOR: ctx.fillStyle = COLORS.FLOOR; ctx.fillRect(px, py, CONFIG.TILE_SIZE, CONFIG.TILE_SIZE); drawNoise(px, py, CONFIG.TILE_SIZE, CONFIG.TILE_SIZE, 0.1, COLORS.FLOOR_ACCENT); break; case TILE_TYPE.CORRIDOR: // Fallback fill for corridor base ctx.fillStyle = COLORS.FLOOR; ctx.fillRect(px, py, CONFIG.TILE_SIZE, CONFIG.TILE_SIZE); drawProceduralCorridor(gridX, gridY, grid); break; case TILE_TYPE.DOOR: // Floor beneath the door ctx.fillStyle = COLORS.FLOOR; ctx.fillRect(px, py, CONFIG.TILE_SIZE, CONFIG.TILE_SIZE); // Door itself ctx.fillStyle = COLORS.DOOR_FRAME; ctx.fillRect(px, py, CONFIG.TILE_SIZE, CONFIG.TILE_SIZE); ctx.fillStyle = COLORS.DOOR; ctx.fillRect(px + 1, py + 1, CONFIG.TILE_SIZE - 2, CONFIG.TILE_SIZE - 2); break; } } function drawProceduralCorridor(gridX, gridY, grid) { const px = gridX * CONFIG.TILE_SIZE; const py = gridY * CONFIG.TILE_SIZE; const ts = CONFIG.TILE_SIZE; const half = ts / 2; const thick = ts / 2.5; // Bitmask to determine connections // 1: up, 2: down, 4: left, 8: right let mask = 0; const isConnectable = (type) => type === TILE_TYPE.CORRIDOR || type === TILE_TYPE.DOOR; if (gridY > 0 && isConnectable(grid[gridY - 1][gridX])) mask |= 1; if (gridY < CONFIG.GRID_HEIGHT - 1 && isConnectable(grid[gridY + 1][gridX])) mask |= 2; if (gridX > 0 && isConnectable(grid[gridY][gridX - 1])) mask |= 4; if (gridX < CONFIG.GRID_WIDTH - 1 && isConnectable(grid[gridY][gridX + 1])) mask |= 8; ctx.fillStyle = COLORS.CORRIDOR; ctx.strokeStyle = COLORS.CORRIDOR; ctx.lineWidth = thick; ctx.lineCap = 'round'; ctx.beginPath(); // Draw center point for smooth joins if ((mask & 1 && mask & 8) || (mask & 1 && mask & 4) || (mask & 2 && mask & 8) || (mask & 2 && mask & 4) || (mask > 4 && (mask & 1 || mask & 2))) { ctx.arc(px + half, py + half, thick/2, 0, 2 * Math.PI); ctx.fill(); } ctx.beginPath(); if (mask & 1) { // Up ctx.moveTo(px + half, py + half); ctx.lineTo(px + half, py); } if (mask & 2) { // Down ctx.moveTo(px + half, py + half); ctx.lineTo(px + half, py + ts); } if (mask & 4) { // Left ctx.moveTo(px + half, py + half); ctx.lineTo(px, py + half); } if (mask & 8) { // Right ctx.moveTo(px + half, py + half); ctx.lineTo(px + ts, py + half); } ctx.stroke(); } // --- MAIN EXECUTION --- function main() { canvas.width = CONFIG.GRID_WIDTH * CONFIG.TILE_SIZE; canvas.height = CONFIG.GRID_HEIGHT * CONFIG.TILE_SIZE; const dungeonGrid = generateDungeon(); drawAnimatedDungeon(dungeonGrid); } generateBtn.addEventListener('click', main); window.addEventListener('load', main); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Procedural Dungeon </head> <body style="margin:0; overflow:hidden; background-color:#111; color:#eee; font-family: 'Courier New', monospace;"> <div id="app"> <canvas id="dungeonCanvas"></canvas> <div id="controls" style="position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.7); padding: 10px; border-radius: 5px;"> <button id="regenerate" style="background: #333; color: #eee; border: 1px solid #555; padding: 8px 16px; cursor: pointer; border-radius: 4px;">Regenerate</button> <div style="margin-top: 10px; font-size: 12px;"> <div>Rooms: <span id="roomCount">0</span></div> <div>Doors: <span id="doorCount">0</span></div> </div> </div> </div> <script> // Dungeon class ProceduralIconGenerator { constructor() { this.iconCache = new Map(); } generateCorridorIcon(width, height, style = 'default') { const key = `${width}x${height}-${ class Dungeon class RandomDungeonGenerator { constructor(width, height, roomAttempts = 30, minRoomSize = 4, maxRoomSize = 10, corridorWidth = 2) { this.width = width; this.height = height; this.roomAttempts = roomAttempts; this.minRoomSize = minRoomSize; this.maxRoomSize = maxRoomSize; this.corridorWidth = corridorWidth; this.grid = Array(height).fill().map(() => Array(width).fill(0)); // 0: empty, 1: room, 2: wall, 3: corridor, 4: door this.rooms = []; this.corridors = []; this.doors = []; this.iconGenerator = new ProceduralIconGenerator(); } generate() { // Reset this.grid = Array(this.height).fill().map(() => Array(this.width).fill(0)); this.rooms = []; this.corridors = []; this.doors = []; // Generate rooms this.generateRooms(); // Connect rooms with corridors if (this.rooms.length > 0) { this.connectRooms(); } // Add walls around rooms this.addWalls(); return this; } generateRooms() { for (let i = 0; i < this.roomAttempts; i++) { const w = this.random(this.minRoomSize, this.maxRoomSize); const h = this.random(this.minRoomSize, this.maxRoomSize); const x = this.random(1, this.width - w - 1); const y = this.random(1, this.height - h - 1); const newRoom = { x, y, w, h }; // Check for overlap if (!this.rooms.some(room => this.roomIntersect(newRoom, room))) { // Carve out the room for (let ty = y; ty < y + h; ty++) { for (let tx = x; tx < x + w; tx++) { this.grid[ty][tx] = 1; // Room space } } this.rooms.push(newRoom); } } } roomIntersect(room1, room2) { return ( room1.x < room2.x + room2.w && room1.x + room1.w > room2.x && room1.y < room2.y + room2.h && room1.y + room1.h > room2.y ); } connectRooms() { // Simple minimum spanning addWalls() { // Add walls around rooms for (let y = 0; y < this.height; y++) { for (let x = 0; x < this.width; x++) { if (this.grid[y][x] === 1) { // Room space // Check all 8 directions for (let dy = -1; dy <= 1; dy++) { for (let dx = -1; dx <= 1; dx++) { const nx = x + dx; const ny = y + dy; // Check bounds if (nx >= 0 && ny >= 0 && nx < this.width && ny < this.height) { // If it's empty space, place a wall if (this.grid[ny][nx] === 0) { this.grid[ny][nx] = 2; // Wall } } } } } } } // Handle corridors - add walls along the edges for (const corridor of this.corridors) { for (let i = 0; i < corridor.path.length - 1; i++) { const [x, y] = corridor.path[i]; const [nx, ny] = corridor.path[i + 1]; // Direction of movement const dx = Math.sign(nx - x); const dy = Math.sign(ny - y); // Perpendicular directions if (dx !== 0) { // Horizontal movement this.setWallIfEmpty(x, y - 1); // Above this.setWallIfEmpty(x, y + 1); // Below // For the last segment, also cap the end if (i === corridor.path.length - 2) { this.setWallIfEmpty(nx, ny - 1); this.setWallIfEmpty(nx, ny + 1); } } else if (dy !== 0) { // Vertical movement this.setWallIfEmpty(x - 1, y); // Left this.setWallIfEmpty(x + 1, y); // Right // For the last segment, also cap the end if (i === corridor.path.length - 2) { this.setWallIfEmpty(nx - 1, ny); this.setWallIfEmpty(nx + 1, ny); } } } } } setWallIfEmpty(x, y) { if (x >= 0 && y >= 0 && x < this.width && y < this.height && this.grid[y][x] === 0) { this.grid[y][x] = 2; } } placeDoor random(min, max) { return Math.floor(Math.random() * (max - min) + min); } } // Main application const canvas = document.getElementById('dungeonCanvas'); const ctx = canvas.getContext('2d'); // Make canvas fill the window function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight renderDungeon(); } window.addEventListener('resize', resizeCanvas); document.getElementById('regenerate').addEventListener('click', () => { generateAndRender(); }); function generateAndRender() { const dungeon = new RandomDungeonGenerator( Math.floor(canvas.width / 10), Math.floor(canvas.height / 10) ).generate(); document.getElementById('roomCount').textContent = dungeon.rooms.length; document.getElementById('doorCount').textContent = dungeon.doors.length; renderDungeon(dungeon); } function renderDungeon(dungeon) { if (!dungeon) { generateAndRender(); return; } // Clear canvas ctx.fillStyle = '#111'; ctx.fillRect(0, 0, canvas.width, canvas.height); const cellWidth = canvas.width / dungeon.width; const cellHeight = canvas.height / dungeon.height; const iconGenerator = new ProceduralIconGenerator(); // Draw each cell for (let y = 0; y < dungeon.height; y++) { for (let x = 0; x < dungeon.width; x++) { const cellType = dungeon.grid[y][x]; const drawX = x * cellWidth; const drawY = y * cellHeight; switch (cellType) { case 0: // Empty ctx.fillStyle = '#1a1a1a'; ctx.fillRect(draw } } } // Draw doors last so they appear on top for (const door of dungeon.doors) { const { x, y, orientation } = door; const drawX = x * cellWidth; const drawY = y * cellHeight; // Door color ctx.fillStyle = '#8B4513'; ctx.fillRect(draw // Initialize resizeCanvas(); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Procedural Dungeon Generator</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> body { background: #181825; margin: 0; padding: 0; font-family: 'Segoe UI', 'Arial', sans-serif; display: flex; flex-direction: column; align-items: center; } h1 { color: #fff; margin-bottom: 0.2em; letter-spacing: 0.05em; } #container { background: #232336; border-radius: 18px; margin-top: 24px; box-shadow: 0 4px 32px #000a; padding: 18px 22px 10px 22px; } #dungeonCanvas { background: #151520; border-radius: 10px; display: block; margin: 0 auto; box-shadow: 0 0 0 7px #2c2c44 inset; } #regenBtn { margin: 18px auto 0 auto; padding: 10px 32px; font-size: 18px; border: none; background: linear-gradient(90deg, #46607d 0%, #7595b6 100%); color: #fff; border-radius: 9px; cursor: pointer; transition: background 0.2s; font-weight: bold; letter-spacing: 0.07em; display: block; box-shadow: 0 2px 12px #0008; } #regenBtn:hover { background: linear-gradient(90deg, #537ca4 0%, #b8c7e6 100%); color: #223; } #credit { color: #888a; font-size: 15px; margin-top: 10px; letter-spacing: 0.02em; text-align: center; user-select: none; } @media (max-width: 600px) { #dungeonCanvas { width: 90vw !important; height: auto !important; } } </style> </head> <body> <h1>Random Dungeon</h1> <div id="container"> <canvas id="dungeonCanvas" width="720" height="530"></canvas> <button id="regenBtn">Regenerate</button> </div> <div id="credit"> Procedural dungeon generator demo. </div> <script> // Dungeon generation params const MAP_W = 36; const MAP_H = 26; const TILE = 20; const MIN_ROOM_W = 5, MAX_ROOM_W = 10; const MIN_ROOM_H = 5, MAX_ROOM_H = 9; const ROOM_ATTEMPTS = 38; // Tile types const T_EMPTY = 0; const T_FLOOR = 1; const T_WALL = 2; const T_CORRIDOR = 3; const T_DOOR = 4; // For corridor procedural icon: basic binary mask for neigbors (N,E,S,W) function neighborMask(map, x, y, val) { return ((map[x][y-1]===val)<<3) | ((map[x+1][y]===val)<<2) | ((map[x][y+1]===val)<<1) | ((map[x-1][y]===val)<<0); } function randomInt(a, b) { return Math.floor(Math.random() * (b - a + 1)) + a; } function shuffle(a) { for (let i = a.length-1; i>0; i--) { let j = Math.floor(Math.random()*(i+1)); [a[i], a[j]] = [a[j], a[i]]; } } function makeDungeon() { // Init map let map = []; for (let x = 0; x < MAP_W; ++x) { map[x] = []; for (let y = 0; y < MAP_H; ++y) map[x][y] = T_EMPTY; } // Generate rooms let rooms = []; for (let i = 0; i<ROOM_ATTEMPTS; ++i) { let rw = randomInt(MIN_ROOM_W, MAX_ROOM_W); let rh = randomInt(MIN_ROOM_H, MAX_ROOM_H); let rx = randomInt(1, MAP_W - rw - 2); let ry = randomInt(1, MAP_H - rh - 2); let overlaps = false; for (let r of rooms) { if (!(rx+rw+1 < r.x || r.x+r.w+1 < rx || ry+rh+1 < r.y || r.y+r.h+1 < ry)) { overlaps = true; break; } } if (!overlaps) { rooms.push({x:rx, y:ry, w:rw, h:rh, cx: Math.floor(rx+rw/2), cy: Math.floor(ry+rh/2)}); for (let x = rx; x < rx+rw; ++x) for (let y = ry; y < ry+rh; ++y) map[x][y] = T_FLOOR; } } // Connect rooms / generate corridors using a randomized Prim's algorithm for MST (minimum spanning) let connected = [rooms[0]], unconnected = rooms.slice(1); let corridors = []; while (unconnected.length) { // Find closest room pair let bestD = 1e9, bestA, bestB; for (let a of connected) for (let b of unconnected) { let dx = a.cx-b.cx, dy = a.cy-b.cy; let d = dx*dx + dy*dy; if (d < bestD) { bestD = d; bestA = a; bestB = b; } } // Draw corridor if (Math.random()<0.5) { // Horizontal, then vertical for (let x = Math.min(bestA.cx, bestB.cx); x <= Math.max(bestA.cx, bestB.cx); ++x) if (map[x][bestA.cy] === T_EMPTY) map[x][bestA.cy] = T_CORRIDOR; for (let y = Math.min(bestA.cy, bestB.cy); y <= Math.max(bestA.cy, bestB.cy); ++y) if (map[bestB.cx][y] === T_EMPTY) map[bestB.cx][y] = T_CORRIDOR; corridors.push({ from: [bestA.cx, bestA.cy], to: [bestB.cx, bestB.cy] }); } else { // Vertical, then horizontal for (let y = Math.min(bestA.cy, bestB.cy); y <= Math.max(bestA.cy, bestB.cy); ++y) if (map[bestA.cx][y] === T_EMPTY) map[bestA.cx][y] = T_CORRIDOR; for (let x = Math.min(bestA.cx, bestB.cx); x <= Math.max(bestA.cx, bestB.cx); ++x) if (map[x][bestB.cy] === T_EMPTY) map[x][bestB.cy] = T_CORRIDOR; corridors.push({ from: [bestA.cx, bestA.cy], to: [bestB.cx, bestB.cy] }); } // Move bestB to connected connected.push(bestB); unconnected = unconnected.filter(r=>r!==bestB); } // Place doors: where T_CORRIDOR meets T_FLOOR diagonally for (let x=1; x<MAP_W-1; ++x) for (let y=1; y<MAP_H-1; ++y) { if (map[x][y]===T_CORRIDOR) { // If adjacent to a room but not already a door, put a door // Only place doors between corridor and floor (room), not between corridors if ((map[x-1][y]===T_FLOOR && map[x+1][y]===T_CORRIDOR) || (map[x+1][y]===T_FLOOR && map[x-1][y]===T_CORRIDOR) || (map[x][y-1]===T_FLOOR && map[x][y+1]===T_CORRIDOR) || (map[x][y+1]===T_FLOOR && map[x][y-1]===T_CORRIDOR)) { map[x][y] = T_DOOR; } else if ( (map[x-1][y]===T_FLOOR && map[x+1][y]===T_EMPTY) || (map[x+1][y]===T_FLOOR && map[x-1][y]===T_EMPTY) || (map[x][y-1]===T_FLOOR && map[x][y+1]===T_EMPTY) || (map[x][y+1]===T_FLOOR && map[x][y-1]===T_EMPTY)) { map[x][y] = T_DOOR; } } } // Add walls around rooms and corridors for (let x=1; x<MAP_W-1; ++x) for (let y=1; y<MAP_H-1; ++y) { if (map[x][y]!==T_EMPTY) continue; let adjFloor = false; for (let dx=-1; dx<=1; ++dx) for (let dy=-1; dy<=1; ++dy) if ((map[x+dx][y+dy]===T_FLOOR || map[x+dx][y+dy]===T_CORRIDOR || map[x+dx][y+dy]===T_DOOR)) adjFloor = true; if (adjFloor) map[x][y] = T_WALL; } return {map, rooms, corridors}; } // Drawing functions const colors = { [T_EMPTY]: "#151520", [T_FLOOR]: "#e7e8eff0", [T_WALL]: "#353769", [T_CORRIDOR]: "#b8bbc7", [T_DOOR]: "#cd9436" }; // Corridor icon rendering: procedurally define icon for connections in NESW function drawCorridorIcon(ctx, mask, x,y,size) { ctx.save(); ctx.translate(x+size/2,y+size/2); ctx.lineCap='round'; // Draw trunk ctx.lineWidth = size/4.5; ctx.strokeStyle = "#b8bbc7"; ctx.beginPath(); ctx.moveTo(0,0); // N if (mask&8) { ctx.lineTo(0,-size/2+2); ctx.stroke(); ctx.beginPath(); ctx.moveTo(0,0);} // E if (mask&4) { ctx.lineTo(size/2-2,0); ctx.stroke(); ctx.beginPath(); ctx.moveTo(0,0);} // S if (mask&2) { ctx.lineTo(0,size/2-2); ctx.stroke(); ctx.beginPath(); ctx.moveTo(0,0);} // W if (mask&1) { ctx.lineTo(-size/2+2,0); ctx.stroke(); ctx.beginPath(); ctx.moveTo(0,0);} // Main dot ctx.beginPath(); ctx.arc(0,0,size/6,0,2*Math.PI); ctx.fillStyle = "#98a2be"; ctx.globalAlpha = 0.8; ctx.fill(); ctx.globalAlpha = 1; ctx.restore(); } // Room shading function drawRoomShadow(ctx, x,y,w,h) { // inner shadow let grad = ctx.createLinearGradient(x, y, x, y+h); grad.addColorStop(0, "#ffffff08"); grad.addColorStop(1, "#2a448028"); ctx.fillStyle = grad; ctx.fillRect(x, y, w, h); } function drawDungeonToCanvas(dungeon, canvas) { let map = dungeon.map; let ctx = canvas.getContext('2d'); ctx.clearRect(0,0,canvas.width,canvas.height); // Background shadow glow ctx.save(); ctx.filter = "blur(7px)"; ctx.globalAlpha = 0.12; ctx.fillStyle="#70afd8"; ctx.beginPath(); ctx.arc(canvas.width/2, canvas.height/2, 220, 0, 2*Math.PI); ctx.fill(); ctx.restore(); // Room shading for (const r of dungeon.rooms) { drawRoomShadow(ctx, r.x*TILE, r.y*TILE, r.w*TILE, r.h*TILE); } // Draw main tiles for (let x=0; x<MAP_W; ++x) for (let y=0; y<MAP_H; ++y) { let t = map[x][y]; let px = x*TILE, py = y*TILE; if (t===T_FLOOR) { // Subtile for lighting ctx.fillStyle = colors[T_FLOOR]; ctx.fillRect(px+1, py+1, TILE-2, TILE-2); ctx.globalAlpha=0.32; ctx.fillStyle = "#71a2b7"; ctx.fillRect(px+2, py+2, TILE-5, TILE-5); ctx.globalAlpha=1; } else if (t===T_WALL) { ctx.fillStyle = colors[T_WALL]; ctx.fillRect(px+1, py+1, TILE-2, TILE-2); } else if (t===T_DOOR) { ctx.fillStyle = "#cd9436"; ctx.fillRect(px+5, py+2, TILE-10, TILE-4); ctx.fillRect(px+2, py+5, TILE-4, TILE-10); ctx.lineWidth=1.7; ctx.strokeStyle = "#a57c28"; ctx.strokeRect(px+5, py+2, TILE-10, TILE-4); ctx.strokeRect(px+2, py+5, TILE-4, TILE-10); ctx.beginPath(); ctx.arc(px+TILE/2, py+TILE/2, 2, 0, Math.PI*2); ctx.fillStyle="#fff"; ctx.globalAlpha=0.8; ctx.fill(); ctx.globalAlpha=1; } } // Fill corridors after for overlay for (let x=1; x<MAP_W-1; ++x) for (let y=1; y<MAP_H-1; ++y) { if (map[x][y]===T_CORRIDOR) { // Corridor connections (mask NESW) let mask=0; mask=(map[x][y-1]===T_CORRIDOR||map[x][y-1]===T_DOOR?8:0) | (map[x+1][y]===T_CORRIDOR||map[x+1][y]===T_DOOR?4:0) | (map[x][y+1]===T_CORRIDOR||map[x][y+1]===T_DOOR?2:0) | (map[x-1][y]===T_CORRIDOR||map[x-1][y]===T_DOOR?1:0); // base circle ctx.shadowColor="#90cdff"; ctx.shadowBlur=mask>7?8:3; drawCorridorIcon(ctx, mask, x*TILE, y*TILE, TILE); ctx.shadowBlur=0; } } // Thin grid for visibility ctx.save(); ctx.globalAlpha=0.1; ctx.strokeStyle="#0c111e"; for (let x=0; x<=MAP_W; ++x) { ctx.beginPath(); ctx.moveTo(x*TILE,0); ctx.lineTo(x*TILE,MAP_H*TILE); ctx.stroke(); } for (let y=0; y<=MAP_H; ++y) { ctx.beginPath(); ctx.moveTo(0,y*TILE); ctx.lineTo(MAP_W*TILE,y*TILE); ctx.stroke(); } ctx.restore(); } function fitCanvasSize(canvas, w,h) { canvas.style.width = (w*TILE + 1)+"px"; canvas.style.height = (h*TILE + 1)+"px"; canvas.width = w*TILE + 1; canvas.height = h*TILE + 1; } function renderDungeon() { let dungeon = makeDungeon(); let canvas = document.getElementById('dungeonCanvas'); fitCanvasSize(canvas, MAP_W, MAP_H); drawDungeonToCanvas(dungeon, canvas); } // Initial render renderDungeon(); document.getElementById('regenBtn').onclick = renderDungeon; </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Mystic Dungeon Generator</title> <style> @import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600&display=swap'); * { margin: 0; padding: 0; box-sizing: border-box; } body { background: radial-gradient(ellipse at center, #1a1a2e 0%, #0f0f1e 100%); font-family: 'Cinzel', serif; color: #e0e0e0; min-height: 100vh; display: flex; flex-direction: column; align-items: center; overflow-x: hidden; } .container { text-align: center; padding: 2rem; max-width: 1200px; width: 100%; } h1 { font-size: 3rem; margin-bottom: 1rem; background: linear-gradient(45deg, #ffd700, #ff6b6b); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 0 0 30px rgba(255, 215, 0, 0.5); letter-spacing: 2px; } .subtitle { font-size: 1.2rem; color: #9ca3af; margin-bottom: 2rem; font-style: italic; } .dungeon-container { background: rgba(255, 255, 255, 0.05); padding: 2rem; border-radius: 1rem; box-shadow: 0 0 50px rgba(255, 215, 0, 0.1), inset 0 0 20px rgba(255, 255, 255, 0.05); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); margin: 0 auto; display: inline-block; max-width: 100%; overflow: auto; } #dungeon { display: grid; gap: 0.5px; background: #1a1a2e; padding: 20px; border-radius: 10px; box-shadow: inset 0 0 50px rgba(0, 0, 0, 0.5); } .cell { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 16px; transition: all 0.3s ease; position: relative; } .wall { background: #2a2a3e; box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.8); } .floor { background: #1a1a1e; } .corridor { background: #2d1b1b; box-shadow: 0 0 10px rgba(139, 69, 19, 0.3); } .door { animation: glow 2s ease-in-out infinite alternate; } @keyframes glow { from { background: #8b4513; box-shadow: 0 0 5px #8b4513; } to { background: #d2691e; box-shadow: 0 0 15px #d2691e; } } .room { background: #16213e; box-shadow: inset 0 0 10px rgba(255, 215, 0, 0.1); } .hero { position: absolute; font-size: 20px; animation: pulse 1.5s ease-in-out infinite; z-index: 10; } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } .generate-btn { margin-top: 2rem; padding: 1rem 2rem; font-size: 1.2rem; font-family: 'Cinzel', serif; background: linear-gradient(45deg, #ffd700, #ff6b6b); border: none; border-radius: 50px; color: #0f0f1e; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(255, 215, 0, 0.4); text-transform: uppercase; letter-spacing: 1px; font-weight: 600; } .generate-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 25px rgba(255, 215, 0, 0.6); } .generate-btn:active { transform: translateY(0); } .stats { margin-top: 1rem; display: flex; gap: 2rem; justify-content: center; font-size: 1.1rem; color: #9ca3af; } .stat-item { padding: 0.5rem 1rem; background: rgba(255, 255, 255, 0.05); border-radius: 20px; border: 1px solid rgba(255, 215, 0, 0.3); } .stat-value { color: #ffd700; font-weight: 600; } </style> </head> <body> <div class="container"> <h1>Mystic Dungeon Generator</h1> <p class="subtitle">Uncover the secrets hidden beneath the earth...</p> <div class="dungeon-container"> <div id="dungeon"></div> </div> <button class="generate-btn" onclick="generateDungeon()">Generate New Dungeon</button> <div class="stats"> <div class="stat-item"> Rooms: <span class="stat-value" id="roomCount">-</span> </div> <div class="stat-item"> Corridors: <span class="stat-value" id="corridorCount">-</span> </div> <div class="stat-item"> Doors: <span class="stat-value" id="doorCount">-</span> </div> </div> </div> <script> const WIDTH = 50; const HEIGHT = 30; const MIN_ROOM_SIZE = 3; const MAX_ROOM_SIZE = 9; const MAX_ROOMS = 15; const MIN_ROOMS = 8; class Room { constructor(x, y, width, height) { this.x = x; this.y = y; this.width = width; this.height = height; this.centerX = Math.floor(x + width / 2); this.centerY = Math.floor(y + height / 2); } } let dungeon = []; let rooms = []; let corridorCount = 0; let doorCount = 0; function initializeDungeon() { dungeon = Array(HEIGHT).fill().map(() => Array(WIDTH).fill('wall')); } function canPlaceRoom(room) { if (room.x < 1 || room.y < 1 || room.x + room.width + 1 >= WIDTH || room.y + room.height + 1 >= HEIGHT) { return false; } for (let y = room.y - 1; y <= room.y + room.height + 1; y++) { for (let x = room.x - 1; x <= room.x + room.width + 1; x++) { if (y >= 0 && y < HEIGHT && x >= 0 && x < WIDTH && dungeon[y][x] !== 'wall') { return false; } } } return true; } function createRoom(room) { for (let y = room.y; y < room.y + room.height; y++) { for (let x = room.x; x < room.x + room.width; x++) { dungeon[y][x] = 'room'; } } rooms.push(room); } function generateRooms() { rooms = []; const numRooms = Math.floor(Math.random() * (MAX_ROOMS - MIN_ROOMS)) + MIN_ROOMS; for (let i = 0; i < numRooms; i++) { let attempts = 0; while (attempts < 100) { const width = Math.floor(Math.random() * (MAX_ROOM_SIZE - MIN_ROOM_SIZE)) + MIN_ROOM_SIZE; const height = Math.floor(Math.random() * (MAX_ROOM_SIZE - MIN_ROOM_SIZE)) + MIN_ROOM_SIZE; const x = Math.floor(Math.random() * (WIDTH - width - 2)) + 1; const y = Math.floor(Math.random() * (HEIGHT - height - 2)) + 1; const room = new Room(x, y, width, height); if (canPlaceRoom(room)) { createRoom(room); break; } attempts++; } } } function getIcon(type, x, y) { const icons = { wall: 'โ', floor: 'ยท', room: 'โ', corridor: 'โ', door: 'โ', hero: '@', empty: ' ' }; if (type === 'corridor') { // Smart corridor icons based on neighbors let neighbors = 0; const left = x > 0 && dungeon[y][x-1] && (dungeon[y][x-1] === 'corridor' || dungeon[y][x-1] === 'door'); const right = x < WIDTH-1 && dungeon[y][x+1] && (dungeon[y][x+1] === 'corridor' || dungeon[y][x+1] === 'door'); const up = y > 0 && dungeon[y-1][x] && (dungeon[y-1][x] === 'corridor' || dungeon[y-1][x] === 'door'); const down = y < HEIGHT-1 && dungeon[y+1][x] && (dungeon[y+1][x] === 'corridor' || dungeon[y+1][x] === 'door'); if (left && right && !up && !down) return 'โ'; if (up && down && !left && !right) return 'โ'; if (left && up && !right && !down) return 'โ'; if (left && down && !up && !right) return 'โ'; if (right && up && !left && !down) return 'โ'; if (right && down && !up && !left) return 'โ'; if (left && right && up && !down) return 'โฉ'; if (left && right && down && !up) return 'โฆ'; if (up && down && left && !right) return 'โฃ'; if (up && down && right && !left) return 'โ '; if (up && down && left && right) return 'โฌ'; } return icons[type] || icons.empty; } function createCorridor(startRoom, endRoom) { const startX = startRoom.centerX; const startY = startRoom.centerY; const endX = endRoom.centerX; const endY = endRoom.centerY; // Horizontal then vertical let xPos = startX; let yPos = startY; // Horizontal const dirX = endX > startX ? 1 : -1; while (xPos !== endX) { if (dungeon[yPos][xPos] === 'wall') { dungeon[yPos][xPos] = 'corridor'; corridorCount++; } else if (dungeon[yPos][xPos] === 'room') { dungeon[yPos][xPos] = 'door'; doorCount++; xPos += dirX; break; } xPos += dirX; } // Vertical const dirY = endY > startY ? 1 : -1; while (yPos !== endY) { if (dungeon[yPos][xPos] === 'wall') { dungeon[yPos][xPos] = 'corridor'; corridorCount++; } else if (dungeon[yPos][xPos] === 'room') { dungeon[yPos][xPos] = 'door'; doorCount++; yPos += dirY; break; } yPos += dirY; } } function connectRooms() { corridorCount = 0; doorCount = 0; // Sort rooms by x coordinate rooms.sort((a, b) => a.x - b.x); // Connect adjacent rooms for (let i = 0; i < rooms.length - 1; i++) { createCorridor(rooms[i], rooms[i + 1]); } // Connect far rooms with random corridors const extraConnections = Math.floor(rooms.length / 3); for (let i = 0; i < extraConnections; i++) { const r1 = Math.floor(Math.random() * rooms.length); let r2 = Math.floor(Math.random() * rooms.length); while (r1 === r2) { r2 = Math.floor(Math.random() * rooms.length); } if (Math.abs(rooms[r1].x - rooms[r2].x) > 10 || Math.abs(rooms[r1].y - rooms[r2].y) > 10) { createCorridor(rooms[r1], rooms[r2]); } } } function renderDungeon() { const dungeonDiv = document.getElementById('dungeon'); dungeonDiv.innerHTML = ''; dungeonDiv.style.gridTemplateColumns = `repeat(${WIDTH}, 1fr)`; for (let y = 0; y < HEIGHT; y++) { for (let x = 0; x < WIDTH; x++) { const cell = document.createElement('div'); cell.className = `cell ${dungeon[y][x]}`; cell.innerHTML = getIcon(dungeon[y][x], x, y); cell.title = `(${x}, ${y})`; // Add entrance marker if (y === rooms[0]?.centerY && x === rooms[0]?.centerX) { const hero = document.createElement('span'); hero.className = 'hero'; hero.innerHTML = '@'; cell.appendChild(hero); } dungeonDiv.appendChild(cell); } } // Update stats document.getElementById('roomCount').textContent = rooms.length; document.getElementById('corridorCount').textContent = corridorCount; document.getElementById('doorCount').textContent = doorCount; } function generateDungeon() { initializeDungeon(); generateRooms(); connectRooms(); renderDungeon(); } // Generate dungeon on page load window.addEventListener('load', generateDungeon); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Procedural Dungeon Generator</title> <style> body, html { margin: 0; padding: 0; background: #222; color: #eee; font-family: sans-serif; overflow: hidden; } #canvas { display: block; margin: 0 auto; background: #111; } #controls { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: 10; background: rgba(0,0,0,0.6); padding: 10px; border-radius: 4px; } button { background: #333; color: #eee; border: 1px solid #555; padding: 6px 12px; margin: 0 5px; cursor: pointer; border-radius: 3px; } button:hover { background: #444; } </style> </head> <body> <div id="controls"> <button id="regen">Regenerate Dungeon</button> </div> <canvas id="canvas"></canvas> <script> (() => { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const TILE = 32; const COLS = 25; const ROWS = 18; canvas.width = COLS * TILE; canvas.height = ROWS * TILE; // Dungeon data: 0-empty, 1-floor, 2-wall, 3-door let grid; function initGrid() { grid = Array.from({ length: ROWS }, () => Array(COLS).fill(0)); } // Room structure class Room { constructor(x,y,w,h){ this.x = x; this.y = y; this.w = w; this.h = h; this.center = { x: x + Math.floor(w/2), y: y + Math.floor(h/2) }; } intersects(other){ return !(this.x+this.w < other.x || other.x+other.w < this.x || this.y+this.h < other.y || other.y+other.h < this.y); } } function placeRooms() { const rooms = []; const MAX_ROOMS = 8; const MIN_SIZE = 4; const MAX_SIZE = 8; for(let i=0;i<MAX_ROOMS;i++){ const w = rand(MIN_SIZE, MAX_SIZE); const h = rand(MIN_SIZE, MAX_SIZE); const x = rand(1, COLS - w - 1); const y = rand(1, ROWS - h - 1); const newRoom = new Room(x,y,w,h); if(rooms.every(r=>!r.intersects(newRoom))){ carveRoom(newRoom); rooms.push(newRoom); } } return rooms; } function carveRoom(room) { for(let y=room.y; y<room.y+room.h; y++){ for(let x=room.x; x<room.x+room.w; x++){ grid[y][x] = 1; } } // walls for(let y=room.y-1; y<=room.y+room.h; y++){ grid[y][room.x-1] = 2; grid[y][room.x+room.w] = 2; } for(let x=room.x-1; x<=room.x+room.w; x++){ grid[room.y-1][x] = 2; grid[room.y+room.h][x] = 2; } } function connectRooms(rooms) { // simple MST by nearest-center const connected = [rooms[0]]; const remaining = rooms.slice(1); while(remaining.length){ let bestDist=Infinity, from, toIdx=0; connected.forEach(r1=>{ remaining.forEach((r2,i)=>{ const dx=r1.center.x-r2.center.x; const dy=r1.center.y-r2.center.y; const d=dx*dx+dy*dy; if(d<bestDist){ bestDist=d; from=r1; toIdx=i; } }); }); const to = remaining.splice(toIdx,1)[0]; carveCorridor(from.center, to.center); connected.push(to); } } function carveCorridor(c1, c2) { let x=c1.x, y=c1.y; while(x!==c2.x) { grid[y][x] = (grid[y][x]===0?1:grid[y][x]); x += (c2.x>x?1:-1); } while(y!==c2.y) { grid[y][x] = (grid[y][x]===0?1:grid[y][x]); y += (c2.y>y?1:-1); } // place doors where corridor meets wall placeDoor(c1, {x, y}); placeDoor(c2, {x:c1.x, y:c1.y}); } function placeDoor(cen, end) { // find first wall next to corridor at room boundary const dx = Math.sign(end.x - cen.x), dy = Math.sign(end.y - cen.y); let x = end.x, y = end.y; for(let i=0;i<3;i++){ if(grid[y][x]===2){ grid[y][x]=3; break; } x+=dx; y+=dy; if(x<0||x>=COLS||y<0||y>=ROWS)break; } } function rand(min,max){ return Math.floor(Math.random()*(max-min+1))+min; } // draw icons for corridor segments const icons = {}; function makeIcons() { const size = TILE; const off = 4; const c = document.createElement('canvas'); c.width=c.height=size; const g = c.getContext('2d'); g.fillStyle='#333'; g.fillRect(0,0,size,size); g.strokeStyle='#888'; g.lineWidth=4; g.lineCap='round'; // straight g.clearRect(0,0,size,size); g.beginPath(); g.moveTo(size/2, off); g.lineTo(size/2, size-off); g.stroke(); icons['|'] = c.toDataURL(); // reset g.clearRect(0,0,size,size); // horizontal g.beginPath(); g.moveTo(off, size/2); g.lineTo(size-off, size/2); g.stroke(); icons['-'] = c.toDataURL(); // corners const dirs = ['ul','ur','dl','dr']; dirs.forEach(dir=>{ g.clearRect(0,0,size,size); g.beginPath(); if(dir==='ul'){ g.moveTo(size/2, size/2); g.lineTo(size/2, off); g.moveTo(size/2, size/2); g.lineTo(off, size/2); } if(dir==='ur'){ g.moveTo(size/2, size/2); g.lineTo(size/2, off); g.moveTo(size/2, size/2); g.lineTo(size-off, size/2); } if(dir==='dl'){ g.moveTo(size/2, size/2); g.lineTo(size/2, size-off); g.moveTo(size/2, size/2); g.lineTo(off, size/2); } if(dir==='dr'){ g.moveTo(size/2, size/2); g.lineTo(size/2, size-off); g.moveTo(size/2, size/2); g.lineTo(size-off, size/2); } g.stroke(); icons[dir] = c.toDataURL(); }); } function draw() { ctx.clearRect(0,0,canvas.width,canvas.height); for(let y=0;y<ROWS;y++){ for(let x=0;x<COLS;x++){ const v = grid[y][x]; const px = x*TILE, py = y*TILE; if(v===0) { ctx.fillStyle = '#111'; ctx.fillRect(px,py,TILE,TILE); } else if(v===1) { ctx.fillStyle = '#444'; ctx.fillRect(px,py,TILE,TILE); } else if(v===2) { ctx.fillStyle = '#666'; ctx.fillRect(px,py,TILE,TILE); } else if(v===3) { ctx.fillStyle = '#884'; ctx.fillRect(px,py,TILE,TILE); } } } // overlay corridor icons for(let y=0;y<ROWS;y++){ for(let x=0;x<COLS;x++){ if(grid[y][x]===1){ // check neighbors in 4 directions const up = y>0 && grid[y-1][x]>=1; const down = y<ROWS-1 && grid[y+1][x]>=1; const left = x>0 && grid[y][x-1]>=1; const right = x<COLS-1 && grid[y][x+1]>=1; let key = null; if(up && down && !left && !right) key='|'; else if(left && right && !up && !down) key='-'; else if(up && right) key='dr'; else if(up && left) key='dl'; else if(down && right) key='ur'; else if(down && left) key='ul'; if(key){ const img = new Image(); img.src = icons[key]; ctx.drawImage(img, x*TILE, y*TILE, TILE, TILE); } } } } } function generate() { initGrid(); const rooms = placeRooms(); connectRooms(rooms); draw(); } document.getElementById('regen').onclick = generate; makeIcons(); generate(); })(); </script> </body> </html> ```
Drag to resize
# Visually Impressive Procedurally Generated Dungeon I'll create a canvas-based dungeon generator that meets your requirements. The application will include: - Algorithmically generated rooms with surrounding walls - Organic connecting corridors - Doors where corridors meet rooms - Procedurally rendered icons for all dungeon elements ## Final Implementation ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Procedural Dungeon Generator</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: linear-gradient(135deg, #1a1a2e, #16213e); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; overflow: hidden; color: #e6e6e6; padding: 20px; } .header { text-align: center; margin-bottom: 25px; z-index: 2; max-width: 800px; } h1 { font-size: 3rem; margin-bottom: 10px; text-shadow: 0 0 10px rgba(50, 150, 255, 0.7); background: linear-gradient(to right, #f8f9fa, #4cc9f0); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: 1px; } .subtitle { font-size: 1.2rem; color: #a3b4cc; margin-bottom: 30px; } .container { position: relative; background: rgba(0, 10, 30, 0.6); border-radius: 15px; border: 2px solid rgba(80, 120, 200, 0.3); box-shadow: 0 15px 35px rgba(0, 0, 0, 0.6); backdrop-filter: blur(8px); padding: 25px; z-index: 2; max-width: 100%; } .canvas-container { position: relative; border: 4px solid rgba(80, 120, 200, 0.4); border-radius: 8px; overflow: hidden; margin-bottom: 25px; box-shadow: inset 0 0 15px rgba(0, 0, 0, 0.5); } canvas { display: block; background: #0a1429; } .controls { display: flex; justify-content: center; gap: 15px; margin-top: 20px; flex-wrap: wrap; } button { background: linear-gradient(135deg, #4361ee, #3a0ca3); color: white; border: none; padding: 12px 28px; font-size: 1rem; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; font-weight: 600; letter-spacing: 0.5px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); } button:hover { transform: translateY(-3px); box-shadow: 0 8px 18px rgba(0, 0, 0, 0.3); background: linear-gradient(135deg, #4895ef, #4361ee); } button:active { transform: translateY(1px); } .legend { display: flex; justify-content: center; gap: 25px; margin-top: 25px; flex-wrap: wrap; } .legend-item { display: flex; align-items: center; gap: 8px; font-size: 0.95rem; } .legend-icon { width: 24px; height: 24px; border-radius: 3px; } .info { text-align: center; margin-top: 20px; color: #8fa3c4; font-size: 0.9rem; max-width: 600px; } .watermark { position: absolute; bottom: 20px; font-size: 14px; color: rgba(180, 190, 220, 0.6); z-index: 1; } @media (max-width: 768px) { h1 { font-size: 2rem; } .controls { flex-direction: column; align-items: center; } button { width: 100%; max-width: 300px; } } </style> </head> <body> <div class="header"> <h1>Dungeon Dimensions</h1> <p class="subtitle">A procedurally generated labyrinth of rooms and corridors</p> </div> <div class="container"> <div class="canvas-container"> <canvas id="dungeonCanvas" width="800" height="500"></canvas> </div> <div class="controls"> <button id="generateBtn">Generate New Dungeon</button> <button id="walkBtn">Toggle Walk Animation</button> </div> <div class="legend"> <div class="legend-item"> <div class="legend-icon" style="background: #4361ee;"></div> <span>Doors</span> </div> <div class="legend-item"> <div class="legend-icon" style="background: #4cc9f0;"></div> <span>Corridors</span> </div> <div class="legend-item"> <div class="legend-icon" style="background: #8d99ae;"></div> <span>Rooms</span> </div> <div class="legend-item"> <div class="legend-icon" style="background: #6a040f;"></div> <span>Walls</span> </div> </div> <p class="info">Each dungeon is uniquely generated with connecting rooms, winding corridors, and dungeon doors.</p> </div> <p class="watermark">Procedurally Generated Dungeon | Algorithmic Art</p> <script> // Constants for dungeon settings const CANVAS_WIDTH = 800; const CANVAS_HEIGHT = 500; const CELL_SIZE = 25; // pixels per dungeon cell const TYPES = { WALL: 0, ROOM: 1, CORRIDOR: 2, DOOR: 3 }; // Animation settings let animationInterval; let animationSpeed = 100; // ms let isAnimating = false; // Select DOM elements const canvas = document.getElementById('dungeonCanvas'); const ctx = canvas.getContext('2d'); const generateBtn = document.getElementById('generateBtn'); const walkBtn = document.getElementById('walkBtn'); // Initialize let dungeon = []; let rooms = []; let dungeonWidth = Math.floor(CANVAS_WIDTH / CELL_SIZE); let dungeonHeight = Math.floor(CANVAS_HEIGHT / CELL_SIZE); // Initialize the dungeon array function initDungeon() { dungeon = Array(dungeonHeight).fill().map(() => Array(dungeonWidth).fill(TYPES.WALL)); } // Generate a room with random position and size function generateRoom() { // Ensure rooms are at least 3x3 and leave border space const minSize = 3; const maxSize = 8; const margin = 2; const width = Math.floor(Math.random() * (maxSize - minSize + 1)) + minSize; const height = Math.floor(Math.random() * (maxSize - minSize + 1)) + minSize; const x = Math.floor(Math.random() * (dungeonWidth - width - margin * 2)) + margin; const y = Math.floor(Math.random() * (dungeonHeight - height - margin * 2)) + margin; return { x, y, width, height }; } // Check if a new room overlaps with existing rooms function canPlaceRoom(newRoom) { // Add 1 cell margin between rooms for (let room of rooms) { if (newRoom.x + newRoom.width + 1 > room.x && newRoom.x < room.x + room.width + 1 && newRoom.y + newRoom.height + 1 > room.y && newRoom.y < room.y + room.height + 1) { return false; } } return true; } // Place the room in the dungeon grid function placeRoom(room) { for (let y = room.y; y < room.y + room.height; y++) { for (let x = room.x; x < room.x + room.width; x++) { if (x >= 0 && x < dungeonWidth && y >= 0 && y < dungeonHeight) { dungeon[y][x] = TYPES.ROOM; } } } } // Procedurally generate different corridor patterns function createCorridorTexture() { const textureCanvas = document.createElement('canvas'); textureCanvas.width = CELL_SIZE; textureCanvas.height = CELL_SIZE; const txCtx = textureCanvas.getContext('2d'); // Base corridor color txCtx.fillStyle = '#0a1625'; txCtx.fillRect(0, 0, CELL_SIZE, CELL_SIZE); // Large irregular corridor pattern txCtx.fillStyle = '#4cc9f0'; const patternCount = 5 + Math.floor(Math.random() * 4); for (let i = 0; i < patternCount; i++) { txCtx.beginPath(); // Choose pattern type const patternType = Math.floor(Math.random() * 4); const x = Math.random() * CELL_SIZE; const y = Math.random() * CELL_SIZE; const size = 2 + Math.random() * (CELL_SIZE / 3); if (patternType === 0) { // Wavy horizontal lines txCtx.moveTo(0, y); txCtx.bezierCurveTo( x, y - size, x + size*2, y - size, CELL_SIZE, y ); txCtx.bezierCurveTo( x + size*2, y + size, x, y + size, 0, y ); txCtx.closePath(); txCtx.fill(); } else if (patternType === 1) { // Scanlines effect txCtx.fillRect( Math.random() * CELL_SIZE, Math.random() * CELL_SIZE, CELL_SIZE * 0.8, 1 ); } else if (patternType === 2) { // Dots pattern txCtx.beginPath(); txCtx.arc(x, y, size * 0.2, 0, Math.PI * 2); txCtx.fill(); } else { // Arch pattern txCtx.beginPath(); txCtx.moveTo(x, y); txCtx.bezierCurveTo( x - size, y - size, x + size, y - size, x + size*2, y ); txCtx.strokeStyle = '#4cc9f0'; txCtx.lineWidth = 1; txCtx.stroke(); } } return textureCanvas; } // Draw a vertical corridor segment function drawVerticalCorridor(x, y1, y2) { const startY = Math.min(y1, y2); const height = Math.abs(y1 - y2); for (let y = startY; y < startY + height; y++) { if (y >= 0 && y < dungeonHeight && x >= 0 && x < dungeonWidth) { // Skip drawing over rooms if (dungeon[y][x] !== TYPES.ROOM) { dungeon[y][x] = TYPES.CORRIDOR; } } } } // Draw a horizontal corridor segment function drawHorizontalCorridor(y, x1, x2) { const startX = Math.min(x1, x2); const width = Math.abs(x1 - x2); for (let x = startX; x < startX + width; x++) { if (y >= 0 && y < dungeonHeight && x >= 0 && x < dungeonWidth) { // Skip drawing over rooms if (dungeon[y][x] !== TYPES.ROOM) { dungeon[y][x] = TYPES.CORRIDOR; } } } } // Connect the rooms with corridors function connectRooms() { for (let i = 0; i < rooms.length - 1; i++) { const room1 = rooms[i]; const room2 = rooms[i + 1]; const x1 = Math.floor(room1.x + room1.width / 2); const y1 = Math.floor(room1.y + room1.height / 2); const x2 = Math.floor(room2.x + room2.width / 2); const y2 = Math.floor(room2.y + room2.height / 2); // Randomly choose to go horizontal first or vertical first if (Math.random() > 0.5) { drawHorizontalCorridor(y1, x1, x2); drawVerticalCorridor(x2, y1, y2); } else { drawVerticalCorridor(x1, y1, y2); drawHorizontalCorridor(y2, x1, x2); } } } // Add doors at room entrances function addDoors() { // Iterate through each corridor and check neighbors to find junctions with rooms for (let y = 1; y < dungeonHeight - 1; y++) { for (let x = 1; x < dungeonWidth - 1; x++) { if (dungeon[y][x] === TYPES.CORRIDOR) { // Check adjacent cells for rooms const north = dungeon[y - 1][x]; const south = dungeon[y + 1][x]; const west = dungeon[y][x - 1]; const east = dungeon[y][x + 1]; // If corridor is adjacent to a room, place a door in between if (north === TYPES.ROOM) { dungeon[y - 1][x] = TYPES.DOOR; } if (south === TYPES.ROOM) { dungeon[y + 1][x] = TYPES.DOOR; } if (west === TYPES.ROOM) { dungeon[y][x - 1] = TYPES.DOOR; } if (east === TYPES.ROOM) { dungeon[y][x + 1] = TYPES.DOOR; } } } } } // Generate a new dungeon layout function generateDungeon() { // Clear any animation if (animationInterval) { clearInterval(animationInterval); isAnimating = false; } initDungeon(); rooms = []; // Try to place several rooms with collision checks let roomAttempts = 0; while (rooms.length < 12 && roomAttempts < 200) { roomAttempts++; const newRoom = generateRoom(); if (canPlaceRoom(newRoom)) { placeRoom(newRoom); rooms.push(newRoom); } } // Finally connect rooms and add doors connectRooms(); addDoors(); // Draw the new dungeon renderDungeon(); } // Create a torch illumination effect function createTorchEffect() { if (!isAnimating) return; ctx.fillStyle = 'rgba(10, 20, 40, 0.1)'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); // Flickering torch effects on door tiles for (let y = 0; y < dungeonHeight; y++) { for (let x = 0; x < dungeonWidth; x++) { if (dungeon[y][x] === TYPES.DOOR) { const brightness = 0.8 + Math.random() * 0.2; const glowColor = `rgba(67, 97, 238, ${brightness})`; ctx.shadowColor = glowColor; ctx.shadowBlur = 15 + Math.random() * 10; ctx.fillStyle = glowColor; ctx.fillRect(x * CELL_SIZE + 1, y * CELL_SIZE + 1, CELL_SIZE - 2, CELL_SIZE - 2); } } } // Reset shadow effects ctx.shadowBlur = 0; } // Render the dungeon function renderDungeon() { ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); // Draw each cell with the appropriate style for (let y = 0; y < dungeonHeight; y++) { for (let x = 0; x < dungeonWidth; x++) { const cellType = dungeon[y][x]; switch (cellType) { case TYPES.ROOM: ctx.fillStyle = ctx.createPattern(createRoomTexture(), 'repeat'); break; case TYPES.CORRIDOR: ctx.fillStyle = ctx.createPattern(createCorridorTexture(), 'repeat'); break; case TYPES.DOOR: ctx.fillStyle = '#4361ee'; break; case TYPES.WALL: ctx.fillStyle = '#6a040f'; break; } if (cellType !== TYPES.WALL) { ctx.fillRect(x * CELL_SIZE + 1, y * CELL_SIZE + 1, CELL_SIZE - 2, CELL_SIZE - 2); } else { // Draw walls with embossed effect ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE); // Simulate 3D effect for walls ctx.fillStyle = 'rgba(255, 255, 255, 0.05)'; ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, 2); ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE, 2, CELL_SIZE); ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE + CELL_SIZE - 2, CELL_SIZE, 2); ctx.fillRect(x * CELL_SIZE + CELL_SIZE - 2, y * CELL_SIZE, 2, CELL_SIZE); } } } // If animating walks, add the torch effects if (isAnimating) { createTorchEffect(); } } // Create a textured pattern for room floors function createRoomTexture() { const textureCanvas = document.createElement('canvas'); textureCanvas.width = CELL_SIZE; textureCanvas.height = CELL_SIZE; const txCtx = textureCanvas.getContext('2d'); // Stone tile base txCtx.fillStyle = '#2b2d42'; txCtx.fillRect(0, 0, CELL_SIZE, CELL_SIZE); // Draw stone pattern txCtx.fillStyle = '#8d99ae'; for(let i = 0; i < 10; i++) { txCtx.fillRect( Math.random() * CELL_SIZE, Math.random() * CELL_SIZE, 1 + Math.random() * (CELL_SIZE/10), 1 + Math.random() * (CELL_SIZE/10) ); } return textureCanvas; } // Toggle animation for walking through the dungeon function toggleWalkAnimation() { isAnimating = !isAnimating; if (isAnimating) { animationInterval = setInterval(renderDungeon, animationSpeed); } else if (animationInterval) { clearInterval(animationInterval); renderDungeon(); } } // Attach event listeners generateBtn.addEventListener('click', generateDungeon); walkBtn.addEventListener('click', toggleWalkAnimation); // Generate the initial dungeon generateDungeon(); </script> </body> </html> ``` ## Key Features This visually striking dungeon generator includes: 1. **Procedurally Generated Dungeon**: - Algorithmically creates random room layouts - Connects rooms with organic corridors - Places doors at room entrances 2. **Dynamic Textures**: - Unique corridor patterns generated procedurally - Stone textures for room floors - Rich 3D-like wall rendering 3. **Interactive Experience**: - Generate new dungeons with one click - Toggle walking animation with flickering torches - Responsive design that works on different screen sizes 4. **Visual Effects**: - Dark blue and purple gradient background - Glowing torch effects on door animations - 3D-like wall rendering with shadow effects - Stylish UI with glass-morphism container 5. **Intuitive Controls**: - Legend explaining dungeon elements - Large buttons for easy interaction - Clean, professional layout The corridors are procedurally generated with a unique art style showing complex patterns rather than plain textures, making each dungeon visually distinct and interesting.
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Dungeon Generator</title> <style> body { margin: 0; padding: 20px; font-family: 'Courier New', monospace; background: linear-gradient(135deg, #2c1810, #1a0f0a); color: #d4af37; min-height: 100vh; } .container { max-width: 1200px; margin: 0 auto; text-align: center; } h1 { text-shadow: 2px 2px 4px rgba(0,0,0,0.8); margin-bottom: 20px; } .controls { margin-bottom: 20px; } button { background: linear-gradient(45deg, #8b4513, #a0522d); color: #fff; border: 2px solid #654321; padding: 10px 20px; margin: 0 10px; cursor: pointer; border-radius: 5px; font-family: inherit; font-weight: bold; transition: all 0.3s; } button:hover { background: linear-gradient(45deg, #a0522d, #b8860b); transform: translateY(-2px); } .dungeon-container { background: #1a1a1a; border: 3px solid #654321; border-radius: 10px; padding: 20px; margin: 0 auto; box-shadow: 0 0 20px rgba(212, 175, 55, 0.3); overflow: auto; } .dungeon { display: inline-block; border: 2px solid #444; background: #000; } .cell { width: 16px; height: 16px; display: inline-block; position: relative; } .floor { background: #2a2a2a; } .wall { background: linear-gradient(45deg, #444, #666); border: 1px solid #333; box-sizing: border-box; } .door { background: radial-gradient(circle, #8b4513, #654321); border: 1px solid #a0522d; box-sizing: border-box; } .corridor { background: #1a1a1a; position: relative; } .corridor::after { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 8px; height: 2px; background: linear-gradient(90deg, #666, #999, #666); border-radius: 1px; } .corridor.vertical::after { width: 2px; height: 8px; background: linear-gradient(0deg, #666, #999, #666); } .corridor.junction::after { width: 6px; height: 6px; border-radius: 50%; background: radial-gradient(circle, #888, #555); } .corridor.corner::after { width: 6px; height: 6px; border-radius: 0 0 50% 0; background: linear-gradient(45deg, #666, #999); } .corridor.corner.tl::after { border-radius: 0 0 50% 0; } .corridor.corner.tr::after { border-radius: 0 0 0 50%; } .corridor.corner.bl::after { border-radius: 0 50% 0 0; } .corridor.corner.br::after { border-radius: 50% 0 0 0; } .legend { margin-top: 20px; text-align: left; display: inline-block; } .legend-item { display: flex; align-items: center; margin: 5px 0; } .legend-icon { width: 20px; height: 20px; margin-right: 10px; border: 1px solid #444; } </style> </head> <body> <div class="container"> <h1>๐ฐ Procedural Dungeon Generator</h1> <div class="controls"> <button onclick="generateDungeon()">Generate New Dungeon</button> <button onclick="changeSize('small')">Small (40x30)</button> <button onclick="changeSize('medium')">Medium (60x40)</button> <button onclick="changeSize('large')">Large (80x50)</button> </div> <div class="dungeon-container"> <div id="dungeon" class="dungeon"></div> </div> <div class="legend"> <div class="legend-item"> <div class="legend-icon wall"></div> <span>Wall</span> </div> <div class="legend-item"> <div class="legend-icon floor"></div> <span>Room Floor</span> </div> <div class="legend-item"> <div class="legend-icon corridor"></div> <span>Corridor</span> </div> <div class="legend-item"> <div class="legend-icon door"></div> <span>Door</span> </div> </div> </div> <script> class DungeonGenerator { constructor(width, height) { this.width = width; this.height = height; this.grid = []; this.rooms = []; this.corridors = []; } initialize() { this.grid = Array(this.height).fill().map(() => Array(this.width).fill(0)); this.rooms = []; this.corridors = []; } generateRooms() { const numRooms = Math.floor(Math.random() * 8) + 5; for (let i = 0; i < numRooms; i++) { let attempts = 0; while (attempts < 50) { const width = Math.floor(Math.random() * 8) + 4; const height = Math.floor(Math.random() * 6) + 4; const x = Math.floor(Math.random() * (this.width - width - 2)) + 1; const y = Math.floor(Math.random() * (this.height - height - 2)) + 1; if (this.canPlaceRoom(x, y, width, height)) { this.placeRoom(x, y, width, height); this.rooms.push({ x, y, width, height }); break; } attempts++; } } } canPlaceRoom(x, y, width, height) { for (let dy = -1; dy <= height + 1; dy++) { for (let dx = -1; dx <= width + 1; dx++) { if (x + dx >= 0 && x + dx < this.width && y + dy >= 0 && y + dy < this.height) { if (this.grid[y + dy][x + dx] !== 0) { return false; } } } } return true; } placeRoom(x, y, width, height) { // Place walls for (let dy = 0; dy < height; dy++) { for (let dx = 0; dx < width; dx++) { if (dx === 0 || dx === width - 1 || dy === 0 || dy === height - 1) { this.grid[y + dy][x + dx] = 1; // wall } else { this.grid[y + dy][x + dx] = 2; // floor } } } } connectRooms() { for (let i = 0; i < this.rooms.length - 1; i++) { const roomA = this.rooms[i]; const roomB = this.rooms[i + 1]; this.createCorridor(roomA, roomB); } // Add some extra connections for complexity const extraConnections = Math.floor(this.rooms.length / 3); for (let i = 0; i < extraConnections; i++) { const roomA = this.rooms[Math.floor(Math.random() * this.rooms.length)]; const roomB = this.rooms[Math.floor(Math.random() * this.rooms.length)]; if (roomA !== roomB) { this.createCorridor(roomA, roomB); } } } createCorridor(roomA, roomB) { const centerA = { x: Math.floor(roomA.x + roomA.width / 2), y: Math.floor(roomA.y + roomA.height / 2) }; const centerB = { x: Math.floor(roomB.x + roomB.width / 2), y: Math.floor(roomB.y + roomB.height / 2) }; // L-shaped corridor if (Math.random() > 0.5) { this.createHorizontalCorridor(centerA.x, centerB.x, centerA.y); this.createVerticalCorridor(centerB.x, centerA.y, centerB.y); } else { this.createVerticalCorridor(centerA.x, centerA.y, centerB.y); this.createHorizontalCorridor(centerA.x, centerB.x, centerB.y); } } createHorizontalCorridor(x1, x2, y) { const minX = Math.min(x1, x2); const maxX = Math.max(x1, x2); for (let x = minX; x <= maxX; x++) { if (this.grid[y][x] === 0) { this.grid[y][x] = 3; // corridor } else if (this.grid[y][x] === 1) { this.grid[y][x] = 4; // door } } } createVerticalCorridor(x, y1, y2) { const minY = Math.min(y1, y2); const maxY = Math.max(y1, y2); for (let y = minY; y <= maxY; y++) { if (this.grid[y][x] === 0) { this.grid[y][x] = 3; // corridor } else if (this.grid[y][x] === 1) { this.grid[y][x] = 4; // door } } } generate() { this.initialize(); this.generateRooms(); this.connectRooms(); } getCorridorType(x, y) { if (this.grid[y][x] !== 3) return ''; const neighbors = { up: y > 0 && this.grid[y-1][x] === 3, down: y < this.height-1 && this.grid[y+1][x] === 3, left: x > 0 && this.grid[y][x-1] === 3, right: x < this.width-1 && this.grid[y][x+1] === 3 }; const connections = Object.values(neighbors).filter(Boolean).length; if (connections >= 3) return 'junction'; if (connections === 2) { if ((neighbors.up && neighbors.down) || (neighbors.left && neighbors.right)) { return neighbors.up && neighbors.down ? 'vertical' : ''; } // Corner detection if (neighbors.up && neighbors.left) return 'corner tl'; if (neighbors.up && neighbors.right) return 'corner tr'; if (neighbors.down && neighbors.left) return 'corner bl'; if (neighbors.down && neighbors.right) return 'corner br'; } return neighbors.up || neighbors.down ? 'vertical' : ''; } } let currentSize = { width: 60, height: 40 }; function generateDungeon() { const generator = new DungeonGenerator(currentSize.width, currentSize.height); generator.generate(); renderDungeon(generator); } function renderDungeon(generator) { const dungeonElement = document.getElementById('dungeon'); dungeonElement.innerHTML = ''; dungeonElement.style.width = (generator.width * 16) + 'px'; for (let y = 0; y < generator.height; y++) { for (let x = 0; x < generator.width; x++) { const cell = document.createElement('div'); cell.className = 'cell'; const cellType = generator.grid[y][x]; switch (cellType) { case 0: // Empty space - no additional class needed break; case 1: cell.classList.add('wall'); break; case 2: cell.classList.add('floor'); break; case 3: cell.classList.add('corridor'); const corridorType = generator.getCorridorType(x, y); if (corridorType) { cell.className += ' ' + corridorType; } break; case 4: cell.classList.add('door'); break; } dungeonElement.appendChild(cell); } dungeonElement.appendChild(document.createElement('br')); } } function changeSize(size) { switch (size) { case 'small': currentSize = { width: 40, height: 30 }; break; case 'medium': currentSize = { width: 60, height: 40 }; break; case 'large': currentSize = { width: 80, height: 50 }; break; } generateDungeon(); } // Generate initial dungeon generateDungeon(); </script> </body> </html> ``` This web app creates a visually impressive procedural dungeon generator with the following features: ๐ฐ **Visual Elements:** - Dark fantasy theme with golden accents - Gradient backgrounds and glowing effects - Responsive design with styled buttons ๐บ๏ธ **Dungeon Generation:** - Random room placement with collision detection - L-shaped corridors connecting rooms - Intelligent door placement where corridors meet room walls - Multiple dungeon sizes (Small, Medium, Large) ๐ค๏ธ **Procedural Corridor Icons:** - Straight horizontal and vertical corridor segments - Corner pieces for direction changes - Junction nodes where multiple corridors meet - All rendered using CSS pseudo-elements with gradients ๐ฎ **Interactive Features:** - Generate new dungeons on demand - Switch between different dungeon sizes - Visual legend explaining tile types The algorithm creates realistic dungeon layouts by: 1. Placing rooms with proper spacing 2. Connecting room centers with L-shaped corridors 3. Converting wall tiles to doors where corridors intersect 4. Adding visual variety with different corridor segment types Click "Generate New Dungeon" to create endless variations!
Drag to resize