Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

HTML

            
              <div class="score">0</div>
<div class="stage"></div>
            
          
!

CSS

            
              /*================================================

General

================================================*/

* {
  box-sizing: border-box;
}

html,
body {
  background-color: #000;
  height: 100%;
}

body {
  background: #222;
  background: radial-gradient(#333, #111);
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  color: #fff;
  font: 100%/1.5 sans-serif;
  overflow: hidden;
}

/*================================================

Score

================================================*/

.score {
  color: rgba(255, 255, 255, 0.5);
  font-size: 16px;
  font-weight: bold;
  padding-top: 5px;
  text-align: center;
}

/*================================================

Stage

================================================*/

.stage {
  bottom: 0;
  left: 0;
  margin: auto;
  position: absolute;
  right: 0;
  top: 0;
  z-index: 2;
}

/*================================================

Tiles

================================================*/

.tile {
  background: rgba(0, 0, 0, 0.15);
  position: absolute;
  transition-property:
    background,
    box-shadow,
    opacity,
    transform
  ;
  transform: translateZ(0);
  transition-duration: 3000ms;
}

.tile:before {
  bottom: 0;
  content: '';
  height: 0;
  left: 0;
  margin: auto;
  opacity: 0;
  position: absolute;
  right: 0;
  top: 0;
  width: 0;
  transition: opacity 300ms;
}

.tile.path:before {
  opacity: 1;
}

.tile.up:before {
  border-bottom: 4px inset rgba(255, 255, 255, 0.15);
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
}

.tile.down:before {
  border-top: 4px inset rgba(255, 255, 255, 0.15);
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
}

.tile.left:before { 
  border-right: 4px inset rgba(255, 255, 255, 0.15);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
}

.tile.right:before { 
  border-left: 4px inset rgba(255, 255, 255, 0.15);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
}

@media (max-width: 900px), (max-height: 900px) {
  .tile.up:before,
  .tile.down:before,
  .tile.left:before,
  .tile.right:before {
    border-width: 3px;
  }
}

@media (max-width: 500px), (max-height: 500px) {
  .tile.up:before,
  .tile.down:before,
  .tile.left:before,
  .tile.right:before {
    border-width: 2px;
  }
}

.tile.pressed {
  background: rgba(0, 0, 0, 0.3);
  box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.6);
  transition-duration: 0ms;
}
            
          
!

JS

            
              /*================================================

Polyfill

================================================*/

(function(){ 'use strict';

  /*================================================

  Request Animation Frame

  ================================================*/
  
  var lastTime = 0;
  var vendors = [ 'webkit', 'moz' ];
  for( var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x ) {
    window.requestAnimationFrame = window[ vendors[ x ] + 'RequestAnimationFrame' ];
    window.cancelAnimationFrame = window[ vendors[ x ] + 'CancelAnimationFrame' ] || window[ vendors[ x ] + 'CancelRequestAnimationFrame' ];
  }

  if( !window.requestAnimationFrame ) {
    window.requestAnimationFrame = function( callback, element ) {
      var currTime = new Date().getTime();
      var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) );
      var id = window.setTimeout(
        function() { 
          callback( currTime + timeToCall ); 
        }, timeToCall );
      lastTime = currTime + timeToCall;
      return id;
    }
  }

  if( !window.cancelAnimationFrame ) {
    window.cancelAnimationFrame = function( id ) {
      clearTimeout( id );
    }
  }

})();

/*================================================

DOM Manipulation

================================================*/

