// -- MODEL -- 

function model()  
{ var s = ball.s;
  this.left   = {  pos: 0,   vel: 0 }; 
  this.right  = {  pos: 0,   vel: 0 }; 
  this.ball   = {    x: 0,     y: 0,    
                    vx: s,    vy: s }; 
  this.scores = { left: 0, right: 0 } 
}

var field    = { w: 320, h: 240 }; 
var paddle   = { x: 300, w:   2, h:  30, s: 76 }; 
var ball     = { w:   5, h:   5, s: 120 }; 
var timestep = 0.01;

model.prototype.moveLeft = function(d)
{ this.left.vel  = d * paddle.s };

model.prototype.moveRight = function(d)
{ this.right.vel = d * paddle.s };

model.prototype.update = function()
{ 
  var t = timestep;
  
  this.left.pos  += t * this.left.vel;
  this.right.pos += t * this.right.vel;
  this.ball.x    += t * this.ball.vx;
  this.ball.y    += t * this.ball.vy;
  
  var clip = function(pad)
  { var pos = pad.pos;
    pos = Math.min(pos,field.h - paddle.h);
    pos = Math.max(pos,paddle.h - field.h);
    if (pos != pad.pos) pad.vel = 0;
    pad.pos = pos;
  };
  
  clip(this.left);
  clip(this.right);
  
  if (this.ball.y + ball.h > field.h)
  { this.ball.vy = - ball.s }
  
  if (this.ball.y - ball.h < - field.h)
  { this.ball.vy = ball.s }
  
  if (this.ball.x - ball.h < - paddle.x + paddle.w && 
      this.ball.x + ball.h > - paddle.x - paddle.w &&
      this.ball.y + ball.h > this.left.pos - paddle.h &&
      this.ball.y - ball.h < this.left.pos + paddle.h )
  { this.ball.vx = ball.s }
  
  if (this.ball.x - ball.h < paddle.x + paddle.w && 
      this.ball.x + ball.h > paddle.x - paddle.w &&
      this.ball.y + ball.h > this.right.pos - paddle.h &&
      this.ball.y - ball.h < this.right.pos + paddle.h)
  { this.ball.vx = - ball.s } 
      
  if (this.ball.x - ball.h < -field.w) 
  { this.scores.right++;
    this.ball.x = 0;
    this.ball.vx = ball.s }
    
  if (this.ball.x + ball.h > field.w)
  { this.scores.left++;
    this.ball.x = 0;
    this.ball.vx = - ball.s }
    
};

// -- VIEW -- 

function view()
{ this.ball   = {   vx: null,    vy: null };
  this.scores = { left:    0, right:    0 };
  this.paddle = { left: null, right: null };
  this.avatar = { ball: $('div#ball'),
                  sc_l: $('div#score_left'),
                  sc_r: $('div#score_right'),
                  pa_l: $('div#paddle_left'),
                  pa_r: $('div#paddle_right'),
                  play: $('input#playfield') };
                  
  this.avatar.ball.width(1.9*ball.w)  .height(1.9*ball.h);                  
  this.avatar.pa_l.width(1.9*paddle.w).height(1.9*paddle.h);              
  this.avatar.pa_r.width(1.9*paddle.w).height(1.9*paddle.h);
  this.avatar.play.width(2*field.w)   .height(2*field.h);
}

view.prototype.render = function(model)
{ var move = function(avatar,pos,spd,size)
  { var x = pos.x - size.w + field.w;
    var y = pos.y - size.h + field.h;
    avatar.stop()
      .css({left:x,top:y})
      .animate({left:x+10*spd.vx, top:y+10*spd.vy},10000,"linear");
  };
  
  if (this.ball.vx != model.ball.vx || this.ball.vy != model.ball.vy)
  { this.ball.vx = model.ball.vx;
    this.ball.vy = model.ball.vy;
    move(this.avatar.ball,model.ball,this.ball,ball) }
  
  if (this.paddle.left != model.left.vel)
  { this.paddle.left = model.left.vel;
    move(this.avatar.pa_l,
         {x: -paddle.x, y:model.left.pos},
         {vx: 0, vy: this.paddle.left}, paddle) }
    
  if (this.paddle.right != model.right.vel)
  { this.paddle.right = model.right.vel;
    move(this.avatar.pa_r,
         {x: paddle.x, y: model.right.pos},
         {vx: 0, vy: this.paddle.right}, paddle) }
  
  if (this.scores.left != model.scores.left)
  { this.scores.left = model.scores.left; 
    this.avatar.sc_l.html(model.scores.left) }
  
  if (this.scores.right != model.scores.right)
  { this.scores.right = model.scores.right;
    this.avatar.sc_r.html(model.scores.right) }
}

// -- CONTROLLER --

function controller()
{ var gameModel = new model();
  var gameView  = new view();  
  var keys  = { up: false, down: false };
  
  gameView.avatar.play.keydown(function(event){
    if (event.which == 38) keys.up = true;
    if (event.which == 40) keys.down = true;
    return false
  });
  
  gameView.avatar.play.keyup(function(event){
    if (event.which == 38) keys.up = false;
    if (event.which == 40) keys.down = false;
    return false
  });  
  
  var playerMoveLeft = function(model)
  { model.moveLeft((keys.up ? -1 : 0) + (keys.down ? 1 : 0)); }
  
  var aiMoveRight = function(model)
  { var dir = 0;
    if (model.ball.vx > 0) 
    { var dist = (paddle.x - model.ball.x);    
      var target = model.ball.y + model.ball.vy * (dist / model.ball.vx);
      while (target > field.h-ball.h || target < ball.h-field.h) 
      { if (target > 0) { target = 2*(field.h-ball.h) - target }
        else            { target = 2*(ball.h-field.h) - target }
      }           
          
      dir =  
        (model.right.pos + paddle.h < target ?  1 : 0) +
        (model.right.pos - paddle.h > target ? -1 : 0) 
        
      var accuracy = 
        field.h * Math.exp((model.scores.left - model.scores.right)/3);
        
      if (dist > accuracy) 
      { dir = -dir }
    }
  
    model.moveRight(dir);
  }
  
  var time = function() { return +(new Date) / 1000 };
  var last = time();
  
  var act  = function()
  { 
    playerMoveLeft(gameModel);      
    aiMoveRight(gameModel);
    
    var now = time();
    while (now > last) 
    { gameModel.update(now - last);
      last += timestep }
      
    gameView.render(gameModel);
    setTimeout(act,50);
  }
  
  act();
}

$(function(){new controller});

    
