diff --git a/assets/css/main.css b/assets/css/main.css index 035659a..54b8e98 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -27,3 +27,12 @@ body * { flex-direction: column; height: 100%; } + +#restart-btn { + width: 100%; + max-width: 150px; + position: absolute; +} +.hidden { + display: none; +} \ No newline at end of file diff --git a/assets/js/constants.js b/assets/js/constants.js index fdc2d64..e257dce 100644 --- a/assets/js/constants.js +++ b/assets/js/constants.js @@ -1 +1,2 @@ -const KEY_UP = 32; +const ARROW_UP = 38; +const SPACE = 32; \ No newline at end of file diff --git a/assets/js/index.js b/assets/js/index.js index 660a2c6..9ec8636 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -1,4 +1,31 @@ window.addEventListener('load', () => { + const startBtn = document.getElementById('restart-btn') + // iteration - 1: create & start the game + const game = new Game('canvas-game', () => { + startBtn.classList.toggle('hidden'); + }); + + game.start(); + // iteration - 2: add key listeners to the game + + startBtn.addEventListener('click', () => { + game.restart(); + startBtn.classList.toggle('hidden'); + }) + + document.addEventListener('keydown', () => { + if (game.drawIntervalId) { + game.onKeyEvent(event) + } else { + startBtn.classList.toggle('hidden'); + game.restart() + } + }); + + document.addEventListener('keyup', () => { + game.onKeyEvent(event); + }); + }); diff --git a/assets/js/models/background.js b/assets/js/models/background.js new file mode 100644 index 0000000..67ca65a --- /dev/null +++ b/assets/js/models/background.js @@ -0,0 +1,72 @@ +class Background { + + constructor(ctx) { + this.ctx = ctx; + // positions + this.x = 0; + this.y = 434; + this.vx = 3; + this.width = this.ctx.canvas.width; + this.height = this.ctx.canvas.height; + + this.bgImg = new Image(); + this.bgImg.src = 'assets/img/game-bg.png'; + // set image dimensions + this.bgImg.isReady = false; + this.bgImg.onload = () => { + this.bgImg.isReady = true; + this.bgImg.width = this.width; + this.bgImg.height = this.height; + } + + this.footerImg = new Image(); + this.footerImg.src = 'assets/img/game-bg-footer.png'; + // set image dimensions + this.footerImg.isReady = false; + this.footerImg.onload = () => { + this.footerImg.isReady = true; + this.footerImg.width = this.width; + this.footerImg.height = 64; + } + + } + + draw() { + // iteration 1: draw the static backgorund img + if (this.bgImg.isReady && this.footerImg.isReady) { + this.ctx.drawImage( + this.bgImg, + 0, + 0, + this.bgImg.width, + this.bgImg.height, + ) + + // iteration 1: draw footer img twice + this.ctx.drawImage( + this.footerImg, + this.x, + this.y, + this.footerImg.width, + this.footerImg.height, + ) + + this.ctx.drawImage( + this.footerImg, + this.x + this.footerImg.width, + this.y, + this.footerImg.width, + this.footerImg.height, + ) + } + } + + move() { + // iteration 1: move the ground + this.x -= this.vx; + // iteration 1: check bounds and reset position + if (this.width + this.x <= 0) { + this.x = 0; + } + } +} diff --git a/assets/js/models/brackgorund.js b/assets/js/models/brackgorund.js deleted file mode 100644 index 401d439..0000000 --- a/assets/js/models/brackgorund.js +++ /dev/null @@ -1,36 +0,0 @@ -class Background { - - constructor(ctx) { - this.ctx = ctx; - // positions - this.x = 0; - this.y = 434; - this.vx = 3; - this.width = this.ctx.canvas.width; - this.height = this.ctx.canvas.height; - - this.bgImg = new Image(); - this.bgImg.src = 'assets/img/game-bg.png'; - // set image dimensions - this.bgImg.width = this.width; - this.bgImg.height = this.height; - - - this.footerImg = new Image(); - this.footerImg.src = 'assets/img/game-bg-footer.png'; - // set image dimensions - this.footerImg.width = this.width; - this.footerImg.height = 64; - - } - - draw() { - // iteration 1: draw the static backgorund img - - // iteration 1: draw footer img twice - - move() { - // iteration 1: move the ground - // iteration 1: check bounds and reset position - } -} diff --git a/assets/js/models/flappybird.js b/assets/js/models/flappybird.js index e4460af..3e90ee2 100644 --- a/assets/js/models/flappybird.js +++ b/assets/js/models/flappybird.js @@ -10,11 +10,13 @@ class FlappyBird { this.sprite = new Image(); this.sprite.src = 'assets/img/bird.png'; // sprite setup + this.sprite.isReady = false; this.sprite.horizontalFrameIndex = 0; this.sprite.verticalFrameIndex = 0; this.sprite.horizontalFrames = 3; this.sprite.verticalFrames = 1; this.sprite.onload = () => { + this.sprite.isReady = true; this.sprite.frameWith = Math.floor(this.sprite.width / this.sprite.horizontalFrames); this.sprite.frameHeight = Math.floor(this.sprite.height / this.sprite.verticalFrames); this.width = this.sprite.frameWith; @@ -27,27 +29,67 @@ class FlappyBird { onKeyEvent(event) { const isJumping = event.type === 'keydown'; switch (event.keyCode) { - case KEY_UP: + case ARROW_UP: + case SPACE: // iteration 2: jump! if necessary =D + if (!isJumping) { + this.y -= this.jumpImpulse; + } } } draw() { // draw sprite - - // animate sprite - this.animate(); + if (this.sprite.isReady) { + this.ctx.drawImage( + this.sprite, + this.sprite.horizontalFrameIndex * this.sprite.frameWith, + this.sprite.verticalFrameIndex * this.sprite.frameHeight, + this.sprite.frameWith, + this.sprite.frameHeight, + this.x, + this.y, + this.width, + this.height, + ) + this.drawCount++; + // animate sprite + this.animate(); + } } animate() { // iteration 2: configure frame animation + this.animateFrame(0, 2, 3, 10); + } + + animateFrame(initVerticalFrame, initHorizontalFrame, segments, frequency) { + // Paso 1: Verifica si el índice de fotograma vertical actual es diferente del inicial proporcionado. + if (this.sprite.verticalFrameIndex !== initVerticalFrame) { + // Si es diferente, establece el índice vertical al valor inicial proporcionado + this.sprite.verticalFrameIndex = initVerticalFrame; + // También establece el índice de fotograma horizontal al valor inicial proporcionado + this.sprite.horizontalFrameIndex = initHorizontalFrame; + } + // Paso 2: Si el índice vertical ya es el inicial, verifica si debe actualizarse el fotograma horizontal + else if (this.drawCount % frequency === 0) { + // Actualiza el índice horizontal al siguiente fotograma, asegurándose de que se reinicie después de alcanzar el límite (uso de módulo %) + this.sprite.horizontalFrameIndex = (this.sprite.horizontalFrameIndex + 1) % segments; + // Reinicia el contador de dibujo para volver a contar los frames hasta el próximo cambio + this.drawCount = 0; + } } move() { // iteration 2: move the y + this.y += this.vy } collides(element) { // iteration 3: check collisions (true|false) + return this.x < element.x + element.width && + this.x + this.width > element.x && + this.y < element.y + element.height && + this.y + this.height > element.y; } } diff --git a/assets/js/models/game.js b/assets/js/models/game.js index 43081e1..6253c24 100644 --- a/assets/js/models/game.js +++ b/assets/js/models/game.js @@ -10,78 +10,160 @@ class Game { this.fps = 1000 / 60; // iteration 1: setup the background + this.background = new Background(this.ctx); // iteration 2: setup the flappy + this.flappyBird = new FlappyBird( + this.ctx, + 70, + this.canvas.height / 2, + ); this.pipes = []; this.drawPipesCount = 0; this.pipesFrequency = 100; // bonus: setup the score + + this.score = 0; + this.bestScore = Number(localStorage.getItem('best-score') || 0); + this.onGameEnd = onGameEnd; } - + onKeyEvent(event) { // iteration 2: link flappy key events + this.flappyBird.onKeyEvent(event) } start() { if (!this.drawIntervalId) { this.drawIntervalId = setInterval(() => { // Iteration 1: each 60f clear - move - draw - [next iterations: addPipes - checkCollisions - checkScore] + this.clear(); + this.draw(); + this.move(); + this.addPipes(); + this.checkCollisions(); + this.checkScore(); }, this.fps); } } stop() { // Iteration 1: stop the game + clearInterval(this.drawIntervalId); + this.drawIntervalId = undefined; } restart() { // Bonus: restart on demand + this.score = 0; + this.pipes = []; + this.flappyBird.x = 70; + this.flappyBird.y = this.canvas.height / 2; + this.start(); } end() { // Iteration 4: stop the game and setup score + this.stop(); + if (this.score > this.bestScore) { + this.bestScore = this.score; + localStorage.setItem('best-score', this.bestScore) + } + this.onGameEnd(); } clear() { // Iteration 1: clean the screen + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.pipes = this.pipes.filter(pipe => pipe.x + pipe.width >= 0); } move() { // Iteration 1: move the background + this.background.move(); // Iteration 2: move the flappy + this.flappyBird.move(); // Iteration 3: move the pipes + this.pipes.forEach(pipe => pipe.move()) } addPipes() { // Iteration 3: each draw pipes frequency cycles concat a pair of pipes to the pipes array and reset the draw cycle + if (this.flappyBird.sprite.isReady && (this.drawPipesCount % this.pipesFrequency === 0)) { + this.pipes = this.pipes.concat(this.randPairOfPipes()); + this.drawPipesCount = 0; + } } randPairOfPipes() { const space = this.canvas.height - this.background.footerImg.height; - const gap = (this.flappybird.height * 2) + this.flappybird.jumpImpulse; + const gap = (this.flappyBird.height * 2) + this.flappyBird.jumpImpulse; const topSize = Math.floor(Math.random() * (space - gap) * 0.75) const bottomSize = space - topSize - gap; // Iteration 3: return two new pipes one at the top and other at the bottom - return [] + return [ + new Pipe( + this.ctx, + this.canvas.width, + 0, + topSize, + 'top' + ), + new Pipe( + this.ctx, + this.canvas.width, + this.canvas.height - this.background.footerImg.height - bottomSize, + bottomSize, + 'bottom' + ), + ] } checkCollisions() { // Iteration 4: check pipes collisions among flappy and end game if any pipe collides with the bird + const pipeCollides = this.pipes.some(pipe => this.flappyBird.collides(pipe)); + + if (pipeCollides || this.flappyBird.y + this.flappyBird.height >= this.canvas.height - this.background.footerImg.height) { + this.end(); + } } checkScore() { // Bonus + const pipe = this.pipes + .filter(pipe => pipe.mode === 'top') + .filter(pipe => !pipe.isChecked) + .find(pipe => pipe.x + pipe.width < this.flappyBird.x); + + if (pipe) { + pipe.isChecked = true; + this.score++; + } } draw() { // Iteration 1: draw the background + this.background.draw(); // Iteration 2: draw the flappy - // Iteration 2: draw the pipes + this.flappyBird.draw(); + // Iteration 3: draw the pipes + this.pipes.forEach(pipe => pipe.draw()); // Bonus: draw the score - + this.ctx.save(); + this.ctx.font = "30px FlappyFont"; + this.ctx.fillStyle = "#FFFFFF"; + this.ctx.fillText(this.score, 10, 40); + this.ctx.font = "20px FlappyFont"; + this.ctx.fillStyle = "#73BF2E"; + this.ctx.fillText ( + `best: ${this.bestScore}`, + 10, + this.canvas.height -10 + ) + this.ctx.restore(); this.drawPipesCount++; } } diff --git a/assets/js/models/pipe.js b/assets/js/models/pipe.js index cf42cd8..c12e317 100644 --- a/assets/js/models/pipe.js +++ b/assets/js/models/pipe.js @@ -11,16 +11,37 @@ class Pipe { this.img = new Image(); // iteration 3: load the source checking the mode and setup this.with (must be the image with) this.img.src = `assets/img/pipe-${mode}.png`; + this.img.isReady = false; this.img.onload = () => { + this.img.isReady = true; this.width = this.img.width; } + this.isChecked = false; } draw() { - // iteration 3: draw the pipe don't worry if looks unscaled. You can start drawing a green rectangle + if (this.img.isReady) { + let ySeek = 0; + // Ajustar la posición vertical de recorte según el modo + if (this.mode === 'top') { + ySeek = (this.height > this.img.height) ? 0 : this.img.height - this.height; + } + this.ctx.drawImage( + this.img, + 0, // Coordenada x de inicio en la imagen + ySeek, // Coordenada y de inicio en la imagen + Math.min(this.img.width, this.width), // Ancho de recorte de la imagen + Math.min(this.img.height, this.height), // Alto de recorte de la imagen + this.x, // Coordenada x en el canvas + this.y, // Coordenada y en el canvas + this.width, // Ancho en el canvas + this.height // Alto en el canvas + ); + } } - move () { + move() { // iteration 3: move the pipe + this.x -= this.vx; } } diff --git a/index.html b/index.html index 9a40904..18a18a6 100644 --- a/index.html +++ b/index.html @@ -11,12 +11,13 @@

Flappybird

+
- +