jeudi 10 août 2017

2D scroll




<html>
<head>
  <title>Guinea Pig Runner</title>
  <link rel="stylesheet" href="https://tutsplus.github.io/kandi-runner/kandi.css" type="text/css" media="screen">
  <script type="text/javascript" src="https://tutsplus.github.io/kandi-runner/jquery.1.10.2.min.js"></script>
</head>
<body>
  <div class="wrapper">
    <div class="sound sound-off" style="display: block;">
</div>
<div id="menu" class="main">
      <div id="progress" style="display: none;">
        <div id="percent">
Downloading: <span id="p">100%</span></div>
<progress id="progress-bar" value="1"></progress>
      </div>
<div id="main" style="display: block;">
        <h1>
Guinea Pig Runner</h1>
<ul>
<li><a href="#" class="play">Start Game</a></li>
<li><a href="#" class="credits">Credits</a></li>
</ul>
</div>
<div id="credits">
        <ul>
<li class="artwork">Character design and art: <a href="http://www.acrylicana.com/">Mary Winkler</a></li>
<li class="artwork">Walk cycle animation: <a href="http://vimeo.com/hellostudios">Adam Everett Miller</a></li>
<li class="artwork">Platformer tiles: <a href="http://www.kenney.nl/">Kenney</a></li>
</ul>
<ul>
<li class="music">Game music: <a href="http://opengameart.org/content/jump-and-run-8-bit">bart</a></li>
<li class="music">Jump sound effect: <a href="http://opengameart.org/content/platformer-jumping-sounds">dklon</a></li>
<li class="music">Game over music: <a href="http://opengameart.org/content/lose-music-3">remaxim</a></li>
</ul>
<ul>
<li class="developer">Developer: <a href="http://blog.sklambert.com/">Steven Lambert</a></li>
</ul>
<div>
<a href="#" class="back">Back</a></div>
</div>
</div>
<canvas id="canvas" width="800" height="480"></canvas>
    <div id="game-over">
      <h2>
You ran <span id="score"></span> meters!</h2>
<a href="#" class="restart">Try again?</a>
    </div>