(function(){ 'use strict';

  function hasClass( elem, className ) {
    return new RegExp( ' ' + className + ' ' ).test( ' ' + elem.className + ' ' );
  };

  function addClass( elem, className ) {
    if( !hasClass(elem, className ) ) {
      elem.className += ' ' + className;
    }
  };

  function removeClass( elem, className ) {
    var newClass = ' ' + elem.className.replace( /[\t\r\n]/g, ' ' ) + ' ';
    if( hasClass( elem, className ) ) {
      while( newClass.indexOf(' ' + className + ' ' ) >= 0 ) {
        newClass = newClass.replace( ' ' + className + ' ', ' ' );
      }
      elem.className = newClass.replace( /^\s+|\s+$/g, '' );
    }
  };

  function toggleClass( elem, className ) {
    var newClass = ' ' + elem.className.replace( /[\t\r\n]/g, ' ' ) + ' ';
    if( hasClass(elem, className ) ) {
      while( newClass.indexOf( ' ' + className + ' ' ) >= 0 ) {
        newClass = newClass.replace( ' ' + className + ' ' , ' ' );
      }
      elem.className = newClass.replace( /^\s+|\s+$/g, '' );
    } else {
      elem.className += ' ' + className;
    }
  };

})();

/*================================================

Core

================================================*/

g = {};

(function(){ 'use strict';

  /*================================================

  Math

  ================================================*/

  g.m = Math;
  g.mathProps = 'E LN10 LN2 LOG2E LOG10E PI SQRT1_2 SQRT2 abs acos asin atan ceil cos exp floor log round sin sqrt tan atan2 pow max min'.split( ' ' );
  for ( var i = 0; i < g.mathProps.length; i++ ) {
    g[ g.mathProps[ i ] ] = g.m[ g.mathProps[ i ] ];
  }
  g.m.TWO_PI = g.m.PI * 2;

  /*================================================

  Miscellaneous

  ================================================*/

  g.isset = function( prop ) {
    return typeof prop != 'undefined';
  };

  g.log = function() {
    if( g.isset( g.config ) && g.config.debug && window.console ){
      console.log( Array.prototype.slice.call( arguments ) );
    }
  };

})();

/*================================================

Group

================================================*/

(function(){ 'use strict';

  g.Group = function() {
    this.collection = [];
    this.length = 0;
  };

  g.Group.prototype.add = function( item ) {
    this.collection.push( item );
    this.length++;
  };

  g.Group.prototype.remove = function( index ) {
    if( index < this.length ) {
      this.collection.splice( index, 1 );
      this.length--;
    }
  };

  g.Group.prototype.empty = function() {
    this.collection.length = 0;
    this.length = 0;
  };

  g.Group.prototype.each = function( action, asc ) {
    var asc = asc || 0,
      i;
    if( asc ) {
      for( i = 0; i < this.length; i++ ) {
        this.collection[ i ][ action ]( i );
      }
    } else {
      i = this.length;
      while( i-- ) {
        this.collection[ i ][ action ]( i );
      }
    }
  };

})();

/*================================================

Utilities

================================================*/

(function(){ 'use strict';

  g.util = {};

  /*================================================

  Random

  ================================================*/
  
  g.util.rand = function( min, max ) {
    return g.m.random() * ( max - min ) + min;
  };

  g.util.randInt = function( min, max ) {
    return g.m.floor( g.m.random() * ( max - min + 1) ) + min;
  };

}());

/*================================================

State

================================================*/

(function(){ 'use strict';

  g.states = {};

  g.addState = function( state ) {
    g.states[ state.name ] = state;
  };

  g.setState = function( name ) {
    if( g.state ) {
      g.states[ g.state ].exit();
    }
    g.state = name;
    g.states[ g.state ].init();
  };

  g.currentState = function() {
    return g.states[ g.state ];
  };

}());

/*================================================

Time

================================================*/

(function(){ 'use strict';

  g.Time = function() {
    this.reset();
  }

  g.Time.prototype.reset = function() {
    this.now = Date.now();
    this.last = Date.now();
    this.delta = 60;
    this.ndelta = 1;
    this.elapsed = 0;
    this.nelapsed = 0;
    this.tick = 0;
  };

  g.Time.prototype.update = function() {
    this.now = Date.now();
    this.delta = this.now - this.last;
    this.ndelta = Math.min( Math.max( this.delta / ( 1000 / 60 ), 0.0001 ), 10 );
    this.elapsed += this.delta;
    this.nelapsed += this.ndelta;
    this.last = this.now;
    this.tick++;
  };

})();

