/* Elliott Kember's jSnake, version 3 */
// Usage: new Snake()
// Adding options: new Snake({ width: 30, mouse_chasing: true })
// TODO: Add list of options

// Compass bearings for directions
UP = 0;
RIGHT = 90;
DOWN = 180;
LEFT = 270;

/* Fruit object */
// We don't need all these variables. I'll take them out sooner or later.
Fruit = function(options) {
  this.init(options)
}
$.extend(Fruit.prototype, {
  stadium: false,
  position_x: 0,
  position_y: 0,
  element: 0,
  width: 0,
  left: 0,
  right: 0,
  
  // Create.
  init: function(options){
    
    this.options = $.extend({
      width: 20
    }, options)
    
    this.stadium = $('#snake-stadium');
    this.element = $('#fruit');
    
    if(this.element.length == 0){
      this.element = $('<'+'div id="fruit"><'+'/div>');  
    }
    if(this.stadium.length == 0){
      this.stadium = $('<'+'div id="snake-stadium"><'+'/div>')
      this.stadium.appendTo($('body'));
    }
    
    if(this.options['mouse_chasing']){
      this.element.css('display', 'none')
      $().mousemove(function(e){
        self.position_x = e.pageX
        self.position_y = e.pageY
        self.element.css({'top' : self.position_y, 'left' : self.position_x}); 
      });
    }
    
    this.element.css('position', 'fixed !important').css('background', '#00FF00')
    this.element.css('width', this.options['width']).css('height', this.options['width'])
    
    this.element.appendTo(this.stadium);
    this.place();
    var self = this;

    $(window).bind('resize', function(){    
      // On resize, move it move it. 
      // This could be refactored into only-moving when outside the window.
      self.place();
    });


  },
  
  remove: function(){
    this.element.remove();
  },
  
  place: function(){
    // Come up with a random number
    this.position_x = Math.floor(Math.random() * $(window).width() - this.options['width']);
    // Take off remainder
    this.position_x -= (this.position_x % this.options['width']);

    // Come up with a random number
    this.position_y = Math.floor(Math.random() * $(window).height()) - this.options['width'];
    // Again, take off remainder
    this.position_y -= (this.position_y % this.options['width']);

    // Don't let it place off the page!  
    if(
      this.position_y < 0 
      || this.position_x < 0 
      || this.position_x > ($(window).width() - this.options['width']) 
      || this.position_y > ($(window).width() - this.options['width'])
    ){
      this.place();
    }else{
      this.element.css({'top' : this.position_y, 'left' : this.position_x}); 
    }
  }
});

/* A snake */

Snake = function(options) {
  this.init(options);
}

