about summary refs log tree commit diff stats
path: root/html/mountain
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2024-12-05 20:29:14 -0500
committerelioat <elioat@tilde.institute>2024-12-05 20:29:14 -0500
commit99d2a6c0fca85a67f8579dd2efa26bfb89d54ff0 (patch)
tree39e7368822d5170116f7c698f45180e9e44175c9 /html/mountain
parent977bb2ea686f36a7be47553a86f326e9a6f7aa21 (diff)
downloadtour-99d2a6c0fca85a67f8579dd2efa26bfb89d54ff0.tar.gz
*
Diffstat (limited to 'html/mountain')
-rw-r--r--html/mountain/game.js433
-rw-r--r--html/mountain/index.html11
2 files changed, 444 insertions, 0 deletions
diff --git a/html/mountain/game.js b/html/mountain/game.js
new file mode 100644
index 0000000..acabef1
--- /dev/null
+++ b/html/mountain/game.js
@@ -0,0 +1,433 @@
+// Set up canvas
+const canvas = document.createElement('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'
+};
+
+const PLATFORM_TYPE = {
+    NORMAL: 'normal',
+    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 = 5;
+
+// 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
+
+// Add this constant for minimum platform spacing
+const MIN_PLATFORM_SPACING = 100; // Minimum distance between platforms
+
+// 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,
+        y,
+        width,
+        height,
+        platform: null,
+        left: null,
+        right: null
+    };
+}
+
+// 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,
+        vertical ? partition.width * (1 - PARTITION_RATIO) : partition.height * (1 - PARTITION_RATIO)
+    );
+
+    return {
+        left: vertical 
+            ? createPartition(partition.x, partition.y, splitPoint, partition.height)
+            : createPartition(partition.x, partition.y, partition.width, splitPoint),
+        right: vertical
+            ? createPartition(partition.x + splitPoint, partition.y, partition.width - splitPoint, partition.height)
+            : createPartition(partition.x, partition.y + splitPoint, partition.width, partition.height - splitPoint)
+    };
+}
+
+// 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 ||
+        platform1.y + platform1.height + buffer < platform2.y ||
+        platform1.y > platform2.y + platform2.height + buffer
+    );
+}
+
+// Modify the generatePlatformsForPartition function
+function generatePlatformsForPartition(partition) {
+    if (partition.width < MIN_PLATFORM_WIDTH || partition.height < PLATFORM_HEIGHT * 2) {
+        return [];
+    }
+
+    const platformCount = partition.width > MAX_PLATFORM_WIDTH * 1.5 
+        ? Math.floor(randomRange(1, 3)) 
+        : 1;
+
+    const newPlatforms = [];
+    const maxAttempts = 20; // Increased max attempts for better placement
+
+    for (let i = 0; i < platformCount; i++) {
+        let validPlatform = null;
+        let attempts = 0;
+
+        while (!validPlatform && attempts < maxAttempts) {
+            const platformWidth = randomRange(
+                MIN_PLATFORM_WIDTH, 
+                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))
+            );
+            const maxX = Math.min(
+                partition.x + partition.width - platformWidth,
+                partition.x + (partition.width * ((i + 1) / platformCount))
+            );
+            
+            // Only proceed if we have valid space
+            if (maxX > minX) {
+                const candidatePlatform = {
+                    x: randomRange(minX, maxX),
+                    y: randomRange(
+                        partition.y + PLATFORM_HEIGHT,
+                        partition.y + partition.height - PLATFORM_HEIGHT * 2
+                    ),
+                    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)) {
+                        overlapping = true;
+                        break;
+                    }
+                }
+
+                if (!overlapping) {
+                    validPlatform = candidatePlatform;
+                }
+            }
+
+            attempts++;
+        }
+
+        if (validPlatform) {
+            newPlatforms.push(validPlatform);
+        }
+    }
+
+    return newPlatforms;
+}
+
+// Game state
+let gameState = GAME_STATE.PLAYING;
+let level = 1;
+let platforms = [];
+let player = {
+    x: PLAYER_SIZE,
+    y: window.innerHeight - PLAYER_SIZE,
+    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
+};
+
+// Add reset player function
+function resetPlayer() {
+    player.x = PLAYER_SIZE;
+    player.y = window.innerHeight - 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();
+}
+
+// Update the generateLevel function to use these new functions
+function generateLevel() {
+    platforms = [];
+    
+    // Add start platform
+    const startPlatform = {
+        x: 0,
+        y: window.innerHeight - PLATFORM_HEIGHT,
+        width: MIN_PLATFORM_WIDTH,
+        height: PLATFORM_HEIGHT
+    };
+    platforms.push(startPlatform);
+    
+    // Add end platform
+    const endPlatform = {
+        x: window.innerWidth - MIN_PLATFORM_WIDTH,
+        y: PLAYER_SIZE * 3,
+        width: MIN_PLATFORM_WIDTH,
+        height: PLATFORM_HEIGHT
+    };
+    platforms.push(endPlatform);
+
+    // Create multiple root partitions for better coverage
+    const verticalSections = 3;
+    const horizontalSections = 2;
+    const sectionWidth = window.innerWidth / horizontalSections;
+    const sectionHeight = (window.innerHeight - PLATFORM_HEIGHT * 6) / verticalSections;
+
+    function subdivide(node, depth) {
+        if (depth === 0) {
+            return generatePlatformsForPartition(node);
+        }
+        
+        const vertical = Math.random() > 0.4;
+        if ((vertical && node.width > MIN_PARTITION_SIZE * 1.5) ||
+            (!vertical && node.height > MIN_PARTITION_SIZE * 1.5)) {
+            
+            const { left, right } = splitPartition(node, vertical);
+            return [
+                ...subdivide(left, depth - 1),
+                ...subdivide(right, depth - 1)
+            ];
+        }
+        
+        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(
+                i * sectionWidth,
+                PLATFORM_HEIGHT * 2 + (j * sectionHeight),
+                sectionWidth,
+                sectionHeight
+            );
+
+            const newPlatforms = subdivide(root, Math.min(3 + Math.floor(level / 2), 5));
+            platforms.push(...newPlatforms);
+        }
+    }
+}
+
+// Handle player input
+const keys = {};
+window.addEventListener('keydown', e => keys[e.key] = true);
+window.addEventListener('keyup', e => keys[e.key] = false);
+
+function updatePlayer() {
+    if (gameState === GAME_STATE.GAME_OVER) {
+        if (keys[' ']) { // Space key to restart when game over
+            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.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;
+    }
+    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 &&
+            player.x < platform.x + platform.width) {
+            
+            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;
+                    player.y = platform.y + platform.height;
+                }
+            }
+
+            if (collision) {
+                if (platform.type === PLATFORM_TYPE.DEADLY) {
+                    player.isDead = true;
+                    gameState = GAME_STATE.GAME_OVER;
+                } else {
+                    player.velocityY = 0;
+                    player.isJumping = false;
+                    player.jumpsLeft = 2;
+                }
+            }
+        }
+    }
+    
+    // Keep player in bounds (modified for gravity direction)
+    if (player.x < 0) player.x = 0;
+    if (player.x + PLAYER_SIZE > canvas.width) player.x = canvas.width - PLAYER_SIZE;
+    if (player.y < 0) {
+        player.y = 0;
+        player.velocityY = 0;
+        if (player.gravityMultiplier < 0) {
+            player.isJumping = false;
+            player.jumpsLeft = 2;
+        }
+    }
+    if (player.y + PLAYER_SIZE > canvas.height) {
+        player.y = canvas.height - PLAYER_SIZE;
+        player.velocityY = 0;
+        if (player.gravityMultiplier > 0) {
+            player.isJumping = false;
+            player.jumpsLeft = 2;
+        }
+    }
+    
+    // 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();
+    }
+}
+
+function draw() {
+    // Clear canvas with light grey
+    ctx.fillStyle = '#E0E0E0';
+    ctx.fillRect(0, 0, canvas.width, canvas.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
+        ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
+    }
+    
+    // Draw exit (keeping green for now)
+    ctx.fillStyle = '#32CD32';
+    ctx.fillRect(exit.x, exit.y, exit.size, exit.size);
+    
+    // Draw player in almost black
+    if (!player.isDead) {
+        ctx.fillStyle = '#1A1A1A';
+        ctx.fillRect(player.x, player.y, PLAYER_SIZE, PLAYER_SIZE);
+    }
+    
+    // Draw level number
+    ctx.fillStyle = '#000000';
+    ctx.font = '20px Arial';
+    ctx.fillText(`Level: ${level}`, 10, 30);
+
+    // Draw game over screen
+    if (gameState === GAME_STATE.GAME_OVER) {
+        ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
+        ctx.fillRect(0, 0, canvas.width, canvas.height);
+        
+        ctx.fillStyle = '#FFFFFF';
+        ctx.font = '48px Arial';
+        ctx.textAlign = 'center';
+        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.textAlign = 'left'; // Reset text align
+    }
+}
+
+// Game loop
+function gameLoop() {
+    updatePlayer();
+    draw();
+    requestAnimationFrame(gameLoop);
+}
+
+// Start game
+generateLevel();
+gameLoop();
diff --git a/html/mountain/index.html b/html/mountain/index.html
new file mode 100644
index 0000000..cac722a
--- /dev/null
+++ b/html/mountain/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Mountain</title>
+</head>
+<body>
+    <script src="game.js"></script>
+</body>
+</html>
\ No newline at end of file