about summary refs log tree commit diff stats
path: root/html/plains/game.js
diff options
context:
space:
mode:
authorelioat <{ID}+{username}@users.noreply.github.com>2024-12-17 07:14:01 -0500
committerelioat <{ID}+{username}@users.noreply.github.com>2024-12-17 07:14:01 -0500
commitf66730b7aeb9494c266260df7eeba8a8e26beb4e (patch)
treeb0f346ae7bb941d92bf846bd9bc998dbc848ae6f /html/plains/game.js
parent2264678588f01b86e986f0820ff7bd875307e584 (diff)
downloadtour-f66730b7aeb9494c266260df7eeba8a8e26beb4e.tar.gz
*
Diffstat (limited to 'html/plains/game.js')
-rw-r--r--html/plains/game.js175
1 files changed, 165 insertions, 10 deletions
diff --git a/html/plains/game.js b/html/plains/game.js
index 9e91353..5923b16 100644
--- a/html/plains/game.js
+++ b/html/plains/game.js
@@ -286,6 +286,43 @@ const CONFIG = {
                 sizeMultiplier: 1.0
             }
         }
+    },
+    enemies: {
+        size: {
+            min: 15,
+            max: 20
+        },
+        colors: {
+            active: {
+                min: {
+                    red: 150,
+                    green: 0,
+                    blue: 0
+                },
+                max: {
+                    red: 255,
+                    green: 100,
+                    blue: 0
+                }
+            },
+            defeated: 'rgb(100, 100, 100)'
+        },
+        patrol: {
+            radius: {
+                min: 100,
+                max: 200
+            },
+            speed: {
+                base: 100  // pixels per second
+            }
+        },
+        chase: {
+            range: 2,
+            speedMultiplier: 1.5  // 50% faster than base speed
+        },
+        return: {
+            speedMultiplier: 1.25  // 25% faster than base speed
+        }
     }
 };
 