/*================================================

Grid Entity

================================================*/

(function(){ 'use strict';

  g.Grid = function( cols, rows ) {
    this.cols = cols;
    this.rows = rows;
    this.tiles = [];
    for( var x = 0; x < cols; x++ ) {
      this.tiles[ x ] = [];
      for( var y = 0; y < rows; y++ ) {
        this.tiles[ x ].push( 'empty' );
      }
    }
  };

  g.Grid.prototype.get = function( x, y ) {
    return this.tiles[ x ][ y ];
  };

  g.Grid.prototype.set = function( x, y, val ) {
    this.tiles[ x ][ y ] = val;
  };

})();

/*================================================

Board Tile Entity

================================================*/

(function(){ 'use strict';

  g.BoardTile = function( opt ) {
    this.parentState = opt.parentState;
    this.parentGroup = opt.parentGroup;
    this.col = opt.col;
    this.row = opt.row;
    this.x = opt.x;
    this.y = opt.y;
    this.z = 0;
    this.w = opt.w;
    this.h = opt.h;
    this.elem = document.createElement( 'div' );
    this.elem.style.position = 'absolute';
    this.elem.className = 'tile';
    this.parentState.stageElem.appendChild( this.elem );
    this.classes = {
      pressed: 0,
      path: 0,
      up: 0,
      down: 0,
      left: 0,
      right: 0
    }
    this.updateDimensions();
  };

  g.BoardTile.prototype.update = function() {
    for( var k in this.classes ) {
      if( this.classes[ k ] ) {
        this.classes[ k ]--;
      }
    }

    if( this.parentState.food.tile.col == this.col || this.parentState.food.tile.row == this.row ) {
      this.classes.path = 1;
      if( this.col < this.parentState.food.tile.col ) {
        this.classes.right = 1;
      } else {
        this.classes.right = 0;
      }
      if( this.col > this.parentState.food.tile.col ) {
        this.classes.left = 1;
      } else {
        this.classes.left = 0;
      }
      if( this.row > this.parentState.food.tile.row ) {
        this.classes.up = 1;
      } else {
        this.classes.up = 0;
      }
      if( this.row < this.parentState.food.tile.row ) {
        this.classes.down = 1;
      } else {
        this.classes.down = 0;
      }
    } else {
      this.classes.path = 0;
    }

    if( this.parentState.food.eaten ) {
      this.classes.path = 0;
    }
  };

  g.BoardTile.prototype.updateDimensions = function() {
    this.x = this.col * this.parentState.tileWidth;
    this.y = this.row * this.parentState.tileHeight;
    this.w = this.parentState.tileWidth - this.parentState.spacing;
    this.h = this.parentState.tileHeight - this.parentState.spacing;
    this.elem.style.left = this.x + 'px';
    this.elem.style.top = this.y + 'px';
    this.elem.style.width = this.w + 'px';
    this.elem.style.height = this.h + 'px';
  };

  g.BoardTile.prototype.render = function() {
    var classString = '';
    for( var k in this.classes ) {
      if( this.classes[ k ] ) {
        classString += k + ' ';
      }
    }
    this.elem.className = 'tile ' + classString;
  };

})();

/*================================================

Snake Tile Entity

================================================*/

