diff options
author | elioat <elioat@tilde.institute> | 2024-12-05 20:29:14 -0500 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2024-12-05 20:29:14 -0500 |
commit | 99d2a6c0fca85a67f8579dd2efa26bfb89d54ff0 (patch) | |
tree | 39e7368822d5170116f7c698f45180e9e44175c9 /html/mountain | |
parent | 977bb2ea686f36a7be47553a86f326e9a6f7aa21 (diff) | |
download | tour-99d2a6c0fca85a67f8579dd2efa26bfb89d54ff0.tar.gz |
*
Diffstat (limited to 'html/mountain')
-rw-r--r-- | html/mountain/game.js | 433 | ||||
-rw-r--r-- | html/mountain/index.html | 11 |
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 |