@@ -307,24 +344,29 @@ const CAMERA_DEADZONE_Y = GAME_HEIGHT * CONFIG.display.camera.deadzoneMultiplier
 // ============= State Management =============
 const createInitialState = () => ({
     player: {
-        x: CONFIG.player.size,  // A bit offset from the edge
-        y: CONFIG.player.size,  // A bit offset from the edge
+        x: CONFIG.player.size,
+        y: CONFIG.player.size,
         isDefending: false,
         direction: { x: 0, y: -1 },
         swordAngle: 0,
         isSwinging: false,
-        equipment: 'unarmed',  // Start without sword
+        equipment: 'unarmed',
         bubbles: [],
         bubbleParticles: [],
         lastBubbleTime: 0,
-        dashStartTime: 0, // When the current dash started
-        isDashing: false, // Currently dashing?
-        dashExhausted: false, // Is dash on cooldown?
-        lastInputTime: 0, // Track when the last input occurred
+        dashStartTime: 0,
+        isDashing: false,
+        dashExhausted: false,
+        lastInputTime: 0,
         baseDirection: { x: 0, y: -1 },
         lastDashEnd: 0,
         swordUnlocked: false,
-        rescuedCount: 0
+        rescuedCount: 0,
+        hp: 15,
+        maxHp: 15,
+        isInvulnerable: false,
+        invulnerableUntil: 0,
+        isDead: false
     },
     particles: [],
     footprints: [],
@@ -338,7 +380,8 @@ const createInitialState = () => ({
     collisionMap: new Map(),
     villagers: [],
     gameComplete: false,
-    gameCompleteMessageShown: false
+    gameCompleteMessageShown: false,
+    enemies: []
 });
 
 let state = createInitialState();
@@ -348,6 +391,14 @@ let state = createInitialState();
 const keys = new Set();
 
 const handleKeyDown = (e) => {
+    // If player is dead, only handle restart
+    if (state.player.isDead) {
+        if (e.code === 'Space') {
+            window.location.reload();
+        }
+        return;
+    }
+    
     keys.add(e.key);
     
     if (e.key === 'z' && !state.player.isDefending) {
@@ -1022,6 +1073,53 @@ const renderPlayer = () => {
     ctx.restore();
 };
 
+const renderPlayerHUD = (ctx) => {
+    // Save the current context state
+    ctx.save();
+    
+    // Switch to screen coordinates
+    ctx.setTransform(1, 0, 0, 1, 0, 0);
+    
+    // HP Bar container
+    const barWidth = 200;
+    const barHeight = 20;
+    const barX = 20;
+    const barY = 20;
+    const borderWidth = 2;
+    
+    // Draw border
+    ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
+    ctx.fillRect(
+        barX - borderWidth, 
+        barY - borderWidth, 
+        barWidth + borderWidth * 2, 
+        barHeight + borderWidth * 2
+    );
+    
+    // Background
+    ctx.fillStyle = 'rgba(50, 50, 50, 0.8)';
+    ctx.fillRect(barX, barY, barWidth, barHeight);
+    
+    // HP
+    const hpRatio = state.player.hp / state.player.maxHp;
+    ctx.fillStyle = `rgb(${Math.floor(255 * (1 - hpRatio))}, ${Math.floor(255 * hpRatio)}, 0)`;
+    ctx.fillRect(barX, barY, barWidth * hpRatio, barHeight);
+    
+    // HP Text
+    ctx.fillStyle = 'white';
+    ctx.font = '14px Arial';
+    ctx.textAlign = 'center';
+    ctx.textBaseline = 'middle';
+    ctx.fillText(
+        `HP: ${state.player.hp}/${state.player.maxHp}`,
+        barX + barWidth/2,
+        barY + barHeight/2
+    );
+    
+    // Restore the context state
+    ctx.restore();
+};
+
 const render = () => {
     ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
     
@@ -1207,7 +1305,7 @@ const render = () => {
                         
                         // Choose color for this dot
                         const colorIndex = useSingleColor ? cellColorIndex :
-                            Math.floor(seededRandom(cellX * i * j, cellY * i * j) * config.colors.length);
+                            Math.floor(seededRandom(cellX * i, cellY * j) * config.colors.length);
                         ctx.fillStyle = config.colors[colorIndex];
                         
                         ctx.beginPath();
@@ -1332,6 +1430,9 @@ const render = () => {
         ctx.restore();
     });
 
+    // Render enemies
+    enemySystem.renderEnemies(ctx, state.enemies);
+
     // Draw player
     renderPlayer();
 
@@ -1361,6 +1462,33 @@ const render = () => {
         ctx.restore();
     });
     
+    // Render particles
+    state.particles = state.particles.filter(particle => {
+        const age = animationTime - particle.createdAt;
+        if (age >= particle.lifetime) return false;
+        
+        const alpha = 1 - (age / particle.lifetime);
+        ctx.beginPath();
+        ctx.arc(
+            particle.x + particle.dx * age * 0.1,
+            particle.y + particle.dy * age * 0.1,
+            particle.size,
+            0, Math.PI * 2
+        );
+        ctx.fillStyle = particle.color.replace('rgb', 'rgba').replace(')', `, ${alpha})`);
+        ctx.fill();
+        
+        return true;
+    });
+    
+    // Render HUD elements
+    renderPlayerHUD(ctx);
+    
+    // Update player invulnerability
+    if (state.player.isInvulnerable && animationTime >= state.player.invulnerableUntil) {
+        state.player.isInvulnerable = false;
+    }
+    
     ctx.restore();
 };
 
@@ -1462,7 +1590,14 @@ const gameLoop = (currentTime) => {
     if (deltaTime >= FRAME_TIME) {
         animationTime += FRAME_TIME;
         
+        // Check for player death
+        if (state.player.hp <= 0 && !state.player.isDead) {
+            state.player.isDead = true;
+            showGameOver();
+        }
+        
         updatePlayer();
+        state.enemies = enemySystem.updateEnemies(state.enemies, deltaTime);
         render();
         
         lastFrameTime = currentTime;
@@ -1495,6 +1630,7 @@ resizeCanvas();
 
 // Initialize villagers after collision map is populated
 state.villagers = generateVillagers();
+state.enemies = enemySystem.generateEnemies(state.villagers, state.collisionMap);
 
 // Start the game loop
 requestAnimationFrame(gameLoop);
@@ -1696,4 +1832,23 @@ const showSwordUnlockAnimation = () => {
         document.body.removeChild(container);
         document.head.removeChild(style);
     }, CONFIG.player.equipment.unlockAnimation.duration);
+};
+
+const showGameOver = () => {
+    const gameOverDiv = document.createElement('div');
+    gameOverDiv.style.position = 'fixed';
+    gameOverDiv.style.top = '50%';
+    gameOverDiv.style.left = '50%';
+    gameOverDiv.style.transform = 'translate(-50%, -50%)';
+    gameOverDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
+    gameOverDiv.style.padding = '20px';
+    gameOverDiv.style.borderRadius = '10px';
+    gameOverDiv.style.color = 'white';
+    gameOverDiv.style.textAlign = 'center';
+    gameOverDiv.style.fontSize = '24px';
+    gameOverDiv.innerHTML = `
+        <h2>Game Over</h2>
+        <p>Press Space to restart</p>
+    `;
+    document.body.appendChild(gameOverDiv);
 };
\ No newline at end of file