(function(){ 'use strict';

  g.SnakeTile = function( opt ) {
    this.parentState = opt.parentState;
    this.parentGroup = opt.parentGroup;
    this.col = opt.col;
    this.row = opt.row;
    this.x = opt.x;
    this.y = opt.y;
    this.w = opt.w;
    this.h = opt.h;
    this.color = null;
    this.scale = 1;
    this.rotation = 0;
    this.blur = 0;
    this.alpha = 1;
    this.borderRadius = 0;
    this.borderRadiusAmount = 0;
    this.elem = document.createElement( 'div' );
    this.elem.style.position = 'absolute';
    this.parentState.stageElem.appendChild( this.elem );
  };

  g.SnakeTile.prototype.update = function( i ) {
    this.x = this.col * this.parentState.tileWidth;
    this.y = this.row * this.parentState.tileHeight;
    if( i == 0 ) {
      this.color = '#fff';
      this.blur = this.parentState.dimAvg * 0.03 + Math.sin( this.parentState.time.elapsed / 200 ) * this.parentState.dimAvg * 0.015;
      if( this.parentState.snake.dir == 'n' ) {
        this.borderRadius = this.borderRadiusAmount + '% ' + this.borderRadiusAmount + '% 0 0';
      } else if( this.parentState.snake.dir == 's' ) {
        this.borderRadius = '0 0 ' + this.borderRadiusAmount + '% ' + this.borderRadiusAmount + '%';
      } else if( this.parentState.snake.dir == 'e' ) {
        this.borderRadius = '0 ' + this.borderRadiusAmount + '% ' + this.borderRadiusAmount + '% 0';
      } else if( this.parentState.snake.dir == 'w' ) {
        this.borderRadius = this.borderRadiusAmount + '% 0 0 ' + this.borderRadiusAmount + '%';
      }
    } else {
      this.color = '#fff';
      this.blur = 0;
      this.borderRadius = '0';
    }
    this.alpha = 1 - ( i / this.parentState.snake.tiles.length ) * 0.6;
    this.rotation = ( this.parentState.snake.justAteTick / this.parentState.snake.justAteTickMax ) * 90;
    this.scale = 1 + ( this.parentState.snake.justAteTick / this.parentState.snake.justAteTickMax ) * 1;
  };

  g.SnakeTile.prototype.updateDimensions = function() {
    this.w = this.parentState.tileWidth - this.parentState.spacing;
    this.h = this.parentState.tileHeight - this.parentState.spacing;
  };

  g.SnakeTile.prototype.render = function( i ) {
    this.elem.style.left = this.x + 'px';
    this.elem.style.top = this.y + 'px';
    this.elem.style.width = this.w + 'px';
    this.elem.style.height = this.h + 'px';
    this.elem.style.backgroundColor = 'rgba(255, 255, 255, ' + this.alpha + ')';
    this.elem.style.boxShadow = '0 0 ' + this.blur + 'px #fff';
    this.elem.style.borderRadius = this.borderRadius;
  };

})();

/*================================================

Food Tile Entity

================================================*/

(function(){ 'use strict';

  g.FoodTile = function( opt ) {
    this.parentState = opt.parentState;
    this.parentGroup = opt.parentGroup;
    this.col = opt.col;
    this.row = opt.row;
    this.x = opt.x;
    this.y = opt.y;
    this.w = opt.w;
    this.h = opt.h;
    this.blur = 0;
    this.scale = 1;
    this.hue = 100;
    this.opacity = 0;
    this.elem = document.createElement( 'div' );
    this.elem.style.position = 'absolute';
    this.parentState.stageElem.appendChild( this.elem );
  };

  g.FoodTile.prototype.update = function() {
    this.x = this.col * this.parentState.tileWidth;
    this.y = this.row * this.parentState.tileHeight;
    this.blur = this.parentState.dimAvg * 0.03 + Math.sin( this.parentState.time.elapsed / 200 ) * this.parentState.dimAvg * 0.015;
    this.scale = 0.8 + Math.sin( this.parentState.time.elapsed / 200 ) * 0.2;

    if( this.parentState.food.birthTick || this.parentState.food.deathTick ) {
      if( this.parentState.food.birthTick ) {
        this.opacity = 1 - ( this.parentState.food.birthTick / 1 ) * 1;
      } else {
        this.opacity = ( this.parentState.food.deathTick / 1 ) * 1;
      }
    } else {
      this.opacity = 1;
    }
  };

  g.FoodTile.prototype.updateDimensions = function() {
    this.w = this.parentState.tileWidth - this.parentState.spacing;
    this.h = this.parentState.tileHeight - this.parentState.spacing;
  };

  g.FoodTile.prototype.render = function() {
    this.elem.style.left = this.x + 'px';
    this.elem.style.top = this.y + 'px';
    this.elem.style.width = this.w + 'px';
    this.elem.style.height = this.h + 'px';
    this.elem.style[ 'transform' ] = 'translateZ(0) scale(' + this.scale + ')';
    this.elem.style.backgroundColor = 'hsla(' + this.hue + ', 100%, 60%, 1)';
    this.elem.style.boxShadow = '0 0 ' + this.blur + 'px hsla(' + this.hue + ', 100%, 60%, 1)';
    this.elem.style.opacity = this.opacity;
  };

})();

