diff options
Diffstat (limited to 'html/mountain/game.js')
-rw-r--r-- | html/mountain/game.js | 810 |
1 files changed, 810 insertions, 0 deletions
diff --git a/html/mountain/game.js b/html/mountain/game.js new file mode 100644 index 0000000..39fff3b --- /dev/null +++ b/html/mountain/game.js @@ -0,0 +1,810 @@ +/* ================================ + +45 + +There's something quieter than sleep +Within this inner room! +It wears a sprig upon its breast— +And will not tell its name. + +Some touch it, and some kiss it— +Some chafe its idle hand— +It has a simple gravity +I do not understand! + +I would not weep if I were they— +How rude in one to sob! +Might scare the quiet fairy +Back to her native wood! + +While simple-hearted neighbors +Chat of the "Early dead"— +We—prone to periphrasis +Remark that Birds have fled! + +Emily Dickinson + +================================*/ + +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'; + +const GAME_STATE = { + PLAYING: 'playing', + GAME_OVER: 'game_over' // (ノಠ益ಠ)ノ +}; + +const PLATFORM_TYPE = { + NORMAL: 'normal', + DEADLY: 'deadly', + FALLING: 'falling' +}; + +const PLAYER_SIZE = 20; +const GRAVITY = 0.5; +const JUMP_FORCE = 12; +const MOVE_SPEED = 7; + +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 ENEMY_SPEED = 2; + +const FALLING_PLATFORM_DELAY = 800; +const FALLING_PLATFORM_GRAVITY = 0.5; + +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; + +const PLATFORM_PARTICLE_COUNT = 30; +const PLATFORM_PARTICLE_SPEED = 8; +const PLATFORM_PARTICLE_SIZE = 4; +const PLATFORM_PARTICLE_LIFETIME = 40; + +const HARD_MODE_TIME_LIMIT = 7; // seconds +const SUPER_HARD_MODE_TIME_LIMIT = 5; + +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); + +const COLORS = { + BACKGROUND: '#E0E0E0', + PLATFORM: { + NORMAL: '#1A1A1A', + DEADLY: 'tomato', + FALLING: 'rgba(26, 26, 26, 0.5)' + }, + PLAYER: { + NORMAL: '#1A1A1A', + INVERTED: '#4A90E2' + }, + ENEMY: 'tomato', + EXIT: 'teal', + TEXT: '#1A1A1A', + GAME_OVER: { + OVERLAY: 'rgba(0, 0, 0, 0.7)', + TEXT: 'rgba(255, 255, 255, 0.75)' + }, + DEADLY_BORDER: 'tomato' +}; + +function randomRange(min, max) { + return min + Math.random() * (max - min); +} + +function createPartition(x, y, width, height) { + return { + x, + y, + width, + height, + platform: null, + left: null, + right: null + }; +} + +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) + }; +} + +function platformsOverlap(platform1, platform2) { + const buffer = 5; + + 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 + ); +} + +function createParticle(x, y, velocityY) { + return { + // Randomly places particles around the player, within the player's width + x: x + PLAYER_SIZE / 2 + (Math.random() - 0.5) * PLAYER_SIZE, + + // Put particles to the top or bottom of the player depending on the direction + // the player is moving + y: y + (velocityY > 0 ? 0 : PLAYER_SIZE), + + // Zoot out to the left and right of the player + velocityX: (Math.random() - 0.5) * PARTICLE_SPEED * 2, + + // Zoot up or down, matching the player's direction + velocityY: (Math.random() * PARTICLE_SPEED) * Math.sign(velocityY), + + // Add some variety to the particle sizes + 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; + }); +} + +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; + + for (let i = 0; i < platformCount; i++) { + let validPlatform = null; + let attempts = 0; + + // Try to create a platform, but don't try too hard so that things get stuck + while (!validPlatform && attempts < maxAttempts) { + // Generate a random width for the platform, that is between a min and a max + const platformWidth = randomRange( + MIN_PLATFORM_WIDTH, + Math.min(MAX_PLATFORM_WIDTH, partition.width * 0.8) + ); + + // Generate the minimum x position, which is either the partition's start + // or a position based on the platform's number being placed (i/platformCount) + // This spreads the platforms out more evenly across the partition + const minX = Math.max( + partition.x, + partition.x + (partition.width * (i / platformCount)) + ); + + // Along the same lines, calculate a max x position that accounts for platform width + const maxX = Math.min( + partition.x + partition.width - platformWidth, + partition.x + (partition.width * ((i + 1) / platformCount)) + ); + + // Try to place a platform if there is a valid range + if (maxX > minX) { + // Create a candidate platform with a random position and properties + const candidatePlatform = { + x: randomRange(minX, maxX), + y: randomRange( + partition.y + PLATFORM_HEIGHT, + partition.y + partition.height - PLATFORM_HEIGHT * 2 + ), + width: platformWidth, + height: PLATFORM_HEIGHT, + // After level 1, there is a chance that a platform will be deadly, or falling! + type: (() => { + if (level > 1 && Math.random() < 0.2) return PLATFORM_TYPE.DEADLY; + if (level > 1 && Math.random() < 0.3) { + return PLATFORM_TYPE.FALLING; + } + return PLATFORM_TYPE.NORMAL; + })(), + fallTimer: null, + velocityY: 0 + }; + + // Check if the platform overlaps with any existing platforms + let overlapping = false; + for (const existingPlatform of [...platforms, ...newPlatforms]) { + if (platformsOverlap(candidatePlatform, existingPlatform)) { + overlapping = true; + break; + } + } + + // If there isn't an overlap the platform is valid! + // Place it! + if (!overlapping) { + validPlatform = candidatePlatform; + } + } + + attempts++; + } + + if (validPlatform) { + newPlatforms.push(validPlatform); + } + } + + newPlatforms.forEach(platform => { + if (platform.type === PLATFORM_TYPE.NORMAL && Math.random() < 0.2) { // 20% chance + enemies.push(createEnemy(platform)); + } + }); + + return newPlatforms; +} + +function generateLevel() { + platforms = []; + enemies = []; + + platforms.push({ + x: 0, + y: window.innerHeight - PLATFORM_HEIGHT, + width: MIN_PLATFORM_WIDTH, + height: PLATFORM_HEIGHT, + type: PLATFORM_TYPE.NORMAL + }); + + platforms.push({ + x: window.innerWidth - MIN_PLATFORM_WIDTH, + y: PLAYER_SIZE * 3, + width: MIN_PLATFORM_WIDTH, + height: PLATFORM_HEIGHT, + type: PLATFORM_TYPE.NORMAL + }); + + const horizontalSections = 3; + const verticalSections = 2; + const sectionWidth = window.innerWidth / horizontalSections; + const sectionHeight = (window.innerHeight - PLATFORM_HEIGHT * 4) / 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); + } + + 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); + } + } + + levelStartTime = Date.now(); +} + +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 = []; + levelStartTime = Date.now(); +} + +function killPlayer() { + if (!player.isDead) { + createDeathParticles(player.x, player.y); + player.isDead = true; + gameState = GAME_STATE.GAME_OVER; + } +} + +// Try to compensate for varying viewport widths +function calculateTimeLimit(isSuper) { + const baseLimit = isSuper ? SUPER_HARD_MODE_TIME_LIMIT : HARD_MODE_TIME_LIMIT; + + if (canvas.width <= 2000) return baseLimit; + + const extraWidth = canvas.width - 2000; + const extraSeconds = Math.floor(extraWidth / 1000) * (isSuper ? 0.5 : 1); + + return baseLimit + extraSeconds; +} + +function updatePlayer() { + if (gameState === GAME_STATE.GAME_OVER) { + if (deathAnimationTimer <= 0 && keys['Enter']) { + resetPlayer(); + } + return; + } + + const timeLimit = superHardMode ? + calculateTimeLimit(true) : + calculateTimeLimit(false); + + if ((hardMode || superHardMode) && Date.now() - levelStartTime > timeLimit * 1000) { + killPlayer(); + return; + } + + if (keys['ArrowLeft']) player.velocityX = -MOVE_SPEED; + else if (keys['ArrowRight']) player.velocityX = MOVE_SPEED; + else player.velocityX = 0; + + if ((keys['g'] || keys[' ']) && !player.lastGravityKey) { + player.gravityMultiplier *= -1; + player.velocityY = 0; + } + player.lastGravityKey = keys['g'] || keys[' ']; + + player.velocityY += GRAVITY * player.gravityMultiplier; + + if (keys['ArrowUp'] && keys['ArrowUp'] !== player.lastJumpKey && player.jumpsLeft > 0) { + player.velocityY = -JUMP_FORCE * player.gravityMultiplier; + player.jumpsLeft--; + player.isJumping = true; + + // Add particles for every jump + for (let i = 0; i < PARTICLE_COUNT; i++) { + particles.push(createParticle(player.x, player.y, player.velocityY)); + } + } + player.lastJumpKey = keys['ArrowUp']; + + player.x += player.velocityX; + player.y += player.velocityY; + + 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) { + 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 { + 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) { + killPlayer(); + } else { + if (platform.type === PLATFORM_TYPE.FALLING && !platform.fallTimer) { + platform.fallTimer = setTimeout(() => { + platform.isFalling = true; + }, FALLING_PLATFORM_DELAY); + } + player.velocityY = 0; + player.isJumping = false; + player.jumpsLeft = 2; + } + } + } + } + + if (player.y <= DEADLY_BORDER_HEIGHT) { + if (player.x < canvas.width - exit.size - 300) { + killPlayer(); + } + } else if (player.y + PLAYER_SIZE >= canvas.height - DEADLY_BORDER_HEIGHT) { + killPlayer(); + } + + 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.y + PLAYER_SIZE > canvas.height) { + player.y = canvas.height - PLAYER_SIZE; + player.velocityY = 0; + } + + 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++; + 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; + } + + 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(); + } + }); +} + +function resizeCanvas() { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; +} + +function draw(currentTime) { + if ((hardMode || superHardMode) && gameState === GAME_STATE.PLAYING) { + const timeLimit = superHardMode ? + calculateTimeLimit(true) : + calculateTimeLimit(false); + + const timeElapsed = Date.now() - levelStartTime; + const timeRemaining = Math.max(0, timeLimit * 1000 - timeElapsed); + const progressRatio = timeRemaining / (timeLimit * 1000); + + ctx.fillStyle = COLORS.BACKGROUND; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; + const progressWidth = canvas.width * progressRatio; + ctx.fillRect(progressWidth, 0, canvas.width - progressWidth, canvas.height); + } else { + ctx.fillStyle = COLORS.BACKGROUND; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + + ctx.fillStyle = COLORS.DEADLY_BORDER; + ctx.fillRect(0, 0, canvas.width - exit.size - 300, 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 + ); + + for (let platform of platforms) { + ctx.fillStyle = platform.type === PLATFORM_TYPE.DEADLY ? COLORS.PLATFORM.DEADLY : + platform.type === PLATFORM_TYPE.FALLING ? COLORS.PLATFORM.FALLING : + COLORS.PLATFORM.NORMAL; + ctx.fillRect(platform.x, platform.y, platform.width, platform.height); + } + + ctx.fillStyle = COLORS.EXIT; + ctx.fillRect(exit.x, exit.y, exit.size, exit.size); + + if (!player.isDead) { + ctx.fillStyle = player.gravityMultiplier > 0 ? COLORS.PLAYER.NORMAL : COLORS.PLAYER.INVERTED; + ctx.fillRect(player.x, player.y, PLAYER_SIZE, PLAYER_SIZE); + } + + ctx.fillStyle = COLORS.TEXT; + ctx.font = '20px Arial'; + ctx.textAlign = 'left'; + ctx.fillText(`Level: ${level}`, 10, 30); + + // Particles can have 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 = COLORS.ENEMY; + ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height); + }); + + // Death particles x_x + 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 = COLORS.GAME_OVER.OVERLAY; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.fillStyle = COLORS.GAME_OVER.TEXT; + ctx.font = '100px Arial'; + ctx.textAlign = 'center'; + ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2); + + ctx.font = '24px Arial'; + ctx.fillText('Press ENTER to restart', canvas.width / 2, canvas.height / 2 + 40); + + ctx.textAlign = 'left'; + } + + // Show some help text on the first level so that folks know what to do + if (level === 1 && gameState === GAME_STATE.PLAYING) { + ctx.fillStyle = COLORS.TEXT; + 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'; + } +} + +function gameLoop(currentTime) { + if (lastFrameTime === 0) { + lastFrameTime = currentTime; + lastFpsUpdate = currentTime; + } + + const deltaTime = currentTime - lastFrameTime; + lastFrameTime = currentTime; + + accumulator += deltaTime; + + while (accumulator >= FRAME_TIME) { + updatePlayer(); + updateEnemies(); + updateParticles(); + updateDeathParticles(); + updatePlatforms(); + accumulator -= FRAME_TIME; + } + + draw(currentTime); + requestAnimationFrame(gameLoop); +} + +function createPlatformParticles(platform) { + for (let i = 0; i < PLATFORM_PARTICLE_COUNT; i++) { + const angle = (Math.PI * 2 * i) / PLATFORM_PARTICLE_COUNT; + particles.push({ + x: platform.x + platform.width / 2, + y: platform.y + platform.height / 2, + velocityX: Math.cos(angle) * PLATFORM_PARTICLE_SPEED * (0.5 + Math.random()), + velocityY: Math.sin(angle) * PLATFORM_PARTICLE_SPEED * (0.5 + Math.random()), + size: PLATFORM_PARTICLE_SIZE + Math.random() * 2, + life: PLATFORM_PARTICLE_LIFETIME, + initialOpacity: 0.6 + Math.random() * 0.4 + }); + } +} + +function updatePlatforms() { + platforms.forEach(platform => { + if (platform.type === PLATFORM_TYPE.FALLING && platform.isFalling) { + platform.velocityY += FALLING_PLATFORM_GRAVITY * player.gravityMultiplier; + platform.y += platform.velocityY; + + // Create particles when platform goes off screen + if ((player.gravityMultiplier > 0 && platform.y > canvas.height + 50) || + (player.gravityMultiplier < 0 && platform.y < -50)) { + createPlatformParticles(platform); + } + } + }); + + // Remove platforms that have fallen off screen in either direction + platforms = platforms.filter(platform => + platform.type !== PLATFORM_TYPE.FALLING || + (player.gravityMultiplier > 0 ? platform.y < canvas.height + 100 : platform.y > -100) + ); +} + +let hardMode = false; +let superHardMode = false; +let levelStartTime = 0; + +const hardModeButton = document.createElement('button'); +hardModeButton.textContent = 'Hard Mode: OFF'; +hardModeButton.style.position = 'fixed'; +hardModeButton.style.left = '10px'; +hardModeButton.style.top = '40px'; +hardModeButton.style.padding = '5px 10px'; +hardModeButton.style.backgroundColor = COLORS.PLATFORM.NORMAL; +hardModeButton.style.color = 'white'; +hardModeButton.style.border = 'none'; +hardModeButton.style.cursor = 'pointer'; +document.body.appendChild(hardModeButton); + +const superHardModeButton = document.createElement('button'); +superHardModeButton.textContent = 'Super Hard Mode: OFF'; +superHardModeButton.style.position = 'fixed'; +superHardModeButton.style.left = '10px'; +superHardModeButton.style.top = '70px'; +superHardModeButton.style.padding = '5px 10px'; +superHardModeButton.style.backgroundColor = COLORS.PLATFORM.NORMAL; +superHardModeButton.style.color = 'white'; +superHardModeButton.style.border = 'none'; +superHardModeButton.style.cursor = 'pointer'; +document.body.appendChild(superHardModeButton); + +hardModeButton.addEventListener('click', () => { + hardMode = !hardMode; + superHardMode = false; + hardModeButton.textContent = `Hard Mode: ${hardMode ? 'ON' : 'OFF'}`; + superHardModeButton.textContent = 'Super Hard Mode: OFF'; + resetPlayer(); + hardModeButton.blur(); +}); + +superHardModeButton.addEventListener('click', () => { + superHardMode = !superHardMode; + hardMode = false; + superHardModeButton.textContent = `Super Hard Mode: ${superHardMode ? 'ON' : 'OFF'}`; + hardModeButton.textContent = 'Hard Mode: OFF'; + resetPlayer(); + superHardModeButton.blur(); +}); + +document.body.style.margin = '0'; +document.body.style.overflow = 'hidden'; +resizeCanvas(); +window.addEventListener('resize', resizeCanvas); +generateLevel(); +requestAnimationFrame(gameLoop); |