$.extend(Snake.prototype, {
  head: false,
  tail: [],
  width: 20,
  position_x: 0,
  position_y: 0,
  screen_width: false,
  screen_height: false,
  timeout: 100,
  direction: DOWN,
  snake_name: 'snake',
  self: this,
  automatic: 0,
  score: 0,
  
  // Let's get this party started!
  init: function(options){
        
    this.options = $.extend({
      showScores : false,
      keys       : 'arrows',
      starting_x : 'random',
      starting_y : 'random',
      snake_name : 'one',
      timeout    : 100,
      name       : 'Elliott',
      colour     : 'random',
      skynet_only: false,
      human_only : false,
      width      : 20,
      mouse_chasing: false
    }, options);
    
    this.width = this.options['width'];

    // If it has no fruit, create one!
    if(typeof(fruit) == 'undefined' || fruit == false){
      this.fruit_object = $('<'+'div id="fruit"><'+'/div>');
      this.fruit_object.appendTo($("body"));
      this.fruit = new Fruit({ height:this.height, width:this.width, mouse_chasing: this.options['mouse_chasing']});
      fruit = this.fruit; 
    }else{
      this.fruit = fruit;
    }
    
    if(this.width != this.fruit.options['width']){
      this.width = this.fruit.options['width']
    }
    
    this.randomizePlacement();
    this.randomizeDirection();
    
    
    this.tail = [];
    
    this.setKeys();
    
    this.position_x = this.options['starting_x'];
    this.position_y = this.options['starting_y'];
    this.snake_name = this.options['name'];
    this.timeout    = this.options['timeout'];
    
    this.doSizes();
    this.doHTML();
    this.doColours();
    this.move();
    
    var self = this;
    
    // Default tt mode timeout: 2 seconds
    this.skynetModeTimeout = this.options['skynetModeTimeout'] ? this.options['skynetModeTimeout'] : 2000
        
    // Enable Skynet mode after 2 seconds.
    if(!this.options['human_only']){
      setTimeout(function(){if(!self.automatic){
        if(self.automatic == 0)
          self.automatic = true;
      }}, this.skynetModeTimeout)
    }
    
    // This is what the doSizes is for. 
    $(window).bind('resize', function(){
      self.doSizes();
    })
    
    // If they have human control, add human control.
    if(!this.options['skynet_only']){
      $(document).keydown(function(e){
        var code = (e.keyCode ? e.keyCode : e.which);
        self.getDirectionFrom(code);
      });
    }else{
      this.automatic = true;
    }
  },
  
  // Append a tail.
  addTail: function(){
    var h = this.head.clone().text('');
    h.attr('id', this.position_x+'x'+this.position_y);
    h.addClass('block tail '+this.snake_name);
    h.css('left', this.position_x).css('top', this.position_y);
    h.css('background-color', this.options['tail_colour'])
    // This has to be Before instead of After. Don't ask me why, because I can't remember.
    h.insertBefore($(this.head));
    this.tail.unshift(h);
  },
  
  // Have they lost!?!?!?!
  hasLost: function(){
    var element = '#'+this.position_x+'x'+this.position_y
    if ($(element).length > 0){
      return true;
    }else{
      return false;
    }
  },
  
  // Functions for creating HTML and colours
  doHTML: function(){
    name = this.position_x + 'x' + this.position_y
    this.head = $('<'+'div id="'+name+'" class="head"><'+'/div>');
    this.head.css('width', this.width).css('height', this.width)
    this.head.css('left', this.position_y).css('top', this.position_x);
    this.head.attr('id', this.position_x+'x'+this.position_y);
    
    this.stadium = $('#snake-stadium');
    if(this.stadium.length == 0){
      this.stadium = $('<'+'div id="snake-stadium"><'+'/div>');
      this.stadium.appendTo($("body"))
    }
    
    this.head.appendTo(this.stadium);
  },
  
  doColours: function(){
    this.head.css('width', this.width);   // Probably shouldnt' be hard coded. TODO.
    this.head.css('height', this.width);  // See above and TODO.
    
    if (this.options['colour'] == 'random'){
      n = Math.floor(Math.random()*16777215)
      this.options['head_colour'] = '#'+n.toString(16)
      this.options['tail_colour'] = '#'+colorscale(this.options['head_colour'], 6)
    }else{
      this.options['head_colour'] = this.options['colour'];
      if(!this.options['tail_colour']){
        this.options['tail_colour'] = '#'+colorscale(this.options['colour'], 6)
      }
    }
    this.head.css('background', this.options['head_colour']);
  },
  
  // Set the key codes to keys.
  setKeys: function(){
    switch(this.options['keys']){
      case 'arrows':
        this.up_key = 38
        this.down_key = 40
        this.left_key = 37
        this.right_key = 39
        break;
      case 'wasd':
        this.up_key = 87
        this.down_key = 83
        this.left_key = 65
        this.right_key = 68
        break;
      case 'keypad':
        this.up_key = 104
        this.down_key = 98
        this.left_key = 100
        this.right_key = 102
        break;
      case 'ijkl':
        this.up_key = 73
        this.down_key = 75
        this.left_key = 74
        this.right_key = 76
        break;
      default:
        this.up_key = this.options['up_key'];
        this.down_key = this.options['down_key'];
        this.left_key = this.options['left_key'];
        this.right_key = this.options['right_key'];
        break;
    }
  },
  
  // Convert a keycode to a direction
  getDirectionFrom: function(input){
    switch(input){
      case this.up_key:
        if (this.direction != DOWN)   {this.automatic = false; this.direction = UP;    }  break;
      case this.right_key:                                                                      
        if (this.direction != LEFT)   {this.automatic = false; this.direction = RIGHT; }  break;
      case this.down_key:                                                                       
        if (this.direction != UP)     {this.automatic = false; this.direction = DOWN;  }  break;
      case this.left_key:                                                                       
        if (this.direction != RIGHT)  {this.automatic = false; this.direction = LEFT;  }  break;
    }
  },
  
  // Clear functions. Pathfinding.
  clearLeft: function(){
    left = $('#'+(this.position_x - this.width)+"x"+this.position_y).length == 0
    return left
  },
  clearRight: function(){
    right = $('#'+(this.position_x + this.width)+"x"+this.position_y).length == 0
    return right;
  },
  clearUp: function(){
    return $('#'+(this.position_x)+"x"+(this.position_y - this.width)).length == 0
  },
  clearDown: function(){
    return $('#'+(this.position_x)+"x"+(this.position_y + this.width)).length == 0
  },
  
  // Move
  move: function(){
    
    // Skynet
    if (this.automatic != false){
      this.doSkynet();
    }
    
    switch(this.direction){
      case UP:    this.position_y -= this.width; break;
      case RIGHT: this.position_x += this.width; break;
      case DOWN:  this.position_y += this.width; break;
      case LEFT:  this.position_x -= this.width; break;
    }

    this.fixOverlaps();

    if (this.hasLost()){
      this.reset();
    }
        
    // Do the actual moving
    this.head.css('position', 'fixed');
    this.head.css('left', this.position_x);
    this.head.css('top', this.position_y);
    this.head.attr('id', this.position_x+'x'+this.position_y);
    
    // Remove the last tail, unless they've eaten a fruit.
    if (this.eatingFruit()){
      this.fruit.place();
      if(!this.automatic){
        this.timeout -= 2;
      }
    }else{
      if (this.tail.length > 1){
        this.tail.pop().remove();
      }
    }
    
    // Set up the next loop too
    var self = this;
    this.moving = setTimeout(function(){self.move();},this.timeout);
    
    this.addTail();
  },
  
  // Random placement.
  randomizePlacement: function(force){
    if(this.options['starting_x'] == 'random' || force){
      r = Math.floor(Math.random()* $(window).width());
      r -= r % this.width;
      this.options['starting_x'] = r; //this.width;
      this.position_x = r;
    }
    if(this.options['starting_y'] == 'random' || force){
      r = Math.floor(Math.random()* $(window).height());
      r -= r % this.width;
      this.options['starting_y'] = r; //this.width;
      this.position_y = r;
    }
  },

  // Randomise direction
  randomizeDirection: function(){
    seed_direction = Math.random();
    if(seed_direction < 0.25){
      this.direction = UP;
    }else if(seed_direction < 0.5){
      this.direction = DOWN;
    }else if(seed_direction < 0.75){
      this.direction = LEFT;
    }else{
      this.direction = RIGHT;
    }
  },
  
  // Automatic-move
  doSkynet: function(){
    if (this.fruit.position_x < this.position_x && this.clearLeft() ){ this.direction = LEFT}
    else if (this.fruit.position_x >  this.position_x && this.clearRight() ){ 
      this.direction = RIGHT
    }else if (this.fruit.position_y >  this.position_y && this.clearDown() ){ 
      this.direction = DOWN
    }else if (this.fruit.position_x == this.position_x && this.fruit.position_y <= this.position_y && this.clearUp() ){ 
      this.direction = UP
    }else if (this.fruit.position_x == this.position_x && this.fruit.position_x >=  this.position_y && this.clearDown() ){
      this.direction = DOWN
    }
  
  
    // This code does diagonals. It's pretty creepy, so it's commented out.
    if(Math.random() > 0.5){
      if(this.fruit.position_y > this.position_y && this.direction == LEFT && this.clearDown()){
        this.direction = DOWN;
      }else if(this.fruit.position_y > this.position_y && this.direction == RIGHT && this.clearDown()){
        this.direction = DOWN;
      }
      if(this.fruit.position_y < this.position_y && this.direction == LEFT && this.clearDown()){
        this.direction = UP;
      }else if(this.fruit.position_y < this.position_y && this.direction == RIGHT && this.clearDown()){
        this.direction = UP;
      }
    }
  
    if(this.direction == UP && !this.clearUp()){
      this.direction = LEFT;
    }
    if (this.direction == LEFT && !this.clearLeft()){
      this.direction = DOWN;
    }
    if (this.direction == DOWN && !this.clearDown()){
      this.direction = RIGHT;
    }
    if (this.direction == RIGHT && !this.clearRight()){
      this.direction = UP;
    }
  },
  
  // Fixing modulus-related overlaps
  fixOverlaps: function(){
    // Overlap
    if (this.position_y < 0){                               // Off the top
      this.position_y =  (this.screen_height - this.width); 
      this.position_y -= (this.screen_height % this.width); // remainder if there is one
    }
    if (this.position_x < 0){                               // Off the left
      this.position_x =  (this.screen_width - this.width);
      this.position_x -= (this.screen_width % this.width);  // remainder if there is one
    }
    if (this.position_x > (this.screen_width - this.width)){  // Off the right
      this.position_x = 0;
    }
    if (this.position_y >= (this.screen_height - this.width)){ // Off the bottom
      this.position_y = 0;
    }

    if (this.position_x % this.width != 0){
      this.position_x -= this.screen_width % this.width
    }
  },
  
  // Start over, you lost!
  reset: function(){
    // A collision! Set the timeout to 1000 before their next move.
    this.timeout = 1000;
    
    this.randomizeDirection();
    
    var self = this;
    // Reset everything after 1 second
    setTimeout(function(){
      $.each(self.tail, function(i, t){t.remove()})
      self.randomizePlacement(true);
      self.tail = [];
      self.timeout = self.options['timeout'];
    }, 1000);
  },
  
  // Screen sizes
  doSizes: function(){
    this.screen_width   =   $(window).width();
    this.screen_height  =   $(window).height();
    this.screen_width   -=  (this.screen_width % this.width);
    this.screen_height  -=  (this.screen_width % this.width);
  },
  
  // Is there a fruit where the head is?
  eatingFruit: function(){
    var fruit_x = this.fruit.position_x;
    var fruit_y = this.fruit.position_y;
    
    var fruit_x1 = fruit_x + this.fruit.width;
    var fruit_y1 = fruit_y + this.fruit.width;
    
    if (fruit_x == 0){
      return true;
    }
    if (this.position_x == fruit_x && this.position_y == fruit_y) {
      this.score += 1;
      if(this.showScores){
        this.head.text(this.score+11);
      }
      return true;
    }
    
    
    
  },
  
  stop: function(){
    $.each(this.tail, function(i, t){
      t.remove();
    });
    this.head.remove();
    if(this.fruit){
      this.fruit.remove();
    }
    
    clearTimeout(this.moving);
    
    return true;
  }
  
});