/*================================================

Snake Entity

================================================*/

(function(){ 'use strict';

  g.Snake = function( opt ) {
    this.parentState = opt.parentState;
    this.dir = 'e',
    this.currDir = this.dir;
    this.tiles = [];
    for( var i = 0; i < 5; i++ ) {
      this.tiles.push( new g.SnakeTile({
        parentState: this.parentState,
        parentGroup: this.tiles,
        col: 8 - i,
        row: 3,
        x: ( 8 - i ) * opt.parentState.tileWidth,
        y: 3 * opt.parentState.tileHeight,
        w: opt.parentState.tileWidth - opt.parentState.spacing,
        h: opt.parentState.tileHeight - opt.parentState.spacing
      }));
    }
    this.last = 0;
    this.updateTick = 10;
    this.updateTickMax = this.updateTick;
    this.updateTickLimit = 3;
    this.updateTickChange = 0.2;
    this.deathFlag = 0;
    this.justAteTick = 0;
    this.justAteTickMax = 1;
    this.justAteTickChange = 0.05;

    // sync data grid of the play state
    var i = this.tiles.length;

    while( i-- ) {
      this.parentState.grid.set( this.tiles[ i ].col, this.tiles[ i ].row, 'snake' );
    }
  };

  g.Snake.prototype.updateDimensions = function() {
    var i = this.tiles.length;
    while( i-- ) {
      this.tiles[ i ].updateDimensions();
    }
  };

  g.Snake.prototype.update = function() {
    if( this.parentState.keys.up ) {
      if( this.dir != 's' && this.dir != 'n' && this.currDir != 's' && this.currDir != 'n' ) {
        this.dir = 'n';
      }
    } else if( this.parentState.keys.down) {
      if( this.dir != 'n' && this.dir != 's' && this.currDir != 'n' && this.currDir != 's' ) {
        this.dir = 's';
      }
    } else if( this.parentState.keys.right ) {
      if( this.dir != 'w' && this.dir != 'e' && this.currDir != 'w' && this.currDir != 'e' ) {
        this.dir = 'e';
      }
    } else if( this.parentState.keys.left ) {
      if( this.dir != 'e' && this.dir != 'w' && this.currDir != 'e' && this.currDir != 'w' ) {
        this.dir = 'w';
      }
    }

    this.parentState.keys.up = 0;
    this.parentState.keys.down = 0;
    this.parentState.keys.right = 0;
    this.parentState.keys.left = 0;

    this.updateTick += this.parentState.time.ndelta;
    if( this.updateTick >= this.updateTickMax ) {
      // reset the update timer to 0, or whatever leftover there is
      this.updateTick = ( this.updateTick - this.updateTickMax );

      // rotate snake block array
      this.tiles.unshift( new g.SnakeTile({
        parentState: this.parentState,
        parentGroup: this.tiles,
        col: this.tiles[ 0 ].col,
        row: this.tiles[ 0 ].row,
        x: this.tiles[ 0 ].col * this.parentState.tileWidth,
        y: this.tiles[ 0 ].row * this.parentState.tileHeight,
        w: this.parentState.tileWidth - this.parentState.spacing,
        h: this.parentState.tileHeight - this.parentState.spacing
      }));
      this.last = this.tiles.pop();
      this.parentState.stageElem.removeChild( this.last.elem );

      this.parentState.boardTiles.collection[ this.last.col + ( this.last.row * this.parentState.cols ) ].classes.pressed = 2;

      // sync data grid of the play state
      var i = this.tiles.length;

      while( i-- ) {
        this.parentState.grid.set( this.tiles[ i ].col, this.tiles[ i ].row, 'snake' );
      }
      this.parentState.grid.set( this.last.col, this.last.row, 'empty' );


      // move the snake's head
      if ( this.dir == 'n' ) {
        this.currDir = 'n';
        this.tiles[ 0 ].row -= 1;
      } else if( this.dir == 's' ) {
        this.currDir = 's';
        this.tiles[ 0 ].row += 1;
      } else if( this.dir == 'w' ) {
        this.currDir = 'w';
        this.tiles[ 0 ].col -= 1;
      } else if( this.dir == 'e' ) {
        this.currDir = 'e';
        this.tiles[ 0 ].col += 1;
      }

      // wrap walls
      this.wallFlag = false;
      if( this.tiles[ 0 ].col >= this.parentState.cols ) {
        this.tiles[ 0 ].col = 0;
        this.wallFlag = true;
      }
      if( this.tiles[ 0 ].col < 0 ) {
        this.tiles[ 0 ].col = this.parentState.cols - 1;
        this.wallFlag = true;
      }
      if( this.tiles[ 0 ].row >= this.parentState.rows ) {
        this.tiles[ 0 ].row = 0;
        this.wallFlag = true;
      }
      if( this.tiles[ 0 ].row < 0 ) {
        this.tiles[ 0 ].row = this.parentState.rows - 1;
        this.wallFlag = true;
      }

      // check death by eating self
      if( this.parentState.grid.get( this.tiles[ 0 ].col, this.tiles[ 0 ].row ) == 'snake' ) {
        this.deathFlag = 1;
        clearTimeout( this.foodCreateTimeout );
      }

      // check eating of food
      if( this.parentState.grid.get( this.tiles[ 0 ].col, this.tiles[ 0 ].row ) == 'food' ) {
        this.tiles.push( new g.SnakeTile({
          parentState: this.parentState,
          parentGroup: this.tiles,
          col: this.last.col,
          row: this.last.row,
          x: this.last.col * this.parentState.tileWidth,
          y: this.last.row * this.parentState.tileHeight,
          w: this.parentState.tileWidth - this.parentState.spacing,
          h: this.parentState.tileHeight - this.parentState.spacing
        }));
        if( this.updateTickMax - this.updateTickChange > this.updateTickLimit ) {
          this.updateTickMax -= this.updateTickChange;
        }
        this.parentState.score++;
        this.parentState.scoreElem.innerHTML = this.parentState.score;
        this.justAteTick = this.justAteTickMax;

        this.parentState.food.eaten = 1;
        this.parentState.stageElem.removeChild( this.parentState.food.tile.elem );

        var _this = this;
        
        this.foodCreateTimeout = setTimeout( function() {
          _this.parentState.food = new g.Food({
            parentState: _this.parentState
          });
        }, 300);
      }

      // check death by eating self
      if( this.deathFlag ) {
        g.setState( 'play' );
      }
    }

    // update individual snake tiles
    var i = this.tiles.length;
    while( i-- ) {
      this.tiles[ i ].update( i );
    }

    if( this.justAteTick > 0 ) {
      this.justAteTick -= this.justAteTickChange;
    } else if( this.justAteTick < 0 ) {
      this.justAteTick = 0;
    }
  };

  g.Snake.prototype.render = function() {
    // render individual snake tiles
    var i = this.tiles.length;
    while( i-- ) {
      this.tiles[ i ].render( i );
    }
  };

})();

