about summary refs log blame commit diff stats
path: root/html/broughlike/index.html
blob: 0a732c5ffe8fae7eaf255a0a5a5546ed4ca55f92 (plain) (tree)
1
2
3
4
5
6
7
8
9
10




                                                                          

                                                                  


                                      
                               










                                      



























                                                                        


            



                                                                                                    















                                         

                                     

                                       

                                       



                                   
                            
                            
                               
 

                                                               









                                                             

                                
                             

                             


























                                                                                                            




                                                                                                                          

























                                                                                                                    



                                                                                                                                                























                                                                                                 

                                                                                    









                                                                                                          
                                                                                                                       



                                                                                   





























































                                                                                           












                                                                                       




                                                                                           


                                                                    
                                
                                                      

                                                             

                                                               

               
        









































                                                                                                   
                                        

                                                       
                                                                               








                                                                                              

                                             




                                        

























                                                                                         







                                                                                                          
                                                                                                      
                                        





                                                            
        

                                      

















                                                                                                        




                     












































                                                                                          








                                                                                                                         

                                                             
                                                                                   










                                                                                                                             
                                              
                                                                                       







                                                                                                  
                                    


                                             
                                                        



                                                                                            



                                                                 
                                    
                                   



                                                           



                                                                 
                                                                                                                                                                                                                                                                   











                                               
                                 
                                      



                            

                              
                            





                                                             






                                                                                 
                                                                       
                                                                         
                                                        
                                   

                                

                                  
                                





                           
                         


                          
                        







                               







                       











































                                                                  
                                                                                                                                          













                                                                                               

                          
                        



                 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Eli's Broughlike</title>
    <meta description="A Broughlike, or something like Flatland.">
    <style>
        body {
            background-color: #f0f0f0;
            font-size: x-large;
        }
        canvas {
            width: 90vw; 
            height: 90vw; 
            max-width: 600px; 
            max-height: 600px; 
            border: 2px solid black;
            display: block;
            margin: 0 auto;
            background-color: #f0f0f0;
        }
        @keyframes shake {
            0% { transform: translate(0.5px, 0.5px) rotate(0deg); }
            10% { transform: translate(-0.5px, -1px) rotate(-0.5deg); }
            20% { transform: translate(-1.5px, 0px) rotate(0.5deg); }
            30% { transform: translate(1.5px, 1px) rotate(0deg); }
            40% { transform: translate(0.5px, -0.5px) rotate(0.5deg); }
            50% { transform: translate(-0.5px, 1px) rotate(-0.5deg); }
            60% { transform: translate(-1.5px, 0.5px) rotate(0deg); }
            70% { transform: translate(1.5px, 0.5px) rotate(-0.5deg); }
            80% { transform: translate(-0.5px, -0.5px) rotate(0.5deg); }
            90% { transform: translate(0.5px, 1px) rotate(0deg); }
            100% { transform: translate(0.5px, -1px) rotate(-0.5deg); }
        }
        .shake {
            animation: shake 0.5s;
            animation-iteration-count: 1;
        }
        .header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0 0.5em;
            background-color: #f0f0f0;
        }
        .toggleShake {
            padding: 0.75em;
            margin: 0.5em;
        }
    </style>
