about summary refs log tree commit diff stats
path: root/html/fps/game.js
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2025-04-09 22:29:57 -0400
committerelioat <elioat@tilde.institute>2025-04-09 22:29:57 -0400
commit6bab5e8daf7c09cc31882b0018c59ac0b9356c67 (patch)
tree14ad7db2168310636286ded314e0e50a088ead62 /html/fps/game.js
parent0b4e014792b6969a0627fcf8e651f236102027af (diff)
downloadtour-6bab5e8daf7c09cc31882b0018c59ac0b9356c67.tar.gz
*
Diffstat (limited to 'html/fps/game.js')
-rw-r--r--html/fps/game.js446
1 files changed, 372 insertions, 74 deletions
diff --git a/html/fps/game.js b/html/fps/game.js
index 0a18d79..bac8f9b 100644
--- a/html/fps/game.js
+++ b/html/fps/game.js
@@ -20,9 +20,12 @@ const GameState = {
     gun: {
         recoil: 0,
         lastShot: 0,
-        muzzleFlash: 0
+        muzzleFlash: 0,
+        slidePosition: 0,  // 0 = forward, 1 = back
+        tilt: 0  // 0 = normal, 1 = tilted
     },
-    shots: []
+    shots: [],
+    isStarted: false
 };
 
 // Level generation using a simple maze algorithm
@@ -31,7 +34,7 @@ const generateLevel = () => {
         Array(GameState.level.width).fill(1)
     );
     