/*================================================

Food Entity

================================================*/

(function(){ 'use strict';

  g.Food = function( opt ) {
    this.parentState = opt.parentState;
    this.tile = new g.FoodTile({
      parentState: this.parentState,
      col: 0,
      row: 0,
      x: 0,
      y: 0,
      w: opt.parentState.tileWidth - opt.parentState.spacing,
      h: opt.parentState.tileHeight - opt.parentState.spacing
    });
    this.reset();
    this.eaten = 0;
    this.birthTick = 1;
    this.deathTick = 0;
    this.birthTickChange = 0.025;
    this.deathTickChange = 0.05;
  };

  g.Food.prototype.reset = function() {
    var empty = [];
    for( var x = 0; x < this.parentState.cols; x++) {
      for( var y = 0; y < this.parentState.rows; y++) {
        var tile = this.parentState.grid.get( x, y );
        if( tile == 'empty' ) {
          empty.push( { x: x, y: y } );
        }
      }
    }
    var newTile = empty[ g.util.randInt( 0, empty.length - 1 ) ];
    this.tile.col = newTile.x;
    this.tile.row = newTile.y;
  };

  g.Food.prototype.updateDimensions = function() {
    this.tile.updateDimensions();
  };

  g.Food.prototype.update = function() {
    // update food tile
    this.tile.update();

    if( this.birthTick > 0 ) {
      this.birthTick -= this.birthTickChange;
    } else if( this.birthTick < 0 ) {
      this.birthTick = 0;
    }

    // sync data grid of the play state
    this.parentState.grid.set( this.tile.col, this.tile.row, 'food' );
  };

  g.Food.prototype.render = function() {
    this.tile.render();
  };

})();

