diff options
-rw-r--r-- | js/mountain/game.js | 235 | ||||
-rw-r--r-- | js/mountain/hill.js | 319 | ||||
-rw-r--r-- | js/mountain/index.html | 19 |
3 files changed, 247 insertions, 326 deletions
diff --git a/js/mountain/game.js b/js/mountain/game.js index 3c96005..5601d94 100644 --- a/js/mountain/game.js +++ b/js/mountain/game.js @@ -1,3 +1,234 @@ -// a 2d platformer game, where the character can jump, double jump, move left and right. -// That character can avoid spikes and obstacles. +// TODO: if there are no more levels show a "game win" message +// Game constants +const canvas = document.getElementById('gameCanvas'); +const ctx = canvas.getContext('2d'); +canvas.width = 800; +canvas.height = 600; + +const GRAVITY = 0.5; +const JUMP_STRENGTH = -12; +const MOVE_SPEED = 5; + +let keys = { left: false, right: false, up: false, down: false, space: false }; + +let levels = [ + [ + [4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ], + [ + // Next level example + [4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ] + // Add more levels as needed +]; + +let currentLevel = 0; + +class Player { + constructor(x, y) { + this.x = x; + this.y = y; + this.width = 30; + this.height = 30; + this.velocityX = 0; + this.velocityY = 0; + this.jumping = false; + this.doubleJumping = false; + } + + update() { + if (keys.left) this.velocityX = -MOVE_SPEED; + if (keys.right) this.velocityX = MOVE_SPEED; + if (!keys.left && !keys.right) this.velocityX = 0; + + this.velocityY += GRAVITY; + this.x += this.velocityX; + this.y += this.velocityY; + + // Collision detection + this.checkCollision(); + + // Limit falling speed + if (this.velocityY > 10) this.velocityY = 10; + } + + jump() { + if (!this.jumping) { + this.velocityY = JUMP_STRENGTH; + this.jumping = true; + } else if (!this.doubleJumping) { + this.velocityY = JUMP_STRENGTH; + this.doubleJumping = true; + } + } + + checkCollision() { + let row, col, belowRow, aboveRow, leftCol, rightCol; + + // Check collision with ground from below + row = Math.floor(this.y / 30); + col = Math.floor(this.x / 30); + belowRow = Math.floor((this.y + this.height) / 30); + + if (levels[currentLevel][belowRow] && levels[currentLevel][belowRow][col] === 1) { + if (this.velocityY > 0) { + this.y = belowRow * 30 - this.height; + this.velocityY = 0; + this.jumping = false; + this.doubleJumping = false; + } + } + + // Check collision with ground from above + aboveRow = Math.floor((this.y - 1) / 30); + if (levels[currentLevel][aboveRow] && levels[currentLevel][aboveRow][col] === 1) { + if (this.velocityY < 0) { + this.y = (aboveRow + 1) * 30; + this.velocityY = 0; + } + } + + // Check collision with ground from the left + leftCol = Math.floor(this.x / 30); + if (levels[currentLevel][row] && levels[currentLevel][row][leftCol] === 1) { + if (this.velocityX < 0) { + this.x = (leftCol + 1) * 30; + this.velocityX = 0; + } + } + + // Check collision with ground from the right + rightCol = Math.floor((this.x + this.width) / 30); + if (levels[currentLevel][row] && levels[currentLevel][row][rightCol] === 1) { + if (this.velocityX > 0) { + this.x = rightCol * 30 - this.width; + this.velocityX = 0; + } + } + + // Collision with lava + if (levels[currentLevel][row] && levels[currentLevel][row][col] === 2) { + this.reset(); + } + + // Collision with save point + if (levels[currentLevel][row] && levels[currentLevel][row][col] === 3) { + localStorage.setItem('savePoint', JSON.stringify({ x: this.x, y: this.y, level: currentLevel })); + } + + // Collision with goal + if (levels[currentLevel][row] && levels[currentLevel][row][col] === 5) { + alert('Level Complete!'); + loadNextLevel(); + } + } + + reset() { + let savePoint = JSON.parse(localStorage.getItem('savePoint')) || { x: 30, y: 30, level: 0 }; + this.x = savePoint.x; + this.y = savePoint.y; + currentLevel = savePoint.level; + this.velocityY = 0; + } + + render() { + ctx.fillStyle = 'blue'; + ctx.fillRect(this.x, this.y, this.width, this.height); + } +} + +function findPlayerStartPosition(level) { + for (let row = 0; row < levels[level].length; row++) { + for (let col = 0; col < levels[level][row].length; col++) { + if (levels[level][row][col] === 4) { + return { x: col * 30, y: row * 30 }; + } + } + } + // Default position if no start point found + return { x: 30, y: 570 }; +} + +function renderLevel() { + for (let row = 0; row < levels[currentLevel].length; row++) { + for (let col = 0; col < levels[currentLevel][row].length; col++) { + if (levels[currentLevel][row][col] === 1) { + ctx.fillStyle = 'brown'; + ctx.fillRect(col * 30, row * 30, 30, 30); + } else if (levels[currentLevel][row][col] === 2) { + ctx.fillStyle = 'red'; + ctx.fillRect(col * 30, row * 30, 30, 30); + } else if (levels[currentLevel][row][col] === 3) { + ctx.fillStyle = 'green'; + ctx.fillRect(col * 30, row * 30, 30, 30); + } else if (levels[currentLevel][row][col] === 5) { + ctx.fillStyle = 'gold'; + ctx.fillRect(col * 30, row * 30, 30, 30); + } + } + } +} + +function loadNextLevel() { + currentLevel++; + if (currentLevel >= levels.length) { + alert("You've completed all levels!"); + currentLevel = 0; + } + let startPosition = findPlayerStartPosition(currentLevel); + player.x = startPosition.x; + player.y = startPosition.y; + player.velocityY = 0; + localStorage.setItem('savePoint', JSON.stringify({ x: player.x, y: player.y, level: currentLevel })); +} + +let startPosition = findPlayerStartPosition(currentLevel); +let player = new Player(startPosition.x, startPosition.y); + +function gameLoop() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + renderLevel(); + player.update(); + player.render(); + + requestAnimationFrame(gameLoop); +} + +document.addEventListener('keydown', (e) => { + if (e.code === 'ArrowLeft') keys.left = true; + if (e.code === 'ArrowRight') keys.right = true; + if (e.code === 'ArrowUp') keys.up = true; + if (e.code === 'ArrowDown') keys.down = true; + if (e.code === 'Space') player.jump(); +}); + +document.addEventListener('keyup', (e) => { + if (e.code === 'ArrowLeft') keys.left = false; + if (e.code === 'ArrowRight') keys.right = false; + if (e.code === 'ArrowUp') keys.up = false; + if (e.code === 'ArrowDown') keys.down = false; +}); + +gameLoop(); diff --git a/js/mountain/hill.js b/js/mountain/hill.js deleted file mode 100644 index 0e960a9..0000000 --- a/js/mountain/hill.js +++ /dev/null @@ -1,319 +0,0 @@ -const canvas = document.getElementById('gameCanvas'); -canvas.width = window.innerWidth; -canvas.height = window.innerHeight; -canvas.style.backgroundColor = '#f0f0f0'; -const ctx = canvas.getContext('2d'); - -(function(){ - - const COLORS = { - player: '#2d2d2d', - particles: '#2d2d2d', - objects: '#2d2d2d', - terrain: '#2d2d2d' - }; - - const gravity = 1; - const jumpForce = 14.75; - - const player = { - x: 200, - y: 0, - vx: 16, - vy: 16, - vm: 32, - jumpForce: gravity + jumpForce, - onGround: false, - width: 20, - height: 20, - distanceTraveled: 0 - }; - - const terrain = { - start: { x: 0, y: 0 }, - end: { x: canvas.width, y: canvas.height }, - height: 8 - }; - - const objects = []; - let lastObjectX = 0; - - const particles = []; - - const createObject = () => { - const object = { - x: lastObjectX + 10 * canvas.width / 10, - width: Math.random() * 100, - height: Math.random() * 22 - }; - - let terrainY = ((terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x)) * (object.x - terrain.start.x) + terrain.start.y; - - object.y = terrainY - object.height; - - objects.push(object); - - lastObjectX = object.x; - }; - - const isColliding = (obj1, obj2) => ( - obj1.x < obj2.x + obj2.width && - obj1.x + obj1.width > obj2.x && - obj1.y < obj2.y + obj2.height && - obj1.y + obj1.height > obj2.y - ); - - const checkCollision = () => { - objects.forEach(object => { - if (isColliding(player, object)) { - if (player.vx >= 1) { - player.vx -= 0.5; - player.vy -= 0.5; - } - } - }); - }; - - const updatePlayer = () => { - player.vy += gravity; - - player.x += player.vx; - player.y += player.vy; - - const terrainY = (terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x) * (player.x - terrain.start.x) + terrain.start.y; - - if (player.y + player.height > terrainY) { - player.y = terrainY - player.height; - player.vy = 0; - } - - if (particlesEnabled) { - particles.push({ - x: player.x, - y: player.y, - vx: Math.random() * 2 - 1, - vy: Math.random() * 2 - 1, - lifespan: player.vx * 4 - }); - } - }; - - const updateObjects = () => { - for (let object of objects) { - object.y += gravity; - - let terrainY = ((terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x)) * (object.x - terrain.start.x) + terrain.start.y; - - if (object.y + object.height > terrainY) { - object.y = terrainY - object.height; - } - } - }; - - const drawPlayer = () => { - - if (player.vx < 4) { - ctx.fillStyle = COLORS.player; - ctx.font = '22px Arial'; - const text = 'Jump to build up speed!'; - ctx.fillText(text, player.x, player.y - 12); - } else { - if (distanceMeterEnabled) { - ctx.fillStyle = '#aaa'; - ctx.font = '22px Arial'; - const text = player.distanceTraveled.toFixed(0) + ' px'; - ctx.fillText(text, player.x, player.y - 12); - } - } - - ctx.fillStyle = COLORS.player; - ctx.fillRect(player.x, player.y, player.width, player.height); - }; - - const drawObjects = () => { - ctx.fillStyle = COLORS.objects; - objects.forEach(object => { - ctx.beginPath(); - ctx.arc(object.x, object.y, object.width / 2, 0, Math.PI * 2, true); - ctx.fill(); - }); - }; - - const drawTerrain = () => { - ctx.beginPath(); - ctx.moveTo(terrain.start.x, terrain.start.y); - ctx.lineTo(terrain.end.x, terrain.end.y); - ctx.strokeStyle = COLORS.terrain; - ctx.lineWidth = terrain.height; - ctx.stroke(); - }; - - const drawParticles = () => { - if (particlesEnabled) { - ctx.fillStyle = COLORS.particles; - particles.forEach((particle, index) => { - ctx.fillRect(particle.x, particle.y, 5, 5); - particle.x += particle.vx; - particle.y += particle.vy; - particle.lifespan--; - if (particle.lifespan === 0) { - particles.splice(index, 1); - } - }); - } - }; - - - const draw = () => { - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.save(); - ctx.translate(-player.x + canvas.width / 4, -player.y + canvas.height / 2); - drawPlayer(); - terrain.start.x = player.x - canvas.width; - terrain.end.x = player.x + canvas.width; - drawObjects(); - drawTerrain(); - drawParticles(); - ctx.restore(); - }; - - const update = (currentTime = 0) => { - const deltaTime = currentTime - lastUpdateTime; - - player.distanceTraveled = Math.abs(player.x - 200); - - if (deltaTime >= frameDelay) { - createObject(); - updatePlayer(); - updateObjects(); - checkCollision(); - lastUpdateTime = currentTime; - } - - requestAnimationFrame(update); - }; - - const initialize = () => { - terrain.start = { x: 0, y: 0 }; - terrain.end = { x: canvas.width, y: canvas.height }; - }; - - const gameLoop = () => { - initialize(); - draw(); - requestAnimationFrame(gameLoop); - }; - - const playerDoJump = () => { - const terrainY = (terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x) * (player.x - terrain.start.x) + terrain.start.y; - if (player.y + player.height >= terrainY) { - player.vy = player.jumpForce * -1; - if (player.vx < player.vm) { - player.vx += 0.5; - } - } - }; - - const handleKeyDown = event => { - if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowUp' || event.key === 'w' || event.key === 'W') { - playerDoJump(); - } - }; - - canvas.addEventListener('click', playerDoJump); - canvas.addEventListener('touchstart', playerDoJump); - window.addEventListener('keydown', handleKeyDown); - - window.addEventListener('resize', () => { - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; - draw(); - }); - - const helpText = 'Controls:\n\nJump: space, up arrow, w or tap the screen on mobile\nFullscreen: f or the button in the upper-right\nParticles: p\nDistance: d\nHelp: h' - - document.addEventListener('keydown', function(event) { - if (event.key === 'f') { - if (canvas.requestFullscreen) { - canvas.requestFullscreen(); - } else if (canvas.mozRequestFullScreen) { - canvas.mozRequestFullScreen(); - } else if (canvas.webkitRequestFullscreen) { - canvas.webkitRequestFullscreen(); - } else if (canvas.msRequestFullscreen) { - canvas.msRequestFullscreen(); - } - } else if (event.key === 'p' || event.key === 'P') { - particlesEnabled = !particlesEnabled; - } else if (event.key === 'h' || event.key === 'H') { - alert(helpText); - } else if (event.key === 'd' || event.key === 'D') { - distanceMeterEnabled = !distanceMeterEnabled; - } - }); - - let particlesEnabled = true; - let distanceMeterEnabled = false; - - const fullscreenButton = document.createElement('button'); - fullscreenButton.style.position = 'absolute'; - fullscreenButton.style.top = '10px'; - fullscreenButton.style.right = '10px'; - fullscreenButton.textContent = 'Fullscreen'; - - const distanceMeterButton = document.createElement('button'); - distanceMeterButton.style.position = 'absolute'; - distanceMeterButton.style.top = '40px'; - distanceMeterButton.style.right = '10px'; - distanceMeterButton.textContent = 'Distance'; - - const particlesButton = document.createElement('button'); - particlesButton.style.position = 'absolute'; - particlesButton.style.top = '70px'; - particlesButton.style.right = '10px'; - particlesButton.textContent = 'Particles'; - - const helpButton = document.createElement('button'); - helpButton.style.position = 'absolute'; - helpButton.style.top = '100px'; - helpButton.style.right = '10px'; - helpButton.textContent = 'Help'; - - fullscreenButton.addEventListener('click', function() { - if (canvas.requestFullscreen) { - canvas.requestFullscreen(); - } else if (canvas.mozRequestFullScreen) { - canvas.mozRequestFullScreen(); - } else if (canvas.webkitRequestFullscreen) { - canvas.webkitRequestFullscreen(); - } else if (canvas.msRequestFullscreen) { - canvas.msRequestFullscreen(); - } - }); - - distanceMeterButton.addEventListener('click', function() { - distanceMeterEnabled = !distanceMeterEnabled; - }); - - particlesButton.addEventListener('click', function() { - particlesEnabled = !particlesEnabled; - }); - - helpButton.addEventListener('click', function() { - alert(helpText); - }); - - canvas.parentNode.appendChild(fullscreenButton); - canvas.parentNode.appendChild(distanceMeterButton); - canvas.parentNode.appendChild(particlesButton); - canvas.parentNode.appendChild(helpButton); - - - let lastUpdateTime = 0; - const fps = 60; - const frameDelay = 1000 / fps; - - update(); - gameLoop(); - -} )(); \ No newline at end of file diff --git a/js/mountain/index.html b/js/mountain/index.html index 016ad87..75d47aa 100644 --- a/js/mountain/index.html +++ b/js/mountain/index.html @@ -2,11 +2,20 @@ <html lang="en"> <head> <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>mountain</title> - <script src="game.js"></script> + <title>2D Side Scrolling Platformer</title> + <style> + body { + margin: 0; + padding: 0; + background-color: beige; + } + #gameCanvas { + display: block; + } + </style> </head> <body> - <canvas id="mountain"></canvas> + <canvas id="gameCanvas"></canvas> + <script src="game.js"></script> </body> -</html> \ No newline at end of file +</html> |