<canvas id="flappyBlock"></canvas>
body {
  background-color: #000;
  overflow: hidden;
}
View Compiled
/*

FLAPPY BLOCK

Press s to start the game.

Space: Flap wings

*/

var c = document.getElementById("flappyBlock");
var ctx = c.getContext("2d");

var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;

c.width = WIDTH;
c.height = HEIGHT;

function JVector(x, y) {
  this.x = x;
  this.y = y;
}

JVector.prototype.add = function(vector) {
  this.x += vector.x;
  this.y += vector.y;
}

JVector.prototype.mult = function(scalar) {
  this.x *= scalar;
  this.y *= scalar;
}

JVector.prototype.div = function(scalar) {
  this.x /= scalar;
  this.y /= scalar;
}

JVector.prototype.mag = function() {
  return Math.sqrt((this.x * this.x) + (this.y * this.y));
}

JVector.prototype.normalize = function() {
  var m = this.mag();
  this.div(m);
}

function Bird(posVec, velVec, accVec, size, maxSpeed, flapStrength) {
  this.pos = posVec;
  this.vel = velVec;
  this.acc = accVec;

  this.size = size;

  this.maxSpeed = maxSpeed;

  this.flapStrength = flapStrength;

  this.flapping = false;
}

Bird.prototype.flap = function() {
  this.vel = new JVector(this.vel.x, -this.flapStrength);
}

Bird.prototype.reset = function() {
  this.pos = new JVector(0, HEIGHT / 2);
  this.vel = new JVector(0, 0);
}

Bird.prototype.update = function() {
  this.vel.add(this.acc);
  this.pos.add(this.vel);
  if (this.vel.mag() >= this.maxSpeed) {
    this.vel.normalize();
    this.vel.mult(this.maxSpeed);
  }
}

Bird.prototype.display = function() {
  ctx.fillStyle = "#fff";
  ctx.fillRect(this.pos.x - this.size, this.pos.y - this.size, this.size, this.size);
  if (!this.flapping) {
    ctx.beginPath();
    ctx.moveTo(this.pos.x - this.size, this.pos.y - this.size);
    ctx.lineTo(this.pos.x - this.size / 2, this.pos.y - this.size - this.size);
    ctx.lineTo(this.pos.x, this.pos.y - this.size);
    ctx.closePath;
  }
  if (this.flapping) {
    ctx.beginPath();
    ctx.moveTo(this.pos.x - this.size, this.pos.y);
    ctx.lineTo(this.pos.x - this.size / 2, this.pos.y + this.size);
    ctx.lineTo(this.pos.x, this.pos.y);
    ctx.closePath;
  }
  ctx.strokeStyle = "#fff";
  ctx.stroke();
}

Bird.prototype.checkWorld = function() {
  var x = this.pos.x;
  var y = this.pos.y;
  if (x <= 0) {
    this.pos.x = WIDTH;
  } else if (x > WIDTH) {
    this.pos.x = 0;
  }
  if (y <= 0) {
    this.pos.y = 0 + this.size;
  } else if (y > HEIGHT) {
    this.pos.y = HEIGHT;
  }
}

Bird.prototype.frame = function() {
  this.update();
  this.checkWorld();
  this.display();
}

function Obstacle(x) {
  this.x = x;
  this.height = (Math.random() * 2 - 1) * (HEIGHT / 2);
  this.width = Math.random() * 50 + 20;
}

Obstacle.prototype.display = function() {
  ctx.fillStyle = "#fff";
  if (this.height >= 0) {
    ctx.fillRect(this.x - this.width / 2, 0, this.width, this.height);
  } else if (this.height < 0) {
    ctx.fillRect(this.x - this.width / 2, HEIGHT + this.height, this.width, HEIGHT);
  }
  // console.log(this.x + ", " + this.height + ", " + this.width);
}

Obstacle.prototype.detectCollision = function(player) {
  var playerX = player.pos.x;
  var playerY = player.pos.y;

  if (this.height >= 0) {
    if (playerX >= this.x - this.width / 2 && playerX <= this.x + this.width && playerY <= this.height) {
      console.log("Player Hit!");
      return true;
    }
  } else if (this.height < 0) {
    if (playerX >= this.x - this.width / 2 && playerX <= this.x + this.width && playerY >= HEIGHT + this.height) {
      console.log("Player Hit!");
      return true;
    }
  }
  return false;
}