</div>
<script>
(function ($) {
  // define variables
  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  var volume, player, score, stop, ticker;
  var platforms = [], water = [], enemies = [], environment = [];
  var canUseLocalStorage = 'localStorage' in window && window.localStorage !== null;
  ctx.font = "15pt Arial";

  // platform variables
  var platformHeight, platformLength, gapLength;
  var platformWidth = 32;
  var platformBase = canvas.height - platformWidth;
  var platformSpacer = 64;

  // set the sound preference
  if (canUseLocalStorage) {
    var playSound = (localStorage['game.playSound'] == true);

    if (playSound) {
      volume = 1;
      $('.sound').addClass('sound-on').removeClass('sound-off');
    }
    else {
      volume = 0;
      $('.sound').addClass('sound-off').removeClass('sound-on');
    }
  }

  /**
   * Asset pre-loader object. Loads all images and sounds
   */
  var assetLoader = (function() {
    // images
    this.imgs        = {
      'bg'            : 'https://tutsplus.github.io/kandi-runner/imgs/bg.png',
      'sky'           : 'https://tutsplus.github.io/kandi-runner/imgs/sky.png',
      'backdrop'      : 'https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5_Zyp6SdcH42F_ue3tRsMdLj6tMADPJr2q0BDjdjw-LwRUEQLO-E81bW7gHp7xdlxOpNGjXNwxb6t9fxsOQDdCLtGWCtZctvdKr4AN3K6Y9xr9bfYbsmvRmBvB3tjUVc47HLaFtCI-w/w800-h480-no/',
      'backdrop2'     : 'https://tutsplus.github.io/kandi-runner/imgs/backdrop_ground.png',
      'water'         : 'https://tutsplus.github.io/kandi-runner/imgs/water.png',
      'grass1'        : 'https://tutsplus.github.io/kandi-runner/imgs/grassMid1.png',
      'grass2'        : 'https://tutsplus.github.io/kandi-runner/imgs/grassMid2.png',
      'avatar_normal' : 'https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijhgQKzYNjPwqiVls6-eVSJCjj4P0ui5mdvzPYnfypHekvCSNpQtxnRfh7WGU4K2ux1IqQ1Ttd3s7XQvSeY-nUkPJWMlju3Sgqlk5JH7Mn3xr0v7XXpx7BBs-zrA8JPr8Bq3QpTx9EFQ/w240-h384-no/',
      'bridge'        : 'https://tutsplus.github.io/kandi-runner/imgs/bridge.png',
      'plant'         : 'https://tutsplus.github.io/kandi-runner/imgs/plant.png',
      'bush1'         : 'https://tutsplus.github.io/kandi-runner/imgs/bush1.png',
      'bush2'         : 'https://tutsplus.github.io/kandi-runner/imgs/bush2.png',
      'cliff'         : 'https://tutsplus.github.io/kandi-runner/imgs/grassCliffRight.png',
      'spikes'        : 'https://tutsplus.github.io/kandi-runner/imgs/spikes.png',
      'grass'         : 'https://tutsplus.github.io/kandi-runner/imgs/grass.png',
      'box'           : 'https://tutsplus.github.io/kandi-runner/imgs/boxCoin.png',
      'slime'         : 'https://tutsplus.github.io/kandi-runner/imgs/slime.png'
    };

    // sounds
    this.sounds      = {
      'bg'            : 'https://tutsplus.github.io/kandi-runner/sounds/bg.mp3',
      'jump'          : 'https://tutsplus.github.io/kandi-runner/sounds/jump.mp3',
      'gameOver'      : 'https://tutsplus.github.io/kandi-runner/sounds/gameOver.mp3'
    };

    var assetsLoaded = 0;                                // how many assets have been loaded
    var numImgs      = Object.keys(this.imgs).length;    // total number of image assets
    var numSounds    = Object.keys(this.sounds).length;  // total number of sound assets
    this.totalAssest = numImgs + numSounds;              // total number of assets
    this.checkAudio  = {};                               // setInterval variable for checking audio loading

    /**
     * Ensure all assets are loaded before using them
     * @param self Reference to the assetLoader object
     */
    function assetLoaded(self, dic, name) {
      assetsLoaded++;
      self[dic][name].status = 'loaded';
      assetProgress(assetsLoaded, self.totalAssest);
      if (assetsLoaded === self.totalAssest) {
        clearInterval(self.checkAudio);
        mainMenu();
      }
    }

    /**
     * Check the status of all sound files for being loaded
     * Workaround of audio API not firing events when loaded
     */
    function checkAudioStatus() {
      for (var sound in this.sounds) {
        if (this.sounds.hasOwnProperty(sound) && this.sounds[sound].status === 'loading' && this.sounds[sound].readyState === 4) {
          assetLoaded(this, 'sounds', sound);
        }
      }
    }

    // create asset, set callback for asset loading, set asset source
    var self = this;
    var src  = '';
    for (var img in this.imgs) {
      if (this.imgs.hasOwnProperty(img)) {
        src = this.imgs[img];
        this.imgs[img] = new Image();
        this.imgs[img].status = 'loading';
        this.imgs[img].onload = function() { assetLoaded(self, 'imgs', img); };
        this.imgs[img].src = src;
      }
    }
    for (var sound in this.sounds) {
      if (this.sounds.hasOwnProperty(sound)) {
        src = this.sounds[sound];
        this.sounds[sound] = new Audio();
        this.sounds[sound].status = 'loading';
        this.sounds[sound].volume = volume;
        this.sounds[sound].src = src;
      }
    }

    var that = this;
    if (numSounds > 0) {
      this.checkAudio = setInterval(function() { checkAudioStatus.call(that); },1000);
    }

    return {
      imgs: this.imgs,
      sounds: this.sounds,
      totalAssest: this.totalAssest
    };
  })();

  /**
   * Creates a Spritesheet
   * @param {string} - Path to the image.
   * @param {number} - Width (in px) of each frame.
   * @param {number} - Height (in px) of each frame.
   */
  function SpriteSheet(path, frameWidth, frameHeight) {
    this.image = new Image();
    this.frameWidth = frameWidth;
    this.frameHeight = frameHeight;

    // calculate the number of frames in a row after the image loads
    var self = this;
    this.image.onload = function() {
      self.framesPerRow = Math.floor(self.image.width / self.frameWidth);
    };

    this.image.src = path;
  }

  /**
   * Creates an animation from a spritesheet.
   * @param {SpriteSheet} - The spritesheet used to create the animation.
   * @param {number}      - Number of frames to wait for before transitioning the animation.
   * @param {array}       - Range or sequence of frame numbers for the animation.
   * @param {boolean}     - Repeat the animation once completed.
   */
  function Animation(spritesheet, frameSpeed, startFrame, endFrame) {

    var animationSequence = [];  // array holding the order of the animation
    var currentFrame = 0;        // the current frame to draw
    var counter = 0;             // keep track of frame rate

    // start and end range for frames
    for (var frameNumber = startFrame; frameNumber <= endFrame; frameNumber++)
      animationSequence.push(frameNumber);

    /**
     * Update the animation
     */
    this.update = function() {

      // update to the next frame if it is time
      if (counter == (frameSpeed - 1))
        currentFrame = (currentFrame + 1) % animationSequence.length;

      // update the counter
      counter = (counter + 1) % frameSpeed;
    };

    /**
     * Draw the current frame
     * @param {integer} x - X position to draw
     * @param {integer} y - Y position to draw
     */
    this.draw = function(x, y) {
      // get the row and col of the frame
      var row = Math.floor(animationSequence[currentFrame] / spritesheet.framesPerRow);
      var col = Math.floor(animationSequence[currentFrame] % spritesheet.framesPerRow);

      ctx.drawImage(
        spritesheet.image,
        col * spritesheet.frameWidth, row * spritesheet.frameHeight,
        spritesheet.frameWidth, spritesheet.frameHeight,
        x, y,
        spritesheet.frameWidth, spritesheet.frameHeight);
    };
  }

  /**
   * Get a random number between range
   * @param {integer}
   * @param {integer}
   */
  function rand(low, high) {
    return Math.floor( Math.random() * (high - low + 1) + low );
  }

  /**
   * Bound a number between range
   * @param {integer} num - Number to bound
   * @param {integer}
   * @param {integer}
   */
  function bound(num, low, high) {
    return Math.max( Math.min(num, high), low);
  }

  /**
   * A vector for 2d space.
   * @param {integer} x - Center x coordinate.
   * @param {integer} y - Center y coordinate.
   * @param {integer} dx - Change in x.
   * @param {integer} dy - Change in y.
   */
  function Vector(x, y, dx, dy) {
    // position
    this.x = x || 0;
    this.y = y || 0;
    // direction
    this.dx    = dx    || 0;
    this.dy    = dy    || 0;
  }

  /**
   * Advance the vectors position by dx,dy
   */
  Vector.prototype.advance = function() {
    this.x += this.dx;
    this.y += this.dy;
  };

  /**
   * Get the minimum distance between two vectors
   * @param {Vector}
   * @return minDist
   */
  Vector.prototype.minDist = function(vec) {
    var minDist = Infinity;
    var max     = Math.max( Math.abs(this.dx), Math.abs(this.dy),
                            Math.abs(vec.dx ), Math.abs(vec.dy ) );
    var slice   = 1 / max;

    var x, y, distSquared;

    // get the middle of each vector
    var vec1 = {}, vec2 = {};
    vec1.x = this.x + this.width/2;
    vec1.y = this.y + this.height/2;
    vec2.x = vec.x + vec.width/2;
    vec2.y = vec.y + vec.height/2;
    for (var percent = 0; percent < 1; percent += slice) {
      x = (vec1.x + this.dx * percent) - (vec2.x + vec.dx * percent);
      y = (vec1.y + this.dy * percent) - (vec2.y + vec.dy * percent);
      distSquared = x * x + y * y;

      minDist = Math.min(minDist, distSquared);
    }

    return Math.sqrt(minDist);
  };

  /**
   * Create a parallax background
   */
  var background = (function() {
    this.sky   = {};
    this.backdrop = {};
    this.backdrop2 = {};

    this.sky.x = 0;
    this.sky.y = 0;
    this.sky.speed = 0.2;

    this.backdrop.x = 0;
    this.backdrop.y = 0;
    this.backdrop.speed = 0.4;

    this.backdrop2.x = 0;
    this.backdrop2.y = 0;
    this.backdrop2.speed = 0.6;

    /**
     * Draw the backgrounds to the screen at different speeds
     */
    this.draw = function() {
      ctx.drawImage(assetLoader.imgs.bg, 0, 0);

      // Pan background
      this.sky.x -= this.sky.speed;
      this.backdrop.x -= this.backdrop.speed;
      this.backdrop2.x -= this.backdrop2.speed;

      // draw images side by side to loop
      ctx.drawImage(assetLoader.imgs.sky, this.sky.x, this.sky.y);
      ctx.drawImage(assetLoader.imgs.sky, this.sky.x + canvas.width, this.sky.y);

      ctx.drawImage(assetLoader.imgs.backdrop, this.backdrop.x, this.backdrop.y);
      ctx.drawImage(assetLoader.imgs.backdrop, this.backdrop.x + canvas.width, this.backdrop.y);

      ctx.drawImage(assetLoader.imgs.backdrop2, this.backdrop2.x, this.backdrop2.y);
      ctx.drawImage(assetLoader.imgs.backdrop2, this.backdrop2.x + canvas.width, this.backdrop2.y);

      // If the image scrolled off the screen, reset
      if (this.sky.x + assetLoader.imgs.sky.width <= 0)
        this.sky.x = 0;
      if (this.backdrop.x + assetLoader.imgs.backdrop.width <= 0)
        this.backdrop.x = 0;
      if (this.backdrop2.x + assetLoader.imgs.backdrop2.width <= 0)
        this.backdrop2.x = 0;
    };

    return {
      sky: this.sky,
      backdrop: this.backdrop,
      backdrop2: this.backdrop2,
      draw: this.draw
    };
  })();

  /**
   * The player object
   * @param {integer} x - Starting x position of the player
   * @param {integer} y - Starting y position of the player
   */
  function Player(x, y) {
    this.dy        = 0;
    this.gravity   = 1;
    this.speed     = 6;
    this.jumpDy    = -10;
    this.isJumping = false;
    this.width     = 60;
    this.height    = 96;
    this.sheet     = new SpriteSheet('https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijhgQKzYNjPwqiVls6-eVSJCjj4P0ui5mdvzPYnfypHekvCSNpQtxnRfh7WGU4K2ux1IqQ1Ttd3s7XQvSeY-nUkPJWMlju3Sgqlk5JH7Mn3xr0v7XXpx7BBs-zrA8JPr8Bq3QpTx9EFQ/w240-h384-no/', this.width, this.height);
    this.walkAnim  = new Animation(this.sheet, 4, 0, 15);
    this.jumpAnim  = new Animation(this.sheet, 4, 15, 15);
    this.fallAnim  = new Animation(this.sheet, 4, 11, 11);
    this.anim      = this.walkAnim;
    Vector.call(this, x, y, 0, this.dy);

    var jumpCounter = 0;  // how long the jump button can be pressed down

    /**
     * Update the player's position and animation
     */
    this.update = function() {

      // jump if not currently jumping or falling
      if (KEY_STATUS.space && this.dy === 0 && !this.isJumping) {
        this.isJumping = true;
        this.dy = this.jumpDy;
        jumpCounter = 12;
        assetLoader.sounds.jump.play();
      }

      // jump higher if the space bar is continually pressed
      if (KEY_STATUS.space && jumpCounter) {
        this.dy = this.jumpDy;
      }

      jumpCounter = Math.max(jumpCounter-1, 0);

      this.advance();

      // add gravity
      if (this.isFalling || this.isJumping) {
        this.dy += this.gravity;
      }

      // change animation if falling
      if (this.dy > 0) {
        this.anim = this.fallAnim;
      }
      // change animation is jumping
      else if (this.dy < 0) {
        this.anim = this.jumpAnim;
      }
      else {
        this.anim = this.walkAnim;
      }

      this.anim.update();
    };

    /**
     * Draw the player at it's current position
     */
    this.draw = function() {
      this.anim.draw(this.x, this.y);
    };
  }
  Player.prototype = Object.create(Vector.prototype);

  /**
   * Sprites are anything drawn to the screen (platforms, enemies, etc.)
   * @param {integer} x - Starting x position of the player
   * @param {integer} y - Starting y position of the player
   * @param {string} type - Type of sprite
   */
  function Sprite(x, y, type) {
    this.x      = x;
    this.y      = y;
    this.width  = platformWidth;
    this.height = platformWidth;
    this.type   = type;
    Vector.call(this, x, y, 0, 0);

    /**
     * Update the Sprite's position by the player's speed
     */
    this.update = function() {
      this.dx = -player.speed;
      this.advance();
    };

    /**
     * Draw the sprite at it's current position
     */
    this.draw = function() {
      ctx.save();
      ctx.translate(0.5,0.5);
      ctx.drawImage(assetLoader.imgs[this.type], this.x, this.y);
      ctx.restore();
    };
  }
  Sprite.prototype = Object.create(Vector.prototype);

  /**
   * Get the type of a platform based on platform height
   * @return Type of platform
   */
  function getType() {
    var type;
    switch (platformHeight) {
      case 0:
      case 1:
        type = Math.random() > 0.5 ? 'grass1' : 'grass2';
        break;
      case 2:
        type = 'grass';
        break;
      case 3:
        type = 'bridge';
        break;
      case 4:
        type = 'box';
        break;
    }
    if (platformLength === 1 && platformHeight < 3 && rand(0, 3) === 0) {
      type = 'cliff';
    }

    return type;
  }

  /**
   * Update all platforms position and draw. Also check for collision against the player.
   */
  function updatePlatforms() {
    // animate platforms
    player.isFalling = true;
    for (var i = 0; i < platforms.length; i++) {
      platforms[i].update();
      platforms[i].draw();

      // stop the player from falling when landing on a platform
      var angle;
      if (player.minDist(platforms[i]) <= player.height/2 + platformWidth/2 &&
          (angle = Math.atan2(player.y - platforms[i].y, player.x - platforms[i].x) * 180/Math.PI) > -130 &&
          angle < -50) {
        player.isJumping = false;
        player.isFalling = false;
        player.y = platforms[i].y - player.height + 5;
        player.dy = 0;
      }
    }

    // remove platforms that have gone off screen
    if (platforms[0] && platforms[0].x < -platformWidth) {
      platforms.splice(0, 1);
    }
  }

  /**
   * Update all water position and draw.
   */
  function updateWater() {
    // animate water
    for (var i = 0; i < water.length; i++) {
      water[i].update();
      water[i].draw();
    }

    // remove water that has gone off screen
    if (water[0] && water[0].x < -platformWidth) {
      var w = water.splice(0, 1)[0];
      w.x = water[water.length-1].x + platformWidth;
      water.push(w);
    }
  }

  /**
   * Update all environment position and draw.
   */
  function updateEnvironment() {
    // animate environment
    for (var i = 0; i < environment.length; i++) {
      environment[i].update();
      environment[i].draw();
    }

    // remove environment that have gone off screen
    if (environment[0] && environment[0].x < -platformWidth) {
      environment.splice(0, 1);
    }
  }

  /**
   * Update all enemies position and draw. Also check for collision against the player.
   */
  function updateEnemies() {
    // animate enemies
    for (var i = 0; i < enemies.length; i++) {
      enemies[i].update();
      enemies[i].draw();

      // player ran into enemy
      var angle;
      if (player.minDist(enemies[i]) <= player.width - platformWidth/2) {
        gameOver();
      }
    }

    // remove enemies that have gone off screen
    if (enemies[0] && enemies[0].x < -platformWidth) {
      enemies.splice(0, 1);
    }
  }

  /**
   * Update the players position and draw
   */
  function updatePlayer() {
    player.update();
    player.draw();

    // game over
    if (player.y + player.height >= canvas.height) {
      gameOver();
    }
  }

  /**
   * Spawn new sprites off screen
   */
  function spawnSprites() {
    // increase score
    score++;

    // first create a gap
    if (gapLength > 0) {
      gapLength--;
    }
    // then create platforms
    else if (platformLength > 0) {
      var type = getType();

      platforms.push(new Sprite(
        canvas.width + platformWidth % player.speed,
        platformBase - platformHeight * platformSpacer,
        type
      ));
      platformLength--;

      // add random environment sprites
      spawnEnvironmentSprites();

      // add random enemies
      spawnEnemySprites();
    }
    // start over
    else {
      // increase gap length every speed increase of 4
      gapLength = rand(player.speed - 2, player.speed);
      // only allow a platforms to increase by 1
      platformHeight = bound(rand(0, platformHeight + rand(0, 2)), 0, 4);
      platformLength = rand(Math.floor(player.speed/2), player.speed * 4);
    }
  }

  /**
   * Spawn new environment sprites off screen
   */
  function spawnEnvironmentSprites() {
    if (score > 40 && rand(0, 20) === 0 && platformHeight < 3) {
      if (Math.random() > 0.5) {
        environment.push(new Sprite(
          canvas.width + platformWidth % player.speed,
          platformBase - platformHeight * platformSpacer - platformWidth,
          'plant'
        ));
      }
      else if (platformLength > 2) {
        environment.push(new Sprite(
          canvas.width + platformWidth % player.speed,
          platformBase - platformHeight * platformSpacer - platformWidth,
          'bush1'
        ));
        environment.push(new Sprite(
          canvas.width + platformWidth % player.speed + platformWidth,
          platformBase - platformHeight * platformSpacer - platformWidth,
          'bush2'
        ));
      }
    }
  }

  /**
   * Spawn new enemy sprites off screen
   */
  function spawnEnemySprites() {
    if (score > 100 && Math.random() > 0.96 && enemies.length < 3 && platformLength > 5 &&
        (enemies.length ? canvas.width - enemies[enemies.length-1].x >= platformWidth * 3 ||
         canvas.width - enemies[enemies.length-1].x < platformWidth : true)) {
      enemies.push(new Sprite(
        canvas.width + platformWidth % player.speed,
        platformBase - platformHeight * platformSpacer - platformWidth,
        Math.random() > 0.5 ? 'spikes' : 'slime'
      ));
    }
  }

  /**
   * Game loop
   */
  function animate() {
    if (!stop) {
      requestAnimFrame( animate );
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      background.draw();

      // update entities
      updateWater();
      updateEnvironment();
      updatePlayer();
      updatePlatforms();
      updateEnemies();

      // draw the score
      ctx.fillText('Score: ' + score + 'm', canvas.width - 140, 30);

      // spawn a new Sprite
      if (ticker % Math.floor(platformWidth / player.speed) === 0) {
        spawnSprites();
      }

      // increase player speed only when player is jumping
      if (ticker > (Math.floor(platformWidth / player.speed) * player.speed * 20) && player.dy !== 0) {
        player.speed = bound(++player.speed, 0, 15);
        player.walkAnim.frameSpeed = Math.floor(platformWidth / player.speed) - 1;

        // reset ticker
        ticker = 0;

        // spawn a platform to fill in gap created by increasing player speed
        if (gapLength === 0) {
          var type = getType();
          platforms.push(new Sprite(
            canvas.width + platformWidth % player.speed,
            platformBase - platformHeight * platformSpacer,
            type
          ));
          platformLength--;
        }
      }

      ticker++;
    }
  }

  /**
   * Request Animation Polyfill
   */
  var requestAnimFrame = (function(){
    return  window.requestAnimationFrame       ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame    ||
            window.oRequestAnimationFrame      ||
            window.msRequestAnimationFrame     ||
            function(callback, element){
              window.setTimeout(callback, 1000 / 60);
            };
  })();

  /**
   * Keep track of the spacebar events
   */
  var KEY_CODES = {
    32: 'space'
  };
  var KEY_STATUS = {};
  for (var code in KEY_CODES) {
    if (KEY_CODES.hasOwnProperty(code)) {
       KEY_STATUS[KEY_CODES[code]] = false;
    }
  }
  document.onkeydown = function(e) {
    var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
    if (KEY_CODES[keyCode]) {
      e.preventDefault();
      KEY_STATUS[KEY_CODES[keyCode]] = true;
    }
  };
  document.onkeyup = function(e) {
    var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
    if (KEY_CODES[keyCode]) {
      e.preventDefault();
      KEY_STATUS[KEY_CODES[keyCode]] = false;
    }
  };

  /**
   * Show asset loading progress
   * @param {integer} progress - Number of assets loaded
   * @param {integer} total - Total number of assets
   */
  function assetProgress(progress, total) {
    var pBar = document.getElementById('progress-bar');
    pBar.value = progress / total;
    document.getElementById('p').innerHTML = Math.round(pBar.value * 100) + "%";
  }

  /**
   * Show the main menu after loading all assets
   */
  function mainMenu() {
    assetLoader.sounds.bg.loop = true;
    $('#progress').hide();
    $('#main').show();
    $('#menu').addClass('main');
    $('.sound').show();
  }

  /**
   * Start the game - reset all variables and entities, spawn platforms and water.
   */
  function startGame() {
    platforms = [];
    water = [];
    environment = [];
    enemies = [];
    player = new Player(64, 250);
    ticker = 0;
    stop = false;
    score = 0;
    background.sky.x = 0;
    background.backdrop.x = 0;
    background.backdrop.y = 0;
    platformHeight = 2;
    platformLength = 15;
    gapLength = 0;

    for (var i = 0; i < 30; i++) {
      platforms.push(new Sprite(i * (platformWidth-3), platformBase - platformHeight * platformSpacer, 'grass'));
    }

    for (i = 0; i < canvas.width / 32 + 2; i++) {
      water.push(new Sprite(i * platformWidth, platformBase, 'water'));
    }

    animate();

    assetLoader.sounds.gameOver.pause();
    assetLoader.sounds.bg.currentTime = 0;
    assetLoader.sounds.bg.play();
  }

  /**
   * End the game and restart
   */
  function gameOver() {
    stop = true;
    $('#score').html(score);
    $('#game-over').show();
    assetLoader.sounds.bg.pause();
    assetLoader.sounds.gameOver.currentTime = 0;
    assetLoader.sounds.gameOver.play();
  }

  /**
   * Click handlers for the different menu screens
   */
  $('.credits').click(function() {
    $('#main').hide();
    $('#credits').show();
    $('#menu').addClass('credits');
  });
  $('.back').click(function() {
    $('#credits').hide();
    $('#main').show();
    $('#menu').removeClass('credits');
  });
  $('.sound').click(function() {
    var $this = $(this);
    // sound off
    if ($this.hasClass('sound-on')) {
      $this.removeClass('sound-on').addClass('sound-off');
      volume = 0;
    }
    // sound on
    else {
      $this.removeClass('sound-off').addClass('sound-on');
      volume = 1;
    }

    if (canUseLocalStorage) {
      localStorage['game.playSound'] = volume;
    }

    for (var sound in assetLoader.sounds) {
      if (assetLoader.sounds.hasOwnProperty(sound)) {
        assetLoader.sounds[sound].volume = volume;
      }
    }
  });
  $('.play').click(function() {
    $('#menu').hide();
    startGame();
  });
  $('.restart').click(function() {
    $('#game-over').hide();
    startGame();
  });
}(jQuery));
  </script>

</body>
</html>