<!DOCTYPE html>
<html onmousemove='mmove(event);' onmousedown='createball();' onmouseup='launch();'>
<head>
 <meta charset='utf-8'>
 <title> Pong </title>
 <style>
 body { cursor: none; }
 #c { border: 1px solid black; }
 #frame { position: absolute; top: 20px; left: 20px; z-index: 10; }
 </style>
</head>
<body onload='start();'>
<span id='frame'>0</span>
<canvas id='c' width=1024 height=768></canvas>
<script>
var TOP = 0, BOTTOM = 1, LEFT = 2, RIGHT = 3;
var _fn = 0, _frame = document.getElementById("frame");
var _width = 1024, _height=768, _pwidth=80, _pheight=10;
var _ctx = document.getElementById("c").getContext("2d");
var _px = 0, _py = _height-30, _ball = null, _newball = false;
var _tiles = [];

// From: https://stackoverflow.com/questions/9043805/test-if-two-lines-intersect-javascript-function
// returns true iff the line from (a,b)->(c,d) intersects with (p,q)->(r,s)
function intersects(a,b,c,d,p,q,r,s) {
  var det, gamma, lambda;
  det = (c - a) * (s - q) - (r - p) * (d - b);
  if (det === 0) {
    return false;
  } else {
    lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
    gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
    return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1);
  }
};

function intersectx(x1, y1, x2, y2, x3, y3, x4, y4) {
  var a_dx = x2 - x1;
  var a_dy = y2 - y1;
  var b_dx = x4 - x3;
  var b_dy = y4 - y3;
  var s = (-a_dy * (x1 - x3) + a_dx * (y1 - y3)) / (-b_dx * a_dy + a_dx * b_dy);
  var t = (+b_dx * (y1 - y3) - b_dy * (x1 - x3)) / (-b_dx * a_dy + a_dx * b_dy);
  return (s >= 0 && s <= 1 && t >= 0 && t <= 1);
}
function framecounter() {
  _frame.innerHTML = _fn;
  _fn = 0;
}

function start() {
  for(var i = 0; i < _width / 80; i++) {
    _tiles.push(new Tile(i*80, 50));
    _tiles.push(new Tile(i*80, 70));
    _tiles.push(new Tile(i*80, 90));
    _tiles.push(new Tile(i*80, 110));
    _tiles.push(new Tile(i*80, 130));

    _tiles.push(new Tile(i*80, 300));
    _tiles.push(new Tile(i*80, 320));
    _tiles.push(new Tile(i*80, 340));
    _tiles.push(new Tile(i*80, 360));
  }
  setTimeout(update, 16);
  setInterval(framecounter, 1000);
}

function update() {
  _ctx.clearRect(0,0,_width, _height);
  _ctx.fillStyle = "red";
  _ctx.beginPath();
  _ctx.rect(_px, _py, _pwidth, _pheight);
  _ctx.closePath();
  _ctx.fill();
  if (_newball) {
    _ctx.fillStyle = "black";
    _ctx.beginPath();
    _ctx.arc(_px+(_pwidth/2)-5, _py-5, 5, 0, 360);
    _ctx.closePath();
    _ctx.fill();
  }
  if (_ball != null) {
    var ox=_ball.x, oy = _ball.y;
    _ball.update();
    if (_ball != null) {
      for(var i = 0; i < _tiles.length;) {
	if (_tiles[i].hit(_ball.x, _ball.y, _ball.r)) {
	  console.log("hit");
	  var side = _tiles[i].intersect(ox, oy, _ball.x, _ball.y);
	  _ball.bounce(side, _tiles[i].x,_tiles[i].y);
	  _tiles.splice(i, 1);
	  i = 0;
	}
	i++;
      }
      _ball.draw();
    }
  }
  for(var i = 0; i < _tiles.length; i++) {
    _tiles[i].draw();
  }
  _fn++;
  setTimeout(update, 16);
}

function mmove(e) {
  _px = Math.max(0, Math.min(e.clientX-(_pwidth/2), _width-_pwidth));
}