/*================================================

Play State

================================================*/

(function(){ 'use strict';

  function StatePlay() {
    this.name = 'play';
  }

  StatePlay.prototype.init = function() {
    this.scoreElem = document.querySelector( '.score' );
    this.stageElem = document.querySelector( '.stage' );
    this.dimLong = 28;
    this.dimShort = 16;
    this.padding = 0.25;
    this.boardTiles = new g.Group();
    this.keys = {};
    this.foodCreateTimeout = null;
    this.score = 0;
    this.scoreElem.innerHTML = this.score;
    this.time = new g.Time();
    this.getDimensions();
    if( this.winWidth < this.winHeight ) {
      this.rows = this.dimLong;
      this.cols = this.dimShort;
    } else {
      this.rows = this.dimShort;
      this.cols = this.dimLong;
    }
    this.spacing = 1;
    this.grid = new g.Grid( this.cols, this.rows );
    this.resize();
    this.createBoardTiles();
    this.bindEvents();
    this.snake = new g.Snake({
      parentState: this
    });
    this.food = new g.Food({
      parentState: this
    });
  };

  StatePlay.prototype.getDimensions = function() {
    this.winWidth = window.innerWidth;
    this.winHeight = window.innerHeight;
    this.activeWidth = this.winWidth - ( this.winWidth * this.padding );
    this.activeHeight = this.winHeight - ( this.winHeight * this.padding );
  };

  StatePlay.prototype.resize = function() {
    var _this = g.currentState();

    _this.getDimensions();

    _this.stageRatio = _this.rows / _this.cols;

    if( _this.activeWidth > _this.activeHeight / _this.stageRatio ) {
      _this.stageHeight = _this.activeHeight;
      _this.stageElem.style.height = _this.stageHeight + 'px';
      _this.stageWidth = Math.floor( _this.stageHeight /_this.stageRatio );
      _this.stageElem.style.width = _this.stageWidth + 'px';
    } else {
      _this.stageWidth = _this.activeWidth;
      _this.stageElem.style.width = _this.stageWidth + 'px';
      _this.stageHeight = Math.floor( _this.stageWidth * _this.stageRatio );
      _this.stageElem.style.height = _this.stageHeight + 'px';
    }

    _this.tileWidth = ~~( _this.stageWidth / _this.cols );
    _this.tileHeight = ~~( _this.stageHeight / _this.rows );
    _this.dimAvg = ( _this.activeWidth + _this.activeHeight ) / 2;
    _this.spacing = Math.max( 1, ~~( _this.dimAvg * 0.0025 ) );

    _this.stageElem.style.marginTop = ( -_this.stageElem.offsetHeight / 2 ) + _this.headerHeight / 2 + 'px';

    _this.boardTiles.each( 'updateDimensions' );
    _this.snake !== undefined && _this.snake.updateDimensions();
    _this.food !== undefined && _this.food.updateDimensions();
  };

  StatePlay.prototype.createBoardTiles = function() {
    for( var y = 0; y < this.rows; y++ ) {
      for( var x = 0; x < this.cols; x++ ) {
        this.boardTiles.add( new g.BoardTile({
          parentState: this,
          parentGroup: this.boardTiles,
          col: x,
          row: y,
          x: x * this.tileWidth,
          y: y * this.tileHeight,
          w: this.tileWidth - this.spacing,
          h: this.tileHeight - this.spacing
        }));
      }
    }
  };

  StatePlay.prototype.upOn = function() { g.currentState().keys.up = 1; }
  StatePlay.prototype.downOn = function() { g.currentState().keys.down = 1; }
  StatePlay.prototype.rightOn = function() { g.currentState().keys.right = 1; }
  StatePlay.prototype.leftOn = function() { g.currentState().keys.left = 1; }
  StatePlay.prototype.upOff = function() { g.currentState().keys.up = 0; }
  StatePlay.prototype.downOff = function() { g.currentState().keys.down = 0; }
  StatePlay.prototype.rightOff = function() { g.currentState().keys.right = 0; }
  StatePlay.prototype.leftOff = function() { g.currentState().keys.left = 0; }

  StatePlay.prototype.keydown = function( e ) {
    e.preventDefault();
    var e = ( e.keyCode ? e.keyCode : e.which ),
      _this = g.currentState();
    if( e === 38 || e === 87 ) { _this.upOn(); }
    if( e === 39 || e === 68 ) { _this.rightOn(); }
    if( e === 40 || e === 83 ) { _this.downOn(); }
    if( e === 37 || e === 65 ) { _this.leftOn(); }
  };

  StatePlay.prototype.bindEvents = function() {
    var _this = g.currentState();
    window.addEventListener( 'keydown', _this.keydown, false );
    window.addEventListener( 'resize', _this.resize, false );
  };

  StatePlay.prototype.step = function() {
    this.boardTiles.each( 'update' );
    this.boardTiles.each( 'render' );
    this.snake.update();
    this.snake.render();
    this.food.update();
    this.food.render();
    this.time.update();
  };

  StatePlay.prototype.exit = function() {
    window.removeEventListener( 'keydown', this.keydown, false );
    window.removeEventListener( 'resize', this.resize, false );
    this.stageElem.innerHTML = '';
    this.grid.tiles = null;
    this.time = null;
  };

  g.addState( new StatePlay() );

})();

/*================================================

Game

================================================*/

(function(){ 'use strict';

  g.config = {
    title: 'Snakely',
    debug: window.location.hash == '#debug' ? 1 : 0,
    state: 'play'
  };

  g.setState( g.config.state );

  g.time = new g.Time();

  g.step = function() {
    requestAnimationFrame( g.step );
    g.states[ g.state ].step();
    g.time.update();
  };

  window.addEventListener( 'load', g.step, false );

})();
            
          
!
999px

Console