</head>
<body>
    <div class="header">
        <p><a href="about.html">About</a></p>
        <button class="toggleShake" id="toggleShake" onclick="toggleShake()">Turn Shake Off</button>
    </div>
    <canvas id="gameCanvas"></canvas>
    <script>

        const COLORS = {
            grid: '#2d2d2d',
            walls: '#2d2d2d',
            exit: 'teal',
            diamond: 'gold',
            pentagon: 'blueviolet', 
            player: 'rgba(0, 237, 209, ',
            enemy: 'rgba(255, 155, 28, ',
            combatDotPlayer: '#00edd1',
            combatDotEnemy: '#ff731c'
        };

        const GRID_SIZE = 6;
        const PLAYER_HEALTH = 12;
        const PLAYER_MAX_HEALTH = 16;
        const PLAYER_BASE_DAMAGE = 1;
        const ENEMY_CHASE_DISTANCE = 4;
        const MIN_ENEMIES_ON_LEVEL = 1;
        const MAX_ENEMIES_ON_LEVEL = 4;
        const MAX_ENEMY_HEALTH = 7;
        const MIN_ENEMY_HEALTH = 2;
        const WALLS_MIN = 5;
        const WALLS_MAX = 20;
        const ITEMS_MIN = 1;
        const ITEMS_MAX = 3;
        const DOTS_PER_HIT = 7;

        let highScore = localStorage.getItem('highScore') || 0;

        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        let tileSize = canvas.width / GRID_SIZE;

        const player = {
            x: 0,
            y: 0,
            health: PLAYER_HEALTH,
            score: 0,
            damage: PLAYER_BASE_DAMAGE,
            totalDamageDone: 0,
            totalDamageTaken: 0,
            cellsTraveled: 0,
            killCount: 0,
            itemsCollected: 0
        };

        const exit = { x: Math.floor(Math.random() * GRID_SIZE), y: Math.floor(Math.random() * GRID_SIZE) };
        let walls = [];
        let enemies = [];
        let items = [];
        let combatDots = {};

        function isValidMove(newX, newY) {
            return (
                newX >= 0 && newX < GRID_SIZE &&
                newY >= 0 && newY < GRID_SIZE &&
                !walls.some(wall => wall.x === newX && wall.y === newY)
            );
        }

        function generateExit() {
            let distance = 0;
            do {
                exit.x = Math.floor(Math.random() * GRID_SIZE);
                exit.y = Math.floor(Math.random() * GRID_SIZE);
                distance = Math.abs(exit.x - player.x) + Math.abs(exit.y - player.y);
            } while (distance < 4);
        }

        function generateEnemies() {
            enemies = [];
            // Generate between 0 and MAX_ENEMIES_ON_LEVEL enemies if the player's score is 4 or lower
            // Generate between MIN_ENEMIES_ON_LEVEL and MAX_ENEMIES_ON_LEVEL enemies if the player's score is 5 or higher
            const numEnemies = player.score > 4 
                ? Math.floor(Math.random() * (MAX_ENEMIES_ON_LEVEL - MIN_ENEMIES_ON_LEVEL + 1)) + MIN_ENEMIES_ON_LEVEL 
                : Math.floor(Math.random() * (MAX_ENEMIES_ON_LEVEL + 1));
            for (let i = 0; i < numEnemies; i++) {
                let enemyX, enemyY;
                do {
                    enemyX = Math.floor(Math.random() * GRID_SIZE);
                    enemyY = Math.floor(Math.random() * GRID_SIZE);
                } while (
                    (enemyX === player.x && enemyY === player.y) ||
                    (enemyX === exit.x && enemyY === exit.y) ||
                    walls.some(wall => wall.x === enemyX && wall.y === enemyY)
                );
                enemies.push({
                    x: enemyX,
                    y: enemyY,
                    health: Math.floor(Math.random() * (MAX_ENEMY_HEALTH - MIN_ENEMY_HEALTH + 1)) + MIN_ENEMY_HEALTH
                });
            }
        }

        function generateWalls() {
            walls = [];
            let numWalls = Math.floor(Math.random() * (WALLS_MAX - WALLS_MIN + 1)) + WALLS_MIN;
            while (walls.length < numWalls) {
                const wallX = Math.floor(Math.random() * GRID_SIZE);
                const wallY = Math.floor(Math.random() * GRID_SIZE);

                if (
                    (wallX === player.x && wallY === player.y) ||                    // Check that a wall is not placed on the starting position
                    (wallX === exit.x && wallY === exit.y) ||                        // Check that a wall is not placed on the exit
                    enemies.some(enemy => enemy.x === wallX && enemy.y === wallY) || // Check that a wall is not placed on any enemies
                    items.some(item => item.x === wallX && item.y === wallY)         // Check that a wall is not placed on any items
                ) continue;

                if (!walls.some(wall => wall.x === wallX && wall.y === wallY)) {
                    walls.push({ x: wallX, y: wallY });
                }
            }

            if (!isPassable()) {
                generateWalls();
            }
        }

        function generateItems() {
            items = [];
            const numItems = Math.floor(Math.random() * (ITEMS_MAX - ITEMS_MIN + 1)) + ITEMS_MIN;
            for (let i = 0; i < numItems; i++) {
                let itemX, itemY;
                do {
                    itemX = Math.floor(Math.random() * GRID_SIZE);
                    itemY = Math.floor(Math.random() * GRID_SIZE);
                } while (
                    (itemX === player.x && itemY === player.y) ||
                    (itemX === exit.x && itemY === exit.y) ||
                    walls.some(wall => wall.x === itemX && wall.y === itemY) ||
                    enemies.some(enemy => enemy.x === itemX && enemy.y === itemY) ||
                    items.some(item => item.x === itemX && item.y === itemY)
                );
                const itemType = Math.random() < 0.5 ? 'diamond' : 'pentagon'; // 50% chance for each type
                items.push({ x: itemX, y: itemY, type: itemType });
            }
        }

        function isPassable() {
            const visited = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(false));

            function dfs(x, y) {
                if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE) return false; // Are the coordinates in bounds?
                if (visited[x][y]) return false;
                if (walls.some(wall => wall.x === x && wall.y === y)) return false;
                visited[x][y] = true;
                if (x === exit.x && y === exit.y) return true;
                return dfs(x + 1, y) || dfs(x - 1, y) || dfs(x, y + 1) || dfs(x, y - 1);
            }

            return dfs(player.x, player.y);
        }

        function drawGrid() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.strokeStyle = COLORS.grid;
            for (let row = 0; row < GRID_SIZE; row++) {
                for (let col = 0; col < GRID_SIZE; col++) {
                    ctx.strokeRect(col * tileSize, row * tileSize, tileSize, tileSize);
                }
            }
        }

        function drawExit() {
            const x = exit.x * tileSize + tileSize / 2;
            const y = exit.y * tileSize + tileSize / 2;
            ctx.beginPath();
            ctx.moveTo(x, y - tileSize / 3);
            ctx.lineTo(x + tileSize / 3, y + tileSize / 3);
            ctx.lineTo(x - tileSize / 3, y + tileSize / 3);
            ctx.closePath();
            ctx.fillStyle = COLORS.exit;
            ctx.fill();
        }

        function drawWalls() {
            ctx.fillStyle = COLORS.walls;
            walls.forEach(wall => {
                ctx.fillRect(wall.x * tileSize, wall.y * tileSize, tileSize, tileSize);
            });
        }

        function drawItems() {
            items.forEach(item => {
                const x = item.x * tileSize + tileSize / 2;
                const y = item.y * tileSize + tileSize / 2;
                ctx.fillStyle = item.type === 'diamond' ? COLORS.diamond : COLORS.pentagon;
                ctx.beginPath();
                if (item.type === 'diamond') {
                    ctx.moveTo(x, y - tileSize / 4);
                    ctx.lineTo(x + tileSize / 4, y);
                    ctx.lineTo(x, y + tileSize / 4);
                    ctx.lineTo(x - tileSize / 4, y);
                } else {
                    const sides = 5;
                    const radius = tileSize / 4;
                    for (let i = 0; i < sides; i++) {
                        const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;
                        const pointX = x + radius * Math.cos(angle);
                        const pointY = y + radius * Math.sin(angle);
                        if (i === 0) ctx.moveTo(pointX, pointY);
                        else ctx.lineTo(pointX, pointY);
                    }
                }
                ctx.closePath();
                ctx.fill();
            });
        }

        function drawCharacterBorder(x, y, radius, damageTaken) {
            const dashLength = 5;
            const gapLength = Math.max(1, damageTaken * 2); // More damage, larger gaps

            ctx.lineWidth = 2;
            ctx.strokeStyle = '#2d2d2d';
            ctx.setLineDash([dashLength, gapLength]);
            ctx.beginPath();
            ctx.arc(x, y, radius, 0, 2 * Math.PI);
            ctx.stroke();
            ctx.setLineDash([]); // Reset to a solid line
        }

        function drawEnemies() {
            enemies.forEach(enemy => {
                const x = enemy.x * tileSize + tileSize / 2;
                const y = enemy.y * tileSize + tileSize / 2;
                const opacity = enemy.health / MAX_ENEMY_HEALTH; // Opacity based on health
                const radius = tileSize / 3;
                const damageTaken = MAX_ENEMY_HEALTH - enemy.health;

                ctx.beginPath();
                ctx.arc(x, y, radius, 0, 2 * Math.PI);
                ctx.fillStyle = `${COLORS.enemy}${opacity})`;
                ctx.fill();

                drawCharacterBorder(x, y, radius, damageTaken);
            });
        }
        

        function drawPlayer() {
            const x = player.x * tileSize + tileSize / 2;
            const y = player.y * tileSize + tileSize / 2;
            const radius = tileSize / 3;
            const playerOpacity = player.health / PLAYER_HEALTH; // Opacity based on health
            ctx.beginPath();
            for (let i = 0; i < 6; i++) {
                const angle = (Math.PI / 3) * i;
                const hexX = x + radius * Math.cos(angle);
                const hexY = y + radius * Math.sin(angle);
                if (i === 0) {
                    ctx.moveTo(hexX, hexY);
                } else {
                    ctx.lineTo(hexX, hexY);
                }
            }
            ctx.closePath();
            ctx.fillStyle = `${COLORS.player}${playerOpacity})`;
            ctx.fill();
            ctx.lineWidth = 2;
            ctx.strokeStyle = '#2d2d2d';
            ctx.stroke();
        }

        function drawCombatDots() {
            for (const key in combatDots) {
                const [cellX, cellY] = key.split(',').map(Number);
                const dots = combatDots[key];
                dots.forEach(dot => {
                    ctx.beginPath();
                    ctx.arc(cellX * tileSize + dot.x, cellY * tileSize + dot.y, 2, 0, Math.PI * 2);
                    ctx.fillStyle = dot.color;
                    ctx.fill();
                    ctx.closePath();
                });
            }
        }

        function handleItemCollection() {
            const collectedItem = items.find(item => item.x === player.x && item.y === player.y);
            if (collectedItem) {
                player.itemsCollected++;
                if (collectedItem.type === 'diamond') {
                    player.damage += 3;
                    console.log("Collected diamond! +3 damage on this level.");
                } else if (collectedItem.type === 'pentagon') {
                    const healAmount = Math.floor(Math.random() * 2) + 1;
                    player.health = Math.min(player.health + healAmount, PLAYER_MAX_HEALTH);
                    console.log("Collected pentagon! Healed " + healAmount + " health.");
                }
                items = items.filter(item => item !== collectedItem); // Remove collected item
            }
        }

        function addCombatDots(x, y, color) {
            const cellsToFill = [
                [x, y],        // Center
                [x - 1, y],    // Left
                [x + 1, y],    // Right
                [x, y - 1],    // Up
                [x, y + 1],    // Down
                [x - 1, y - 1], // Top-left
                [x + 1, y - 1], // Top-right
                [x - 1, y + 1], // Bottom-left
                [x + 1, y + 1]  // Bottom-right
            ];

            cellsToFill.forEach(([cellX, cellY]) => {
                if (cellX >= 0 && cellX < GRID_SIZE && cellY >= 0 && cellY < GRID_SIZE) {
                    const key = `${cellX},${cellY}`;
                    if (!combatDots[key]) {
                        combatDots[key] = [];
                    }
                    for (let i = 0; i < DOTS_PER_HIT; i++) {
                        combatDots[key].push({
                            x: Math.random() * tileSize,
                            y: Math.random() * tileSize,
                            color: color
                        });
                    }
                }
            });
        }

        function movePlayer(dx, dy) {
            const newX = player.x + dx;
            const newY = player.y + dy;
            if (isValidMove(newX, newY) && !enemies.some(enemy => enemy.x === newX && enemy.y === newY)) {
                if (newX !== player.x || newY !== player.y) player.cellsTraveled++;
                player.x = newX;
                player.y = newY;
                handleItemCollection(); // Did the player collect an item?
                checkPlayerAtExit();    // Did the player get to the exit after moving?
            } else {
                // If an enemy is in the target cell, player stays put and does combat
                const enemyInTargetCell = enemies.find(enemy => enemy.x === newX && enemy.y === newY);
                if (enemyInTargetCell) {
                    handleDamage(player, enemyInTargetCell);
                }
            }
            moveEnemies();
            render();
        }
        
        function moveEnemies() {
            enemies.forEach(enemy => {
                const distanceToPlayer = Math.abs(enemy.x - player.x) + Math.abs(enemy.y - player.y);
                const distanceToExit = Math.abs(enemy.x - exit.x) + Math.abs(enemy.y - exit.y);
        
                const target = distanceToPlayer <= ENEMY_CHASE_DISTANCE ? player : exit;
                const path = findPath(enemy, target);
        
                if (path.length > 1) {
                    const nextStep = path[1];
                    const enemyInNextStep = enemies.find(e => e.x === nextStep.x && e.y === nextStep.y);
        
                    // Is the next step occupied by an enemy?
                    if (!enemyInNextStep && !(nextStep.x === player.x && nextStep.y === player.y)) {
                        // Move to the next place
                        enemy.x = nextStep.x;
                        enemy.y = nextStep.y;
                    } else if (nextStep.x === player.x && nextStep.y === player.y) {
                        // If the player is in the next step, stay put and do combat
                        handleDamage(player, enemy);
                    }
                }
            });
        }

        function findPath(start, goal) {
            const queue = [{ x: start.x, y: start.y, path: [] }];
            const visited = new Set();
            visited.add(`${start.x},${start.y}`);

            while (queue.length > 0) {
                const { x, y, path } = queue.shift();
                const newPath = [...path, { x, y }];

                if (x === goal.x && y === goal.y) {
                    return newPath;
                }

                const directions = [
                    { dx: 1, dy: 0 },
                    { dx: -1, dy: 0 },
                    { dx: 0, dy: 1 },
                    { dx: 0, dy: -1 }
                ];

                directions.forEach(({ dx, dy }) => {
                    const newX = x + dx;
                    const newY = y + dy;
                    const key = `${newX},${newY}`;

                    // Check if the new position is within the level and if it is passable
                    if (
                        newX >= 0 && newX < GRID_SIZE &&
                        newY >= 0 && newY < GRID_SIZE &&
                        // Have we already been here?
                        !visited.has(key) &&
                        // Is it a wall?
                        !walls.some(wall => wall.x === newX && wall.y === newY) &&
                        // Is there an enemy already there?
                        !enemies.some(enemy => enemy.x === newX && enemy.y === newY)
                    ) {
                        queue.push({ x: newX, y: newY, path: newPath });
                        visited.add(key);
                    }
                });
            }

            return [];
        }

        let combatAnimationEnabled = localStorage.getItem('combatAnimationEnabled');
        if (combatAnimationEnabled === null) {
            combatAnimationEnabled = true; // default to on...is that a good idea?
            localStorage.setItem('combatAnimationEnabled', combatAnimationEnabled);
        } else {
            combatAnimationEnabled = combatAnimationEnabled === 'true';
        }
        document.getElementById('toggleShake').textContent = combatAnimationEnabled ? 'Turn Shake Off' : 'Turn Shake On';

        function toggleShake() {
            combatAnimationEnabled = !combatAnimationEnabled;
            localStorage.setItem('combatAnimationEnabled', combatAnimationEnabled);
            document.getElementById('toggleShake').textContent = combatAnimationEnabled ? 'Turn Shake Off' : 'Turn Shake On';
        }

        function combatAnimation() {
            const canvas = document.getElementById('gameCanvas');
            canvas.classList.add('shake');
            canvas.addEventListener('animationend', () => {
                canvas.classList.remove('shake');
            }, { once: true });
        }

        function handleDamage(player, enemy) {
            const enemyMisses = Math.random() < 0.5; // 50% chance the enemy misses you
            const cellX = player.x;
            const cellY = player.y;

            if (!enemyMisses) {
                player.health--;
                player.totalDamageTaken++;
                addCombatDots(cellX, cellY, COLORS.combatDotPlayer); // Add dots for player damage
                console.log("Enemy hit! Player health: " + player.health);
            } else {                
                console.log("Enemy missed!");
            }

            enemy.health = enemy.health - player.damage;
            player.totalDamageDone++;
            addCombatDots(cellX, cellY, COLORS.combatDotEnemy); // Add dots for enemy damage
            console.log("Player hit! Enemy health: " + enemy.health);

            if (combatAnimationEnabled) {
                combatAnimation(); // Trigger the shake animation
            }

            if (enemy.health <= 0) {
                player.killCount++;
                enemies = enemies.filter(e => e !== enemy);
            }

            if (player.health <= 0) {
                if (player.score > highScore) {
                    highScore = player.score;
                    localStorage.setItem('highScore', highScore);
                }
                alert(`Score: ${player.score}\nDistance Traveled: ${player.cellsTraveled}\nTotal Damage Dealt: ${player.totalDamageDone}\nTotal Damage Received: ${player.totalDamageTaken}\nCircles Vanquished: ${player.killCount}\n\nHigh Score: ${highScore}`);
                resetGame();
            }
        }

        function resetGame() {
            player.health = PLAYER_HEALTH;
            player.damage = PLAYER_BASE_DAMAGE;
            player.bonusDamageTurns = 0;
            player.totalDamageDone = 0;
            player.totalDamageTaken = 0;
            player.cellsTraveled = 0;
            player.score = 0;
            player.killCount = 0;
            player.itemsCollected = 0;
            player.x = 0;
            player.y = 0;
            combatDots = {};
            generateExit();
            generateEnemies();
            generateItems();
            generateWalls();
            render();
        }

        function checkPlayerAtExit() {
            if (player.x === exit.x && player.y === exit.y) {
                player.score += 1;
                player.damage = PLAYER_BASE_DAMAGE;
                console.group("Level complete! " + player.score);
                console.log("Score: " + player.score);
                console.log("Current health: " + player.health);
                console.log("Distance Traveled: " + player.cellsTraveled);
                console.log("Total Damage Dealt: " + player.totalDamageDone);
                console.log("Total Damage Received: " + player.totalDamageTaken);
                console.log("Circles Vanquished: " + player.killCount);
                console.log("Items Collected: " + player.itemsCollected);
                console.log("High Score: " + highScore);
                console.groupEnd();
                combatDots = {};
                generateExit();
                generateEnemies();
                generateItems();
                generateWalls();
                render();
            }
        }

        function render() {
            drawGrid();
            drawPlayer();
            drawExit();
            drawItems();
            drawEnemies();
            drawWalls();
            drawCombatDots();
        }

        const directionMap = {
            ArrowUp: [0, -1],
            ArrowDown: [0, 1],
            ArrowLeft: [-1, 0],
            ArrowRight: [1, 0],
            w: [0, -1],
            s: [0, 1],
            a: [-1, 0],
            d: [1, 0],
            h: [-1, 0],
            j: [0, 1],
            k: [0, -1],
            l: [1, 0]
        };

        document.addEventListener('keydown', (e) => {
            const direction = directionMap[e.key];
            if (direction) {
                movePlayer(...direction);
                checkPlayerAtExit();
                render();
            }
        });

        let touchStartX = 0;
        let touchStartY = 0;

        canvas.addEventListener('touchstart', (e) => {
            e.preventDefault(); // Prevent scrolling on touchstart
            touchStartX = e.touches[0].clientX;
            touchStartY = e.touches[0].clientY;
        });

        canvas.addEventListener('touchend', (e) => {
            e.preventDefault(); // Prevent scrolling on touchend
            const touchEndX = e.changedTouches[0].clientX;
            const touchEndY = e.changedTouches[0].clientY;
            const dx = touchEndX - touchStartX;
            const dy = touchEndY - touchStartY;

            if (Math.abs(dx) > Math.abs(dy)) {
                // Horizontal swipe
                if (dx > 0) {
                    movePlayer(1, 0); // Swipe right
                } else {
                    movePlayer(-1, 0); // Swipe left
                }
            } else {
                // Vertical swipe
                if (dy > 0) {
                    movePlayer(0, 1); // Swipe down
                } else {
                    movePlayer(0, -1); // Swipe up
                }
            }

            render();
        }, { passive: false }); // TIL you can use passive set to false to help make preventDefault actually work? Feels like superstition

        const resizeCanvas = () => {
            const rect = canvas.getBoundingClientRect();
            canvas.width = rect.width;
            canvas.height = rect.height;
            tileSize = canvas.width / GRID_SIZE; // Update tile size based on canvas dimensions
            render();
        };

        window.addEventListener('resize', resizeCanvas);
        resizeCanvas();

        // Initial level setup
        generateExit();
        generateEnemies();
        generateItems();
        generateWalls();
        render();
    </script>
</body>
</html>