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:47:53 -0400
committerelioat <elioat@tilde.institute>2025-04-09 22:47:53 -0400
commita96cbb4bdec31d07d53c618d4f8e824dddb06538 (patch)
tree28bc9cb7f71aa57b498c6eb78bda6235aaa50ddf /html/fps/game.js
parent6bab5e8daf7c09cc31882b0018c59ac0b9356c67 (diff)
downloadtour-a96cbb4bdec31d07d53c618d4f8e824dddb06538.tar.gz
*
Diffstat (limited to 'html/fps/game.js')
-rw-r--r--html/fps/game.js201
1 files changed, 149 insertions, 52 deletions
diff --git a/html/fps/game.js b/html/fps/game.js
index bac8f9b..67cebc8 100644
--- a/html/fps/game.js
+++ b/html/fps/game.js
@@ -25,7 +25,9 @@ const GameState = {
         tilt: 0  // 0 = normal, 1 = tilted
     },
     shots: [],
-    isStarted: false
+    isStarted: false,
+    gradients: {}, // Cache for wall gradients
+    lastGradientUpdate: 0
 };
 
 // Level generation using a simple maze algorithm
@@ -329,19 +331,39 @@ const render = (ctx) => {
     
     // Draw walls and sprites using ray casting
     const fov = Math.PI / 3;
-    const numRays = width;
+    const numRays = Math.floor(width / 2); // Reduce ray count by half
     const rayResults = [];
     const sprites = [];
     
-    for (let i = 0; i < numRays; i++) {
-        const rayAngle = GameState.player.angle - fov/2 + fov * i / numRays;
+    // Pre-calculate common values
+    const halfHeight = height / 2;
+    const brightnessSteps = 20; // Number of brightness levels to cache
+    const gradientCache = {};
+    
+    // Create cached gradients for different brightness levels
+    for (let b = 0; b <= brightnessSteps; b++) {
+        const brightness = b / brightnessSteps;
+        const baseColor = Math.floor(brightness * 100);
+        const distanceColor = Math.floor(brightness * 50);
+        
+        gradientCache[b] = {
+            top: `rgb(${distanceColor}, ${baseColor + 20}, ${distanceColor})`,
+            middle: `rgb(${distanceColor}, ${baseColor}, ${distanceColor})`,
+            bottom: `rgb(${distanceColor}, ${baseColor - 20}, ${distanceColor})`,
+            line: `rgba(0, ${baseColor - 30}, 0, 0.3)`
+        };
+    }
+    
+    for (let i = 0; i < width; i += 2) { // Draw every other pixel
+        const rayIndex = Math.floor(i / 2);
+        const rayAngle = GameState.player.angle - fov/2 + fov * rayIndex / numRays;
         let distance = 0;
         let hit = false;
         let hitEnemy = null;
         let hitItem = null;
         
         while (!hit && distance < 20) {
-            distance += 0.1;
+            distance += 0.2; // Increase ray step size
             const testX = GameState.player.x + Math.sin(rayAngle) * distance;
             const testY = GameState.player.y + Math.cos(rayAngle) * distance;
             
@@ -361,7 +383,7 @@ const render = (ctx) => {
                     hit = true;
                     sprites.push({
                         type: 'enemy',
-                        x: i,
+                        x: rayIndex,
                         distance,
                         angle: rayAngle,
                         data: hitEnemy
@@ -380,7 +402,7 @@ const render = (ctx) => {
                     hit = true;
                     sprites.push({
                         type: 'item',
-                        x: i,
+                        x: rayIndex,
                         distance,
                         angle: rayAngle,
                         data: hitItem
@@ -389,32 +411,48 @@ const render = (ctx) => {
             }
         }
         
-        rayResults.push({
+        rayResults[rayIndex] = {
             distance,
             hitEnemy,
             angle: rayAngle
-        });
+        };
         
         const wallHeight = height / (distance * Math.cos(rayAngle - GameState.player.angle));
         const brightness = Math.max(0, 1 - distance / 20);
+        const brightnessIndex = Math.floor(brightness * brightnessSteps);
+        const colors = gradientCache[brightnessIndex];
         
         if (hitEnemy) {
             // Draw enemy
             const enemyColor = Math.floor(brightness * 255);
             ctx.fillStyle = `rgb(${enemyColor}, 0, 0)`;
-            ctx.fillRect(i, height/2 - wallHeight/2, 1, wallHeight);
+            ctx.fillRect(i, halfHeight - wallHeight/2, 2, 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);
+            ctx.fillRect(i, halfHeight - wallHeight/2, 2, wallHeight);
         } else {
-            // Draw wall
-            const wallColor = Math.floor(brightness * 100);
-            ctx.fillStyle = `rgb(0, ${wallColor}, 0)`;
-            ctx.fillRect(i, height/2 - wallHeight/2, 1, wallHeight);
+            const wallTop = halfHeight - wallHeight/2;
+            const wallBottom = halfHeight + wallHeight/2;
+            
+            // Draw wall with cached colors
+            ctx.fillStyle = colors.middle;
+            ctx.fillRect(i, wallTop, 2, wallHeight); // Draw 2 pixels wide
+            
+            // Draw top and bottom highlights
+            ctx.fillStyle = colors.top;
+            ctx.fillRect(i, wallTop, 2, 1);
+            ctx.fillStyle = colors.bottom;
+            ctx.fillRect(i, wallBottom - 1, 2, 1);
+            
+            // Draw vertical lines less frequently
+            if (i % 8 === 0) {
+                ctx.fillStyle = colors.line;
+                ctx.fillRect(i, wallTop, 2, wallHeight);
+            }
         }
     }
     
@@ -437,7 +475,7 @@ const render = (ctx) => {
         
         if (screenX >= 0 && screenX < width) {
             ctx.fillStyle = '#ffff00';
-            ctx.fillRect(screenX, height/2, 2, 2);
+            ctx.fillRect(screenX, halfHeight, 2, 2);
         }
         
         return true;
@@ -447,7 +485,7 @@ const render = (ctx) => {
     const gunY = height - 150;
     const gunX = width/2;
     const recoilOffset = Math.sin(GameState.gun.recoil * Math.PI) * 50;
-    const tiltAngle = GameState.gun.tilt * Math.PI / 6; // 30 degrees max tilt
+    const tiltAngle = GameState.gun.tilt * Math.PI / 6;
     
     // Only draw gun if it's not in full recoil
     if (GameState.gun.recoil < 0.8) {
@@ -455,21 +493,22 @@ const render = (ctx) => {
         ctx.translate(gunX, gunY + recoilOffset);
         ctx.rotate(tiltAngle);
         
-        // Gun body (larger rectangle)
+        // Gun body (larger rectangle) extending below screen
         ctx.fillStyle = '#333';
-        ctx.fillRect(-30, 0, 60, 90);
+        ctx.fillRect(-30, 0, 60, height);
         
         // Gun slide (smaller rectangle) with sliding animation
         const slideOffset = GameState.gun.slidePosition * 20;
         ctx.fillStyle = '#222';
-        ctx.fillRect(-8, -30 - slideOffset, 16, 60);
+        // Adjusted slide dimensions: shorter above, longer below
+        ctx.fillRect(-8, -15 - slideOffset, 16, 90); // Changed from -30 to -15 for top, and 60 to 90 for height
         
         // 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(0, -30 - slideOffset, flashSize, 0, Math.PI * 2);
+            ctx.arc(0, -15 - slideOffset, flashSize, 0, Math.PI * 2); // Adjusted to match new slide position
             ctx.fill();
         }
         
@@ -478,25 +517,25 @@ const render = (ctx) => {
     
     // Draw crosshair
     ctx.fillStyle = '#fff';
-    ctx.fillRect(width/2 - 5, height/2, 10, 1);
-    ctx.fillRect(width/2, height/2 - 5, 1, 10);
+    ctx.fillRect(width/2 - 5, halfHeight, 10, 1);
+    ctx.fillRect(width/2, halfHeight - 5, 1, 10);
     
     // Draw HUD - only canvas-based
     ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
-    ctx.fillRect(10, 10, 200, 100);
+    ctx.fillRect(10, 10, 200, 120);
     
     ctx.fillStyle = '#fff';
     ctx.font = '24px monospace';
-    ctx.fillText(`Health: ${GameState.player.health}`, 20, 45);
-    ctx.fillText(`Ammo: ${GameState.player.ammo}`, 20, 80);
-    ctx.fillText(`Score: ${GameState.player.score}`, 20, 115);
+    ctx.fillText(`Health: ${GameState.player.health}`, 100, 45);
+    ctx.fillText(`Ammo: ${GameState.player.ammo}`, 80, 80);
+    ctx.fillText(`Score: ${GameState.player.score}`, 80, 115);
     
     // 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;
+        const spriteY = halfHeight - spriteHeight/2;
         
         // Only draw if not occluded by a wall
         if (sprite.distance < rayResults[sprite.x].distance) {
@@ -535,7 +574,7 @@ const render = (ctx) => {
     });
     
     // Draw mini-map
-    const miniMapSize = 150;
+    const miniMapSize = 200; // Increased from 150
     const miniMapX = width - miniMapSize - 10;
     const miniMapY = 10;
     const cellSize = miniMapSize / GameState.level.width;
@@ -571,12 +610,12 @@ const render = (ctx) => {
     );
     ctx.fill();
     
-    // Draw items in mini-map with matching colors
+    // Draw items
     GameState.items.forEach(item => {
         if (item.type === 'health') {
-            ctx.fillStyle = '#ffd700'; // Golden
-        } else if (item.type === 'ammo') {
-            ctx.fillStyle = '#0ff'; // Teal
+            ctx.fillStyle = '#0f0';
+        } else {
+            ctx.fillStyle = '#0ff';
         }
         ctx.beginPath();
         ctx.arc(
@@ -589,28 +628,22 @@ const render = (ctx) => {
         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';
+    // Draw player with enhanced visibility
     const playerX = miniMapX + GameState.player.x * cellSize;
     const playerY = miniMapY + GameState.player.y * cellSize;
-    const size = cellSize;
+    const size = cellSize * 1.5; // Increased player size
     
+    // Draw player outline
+    ctx.strokeStyle = '#fff';
+    ctx.lineWidth = 2;
+    ctx.beginPath();
+    ctx.arc(playerX + cellSize/2, playerY + cellSize/2, size/2 + 2, 0, Math.PI * 2);
+    ctx.stroke();
+    
+    // Draw player triangle
+    ctx.fillStyle = '#00f';
     ctx.save();
-    ctx.translate(playerX, playerY);
+    ctx.translate(playerX + cellSize/2, playerY + cellSize/2);
     ctx.rotate(GameState.player.angle);
     ctx.beginPath();
     ctx.moveTo(0, -size/2);
@@ -619,6 +652,20 @@ const render = (ctx) => {
     ctx.closePath();
     ctx.fill();
     ctx.restore();
+    
+    // Draw enemies
+    ctx.fillStyle = '#f00';
+    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();
+    });
 };
 
 // Game loop
@@ -718,6 +765,12 @@ const keys = {
 };
 
 document.addEventListener('keydown', e => {
+    if (!GameState.isStarted && (e.key === ' ' || e.key === 'Enter')) {
+        GameState.isStarted = true;
+        generateLevel();
+        return;
+    }
+    
     if (e.key.toLowerCase() in keys) keys[e.key.toLowerCase()] = true;
     if (e.key in keys) keys[e.key] = true;
 });
@@ -728,9 +781,12 @@ document.addEventListener('keyup', e => {
 });
 
 document.addEventListener('mousemove', e => {
-    GameState.player.angle += e.movementX * 0.01;
+    if (GameState.isStarted) {
+        GameState.player.angle += e.movementX * 0.01;
+    }
 });
 
+// Update click handler to handle both start screen and firing
 document.addEventListener('click', (e) => {
     if (!GameState.isStarted) {
         const canvas = document.getElementById('gameCanvas');
@@ -748,6 +804,47 @@ document.addEventListener('click', (e) => {
             GameState.isStarted = true;
             generateLevel();
         }
+        return;
+    }
+    
+    // Handle firing on click during gameplay
+    if (GameState.player.ammo > 0 && Date.now() - GameState.gun.lastShot > 200) {
+        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
+        const fov = Math.PI / 3;
+        const rayAngle = GameState.player.angle;
+        let distance = 0;
+        let hitEnemy = null;
+        let hitWall = false;
+        
+        while (!hitEnemy && !hitWall && distance < 20) {
+            distance += 0.1;
+            const testX = GameState.player.x + Math.sin(rayAngle) * distance;
+            const testY = GameState.player.y + Math.cos(rayAngle) * distance;
+            
+            if (GameState.level.map[Math.floor(testY)][Math.floor(testX)] === 1) {
+                hitWall = true;
+                break;
+            }
+            
+            hitEnemy = GameState.enemies.find(enemy => {
+                const dx = testX - enemy.x;
+                const dy = testY - enemy.y;
+                return Math.sqrt(dx * dx + dy * dy) < 0.5;
+            });
+        }
+        
+        if (hitEnemy) {
+            hitEnemy.health--;
+        }
     }
 });