/* This script and many more are available free online at
The JavaScript Source!! http://javascript.internet.com
Created by: Joseph Myers | http://www.codelib.net/ */

function colorscale(hexstr, scalefactor) {
/* declared variables first, in order;
  afterwards, undeclared local variables */
  var r = scalefactor;
  var a, i;
  if (r < 0 || typeof(hexstr) != 'string')
    return hexstr;
    hexstr = hexstr.replace(/[^0-9a-f]+/ig, '');
    if (hexstr.length == 3) {
    a = hexstr.split('');
  } else if (hexstr.length == 6) {
    a = hexstr.match(/(\w{2})/g);
  } else
    return hexstr;
  for (i=0; i < a.length; i++) {
    if (a[i].length == 2)
      a[i] = parseInt(a[i], 16);
    else {
      a[i] = parseInt(a[i], 16);
      a[i] = a[i]*16 + a[i];
  }
}

var maxColor = parseInt('ff', 16);

function relsize(a) {
  if (a == maxColor)
  return Infinity;
  return a/(maxColor-a);
}

function relsizeinv(y) {
  if (y == Infinity)
  return maxColor;
  return maxColor*y/(1+y);
}

for (i=0; i < a.length; i++) {
  a[i] = relsizeinv(relsize(a[i])*r);
  a[i] = Math.floor(a[i]).toString(16);
  if (a[i].length == 1)
  a[i] = '0' + a[i];
}
return a.join('');
}