-    // Create a larger starting room
+    // Create starting room
     const startRoomSize = 5;
     for (let y = 1; y < startRoomSize; y++) {
         for (let x = 1; x < startRoomSize; x++) {
@@ -39,9 +42,36 @@ const generateLevel = () => {
         }
     }
     
-    // Simple maze generation using depth-first search
+    // Add fewer random larger rooms
+    const numLargeRooms = 2; // Reduced from 3
+    for (let i = 0; i < numLargeRooms; i++) {
+        const roomWidth = Math.floor(Math.random() * 3) + 4; // 4-6 (reduced from 4-7)
+        const roomHeight = Math.floor(Math.random() * 3) + 4; // 4-6 (reduced from 4-7)
+        const roomX = Math.floor(Math.random() * (GameState.level.width - roomWidth - 2)) + 1;
+        const roomY = Math.floor(Math.random() * (GameState.level.height - roomHeight - 2)) + 1;
+        
+        // Create room
+        for (let y = roomY; y < roomY + roomHeight; y++) {
+            for (let x = roomX; x < roomX + roomWidth; x++) {
+                map[y][x] = 0;
+            }
+        }
+        
+        // Connect to maze
+        const connectX = roomX + Math.floor(roomWidth/2);
+        const connectY = roomY + Math.floor(roomHeight/2);
+        map[connectY][connectX] = 0;
+    }
+    
+    // Simple maze generation using depth-first search with single-cell hallways
     const carveMaze = (x, y) => {
+        // Ensure we're within bounds
+        if (x <= 0 || x >= GameState.level.width - 1 || y <= 0 || y >= GameState.level.height - 1) {
+            return;
+        }
+        
         map[y][x] = 0;
+        
         const directions = [
             [0, -2], [2, 0], [0, 2], [-2, 0]
         ].sort(() => Math.random() - 0.5);
@@ -52,7 +82,13 @@ const generateLevel = () => {
             if (nx > 0 && nx < GameState.level.width - 1 &&
                 ny > 0 && ny < GameState.level.height - 1 &&
                 map[ny][nx] === 1) {
-                map[y + dy/2][x + dx/2] = 0;
+                // Carve single-cell paths
+                const midX = x + Math.floor(dx/2);
+                const midY = y + Math.floor(dy/2);
+                if (midX >= 0 && midX < GameState.level.width &&
+                    midY >= 0 && midY < GameState.level.height) {
+                    map[midY][midX] = 0;
+                }
                 carveMaze(nx, ny);
             }
         }
@@ -86,12 +122,24 @@ const generateLevel = () => {
     
     // Place items in open spaces (not in starting room)
     GameState.items = [];
-    for (let i = 0; i < 10; i++) {
+    // Add health packs
+    for (let i = 0; i < 8; i++) {
         const pos = openSpaces[Math.floor(Math.random() * openSpaces.length)];
         GameState.items.push({
             x: pos.x,
             y: pos.y,
-            type: Math.random() > 0.5 ? 'ammo' : 'health'
+            type: 'health',
+            value: Math.floor(Math.random() * 5) + 1
+        });
+    }
+    // Add ammo packs
+    for (let i = 0; i < 6; i++) {
+        const pos = openSpaces[Math.floor(Math.random() * openSpaces.length)];
+        GameState.items.push({
+            x: pos.x,
+            y: pos.y,
+            type: 'ammo',
+            value: 5
         });
     }
     
@@ -103,7 +151,7 @@ const generateLevel = () => {
 
 // Player movement and controls
 const handlePlayerMovement = (keys) => {
-    const moveSpeed = 0.1;
+    const moveSpeed = 0.05;
     const rotateSpeed = 0.05;
     
     if (keys.w) {
@@ -115,12 +163,20 @@ const handlePlayerMovement = (keys) => {
         GameState.player.y -= Math.cos(GameState.player.angle) * moveSpeed;
     }
     if (keys.a) {
-        GameState.player.x -= Math.sin(GameState.player.angle - Math.PI/2) * moveSpeed;
-        GameState.player.y -= Math.cos(GameState.player.angle - Math.PI/2) * moveSpeed;
+        GameState.player.x -= Math.cos(GameState.player.angle) * moveSpeed;
+        GameState.player.y += Math.sin(GameState.player.angle) * moveSpeed;
     }
     if (keys.d) {
-        GameState.player.x += Math.sin(GameState.player.angle - Math.PI/2) * moveSpeed;
-        GameState.player.y += Math.cos(GameState.player.angle - Math.PI/2) * moveSpeed;
+        GameState.player.x += Math.cos(GameState.player.angle) * moveSpeed;
+        GameState.player.y -= Math.sin(GameState.player.angle) * moveSpeed;
+    }
+    
+    // Add arrow key rotation
+    if (keys.ArrowLeft) {
+        GameState.player.angle -= rotateSpeed;
+    }
+    if (keys.ArrowRight) {
+        GameState.player.angle += rotateSpeed;
     }
 };
 
@@ -163,9 +219,19 @@ const updateEnemies = () => {
 
 // Collision detection
 const checkCollisions = () => {
-    // Wall collisions with improved precision
+    // Wall collisions with improved precision and bounds checking
     const playerX = GameState.player.x;
     const playerY = GameState.player.y;
+    
+    // Check if player is within map bounds
+    if (playerX < 0 || playerX >= GameState.level.width ||
+        playerY < 0 || playerY >= GameState.level.height) {
+        // Push player back to last valid position
+        GameState.player.x = Math.max(0, Math.min(GameState.player.x, GameState.level.width - 1));
+        GameState.player.y = Math.max(0, Math.min(GameState.player.y, GameState.level.height - 1));
+        return;
+    }
+    
     const nextX = playerX + Math.sin(GameState.player.angle) * 0.1;
     const nextY = playerY + Math.cos(GameState.player.angle) * 0.1;
     
@@ -173,6 +239,11 @@ const checkCollisions = () => {
     const checkPoint = (x, y) => {
         const gridX = Math.floor(x);
         const gridY = Math.floor(y);
+        // Ensure we're within bounds
+        if (gridX < 0 || gridX >= GameState.level.width ||
+            gridY < 0 || gridY >= GameState.level.height) {
+            return true; // Treat out of bounds as a wall
+        }
         return GameState.level.map[gridY][gridX] === 1;
     };
     
@@ -193,9 +264,9 @@ const checkCollisions = () => {
         const dy = GameState.player.y - item.y;
         if (Math.sqrt(dx * dx + dy * dy) < 0.5) {
             if (item.type === 'ammo') {
-                GameState.player.ammo += 5;
+                GameState.player.ammo += item.value;
             } else {
-                GameState.player.health = Math.min(100, GameState.player.health + 25);
+                GameState.player.health = Math.min(100, GameState.player.health + item.value);
             }
             return false;
         }
@@ -221,6 +292,33 @@ const render = (ctx) => {
     ctx.fillStyle = '#000';
     ctx.fillRect(0, 0, width, height);
     
+    if (!GameState.isStarted) {
+        // Draw start screen
+        ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
+        ctx.fillRect(0, 0, width, height);
+        
+        // Draw title
+        ctx.fillStyle = '#fff';
+        ctx.font = '48px monospace';
+        ctx.textAlign = 'center';
+        ctx.fillText('Hedge Maze', width/2, height/2 - 50);
+        
+        // Draw start button
+        const buttonWidth = 200;
+        const buttonHeight = 60;
+        const buttonX = width/2 - buttonWidth/2;
+        const buttonY = height/2;
+        
+        ctx.fillStyle = '#333';
+        ctx.fillRect(buttonX, buttonY, buttonWidth, buttonHeight);
+        
+        ctx.fillStyle = '#fff';
+        ctx.font = '24px monospace';
+        ctx.fillText('START GAME', width/2, buttonY + buttonHeight/2 + 8);
+        
+        return;
+    }
+    
     // Draw ceiling
     ctx.fillStyle = '#1a1a4a';
     ctx.fillRect(0, 0, width, height/2);
@@ -229,16 +327,18 @@ const render = (ctx) => {
     ctx.fillStyle = '#4a2a00';
     ctx.fillRect(0, height/2, width, height/2);
     
-    // Draw walls and enemies using ray casting
+    // Draw walls and sprites using ray casting
     const fov = Math.PI / 3;
     const numRays = width;
     const rayResults = [];
+    const sprites = [];
     
     for (let i = 0; i < numRays; i++) {
         const rayAngle = GameState.player.angle - fov/2 + fov * i / numRays;
         let distance = 0;
         let hit = false;
         let hitEnemy = null;
+        let hitItem = null;
         
         while (!hit && distance < 20) {
             distance += 0.1;
@@ -259,6 +359,32 @@ const render = (ctx) => {
                 });
                 if (hitEnemy) {
                     hit = true;
+                    sprites.push({
+                        type: 'enemy',
+                        x: i,
+                        distance,
+                        angle: rayAngle,
+                        data: hitEnemy
+                    });
+                }
+            }
+            
+            // Check for item hits
+            if (!hit) {
+                hitItem = GameState.items.find(item => {
+                    const dx = testX - item.x;
+                    const dy = testY - item.y;
+                    return Math.sqrt(dx * dx + dy * dy) < 0.5;
+                });
+                if (hitItem) {
+                    hit = true;
+                    sprites.push({
+                        type: 'item',
+                        x: i,
+                        distance,
+                        angle: rayAngle,
+                        data: hitItem
+                    });
                 }
             }
         }
@@ -277,6 +403,13 @@ const render = (ctx) => {
             const enemyColor = Math.floor(brightness * 255);
             ctx.fillStyle = `rgb(${enemyColor}, 0, 0)`;
             ctx.fillRect(i, height/2 - wallHeight/2, 1, wallHeight);
+        } else if (hitItem) {
+            // Draw item
+            const itemColor = hitItem.type === 'health' ? 
+                `rgb(0, ${Math.floor(brightness * 255)}, 0)` :
+                `rgb(0, ${Math.floor(brightness * 255)}, ${Math.floor(brightness * 255)})`;
+            ctx.fillStyle = itemColor;
+            ctx.fillRect(i, height/2 - wallHeight/2, 1, wallHeight);
         } else {
             // Draw wall
             const wallColor = Math.floor(brightness * 100);
@@ -313,26 +446,34 @@ const render = (ctx) => {
     // Draw gun
     const gunY = height - 150;
     const gunX = width/2;
-    const recoilOffset = Math.sin(GameState.gun.recoil * Math.PI) * 50; // Increased recoil range
+    const recoilOffset = Math.sin(GameState.gun.recoil * Math.PI) * 50;
+    const tiltAngle = GameState.gun.tilt * Math.PI / 6; // 30 degrees max tilt
     
     // Only draw gun if it's not in full recoil
     if (GameState.gun.recoil < 0.8) {
-        // Gun body
+        ctx.save();
+        ctx.translate(gunX, gunY + recoilOffset);
+        ctx.rotate(tiltAngle);
+        
+        // Gun body (larger rectangle)
         ctx.fillStyle = '#333';
-        ctx.fillRect(gunX - 30, gunY + recoilOffset, 60, 90);
+        ctx.fillRect(-30, 0, 60, 90);
         
-        // Gun barrel
+        // Gun slide (smaller rectangle) with sliding animation
+        const slideOffset = GameState.gun.slidePosition * 20;
         ctx.fillStyle = '#222';
-        ctx.fillRect(gunX - 8, gunY - 30 + recoilOffset, 16, 60);
+        ctx.fillRect(-8, -30 - slideOffset, 16, 60);
         
         // Muzzle flash
         if (GameState.gun.muzzleFlash > 0) {
             const flashSize = GameState.gun.muzzleFlash * 30;
             ctx.fillStyle = `rgba(255, 255, 0, ${GameState.gun.muzzleFlash})`;
             ctx.beginPath();
-            ctx.arc(gunX, gunY - 30 + recoilOffset, flashSize, 0, Math.PI * 2);
+            ctx.arc(0, -30 - slideOffset, flashSize, 0, Math.PI * 2);
             ctx.fill();
         }
+        
+        ctx.restore();
     }
     
     // Draw crosshair
@@ -350,32 +491,144 @@ const render = (ctx) => {
     ctx.fillText(`Ammo: ${GameState.player.ammo}`, 20, 80);
     ctx.fillText(`Score: ${GameState.player.score}`, 20, 115);
     
-    // Draw enemy health bars
-    GameState.enemies.forEach(enemy => {
-        const dx = enemy.x - GameState.player.x;
-        const dy = enemy.y - GameState.player.y;
-        const angle = Math.atan2(dx, dy) - GameState.player.angle;
-        const screenX = (angle / fov + 0.5) * width;
+    // Draw sprites with proper wall occlusion
+    sprites.forEach(sprite => {
+        const spriteHeight = height / (sprite.distance * Math.cos(sprite.angle - GameState.player.angle));
+        const spriteWidth = spriteHeight;
+        const spriteX = sprite.x - spriteWidth/2;
+        const spriteY = height/2 - spriteHeight/2;
         
-        if (screenX >= 0 && screenX < width) {
-            const distance = Math.sqrt(dx * dx + dy * dy);
-            const height = canvas.height / (distance * Math.cos(angle));
-            const y = canvas.height/2 - height/2;
+        // Only draw if not occluded by a wall
+        if (sprite.distance < rayResults[sprite.x].distance) {
+            ctx.save();
+            ctx.translate(spriteX + spriteWidth/2, spriteY + spriteHeight/2);
+            
+            // Enable anti-aliasing for clean edges
+            ctx.imageSmoothingEnabled = true;
             
-            // Health bar background
-            ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
-            ctx.fillRect(screenX - 10, y - 20, 20, 5);
+            if (sprite.type === 'enemy') {
+                // Draw enemy as red square
+                ctx.fillStyle = '#f00';
+                ctx.fillRect(-spriteWidth/2, -spriteHeight/2, spriteWidth, spriteHeight);
+            } else if (sprite.data.type === 'health') {
+                // Draw health as golden square
+                ctx.fillStyle = '#ffd700';
+                ctx.fillRect(-spriteWidth/2, -spriteHeight/2, spriteWidth, spriteHeight);
+            } else if (sprite.data.type === 'ammo') {
+                // Draw ammo as teal square
+                ctx.fillStyle = '#0ff';
+                ctx.fillRect(-spriteWidth/2, -spriteHeight/2, spriteWidth, spriteHeight);
+            } else if (sprite.data.type === 'flag') {
+                // Draw flag as blue diamond
+                ctx.fillStyle = '#00f';
+                ctx.beginPath();
+                ctx.moveTo(0, -spriteHeight/2);
+                ctx.lineTo(spriteWidth/2, 0);
+                ctx.lineTo(0, spriteHeight/2);
+                ctx.lineTo(-spriteWidth/2, 0);
+                ctx.closePath();
+                ctx.fill();
+            }
             
-            // Health bar
-            const healthPercent = enemy.health / 5; // Assuming max health is 5
-            ctx.fillStyle = `rgb(${255 * (1 - healthPercent)}, ${255 * healthPercent}, 0)`;
-            ctx.fillRect(screenX - 10, y - 20, 20 * healthPercent, 5);
+            ctx.restore();
         }
     });
+    
+    // Draw mini-map
+    const miniMapSize = 150;
+    const miniMapX = width - miniMapSize - 10;
+    const miniMapY = 10;
+    const cellSize = miniMapSize / GameState.level.width;
+    
+    // Draw mini-map background
+    ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
+    ctx.fillRect(miniMapX, miniMapY, miniMapSize, miniMapSize);
+    
+    // Draw walls
+    ctx.fillStyle = '#fff';
+    for (let y = 0; y < GameState.level.height; y++) {
+        for (let x = 0; x < GameState.level.width; x++) {
+            if (GameState.level.map[y][x] === 1) {
+                ctx.fillRect(
+                    miniMapX + x * cellSize,
+                    miniMapY + y * cellSize,
+                    cellSize,
+                    cellSize
+                );
+            }
+        }
+    }
+    
+    // Draw flag
+    ctx.fillStyle = '#ff0';
+    ctx.beginPath();
+    ctx.arc(
+        miniMapX + GameState.level.flag.x * cellSize + cellSize/2,
+        miniMapY + GameState.level.flag.y * cellSize + cellSize/2,
+        cellSize/2,
+        0,
+        Math.PI * 2
+    );
+    ctx.fill();
+    
+    // Draw items in mini-map with matching colors
+    GameState.items.forEach(item => {
+        if (item.type === 'health') {
+            ctx.fillStyle = '#ffd700'; // Golden
+        } else if (item.type === 'ammo') {
+            ctx.fillStyle = '#0ff'; // Teal
+        }
+        ctx.beginPath();
+        ctx.arc(
+            miniMapX + item.x * cellSize + cellSize/2,
+            miniMapY + item.y * cellSize + cellSize/2,
+            cellSize/3,
+            0,
+            Math.PI * 2
+        );
+        ctx.fill();
+    });
+    
+    // Draw enemies in mini-map
+    ctx.fillStyle = '#f00'; // Red
+    GameState.enemies.forEach(enemy => {
+        ctx.beginPath();
+        ctx.arc(
+            miniMapX + enemy.x * cellSize + cellSize/2,
+            miniMapY + enemy.y * cellSize + cellSize/2,
+            cellSize/2,
+            0,
+            Math.PI * 2
+        );
+        ctx.fill();
+    });
+    
+    // Draw player as a triangle
+    ctx.fillStyle = '#00f';
+    const playerX = miniMapX + GameState.player.x * cellSize;
+    const playerY = miniMapY + GameState.player.y * cellSize;
+    const size = cellSize;
+    
+    ctx.save();
+    ctx.translate(playerX, playerY);
+    ctx.rotate(GameState.player.angle);
+    ctx.beginPath();
+    ctx.moveTo(0, -size/2);
+    ctx.lineTo(size/2, size/2);
+    ctx.lineTo(-size/2, size/2);
+    ctx.closePath();
+    ctx.fill();
+    ctx.restore();
 };
 
 // Game loop
 const gameLoop = (ctx) => {
+    if (!GameState.isStarted) {
+        render(ctx);
+        requestAnimationFrame(() => gameLoop(ctx));
+        return;
+    }
+    
     if (GameState.isGameOver) {
         ctx.fillStyle = '#fff';
         ctx.font = '48px monospace';
@@ -383,46 +636,19 @@ const gameLoop = (ctx) => {
         return;
     }
     
-    // Update gun recoil and muzzle flash
-    if (GameState.gun.recoil > 0) {
-        GameState.gun.recoil -= 0.1;
-    }
-    if (GameState.gun.muzzleFlash > 0) {
-        GameState.gun.muzzleFlash -= 0.2;
-    }
-    
-    handlePlayerMovement(keys);
-    updateEnemies();
-    checkCollisions();
-    render(ctx);
-    
-    requestAnimationFrame(() => gameLoop(ctx));
-};
-
-// Input handling
-const keys = { w: false, a: false, s: false, d: false };
-document.addEventListener('keydown', e => {
-    if (e.key.toLowerCase() in keys) keys[e.key.toLowerCase()] = true;
-});
-document.addEventListener('keyup', e => {
-    if (e.key.toLowerCase() in keys) keys[e.key.toLowerCase()] = false;
-});
-
-document.addEventListener('mousemove', e => {
-    GameState.player.angle += e.movementX * 0.01;
-});
-
-document.addEventListener('click', () => {
-    if (GameState.player.ammo > 0) {
+    // Check for firing input
+    if ((keys[' '] || keys.e) && GameState.player.ammo > 0 && 
+        Date.now() - GameState.gun.lastShot > 200) { // 200ms cooldown
         GameState.player.ammo--;
         GameState.gun.recoil = 1;
         GameState.gun.muzzleFlash = 1;
+        GameState.gun.lastShot = Date.now();
         GameState.shots.push({
             time: Date.now(),
             angle: GameState.player.angle
         });
         
-        // Check for enemy hits with wall collision
+        // Check for enemy hits
         const fov = Math.PI / 3;
         const rayAngle = GameState.player.angle;
         let distance = 0;
@@ -434,13 +660,11 @@ document.addEventListener('click', () => {
             const testX = GameState.player.x + Math.sin(rayAngle) * distance;
             const testY = GameState.player.y + Math.cos(rayAngle) * distance;
             
-            // Check for wall hit first
             if (GameState.level.map[Math.floor(testY)][Math.floor(testX)] === 1) {
                 hitWall = true;
                 break;
             }
             
-            // Then check for enemy hit
             hitEnemy = GameState.enemies.find(enemy => {
                 const dx = testX - enemy.x;
                 const dy = testY - enemy.y;
@@ -452,6 +676,79 @@ document.addEventListener('click', () => {
             hitEnemy.health--;
         }
     }
+    
+    // Update gun recoil, muzzle flash, and slide position
+    if (GameState.gun.recoil > 0) {
+        GameState.gun.recoil -= 0.1;
+        // Animate slide moving back
+        GameState.gun.slidePosition = Math.min(1, GameState.gun.slidePosition + 0.2);
+    } else if (GameState.gun.slidePosition > 0) {
+        // Animate slide moving forward
+        GameState.gun.slidePosition = Math.max(0, GameState.gun.slidePosition - 0.1);
+    }
+    if (GameState.gun.muzzleFlash > 0) {
+        GameState.gun.muzzleFlash -= 0.2;
+    }
+
+    // Update gun tilt based on ammo
+    if (GameState.player.ammo === 0) {
+        GameState.gun.tilt = Math.min(1, GameState.gun.tilt + 0.1);
+    } else {
+        GameState.gun.tilt = Math.max(0, GameState.gun.tilt - 0.1);
+    }
+    
+    handlePlayerMovement(keys);
+    updateEnemies();
+    checkCollisions();
+    render(ctx);
+    
+    requestAnimationFrame(() => gameLoop(ctx));
+};
+
+// Input handling
+const keys = { 
+    w: false, 
+    a: false, 
+    s: false, 
+    d: false,
+    ArrowLeft: false,
+    ArrowRight: false,
+    ' ': false,  // Space bar
+    e: false     // E key
+};
+
+document.addEventListener('keydown', e => {
+    if (e.key.toLowerCase() in keys) keys[e.key.toLowerCase()] = true;
+    if (e.key in keys) keys[e.key] = true;
+});
+
+document.addEventListener('keyup', e => {
+    if (e.key.toLowerCase() in keys) keys[e.key.toLowerCase()] = false;
+    if (e.key in keys) keys[e.key] = false;
+});
+
+document.addEventListener('mousemove', e => {
+    GameState.player.angle += e.movementX * 0.01;
+});
+
+document.addEventListener('click', (e) => {
+    if (!GameState.isStarted) {
+        const canvas = document.getElementById('gameCanvas');
+        const rect = canvas.getBoundingClientRect();
+        const x = e.clientX - rect.left;
+        const y = e.clientY - rect.top;
+        
+        const buttonWidth = 200;
+        const buttonHeight = 60;
+        const buttonX = canvas.width/2 - buttonWidth/2;
+        const buttonY = canvas.height/2;
+        
+        if (x >= buttonX && x <= buttonX + buttonWidth &&
+            y >= buttonY && y <= buttonY + buttonHeight) {
+            GameState.isStarted = true;
+            generateLevel();
+        }
+    }
 });
 
 // Initialize game
@@ -461,7 +758,8 @@ const init = () => {
     canvas.height = window.innerHeight;
     const ctx = canvas.getContext('2d');
     
-    generateLevel();
+    // Don't generate level or start game loop until start button is clicked
+    render(ctx);
     gameLoop(ctx);
 };