function ObstacleManager(maxNum) {
  this.maxNum = maxNum;
  this.obstacles = [];

  this.generateObstacles = function() {
    this.obstacles = [];
    for (var i = 0; i < this.maxNum; i++) {
      var xPos = ((i * WIDTH / this.maxNum) + Math.floor(Math.random() * 50)) + WIDTH / 4;
      this.obstacles.push(new Obstacle(xPos));
    }
  }

  this.renderObstacles = function() {
    for (var i = 0; i < this.obstacles.length; i++) {
      this.obstacles[i].display();
    }
  }

  this.detectCollisions = function(player) {
    for (var i = 0; i < this.obstacles.length; i++) {
      var collision = this.obstacles[i].detectCollision(player);
      if (collision) {
        return true;
      }
    }
  }
}

function Score() {
  this.score = 0;

  this.increment = function() {
    this.score += 100;
    console.log(this.score);
  }
  
  this.reset = function() {
    this.score = 0;
  }

  this.display = function() {
    ctx.fillStyle = "#fff";
    ctx.font = "20px Oswald";
    ctx.textAlign = "left";
    ctx.fillText("Score: " + this.score, 15, 35);
  }
}

var bg = "#000";

var Game = function() {
  this.startScreen = true;
  this.playing = false;
  this.gameOver = false;

  this.locVec = new JVector(0, HEIGHT / 2);
  this.velVec = new JVector(0, 0);
  this.accVec = new JVector(0.1, 0.4);

  this.score = new Score();

  this.player = new Bird(this.locVec, this.velVec, this.accVec, 20, 12, 10);

  this.obstacleManager = new ObstacleManager(5);

  this.gravity = new JVector(0, .1);

  this.startGame = function() {
    this.startScreen = false;
    this.playing = true;
    this.gameOver = false;
    this.obstacleManager.generateObstacles();
    this.player.reset();
  }

  this.killPlayer = function() {
    this.startScreen = false;
    this.playing = false;
    this.gameOver = true;
  }

  this.draw = function() {
    ctx.fillStyle = bg;
    ctx.fillRect(0, 0, WIDTH, HEIGHT);

    if (this.startScreen) {
      ctx.strokeStyle = "#fff";
      ctx.fillStyle = "#fff";
      ctx.font = "48px Oswald";
      ctx.textAlign = "center";
      ctx.strokeText("Flappy Block", WIDTH / 2, HEIGHT / 2);
      ctx.font = "18px Oswald";
      ctx.fillText("Hit 'S' to start the game!", WIDTH / 2, HEIGHT / 2 + 40);
      this.score.reset();
    }

    if (this.playing) {
      this.score.display();
      this.player.vel.add(this.gravity);
      this.obstacleManager.renderObstacles();
      var collision = this.obstacleManager.detectCollisions(this.player);
      if (collision) {
        this.killPlayer();
      }
      this.player.frame();
      if (this.player.pos.x == 0) {
        this.obstacleManager.generateObstacles();
        this.score.increment();
      }
    }

    if (this.gameOver) {
      ctx.strokeStyle = "#fff";
      ctx.fillStyle = "#fff";
      ctx.font = "48px Oswald";
      ctx.textAlign = "center";
      ctx.strokeText("Gameover!", WIDTH / 2, HEIGHT / 2);
      ctx.font= "24px Oswald";
      ctx.fillText("Final Score: " + this.score.score, WIDTH / 2, HEIGHT / 2 + 40);
      ctx.font = "18px Oswald";
      ctx.fillText("Hit 'S' to play again!", WIDTH / 2, HEIGHT / 2 + 80);
    }

    requestAnimationFrame(this.draw.bind(this));
  }
}

var flappyBlock = new Game();
flappyBlock.draw();

window.addEventListener("resize", function() {
  WIDTH = window.innerWidth;
  HEIGHT = window.innerHeight;

  c.width = WIDTH;
  c.height = HEIGHT;
});

document.onkeydown = keyDown;
document.onkeyup = keyUp;

function keyDown(e) {
  var event = e || window.event;
  var key = event.keyCode;
  console.log(key);

  if (key == 83 && !flappyBlock.playing) {
    flappyBlock.startGame();
    flappyBlock.score.reset();
  }

  if (key == 32 && !flappyBlock.player.flapping && flappyBlock.playing) {
    flappyBlock.player.flap();
    flappyBlock.player.flapping = true;
  }
}

function keyUp(e) {
  var event = e || window.event;
  var key = event.keyCode;
  console.log(key);

  if (key == 32 && flappyBlock.player.flapping) {
    flappyBlock.player.flapping = false;
  }
}

function lerp(current, target, percent) {
  return (target - current) * percent;
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.