function createball() {
  if (_ball == null) _newball = true;
}

function R(a, b) {
  var range = Math.abs(a-b)+1;
  var min = Math.min(a,b);
  return Math.floor(Math.random() * range) + min;
}

// Define a new tile object:
function Tile(x,y) {
  this.x = x;
  this.xr = x + 80;
  this.y = y;
  this.yb = y + 20;
  this.color = "rgb(" + R(0,255) + "," + R(0,255) + "," + R(0,255) + ")";

  this.draw = function() {
    _ctx.fillStyle = this.color;
    _ctx.beginPath();
    _ctx.rect(this.x, this.y, 80, 20);
    _ctx.closePath();
    _ctx.fill();
  }
  // Check if the ball would be inside of the box (i.e. has hit it):
  this.hit = function(x, y, r) {
    if (x >= this.x && x <= this.xr)
      if (y >= this.y && y <= this.yb) return true;
    return false;
  }
  // Determine which side of the box the path of the ball intersects:
  this.intersect = function(ox, oy, x, y) {
    if (intersects(ox,oy,x,y, this.x, this.yb, this.xr, this.yb)) return BOTTOM;
    if (intersects(ox,oy,x,y, this.x, this.y, this.xr, this.y)) return TOP;
    if (Math.abs((x+9) - this.x) < Math.abs(x - (this.xr))) return LEFT;
    return RIGHT;
  }
}

function Ball(x, y) {
  this.x = x;
  this.y = y;
  this.r = 5;
  var speed = (Math.random()*8)+8;
  var angle = -((Math.random()*(Math.PI/2)) + Math.PI/4);
  this.dx = speed * Math.cos(angle);
  this.dy = speed * Math.sin(angle);

  this.draw = function() {
    _ctx.fillStyle = "black";
    _ctx.beginPath();
    _ctx.arc(this.x,this.y,this.r,0,360);
    _ctx.closePath();
    _ctx.fill();
  };
  // Determine how the ball would bounce off a tile:
  this.bounce = function(side, x, y) {
//  console.log("bounce %s", side == TOP? "TOP" : side == BOTTOM ? "BOTTOM" : side == LEFT? "LEFT" : "RIGHT");
    switch(side) {
      case TOP:
	this.y = (y - ((this.y+5)-y))-5;
	break;
      case BOTTOM:
	y+=20;
	this.y = y + (y-this.y);
	break;
      case LEFT:
	this.x = (x - ((this.x+10)-x))-10;
	break;
      case RIGHT:
	x += 80;
	this.x = x + (x-this.x);
	break;
    }
    if (side == TOP || side == BOTTOM) this.dy = -this.dy;
    else this.dx = -this.dx;
  };
  // Update the ball position:
  this.update = function() {
    var ox = this.x, oy = this.y;
    this.x += this.dx;
    if (this.x-this.r < 0) {
      this.x = this.r - (this.x-this.r);
      this.dx = -this.dx;
    }
    if (this.x+this.r > _width) {
      this.x = (_width-this.r) - ((this.x+this.r)-_width);
      this.dx = -this.dx;
    }
    this.y += this.dy;
    if (this.y < 0) {
      this.y = this.r - (this.y-this.r);
      this.dy = -this.dy;
    }
    if (intersectx(ox, oy, this.x, this.y+9, _px, _py, _px+_pwidth, _py)) {
//     if (this.y+10 >= _py && this.y < _py+10 && this.x+9 >= _px && this.x <= _px+_pwidth) {
      this.y = (_py-10) - (_py-(this.y+9));
      this.dy = -this.dy;
    }
    // If we drop off the bottom, delete the ball:
    if (this.y > _height) {
      _ball = null;
    }
  }
}

// Launch a ball on mouse-up if we've previously pressed down the mouse button:
function launch() {
  if (_ball == null && _newball == true) {
    _ball = new Ball(_px+(_pwidth/2)-10, _py-10);
    _newball = false;
  }
}
</script>
</body>
</html>