about summary refs log tree commit diff stats
path: root/html/mountain
diff options
context:
space:
mode:
Diffstat (limited to 'html/mountain')
-rw-r--r--html/mountain/game.js400
1 files changed, 254 insertions, 146 deletions
diff --git a/html/mountain/game.js b/html/mountain/game.js
index be75810..8243e9f 100644
--- a/html/mountain/game.js
+++ b/html/mountain/game.js
@@ -1,25 +1,11 @@
-// Set up canvas
 const canvas = document.createElement('canvas');
+const ctx = canvas.getContext('2d');
+document.body.appendChild(canvas);
 canvas.style.display = 'block';
 canvas.style.position = 'fixed';
 canvas.style.top = '0';
 canvas.style.left = '0';
-document.body.appendChild(canvas);
-const ctx = canvas.getContext('2d');
-
-// Add CSS to body
-document.body.style.margin = '0';
-document.body.style.overflow = 'hidden';
 
-// Make canvas fullscreen
-function resizeCanvas() {
-    canvas.width = window.innerWidth;
-    canvas.height = window.innerHeight;
-}
-resizeCanvas();
-window.addEventListener('resize', resizeCanvas);
-
-// Add these at the top of the file, after the canvas setup but before the game constants
 const GAME_STATE = {
     PLAYING: 'playing',
     GAME_OVER: 'game_over'
@@ -30,28 +16,73 @@ const PLATFORM_TYPE = {
     DEADLY: 'deadly'
 };
 
-// Game constants
 const PLAYER_SIZE = 20;
-const PLATFORM_HEIGHT = 20;
-const MIN_PLATFORM_WIDTH = 100;
-const MAX_PLATFORM_WIDTH = 300;
 const GRAVITY = 0.5;
 const JUMP_FORCE = 12;
 const MOVE_SPEED = 7;
 
-// Add these constants for level generation
-const MIN_PARTITION_SIZE = MAX_PLATFORM_WIDTH + 20; // Reduced from 50 to allow more splits
-const PARTITION_RATIO = 0.3; // Reduced from 0.4 to allow more uneven splits
+const PLATFORM_HEIGHT = 20;
+const MIN_PLATFORM_WIDTH = 100;
+const MAX_PLATFORM_WIDTH = 300;
+const MIN_PARTITION_SIZE = MAX_PLATFORM_WIDTH + 20;
+const PARTITION_RATIO = 0.3;
+const MIN_PLATFORM_SPACING = 100;
+const DEADLY_BORDER_HEIGHT = 7;
+
+const FPS = 60;
+const FRAME_TIME = 1000 / FPS;
+
+const PARTICLE_COUNT = 20;
+const PARTICLE_SPEED = 4;
+const PARTICLE_SIZE = 4;
+const PARTICLE_LIFETIME = 25;
+
+const DEATH_PARTICLE_COUNT = 60;
+const DEATH_PARTICLE_SPEED = 10;
+const DEATH_PARTICLE_SIZE = 4;
+const DEATH_PARTICLE_LIFETIME = 50;
+const DEATH_ANIMATION_DURATION = 55;
 
-// Add this constant for minimum platform spacing
-const MIN_PLATFORM_SPACING = 100; // Minimum distance between platforms
+const ENEMY_SPEED = 2;
+
+let gameState = GAME_STATE.PLAYING;
+let level = 1;
+let platforms = [];
+let enemies = [];
+let particles = [];
+let deathParticles = [];
+let deathAnimationTimer = 0;
+let frameCount = 0;
+let lastFpsUpdate = 0;
+let currentFps = 0;
+let lastFrameTime = 0;
+let accumulator = 0;
+
+let player = {
+    x: PLAYER_SIZE,
+    y: window.innerHeight - PLAYER_SIZE * 2,
+    velocityX: 0,
+    velocityY: 0,
+    isJumping: false,
+    jumpsLeft: 2,
+    gravityMultiplier: 1,
+    isDead: false
+};
+
+let exit = {
+    x: window.innerWidth - PLAYER_SIZE * 2,
+    y: PLAYER_SIZE * 2,
+    size: PLAYER_SIZE
+};
+
+const keys = {};
+window.addEventListener('keydown', e => keys[e.key] = true);
+window.addEventListener('keyup', e => keys[e.key] = false);
 
-// Add these helper functions for the RSP tree
 function randomRange(min, max) {
     return min + Math.random() * (max - min);
 }
 
-// Core partition data structure (plain object)
 function createPartition(x, y, width, height) {
     return {
         x,
@@ -64,7 +95,6 @@ function createPartition(x, y, width, height) {
     };
 }
 
-// Split function that returns new partitions instead of mutating
 function splitPartition(partition, vertical) {
     const splitPoint = randomRange(
         vertical ? partition.width * PARTITION_RATIO : partition.height * PARTITION_RATIO,
@@ -81,12 +111,9 @@ function splitPartition(partition, vertical) {
     };
 }
 
-// Replace the platformsOverlap function with this new version
 function platformsOverlap(platform1, platform2) {
-    // Add a small buffer space around platforms (5 pixels)
     const buffer = 5;
     
-    // Check if the rectangles (with buffer) overlap
     return !(
         platform1.x + platform1.width + buffer < platform2.x ||
         platform1.x > platform2.x + platform2.width + buffer ||
@@ -95,7 +122,91 @@ function platformsOverlap(platform1, platform2) {
     );
 }
 
-// Modify the generatePlatformsForPartition function
+function createParticle(x, y, velocityY) {
+    return {
+        x: x + PLAYER_SIZE / 2 + (Math.random() - 0.5) * PLAYER_SIZE,
+        y: y + (velocityY > 0 ? 0 : PLAYER_SIZE),
+        velocityX: (Math.random() - 0.5) * PARTICLE_SPEED * 2,
+        velocityY: (Math.random() * PARTICLE_SPEED) * Math.sign(velocityY),
+        size: PARTICLE_SIZE + Math.random() * 2,
+        life: PARTICLE_LIFETIME,
+        initialOpacity: 0.3 + Math.random() * 0.7
+    };
+}
+
+function createDeathParticles(x, y) {
+    deathParticles = [];
+    for (let i = 0; i < DEATH_PARTICLE_COUNT; i++) {
+        const angle = (Math.PI * 2 * i) / DEATH_PARTICLE_COUNT;
+        deathParticles.push({
+            x: x + PLAYER_SIZE / 2,
+            y: y + PLAYER_SIZE / 2,
+            velocityX: Math.cos(angle) * DEATH_PARTICLE_SPEED * (0.5 + Math.random()),
+            velocityY: Math.sin(angle) * DEATH_PARTICLE_SPEED * (0.5 + Math.random()),
+            size: DEATH_PARTICLE_SIZE + Math.random() * 2,
+            life: DEATH_PARTICLE_LIFETIME,
+            initialOpacity: 0.6 + Math.random() * 0.4
+        });
+    }
+    deathAnimationTimer = DEATH_ANIMATION_DURATION;
+}
+
+function updateParticles() {
+    particles = particles.filter(particle => {
+        particle.x += particle.velocityX;
+        particle.y += particle.velocityY;
+        particle.life--;
+        return particle.life > 0;
+    });
+}
+
+function updateDeathParticles() {
+    if (deathAnimationTimer > 0) {
+        deathAnimationTimer--;
+    }
+    
+    deathParticles = deathParticles.filter(particle => {
+        particle.x += particle.velocityX;
+        particle.y += particle.velocityY;
+        particle.velocityY += GRAVITY * 0.5; // GRAVITY! Is working against me...
+        particle.life--;
+        return particle.life > 0;
+    });
+}
+
+function createEnemy(platform) {
+    const startsOnTop = Math.random() < 0.5;  // 50% chance to start on top
+    return {
+        x: platform.x,
+        y: startsOnTop ? 
+            platform.y - PLAYER_SIZE : 
+            platform.y + platform.height,
+        width: PLAYER_SIZE,
+        height: PLAYER_SIZE,
+        platform: platform,
+        direction: 1,
+        moveRight: true,
+        isOnTop: startsOnTop  // Track where things started
+    };
+}
+
+function updateEnemies() {
+    enemies.forEach(enemy => {
+        enemy.x += ENEMY_SPEED * (enemy.moveRight ? 1 : -1);
+
+        if (enemy.moveRight && enemy.x + enemy.width > enemy.platform.x + enemy.platform.width) {
+            enemy.moveRight = false;
+        } else if (!enemy.moveRight && enemy.x < enemy.platform.x) {
+            enemy.moveRight = true;
+        }
+
+        enemy.y = (player.gravityMultiplier > 0) === enemy.isOnTop ? 
+            enemy.platform.y - enemy.height :
+            enemy.platform.y + enemy.platform.height;
+    });
+}
+
+// 9. Level Generation
 function generatePlatformsForPartition(partition) {
     if (partition.width < MIN_PLATFORM_WIDTH || partition.height < PLATFORM_HEIGHT * 2) {
         return [];
@@ -106,7 +217,7 @@ function generatePlatformsForPartition(partition) {
         : 1;
 
     const newPlatforms = [];
-    const maxAttempts = 20; // Increased max attempts for better placement
+    const maxAttempts = 20;
 
     for (let i = 0; i < platformCount; i++) {
         let validPlatform = null;
@@ -118,7 +229,6 @@ function generatePlatformsForPartition(partition) {
                 Math.min(MAX_PLATFORM_WIDTH, partition.width * 0.8)
             );
             
-            // Calculate valid range for platform placement
             const minX = Math.max(
                 partition.x, 
                 partition.x + (partition.width * (i / platformCount))
@@ -128,7 +238,6 @@ function generatePlatformsForPartition(partition) {
                 partition.x + (partition.width * ((i + 1) / platformCount))
             );
             
-            // Only proceed if we have valid space
             if (maxX > minX) {
                 const candidatePlatform = {
                     x: randomRange(minX, maxX),
@@ -138,13 +247,11 @@ function generatePlatformsForPartition(partition) {
                     ),
                     width: platformWidth,
                     height: PLATFORM_HEIGHT,
-                    // 20% chance for a platform to be deadly after level 1
                     type: (level > 1 && Math.random() < 0.2) ? 
                         PLATFORM_TYPE.DEADLY : 
                         PLATFORM_TYPE.NORMAL
                 };
 
-                // Check overlap with all existing platforms
                 let overlapping = false;
                 for (const existingPlatform of [...platforms, ...newPlatforms]) {
                     if (platformsOverlap(candidatePlatform, existingPlatform)) {
@@ -166,49 +273,19 @@ function generatePlatformsForPartition(partition) {
         }
     }
 
-    return newPlatforms;
-}
-
-// Game state
-let gameState = GAME_STATE.PLAYING;
-let level = 1;
-let platforms = [];
-let player = {
-    x: PLAYER_SIZE,
-    y: window.innerHeight - PLAYER_SIZE * 2,
-    velocityX: 0,
-    velocityY: 0,
-    isJumping: false,
-    jumpsLeft: 2,
-    gravityMultiplier: 1,
-    isDead: false
-};
-let exit = {
-    x: window.innerWidth - PLAYER_SIZE * 2,
-    y: PLAYER_SIZE * 2,
-    size: PLAYER_SIZE
-};
+    newPlatforms.forEach(platform => {
+        if (platform.type === PLATFORM_TYPE.NORMAL && Math.random() < 0.2) {  // 20% chance
+            enemies.push(createEnemy(platform));
+        }
+    });
 
-// Add reset player function
-function resetPlayer() {
-    player.x = PLAYER_SIZE;
-    player.y = window.innerHeight - PLAYER_SIZE * 2;
-    player.velocityX = 0;
-    player.velocityY = 0;
-    player.isJumping = false;
-    player.jumpsLeft = 2;
-    player.gravityMultiplier = 1;
-    player.isDead = false;
-    gameState = GAME_STATE.PLAYING;
-    level = 1;
-    generateLevel();
+    return newPlatforms;
 }
 
-// Restore the original generateLevel function
 function generateLevel() {
     platforms = [];
+    enemies = [];
     
-    // Add starting platform
     platforms.push({
         x: 0,
         y: window.innerHeight - PLATFORM_HEIGHT,
@@ -217,7 +294,6 @@ function generateLevel() {
         type: PLATFORM_TYPE.NORMAL
     });
 
-    // Add end platform
     platforms.push({
         x: window.innerWidth - MIN_PLATFORM_WIDTH,
         y: PLAYER_SIZE * 3,
@@ -250,7 +326,6 @@ function generateLevel() {
         return generatePlatformsForPartition(node);
     }
 
-    // Create sections and generate platforms
     for (let i = 0; i < horizontalSections; i++) {
         for (let j = 0; j < verticalSections; j++) {
             const root = createPartition(
@@ -266,47 +341,64 @@ function generateLevel() {
     }
 }
 
-// Handle player input
-const keys = {};
-window.addEventListener('keydown', e => keys[e.key] = true);
-window.addEventListener('keyup', e => keys[e.key] = false);
+function resetPlayer() {
+    player.x = PLAYER_SIZE;
+    player.y = window.innerHeight - PLATFORM_HEIGHT - PLAYER_SIZE;
+    player.velocityX = 0;
+    player.velocityY = 0;
+    player.isJumping = false;
+    player.jumpsLeft = 2;
+    player.gravityMultiplier = 1;
+    player.isDead = false;
+    gameState = GAME_STATE.PLAYING;
+    level = 1;
+    generateLevel();
+    enemies = [];
+}
+
+function killPlayer() {
+    if (!player.isDead) {
+        createDeathParticles(player.x, player.y);
+        player.isDead = true;
+        gameState = GAME_STATE.GAME_OVER;
+    }
+}
 
 function updatePlayer() {
     if (gameState === GAME_STATE.GAME_OVER) {
-        if (keys[' ']) { // Space key to restart when game over
+        if (deathAnimationTimer <= 0 && keys['Enter']) {
             resetPlayer();
         }
         return;
     }
 
-    // Horizontal movement
     if (keys['ArrowLeft']) player.velocityX = -MOVE_SPEED;
     else if (keys['ArrowRight']) player.velocityX = MOVE_SPEED;
     else player.velocityX = 0;
     
-    // Toggle gravity when 'g' or spacebar is pressed
     if ((keys['g'] || keys[' ']) && !player.lastGravityKey) {
         player.gravityMultiplier *= -1;
-        player.velocityY = 0; // Reset vertical velocity when flipping gravity
+        player.velocityY = 0;
     }
     player.lastGravityKey = keys['g'] || keys[' '];
     
-    // Apply gravity (now with direction)
     player.velocityY += GRAVITY * player.gravityMultiplier;
     
-    // Jump (in the direction opposite to gravity)
     if (keys['ArrowUp'] && keys['ArrowUp'] !== player.lastJumpKey && player.jumpsLeft > 0) {
         player.velocityY = -JUMP_FORCE * player.gravityMultiplier;
         player.jumpsLeft--;
         player.isJumping = true;
+        
+        // Add particles whenever you jump
+        for (let i = 0; i < PARTICLE_COUNT; i++) {
+            particles.push(createParticle(player.x, player.y, player.velocityY));
+        }
     }
     player.lastJumpKey = keys['ArrowUp'];
     
-    // Update position
     player.x += player.velocityX;
     player.y += player.velocityY;
     
-    // Check platform collisions (modified for deadly platforms)
     player.isJumping = true;
     for (let platform of platforms) {
         if (player.x + PLAYER_SIZE > platform.x &&
@@ -315,14 +407,12 @@ function updatePlayer() {
             let collision = false;
             
             if (player.gravityMultiplier > 0) {
-                // Normal gravity collision
                 if (player.y + PLAYER_SIZE > platform.y &&
                     player.y + PLAYER_SIZE < platform.y + platform.height + player.velocityY) {
                     collision = true;
                     player.y = platform.y - PLAYER_SIZE;
                 }
             } else {
-                // Reversed gravity collision
                 if (player.y < platform.y + platform.height &&
                     player.y > platform.y + player.velocityY) {
                     collision = true;
@@ -332,8 +422,7 @@ function updatePlayer() {
 
             if (collision) {
                 if (platform.type === PLATFORM_TYPE.DEADLY) {
-                    player.isDead = true;
-                    gameState = GAME_STATE.GAME_OVER;
+                    killPlayer();
                 } else {
                     player.velocityY = 0;
                     player.isJumping = false;
@@ -343,19 +432,14 @@ function updatePlayer() {
         }
     }
     
-    // Check for deadly border collisions (modified for gap)
     if (player.y <= DEADLY_BORDER_HEIGHT) {
-        // Only die if touching the actual drawn deadly border
         if (player.x < canvas.width - exit.size - 300) {
-            player.isDead = true;
-            gameState = GAME_STATE.GAME_OVER;
+            killPlayer();
         }
     } else if (player.y + PLAYER_SIZE >= canvas.height - DEADLY_BORDER_HEIGHT) {
-        player.isDead = true;
-        gameState = GAME_STATE.GAME_OVER;
+        killPlayer();
     }
     
-    // Keep player in bounds (modified to account for deadly borders)
     if (player.x < 0) player.x = 0;
     if (player.x + PLAYER_SIZE > canvas.width) player.x = canvas.width - PLAYER_SIZE;
     if (player.y < 0) {
@@ -367,66 +451,90 @@ function updatePlayer() {
         player.velocityY = 0;
     }
     
-    // Check if player reached exit
     if (player.x + PLAYER_SIZE > exit.x &&
         player.x < exit.x + exit.size &&
         player.y + PLAYER_SIZE > exit.y &&
         player.y < exit.y + exit.size) {
         level++;
-        player.x = PLAYER_SIZE;
-        player.y = window.innerHeight - PLAYER_SIZE;
         generateLevel();
+        player.x = PLAYER_SIZE;
+        player.y = window.innerHeight - PLATFORM_HEIGHT - PLAYER_SIZE;
+        player.velocityY = 0;
+        player.velocityX = 0;
+        player.jumpsLeft = 2;
+        player.isJumping = false;
     }
-}
 
-// Add these variables at the top with other game state
-let frameCount = 0;
-let lastFpsUpdate = 0;
-let currentFps = 0;
+    enemies.forEach(enemy => {
+        if (player.x < enemy.x + enemy.width &&
+            player.x + PLAYER_SIZE > enemy.x &&
+            player.y < enemy.y + enemy.height &&
+            player.y + PLAYER_SIZE > enemy.y) {
+            killPlayer();
+        }
+    });
+}
 
-// Add this constant at the top with other constants
-const DEADLY_BORDER_HEIGHT = 7;
+function resizeCanvas() {
+    canvas.width = window.innerWidth;
+    canvas.height = window.innerHeight;
+}
 
-// Update the draw function to include the deadly borders
 function draw(currentTime) {
-    // Clear canvas with light grey
     ctx.fillStyle = '#E0E0E0';
     ctx.fillRect(0, 0, canvas.width, canvas.height);
     
-    // Draw deadly borders with gap for exit
-    ctx.fillStyle = '#FF0000';
-    // Top border (with gap)
-    ctx.fillRect(0, 0, canvas.width - exit.size - 300, DEADLY_BORDER_HEIGHT); // Left section stops before exit
+    ctx.fillStyle = 'tomato';
+    ctx.fillRect(0, 0, canvas.width - exit.size - 300, DEADLY_BORDER_HEIGHT);
     
-    // Bottom border (full width)
-    ctx.fillRect(0, canvas.height - DEADLY_BORDER_HEIGHT, canvas.width, DEADLY_BORDER_HEIGHT);
+    ctx.fillRect(0, canvas.height - DEADLY_BORDER_HEIGHT, platforms[0].x, DEADLY_BORDER_HEIGHT);
+    ctx.fillRect(
+        platforms[0].x + platforms[0].width, 
+        canvas.height - DEADLY_BORDER_HEIGHT, 
+        canvas.width - (platforms[0].x + platforms[0].width), 
+        DEADLY_BORDER_HEIGHT
+    );
     
-    // Draw platforms (almost black for normal platforms, red for deadly)
     for (let platform of platforms) {
         ctx.fillStyle = platform.type === PLATFORM_TYPE.DEADLY ? 
-            '#FF0000' : // Red for deadly platforms
-            '#1A1A1A'; // Almost black for normal platforms
+            'tomato' : 
+            '#1A1A1A';
         ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
     }
     
-    // Draw exit (keeping green for now)
-    ctx.fillStyle = '#32CD32';
+    ctx.fillStyle = 'teal';
     ctx.fillRect(exit.x, exit.y, exit.size, exit.size);
     
-    // Draw player in almost black
     if (!player.isDead) {
-        ctx.fillStyle = '#1A1A1A';
+        ctx.fillStyle = player.gravityMultiplier > 0 ? '#1A1A1A' : '#4A90E2';
         ctx.fillRect(player.x, player.y, PLAYER_SIZE, PLAYER_SIZE);
     }
     
-    // Draw level number only
     ctx.fillStyle = '#000000';
     ctx.font = '20px Arial';
     ctx.textAlign = 'left';
     ctx.fillText(`Level: ${level}`, 10, 30);
 
-    // Draw game over screen
-    if (gameState === GAME_STATE.GAME_OVER) {
+    // Draw particles with different opacities
+    particles.forEach(particle => {
+        const alpha = (particle.life / PARTICLE_LIFETIME) * particle.initialOpacity;
+        ctx.fillStyle = `rgba(26, 26, 26, ${alpha})`;
+        ctx.fillRect(particle.x, particle.y, particle.size, particle.size);
+    });
+
+    enemies.forEach(enemy => {
+        ctx.fillStyle = 'tomato';
+        ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
+    });
+
+    // Death particles!
+    deathParticles.forEach(particle => {
+        const alpha = (particle.life / DEATH_PARTICLE_LIFETIME) * particle.initialOpacity;
+        ctx.fillStyle = `rgba(26, 26, 26, ${alpha})`;
+        ctx.fillRect(particle.x, particle.y, particle.size, particle.size);
+    });
+
+    if (gameState === GAME_STATE.GAME_OVER && deathAnimationTimer <= 0) {
         ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
         ctx.fillRect(0, 0, canvas.width, canvas.height);
         
@@ -436,19 +544,21 @@ function draw(currentTime) {
         ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2);
         
         ctx.font = '24px Arial';
-        ctx.fillText('Press SPACE to restart', canvas.width / 2, canvas.height / 2 + 40);
+        ctx.fillText('Press ENTER to restart', canvas.width / 2, canvas.height / 2 + 40);
         
-        ctx.textAlign = 'left'; // Reset text align
+        ctx.textAlign = 'left';
     }
-}
 
-// Add these constants at the top with other game constants
-const FPS = 60;
-const FRAME_TIME = 1000 / FPS;
-
-// Replace the simple game loop with this new version
-let lastFrameTime = 0;
-let accumulator = 0;
+    // Show help text on the first level
+    if (level === 1 && gameState === GAME_STATE.PLAYING) {
+        ctx.fillStyle = '#1A1A1A';
+        ctx.font = '24px Arial';
+        ctx.textAlign = 'center';
+        ctx.fillText('Arrow keys move the player.', canvas.width / 2, canvas.height / 2 - 20);
+        ctx.fillText('Space bar reverses gravity.', canvas.width / 2, canvas.height / 2 + 20);
+        ctx.textAlign = 'left';  // Reset text alignment for other text
+    }
+}
 
 function gameLoop(currentTime) {
     if (lastFrameTime === 0) {
@@ -456,28 +566,26 @@ function gameLoop(currentTime) {
         lastFpsUpdate = currentTime;
     }
 
-    // Calculate delta time
     const deltaTime = currentTime - lastFrameTime;
     lastFrameTime = currentTime;
     
-    // Accumulate time to process
     accumulator += deltaTime;
     
-    // Update physics at a fixed time step
     while (accumulator >= FRAME_TIME) {
         updatePlayer();
+        updateEnemies();
+        updateParticles();
+        updateDeathParticles();
         accumulator -= FRAME_TIME;
     }
     
-    // Render at whatever frame rate the browser can handle
     draw(currentTime);
     requestAnimationFrame(gameLoop);
 }
 
-// Initialize the first level before starting the game loop
+document.body.style.margin = '0';
+document.body.style.overflow = 'hidden';
+resizeCanvas();
+window.addEventListener('resize', resizeCanvas);
 generateLevel();
-
-// Start the game loop
-lastFrameTime = 0;
-accumulator = 0;
 requestAnimationFrame(gameLoop);