text:
/**
 * jQuery Hotkeys Plugin
 *
 * Dual licensed under the MIT or GPLv2 licenses.
 *
 * The full project and documentation are available at
 * https://github.com/IceCreamYou/jquery.hotkeys
 *
 * Credits:
 * - [Isaac Sukin](https://github.com/IceCreamYou) wrote this revision
 * - [John Resig](https://github.com/jeresig/jquery.hotkeys)
 * - [Tzury Bar Yochay](https://github.com/tzuryby/jquery.hotkeys)
 * - [Binny V A](http://www.openjs.com/scripts/events/keyboard_shortcuts/)
 * - [kwillia](https://github.com/jeresig/jquery.hotkeys/pull/4/files)
 * - [kevingorski](https://github.com/jeresig/jquery.hotkeys/pull/2/files)
 *
 * @ignore
 */
(function(jQuery){
  /**
   * @class jQuery.hotkeys
   *   Provides easy, human-friendly handling for keyboard input.
   *
   * USAGE:
   *
   * - Bind the `keydown`, `keypress`, or `keyup` events to an element:
   *
   *       $(selector).keypress('ctrl+a down', function(event) {});
   *       // OR
   *       $(selector).on('keypress', 'ctrl+a down', function(event) {});
   *
   *   Separate key combinations that should trigger the callback with spaces.
   *   In the examples above, the callback would fire if `ctrl+a` or `down` was
   *   pressed. In the event callback, `event.keyPressed` holds the combination
   *   that actually triggered the callback.
   *
   * - You can specify keys in combination with the control keys: `alt`,
   *   `ctrl`, `meta`, and `shift`. If you use multiple control keys in a
   *   combination, specify them in alphabetical order.
   *
   * - Instead of binding to key events, you can also just call
   *   `jQuery.hotkeys.areKeysDown()` to determine whether a set of keys is
   *   currently being pressed, or examine the list of currently pressed keys
   *   yourself in `jQuery.hotkeys.keysDown`. This is useful if you want to
   *   bind to key events for all keys since `event.keyPressed` does not exist
   *   in this scenario:
   *
   *       $(selector).keypress(function(event) {});
   *
   * - If you only care about keys that were pressed (and released) instead of
   *   which keys are being held down, you can call
   *   `jQuery.hotkeys.lastKeyPressed()` or examine the last 5 keys pressed in
   *   `jQuery.hotkeys.lastKeysPressed`.
   *
   * Hotkeys aren't tracked if you're inside of an input element (unless you
   * explicitly bind the hotkey directly to the input). This helps avoid
   * conflicts with normal user typing.
   *
   * NOTE: Firefox is the only major browser that will reliably let you override
   * all key shortcuts built into the browser. This won't be a problem for most
   * applications, but you should avoid binding to combinations like ctrl+Q and
   * alt+F4 because most browsers will still react to those by closing the
   * window.
   */
  jQuery.hotkeys = {
    version: "0.9",

    // Keys currently held down
    keysDown: [],

    // The last 5 keys pressed and released (most recent key at the end)
    lastKeysPressed: [],

    // HTML elements that support text input
    textTypes: [
      'text', 'search', 'tel', 'url', 'email', 'password', 'number', 'range',
      'date', 'month', 'week', 'time', 'datetime', 'datetime-local', 'color'
    ],

    // Charcodes for when String.fromCharCode() doesn't work
    specialKeys: {
      8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift",
      17: "ctrl", 18: "alt", 19: "pause", 20: "capslock", 27: "esc",
      32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
      37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
      59: ";", 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5",
      102: "6", 103: "7", 104: "8", 105: "9", 106: "*", 107: "+", 109: "-",
      110: ".", 111 : "/", 112: "f1", 113: "f2", 114: "f3", 115: "f4",
      116: "f5", 117: "f6", 118: "f7", 119: "f8", 120: "f9", 121: "f10",
      122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 186: ";",
      187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 219: "[", 220: "\\",
      221: "]", 222: "'", 224: "meta"
    },

    // Map of characters to the character they upshift to
    shiftNums: {
      "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^",
      "7": "&", "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ":",
      "'": "\"", ",": "<", ".": ">",  "/": "?",  "\\": "|", "[": "{", "]": "}"
    },

    /**
     * Tests whether a set of keys is currently pressed down.
     *
     * If no control key (alt, ctrl, meta, shift) is currently held down,
     * the specified keys will match in any order. Otherwise, they have to
     * match in the given order.
     *
     * @param {Array/String} keyArray
     *   An Array or string of keys to check. If an Array is passed, this
     *   method tests whether *all* the keys in the array are currently held
     *   down *and* whether any keys are held down that are not in the array.
     *   If a String is passed, combinations of characters should be connected
     *   with + signs and separated with spaces. Each combination will be
     *   checked and this function will return true if any of the combinations
     *   matches. For example, the string "up down left+right" will return true
     *   if either the up arrow key, the down arrow key, or both the left and
     *   right arrow keys are currently pressed.
     *
     *   NOTE: Instead of writing shift-key characters like "@", write
     *   "shift+2". This avoids ambiguity and makes it less likely that
     *   invalid character sequences could be specified.
     *
     * @return {Boolean}
     *   true if the given keys match the set of keys currently pressed
     *   down; false otherwise.
     */
    areKeysDown: function(keyArray) {
      var i;
      // If the parameter is a string, split it apart and check each combination.
      if (typeof keyArray == 'string') {
        var choices = keyArray.split(' ');
        for (i = 0; i < choices.length; i++) {
          if (this.areKeysDown(choices[i].split('+'))) {
            return true;
          }
        }
        return false;
      }
      var foundControlKey = false, l = this.keysDown.length;
      // The combinations won't match if they aren't the same length.
      if (l != keyArray.length) {
        return false;
      }
      // Check for control keys so we know whether order matters.
      for (i = 0; i < l; i++) {
        if (jQuery.inArray(this.keysDown[i], ['alt', 'ctrl', 'meta', 'shift']) > -1) {
          foundControlKey = true;
          break;
        }
      }
      if (foundControlKey) {
        // Compare keyArray with $.hotkeys.keysDown, order doesn't matter
        for (i = 0; i < l; i++) {
          if (jQuery.inArray(this.keysDown[i], keyArray) == -1) {
            return false;
          }
        }
      }
      else {
        // Compare keyArray with $.hotkeys.keysDown, order matters
        for (i = 0; i < l; i++) {
          if (this.keysDown[i] != keyArray[i]) {
            return false;
          }
        }
      }
      return true;
    },

    /**
     * Return the last key pressed.
     */
    lastKeyPressed: function() {
      return this.lastKeysPressed[this.lastKeysPressed.length-1];
    }
  };

  // Respond to bound keyboard events.
  function keyHandler(handleObj) {
    // Only care when a possible input has been specified
    if (typeof handleObj.data !== "string") {
      return;
    }

    var origHandler = handleObj.handler,
        keys = handleObj.data.toLowerCase().split(" ");

    handleObj.handler = function(event) {
      // Don't fire in text-accepting inputs that we didn't directly bind to
      if (this !== event.target && (/textarea|select/i.test(event.target.nodeName) ||
          jQuery.inArray(event.target.type, jQuery.hotkeys.textTypes) > -1)) {
        return;
      }

      // Keypress represents characters, not special keys
      var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which],
          character = String.fromCharCode(event.which).toLowerCase(),
          modif = "", possible = {};

      // Check combinations (alt|ctrl|command|shift+anything)
      if (event.altKey && special !== "alt") {
        modif += "alt+";
      }

      if (event.ctrlKey && special !== "ctrl") {
        modif += "ctrl+";
      }

      if (event.metaKey && !event.ctrlKey && special !== "meta") {
        modif += "meta+";
      }

      if (event.shiftKey && special !== "shift") {
        modif += "shift+";
      }

      if (special) {
        possible[modif + special] = true;
      }
      else {
        possible[modif + character] = true;
        possible[modif + jQuery.hotkeys.shiftNums[character]] = true;

        // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
        if (modif === "shift+") {
          possible[jQuery.hotkeys.shiftNums[character]] = true;
        }
      }

      // Record which keys are down
      var i, keyPressed;
      if (event.type === "keydown") {
        keyPressed = special || character;
        i = jQuery.inArray(keyPressed, jQuery.hotkeys.keysDown);
        if (i === undefined || i < 0) {
          jQuery.hotkeys.keysDown.push(keyPressed);
        }
      }
      // Release keys
      else if (event.type === "keyup") {
        keyPressed = special || character;
        i = jQuery.inArray(keyPressed, jQuery.hotkeys.keysDown);
        if (i !== undefined && i > -1) {
          jQuery.hotkeys.keysDown.splice(i, 1);
        }
        jQuery.hotkeys.lastKeysPressed.push(keyPressed);
        if (jQuery.hotkeys.lastKeysPressed.length > 5) {
          jQuery.hotkeys.lastKeysPressed.shift();
        }
      }

      for (i = 0, l = keys.length; i < l; i++) {
        if (possible[keys[i]]) {
          event.keyPressed = keys[i]; // note which key combination was actually pressed
          return origHandler.apply(this, arguments);
        }
      }
    };
  }

  // Intercept keyboard events
  jQuery.each(["keydown", "keyup", "keypress"], function() {
    jQuery.event.special[this] = { add: keyHandler };
  });

  // Listen to every keydown/keyup event so we can record them.
  jQuery(document).keydown('na', function() {});
  jQuery(document).keyup('na', function() {});

})(this.jQuery);

/**
 * Simple JavaScript Inheritance
 * By [John Resig](http://ejohn.org/)
 * MIT Licensed.
 * @ignore
 */
// Inspired by base2 and Prototype
(function() {
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

  /**
   * @class Class
   *   The base Class implementation (does nothing).
   *
   * This is [John Resig's "simple JavaScript inheritance" library](http://ejohn.org/blog/simple-javascript-inheritance/).
   *
   * Define a new class with Class#extend:
   *
   *      var MyClass = Class.extend({
   *        // This is the constructor.
   *        init: function() {
   *          // this._super is the parent object's method.
   *          this._super.apply(this, arguments);
   *        },
   *        // This is a custom method.
   *        myMethod: function() {
   *          alert('hi');
   *        },
   *      });
   *      var myInstance = new MyClass();
   *      alert(myInstance instanceof MyClass); // true
   *      var MyChildClass = MyClass.extend({
   *        // Overrides the parent method.
   *        myMethod: function() {
   *          alert('hi there');
   *        },
   *      });
   *      var myChild = new MyChildClass();
   *      alert(myChild instanceof MyChildClass && myChild instanceof myClass); // true
   *      myChild.myMethod(); // hi there
   */
  this.Class = function() {};

  /**
   * Create a new Class that inherits from this class.
   */
  Class.extend = function(prop) {
    var _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;

    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;

            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];

            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);
            this._super = tmp;

            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }

    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }

    // Populate our constructed prototype object
    Class.prototype = prototype;

    // Enforce the constructor to be what we expect
    Class.prototype.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;

    return Class;
  };
})();

/**
 * A powerful, easy-to-use Sprite animation library for HTML5 Canvas.
 *
 * MIT License: http://opensource.org/licenses/mit-license.php
 *
 * @author Isaac Sukin (IceCreamYou)
 * @ignore
 */

// BEGIN SPRITE MAP LIBRARY ===================================================

(function() {

/**
 * Manage multiple sprite animations in the same sprite sheet.
 *
 * All methods except SpriteMap#clone are chainable (they return the SpriteMap
 * instance).
 *
 * @constructor
 *   Creates a new SpriteMap instance.
 *
 * @param {String} src
 *   The image to draw, in the form of one of the following:
 *
 *   - The file path of the base image
 *   - An Image
 *   - A Canvas
 * @param {Object} animations
 *   A map (Object) where the keys are the names of animation sequences and the
 *   values are maps (Objects) specifying the starting and ending frames of the
 *   relevant animation sequence. All properties are optional:
 *
 *   - startRow: The row at which to start the animation sequence. Defaults to
 *     0 (zero) - the first row.
 *   - startCol: The column at which to start the animation sequence. Defaults
 *     to 0 (zero) - the first column.
 *   - endRow: The row at which to end the animation sequence. Defaults to the
 *     last row.
 *   - endCol: The column at which to end the animation sequence. Defaults to
 *     the last column.
 *   - squeeze: Determines which frames are included in the animation loop. If
 *     set to true, frames are constrained within startCol and endCol,
 *     regardless of the row. If set to false (the default), frames will run to
 *     the last column in the Sprite and then loop back to the first column on
 *     the next row in the Sprite until reaching the last frame in the loop.
 *     More details on how this work are documented in the {@link Sprite}
 *     constructor.
 *   - flipped: An object with "horizontal" and "vertical" properties
 *     (both Booleans) indicating whether the Sprite should be drawn flipped
 *     along the horizontal or vertical axes.
 *
 *   Alternatively, instead of the inner values being Objects with the
 *   properties specified above, they can be Arrays that hold the same values
 *   (in the same order). This is less clear to read, but more concise to
 *   write.
 * @param {Object} options
 *   This parameter is the same as the options parameter for the {@link Sprite}
 *   class.
 */
function SpriteMap(src, animations, options) {
  var origPIC = typeof options.postInitCallback == 'function' ? options.postInitCallback : null;
  var t = this;
  options.postInitCallback = function(sprite) {
    t.sprite = sprite;
    t.baseImage = sprite.image;
    t.cachedImages = {'00': t.baseImage};
    t.maps = {};
    for (var name in animations) {
      if (animations.hasOwnProperty(name)) {
        t.set(name, animations[name]);
      }
    }
    if (origPIC) {
      origPIC.apply(this, arguments);
    }
  };
  this.sprite = new Sprite(src, options);
  this.sprite.spriteMap = this;
}
SpriteMap.prototype = {
  /**
   * Add or modify an animation sequence.
   *
   * @param {String} name
   *   The name of the sequence.
   * @param {Object/Array} [options]
   *   Specifies the frames of the animation sequence. If an Array is passed,
   *   the values should be included in the order below.
   * @param {Number} [options.startRow=0]
   *   The index of the sequence's starting row.
   * @param {Number} [options.startCol=0]
   *   The index of the sequence's starting column.
   * @param {Number} [options.endRow]
   *   The index of the sequence's ending row. Defaults to the Sprite's last
   *   row.
   * @param {Number} [options.endCol]
   *   The index of the sequence's ending column. Defaults to the Sprite's last
   *   column.
   * @param {Boolean} [options.squeeze=false]
   *   Determines which frames are included in the animation loop. If set to
   *   true, frames are constrained within startCol and endCol, regardless of
   *   the row. If set to false (the default), frames will run to the last
   *   column in the Sprite and then loop back to the first column on the next
   *   row in the Sprite until reaching the last frame in the loop. More
   *   details on how this work are documented in the {@link Sprite}
   *   constructor.
   * @param {Object} [options.flipped={horizontal: false, vertical: false}]
   *   An object with "horizontal" and "vertical" properties (both Booleans)
   *   indicating whether the Sprite should be drawn flipped along the
   *   horizontal or vertical axes.
   * @param {Boolean} [options.flipped.horizontal=false]
   * @param {Boolean} [options.flipped.vertical=false]
   */
  set: function(name, options) {
    if (options instanceof Array) {
      options = {
          startRow: options[0],
          startCol: options[1],
          endRow: options[2],
          endCol: options[3],
          squeeze: options[4],
          flipped: options[5]
      };
    }
    this.maps[name] = {
        startRow: typeof options.startRow !== 'undefined' ? options.startRow : 0,
        startCol: typeof options.startCol !== 'undefined' ? options.startCol : 0,
        endRow:   typeof options.endRow   !== 'undefined' ? options.endRow   : this.sprite.rows-1,
        endCol:   typeof options.endCol   !== 'undefined' ? options.endCol   : this.sprite.cols-1,
        squeeze:  typeof options.squeeze  !== 'undefined' ? options.squeeze  : false,
        flipped:  typeof options.flipped  !== 'undefined' ? options.flipped  : {horizontal: false, vertical: false}
    };
    // Pre-render the flipped versions of the image we know we'll need to use.
    var f = this.maps[name].flipped,
        key = (f.horizontal ? '1' : '0') + (f.vertical ? '1' : '0');
    if (typeof this.sprite.cachedImages[key] === 'undefined') {
      this.sprite.cachedImages[key] = this.sprite._prerender(this.baseImage, f);
    }
    return this;
  },
  /**
   * Remove an animation sequence.
   *
   * @param {String} name
   *   The animation sequence to remove.
   */
  unset: function(name) {
    if (this.maps.hasOwnProperty(name)) {
      delete this.maps[name];
    }
    return this;
  },
  /**
   * Switch the active animation sequence.
   *
   * @param {String} name
   *   The name of the animation sequence to switch to.
   * @param {Boolean} [restartIfInUse=false]
   *   A Boolean indicating whether to restart the animation sequence if the
   *   specified sequence is already in use.
   */
  use: function(name, restartIfInUse) {
    if (this.activeLoop == name && !restartIfInUse) {
      return this;
    }
    /**
     * @property {String} activeLoop
     *   The name of the active animation sequence.
     */
    this.activeLoop = name;
    var m = this.maps[name];
    this.sprite.setLoop(m.startRow, m.startCol, m.endRow, m.endCol, m.squeeze, m.flipped);
    return this;
  },
  /**
   * Start the animation sequence.
   *
   * @param {String} [name]
   *   The name of the animation sequence to start. If not given, defaults to
   *   the active animation sequence. If no animation sequence is active, the
   *   default sequence is to show the whole sprite sheet.
   */
  start: function(name) {
    if (name) {
      this.use(name);
    }
    this.sprite.startLoop();
    return this;
  },
  /**
   * Stop the currently running animation sequence.
   */
  stop: function() {
    this.sprite.stopLoop();
    return this;
  },
  /**
   * Reset the active animation sequence to the first frame.
   *
   * If the sequence is running when SpriteMap#reset() is called, it will still
   * be running afterwards, so usually SpriteMap#stop() is called first.
   */
  reset: function() {
    this.sprite.reset();
    return this;
  },
  /**
   * Run an animation sequence once.
   *
   * @param {Function} [callback]
   *   A function to call after the animation sequence is done running.
   * @param {Sprite} [callback.sprite]
   *   The Sprite that was animated. Its "spriteMap" property holds the parent
   *   SpriteMap.
   * @param {String} [name]
   *   The name of the animation sequence to start. If not given, defaults to
   *   the active animation sequence. If no animation sequence is active, the
   *   default sequence is to show the whole sprite sheet.
   */
  runOnce: function(callback, name) {
    if (name) {
      this.use(name);
    }
    this.sprite.runLoop(callback);
    return this;
  },
  /**
   * Draw the sprite's current frame.
   *
   * @param {CanvasRenderingContext2D} ctx
   *   The canvas graphics context onto which the sprite should be drawn.
   * @param {Number} x
   *   The x-coordinate of the canvas graphics context at which the upper-left
   *   corner of the Sprite should be drawn. This is usually (but not always)
   *   the horizontal distance in pixels from the left side of the canvas.
   * @param {Number} y
   *   The y-coordinate of the canvas graphics context at which the upper-left
   *   corner of the Sprite should be drawn. This is usually (but not always)
   *   the vertical distance in pixels from the top of the canvas.
   * @param {Number} [w]
   *   The width of the image when drawn onto the canvas. Defaults to the
   *   Sprite's projected width, which in turn defaults to the frame width.
   * @param {Number} [h]
   *   The height of the image when drawn onto the canvas. Defaults to the
   *   Sprite's projected height, which in turn defaults to the frame height.
   */
  draw: function(ctx, x, y, w, h) {
    this.sprite.draw(ctx, x, y, w, h);
    return this;
  },
  /**
   * Clone the SpriteMap.
   *
   * @return {SpriteMap}
   *   A SpriteMap instance that is identical to the current instance.
   */
  clone: function() {
    return new SpriteMap(this.sprite.sourceFile, this.maps, this.sprite);
  }
};

this.SpriteMap = SpriteMap;

}).call(this);

// END SPRITE MAP LIBRARY =====================================================
// BEGIN SPRITE ANIMATION LIBRARY =============================================

(function() {

/**
 * Support sprite animation.
 *
 * - Animations are always run left-to-right, top-to-bottom.
 * - All frames in the loop are assumed to be the same size.
 * - Rows and columns (the row, col, startRow, startCol, endRow, and endCol
 *   properties) are zero-indexed, while frame number starts at 1. Usually
 *   frame 1 will have row and column values (0, 0).
 * - Use {@link SpriteMap}s to maintain multiple loops in the same image.
 * - This class assumes that the properties passed in make sense (i.e. the
 *   starting cell occurs before the ending cell, the image has nonzero
 *   dimensions, etc.). Otherwise behavior is undefined.
 * - All public methods that do not exist to get specific values return `this`
 *   (and therefore are chainable).
 *
 * @constructor
 *   Creates a new Sprite instance.
 *
 * @param {String} src
 *   The image to draw, in the form of one of the following:
 *
 *   - The file path of the base image
 *   - An Image
 *   - A Canvas
 * @param {Object} [options]
 *   An object whose properties affect how the sprite is animated. Each of the
 *   properties will be attached to the Sprite object directly, along with
 *   other calculated properties. It is best to call Sprite#getFrame() if you
 *   need information about the currently displayed frame. You can read other
 *   properties if you need to, but it is strongly recommended not to set
 *   properties directly because the resulting behavior is undefined.
 * @param {Number} [options.frameW]
 *   The width of each frame of the sprite. Defaults to the image width.
 * @param {Number} [options.frameH]
 *   The height of each frame of the sprite. Defaults to the image height.
 * @param {Number} [options.projectedW]
 *   The width of each frame when it is displayed on the canvas (allowing you
 *   to scale the frame). Defaults to the frame width.
 * @param {Number} [options.projectedH]
 *   The height of each frame when it is displayed on the canvas (allowing you
 *   to scale the frame). Defaults to the frame height.
 * @param {Number} [options.startRow=0]
 *   The row at which the animation loop should start.
 * @param {Number} [options.startCol=0]
 *   The column at which the animation loop should start.
 * @param {Number} [options.endRow]
 *   The row at which the animation loop should stop. Animations will run from
 *   (startRow, startCol) to (endRow, endCol), inclusive. Defaults to the last
 *   row in the image.
 * @param {Number} [options.endCol]
 *   The column at which the animation loop should stop. Animations will run
 *   from (startRow, startCol) to (endRow, endCol), inclusive. Defaults to the
 *   last column in the image.
 * @param {Boolean} [options.squeeze=false]
 *   By default, animation loops are assumed to run all the way to the end of
 *   each row before continuing at the start of the next row. For example, a
 *   valid arrangement of loops in an image might look like this:
 *
 *       AAAA
 *       AABB
 *       BBBC
 *       CCDD
 *       DDDD
 *
 *   In this example, the "C" loop starts at (2, 2) and ends at (3, 1).
 *   However, if the squeeze option is set to true, loops will be contained
 *   inside startCol and endCol. For example, a valid arrangement of loops in
 *   an image might look like this:
 *
 *       AABB
 *       AABB
 *       AACC
 *       DDCC
 *
 *   Now the "C" loop starts at (2, 2) and ends at (3, 3) and all its frames
 *   occur within the box formed by those coordinates.
 * @param {Number} [options.interval=125]
 *   The delay in milliseconds before switching frames when running the
 *   animation loop.
 * @param {Boolean} [options.useTimer=true]
 *   If true, Sprite animation loops rely on setInterval() to update their
 *   frames regularly (this is the default). If false, the Sprite will rely on
 *   being drawn as the "tick" that lets it update its frames. This can be
 *   slightly less accurate than using a timer (assuming the sprite gets drawn
 *   on every canvas repaint; otherwise it can be a lot less accurate, and in
 *   any case it can be up to 15ms off on Windows) but it is more
 *   performance-friendly and also ensures that frames will never skip if the
 *   sprite is not drawn.
 * @param {Boolean} [options.advanceFramesManually=false]
 *   If options.useTimer is false and this setting is true, frames will not be
 *   advanced automatically and must be advanced manually instead (i.e. using
 *   Sprite#nextFrame() or Sprite#changeFrame()).
 * @param {Object} [options.flipped={horizontal: false, vertical: false}]
 *   An object with "horizontal" and "vertical" properties (both Booleans)
 *   indicating whether the Sprite should be drawn flipped along the horizontal
 *   or vertical axes.
 * @param {Function} [options.postInitCallback]
 *   A function that will run at the end of the initialization process (if the
 *   source image has not been loaded before, this will be after the image has
 *   been fully loaded asynchronously). If the source image was not pre-loaded
 *   and you draw() the Sprite before this callback is invoked, nothing will be
 *   drawn because the image won't be loaded yet.
 * @param {Sprite} [options.postInitCallback.sprite]
 *   The Sprite that was loaded.
 */
function Sprite(src, options) {
  // String image file path
  if (typeof src == 'string') {
    this.sourceFile = src;
    var cachedImage = Sprite.getImageFromCache(src);
    if (cachedImage) { // cached
      this._init(cachedImage, options);
    }
    else { // not cached
      var image = new Image(), t = this;
      image.onload = function() {
        t._init(this, options);
      };
      image._src = src;
      image.src = src;
      Sprite.saveImageToCache(src, image);
    }
  }
  // Image
  else if (src instanceof HTMLImageElement || src instanceof Image) {
    if (!src.src) {
      return;
    }
    this.sourceFile = src._src || src.src;
    if (src.complete || (src.width && src.height)) { // loaded
      Sprite.saveImageToCache(this.sourceFile, src);
      this._init(src, options);
    }
    else { // not loaded
      if (src._src) { // We've already tried to draw this one
        return; // The onload callback will initialize the sprite when it runs
      }
      var o = src.onload, t = this;
      src.onload = function() {
        if (typeof o == 'function') { // don't overwrite any existing handler
          o();
        }
        Sprite.saveImageToCache(t.sourceFile, src);
        t._init(this, options);
      };
    }
  }
  // Canvas
  else if (src instanceof HTMLCanvasElement) {
    this._init(src, options);
  }
}
Sprite.prototype = {
  // Calculates and stores initial values based on a loaded image.
  _init: function(img, options) {
    this.width = img.width;
    this.height = img.height;
    this.frameW = options.frameW || img.width;
    this.frameH = options.frameH || img.height;
    this.projectedW = options.projectedW || this.frameW;
    this.projectedH = options.projectedH || this.frameH;
    this.rows = Math.floor(this.height / this.frameH);
    this.cols = Math.floor(this.width / this.frameW);
    this.startRow = options.startRow || 0;
    this.startCol = options.startCol || 0;
    this.endRow = (typeof options.endRow === 'undefined' ? this.rows-1 : options.endRow);
    this.endCol = (typeof options.endCol === 'undefined' ? this.cols-1 : options.endCol);
    this.row = this.startRow;
    this.col = this.startCol;
    this.frame = 1;
    this.squeeze = options.squeeze || false;
    this.interval = (typeof options.interval === 'undefined' ? 125 : options.interval);
    this.useTimer = (typeof options.useTimer === 'undefined' ? true : options.useTimer);
    this.advanceFramesManually = options.advanceFramesManually || false;
    this.lastFrameUpdateTime = 0;
    this.flipped = options.flipped || {horizontal: false, vertical: false};
    this.flipped.horizontal = this.flipped.horizontal || false;
    this.flipped.vertical = this.flipped.vertical || false;
    this.cachedImages = {'00': img};
    var f = this.flipped,
        key = (f.horizontal ? '1' : '0') + (f.vertical ? '1' : '0');
    if (typeof this.cachedImages[key] === 'undefined') {
      this.cachedImages[key] = this._prerender(img, f);
    }
    this.image = this.cachedImages[key];
    this._runOnce = false;
    if (this.squeeze) {
      this.cols = this.endCol - this.startCol + 1;
    }
    this.numFrames = this.getNumFrames();
    if (options.postInitCallback) {
      options.postInitCallback(this);
    }
  },
  /**
   * Pre-render the image onto a canvas.
   *
   * Canvases typically draw faster than images, especially when flipped.
   *
   * @return {HTMLCanvasElement} The prerendered flipped image.
   * @ignore
   */
  _prerender: function(image, flipped) {
    var tempCanvas = document.createElement('canvas');
    tempCanvas.width = this.width;
    tempCanvas.height = this.height;
    var tempContext = tempCanvas.getContext('2d');
    if (flipped.horizontal || flipped.vertical) {
      tempContext.translate(flipped.horizontal ? tempCanvas.width : 0,
          flipped.vertical ? tempCanvas.height : 0);
      tempContext.scale(flipped.horizontal ? -1 : 1,
          flipped.vertical ? -1 : 1);
    }
    tempContext.drawImage(image, 0, 0);
    return tempCanvas;
  },
  /**
   * Draws the sprite.
   *
   * @param {CanvasRenderingContext2D} ctx
   *   The canvas graphics context onto which the sprite should be drawn.
   * @param {Number} x
   *   The x-coordinate of the canvas graphics context at which the upper-left
   *   corner of the Sprite should be drawn. This is usually (but not always)
   *   the horizontal distance in pixels from the left side of the canvas.
   * @param {Number} y
   *   The y-coordinate of the canvas graphics context at which the upper-left
   *   corner of the Sprite should be drawn. This is usually (but not always)
   *   the vertical distance in pixels from the top of the canvas.
   * @param {Number} [w]
   *   The width of the image when drawn onto the canvas. Defaults to the
   *   Sprite's projected width, which in turn defaults to the frame width.
   * @param {Number} [h]
   *   The height of the image when drawn onto the canvas. Defaults to the
   *   Sprite's projected height, which in turn defaults to the frame height.
   */
  draw: function(ctx, x, y, w, h) {
    try {
      ctx.save();
      w = w || this.projectedW;
      h = h || this.projectedH;
      var xOffset = this.col * this.frameW,
          yOffset = this.row * this.frameH;
      if (this.flipped.horizontal) {
        xOffset = this.width - xOffset - this.frameW;
      }
      if (this.flipped.vertical) {
        yOffset = this.height - yOffset - this.frameH;
      }
      ctx.drawImage(
          this.image,  // image
          xOffset,     // image x-offset
          yOffset,     // image y-offset
          this.frameW, // image slice width
          this.frameH, // image slice height
          x,           // canvas x-position
          y,           // canvas y-position
          w,           // slice width on canvas
          h            // slice height on canvas
      );
      ctx.restore();
    } catch(e) {
      if (console && console.error) {
        // Usually the reason you would get an error here is if you tried to
        // draw() an image before it was fully loaded. That's an ignore-able
        // error, because if you're animating, the image will just pop in when
        // it loads.
        console.error(e);
      }
    }
    if (!this.useTimer && !this.advanceFramesManually &&
        Date.now() - this.lastFrameUpdateTime > this.interval) {
      this.nextFrame();
    }
    return this;
  },
  /**
   * Reset the animation to its first frame.
   *
   * Usually you will want to call Sprite#stopLoop() immediately before
   * Sprite#reset(); otherwise the animation will keep running (if it was
   * running already).
   */
  reset: function() {
    this.row = this.startRow, this.col = this.startCol, this.frame = 1;
    this.lastFrameUpdateTime = 0;
    return this;
  },
  /**
   * Move forward or backward a specified number of frames.
   *
   * @param {Number} delta
   *   The number of frames by which to move forward or backward (negative
   *   values move backward).
   */
  changeFrame: function(delta) {
    this.frame += delta;
    this.setFrame(this.frame);
    return this;
  },
  /**
   * Moves to a specific frame in the animation loop.
   *
   * This function supports passing either a frame number or row and column
   * coordinates as parameters. Frames outside of the accepted range will
   * overflow/underflow.
   *
   * You may want to call Sprite#stopLoop() immediately before
   * Sprite#setFrame(); otherwise the animation will keep running (if it was
   * running already).
   *
   * @param {Number} row
   *   The row of the frame to which to switch.
   * @param {Number} col
   *   The column of the frame to which to switch.
   */
  setFrame: function(row, col) {
    if (typeof col !== 'undefined') {
      this.row = row, this.col = col;
      if (this.squeeze) {
        this.frame = this.cols * (this.row - this.startRow + 1) -
          (this.endCol - this.col);
      }
      else {
        this.frame = this.cols * (this.row - this.startRow + 1) -
          (this.cols - (this.col + 1)) - (this.startCol);
      }
    }
    else {
      var props = this.frameNumberToRowCol(row);
      this.frame = props.frame, this.row = props.row, this.col = props.col;
    }
    this.lastFrameUpdateTime = Date.now();
    return this;
  },
  /**
   * Sets the range of frames over which the sprite should loop.
   *
   * @param {Number} startRow
   *   The row of the frame at which animation should start.
   * @param {Number} startCol
   *   The column of the frame at which animation should start.
   * @param {Number} [endRow]
   *   The row of the frame at which animation should end. Defaults to the last
   *   row in the image.
   * @param {Number} [endCol]
   *   The column of the frame at which animation should end. Defaults to the
   *   last column in the image.
   * @param {Boolean} [squeeze=false]
   *   A Boolean determining whether startCol and endCol define a box within
   *   which to find frames for this animation, or whether frames from any
   *   column can be used (after startCol in startRow and before endCol in
   *   endRow). For more information on how this works, see the documentation
   *   for the {@link Sprite} constructor.
   * @param {Object} [flipped]
   *   An object with "horizontal" and "vertical" properties (both Booleans)
   *   indicating whether the Sprite should be drawn flipped along the
   *   horizontal or vertical axes. Defaults to the flipped setting for the
   *   current animation sequence.
   */
  setLoop: function(startRow, startCol, endRow, endCol, squeeze, flipped) {
    this.stopLoop();
    if (endRow === null || typeof endRow === 'undefined') {
      endRow = this.rows-1;
    }
    if (endCol === null || typeof endCol === 'undefined') {
      endCol = this.cols-1;
    }
    if (typeof squeeze !== 'undefined') {
      this.squeeze = squeeze;
    }
    if (typeof flipped !== 'undefined') {
      this.flipped = flipped;
      var f = this.flipped,
          key = (f.horizontal ? '1' : '0') + (f.vertical ? '1' : '0');
      if (typeof this.cachedImages[key] === 'undefined') {
        this.cachedImages[key] = this._prerender(this.image, f);
      }
      this.image = this.cachedImages[key];
    }
    this.startRow = startRow, this.startCol = startCol,
    this.endRow = endRow, this.endCol = endCol;
    this.reset();
    this.numFrames = this.getNumFrames();
    return this;
  },
  /**
   * Starts running a new animation loop.
   *
   * Usually this function will be called without parameters since it defaults
   * to using the sprite's settings defined at instantiation time. In cases
   * where the frames that should be used in an animation change, this function
   * takes the same parameters as Sprite#setLoop() for convenience; using these
   * parameters is equivalent to calling sprite.setLoop(params).startLoop().
   *
   * @param {Number} [startRow]
   *   The row of the frame at which animation should start. Defaults to the
   *   starting row of the current animation sequence.
   * @param {Number} [startCol]
   *   The column of the frame at which animation should start. Defaults to the
   *   starting column of the current animation sequence.
   * @param {Number} [endRow]
   *   The row of the frame at which animation should end. Defaults to the
   *   ending row of the current animation sequence unless startRow and
   *   startCol are specified, in which case it defaults to the last row in the
   *   image.
   * @param {Number} [endCol]
   *   The column of the frame at which animation should end. Defaults to the
   *   ending column of the current animation sequence unless startRow and
   *   startCol are specified, in which case it defaults to the last column in
   *   the image.
   * @param {Boolean} [squeeze]
   *   A Boolean determining whether startCol and endCol define a box within
   *   which to find frames for this animation, or whether frames from any
   *   column can be used (after startCol in startRow and before endCol in
   *   endRow). For more information on how this works, see the documentation
   *   for the {@link Sprite} constructor. Defaults to the squeeze setting for
   *   the current animation sequence unless startRow and startCol are
   *   specified, in which case it defaults to false.
   * @param {Object} [flipped]
   *   An object with "horizontal" and "vertical" properties (both Booleans)
   *   indicating whether the Sprite should be drawn flipped along the
   *   horizontal or vertical axes. Defaults to the flipped setting for the
   *   current animation sequence.
   */
  startLoop: function(startRow, startCol, endRow, endCol, squeeze, flipped) {
    if (typeof startRow !== 'undefined' && typeof startCol !== 'undefined') {
      this.setLoop(startRow, startCol, endRow, endCol, squeeze, flipped);
    }
    this.lastFrameUpdateTime = Date.now();
    if (this.interval && this.useTimer) {
      var t = this;
      this.intervalID = setInterval(function() { t.nextFrame(); }, this.interval);
    }
    return this;
  },
  /**
   * Stops running the animation loop.
   */
  stopLoop: function() {
    this.lastFrameUpdateTime = 0;
    if (this.intervalID) {
      clearInterval(this.intervalID);
    }
    return this;
  },
  /**
   * Runs the animation loop once.
   *
   * The loop concludes at the final frame and does not reset to the first
   * frame. Use the callback function to reset it if you need that behavior.
   *
   * Usually this function will be called without parameters since it defaults
   * to using the sprite's settings defined at instantiation time. In cases
   * where the frames that should be used in an animation change, this function
   * takes the same parameters as Sprite#setLoop() for convenience; using these
   * parameters is equivalent to calling sprite.setLoop(params).startLoop().
   *
   * @param {Function} [callback]
   *   A callback function to run after the loop has completed, or a falsey
   *   value to skip this argument.
   * @param {Sprite} [callback.sprite]
   *   The Sprite that was animated.
   * @param {Number} [startRow]
   *   The row of the frame at which animation should start. Defaults to the
   *   starting row of the current animation sequence.
   * @param {Number} [startCol]
   *   The column of the frame at which animation should start. Defaults to the
   *   starting column of the current animation sequence.
   * @param {Number} [endRow]
   *   The row of the frame at which animation should end. Defaults to the
   *   ending row of the current animation sequence unless startRow and
   *   startCol are specified, in which case it defaults to the last row in the
   *   image.
   * @param {Number} [endCol]
   *   The column of the frame at which animation should end. Defaults to the
   *   ending column of the current animation sequence unless startRow and
   *   startCol are specified, in which case it defaults to the last column in
   *   the image.
   * @param {Boolean} [squeeze]
   *   A Boolean determining whether startCol and endCol define a box within
   *   which to find frames for this animation, or whether frames from any
   *   column can be used (after startCol in startRow and before endCol in
   *   endRow). For more information on how this works, see the documentation
   *   for the {@link Sprite} constructor. Defaults to the squeeze setting for
   *   the current animation sequence unless startRow and startCol are
   *   specified, in which case it defaults to false.
   * @param {Object} [flipped]
   *   An object with "horizontal" and "vertical" properties (both Booleans)
   *   indicating whether the Sprite should be drawn flipped along the
   *   horizontal or vertical axes. Defaults to the flipped setting for the
   *   current animation sequence unless startRow and startCol are specified,
   *   in which case it defaults to {horizontal: false, vertical: false}.
   */
  runLoop: function(callback, startRow, startCol, endRow, endCol, squeeze, flipped) {
    this.runLoopCallback = callback || function() {};
    this._runOnce = true;
    Array.prototype.shift.call(arguments);
    this.startLoop.apply(this, arguments);
    return this;
  },
  /**
   * Goes back one frame in the animation loop.
   *
   * This is equivalent to Sprite#changeFrame(-1). It is provided as a
   * convenience and to complement Sprite#nextFrame().
   */
  prevFrame: function() {
    changeFrame(-1);
    return this;
  },
  /**
   * Advances one frame in the animation loop.
   *
   * This is equivalent to (but more efficient than) Sprite#changeFrame(1).
   */
  nextFrame: function() {
    this.col++;
    this.frame++;
    if (this.row == this.endRow && this.col > this.endCol) {
      if (this._runOnce) {
        this.stopLoop();
        this._runOnce = false;
        this.runLoopCallback(this);
      }
      else {
        this.reset();
      }
    }
    else if (this.squeeze && this.col > this.endCol) {
      this.col = this.startCol;
      this.row++;
    }
    else if (!this.squeeze && this.col >= this.cols) {
      this.col = 0;
      this.row++;
    }
    this.lastFrameUpdateTime = Date.now();
    return this;
  },
  /**
   * Returns an object containing the current "row," "col," and "frame" number.
   *
   * Row and Col are zero-indexed; frame is 1-indexed.
   */
  getFrame: function() {
    return {row: this.row, col: this.col, frame: this.frame};
  },
  /**
   * Returns the total number of frames in the current animation loop.
   */
  getNumFrames: function() {
    if (this.squeeze) {
      return (this.endRow - this.startRow + 1) * (this.endCol - this.startCol + 1);
    }
    return (this.endRow - this.startRow) * this.cols - this.startCol + this.endCol + 1;
  },
  /**
   * Converts a frame number to row and column numbers.
   *
   * @param {Number} frame
   *   The frame number to convert.
   *
   * @return {Object}
   *   An object containing the 'frame' number and the corresponding 'row' and
   *   'col' properties.
   */
  frameNumberToRowCol: function(frame) {
    var row, col;
    frame = ((frame + this.numFrames) % this.numFrames) || this.numFrames; // over-/under-flow
    if (this.squeeze) {
      row = this.startRow + Math.floor((frame - 1) / this.cols);
      col = (frame - 1) % this.cols + this.startCol;
    }
    else {
      row = this.startRow + Math.floor((frame + this.startCol - 1) / this.cols);
      col = (frame + this.startCol - 1) % this.cols;
    }
    return {frame: frame, row: row, col: col};
  },
  /**
   * Clone the Sprite (return an identical copy).
   */
  clone: function() {
    return new Sprite(this.sourceFile, this);
  }
};

this.Sprite = Sprite;

}).call(this);

// END SPRITE ANIMATION LIBRARY ===============================================
// BEGIN IMAGE CACHE HELPERS ==================================================

/**
 * Override these functions to provide alternative cache implementations.
 * @ignore
 */
(function() {
  var images = {}; // Image cache

  /**
   * Get an image from the cache.
   *
   * @param {String} src
   *   The file path of the image.
   *
   * @return {Image}
   *   The Image object associated with the file or null if the Image object
   *   has not yet been cached.
   *
   * @static
   */
  Sprite.getImageFromCache = function(src) {
    return images[src] ? images[src] : null;
  };

  /**
   * Save an image to the cache.
   *
   * @param {String} src
   *   The file path of the image.
   * @param {Image} image
   *   The Image object to cache.
   *
   * @static
   */
  Sprite.saveImageToCache = function(src, image) {
    images[src] = image;
  };

  /**
   * Preload a list of images asynchronously.
   *
   * @param {String[]} files
   *   An Array of paths to images to preload.
   * @param {Object} [options]
   *   A map of options for this function.
   * @param {Function} [options.finishCallback]
   *   A function to run when all images have finished loading.
   * @param {Number} [options.finishCallback.numLoaded]
   *   The number of images that were preloaded.
   * @param {Function} [options.itemCallback]
   *   A function to run when an image has finished loading.
   * @param {String} [options.itemCallback.filepath]
   *   The file path of the loaded image.
   * @param {Number} [options.itemCallback.numLoaded]
   *   The number of images that have been loaded so far (including the current
   *   one).
   * @param {Number} [options.itemCallback.numImages]
   *   The total number of images to load.
   *
   * @static
   */
  Sprite.preloadImages = function(files, options) {
    var l = files.length, m = -1, src, image;
    var notifyLoaded = function(itemCallback, src) {
      m++;
      if (typeof itemCallback == 'function') {
        itemCallback(src, m, l);
      }
      if (m == l && typeof options.finishCallback == 'function') {
        options.finishCallback(l);
      }
    };
    notifyLoaded();
    var onload = function() {
      Sprite.saveImageToCache(this._src, this);
      notifyLoaded(options.itemCallback, this._src);
    };
    while (files.length) {
      src = files.pop();
      image = new Image();
      image._num = l-files.length;
      image._src = src;
      image.onload = onload;
      image.src = src;
    }
  };

}).call(this);

// END IMAGE CACHE HELPERS ====================================================

/**
 * Provides helpful utilities for common Canvas operations.
 *
 * This file is the core of the project. It contains all the abstractions that
 * avoid low-level and boilerplate code, namely initializing / manipulating the
 * canvas and running the main animation loop (including physics timing
 * issues) as well as a few utilities.
 *
 * @ignore
 */

/**
 * The App object holds all the miscellaneous things that don't deserve to be
 * global.
 * @static
 */
var App = {};
/**
 * @property
 *   Whether the game is over. Set by calling App#gameOver().
 * @member App
 * @static
 * @readonly
 */
App.isGameOver = false;
/**
 * @property
 *   Whether Debug Mode is on or off (shows or hides debugging information).
 * @member App
 * @static
 */
App.debugMode = false;
/**
 * @property
 *   The minimum number of physics updates each second.
 *
 * If the frame rate goes below this value, the app will appear to slow down;
 * less simulated time will be processed per unit of actual time. Specifically,
 * the simulator will never simulate more than `1 / App.MIN_FPS` seconds of
 * physics during each physics step, even if the time between calculating
 * physics steps is longer than that.
 *
 * Setting this to zero is allowed, but risks entering a "spiral of death"
 * where the system attempts to calculate more and more simulated physics time
 * each frame, which takes longer and longer, which means more simulation must
 * be run...
 *
 * If the frame rate goes below this value, a
 * {@link global#event-low_fps low_fps} event fires, which you can use to
 * reduce display quality in order to improve performance.
 *
 * @member App
 * @static
 */
App.MIN_FPS = 4;
/**
 * @property
 *   The maximum number of physics updates each second.
 *
 * This determines the discrete time-step for physics updates. If the rendering
 * frame rate is faster than the resulting time-step, physics changes are not
 * updated every frame.
 *
 * Higher values cause physics to be updated more times per frame. This
 * increases simulation accuracy, but each physics update takes some time to
 * calculate that is not necessarily proportional to the amount of time being
 * simulated, so ironically having a higher MAX_FPS can actually increase the
 * time required to simulate physics and thereby slow down the frame rate. The
 * recommended value is somewhere between 60 and 100, though if your updates
 * are particularly computationally intensive and your simulation can handle
 * larger time steps, you might consider going as low as 30. Most monitors are
 * capped at 60.
 *
 * @member App
 * @static
 */
App.MAX_FPS = 100;
/**
 * @property
 *   The total amount of time simulated, in seconds.
 *
 * This is the amount of time for which physics has been calculated. Physics
 * updates in discrete chunks of time that are not necessarily synced with the
 * rendering speed, so this is potentially different than the amount of time
 * elapsed since animation started as well as potentially different than the
 * amount of time between when animation started and when the current frame was
 * requested.
 *
 * This is useful for time-based motion like a wave or spring.
 *
 * `App.physicsTimeElapsed` is a useful alternative to `performance.now()` when
 * attempting to measure only time elapsed while animation is running. See also
 * {@link Timer}.
 *
 * @member App
 * @static
 */
App.physicsTimeElapsed = 0;
// Defined later as the amount of simulated physics time since the last update.
App.physicsDelta = 0;
// App.Debug is used for tracking debugging information.
App.Debug = {};
App.Debug.updateTimeElapsed = 0;
App.Debug.clearTimeElapsed = 0;
App.Debug.drawTimeElapsed = 0;

// SETUP ----------------------------------------------------------------------

/**
 * The Canvas DOM element.
 * @member global
 */
var canvas;
/**
 * The jQuery representation of the Canvas element.
 * @member global
 */
var $canvas;
/**
 * The main Canvas graphics context.
 * @member global
 */
var context;
/**
 * The {@link World} object for the current environment.
 * @member global
 */
var world;

/**
 * Set the canvas that the app should use.
 *
 * The canvas will be automatically detected if possible, using the following
 * rules in order:
 *
 * - Look for a canvas with the DOM ID "canvas"
 * - Look for the first canvas on the page without a data-takeover attribute
 *   that has the value "false"
 * - Create a new canvas
 *
 * However, you can force a specific canvas to be used by calling this
 * function. Currently the app can only manage one canvas at a time.
 *
 * @param {Mixed} c
 *   An HTMLCanvasElement (<canvas> object) or a DOM ID belonging to a
 *   canvas. This is the canvas to which the app should switch.
 * @member App
 * @static
 */
App.setCanvas = function(c) {
  // Allow passing a DOM ID
  if (typeof c == 'string') {
    var oc = c;
    c = document.getElementById(c);
    if (!(c instanceof HTMLCanvasElement) && window.console && console.warn) {
      console.warn('No canvas with ID ' + oc + ' found.');
    }
  }
  if (!(c instanceof HTMLCanvasElement)) {
    // If no canvas was specified and we already have one set up, don't switch.
    if (canvas instanceof HTMLCanvasElement && typeof $canvas !== 'undefined') {
      return;
    }
    // Try to use the canvas with the ID "canvas"
    canvas = document.getElementById('canvas');
    // Automatically use the first canvas on the page without data-takeover="false"
    if (!(canvas instanceof HTMLCanvasElement)) {
      for (var i = 0, n = document.getElementsByTagName('canvas'), l = n.length; i < l; i++) {
        if (n[i].getAttribute('data-takeover') != 'false') {
          canvas = n[i];
          break;
        }
      }
    }
    // If there is no available canvas on the page, create one
    if (!(canvas instanceof HTMLCanvasElement)) {
      canvas = document.createElement('canvas');
    }
    $canvas = jQuery(canvas);
    // We've run setup before, we just decided to auto-detect again
    if (App.isSetup) {
      App.beforeSetup();
    }
    // We found the canvas automatically, so run the setup automatically
    else {
      jQuery(window).load(App.beforeSetup);
    }
  }
  else {
    // Reset the existing canvas if there is one.
    if (typeof world !== 'undefined') {
      context.translate(world.xOffset, world.yOffset);
      world.scaleResolution(1);
    }
    canvas = c;
    $canvas = jQuery(canvas);
    // We were passed a specific canvas, so set it up.
    App.beforeSetup();
  }
};

// Set up things that need to be available before setup()
jQuery(document).ready(App.setCanvas);

// Set up the app itself. This runs after main.js loads.
App.beforeSetup = function() {
  App.isSetup = true;

  // Prevent default behavior of these keys because we'll be using them and
  // they can cause other page behavior (like scrolling).
  if (typeof keys !== 'undefined') {
    for (var dir in keys) {
      if (keys.hasOwnProperty(dir)) {
        App.preventDefaultKeyEvents(keys[dir].join(' '));
      }
    }
  }

  // Resize the canvas and get the graphics context
  App.setDefaultCanvasSize();
  context = canvas.getContext('2d');

  // If we have the Stats widget, set it up.
  if (typeof Stats !== 'undefined' && App.debugMode) {
    App.stats = new Stats();
    App.stats.setMode(0);
    App.stats.domElement.style.position = 'absolute';
    App.stats.domElement.style.left = $canvas.offset().left;
    App.stats.domElement.style.top = $canvas.offset().top;
    document.body.appendChild(App.stats.domElement);
  }

  // Pre-load images and start the animation.
  Caches.preloadImages(typeof preloadables === 'undefined' ? [] : preloadables, {
    finishCallback: function() {
      // Expose utilities globally if they are not already defined.
      if (typeof Events === 'undefined' && typeof App.Events !== 'undefined') {
        Events = App.Events;
      }
      if (typeof Utils === 'undefined' && typeof App.Utils !== 'undefined') {
        Utils = App.Utils;
      }

      // Set up global stuff and start.
      App.reset(true);
    },
  });
};

/**
 * Reset the environment.
 *
 * @param {Boolean} first
 *   Whether this is the first time App.reset() is being called (i.e. whether
 *   the app is being set up for the first time or not).
 * @member App
 * @static
 */
App.reset = function(first) {
  // Don't animate while we're resetting things.
  stopAnimating();

  // Stop dragging something if we were dragging when the game stopped...
  if (App.isSomethingBeingDragged) {
    App.isSomethingBeingDragged = false;
    jQuery(document).trigger('canvasdragstop');
  }

  // If the world already exists, reset it to the origin.
  if (typeof world !== 'undefined') {
    context.translate(world.xOffset, world.yOffset);
    world.scaleResolution(1);
  }

  // Set up the world.
  // Lots of drawing depends on the world size, so set this before anything
  // else and try not to change it.
  world = new World();
  context.__layer = world;

  /**
   * @property {Timer} timer
   *   Tracks time elapsed while the app is running and during each frame.
   *
   * **Warning:** The {@link Timer#getDelta delta} is retrieved each frame and
   * used to smooth animation. Tampering with this by calling Timer#getDelta()
   * or Timer#getElapsedTime() while animation is running will cause the next
   * frame to render less simulated time than expected. With animation stopped,
   * `App.timer.getElapsedTime()` will return how long the app has actually
   * been animating. You can also use `Timer.getTimeSince('app start')` to get
   * the total time since animation began. Normally what you actually want is
   * `App.physicsTimeElapsed`.
   *
   * @member App
   * @static
   * @ignore
   */
  App.timer = new Timer(false);
  App.timer.frames = 0; // The number of frames that have been painted.

  // Reset global flags
  App.isGameOver = false;
  App.physicsTimeElapsed = 0;
  App.Debug.updateTimeElapsed = 0;
  App.Debug.clearTimeElapsed = 0;
  App.Debug.drawTimeElapsed = 0;

  // Run the developer's setup code.
  var start = setup(!!first);

  // Start animating!
  Timer.event('app start');
  if (start !== false) {
    startAnimating();
  }

  /**
   * @event start
   *   Fires on the document when all setup is complete.
   *
   * Unless specified otherwise by the return value of setup(), animation
   * will have started by this point.
   *
   * Useful for running intro graphics.
   *
   * @param {Boolean} again
   *   true if the app has been reset and is starting over; false the first
   *   time it is being set up.
   * @member global
   */
  jQuery(document).trigger('start', [!!first]);
};

/**
 * Set the default size of the canvas as early as possible.
 *
 * The canvas is resized according to the following rules, in order of
 * precedence:
 *
 * - The browser will first automatically set the canvas to the size specified
 *   by its `width` and `height` attributes. If the canvas element has a
 *   `data-resize` attribute set to `false`, processing stops here and no more
 *   resizing rules are applied.
 * - If the canvas element has a `data-resize` attribute set to `full`, it will
 *   be resized to the maximum size that fits within the browser window.
 * - If the canvas element has `data-minwidth` or `data-minheight` attributes
 *   (with numeric values in pixels) it will not be scaled smaller than those
 *   dimensions.
 * - If the canvas element has `data-maxwidth` or `data-maxheight` attributes
 *   (with numeric values in pixels) it will not be scaled larger than those
 *   dimensions.
 * - The canvas will scale to the largest size that fits within both the window
 *   and the max attributes (if they are present).
 * - In all cases except when `data-resize="false"`, the `data-aspectratio`
 *   attribute takes effect if present. This causes the canvas to resize to the
 *   largest possible size within the boundaries of the size calculated from
 *   the other rules, while still maintaining the specified aspect ratio. The
 *   value of this attribute can be any floating point number or one of the
 *   common ratios "4:3", "16:9", "16:10", or "8:5". For example, with an
 *   aspect ratio of 4:3, if the width was calculated to be 1024 and the height
 *   was calculated to be 800, the height would be adjusted to 768.
 *
 * Note that using CSS to resize the canvas causes it to scale the graphics as
 * well; it changes the size of each virtual "pixel" on the canvas rather than
 * changing the number of pixels inside the canvas. Unless you want to stretch
 * the canvas display, using CSS to resize the canvas is not recommended. (If
 * you do want to stretch the canvas display, for example to zoom in or reduce
 * the number of pixels to render, take a look at World#scaleResolution.)
 *
 * @member App
 * @static
 */
App.setDefaultCanvasSize = function() {
  // Set the canvas size in CSS. This fixes the size of the canvas on the
  // screen, so from here forward if we change canvas.width or canvas.height
  // (for example, using World#scaleResolution) we will actually be changing
  // the render size; and then CSS will scale the rendered buffer to the
  // display size. (That's right; the width/height attributes mean something
  // different than the width/height CSS properties.) We're also setting the
  // scale3d transform which enables GPU rendering.
  var ensureCanvasSize = function() {
    $canvas.css({
      // Force hardware accelerated rendering
      transform: 'scale3d(1, 1, 1)',
      mozTransform: 'scale3d(1, 1, 1)',
      msTransform: 'scale3d(1, 1, 1)',
      oTransform: 'scale3d(1, 1, 1)',
      webkitTransform: 'scale3d(1, 1, 1)',
    });
  };
  // Do not resize if data-resize is false (fall back to CSS).
  if ($canvas.attr('data-resize') == 'false') {
    ensureCanvasSize();
    return;
  }
  var $window = jQuery(window);
  // If requested, make the canvas the size of the browser window.
  if ($canvas.attr('data-resize') == 'full') {
    canvas.width = $window.innerWidth() -
      parseInt($canvas.css('border-left-width'), 10) -
      parseInt($canvas.css('border-right-width'), 10) - 1;
    canvas.height = $window.innerHeight() -
      parseInt($canvas.css('border-top-width'), 10) -
      parseInt($canvas.css('border-bottom-width'), 10) - 1;
  }
  // Use the following properties to determine canvas size automatically:
  // width, height, data-minwidth, data-maxwidth, data-minheight, data-maxheight
  // If the width and height are not explicitly specified, the canvas is
  // resized to the largest size that fits within the max and the window, with
  // a minimum of min.
  else {
    var maxWidth = $canvas.attr('data-maxwidth') || canvas.width;
    var minWidth = $canvas.attr('data-minwidth') || canvas.width;
    canvas.width = Math.min(maxWidth, Math.max($window.width(), minWidth));
    var maxHeight = $canvas.attr('data-maxheight') || canvas.height;
    var minHeight = $canvas.attr('data-minheight') || canvas.height;
    canvas.height = Math.min(maxHeight, Math.max($window.height(), minHeight));
  }
  var aspectRatio = $canvas.attr('data-aspectratio') || 0;
  if (aspectRatio) {
    if (aspectRatio.indexOf(':') == -1) {
      aspectRatio = parseFloat(aspectRatio);
    }
    else {
      if (aspectRatio == '4:3') aspectRatio = 4/3;
      else if (aspectRatio == '16:9') aspectRatio = 16/9;
      else if (aspectRatio == '16:10' || aspectRatio == '8:5') aspectRatio = 1.6;
    }
    if (canvas.width < canvas.height * aspectRatio) {
      canvas.height = Math.floor(canvas.width / aspectRatio);
    }
    else if (canvas.width > canvas.height * aspectRatio) {
      canvas.width = Math.floor(canvas.height * aspectRatio);
    }
  }
  ensureCanvasSize();
};

// CACHES ---------------------------------------------------------------------

/**
 * Tracks cached items.
 * @static
 */
var Caches = {
    /**
     * A map from image file paths to Image objects.
     * @static
     */
    images: {},
    /**
     * A map from image file paths to CanvasPattern objects.
     * @static
     */
    imagePatterns: {},
    /**
     * Preload a list of images asynchronously.
     *
     * @param {String[]} files
     *   An Array of paths to images to preload.
     * @param {Object} [options]
     *   A map of options for this function.
     * @param {Function} [options.finishCallback]
     *   A function to run when all images have finished loading.
     * @param {Number} [options.finishCallback.numLoaded]
     *   The number of images that were preloaded.
     * @param {Function} [options.itemCallback]
     *   A function to run when an image has finished loading.
     * @param {String} [options.itemCallback.filepath]
     *   The file path of the loaded image.
     * @param {Number} [options.itemCallback.numLoaded]
     *   The number of images that have been loaded so far (including the
     *   current one).
     * @param {Number} [options.itemCallback.numImages]
     *   The total number of images to load.
     *
     * @static
     */
    preloadImages: function(files, options) {
      var l = files.length, m = -1, src, image;
      var notifyLoaded = function(itemCallback, src) {
        m++;
        if (typeof itemCallback == 'function') {
          itemCallback(src, m, l);
        }
        if (m == l && typeof options.finishCallback == 'function') {
          options.finishCallback(l);
        }
      };
      notifyLoaded();
      var onload = function() {
        Caches.images[this._src] = this;
        notifyLoaded(options.itemCallback, this._src);
      };
      while (files.length) {
        src = files.pop();
        image = new Image();
        image._num = l-files.length;
        image._src = src;
        image.onload = onload;
        image.src = src;
      }
    },
};

// Override the Sprite caching mechanisms.
if (typeof Sprite !== 'undefined') {
  Sprite.getImageFromCache = function(src) {
    return Caches.images[src];
  };
  Sprite.saveImageToCache = function(src, image) {
    Caches.images[src] = image;
  };
  Sprite.preloadImages = Caches.preloadImages;
}

// ANIMATION ------------------------------------------------------------------

// requestAnimationFrame shim for smooth animation
window.requestAnimFrame = (function() {
    return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function(callback) {
        window.setTimeout(callback, 1000 / 60);
    };
})();

(function() {
  // Indicates whether the canvas is animating or focused.
  var _animate = false, _blurred = false;

/**
 * Start animating the canvas.
 *
 * See also {@link global#method-stopAnimating stopAnimating()} and
 * {@link global#method-isAnimating isAnimating()}.
 *
 * @member global
 */
window.startAnimating = function() {
  if (!_animate && typeof App.timer !== 'undefined') {
    _animate = true;
    /**
     * @event startAnimating
     *   Fires on the document when the animation loop is about to begin.
     * @member global
     */
    jQuery(document).trigger('startAnimating');
    animate();
  }
};

/**
 * Stop animating the canvas.
 *
 * See also {@link global#method-startAnimating startAnimating()} and
 * {@link global#method-isAnimating isAnimating()}.
 *
 * @member global
 */
window.stopAnimating = function() {
  if (!_animate || typeof App.timer === 'undefined') {
    return;
  }
  _animate = false;
  // Although stopAnimating() can be called asynchronously from the game loop,
  // this is effectively the same as stopping the timer immediately after the
  // last call to App.timer.getDelta() (i.e. when the last frame was rendered)
  // because nothing interesting happens after that, and when animation is
  // started again the timer doesn't start until the first frame is requested
  // so the delta is still correct.
  App.timer.stop();
  /**
   * @event stopAnimating
   *   Fires on the document when the animation loop is ending.
   * @member global
   */
  jQuery(document).trigger('stopAnimating');

  // Output performance statistics.
  // This can be slightly inaccurate because this function can be called in the
  // middle of updating a frame, rather than after the last frame is drawn.
  if (App.debugMode && console && console.log) {
    var elapsed = App.physicsTimeElapsed, frames = App.timer.frames, d = App.Debug;
    var sum = d.updateTimeElapsed + d.clearTimeElapsed + d.drawTimeElapsed;
    console.log({
      'ms/frame': {
        update: d.updateTimeElapsed / frames,
        clear: d.clearTimeElapsed / frames,
        draw: d.drawTimeElapsed / frames,
        animOps: sum / frames,
        other: (elapsed - sum) / frames,
        animate: elapsed / frames,
      },
      percent: {
        update: d.updateTimeElapsed / elapsed * 100,
        clear: d.clearTimeElapsed / elapsed * 100,
        draw: d.drawTimeElapsed / elapsed * 100,
        animOps: sum / elapsed * 100,
        other: (elapsed - sum) / elapsed * 100,
      },
      fps: frames / elapsed,
    });
  }
};

/**
 * Determine whether the canvas is currently animating.
 *
 * See also {@link global#method-startAnimating startAnimating()} and
 * {@link global#method-stopAnimating stopAnimating()}.
 *
 * @member global
 */
window.isAnimating = function() {
  return _animate;
};

/**
 * Animate the canvas. This is intended for private use and should not be
 * called directly. Instead see {@link #startAnimating} and
 * {@link #stopAnimating}.
 *
 * @member App
 * @ignore
 */
function animate() {
  if (typeof App.stats !== 'undefined') {
    App.stats.begin();
  }

  // Record the amount of time since the last tick. Used to smooth animation.
  // This is the only place that App.timer.getDelta() should ever be called
  // because getDelta() returns the time since the last time it was called so
  // calling it elsewhere will skew the result here.
  var frameDelta = App.timer.getDelta();
  // Starts the timer in the first animation frame. Doing this here instead of
  // in startAnimating() lets us get the delta right for the first frame.
  if (!App.timer.running) {
    App.timer.start();
  }
  // Cap the delta in order to avoid the spiral of death.
  if (App.MIN_FPS && frameDelta > 1 / App.MIN_FPS) {
    /**
     * @event low_fps
     *   Fires on the document when the frame rate drops below App.MIN_FPS.
     *
     * @param {Number} fps The frame-per-second rate.
     *
     * @member global
     */
    jQuery(document).trigger('low_fps', [1 / frameDelta]);
    frameDelta = 1 / App.MIN_FPS;
  }
  // Count paints so we can calculate overall FPS
  App.timer.frames++;

  if (App.debugMode) {
    Timer.event('debug timer update', true);
  }

  // update
  if (window.Mouse && Mouse.Scroll) {
    Mouse.Scroll._update();
  }
  while (frameDelta > 0 && !App.isGameOver) {
    // Break the physics updates down into discrete chunks of no more than
    // 1 / App.MAX_FPS in order to keep them as small as possible for accuracy.
    /**
     * @property physicsDelta
     *   The amount of simulated time in seconds since the last physics update.
     *
     * Use this to smooth animation.
     *
     * @member App
     * @static
     */
    App.physicsDelta = Math.min(frameDelta, 1 / App.MAX_FPS);
    if (typeof update == 'function') {
      update(App.physicsDelta, App.physicsTimeElapsed);
    }
    frameDelta -= App.physicsDelta;
    App.physicsTimeElapsed += App.physicsDelta;
  }

  if (App.debugMode) {
    App.Debug.updateTimeElapsed += Timer.getTimeSince('debug timer update');
    Timer.event('debug timer clear', true);
  }

  // clear
  context.clear();

  if (App.debugMode) {
    App.Debug.clearTimeElapsed += Timer.getTimeSince('debug timer clear');
    Timer.event('debug timer draw', true);
  }

  // draw
  draw();

  if (App.debugMode) {
    App.Debug.drawTimeElapsed += Timer.getTimeSince('debug timer draw');
  }

  if (typeof App.stats !== 'undefined') {
    App.stats.end();
  }

  // request new frame
  if (_animate) {
    window.requestAnimFrame(animate);
  }
}

/**
 * Stops animating when the window (tab) goes out of focus.
 *
 * This stops running the CPU when we don't need it, but it can cause
 * unexpected behavior if you expect something to be running in the background
 * while focus is not on the browser. If you don't want this behavior, you can
 * toggle it off with `$(window).off('.animFocus');`
 *
 * @ignore
 */
jQuery(window).on('focus.animFocus', function() {
  if (_blurred && !App.isGameOver) {
    _blurred = false;
    startAnimating();
  }
});
jQuery(window).on('blur.animFocus', function() {
  stopAnimating();
  _blurred = true;
});

})();

// INPUT ----------------------------------------------------------------------

/**
 * Prevent the default behavior from occurring when hitting keys.
 *
 * This won't prevent everything -- for example it won't prevent combinations
 * of multiple non-control-character keys -- and if you want to do something
 * like prevent the default effect of hitting Enter but not Shift+Enter then
 * you need to handle that yourself.
 *
 * Behavior of the keys specified in the `keys` variable are automatically
 * prevented by default.
 *
 * @param {String} combinations
 *   A string containing key combinations to prevent. See
 *   {@link jQuery.hotkeys} for an explanation of what key combinations are
 *   acceptable.
 *
 * @member App
 * @static
 */
App.preventDefaultKeyEvents = function(combinations) {
  if (jQuery.hotkeys) {
    jQuery(document).keydown(combinations, function() { return false; });
  }
  else if (window.console && console.warn) {
    console.warn('Tried to prevent default key events but jQuery.hotkeys does not exist.');
  }
};

// TIMER ----------------------------------------------------------------------

// performance.now() shim
window.performance = window.performance || {};
performance.now = (function() {
  return performance.now       ||
         performance.mozNow    ||
         performance.msNow     ||
         performance.oNow      ||
         performance.webkitNow ||
         function() { return Date.now(); };
})();

/**
 * A timer.
 *
 * Adapted from the
 * [three.js clock](https://github.com/mrdoob/three.js/blob/master/src/core/Clock.js).
 *
 * If you only care about how long something takes (e.g. when testing
 * performance) and you don't need to stop the timer, Timer#event() and
 * Timer#getTimeSince() are more efficient than instantiating a new Timer
 * object.
 *
 * @param {Boolean} [autoStart=true]
 *   Whether to start the timer immediately upon instantiation or wait until
 *   the Timer#start() method is called.
 * @param {Boolean} [whileAnimating=false]
 *   Indicates whether the amount of time measured should be the time elapsed
 *   only while animation was running (true) or the total time elapsed (false).
 */
function Timer(autoStart, whileAnimating) {
  this.autoStart = typeof autoStart === 'undefined' ? true : autoStart;
  this.whileAnimating = whileAnimating || false;
  this.lastStartTime = 0;
  this.lastDeltaTime = 0;
  this.elapsedTime = 0;
  this.running = false;
  /**
   * Get the time elapsed in seconds since the last time a delta was measured.
   *
   * Deltas are taken when the timer starts or stops or elapsed time is
   * measured.
   *
   * Note that if the timer is stopped and autoStart is on, calling this method
   * will start the timer again.
   */
  this.getDelta = function() {
    var diff = 0;
    if (this.running) {
      var now = this.whileAnimating ? App.physicsTimeElapsed : performance.now();
      diff = (now - this.lastDeltaTime) / 1000; // ms to s
      this.lastDeltaTime = now;
      this.elapsedTime += diff;
    }
    return diff;
  };
  /**
   * Start the timer.
   */
  this.start = function() {
    if (this.running) {
      return;
    }
    this.lastStartTime = this.lastDeltaTime = this.whileAnimating ?
        App.physicsTimeElapsed :
          performance.now();
    this.running = true;
  };
  /**
   * Stop the timer.
   */
  this.stop = function () {
    this.elapsedTime += this.getDelta();
    this.running = false;
  };
  /**
   * Get the amount of time the timer has been running, in seconds.
   */
  this.getElapsedTime = function() {
    this.getDelta();
    return this.elapsedTime;
  };
  if (this.autoStart) {
    this.start();
  }
}

(function() {
  var events = {}, noID = 0;
  /**
   * Register the time at which an event occurred.
   *
   * If you only care about how long something takes (e.g. when testing
   * performance) and you don't need to stop the timer, Timer#event() and
   * Timer#getTimeSince() are more efficient than instantiating a new Timer
   * object.
   *
   * @param {String} [id]
   *   An identifier for the event.
   * @param {Boolean} [whileAnimating=false]
   *   Indicates whether the amount of time measured between when Timer#event()
   *   is called and when Timer#getTimeSince() is called should be the time
   *   elapsed only while animation was running (true) or the total time
   *   elapsed (false).
   * @static
   */
  Timer.event = function(id, whileAnimating) {
    var result = {
        whileAnimating: whileAnimating,
        startTime: whileAnimating ? App.physicsTimeElapsed : performance.now(),
    };
    if (typeof id === 'undefined') {
      noID = result;
    }
    else {
      events[id] = result;
    }
  };
  /**
   * Return the time since an event occurred.
   *
   * If you only care about how long something takes (e.g. when testing
   * performance) and you don't need to stop the timer, Timer#event() and
   * Timer#getTimeSince() are more efficient than instantiating a new Timer
   * object.
   *
   * @param {String} [id] An identifier for the event.
   * @return {Number} Seconds since the event, or 0 if the event is not found.
   * @static
   */
  Timer.getTimeSince = function(id) {
    var event = typeof events[id] === 'undefined' ? noID : events[id];
    var now = event.whileAnimating ? App.physicsTimeElapsed : performance.now();
    return event.startTime ? (now - event.startTime) / 1000 : 0;
  };
})();

// WORLD ----------------------------------------------------------------------

/**
 * The World object.
 *
 * The World represents the complete playable game area. Its size can be set
 * explicitly or is automatically determined by the "data-worldwidth" and
 * "data-worldheight" attributes set on the HTML canvas element (with a
 * fallback to the canvas width and height). If the size of the world is larger
 * than the canvas then by default the view of the world will scroll when the
 * {@link global#player player} approaches a side of the canvas.
 *
 * @param {Number} [w]
 *   The width of the world. Defaults to the value of the "data-worldwidth"
 *   attribute on the HTML canvas element, or (if that attribute is not
 *   present) the width of the canvas element.
 * @param {Number} [h]
 *   The height of the world. Defaults to the value of the "data-worldheight"
 *   attribute on the HTML canvas element, or (if that attribute is not
 *   present) the height of the canvas element.
 */
function World(w, h) {
  /**
   * @property {Number} scale
   *   The percent amount (as a fraction) the canvas resolution is linearly
   *   scaled relative to its original size. For example, if the canvas started
   *   at 1024x768 and it is now rendering at 512x384, then the scale will be
   *   0.5. Notice that the number of virtual pixels rendered on the canvas
   *   changes with the square of the scale.
   */
  this.scale = 1;
  /**
   * @property {Number} _actualXscale
   *   The actual amount the width of the canvas is scaled relative to the
   *   display size.
   *
   * This can be different than `this.scale` due to rounding error. This
   * matters when converting document coordinates to canvas coordinates, for
   * example when tracking mouse coordinates.
   * @ignore
   */
  this._actualXscale = 1;
  /**
   * @property {Number} _actualYscale
   *   The actual amount the height of the canvas is scaled relative to the
   *   display size.
   *
   * This can be different than `this.scale` due to rounding error. This
   * matters when converting document coordinates to canvas coordinates, for
   * example when tracking mouse coordinates.
   * @ignore
   */
  this._actualYscale = 1;

  /**
   * @property {Number} width
   *   The width of the world.
   */
  this.width = w || parseInt($canvas.attr('data-worldwidth'), 10) || canvas.width;
  /**
   * @property {Number} height
   *   The height of the world.
   */
  this.height = h || parseInt($canvas.attr('data-worldheight'), 10) || canvas.height;

  /**
   * @property {Number} originalCanvasWidth
   *   The width of the canvas when the world was initialized.
   *
   * This is used to keep the canvas size consistent when scaling resolution.
   * @ignore
   */
  this.originalCanvasWidth = canvas.width;
  /**
   * @property {Number} originalCanvasHeight
   *   The height of the canvas when the world was initialized.
   *
   * This is used to keep the canvas size consistent when scaling resolution.
   * @ignore
   */
  this.originalCanvasHeight = canvas.height;

  /**
   * @property {Number} xOffset
   *   The pixel-offset of what's being displayed in the canvas compared to the
   *   world origin.
   */
  this.xOffset = (this.width - canvas.width)/2;
  /**
   * @property {Number} yOffset
   *   The pixel-offset of what's being displayed in the canvas compared to the
   *   world origin.
   */
  this.yOffset = (this.height - canvas.height)/2;
  context.translate(-this.xOffset, -this.yOffset);

  /**
   * Resize the world to new dimensions.
   *
   * Careful! This will shift the viewport regardless of where the player is.
   * Objects already in the world will retain their coordinates and so may
   * appear in unexpected locations on the screen.
   *
   * @param {Number} newWidth The new width to which to resize the world.
   * @param {Number} newHeight The new height to which to resize the world.
   */
  this.resize = function(newWidth, newHeight) {
    // Try to re-center the offset of the part of the world in the canvas
    // so we're still looking at approximately the same thing.
    var deltaX = (newWidth - this.width) / 2, deltaY = (newHeight - this.height) / 2;
    this.xOffset += deltaX;
    this.yOffset += deltaY;
    context.translate(-deltaX, -deltaY);

    // Change the world dimensions.
    this.width = newWidth;
    this.height = newHeight;

    /**
     * @event resizeWorld
     *   Broadcast that the world size changed so that objects already in the
     *   world or other things that depend on the world size can update their
     *   position or size accordingly.
     * @param {Number} x How far in pixels the viewport shifted horizontally.
     * @param {Number} y How far in pixels the viewport shifted vertically.
     * @param {World} resizedWorld The world that changed size.
     */
    jQuery(document).trigger('resizeWorld', [deltaX, deltaY, this]);
  };

  /**
   * Scale the resolution of the primary canvas.
   *
   * Passing a factor smaller than 1 allows reducing the resolution of the
   * canvas, which should improve performance (since there is less to render in
   * each frame). It does not actually change the size of the canvas on the
   * page; it just scales how big each "pixel" is drawn on the canvas, much
   * like changing the resolution of your monitor does not change its physical
   * size. It is your responsibility to change the size of any fixed-size
   * entities in the world after resizing, if applicable; if you don't do this,
   * calling this function works much like zooming in or out.
   *
   * Note that this method only scales the primary canvas, not any
   * {@link Layer}s that may be drawn onto it. Drawing non-scaled Layers onto a
   * scaled primary canvas will make the result appear scaled, but the spacing
   * may be off depending on how you are positioning the items you are drawing
   * onto the relevant Layers. Of course, you can manually resize your Layers
   * after scaling, or only scale the primary canvas during initalization and
   * then create all Layers subsequently at appropriate sizes, or make Layers
   * be positioned over the canvas using {@link Layer#positionOverCanvas}().
   *
   * You may want to call this in a listener for the
   * {@link global#low_fps Low FPS event}.
   *
   * @param {Number} factor
   *   The percent amount to scale the resolution on each dimension as a
   *   fraction of the <em>original</em> resolution (typically between zero and
   *   one). In other words, if the original resolution is 1024x768, scaling
   *   the resolution by a factor of 0.5 will result in a resolution of 512x384
   *   (showing 25% as many pixels on the screen). If scaled again by a factor
   *   of 2, the result will be 2048x1536. Use the `scale` property to detect
   *   the factor by which the resolution is currently scaled.
   * @param {Number} [x]
   *   The x-coordinate of a location to center the viewport around after
   *   resizing the canvas. A common use is `player.x`. Defaults to centering
   *   around the current view.
   * @param {Number} [y]
   *   The y-coordinate of a location to center the viewport around after
   *   resizing the canvas. A common use is `player.y`. Defaults to centering
   *   around the current view.
   */
  this.scaleResolution = function(factor, x, y) {
    // Define the display size so we can rescale the rendering size
    if (!$canvas.hasClass('canvas-rescaled')) {
      $canvas.css({
        width: canvas.width + 'px',
        height: canvas.height + 'px',
      });
      $canvas.addClass('canvas-rescaled');
    }
    // Default to centering around the current center of the viewport.
    x = x || this.xOffset + canvas.width / 2;
    y = y || this.yOffset + canvas.height / 2;
    // The original canvas size is pre-set using App.setDefaultCanvasSize() so
    // changing the value of the attributes just changes the number of pixels
    // to render. CSS then scales that buffer to the original canvas size.
    canvas.width = Math.round(this.originalCanvasWidth*factor);
    canvas.height = Math.round(this.originalCanvasHeight*factor);
    this._actualXscale = canvas.width / this.originalCanvasWidth;
    this._actualYscale = canvas.height / this.originalCanvasHeight;
    this.xOffset = Math.round(Math.min(this.width - canvas.width, Math.max(0, x - canvas.width / 2)));
    this.yOffset = Math.round(Math.min(this.height - canvas.height, Math.max(0, y - canvas.height / 2)));
    context.translate(-this.xOffset, -this.yOffset);
    this.scale = factor;
    if (!isAnimating()) {
      draw();
    }
  };

  /**
   * Convert a position over the canvas to a position in the world.
   *
   * The canvas position being converted should be in displayed pixels from the
   * upper-left corner of the canvas.
   *
   * This could be useful when placing objects at a given location in the
   * canvas if you need the object to align with something outside of the
   * canvas. (Note that {@link Mouse.Coords} already has worldX() and worldY()
   * methods for representing the mouse position in the world.)
   *
   * @param {Number} x The x-position over the canvas to convert.
   * @param {Number} y The y-position over the canvas to convert.
   *
   * @return {Object}
   *   An object with `x` and `y` properties representing x- and y-coordinates
   *   in the world.
   */
  this.canvasToWorldPosition = function(x, y) {
    return {
      x: x * this._actualXscale + this.xOffset,
      y: y * this._actualYscale + this.yOffset,
    };
  };
  /**
   * Convert a position in the world to a position over the canvas.
   *
   * The returned canvas position will be in displayed pixels from the
   * upper-left corner of the canvas. This is useful when positioning DOM
   * elements over the canvas, for example with
   * {@link App.Utils#positionOverCanvas}.
   *
   * If you instead want the position relative to the rendered canvas, which
   * may be useful for example if you want to transition something from
   * scrolling to fixed position, you should instead use `x - world.xOffset`
   * and `y - world.yOffset`. The difference between displayed and rendered
   * position is only relevant when the canvas has been scaled with
   * {@link World#scaleResolution}, causing the canvas to be rendered at one
   * size and then scaled to another for display using CSS.
   *
   * @param {Number} x The x-position over the canvas to convert.
   * @param {Number} y The y-position over the canvas to convert.
   *
   * @return {Object}
   *   An object with `x` and `y` properties representing x- and y-coordinates
   *   over the canvas.
   */
  this.worldToCanvasPosition = function(x, y) {
    return {
      x: (x - this.xOffset) / this._actualXscale,
      y: (y - this.yOffset) / this._actualYscale,
    };
  };

  /**
   * Center the viewport around a specific location in the world.
   *
   * @param {Number} x The x-coordinate around which to center the viewport.
   * @param {Number} y The y-coordinate around which to center the viewport.
   */
  this.centerViewportAround = function(x, y) {
    var newXOffset = Math.min(this.width - canvas.width, Math.max(0, x - canvas.width / 2)) | 0,
        newYOffset = Math.min(this.height - canvas.height, Math.max(0, y - canvas.height / 2)) | 0,
        deltaX = this.xOffset - newXOffset,
        deltaY = this.yOffset - newYOffset;
    this.xOffset = newXOffset;
    this.yOffset = newYOffset;
    context.translate(deltaX, deltaY);
  };

  /**
   * Determine whether a Box is inside the viewport.
   *
   * To test whether a Box is inside the World, see World#isInWorld().
   *
   * @param {Box} box
   *   The Box object to check for visibility.
   * @param {Boolean} [partial=false]
   *   Indicates whether to consider the Box inside the viewport if it is only
   *   partially inside (true) or fully inside (false).
   *
   * @return {Boolean}
   *   true if the Box is inside the viewport; false otherwise.
   */
  this.isInView = function(box, partial) {
    if (partial) {
      return box.x + box.width > this.xOffset &&
        box.x < this.xOffset + canvas.width &&
        box.y + box.height > this.yOffset &&
        box.y < this.yOffset + canvas.height;
    }
    return box.x > this.xOffset &&
      box.x + box.width < this.xOffset + canvas.width &&
      box.y > this.yOffset &&
      box.y + box.height < this.yOffset + canvas.height;
  };

  /**
   * Determine whether a Box is inside the world.
   *
   * To test whether a Box is inside the viewport, see World#isInView().
   *
   * @param {Box} box
   *   The Box object to check.
   * @param {Boolean} [partial=false]
   *   Indicates whether to consider the box inside the world if it is only
   *   partially inside (true) or fully inside (false).
   *
   * @return {Boolean}
   *   true if the Box is inside the world; false otherwise.
   */
  this.isInWorld = function(box, partial) {
    if (partial) {
      return box.x + box.width >= 0 && box.x <= world.width &&
        box.y + box.height >= 0 && box.y <= world.height;
    }
    return box.x >= 0 && box.x + box.width <= world.width &&
      box.y >= 0 && box.y + box.height <= world.height;
  };
}

// UTILITIES ------------------------------------------------------------------

/**
 * @class App.Utils
 *   A miscellaneous collection of utilities.
 * @alternateClassName Utils
 * @static
 */
App.Utils = {};

/**
 * Get a random number between two numbers.
 *
 * @param {Number} lo The first number.
 * @param {Number} hi The second number.
 * @return {Number} A random number between lo and hi.
 * @static
 */
App.Utils.getRandBetween = function(lo, hi) {
  if (lo > hi) {
    var t = lo;
    lo = hi;
    hi = t;
  }
  return Math.random() * (hi - lo) + lo;
};

/**
 * Get a random integer between two numbers, inclusive.
 *
 * This function makes no assumptions; despite the parameters being called lo
 * and hi, either one can be higher, and either or both can be integers or
 * floats. If either of the numbers is a float, the random distribution remains
 * equal among eligible integers; that is, if lo==3.3 and hi==5, 4 and 5 are
 * equally likely to be returned. Behavior is undefined if there is no integer
 * between lo and hi. Negative numbers work as well.
 *
 * @param {Number} lo The first number.
 * @param {Number} hi The second number.
 * @return {Number} A random integer between lo and hi.
 * @static
 */
App.Utils.getRandIntBetween = function(lo, hi) {
  if (lo > hi) {
    var t = lo;
    lo = hi;
    hi = t;
  }
  lo = Math.ceil(lo);
  hi = Math.floor(hi);
  return Math.floor(Math.random()*(hi-lo+1)+lo);
};

/**
 * Check if any of the elements in an Array are found in another Array.
 *
 * @param {Array} search
 *   An Array of elements to search for in the target.
 * @param {Array} target
 *   An Array in which to search for matching elements.
 *
 * @return {Boolean}
 *   true if at least one match was found; false otherwise.
 *
 * @static
 */
App.Utils.anyIn = function(search, target) {
  for (var i = 0, l = search.length; i < l; i++) {
    if (target.indexOf(search[i]) != -1) {
      return true;
    }
  }
  return false;
};

/**
 * Determine whether a is within e of b, inclusive.
 *
 * @param {Number} a
 * @param {Number} b
 * @param {Number} e
 * @static
 */
App.Utils.almostEqual = function(a, b, e) {
  // Another (slower) way to express this is Math.abs(a - b) < e
  return a >= b - e && a <= b + e;
};

/**
 * Generate a random URL-safe string of arbitrary length.
 *
 * Useful for generating unique IDs.
 *
 * @param {Number} [n=32]
 *   The number of characters in the generated string.
 *
 * @return {String}
 *   A random string n characters long.
 *
 * @static
 */
App.Utils.randomString = function(n) {
  var c = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_',
      s = '';
  n = n || 32;
  for (var i = 0; i < n; i++) {
    s += c.charAt(Math.floor(Math.random() * c.length));
  }
  return s;
};

/**
 * Position a DOM element at a specific location over the canvas.
 *
 * Useful for placing forms, text, menus, and other UI elements that are more
 * easily handled in HTML.
 *
 * To place the DOM element at a specific location in the world, convert the
 * position in the world to a position over the canvas using
 * {@link World#worldToCanvasPosition}.
 *
 * @param {HTMLElement} elem
 *   A DOM element or jQuery representation of a DOM element to position over
 *   the canvas.
 * @param {Number} x
 *   The number of pixels to the right of the upper-left corner of the canvas
 *   at which to locate the DOM element.
 * @param {Number} y
 *   The number of pixels below the upper-left corner of the canvas at which to
 *   locate the DOM element.
 *
 * @return {Object}
 *   The jQuery representation of the positioned DOM element.
 *
 * @static
 */
App.Utils.positionOverCanvas = function(elem, x, y) {
  var o = $canvas.offset();
  return jQuery(elem).detach().appendTo('body').css({
    left: (o.left + parseInt($canvas.css('border-left-width'), 10) + x) + 'px',
    position: 'absolute',
    top: (o.top + parseInt($canvas.css('border-top-width'), 10) + y) + 'px',
    'z-index': 100,
  });
};

/**
 * End the game, display a message, and allow clicking to restart.
 *
 * To disable clicking to restart, run `$canvas.off('.gameover');`
 *
 * @param {String} [text="GAME OVER"]
 *   Text to overlay on the screen.
 *
 * @member App
 * @static
 */
App.gameOver = function(text) {
  if (App.isGameOver) {
    return;
  }
  App.isGameOver = true;
  stopAnimating();
  if (player) {
    player.destroy();
  }
  if (typeof text != 'string') {
    text = "GAME OVER";
  }
  // This runs during update() before the final draw(), so we have to delay it.
  setTimeout(function() {
    context.save();
    context.font = '100px Arial';
    context.fillStyle = 'black';
    context.strokeStyle = 'lightGray';
    context.textBaseline = 'middle';
    context.textAlign = 'center';
    context.shadowColor = 'black';
    context.shadowBlur = 8;
    context.lineWidth = 5;
    var x = Math.round(world.xOffset+canvas.width/2);
    var y = Math.round(world.yOffset+canvas.height/2);
    context.strokeText(text, x, y);
    context.fillText(text, x, y);
    context.restore();
  }, 100);
  $canvas.css('cursor', 'pointer');
  $canvas.one('click.gameover', function(e) {
    e.preventDefault();
    $canvas.css('cursor', 'auto');
    App.reset();
  });
};

/** @class Array The built-in JavaScript Array class. */

/**
 * Remove an item from an array by value.
 *
 * @param {Mixed} item
 *   The item to remove from the array, if it exists.
 *
 * @return {Array}
 *   An array containing the removed elements, if any.
 *
 * @member Array
 */
Array.prototype.remove = function(item) {
  var i = this.indexOf(item);
  if (i === undefined || i < 0) {
    return undefined;
  }
  return this.splice(i, 1);
};

/** @class Number The built-in JavaScript Number class. */

/**
 * Round a number to a specified precision.
 *
 * Usage:
 *
 *     3.5.round(0) // 4
 *     Math.random().round(4) // 0.8179
 *     var a = 5532; a.round(-2) // 5500
 *     Number.prototype.round(12345.6, -1) // 12350
 *     32..round(-1) // 30 (two dots required since the first one is a decimal)
 *
 * @param {Number} v
 *   The number to round. (This parameter only applies if this function is
 *   called directly. If it is invoked on a Number instance, then that number
 *   is used instead, and only the precision parameter needs to be passed.)
 * @param {Number} a
 *   The precision, i.e. the number of digits after the decimal point
 *   (including trailing zeroes, even though they're truncated in the returned
 *   result). If negative, precision indicates the number of zeroes before the
 *   decimal point, e.g. round(1234, -2) yields 1200. If non-integral, the
 *   floor of the precision is used.
 *
 * @return {Number}
 *   `v` rounded to `a` precision.
 *
 * @member Number
 */
Number.prototype.round = function(v, a) {
  if (typeof a === 'undefined') {
    a = v;
    v = this;
  }
  if (!a) a = 0;
  var m = Math.pow(10, a|0);
  return Math.round(v*m)/m;
};

/**
 * Return the sign of a number.
 *
 * @param {Number} v
 *   The number whose sign should be retrieved.
 *
 * @return {Number}
 *   Returns 1 if the number is greater than zero, -1 if it is less than zero,
 *   and 0 if it is equal to zero.
 *
 * @member Number
 */
Number.prototype.sign = function(v) {
  if (typeof v === 'undefined') {
    v = this;
  }
  return v > 0 ? 1 : (v < 0 ? -1 : 0);
};

/**
 * Provides utilities to draw onto the canvas.
 *
 * @ignore
 */

// LAYER ----------------------------------------------------------------------

/**
 * The Layer object (basically a new, utility canvas).
 *
 * Layers allow efficient rendering of complex scenes by acting as caches for
 * parts of the scene that are grouped together. For example, it is recommended
 * to create a Layer for your canvas's background so that you can render the
 * background once and then draw the completely rendered background onto the
 * main canvas in each frame instead of re-computing the background for each
 * frame. This can significantly speed up animation.
 *
 * In general you should create a layer for any significant grouping of items
 * if that grouping moves together when animated. It is more memory-efficient
 * to specify a smaller layer size if possible; otherwise the layer will
 * default to the size of the whole canvas.
 *
 * Draw onto a Layer by using its "context" property, which is a
 * {@link CanvasRenderingContext2D canvas graphics context}.
 *
 * @param {Object} [options]
 *   A set of options.
 * @param {Number} [options.x=0]
 *   The x-coordinate of the top-left corner of the Layer.
 * @param {Number} [options.y=0]
 *   The y-coordinate of the top-left corner of the Layer.
 * @param {Number} [options.width]
 *   The width of the Layer.
 * @param {Number} [options.height]
 *   The height of the Layer.
 * @param {"world"/"canvas"} [options.relative="world"]
 *   Indicates what to draw the Layer relative to:
 *
 *   - 'world': Draw the layer relative to the world so that it will appear
 *     to be in one specific place as the player or viewport moves.
 *   - 'canvas': Draw the layer relative to the canvas so that it stays fixed
 *     as the player moves. This is useful for a HUD, for example.
 *
 *   This option is irrelevant if the world is the same size as the canvas.
 * @param {Number} [options.opacity=1]
 *   A fractional percentage [0, 1] indicating the opacity of the Layer.
 *   0 (zero) means fully transparent; 1 means fully opaque. This value is
 *   applied when {@link Layer#draw drawing} the layer.
 * @param {Number} [options.parallax=1]
 *   A fractional percentage indicating how much to {@link Layer#scroll scroll}
 *   the Layer relative to the viewport's movement.
 * @param {Mixed} [options.src]
 *   Anything that can be passed to the `src` parameter of
 *   {@link CanvasRenderingContext2D#drawImage drawImage()}. This will be used
 *   to draw an image stretched over the whole Layer as a convenience.
 * @param {HTMLElement} [options.canvas]
 *   A Canvas element in which to hold the Layer. If not specified, a new,
 *   invisible canvas is created. Careful; if width and height are specified,
 *   the canvas will be resized (and therefore cleared). This is mainly for
 *   internal use.
 */
function Layer(options) {
  options = options || {};
  /**
   * @property {HTMLElement} canvas
   *   The canvas backing the Layer.
   * @readonly
   */
  this.canvas = options.canvas || document.createElement('canvas');
  /**
   * @property {CanvasRenderingContext2D} context
   *   The Layer's graphics context. Use this to draw onto the Layer.
   * @readonly
   */
  this.context = this.canvas.getContext('2d');
  this.context.__layer = this;
  /**
   * @property {Number} width
   *   The width of the Layer.
   * @readonly
   */
  this.width = options.width || (options.canvas ? options.canvas.width : 0) || (options.relative == 'canvas' ? canvas.width : world.width);
  /**
   * @property {Number} height
   *   The height of the Layer.
   * @readonly
   */
  this.height = options.height || (options.canvas ? options.canvas.height : 0) || (options.relative == 'canvas' ? canvas.height : world.height);
  /**
   * @property {Number} x
   *   The x-coordinate on the {@link global#canvas global canvas} of the
   *   upper-left corner of the Layer.
   */
  this.x = options.x || 0;
  /**
   * @property {Number} y
   *   The y-coordinate on the {@link global#canvas global canvas} of the
   *   upper-left corner of the Layer.
   */
  this.y = options.y || 0;
  /**
   * @property {"world"/"canvas"} relative
   *   What to draw the Layer relative to.
   */
  this.relative = options.relative || 'world';
  /**
   * @property {Number} opacity
   *   A fractional percentage [0, 1] indicating the opacity of the Layer.
   *   0 (zero) means fully transparent; 1 means fully opaque. This value is
   *   applied when {@link Layer#draw drawing} the layer.
   */
  this.opacity = options.opacity || 1;
  /**
   * @property {Number} parallax
   *   A fractional percentage indicating how much to
   *   {@link Layer#scroll scroll} the Layer relative to the viewport's
   *   movement.
   */
  this.parallax = options.parallax || 1;
  if (this.canvas.width != this.width) {
    this.canvas.width = this.width;
  }
  if (this.canvas.height != this.height) {
    this.canvas.height = this.height;
  }
  /**
   * @property {Number} xOffset
   *   The horizontal distance in pixels that the Layer has
   *   {@link Layer#scroll scrolled}.
   */
  this.xOffset = 0;
  /**
   * @property {Number} yOffset
   *   The vertical distance in pixels that the Layer has
   *   {@link Layer#scroll scrolled}.
   */
  this.yOffset = 0;
  if (options.src) {
    this.context.drawImage(options.src, 0, 0, this.width, this.height);
  }
  /**
   * Draw the Layer.
   *
   * This method can be invoked in two ways:
   *
   * - `draw(x, y)`
   * - `draw(ctx, x, y)`
   *
   * All parameters are optional either way.
   *
   * @param {CanvasRenderingContext2D} [ctx]
   *   A canvas graphics context onto which this Layer should be drawn. This is
   *   useful for drawing onto other Layers. If not specified, defaults to the
   *   {@link global#context global context} for the default canvas.
   * @param {Number} [x]
   *   An x-coordinate on the canvas specifying where to draw the upper-left
   *   corner of the Layer. The actual position that the coordinate equates to
   *   depends on the value of the
   *   {@link Layer#relative Layer's "relative" property}. Defaults to the
   *   {@link Layer#x Layer's "x" property} (which defaults to 0 [zero]).
   * @param {Number} [y]
   *   A y-coordinate on the canvas specifying where to draw the upper-left
   *   corner of the Layer. The actual position that the coordinate equates to
   *   depends on the value of the
   *   {@link Layer#relative Layer's "relative" property}. Defaults to the
   *   {@link Layer#y Layer's "y" property} (which defaults to 0 [zero]).
   */
  this.draw = function(ctx, x, y) {
    if (!(ctx instanceof CanvasRenderingContext2D)) {
      y = x;
      x = ctx;
      ctx = context;
    }
    x = typeof x === 'undefined' ? this.x : x;
    y = typeof y === 'undefined' ? this.y : y;
    ctx.save();
    ctx.globalAlpha = this.opacity;
    if (this.relative == 'canvas') {
      ctx.translate(world.xOffset, world.yOffset);
    }
    if (this.xOffset || this.yOffset) {
      ctx.translate(this.xOffset, this.yOffset);
    }
    ctx.drawImage(this.canvas, x, y);
    ctx.restore();
    return this;
  };
  /**
   * Clear the layer, optionally by filling it with a given style.
   *
   * @param {Mixed} [fillStyle]
   *   A canvas graphics context fill style. If not passed, the Layer will
   *   simply be cleared. If passed, the Layer will be filled with the given
   *   style.
   */
  this.clear = function(fillStyle) {
    this.context.clear(fillStyle);
    return this;
  };
  /**
   * Scroll the Layer.
   *
   * @param {Number} x
   *   The horizontal distance the target has shifted.
   * @param {Number} y
   *   The vertical distance the target has shifted.
   * @param {Number} [p]
   *   The parallax factor. Defaults to {@link Layer#parallax this.parallax}.
   */
  this.scroll = function(x, y, p) {
    p = p || this.parallax;
    this.xOffset += -x*p;
    this.yOffset += -y*p;
    return this;
  };
  /**
   * Position the Layer's canvas over the primary canvas.
   *
   * This is an alternative to drawing the Layer directly onto the primary
   * canvas. It is mostly useful when the `relative` property is `"canvas"`.
   * It is also useful when the primary canvas is scaled with
   * World#scaleResolution but this Layer should stay a consistent size.
   * However, since it is literally in front of the primary canvas, any other
   * Layers that need to be drawn in front of this one must also be positioned
   * over the primary canvas instead of drawn directly onto it.
   *
   * @return {HTMLElement}
   *   A jQuery representation of a div containing the Layer's canvas.
   */
  this.positionOverCanvas = function() {
    var $d = jQuery('<div></div>');
    var o = $canvas.offset();
    $d.css({
      left: o.left,
      pointerEvents: 'none',
      position: 'absolute',
      top: o.top,
    });
    var $c = jQuery(this.canvas);
    $c.css({
      backgroundColor: 'transparent',
      margin: '0 auto',
      overflow: 'hidden',
      pointerEvents: 'none',
      position: 'absolute',
      'z-index': 50,
    });
    $d.append($c);
    jQuery('body').append($d);
    return $d;
  };
  /**
   * Display this Layer's canvas in an overlay (for debugging purposes).
   *
   * Clicking the overlay will remove it.
   *
   * @return {HTMLElement}
   *   A jQuery representation of a div containing the Layer's canvas.
   */
  this.showCanvasOverlay = function() {
    stopAnimating();
    var $d = jQuery('<div></div>');
    $d.css({
      cursor: 'pointer',
      display: 'block',
      height: '100%',
      left: 0,
      position: 'absolute',
      top: 0,
      width: '100%',
    });
    var $c = jQuery(this.canvas);
    $c.css({
      border: '1px solid black',
      display: 'block',
      margin: '0 auto',
      position: 'absolute',
      'z-index': 100,
    }).click(function() {
      $d.remove();
      startAnimating();
    });
    $d.append($c);
    jQuery('body').append($d);
    $d.click(function(e) {
      if (e.which != 3) { // Don't intercept right-click events
        $d.remove();
      }
    });
    return $d;
  };
}

// RENDERING ------------------------------------------------------------------

/**
 * @class CanvasRenderingContext2D
 *   The native JavaScript canvas graphics context class.
 *
 * This class has been extended with custom methods (and one overridden
 * method).
 *
 * The canvas graphics context for the main canvas is stored in the
 * {@link global#context context} global variable.
 */

/**
 * Clear the canvas.
 *
 * If the rendering context is the {@link global#context global context} for
 * the main canvas or if it belongs to a {@link Layer}, the visible area of the
 * relevant canvas will be cleared. Otherwise, the context doesn't know its
 * transformation matrix, so we have to temporarily reset it to clear the
 * canvas. This has the effect of clearing the visible area of the canvas, but
 * if the fillStyle is being used to draw something, it will not scroll with
 * the rest of the canvas.
 *
 * @param {Mixed} [fillStyle]
 *   If this parameter is passed, the visible area of the canvas will be filled
 *   in with the specified style. Otherwise, the canvas is simply wiped.
 *
 * @member CanvasRenderingContext2D
 */
CanvasRenderingContext2D.prototype.clear = function(fillStyle) {
  this.save();
  var x = 0, y = 0;
  if (this.__layer) {
    x = this.__layer.xOffset;
    y = this.__layer.yOffset;
  }
  else {
    this.setTransform(1, 0, 0, 1, 0, 0);
  }
  if (fillStyle) {
    this.fillStyle = fillStyle;
    this.fillRect(x, y, this.canvas.width, this.canvas.height);
  }
  else {
    this.clearRect(x, y, this.canvas.width, this.canvas.height);
  }
  this.restore();
};

// Store the original drawImage function so we can actually use it.
CanvasRenderingContext2D.prototype.__drawImage = CanvasRenderingContext2D.prototype.drawImage;
/**
 * Draw an image onto the canvas.
 *
 * This method is better than the original `drawImage()` for several reasons:
 *
 * - It uses a cache to allow images to be drawn immediately if they were
 *   pre-loaded and to store images that were not pre-loaded so that they can
 *   be drawn immediately later.
 * - It can draw {@link Sprite}, {@link SpriteMap}, and {@link Layer} objects
 *   as well as the usual images, videos, and canvases. (Note that when Layers
 *   are drawn using this method, their "relative" property IS taken into
 *   account.)
 * - It allows drawing an image by passing in the file path instead of an
 *   Image object.
 *
 * Additionally, this method has an optional `finished` parameter which is a
 * callback that runs when the image passed in the `src` parameter is finished
 * loading (or immediately if the image is already loaded or is a video). The
 * callback's context (its `this` object) is the canvas graphics object. Having
 * this callback is useful because if you do not pre-load images, the image
 * will not be loaded (and therefore will not be drawn) for at least the first
 * time that drawing it is attempted. You can use the `finished` callback to
 * draw the image after it has been loaded if you want.
 *
 * Apart from the additions above, this method works the same way as the
 * [original in the spec](http://www.w3.org/TR/2dcontext/#drawing-images-to-the-canvas).
 *
 * As a summary, this method can be invoked three ways:
 *
 * - `drawImage(src, x, y[, finished])`
 * - `drawImage(src, x, y, w, h[, finished])`
 * - `drawImage(src, sx, sy, sw, sh, x, y, w, h[, finished])`
 *
 * In each case, the `src` parameter accepts one of the following:
 *
 *   - The file path of an image to draw
 *   - A {@link Sprite} or {@link SpriteMap} object
 *   - A {@link Layer} object
 *   - An HTMLCanvasElement
 *   - An HTMLImageElement (same thing as an Image)
 *   - An HTMLVideoElement
 *
 * The `x` and `y` parameters indicate the coordinates of the canvas graphics
 * context at which to draw the top-left corner of the image. (Often this is
 * the number of pixels from the top-left corner of the canvas, though the
 * context can be larger than the canvas if the viewport has scrolled, e.g.
 * with context.translate().)
 *
 * The `w` and `h` parameters indicate the width and height of the image,
 * respectively. Defaults to the image width and height, respectively (or, for
 * a Sprite or SpriteMap, defaults to the projectedW and projectedH,
 * respectively).
 *
 * The `sx`, `sy`, `sw`, and `sh` parameters define a rectangle within the
 * image that will be drawn onto the canvas. `sx` and `sy` are the x- and y-
 * coordinates (within the image) of the upper-left corner of the source
 * rectangle, respectively, and `sw` and `sh` are the width and height of the
 * source rectangle, respectively. These parameters are ignored when drawing a
 * Sprite or SpriteMap. The W3C provides a helpful image to understand these
 * parameters:
 *
 * <img src="http://www.w3.org/TR/2dcontext/images/drawImage.png" alt="drawImage" />
 *
 * See also {@link CanvasRenderingContext2D#drawPattern}() and
 * Caches.preloadImages().
 *
 * @param {Mixed} src
 * @param {Number} [sx]
 * @param {Number} [sy]
 * @param {Number} [sw]
 * @param {Number} [sh]
 * @param {Number} x
 * @param {Number} y
 * @param {Number} [w]
 * @param {Number} [h]
 * @param {Function} [finished]
 * @param {Array} [finished.args]
 *   An array containing the arguments passed to the drawImage() invocation.
 * @param {Boolean} [finished.drawn]
 *   Whether the image was actually drawn (it will not be drawn if the image
 *   wasn't loaded before drawImage() attempted to draw it).
 *
 * @member CanvasRenderingContext2D
 */
CanvasRenderingContext2D.prototype.drawImage = function(src, sx, sy, sw, sh, x, y, w, h, finished) {
  // Allow the finished parameter to come last,
  // regardless of how many parameters there are.
  if (arguments.length % 2 === 0) {
    finished = Array.prototype.pop.call(arguments);
    // Don't let finished interfere with other arguments.
    if (sw instanceof Function) sw = undefined;
    else if (x instanceof Function) x = undefined;
    else if (w instanceof Function) w = undefined;
    if (typeof finished != 'function') {
      finished = undefined;
    }
  }
  var t = this, a = arguments;
  // Keep the stupid order of parameters specified by the W3C.
  // It doesn't matter that we're not providing the correct default values;
  // those will be implemented by the original __drawImage() later.
  if (typeof x != 'number' && typeof y === 'undefined' &&
      typeof w != 'number' && typeof h === 'undefined') {
    x = sx;
    y = sy;
    if (typeof sw == 'number' && typeof sh !== 'undefined') {
      w = sw;
      h = sh;
    }
    sx = undefined;
    sy = undefined;
    sw = undefined;
    sh = undefined;
  }
  // Wrapper function for doing the actual drawing
  var _drawImage = function(image, x, y, w, h, sx, sy, sw, sh) {
    if (w && h) {
      if (sw && sh) {
        t.__drawImage(image, sx, sy, sw, sh, x, y, w, h);
      }
      else {
        t.__drawImage(image, x, y, w, h);
      }
    }
    else {
      t.__drawImage(image, x, y);
    }
    if (finished instanceof Function) {
      finished.call(t, a, true);
    }
  };
  var image;
  if ((typeof Sprite !== 'undefined' && src instanceof Sprite) ||
      (typeof SpriteMap !== 'undefined' && src instanceof SpriteMap)) { // draw a sprite
    src.draw(this, x, y, w, h);
    if (finished instanceof Function) {
      finished.call(t, a, true); // Sprite images are loaded on instantiation
    }
  }
  else if (typeof Layer !== 'undefined' && src instanceof Layer) { // Draw the Layer's canvas
    t.save();
    t.globalAlpha = src.opacity;
    if (src.relative == 'canvas') {
      t.translate(world.xOffset, world.yOffset);
    }
    var f = finished;
    finished = undefined; // Don't call finished() until after translating back
    _drawImage(src.canvas, x, y, w, h, sx, sy, sw, sh);
    t.restore();
    finished = f;
    if (finished instanceof Function) {
      finished.call(t, a, true);
    }
  }
  else if (src instanceof HTMLCanvasElement || // draw a canvas
      src instanceof HTMLVideoElement) { // draw a video
    _drawImage(src, x, y, w, h, sx, sy, sw, sh);
  }
  else if (src instanceof HTMLImageElement || // draw an image directly
      src instanceof Image) { // same thing
    image = src;
    src = image._src || image.src; // check for preloaded src
    if (!src) { // can't draw an empty image
      if (finished instanceof Function) {
        finished.call(t, a, false);
      }
      return;
    }
    if (!Caches.images[src]) { // cache the image by source
      Caches.images[src] = image;
    }
    if (image.complete || (image.width && image.height)) { // draw loaded images
      _drawImage(image, x, y, w, h, sx, sy, sw, sh);
    }
    else { // if the image is not loaded, don't draw it
      if (image._src) { // We've already tried to draw this one
        // The finished callback will run from the first time it was attempted to be drawn
        return;
      }
      var o = image.onload;
      image.onload = function() {
        if (typeof o == 'function') { // don't overwrite any existing handler
          o();
        }
        if (finished instanceof Function) {
          finished.call(t, a, false);
        }
      };
    }
  }
  else if (typeof src == 'string' && Caches.images[src]) { // cached image path
    image = Caches.images[src];
    if (image.complete || (image.width && image.height)) { // Cached image is loaded
      _drawImage(image, x, y, w, h, sx, sy, sw, sh);
    }
    // If cached image is not loaded, bail; the finished callback will run
    // from the first time it was attempted to be drawn
  }
  else if (typeof src == 'string') { // uncached image path
    image = new Image();
    image.onload = function() {
      if (finished instanceof Function) {
        finished.call(t, a, false);
      }
    };
    image._src = src;
    image.src = src;
    Caches.images[src] = image; // prevent loading an unloaded image multiple times
  }
  else {
    throw new TypeMismatchError('Image type not recognized.');
  }
};

/**
 * Draw a pattern onto the canvas.
 *
 * This function is preferred over createPattern() with fillRect() for drawing
 * patterns for several reasons:
 *
 * - It uses a cache to allow images to be drawn immediately if they were
 *   pre-loaded and to store images that were not pre-loaded so that they can
 *   be drawn immediately later.
 * - It can draw {@link Layer} objects as well as the usual images, videos, and
 *   canvases. (Note that when Layers are drawn using this method, their
 *   "relative" property IS taken into account.)
 * - It allows drawing an image by passing in the file path instead of an
 *   Image object.
 *
 * Unlike our modified `drawImage()`, this method cannot draw {@link Sprite}s
 * or {@link SpriteMap}s. If you need to draw a Sprite or SpriteMap as a
 * pattern, draw the part you want onto a Layer or a new canvas and then pass
 * that as the src.
 *
 * See also {@link CanvasRenderingContext2D#drawImage}() and
 * Caches.preloadImages().
 *
 * @param {Mixed} src
 *   The image to draw as a pattern. Accepts one of the following types:
 *
 *   - The file path of an image to draw
 *   - A {@link Layer} object
 *   - An HTMLCanvasElement
 *   - An HTMLImageElement (same thing as an Image)
 *   - An HTMLVideoElement
 *   - A CanvasPattern
 * @param {Number} [x=0]
 *   The x-coordinate at which to draw the top-left corner of the pattern.
 * @param {Number} [y=0]
 *   The y-coordinate at which to draw the top-left corner of the pattern.
 * @param {Number} [w]
 *   The width of the pattern. Defaults to the canvas width.
 * @param {Number} [h]
 *   The height of the pattern. Defaults to the canvas height.
 * @param {"repeat"/"repeat-x"/"repeat-y"/"no-repeat"} [rpt="repeat"]
 *   The repeat pattern type. This parameter can be omitted even if a finished
 *   callback is passed, so the call `drawPattern(src, x, y, w, h, finished)`
 *   is legal.
 * @param {Function} [finished]
 *   A callback that runs when the image passed in the "src" parameter is
 *   finished loading (or immediately if the image is already loaded or is a
 *   video). The callback's context (its `this` object) is the canvas graphics
 *   object. Having this callback is useful because if you do not pre-load
 *   images, the image will not be loaded (and therefore will not be drawn) for
 *   at least the first time that drawing it is attempted. You can use the
 *   finished callback to draw the image after it has been loaded if you want.
 * @param {Array} [finished.args]
 *   An array containing the arguments passed to the drawPattern() invocation.
 * @param {Boolean} [finished.drawn]
 *   Whether the image was actually drawn (it will not be drawn if the image
 *   wasn't loaded before drawPattern() attempted to draw it).
 *
 * @return {CanvasPattern}
 *   The CanvasPattern object for the pattern that was drawn, if possible; or
 *   undefined if a pattern could not be drawn (usually because the image
 *   specified for drawing had not yet been loaded). If your source parameter
 *   is anything other than an image or a file path, the image and pattern
 *   drawn cannot be cached, so it can be helpful for performance to store this
 *   return value and pass it in as the src parameter in the future if you need
 *   to draw the same pattern repeatedly. (Another option is to cache the
 *   drawn pattern in a {@link Layer}.)
 *
 * @member CanvasRenderingContext2D
 */
CanvasRenderingContext2D.prototype.drawPattern = function(src, x, y, w, h, rpt, finished) {
  if (typeof x === 'undefined') x = 0;
  if (typeof y === 'undefined') y = 0;
  if (typeof w === 'undefined') w = this.canvas.width;
  if (typeof h === 'undefined') h = this.canvas.height;
  if (typeof rpt == 'function') {
    finished = rpt;
    rpt = 'repeat';
  }
  else if (!rpt) {
    rpt = 'repeat';
  }
  if (typeof Layer !== 'undefined' && src instanceof Layer) { // Draw the Layer's canvas
    src = src.canvas;
  }
  var image;
  if (src instanceof CanvasPattern) { // draw an already-created pattern
    this.fillStyle = src;
    this.fillRect(x, y, w, h);
    if (finished instanceof Function) {
      finished.call(this, arguments, true);
    }
  }
  else if (typeof Layer !== 'undefined' && src instanceof Layer) { // Draw the Layer's canvas
    this.save();
    this.globalAlpha = src.opacity;
    if (src.relative == 'canvas') {
      this.translate(world.xOffset, world.yOffset);
    }
    this.fillStyle = this.createPattern(src.canvas, rpt);
    this.fillRect(x, y, w, h);
    this.restore();
    if (finished instanceof Function) {
      finished.call(this, arguments, true);
    }
  }
  else if (src instanceof HTMLCanvasElement || // draw a canvas
      src instanceof HTMLVideoElement) { // draw a video
    this.fillStyle = this.createPattern(src, rpt);
    this.fillRect(x, y, w, h);
    if (finished instanceof Function) {
      finished.call(this, arguments, true);
    }
  }
  else if (src instanceof HTMLImageElement || // draw an image directly
      src instanceof Image) { // same thing
    image = src;
    src = image._src || image.src; // check for preloaded src
    if (!src) { // can't draw an empty image
      if (finished instanceof Function) {
        finished.call(this, arguments, false);
      }
      return;
    }
    if (Caches.imagePatterns[src]) { // We already have a pattern; just draw it
      this.fillStyle = Caches.imagePatterns[src];
      this.fillRect(x, y, w, h);
      if (finished instanceof Function) {
        finished.call(this, arguments, true);
      }
      return this.fillStyle;
    }
    if (!Caches.images[src]) { // cache the image by source
      Caches.images[src] = image;
    }
    if (image.complete || (image.width && image.height)) { // draw loaded images
      this.fillStyle = this.createPattern(image, rpt);
      this.fillRect(x, y, w, h);
      Caches.imagePatterns[src] = this.fillStyle;
      if (finished instanceof Function) {
        finished.call(this, arguments, true);
      }
    }
    else { // if the image is not loaded, don't draw it
      if (image._src) { // We've already tried to draw this one
        // The finished callback will run from the first time it was attempted to be drawn
        return;
      }
      var t = this, o = image.onload;
      image.onload = function() {
        if (typeof o == 'function') { // don't overwrite any existing handler
          o();
        }
        Caches.imagePatterns[src] = this.createPattern(image, rpt);
        if (finished instanceof Function) {
          finished.call(t, arguments, false);
        }
      };
    }
  }
  else if (typeof src == 'string') { // file path
    if (Caches.imagePatterns[src]) { // We already have a pattern; just draw it
      this.fillStyle = Caches.imagePatterns[src];
      this.fillRect(x, y, w, h);
      if (finished instanceof Function) {
        finished.call(this, arguments, true);
      }
    }
    else if (Caches.images[src]) { // Image is cached, but no pattern
      image = Caches.images[src];
      if (image.complete || (image.width && image.height)) { // Cached image is loaded
        this.fillStyle = this.createPattern(image, rpt);
        this.fillRect(x, y, w, h);
        Caches.imagePatterns[src] = this.fillStyle;
        if (finished instanceof Function) {
          finished.call(this, arguments, true);
        }
      }
      // If cached image is not loaded, bail; the finished callback will run
      // from the first time it was attempted to be drawn
    }
    else { // Image not loaded yet
      var that = this;
      image = new Image();
      image.onload = function() {
        Caches.imagePatterns[src] = this.createPattern(image, rpt);
        if (finished instanceof Function) {
          finished.call(that, arguments, false);
        }
      };
      image._src = src;
      image.src = src;
      Caches.images[src] = image;
    }
  }
  if (Caches.imagePatterns[src]) {
    return Caches.imagePatterns[src];
  }
};

/**
 * Draw a checkerboard pattern.
 *
 * This method can be invoked in two ways:
 *
 * - `drawCheckered(squareSize, x, y, w, h, color1, color2);`
 * - `drawCheckered(color1, color2, squareSize, x, y, w, h);`
 *
 * All parameters are optional either way.
 *
 * @param {Number} [squareSize=80]
 *   The width and height, in pixels, of each square in the checkerboard
 *   pattern.
 * @param {Number} [x=0]
 *   The x-coordinate of where the pattern's upper-left corner should be drawn
 *   on the canvas.
 * @param {Number} [y=0]
 *   The y-coordinate of where the pattern's upper-left corner should be drawn
 *   on the canvas.
 * @param {Number} [w=squareSize*2]
 *   The width of the pattern to draw onto the canvas.
 * @param {Number} [h=squareSize*2]
 *   The height of the pattern to draw onto the canvas.
 * @param {String} [color1="silver"]
 *   The color of one set of squares in the checkerboard.
 * @param {String} [color2="lightGray"]
 *   The color of the other set of squares in the checkerboard.
 *
 * @return {CanvasPattern}
 *   The CanvasPattern object for the pattern that was drawn. It can be helpful
 *   for performance to store this return value and use it to call
 *   {@link CanvasRenderingContext2D#drawPattern}() in the future if you need
 *   to draw this same pattern repeatedly. (Another option is to cache the
 *   drawn pattern in a {@link Layer}.)
 *
 * @member CanvasRenderingContext2D
 */
CanvasRenderingContext2D.prototype.drawCheckered = function(squareSize, x, y, w, h, color1, color2) {
  if (typeof squareSize === 'undefined') squareSize = 80;
  if (typeof squareSize == 'string' && typeof x == 'string') {
    var c1 = squareSize, c2 = x;
    squareSize = y;
    x = w;
    y = h;
    w = color1;
    h = color2;
    color1 = c1;
    color2 = c2;
  }
  var pattern = document.createElement('canvas'), pctx = pattern.getContext('2d');
  pattern.width = squareSize*2;
  pattern.height = squareSize*2;
  pctx.fillStyle = color1 || 'silver';
  pctx.fillRect(0, 0, squareSize, squareSize);
  pctx.fillRect(squareSize, squareSize, squareSize, squareSize);
  pctx.fillStyle = color2 || 'lightGray';
  pctx.fillRect(squareSize, 0, squareSize, squareSize);
  pctx.fillRect(0, squareSize, squareSize, squareSize);
  return this.drawPattern(pattern, x || 0, y || 0, w || this.canvas.width, h || this.canvas.height);
};

// DRAW SHAPES ----------------------------------------------------------------

/**
 * Draw a circle.
 *
 * @param {Number} x
 *   The x-coordinate of the center of the circle.
 * @param {Number} y
 *   The y-coordinate of the center of the circle.
 * @param {Number} r
 *   The radius of the circle.
 * @param {Mixed} [fillStyle]
 *   A canvas fillStyle used to fill the circle. If not specified, the circle
 *   uses the current fillStyle. If null, the circle is not filled.
 * @param {Mixed} [strokeStyle]
 *   A canvas strokeStyle used to draw the circle's border. If not specified,
 *   no border is drawn on the circle. If null, the border uses the current
 *   strokeStyle.
 *
 * @member CanvasRenderingContext2D
 */
CanvasRenderingContext2D.prototype.circle = function(x, y, r, fillStyle, strokeStyle) {
  // Circle
  this.beginPath();
  this.arc(x, y, r, 0, 2 * Math.PI, false);
  if (fillStyle !== null) {
    if (fillStyle) {
      this.fillStyle = fillStyle;
    }
    this.fill();
  }
  if (strokeStyle !== undefined) {
    this.lineWidth = Math.max(Math.ceil(r/15), 1);
    if (strokeStyle) {
      this.strokeStyle = strokeStyle;
    }
    this.stroke();
  }
};

/**
 * Handles mouse motion/tracking, scrolling, and dragging.
 *
 * @ignore
 */

/**
 * @property
 *   Whether an {@link Actor} is being dragged.
 *
 * Drop targets can change how they look when a draggable object is hovered
 * over them by testing `this.isHovered() && App.isSomethingBeingDragged` in
 * their {@link Box#draw draw()} methods.
 *
 * @member App
 * @static
 */
App.isSomethingBeingDragged = false;

/**
 * Handles mouse motion and scrolling.
 * @static
 * @ignore
 */
var Mouse = {
    /**
     * @class Mouse.Coords
     *   Handles mouse coordinates.
     * @static
     */
    Coords: {
      /**
       * The mouse x-coordinate relative to the upper-left corner of the canvas.
       *
       * This value is actually relative to `canvas.width` and `canvas.height`,
       * which is an important distinction when the canvas is scaled using
       * {@link World#scaleResolution}. It represents the horizontal location
       * of the mouse on the rendered buffer before it is scaled by CSS. When
       * you want the x-coordinate of the mouse relative to the canvas, this is
       * usually what you want to use.
       */
      x: -9999,
      /**
       * The mouse y-coordinate relative to the upper-left corner of the canvas.
       *
       * This value is actually relative to `canvas.width` and `canvas.height`,
       * which is an important distinction when the canvas is scaled using
       * {@link World#scaleResolution}. It represents the vertical location of
       * the mouse on the rendered buffer before it is scaled by CSS. When you
       * want the y-coordinate of the mouse relative to the canvas, this is
       * usually what you want to use.
       */
      y: -9999,
      /**
       * The mouse x-coordinate over the canvas DOM element.
       *
       * This value is actually relative to `$canvas.css('width')` and
       * `$canvas.css('height')`, or the display size of the canvas. This can
       * be different than the size at which the canvas is rendered if the
       * canvas is scaled with {@link World#scaleResolution}. You may want to
       * use this if you are positioning DOM elements over the canvas.
       */
      physicalX: -9999,
      /**
       * The mouse y-coordinate over the canvas DOM element.
       *
       * This value is actually relative to `$canvas.css('width')` and
       * `$canvas.css('height')`, or the display size of the canvas. This can
       * be different than the size at which the canvas is rendered if the
       * canvas is scaled with {@link World#scaleResolution}. You may want to
       * use this if you are positioning DOM elements over the canvas.
       */
      physicalY: -9999,
      /**
       * The mouse x-coordinate relative to the world.
       *
       * Use this if you want the pointer to interact with the world.
       */
      worldX: function() {
        return this.x >= 0 ? this.x + world.xOffset : -9999;
      },
      /**
       * The mouse y-coordinate relative to the world.
       *
       * Use this if you want the pointer to interact with the world.
       */
      worldY: function() {
        return this.y >= 0 ? this.y + world.yOffset : -9999;
      },
    },
};

// Track mouse events
jQuery(document).ready(function() {
  // Callback for mouse/touch-move event to track cursor location
  var trackmove = function(e) {
    try {
      // Get the cursor location
      var x = e.pageX || e.originalEvent.touches[0].pageX;
      var y = e.pageY || e.originalEvent.touches[0].pageY;
      // Prevent window scrolling on iPhone and display freeze on Android
      if (e.type == 'touchmove') {
        e.preventDefault();
      }
      // The position we want is relative to the canvas
      Mouse.Coords.physicalX = x - $canvas.offset().left;
      Mouse.Coords.physicalY = y - $canvas.offset().top;
      // Adjust for scaled resolution
      Mouse.Coords.x = Math.round(Mouse.Coords.physicalX * world._actualXscale);
      Mouse.Coords.y = Math.round(Mouse.Coords.physicalY * world._actualYscale);
    }
    catch(ex) {
      // Don't report anything. Probably the reason we had an error is because
      // the mouse moved off the document so neither the pageX/Y nor the
      // touches properties have meaningful values.
    }
  };

  // Track cursor for touches
  $canvas.on('touchmove.coords', trackmove);
  // For mice, only track the cursor when it's over the canvas
  $canvas.hover(function() {
    jQuery(this).on('mousemove.coords', trackmove);
  }, function() {
    jQuery(this).off('mousemove.coords');
    Mouse.Coords.physicalX = -9999;
    Mouse.Coords.physicalY = -9999;
    Mouse.Coords.x = -9999;
    Mouse.Coords.y = -9999;
  });

  // Track and delegate click events
  $canvas.on('mousedown mouseup click touchstart touchend', function(e) {
    if (e.type == 'touchstart') {
      trackmove(e);
    }
    if (isAnimating() && typeof App.Events !== 'undefined') {
      App.Events.trigger(e.type, e);
    }
  });

  // Track and delegate dragend events
  $canvas.on('mouseup.drag touchend.drag', function(e) {
    if (typeof App.Events !== 'undefined') {
      App.Events.trigger('canvasdragstop', e);
    }
    App.isSomethingBeingDragged = false;
    /**
     * @event canvasdragstop
     *   Fires on the document when the player stops dragging an object,
     *   i.e. when the player releases the mouse or stops touching the canvas.
     * @member global
     */
    jQuery(document).trigger('canvasdragstop');
  });

  // Track and delegate drop events
  jQuery(document).on('canvasdrop', function(e, target) {
    if (typeof App.Events !== 'undefined') {
      App.Events.trigger('canvasdrop', e, target);
    }
  });
});

/**
 * Determine whether the mouse is hovering over an object.
 *
 * The object in question must have these properties: `x`, `y`, `width`,
 * `height`. (All {@link Box}es have these properties.)
 *
 * @param {Box} obj
 *   The object to check.
 *
 * @return {Boolean}
 *   Whether the mouse is hovering over the object.
 *
 * @member App
 * @static
 */
App.isHovered = function(obj) {
  return Mouse.Coords.worldX() > obj.x && Mouse.Coords.worldX() < obj.x + obj.width &&
      Mouse.Coords.worldY() > obj.y && Mouse.Coords.worldY() < obj.y + obj.height;
};

/**
 * @class Mouse.Scroll
 *   Encapsulates mouse position scrolling.
 *
 * Note that mouse scrolling will be temporarily paused while the mouse is down
 * to avoid scrolling while the user is trying to select something.
 *
 * @static
 */
Mouse.Scroll = (function() {
  var THRESHOLD = 0.2, MOVEAMOUNT = 350;
  // Whether we're allowed to mouse scroll
  var enabled = false;
  // If enabled is true, then whether the mouse is over the canvas
  var hovered = false;
  // Whether the mouse is pressed down over the canvas
  var mousedown = false;
  // Whether we're currently scrolling
  var translating = false;
  // How far we scrolled last time
  var scrolled = {x: 0, y: 0};
  // Available easing functions
  var easings = {
      THRESHOLD: function() { return 1; },
      LINEAR: function(val) { return 1-val; },
      SMOOTH: function(val) { return 0.5 - Math.cos( (1-val)*Math.PI ) / 2; },
      EXPONENTIAL: function(val) { return Math.sqrt(1-val); },
  };
  // The currently active easing function
  var easing = easings.SMOOTH;

  function translate() {
    var ma, gradient, initialTranslationState = translating;

    // Left
    if (Mouse.Coords.x < canvas.width * THRESHOLD) {
      gradient = easing(Mouse.Coords.x / (canvas.width * THRESHOLD));
      ma = Math.round(gradient*Math.min(world.xOffset, MOVEAMOUNT * App.physicsDelta));
      world.xOffset -= ma;
      scrolled.x -= ma;
      context.translate(ma, 0);
    }
    // Right
    else if (Mouse.Coords.x > canvas.width * (1-THRESHOLD)) {
      gradient = easing((canvas.width - Mouse.Coords.x) / (canvas.width * THRESHOLD));
      ma = Math.round(gradient*Math.min(world.width - canvas.width - world.xOffset, MOVEAMOUNT * App.physicsDelta));
      world.xOffset += ma;
      scrolled.x += ma;
      context.translate(-ma, 0);
    }

    // Up
    if (Mouse.Coords.y < canvas.height * THRESHOLD) {
      gradient = easing(Mouse.Coords.y / (canvas.height * THRESHOLD));
      ma = Math.round(gradient*Math.min(world.yOffset, MOVEAMOUNT * App.physicsDelta));
      world.yOffset -= ma;
      scrolled.y -= ma;
      context.translate(0, ma);
    }
    // Down
    else if (Mouse.Coords.y > canvas.height * (1-THRESHOLD)) {
      gradient = easing((canvas.height - Mouse.Coords.y) / (canvas.height * THRESHOLD));
      ma = Math.round(gradient*Math.min(world.height - canvas.height - world.yOffset, MOVEAMOUNT * App.physicsDelta));
      world.yOffset += ma;
      scrolled.y += ma;
      context.translate(0, -ma);
    }

    // We're not translating if we're not moving.
    translating = scrolled.x !== 0 && scrolled.y !== 0;

    // We weren't scrolling. Now we are. Fire the relevant event.
    if (!initialTranslationState && translating) {
      /**
       * @event mousescrollon
       *   Fires on the document when the viewport starts scrolling. Binding
       *   to this event may be useful if you want to pause animation or
       *   display something while the viewport is moving.
       */
      jQuery(document).trigger('mousescrollon');
    }
    // We were scrolling. Now we're not. Fire the relevant event.
    else if (initialTranslationState && !translating) {
      /**
       * @event mousescrolloff
       *   Fires on the document when the viewport stops scrolling. Binding
       *   to this event may be useful if you want to pause animation or
       *   display something while the viewport is moving.
       */
      jQuery(document).trigger('mousescrolloff');
    }
    return scrolled;
  }
  return {
    /**
     * Enable mouse position scrolling.
     * @static
     */
    enable: function() {
      if (enabled) {
        return;
      }
      enabled = true;
      $canvas.one('mousemove.translate', function() {
        // Enable translating if we're over the canvas
        if (Mouse.Coords.x >= 0 && Mouse.Coords.y >= 0) {
          hovered = true;
          translate();
        }
      });
      $canvas.on('mouseenter.translate touchstart.translate', function() {
        hovered = true;
        translate();
      });
      $canvas.on('mouseleave.translate touchleave.translate', function() {
        hovered = false;
        if (translating) {
          translating = false;
          jQuery(document).trigger('mousescrolloff');
        }
      });
      $canvas.on('mousedown.translate touchstart.translate', function() {
        mousedown = true;
      });
      $canvas.on('mouseup.translate touchend.translate', function() {
        mousedown = false;
      });
    },
    /**
     * Disable mouse position scrolling.
     * @static
     */
    disable: function() {
      $canvas.off('.translate');
      hovered = false;
      enabled = false;
      translating = false;
    },
    /**
     * Test whether mouse position scrolling is enabled.
     * @static
     */
    isEnabled: function() {
      return enabled;
    },
    /**
     * Test whether the viewport is currently mouse-scrolling.
     *
     * There is one weird edge case: this will return true if the user is in
     * the middle of a click-and-drag action that was started while the
     * viewport was scrolling.
     *
     * @static
     */
    isScrolling: function() {
      return translating;
    },
    // Called in the core animation loop.
    _update: function() {
      // Don't scroll while dragging.
      if (hovered && !mousedown) {
        return translate();
      }
    },
    /**
     * Available easing modes for scroll movement speed.
     *
     * Modes include:
     *
     * - THRESHOLD: Scroll at max speed when the mouse is past the threshold
     * - LINEAR: Increase scroll speed linearly as the mouse approaches an edge
     * - SMOOTH: S-curve "swing" easing (default)
     * - EXPONENTIAL: Increase scroll speed inverse-exponentially as the mouse
     *   approaches an edge (increase quickly at first, then plateau)
     *
     * @static
     */
    easings: easings,
    /**
     * Set the easing function used to determine scroll speed.
     *
     * The `easings` property contains the possible easing functions, or you
     * can define your own.
     *
     * @static
     */
    setEasingFunction: function(e) {
      easing = e;
    },
    /**
     * Get the easing function used to determine scroll speed.
     *
     * The `easings` property contains the possible easing functions.
     *
     * @static
     */
    getEasingFunction: function() {
      return easing;
    },
    /**
     * Set how close to the edge of the canvas the mouse triggers scrolling.
     *
     * The threshold is a fractional percentage [0.0-0.5) of the width of the
     * canvas. If the mouse is within this percent of the edge of the canvas,
     * the viewport attempts to scroll. The default threshold is 0.2 (20%).
     *
     * See also Mouse.Scroll.getThreshold().
     *
     * @static
     */
    setThreshold: function(t) {
      THRESHOLD = t;
    },
    /**
     * Get how close to the edge of the canvas the mouse triggers scrolling.
     *
     * See also Mouse.Scroll.getThreshold().
     *
     * @return {Number}
     *   The mouse-scrolling threshold. The threshold is a fractional
     *   percentage [0.0-0.5) of the width of the canvas. If the mouse is
     *   within this percent of the edge of the canvas, the viewport attempts
     *   to scroll. The default threshold is 0.2 (20%).
     *
     * @static
     */
    getThreshold: function() {
      return THRESHOLD;
    },
    /**
     * Set how fast the mouse will cause the viewport to scroll.
     *
     * The actual scrolling speed also depends on the easing function. The
     * scroll speed set here is actually the maximum scroll speed.
     *
     * @param {Number} a
     *   The maximum distance in pixels that the viewport will move each second
     *   while scrolling (the movement can be less when the viewport is very
     *   close to an edge of the world). Defaults to 350.
     *
     * @static
     */
    setScrollDistance: function(a) {
      MOVEAMOUNT = a;
    },
    /**
     * Get how fast the mouse will cause the viewport to scroll.
     *
     * The actual scrolling speed also depends on the easing function. The
     * scroll speed retrieved here is actually the maximum scroll speed.
     *
     * @return {Number}
     *   The maximum distance in pixels that the viewport will move each second
     *   while scrolling (the movement can be less when the viewport is very
     *   close to an edge of the world). Defaults to 350.
     *
     * @static
     */
    getScrollDistance: function() {
      return MOVEAMOUNT;
    },
  };
})();

/**
 * Handles events on canvas objects.
 *
 * Objects in a Canvas are not represented in the DOM, so they don't benefit
 * from the traditional JavaScript event model. This file provides a similar
 * replacement event system modeled on jQuery's DOM event wrappers so that you
 * don't have to worry about this problem.
 *
 * Bindings for the {@link Box} class are available for easier access to this
 * event system.
 *
 * @ignore
 */

(function() {

function _handlePointerBehavior() {
  if (typeof App.isHovered != 'function') {
    if (window.console && console.warn) {
      console.warn('Mouse event triggered, but App.isHovered does not exist.');
    }
    return false;
  }
  return App.isHovered(this);
}

var _listeners = {};

/**
 * An event system for canvas objects.
 *
 * The browser has no way to distinguish between different objects being
 * displayed on the canvas; as far as it is concerned, the canvas is just a
 * single image. App.Events provides a way to listen for and trigger events on
 * non-DOM objects.
 *
 * @alternateClassName Events
 * @static
 */
App.Events = {
  /**
   * Listen for a specific event.
   *
   * {@link Box} objects can listen for events by calling Box#listen() rather
   * than calling this method directly.
   *
   * @param {Object} obj
   *   The object which should listen for the event being called on it.
   * @param {String} eventName
   *   The name of the event for which to listen, e.g. "click." The event can
   *   have a namespace using a dot, e.g. "click.custom" will bind to the
   *   "click" event with the "custom" namespace. Namespaces are useful for
   *   unlisten()ing to specific callbacks assigned to that namespace or for
   *   unlisten()ing to callbacks bound to a namespace across multiple events.
   * @param {Function} callback
   *   A function to execute when the relevant event is triggered on the
   *   listening object. The function's `this` object is the listening object
   *   and it receives any other parameters passed by the trigger call. Usually
   *   an event object is the first parameter, and propagation can be stopped
   *   by calling the event's stopPropagation() method.
   * @param {Number} [weight=0]
   *   An integer indicating the order in which callbacks for the relevant
   *   event should be triggered. Lower numbers cause the callback to get
   *   triggered earlier than higher numbers. This can be useful for getting
   *   around the fact that the canvas doesn't track display order so event
   *   callbacks can't distinguish which object should be triggered first if
   *   multiple listening objects are overlapping.
   *
   * @static
   */
  listen: function(obj, eventName, callback, weight) {
    var once = arguments[4];
    // Allow specifying multiple space-separated event names.
    var events = eventName.split(' ');
    if (events.length > 1) {
      for (var j = 0, l = events.length; j < l; j++) {
        App.Events.listen(obj, events[j], callback, weight, once);
      }
      return;
    }
    // Separate the event name from the namespace.
    var namespace = '', i = eventName.indexOf('.');
    if (i !== -1) {
      namespace = eventName.substring(i+1);
      eventName = eventName.substring(0, i);
    }
    // Add a listener for the relevant event.
    if (!_listeners[eventName]) {
      _listeners[eventName] = [];
    }
    _listeners[eventName].push({
      object: obj,
      callback: function() {
        callback.apply(obj, arguments);
      },
      namespace: namespace,
      weight: weight || 0,
      once: once || false,
    });
    // Return the listening object so that this function is chainable.
    return obj;
  },
  /**
   * Listen for a specific event and only react the first time it is triggered.
   *
   * {@link Box} objects have a corresponding Box#once() method.
   *
   * @param {Object} obj
   *   The object which should listen for the event being called on it.
   * @param {String} eventName
   *   The name of the event for which to listen, e.g. "click." The event can
   *   have a namespace using a dot, e.g. "click.custom" will bind to the
   *   "click" event with the "custom" namespace. Namespaces are useful for
   *   unlisten()ing to specific callbacks assigned to that namespace or for
   *   unlisten()ing to callbacks bound to a namespace across multiple events.
   * @param {Function} callback
   *   A function to execute when the relevant event is triggered on the
   *   listening object. The function's `this` object is the listening object
   *   and it receives any other parameters passed by the trigger call. Usually
   *   an event object is the first parameter, and propagation can be stopped
   *   by calling the event's stopPropagation() method.
   * @param {Number} [weight=0]
   *   An integer indicating the order in which callbacks for the relevant
   *   event should be triggered. Lower numbers cause the callback to get
   *   triggered earlier than higher numbers. This can be useful for getting
   *   around the fact that the canvas doesn't track display order so event
   *   callbacks can't distinguish which object should be triggered first if
   *   multiple listening objects are overlapping.
   *
   * @static
   */
  once: function(obj, eventName, callback, weight) {
    return App.Events.listen(obj, eventName, callback, weight, true);
  },
  /**
   * Stop listening for a specific event.
   *
   * {@link Box} objects have a corresponding Box#unlisten() method.
   *
   * @param {Object} obj
   *   The object which should unlisten for the specified event.
   * @param {String} eventName
   *   The name of the event for which to listen, e.g. "click." The event can
   *   have a namespace using a dot, e.g. "click.custom" will unbind obj's
   *   listeners for the "click" that are using the "custom" namespace. You can
   *   also unlisten to multiple events using the same namespace, e.g.
   *   ".custom" could unlisten to "mousemove.custom" and "touchmove.custom."
   *   If the event specified does not have a namespace, all callbacks will be
   *   unbound regardless of their namespace.
   *
   * @static
   */
  unlisten: function(obj, eventName) {
    // Allow specifying multiple space-separated event names.
    var events = eventName.split(' ');
    if (events.length > 1) {
      for (var j = 0, l = events.length; j < l; j++) {
        App.Events.unlisten(obj, events[j]);
      }
      return;
    }
    // Separate the event name from the namespace.
    var namespace = '', i = eventName.indexOf('.'), e;
    if (i !== -1) {
      namespace = eventName.substring(i+1);
      eventName = eventName.substring(0, i);
    }
    // Remove all relevant listeners.
    if (eventName && _listeners[eventName]) {
      for (e = _listeners[eventName], i = e.length-1; i >= 0; i--) {
        if (e[i].object == obj && (!namespace || e[i].namespace == namespace)) {
          _listeners[eventName].splice(i, 1);
        }
      }
    }
    else if (!eventName && namespace) {
      for (eventName in _listeners) {
        if (_listeners.hasOwnProperty(eventName)) {
          for (e = _listeners[eventName], i = e.length-1; i >= 0; i--) {
            if (e[i].object == obj && e[i].namespace == namespace) {
              _listeners[eventName].splice(i, 1);
            }
          }
        }
      }
    }
    // Return the listening object so that this function is chainable.
    return obj;
  },
  /**
   * Trigger an event.
   *
   * {@link Box} objects have a corresponding Box#trigger() method.
   *
   * @param {String} eventName
   *   The name of the event to trigger, e.g. "click."
   * @param {Arguments} ...
   *   Additional arguments to pass to the relevant callbacks. Usually the
   *   first argument is the event object.
   *
   * @static
   */
  trigger: function() {
    var eventName = Array.prototype.shift.call(arguments);
    var event = arguments[0];
    var e = _listeners[eventName]; // All listeners for this event
    if (e) {
      // Sort listeners by weight (lowest last, then we'll iterate in reverse).
      e.sort(function(a, b) {
        return b.weight - a.weight;
      });
      // Execute the callback for each listener for the relevant event.
      for (var i = e.length-1; i >= 0; i--) {
        if (!App.Events.Behaviors[eventName] ||
            App.Events.Behaviors[eventName].apply(e[i].object, arguments)) {
          e[i].callback.apply(e[i].object, arguments);
          // Remove listeners that should only be called once.
          if (e[i].once) {
            App.Events.unlisten(e[i].object, eventName + '.' + e[i].namespace);
          }
          // Stop processing overlapping objects if propagation is stopped.
          if (event && event.isPropagationStopped && event.isPropagationStopped()) {
            break;
          }
        }
      }
    }
  },
  /**
   * Determine whether an object should be triggered for a specific event.
   *
   * The Behaviors object has event names as keys and functions as values. The
   * functions evaluate whether the relevant event has been triggered on a
   * given listening object. The listening object is the functions' `this`
   * object, and the functions receive all the same parameters passed to the
   * App.Events.trigger() method (usually starting with an Event object). Add
   * elements to App.Events.Behaviors if you want to support new event types
   * with conditional filters.
   *
   * @static
   */
  Behaviors: {
    /**
     * @event mousedown
     *   The mousedown event is sent to an object when the mouse pointer is
     *   over the object and the mouse button is pressed.
     * @param {Event} e The event object.
     * @member Box
     */
    mousedown: _handlePointerBehavior,
    /**
     * @event mouseup
     *   The mouseup event is sent to an object when the mouse pointer is over
     *   the object and the mouse button is released.
     * @param {Event} e The event object.
     * @member Box
     */
    mouseup: _handlePointerBehavior,
    /**
     * @event click
     *   The mouseup event is sent to an object when the mouse pointer is over
     *   the object and the mouse button is pressed and released.
     * @param {Event} e The event object.
     * @member Box
     */
    click: _handlePointerBehavior,
    /**
     * @event touchstart
     *   The touchstart event is sent to an object when the object is touched.
     * @param {Event} e The event object.
     * @member Box
     */
    touchstart: _handlePointerBehavior,
    /**
     * @event touchend
     *   The touchend event is sent to an object when a touch is released over
     *   the object.
     * @param {Event} e The event object.
     * @member Box
     */
    touchend: _handlePointerBehavior,
    /**
     * @event canvasdragstop
     *   The canvasdragstop event is sent to an object when a click or touch
     *   event ends and that object is being dragged. This should be used
     *   instead of binding to mouseup and touchend because dragged Actors
     *   still follow collision rules, so dragging an Actor into a solid wall
     *   will let the mouse move off the Actor while it is over the wall. (It
     *   is possible to drag an Actor through a wall, but Actors cannot be
     *   dropped inside of something solid they collide with.)
     * @param {Event} e The event object.
     * @member Actor
     */
    canvasdragstop: function() {
      return !!this.isBeingDragged;
    },
    /**
     * @event canvasdrop
     *   The canvasdrop event is sent to a drop target object when a draggable
     *   {@link Actor} is dropped onto it.
     * @param {Event} e The event object.
     * @param {Box} target The drop target object. (You can use `this` instead.)
     * @member Box
     */
    canvasdrop: function(e, target) {
      return this === target;
    },
  },
};

}).call(this);

/**
 * @class App.Storage
 *   Provides persistent local storage that preserves object type.
 *
 * Behavior is very similar to using localStorage directly except that it
 * supports storing any kind of object rather than just strings.
 *
 * Note that local storage does not work in some browsers on documents accessed
 * via the file:// protocol.
 */
App.Storage = (function(window, undefined) {
  var api = {enabled: true},
      namespace = '__appstore__',
      storage = window.localStorage,
      internal = {},
      fake = false;
  /**
   * Sets the value at key.
   *
   * If value is undefined, removes the value at key.
   *
   * @param {String} key
   *   The identifier for the value to set.
   * @param {Mixed} value
   *   The value to assign at the key.
   */
  api.set = function(key, value) {
    key = namespace + key;
    if (value === undefined) {
      return api.remove(key);
    }
    storage.setItem(key, JSON.stringify(value));
  };
  /**
   * Get the value stored at key.
   *
   * @param {String} key
   *   The identifier for the value to retrieve.
   * @param {Mixed} [defaultValue=undefined]
   *   If there is no value at key, this value is returned instead.
   *
   * @return {Mixed}
   *   The value stored at key if it exists, or defaultValue otherwise.
   */
  api.get = function(key, defaultValue) {
    key = namespace + key;
    try {
      var item = storage.getItem(key);
      // If it's a string, we probably put it there. Un-stringify it.
      if (typeof item === 'string') {
        return JSON.parse(item);
      }
      // Someone put something here.
      else if (item !== null) {
        return item;
      }
    } catch(e) {
      if (console && console.error) {
        console.error(e);
      }
    }
    // There is nothing at key or JSON.parse failed.
    return defaultValue;
  };
  /**
   * Removes the value at key.
   *
   * @param {String} key
   *   The identifier for the value to remove.
   */
  api.remove = function(key) {
    storage.removeItem(namespace + key);
  };
  /**
   * Removes all values from storage.
   */
  api.clear = function() {
    storage.clear();
  };
  /**
   * Returns the number of items in storage.
   *
   * @return {Number}
   *   The number of items in storage.
   */
  api.length = function() {
    if (fake) {
      return internal.length;
    }
    return storage.length;
  };
  /**
   * Returns the value at a numeric index.
   *
   * @param {Number} index
   *   The index at which to retrieve the value.
   *
   * @return {Mixed}
   *   The value at the specified numeric index.
   */
  api.key = function(index) {
    return storage.key(index);
  };
  /**
   * Indicates whether persistent storage is supported in this browser.
   *
   * If it is not supported, storage will only persist for the current session.
   *
   * @return {Boolean}
   *   A Boolean indicating whether persistent storage is supported in this
   *   browser.
   */
  api.isEnabled = function() {
    return api.enabled;
  };
  // Safari Private mode throws an error when localStorage is used.
  // Learned at https://github.com/marcuswestin/store.js
  try {
    api.set(namespace, namespace);
    if (api.get(namespace) != namespace) {
      api.enabled = false;
    }
    api.remove(namespace);
  } catch(e) {
    api.enabled = false;
  }
  // If localStorage is unavailable, fake it. No point falling back to anything
  // else; all we're going for here is avoiding breaking errors. Doing this
  // avoids having to check for App.Storage.enabled every time you want to
  // store something. If you wanted to be really comprehensive with local
  // storage you could use something like
  // [sticky.js](https://github.com/alexmng/sticky/blob/master/sticky-2.8.js).
  // However, localStorage is supported in every browser that supports enough
  // Canvas API operations to be useful, so that is all we need to support.
  if (!api.enabled) {
    storage = {
        setItem: function(key, value) {
          internal[key] = value;
        },
        getItem: function(key) {
          return internal[key];
        },
        removeItem: function(key) {
          delete internal[key];
        },
        clear: function() {
          internal = {};
        },
        key: function(index) {
          var i = 0;
          for (var k in internal) {
            if (internal.hasOwnProperty(k)) {
              if (i == index) {
                return internal[k];
              }
              i++;
            }
          }
        }
    };
  }
  return api;
})(window);

/**
 * Defines structures that are useful containers of other entities.
 *
 * Specifically, this file holds the Collection class, which is like a more
 * useful 1D Array, and the TileMap class, which is like a more useful 2D Array
 * that also helps with initializing tile-based maps.
 *
 * @ignore
 */

/**
 * An Array-like container to keep track of multiple Boxes/Box descendants.
 *
 * The main reason to use Collections instead of Arrays is that Collections
 * have utility methods to easily process Box objects without manually
 * iterating over each one. Collection objects can be treated as arrays in
 * many respects; for example, they have a dynamic `length` property, and they
 * can be read and modified using square-bracket notation (e.g. `a[0]`)
 * although new elements should not be added using square brackets (use the
 * `add()` method instead to make sure the `length` is correctly updated).
 *
 * @constructor
 *   Creates a new Collection instance.
 *
 * @param {Arguments} ...
 *   An Array of items that the Collection should hold.
 */
function Collection() {
  this.concat.apply(this, arguments);
}
Collection.prototype = {
  /**
   * The number of items in the Collection.
   */
  length: 0,
  /**
   * Draw every object in the Collection.
   *
   * This calls Box#draw() on every Box in the Collection.
   *
   * @param {CanvasRenderingContext2D} [ctx]
   *   A canvas graphics context onto which to draw. This is useful for drawing
   *   onto {@link Layer}s. If not specified, defaults to the
   *   {@link global#context global context} for the default canvas.
   */
  draw: function(ctx) {
    ctx = ctx || context;
    for (var i = 0; i < this.length; i++) {
      this[i].draw(ctx);
    }
    return this;
  },
  /**
   * Execute a function on every item in the Collection.
   *
   * @param {Function/String} f
   *   The function to execute on each item, or the (string) name of a method
   *   of each object in the Collection that should be invoked. In the first
   *   case, the function should return a truthy value in order to remove the
   *   item being processed from the Collection. In the second case, additional
   *   arguments to the forEach method are also passed on to the items' method.
   */
  forEach: function(f) {
    if (typeof f == 'string') {
      return this._executeMethod.apply(this, arguments);
    }
    for (var i = this.length-1; i >= 0; i--) {
      if (f(this[i])) {
        if (typeof this[i].destroy == 'function') {
          this[i].destroy();
        }
        this.splice(i, 1);
      }
    }
    return this;
  },
  /**
   * Execute an arbitrary method of all items in the Collection.
   *
   * All items in the Collection are assumed to have the specified method.
   *
   * This is used *internally* by Collection#forEach().
   *
   * @param {String} name
   *    The name of the method to invoke on each object in the Collection.
   * @param {Arguments} ...
   *    Additional arguments are passed on to the specified method.
   *
   * @ignore
   */
  _executeMethod: function() {
    var name = Array.prototype.shift.call(arguments);
    for (var i = 0; i < this.length; i++) {
      this[i][name].apply(this[i], arguments);
    }
    return this;
  },
  /**
   * Check each pair of items in this Collection for collision.
   *
   * This method assumes every item in the Collection has an "overlaps"
   * method.
   *
   * To check all items in a Collection against a single Box, use the
   * Box#collides() method. To check all items in a Collection against all
   * items in another Collection, use the following pattern:
   *
   *     collection1.forEach(function(item) {
   *       if (item.collides(collection2)) {
   *         // do something
   *       }
   *     });
   *
   * In the example above, to get the Boxes that collide with each item, simply
   * pass `true` as the second parameter to `item.collides()` and note the
   * return value.
   *
   * @param {Function} [callback]
   *   A function to call for each pair of items in this Collection that
   *   collide.
   * @param {Mixed} [callback.a]
   *   An item in the Collection that overlaps.
   * @param {Mixed} [callback.b]
   *   An item in the Collection that overlaps.
   *
   * @return {Boolean}
   *   Whether items in this Collection collide.
   */
  collideAll: function(callback) {
    var collision = false;
    for (var i = 0, l = this.length; i < l; i++) {
      for (var j = i+1; j < l; j++) {
        if (this[i].overlaps(this[j])) {
          if (typeof callback == 'function') {
            callback.call(this, this[i], this[j]);
            collision = true;
          }
          else {
            return true;
          }
        }
      }
    }
    return collision;
  },
  /**
   * Changes the content of a Collection by adding and/or removing elements.
   *
   * This method works exactly the same way as
   * [Array#splice](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/splice).
   */
  splice: Array.prototype.splice,
  /**
   * Add an item to the Collection.
   *
   * This method works exactly the same way as
   * [Array#push](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/push).
   */
  add: Array.prototype.push,
  /**
   * Add the items in an Array or another Collection to this Collection.
   *
   * See Collection#combine() to add the items in another Collection to this
   * Collection.
   *
   * @param {Arguments} ...
   *   Array(s) or Collection(s) of Boxes to add to the Collection.
   */
  concat: function() {
    for (var i = 0, l = arguments.length; i < l; i++) {
      if (arguments[i] instanceof Array || arguments[i] instanceof Collection) {
        for (var j = 0, m = arguments[i].length; j < m; j++) {
          Array.prototype.push.call(this, arguments[i][j]);
        }
      }
      else {
        Array.prototype.push.call(this, arguments[i]);
      }
    }
    return this;
  },
  /**
   * Remove an item from the Collection.
   *
   * See Collection#removeLast() to pop the last item in the collection.
   *
   * @param {Mixed} item
   *   The item to remove from the Collection.
   *
   * @return Array
   *   An Array containing the removed element, if any.
   */
  remove: Array.prototype.remove,
  /**
   * Remove and return the last item in the Collection.
   *
   * This method works exactly the same way as
   * [Array#pop](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/pop).
   */
  removeLast: Array.prototype.pop,
  /**
   * Remove and return the first item in the Collection.
   *
   * This method works exactly the same way as
   * [Array#shift](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/shift).
   */
  removeFirst: Array.prototype.shift,
  /**
   * Remove all items in the Collection.
   */
  removeAll: function() {
    Array.prototype.splice.call(this, 0, this.length);
    return this;
  },
  /**
   * Returns index of the specified item in the Collection.
   *
   * This method works exactly the same way as
   * [Array#indexOf](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf).
   */
  indexOf: Array.prototype.indexOf,
};

/**
 * A wrapper for image tiles so they can be drawn in the right location.
 *
 * Used *internally* by TileMap as a lighter version of a Box.
 *
 * `src` can be any object of type String (e.g. a file path to an image),
 * Image, HTMLImageElement, HTMLCanvasElement, Sprite, or SpriteMap.
 *
 * @ignore
 */
function ImageWrapper(src, x, y, w, h) {
  this.src = src;
  this.x = x;
  this.y = y;
  this.width = w;
  this.height = h;
  /**
   * Draws the image.
   */
  this.draw = function(ctx) {
    (ctx || context).drawImage(this.src, this.x, this.y, this.width, this.height);
  };
}

/**
 * A grid of objects (like a 2D {@link Collection}) for easy manipulation.
 *
 * Useful for rapidly initializing and managing large sets of same-sized
 * "tiles."
 *
 * @constructor
 *   Creates a new TileMap instance.
 *
 * @param {String/String[][]} grid
 *   `grid` represents the initial layout of the TileMap. If it is specified as
 *   a 2D Array of Strings, each inner value is used to construct a tile by
 *   using it as a key for the `map` parameter. If it is specified as a single
 *   String, that String is deconstructed into a 2D Array of Strings by
 *   splitting each row at newlines (`\n`) and assuming each character belongs
 *   in its own column. For example, this String:
 *
 *       "    A    \n
 *           BBB   \n
 *        BBBBBBBBB"
 *
 *   is equivalent to this Array:
 *
 *       [[' ', ' ', ' ', ' ', 'A', ' ', ' ', ' ', ' '],
 *        [' ', ' ', ' ', 'B', 'B', 'B', ' ', ' ', ' '],
 *        ['B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B']]
 *
 *   If `map` was specified as `{'A': Actor, 'B': Box}`, then this layout would
 *   correspond to an Actor standing on top of a small hill of Boxes.
 * @param {Object/Array} map
 *   An Array or object whose keys are found in the `grid` parameter and whose
 *   values are one of the following:
 *
 *   - null: Indicates a blank tile.
 *   - A String object: Assumed to be a path to an image file that should be
 *     used to render the tile.
 *   - An Image, HTMLImageElement, HTMLCanvasElement, {@link Sprite}, or
 *     {@link SpriteMap} object: An image used to render the tile.
 *   - {@link Box}, or a descendant of Box: Passing the literal class
 *     constructor (not an instance) will cause the TileMap to automatically
 *     initialize instances of that class.
 *
 *   This value is irrelevant if `options.gridSize` is specified since the
 *   entire TileMap is then created as a blank grid. In this case,
 *   `undefined`, `null`, or an empty array or object are acceptable values.
 * @param {Object} [options]
 *   A set of configuration settings for the TileMap.
 * @param {Number[]} [options.gridSize=null]
 *   Ignored if null. If this is a two-element Array containing positive
 *   integers, then the TileMap is initialized as a blank grid using these
 *   dimensions (col*row) and the `grid` and `map` parameters become
 *   irrelevant.
 * @param {Number[]} [options.cellSize]
 *   A two-element Array containing positive integers indicating the width and
 *   height in pixels of each tile. Defaults to the default dimensions of a
 *   Box.
 * @param {Number[]} [options.startCoords]
 *   A two-element Array containing positive integers indicating the x- and
 *   y-coordinates of the upper-left corner of the TileMap relative to the
 *   World. Defaults to placing the lower-left corner of the TileMap at the
 *   lower-left corner of the world.
 */
function TileMap(grid, map, options) {
  // Setup and options
  var i, j, l, m;
  this.options = {
      cellSize: [Box.prototype.DEFAULT_WIDTH, Box.prototype.DEFAULT_HEIGHT],
      gridSize: null,
      startCoords: options ? options.startCoords : undefined,
  };
  if (options && options.cellSize instanceof Array && options.cellSize.length > 1) {
    this.options.cellSize = options.cellSize;
  }
  if (options && options.gridSize instanceof Array && options.gridSize.length > 1) {
    this.options.gridSize = options.gridSize;
  }
  if (typeof map === 'undefined' || map === null) {
    map = [];
  }
  // Place the TileMap in the lower-left corner of the world.
  if (typeof this.options.startCoords === 'undefined' ||
      this.options.startCoords.length === 0) {
    this.options.startCoords = [0, world.height - this.options.cellSize[1] *
                                  (typeof grid == 'string' ? grid.split("\n") : grid).length
                                ];
  }
  var gs = this.options.gridSize,
      cw = this.options.cellSize[0],
      ch = this.options.cellSize[1],
      sx = this.options.startCoords[0],
      sy = this.options.startCoords[1];

  // If options.gridSize was specified, build a blank grid.
  if (gs instanceof Array && gs.length > 0) {
    grid = new Array(gs[0]);
    for (i = 0; i < gs[0]; i++) {
      grid[i] = new Array(gs[1]);
      for (j = 0; j < gs[1]; j++) {
        grid[i][j] = null;
      }
    }
  }
  // Allow specifying grid as a string; we'll deconstruct it into an array.
  if (typeof grid == 'string') {
    grid = grid.split("\n");
    for (i = 0, l = grid.length; i < l; i++) {
      grid[i] = grid[i].split('');
    }
  }
  this.grid = grid;
  // Make space mean null (blank) unless otherwise specified.
  if (typeof map !== 'undefined' && typeof map[' '] === 'undefined') {
    map[' '] = null;
  }

  // Initialize all the objects in the grid.
  for (i = 0, l = grid.length; i < l; i++) {
    for (j = 0, m = grid[i].length; j < m; j++) {
      // Avoid errors with map[] and allow writing null directly
      if (grid[i][j] === null) {
        this.grid[i][j] = null;
        continue;
      }
      var o = map ? map[grid[i][j]] : grid[i][j];
      // Blank tile or no match is found in map
      if (o === null || o === undefined) {
        this.grid[i][j] = null;
      }
      else {
        var x = sx + j * cw, y = sy + i * ch; // x- and y-coordinates of tile
        // We can handle any image type that context.drawImage() can draw
        if (typeof o === 'string' ||
            o instanceof Image ||
            o instanceof HTMLImageElement ||
            o instanceof HTMLCanvasElement ||
            o instanceof Sprite ||
            o instanceof SpriteMap ||
            o instanceof Layer) {
          this.grid[i][j] = new ImageWrapper(o, x, y, cw, ch);
        }
        // If we have a Class, initialize a new instance of it
        else if (o instanceof Function) {
          this.grid[i][j] = new (Function.prototype.bind.call(o, null, x, y, cw, ch))();
        }
        else { // fallback
          this.grid[i][j] = null;
        }
      }
    }
  }
  /**
   * Draw all the tiles.
   *
   * @param {CanvasRenderingContext2D} [ctx]
   *   A canvas graphics context onto which to draw the tiles. This is useful
   *   for drawing onto {@link Layer}s. If not specified, defaults to the
   *   {@link global#context global context} for the default canvas.
   * @param {Boolean} [occlude=false]
   *   Indicates whether to only draw tiles that are visible within the
   *   viewport (true) or to draw all tiles (false). Drawing only visible tiles
   *   is performance-friendly for huge TileMaps, but requires re-drawing the
   *   TileMap whenever the viewport scrolls. If you are just drawing the
   *   TileMap once, for example onto a background Layer cache, occluding is
   *   unnecessary.
   * @param {Boolean} [smooth]
   *   Indicates whether to force the Box to be drawn at whole-pixel
   *   coordinates. If you don't already know that your coordinates
   *   will be integers, this option can speed up painting since the browser
   *   does not have to interpolate the image. Defaults to whatever the default
   *   is for each object being drawn (the default for Boxes is true).
   */
  this.draw = function(ctx, occlude, smooth) {
    ctx = ctx || context;
    var i, l;
    if (occlude) {
      var active = this.getCellsInRect();
      for (i = 0, l = active.length; i < l; i++) {
        active[i].draw(ctx, smooth);
      }
      return;
    }
    for (i = 0, l = this.grid.length; i < l; i++) {
      for (var j = 0, m = this.grid[i].length; j < m; j++) {
        var o = this.grid[i][j];
        if (o !== null) {
          o.draw(ctx, smooth);
        }
      }
    }
    return this;
  };
  /**
   * Get the object at a specific tile using the row and column.
   *
   * @param {Number} row The row of the tile being retrieved.
   * @param {Number} col The column of the tile being retrieved.
   * @return {Mixed} The object at the specified tile.
   */
  this.getCell = function(row, col) {
    return this.grid[row] ? this.grid[row][col] : undefined;
  };
  /**
   * Place a specific object into a specific tile using the row and column.
   *
   * If an object is already located there, it will be overwritten.
   *
   * Note that placing an object into a specific cell does not adjust its
   * position, but rather merely stores it in the TileMap. If you want an
   * object to appear at a specific position relative to other tiles, you need
   * to place it there yourself.
   *
   * @param {Number} row The row of the tile being set.
   * @param {Number} col The column of the tile being set.
   * @param {Object} obj The object to place at the specified tile.
   */
  this.setCell = function(row, col, obj) {
    if (this.grid[row] && typeof this.grid[row][col] !== 'undefined') {
      this.grid[row][col] = obj;
    }
    return this;
  };
  /**
   * Clear a specific tile (make it blank).
   *
   * @param {Number} row The row of the tile being cleared.
   * @param {Number} col The column of the tile being cleared.
   */
  this.clearCell = function(row, col) {
    if (this.grid[row]) {
      this.grid[row][col] = null;
    }
    return this;
  };
  /**
   * Return an Array of all non-null objects in the TileMap.
   *
   * For large TileMaps, consider using TileMap#getCellsInRect() for
   * efficiency, since it only returns cells within a certain area (the
   * viewport by default).
   *
   * Note that if you just used a TileMap to easily initialize a bunch of
   * tiles, or if you're not adding or removing tiles frequently but you are
   * calling this function frequently, you can also convert your TileMap to a
   * {@link Collection}:
   *
   *     var myCollection = new Collection(myTileMap.getAll());
   *
   * This is more efficient if you always need to process every item in the
   * TileMap and you don't care about their relative position.
   */
  this.getAll = function() {
    var w = this.grid.length, h = (w > 0 ? this.grid[0].length : 0), r = [], i, j;
    for (i = 0; i < w; i++) {
      for (j = 0; j < h; j++) {
        if (this.grid[i][j] !== null) {
          r.push(this.grid[i][j]);
        }
      }
    }
    return r;
  };
  /**
   * Clear all tiles (make them blank).
   */
  this.clearAll = function() {
    var w = this.grid.length, h = (w > 0 ? this.grid[0].length : 0), i, j;
    this.grid = new Array(w);
    for (i = 0; i < w; i++) {
      this.grid[i] = new Array(h);
      for (j = 0; j < h; j++) {
        grid[i][j] = null;
      }
    }
    return this;
  };
  /**
   * Get the number of rows in the grid.
   *
   * See also TileMap#getCols().
   */
  this.getRows = function() {
    return this.grid.length;
  };
  /**
   * Get the number of columns in the grid.
   *
   * See also TileMap#getRows().
   */
  this.getCols = function() {
    return this.grid.length > 0 ? this.grid[0].length : 0;
  };
  /**
   * Execute a function on every element in the TileMap.
   *
   * @param {Function} f
   *   The function to execute on each tile.
   * @param {Mixed} f.obj
   *   The object being processed.
   * @param {Number} f.row
   *   The row of the tile being processed. This lets the function use
   *   TileMap#getCell() if it needs to check surrounding cells.
   * @param {Number} f.col
   *   The column of the tile being processed. This lets the function use
   *   TileMap#getCell() if it needs to check surrounding cells.
   * @param {Boolean} f.return
   *   If the return value is truthy, the object being processed will be
   *   removed from the TileMap. If it has a destroy() method, that method will
   *   be called.
   * @param {Boolean} [includeNull=false]
   *   Indicates whether to execute the function on null (blank) tiles.
   */
  this.forEach = function(f, includeNull) {
    var w = this.grid.length, h = (w > 0 ? this.grid[0].length : 0), i, j;
    for (i = 0; i < w; i++) {
      for (j = 0; j < h; j++) {
        if (this.grid[i][j] !== null || includeNull) {
          if (f(this.grid[i][j], i, j)) {
            if (typeof this.grid[i][j].destroy == 'function') {
              this.grid[i][j].destroy();
            }
            this.clearCell(i, j);
          }
        }
      }
    }
    return this;
  };
  /**
   * Get the max and min array coordinates of cells that are in a rectangle.
   *
   * wx and wy are the x- and y-coordinates in pixels of the upper-left corner
   * of the rectangle to retrieve, respectively. tw and th are the width and
   * height in pixels of the rectangle, respectively. This function returns an
   * Array containing the starting column, starting row, ending column, and
   * ending row of the TileMap grid that fall within the specified pixel
   * rectangle. These values may be outside of the actual bounds of the TileMap
   * grid. This function is only called *internally* (from
   * TileMap#getCellsInRect(), which restricts the returned values to the
   * bounds of the TileMap grid).
   *
   * @ignore
   */
  this._getCellCoordsInRect = function(wx, wy, tw, th) {
    if (typeof wx === 'undefined') wx = world.xOffset;
    if (typeof wy === 'undefined') wy = world.yOffset;
    if (typeof tw === 'undefined') tw = canvas.width;
    if (typeof th === 'undefined') th = canvas.height;
    var x = this.options.startCoords[0], y = this.options.startCoords[1];
    var cw = this.options.cellSize[0], cy = this.options.cellSize[1];
    var sx = (wx - x) / cw, sy = (wy - y) / cy;
    var sxe = (wx + tw - x) / cw, sye = (y - wy + th) / cy;
    // startCol, startRow, endCol, endRow
    return [Math.floor(sx), Math.floor(sy), Math.ceil(sxe), Math.ceil(sye)];
  };
  /**
   * Return all objects within a given rectangle.
   *
   * This method returns an array of all non-null objects in the TileMap within
   * a rectangle specified in pixels. If no rectangle is specified, this method
   * defaults to retrieving all objects in view (i.e. it uses the viewport as
   * the rectangle).
   *
   * This is an efficient way to process only objects that are in view (or
   * nearly in view) which is useful for efficient processing of only relevant
   * information in a very large map.
   *
   * Use TileMap#getAll() to process every tile in a TileMap.
   *
   * @param {Number} [x]
   *   The x-coordinate in pixels of the upper-left corner of the rectangle
   *   to retrieve. Defaults to the upper-left corner of the viewport.
   * @param {Number} [y]
   *   The y-coordinate in pixels of the upper-left corner of the rectangle
   *   to retrieve. Defaults to the upper-left corner of the viewport.
   * @param {Number} [w]
   *   The width of the rectangle to retrieve. Defaults to the width of the
   *   viewport.
   * @param {Number} [h]
   *   The height of the rectangle to retrieve. Defaults to the height of the
   *   viewport.
   *
   * @return {Array}
   *   All non-null objects in the TileMap within the specified rectangle.
   */
  this.getCellsInRect = function(x, y, w, h) {
    // startCol, startRow, endCol, endRow
    var r = this._getCellCoordsInRect(x, y, w, h), s = [];
    var startRow = Math.min(this.getRows(), Math.max(0, r[1]));
    var endRow = Math.min(this.getRows(), Math.max(0, r[3]));
    var startCol = Math.min(this.getCols(), Math.max(0, r[0]));
    var endCol = Math.min(this.getCols(), Math.max(0, r[2]));
    for (var i = startRow, l = endRow; i < l; i++) {
      for (var j = startCol, m = endCol; j < m; j++) {
        if (this.grid[i][j] !== null) {
          s.push(this.grid[i][j]);
        }
      }
    }
    return s;
  };
  /**
   * Return the cell coordinates of a pixel location.
   *
   * @param {Number} x
   *   The x-coordinate in pixels.
   * @param {Number} y
   *   The y-coordinate in pixels.
   *
   * @return {Object}
   *   An object with `row` and `col` attributes containing the row and column
   *   number of the cell containing the specified coordinates. Rows and
   *   columns are zero-indexed and the returned value can be outside of the
   *   TileMap.
   */
  this.getCellCoords = function(x, y) {
    return {
      row: Math.round((y - this.options.startCoords[1])/this.options.cellSize[1]),
      col: Math.round((x - this.options.startCoords[0])/this.options.cellSize[0]),
    };
  };
  /**
   * Return the pixel coordinates of a cell location.
   *
   * @param {Number} col
   *   The column number (zero-indexed).
   * @param {Number} row
   *   The row number (zero-indexed).
   *
   * @return {Object}
   *   An object with `x` and `y` attributes containing the pixel values of the
   *   upper-left corner of the cell at the specified row and column. The
   *   returned value can be outside of the TileMap.
   */
  this.getPixelCoords = function(col, row) {
    return {
      x: this.options.startCoords[0]+this.options.cellSize[0]*col,
      y: this.options.startCoords[1]+this.options.cellSize[1]*row,
    };
  };
}

/**
 * Defines useful classes for actors in the world.
 *
 * Specifically, this file holds the Box, Actor, and Player classes. These are
 * useful abstractions for practically any game-style environment.
 *
 * @ignore
 */

/**
 * A Box shape.
 *
 * Boxes have a position, size, and visual representation.
 *
 * @constructor
 *   Creates a new Box instance.
 *
 * @param {Number} [x]
 *   The x-coordinate of the top-left corner of the Box. Defaults to the center
 *   of the world.
 * @param {Number} [y]
 *   The y-coordinate of the top-left corner of the Box. Defaults to the center
 *   of the world.
 * @param {Number} [w]
 *   The width of the Box. Defaults to
 *   {@link Box#DEFAULT_WIDTH Box.prototype.DEFAULT_WIDTH}.
 * @param {Number} [h]
 *   The height of the Box. Defaults to
 *   {@link Box#DEFAULT_HEIGHT Box.prototype.DEFAULT_HEIGHT}.
 * @param {Mixed} [fillStyle="black"]
 *   A default fillStyle to use when drawing the Box. Defaults to black.
 */
var Box = Class.extend({
  init: function(x, y, w, h, fillStyle) {
    /**
     * @property {Number} x
     *   The x-coordinate of the top-left corner of the Box.
     */
    this.x = typeof x !== 'undefined' ? x : Math.floor((world.width-this.DEFAULT_WIDTH)/2);
    /**
     * @property {Number} y
     *   The y-coordinate of the top-left corner of the Box.
     */
    this.y = typeof y !== 'undefined' ? y : Math.floor((world.height-this.DEFAULT_HEIGHT)/2);
    /**
     * @property {Number} width
     *   The width of the Box.
     */
    this.width = w || this.DEFAULT_WIDTH;
    /**
     * @property {Number} height
     *   The height of the Box.
     */
    this.height = h || this.DEFAULT_HEIGHT;
    /**
     * @property {String} fillStyle
     *   A fillStyle to use when drawing the Box if no `src` is specified.
     */
    this.fillStyle = fillStyle || 'black';
  },
  /**
   * The default width of a Box.
   */
  DEFAULT_WIDTH: 80,
  /**
   * The default height of a Box.
   */
  DEFAULT_HEIGHT: 80,
  /**
   * Something that can be drawn by {@link CanvasRenderingContext2D#drawImage}.
   *
   * If not set, a box will be drawn instead using the fillStyle.
   */
  src: null,
  /**
   * The angle (in radians) at which to draw the Box.
   */
  radians: 0,
  /**
   * Draw the Box.
   *
   * Draws the shape in Box#drawDefault() unless the Box#src property is set.
   *
   * Use Box#drawBoundingBox() to draw an outline of the Box.
   *
   * @param {CanvasRenderingContext2D} [ctx]
   *   A canvas graphics context onto which this Box should be drawn. This is
   *   useful for drawing onto {@link Layer}s. If not specified, defaults to
   *   the {@link global#context global context} for the default canvas.
   * @param {Boolean} [smooth=true]
   *   A boolean indicating whether to force the Box to be drawn at whole-pixel
   *   coordinates. If you don't already know that your coordinates will be
   *   integers, this option can speed up painting since the browser does not
   *   have to interpolate the image.
   */
  draw: function(ctx, smooth) {
    ctx = ctx || context;
    if (typeof smooth === 'undefined') {
      smooth = true;
    }
    ctx.save();
    ctx.fillStyle = this.fillStyle;
    var x = this.x, y = this.y, w = this.width, h = this.height;
    if (smooth) {
      x = Math.round(x);
      y = Math.round(y);
    }
    if (this.radians) {
      ctx.translate(x+w/2, y+h/2);
      ctx.rotate(this.radians);
      ctx.translate(-w/2-x, -h/2-y);
    }
    if (this.src) {
      ctx.drawImage(this.src, x, y, w, h);
    }
    else {
      this.drawDefault(ctx, x, y, w, h);
    }
    ctx.restore();
  },
  /**
   * {@link Box#draw Draw} the default shape when no image has been applied.
   *
   * This is useful to override for classes that have different standard
   * appearances, rather than overriding the whole Box#draw() method.
   *
   * @param {CanvasRenderingContext2D} ctx
   *   A canvas graphics context onto which this Box should be drawn.
   * @param {Number} x
   *   The x-coordinate of the upper-left corner of the Box.
   * @param {Number} y
   *   The y-coordinate of the upper-left corner of the Box.
   * @param {Number} w
   *   The width of the Box.
   * @param {Number} h
   *   The height of the Box.
   */
  drawDefault: function(ctx, x, y, w, h) {
    ctx.fillRect(x, y, w, h);
  },
  /**
   * Draw the outline of the Box used to calculate collision.
   *
   * To draw the Box itself, use Box#draw().
   *
   * @param {CanvasRenderingContext2D} [ctx]
   *   A canvas graphics context onto which the outline should be drawn. This
   *   is useful for drawing onto {@link Layer}s. If not specified, defaults to
   *   the {@link global#context global context} for the default canvas.
   * @param {Mixed} [strokeStyle]
   *   A style to use for the box outline.
   */
  drawBoundingBox: function(ctx, strokeStyle) {
    ctx = ctx || context;
    if (strokeStyle) {
      ctx.strokeStyle = strokeStyle;
    }
    ctx.strokeRect(this.x, this.y, this.width, this.height);
  },
  /**
   * Get the x-coordinate of the center of the Box.
   *
   * See also Box#yC()
   */
  xC: function() {
    return this.x + this.width/2;
  },
  /**
   * Get the y-coordinate of the center of the Box.
   *
   * See also Box#xC()
   */
  yC: function() {
    return this.y + this.height/2;
  },
  /**
   * Determine whether this Box overlaps with another Box or set of Boxes.
   *
   * The main difference between Box#collides() and Box#overlaps() is that
   * Box#overlaps() only checks against a single Box and returns a Boolean
   * indicating whether there is overlap, whereas Box#collides() can check
   * against many Boxes and returns the first item to overlap (if any).
   *
   * The collision checking here uses AABB detection, meaning it uses upright
   * rectangles. It is accurate, but not as fast as it could be if there are
   * many objects to check against. If you need faster collision checking
   * against many objects and you're willing to do it manually, consider
   * implementing
   * [spatial partitioning](http://buildnewgames.com/broad-phase-collision-detection/).
   * If you need different collision models (for example with rotated
   * rectangles or different shapes) or if you need more advanced physics
   * simulation (for example to model springs) consider using the
   * [Box2D library](https://github.com/kripken/box2d.js/).
   *
   * @param {Box/Box[]/Collection/TileMap} collideWith
   *   A Box, Collection or Array of Boxes, or TileMap with which to check for
   *   overlap.
   * @param {Boolean} [returnAll=false]
   *   If this method is passed a Collection or TileMap, whether to return all
   *   items in the group that collide (true) or just the first one (false).
   * @param {Boolean} [collideWithSelf=false]
   *   Whether the Box should be considered to collide with itself.
   *
   * @return {Box/Box[]/Boolean}
   *   false if there is no overlap; otherwise, the first item to overlap, or
   *   an array of overlapping items if returnAll is true and collideWith is
   *   a Collection or TileMap.
   */
  collides: function(collideWith, returnAll, collideWithSelf) {
    if (collideWith instanceof Box && (collideWith !== this || !collideWithSelf)) {
      return this.overlaps(collideWith) ? collideWith : false;
    }
    var items = collideWith, found = [];
    if (typeof TileMap !== 'undefined' && collideWith instanceof TileMap) {
      items = collideWith.getAll();
    }
    for (var i = 0, l = items.length; i < l; i++) {
      if (this.overlaps(items[i]) && (items[i] !== this || !collideWithSelf)) {
        if (returnAll) {
          found.push(items[i]);
        }
        else {
          return items[i];
        }
      }
    }
    if (found.length) {
      return found;
    }
    return false;
  },
  /**
   * Determine whether this Box intersects another Box.
   *
   * See Box#collides() for a discussion of the difference.
   *
   * See Box#overlapsX() and Box#overlapsY() for the actual calculations.
   *
   * @param {Box} otherBox The other Box with which to check for collision.
   */
  overlaps: function(otherBox) {
    return this.overlapsX(otherBox) && this.overlapsY(otherBox);
  },
  /**
   * Determine whether this Box intersects another Box on the x-axis.
   *
   * See also Box#overlaps() and Box#overlapsY()
   *
   * @param {Box} otherBox The other Box with which to check for collision.
   */
  overlapsX: function(otherBox) {
    return this.x + this.width > otherBox.x && otherBox.x + otherBox.width > this.x;
  },
  /**
   * Determine whether this Box intersects another Box on the y-axis.
   *
   * See also Box#overlaps() and Box#overlapsX()
   *
   * @param {Box} otherBox The other Box with which to check for collision.
   */
  overlapsY: function(otherBox) {
    return this.y + this.height > otherBox.y && otherBox.y + otherBox.height > this.y;
  },
  /**
   * Determine whether the mouse is hovering over this Box.
   */
  isHovered: function() {
    if (typeof App.isHovered != 'function') {
      if (window.console && console.warn) {
        console.warn('Box#isHovered called, but App.isHovered does not exist.');
      }
      return false;
    }
    return App.isHovered(this);
  },
  /**
   * Listen for a specific event.
   *
   * To only run the callback the first time the event is triggered on this
   * Box, see Box#once(). To remove a callback, see Box#unlisten().
   *
   * @param {String} eventName
   *   The name of the event for which to listen, e.g. "click." The event can
   *   have a namespace using a dot, e.g. "click.custom" will bind to the
   *   "click" event with the "custom" namespace. Namespaces are useful for
   *   unlisten()ing to specific callbacks assigned to that namespace or for
   *   unlisten()ing to callbacks bound to a namespace across multiple events.
   * @param {Function} callback
   *   A function to execute when the relevant event is triggered on the
   *   listening object. The function's `this` object is the listening Box
   *   and it receives any other parameters passed by the trigger call. Usually
   *   an event object is the first parameter, and propagation can be stopped
   *   by calling the event's stopPropagation() method.
   * @param {Number} [weight=0]
   *   An integer indicating the order in which callbacks for the relevant
   *   event should be triggered. Lower numbers cause the callback to get
   *   triggered earlier than higher numbers. This can be useful for getting
   *   around the fact that the canvas doesn't track display order so event
   *   callbacks can't distinguish which object should be triggered first if
   *   multiple listening objects are overlapping.
   */
  listen: function(eventName, callback, weight) {
    if (App.Events) {
      App.Events.listen(this, eventName, callback, weight);
    }
    else if (window.console && console.warn) {
      console.warn('Box#listen called, but App.Events does not exist.');
    }
    return this;
  },
  /**
   * Listen for a specific event and only react the first time it is triggered.
   *
   * This method is exactly the same as Box#listen() except that the specified
   * callback is only executed the first time it is triggered. To remove a
   * callback, see Box#unlisten().
   *
   * @param {String} eventName
   *   The name of the event for which to listen, e.g. "click." The event can
   *   have a namespace using a dot, e.g. "click.custom" will bind to the
   *   "click" event with the "custom" namespace. Namespaces are useful for
   *   unlisten()ing to specific callbacks assigned to that namespace or for
   *   unlisten()ing to callbacks bound to a namespace across multiple events.
   * @param {Function} callback
   *   A function to execute when the relevant event is triggered on the
   *   listening object. The function's `this` object is the listening Box
   *   and it receives any other parameters passed by the trigger call. Usually
   *   an event object is the first parameter, and propagation can be stopped
   *   by calling the event's stopPropagation() method.
   * @param {Number} [weight=0]
   *   An integer indicating the order in which callbacks for the relevant
   *   event should be triggered. Lower numbers cause the callback to get
   *   triggered earlier than higher numbers. This can be useful for getting
   *   around the fact that the canvas doesn't track display order so event
   *   callbacks can't distinguish which object should be triggered first if
   *   multiple listening objects are overlapping.
   */
  once: function(eventName, callback, weight) {
    if (App.Events) {
      App.Events.once(this, eventName, callback, weight);
    }
    else if (window.console && console.warn) {
      console.warn('Box#once called, but App.Events does not exist.');
    }
    return this;
  },
  /**
   * Stop listening for a specific event.
   *
   * To listen for an event, use Box#listen() or Box#once().
   *
   * @param {String} eventName
   *   The name of the event for which to listen, e.g. "click." The event can
   *   have a namespace using a dot, e.g. "click.custom" will unbind obj's
   *   listeners for the "click" that are using the "custom" namespace. You can
   *   also unlisten to multiple events using the same namespace, e.g.
   *   ".custom" could unlisten to "mousemove.custom" and "touchmove.custom."
   *   If the event specified does not have a namespace, all callbacks will be
   *   unbound regardless of their namespace.
   */
  unlisten: function(eventName) {
    if (App.Events) {
      App.Events.unlisten(this, eventName);
    }
    else if (window.console && console.warn) {
      console.warn('Box#unlisten called, but App.Events does not exist.');
    }
    return this;
  },
  /**
   * Destroy the Box.
   *
   * By default, this method does nothing. Override this method to trigger an
   * event when the object is destroyed. For example, this could allow
   * displaying an explosion when a bullet hits a target.
   *
   * This method can also be used to release memory allocated when the Box is
   * initialized, though by default, only Players need to be destroy()ed to
   * release memory (specifically this releases keyboard control).
   */
  destroy: function() {},

  /**
   * @method stoodOn
   *
   * Invoked on solids every frame when an Actor is standing on them and
   * Actor#GRAVITY is enabled. This is useful for changing Actor behavior
   * depending on the surface. Example applications include making a surface
   * slippery (by changing {@link Actor#DAMPING_FACTOR}), making it bouncy (by
   * adding upward velocity), or changing the Actor's speed. This can also be
   * used for special actions like making the Box move only when a Player is
   * standing on it.
   *
   * @param {Actor} stander
   *   The Actor that is standing on this Box.
   */
  stoodOn: null,
});

/**
 * Actors are {@link Box Boxes} that can move.
 *
 * @extends Box
 */
var Actor = Box.extend({

  /**
   * The velocity the Actor can move in pixels per second.
   */
  MOVEAMOUNT: 400,

  /**
   * Whether gravity (downward acceleration) is enabled.
   *
   * This is effectively a toggle between a top-down and side view.
   */
  GRAVITY: false,

  /**
   * Gravitational acceleration in pixels per second-squared.
   *
   * Has no effect if GRAVITY is false. Setting to 0 (zero) has a similar
   * physical effect to disabling gravity.
   */
  G_CONST: 21,

  /**
   * Jump velocity (impulse) in pixels per second.
   *
   * Has no effect if GRAVITY is false. Set to 0 (zero) to disable jumping.
   */
  JUMP_VEL: 500,

  /**
   * The minimum number of seconds required between jumps.
   *
   * Has no effect if GRAVITY is false or JUMP_VEL is 0 (zero).
   */
  JUMP_DELAY: 0.25,

  /**
   * Percent of normal horizontal velocity Actors can move while in the air.
   *
   * Note that if Actors are moving horizontally before jumping, they keep
   * moving at the same speed in the air; in this case air control only takes
   * effect if they switch direction mid-air. Otherwise, air control only
   * applies if they started moving horizontally after they entered the air.
   */
  AIR_CONTROL: 0.25,

  /**
   * Whether to require that the jump key is released before jumping again.
   *
   * Specifically, this is a boolean which, when true, restricts the Actor from
   * jumping after being in the air until after the Actor is on the ground with
   * the jump key released. This limits the ability to "bounce" by holding down
   * the jump key. This behavior depends on being notified of when keys are
   * released via the release() method, which happens automatically for
   * Players. If you enable this for standard Actors, the meaning of a "key
   * press" is artificial, so you must make sure to call release() before you
   * make the Actor jump again.
   */
  JUMP_RELEASE: false,

  /**
   * The number of times an Actor can jump without touching the ground.
   *
   * -1 allows the Actor to jump in the air an infinite number of times. A
   * value of zero is the same as a value of one (i.e. a value of zero will not
   * stop the Actor from having one jump).
   */
  MULTI_JUMP: 0,

  /**
   * Whether to make the Actor continue moving in the last direction specified.
   */
  CONTINUOUS_MOVEMENT: false,

  /**
   * Whether the Actor will be restricted to not move outside the world.
   */
  STAY_IN_WORLD: true,

  /**
   * The fractional velocity damping factor.
   *
   * If set, this affects whether the Actor can turn on a dime or how much it
   * slides around. Higher means more movement control (less sliding).
   *
   * If you want specific surfaces to feel slippery, set this when the Actor
   * moves onto and off of those surfaces. One way to do this is using the
   * {@link Box#stoodOn stoodOn() method}.
   *
   * Numeric values are interpreted as damping factors. If this is null, full
   * damping is applied (the Actor stops and turns on a dime).
   *
   * Damping does not affect vertical movement when gravity is enabled.
   */
  DAMPING_FACTOR: null,

  /**
   * The last direction (key press) that resulted in looking in a direction.
   *
   * If GRAVITY is enabled, this Array will only contain left or right keys.
   * This is because left/right+up/down is a valid direction but does not
   * result in looking diagonally.
   */
  lastLooked: [],

  /**
   * Whether the Actor is being mouse-dragged.
   * @readonly
   */
  isBeingDragged: false,

  /**
   * An Array of target Boxes onto which this Actor can be dropped.
   *
   * You must call Actor#setDraggable(true) to enable dragging the Actor.
   *
   * Drop targets can change how they look when a draggable object is hovered
   * over them by testing `this.isHovered() && App.isSomethingBeingDragged` in
   * their {@link Box#draw draw()} methods. They can change how they look or
   * perform some action when a draggable object is dropped onto them by
   * listening for the {@link Box#event-canvasdrop canvasdrop event}.
   */
  dropTargets: [],

  /**
   * The horizontal component of the velocity.
   *
   * Use Actor#getVelocityVector() if you want to get velocity as a vector.
   */
  xVelocity: 0,

  /**
   * The vertical component of the velocity.
   *
   * Use Actor#getVelocityVector() if you want to get velocity as a vector.
   */
  yVelocity: 0,

  /**
   * The horizontal component of the acceleration.
   *
   * Use Actor#getAccelerationVector() if you want to get acceleration as a
   * vector.
   */
  xAcceleration: 0,

  /**
   * The vertical component of the acceleration.
   *
   * Use Actor#getAccelerationVector() if you want to get acceleration as a
   * vector.
   */
  yAcceleration: 0,

  /**
   * Keys specific to this actor.
   *
   * Defaults to the global {@link global#keys keys} if not set (and uses the
   * same structure).
   *
   * See also Actor#processInput().
   */
  keys: undefined,

  // Used internally; not publicly documented. Reading is okay, but don't write.
  lastJump: 0, // Time when the last jump occurred in milliseconds since the epoch
  lastDirection: [], // The last direction (i.e. key press) passed to processInput()
  jumpDirection: {right: false, left: false}, // Whether the Actor was moving horizontally before jumping
  jumpKeyDown: false, // Whether the jump key is currently pressed
  numJumps: 0, // Number of jumps since the last time the Actor was touching the ground
  inAir: false, // Whether the Actor is in the air
  fallLeft: null, // The direction the Actor was moving before falling
  isDraggable: false, // Whether the Actor is draggable
  dragStartX: 0, // Last position of the Actor before being dragged
  dragStartY: 0, // Last position of the Actor before being dragged
  animLoop: '', // Current SpriteMap animation sequence

  /**
   * @constructor
   *   Initialize an Actor.
   *
   * Takes the same parameters as the Box constructor.
   * **Inherited documentation:**
   *
   * @inheritdoc Box#constructor
   */
  init: function() {
    this._super.apply(this, arguments);
    this.lastX = this.x;
    this.lastY = this.y;
    this.lastDirection = [];
    this.lastLooked = [];
    this.jumpDirection = {right: false, left: false};
    this.dropTargets = [];
    if (arguments.length < 5) {
      this.fillStyle = 'lightBlue';
    }
  },

  /**
   * @inheritdoc Box#draw
   */
  draw: function() {
    if (this.src instanceof SpriteMap && this.animLoop) {
      this.src.use(this.animLoop); // Switch to the active animation loop
    }
    return this._super.apply(this, arguments);
  },

  /**
   * Actors draw as a smiley face by default.
   *
   * **Inherited documentation:**
   *
   * @inheritdoc Box#drawDefault
   */
  drawDefault: function(ctx, x, y, w, h) {
    x = x + w/2;
    y = y + h/2;
    var r = (w+h)/4;

    // Circle
    ctx.circle(x, y, r, this.fillStyle, 'black');

    // Smile
    ctx.beginPath();
    ctx.arc(x, y, r*0.6, Math.PI*0.1, Math.PI*0.9, false);
    ctx.lineWidth = Math.max(Math.ceil(r/15), 1);
    ctx.strokeStyle = 'black';
    ctx.stroke();

    // Eyes
    ctx.beginPath();
    ctx.arc(x - r*0.3, y - r*0.25, Math.max(Math.ceil(r/15), 1), 0, 2 * Math.PI, false);
    ctx.fillStyle = 'black';
    ctx.fill();
    ctx.arc(x + r*0.3, y - r*0.25, Math.max(Math.ceil(r/15), 1), 0, 2 * Math.PI, false);
    ctx.fill();
  },

  /**
   * Update the Actor for a new frame.
   *
   * @param {String[]} [direction]
   *   An Array of directions in which to move the Actor. Directions are
   *   expected to correspond to keys on the keyboard (as described by
   *   {@link jQuery.hotkeys}).
   */
  update: function(direction) {
    this.lastX = this.x;
    this.lastY = this.y;
    if (this.isBeingDragged && window.Mouse) {
      this.x = Mouse.Coords.worldX() - this.width/2;
      this.y = Mouse.Coords.worldY() - this.height/2;
    }
    else {
      this.processInput(direction);
      this.ambientAcceleration();
      this.move();
      if (App.Utils.almostEqual(this.lastX, this.x, 0.000001)) {
        this.fallLeft = null;
      }
      // If we're not actually falling, collideSolid() will catch us.
      if (this.GRAVITY &&
          (this.y + this.height != world.height || !this.STAY_IN_WORLD)) {
        this.startFalling();
      }
    }
    this.updateAnimation();
    this.dampVelocity();
  },

  /**
   * Process directions and adjust motion accordingly.
   *
   * Called from Actor#update().
   *
   * @param {String[]} direction
   *   An Array of directions in which to move the Actor. Valid directions are
   *   expected to correspond to keys on the keyboard by default (as described
   *   by {@link jQuery.hotkeys}) though for Actors that are not Players the
   *   directions will not normally be sent from actual key presses.
   */
  processInput: function(direction) {
    var left = false,
        right = false,
        looked = false,
        anyIn = App.Utils.anyIn,
        keys = this.keys || window.keys;
    // Bail if someone deleted the keys variable.
    if (typeof keys === 'undefined') {
      return;
    }
    if (typeof direction === 'undefined' || direction.length === 0) {
      // For continuous movement, if no direction is given, use the last one.
      if (this.CONTINUOUS_MOVEMENT) {
        direction = this.lastLooked;
      }
      // No need to keep processing if no directions were given.
      else {
        return;
      }
    }
    this.lastDirection = direction.slice(); // shallow copy

    // Move left.
    if (anyIn(keys.left, direction)) {
      left = true;
      looked = true;
      this.fallLeft = true;
      if (this.GRAVITY && this.isInAir()) {
        if (this.jumpDirection.right || !this.jumpDirection.left) {
          this.xVelocity = -this.MOVEAMOUNT * this.AIR_CONTROL;
          this.jumpDirection.right = false;
          this.jumpDirection.left = false;
        }
      }
      else {
        this.xVelocity = -this.MOVEAMOUNT;
      }
    }
    // Move right.
    else if (anyIn(keys.right, direction)) {
      right = true;
      looked = true;
      this.fallLeft = false;
      if (this.GRAVITY && this.isInAir()) {
        if (this.jumpDirection.left || !this.jumpDirection.right) {
          this.xVelocity = this.MOVEAMOUNT * this.AIR_CONTROL;
          this.jumpDirection.right = false;
          this.jumpDirection.left = false;
        }
      }
      else {
        this.xVelocity = this.MOVEAMOUNT;
      }
    }

    // Move up / jump.
    if (anyIn(keys.up, direction)) {
      if (!this.GRAVITY) {
        this.yVelocity = -this.MOVEAMOUNT;
        looked = true;
      }
      else if (!this.isInAir() ||
          this.MULTI_JUMP > this.numJumps ||
          this.MULTI_JUMP == -1) {
        var now = App.physicsTimeElapsed;
        if (now - this.lastJump > this.JUMP_DELAY && // sufficient delay
            (!this.JUMP_RELEASE || !this.jumpKeyDown)) { // press jump again
          this.yVelocity = -this.JUMP_VEL;
          this.yAcceleration = 0; // Don't aggregate gravity
          this.lastJump = now;
          this.jumpDirection.right = right;
          this.jumpDirection.left = left;
          this.numJumps++;
          this.inAir = true;
        }
      }
      this.jumpKeyDown = true;
    }
    // Move down.
    else if (anyIn(keys.down, direction)) {
      if (!this.isInAir() || !this.GRAVITY) { // don't allow accelerating downward when falling
        this.yVelocity = this.MOVEAMOUNT;
        looked = true;
      }
    }

    if (looked) {
      this.lastLooked = direction.slice(); // shallow copy
      // Avoid looking anywhere but right or left if gravity is enabled.
      // If we didn't have this here, we would be able to look diagonally.
      if (this.GRAVITY) {
        for (var i = this.lastLooked.length-1; i >= 0; i--) {
          if (keys.left.indexOf(this.lastLooked[i]) == -1 &&
              keys.right.indexOf(this.lastLooked[i]) == -1) {
            this.lastLooked.splice(i, 1);
          }
        }
      }
    }
  },

  /**
   * Calculate acceleration from the environment.
   *
   * Acceleration from user input is calculated in Actor#processInput().
   */
  ambientAcceleration: function() {
    // Gravity.
    if (this.GRAVITY) {
      // Air movement (not initiated by user input).
      if (this.isInAir()) {
        this.yAcceleration += this.G_CONST;
        if (this.jumpDirection.left) {
          this.xVelocity = -this.MOVEAMOUNT;
        }
        else if (this.jumpDirection.right) {
          this.xVelocity = this.MOVEAMOUNT;
        }
      }
      else {
        this.stopFalling();
      }
    }
  },

  /**
   * Actually move the Actor.
   */
  move: function() {
    var delta = App.physicsDelta, d2 = delta / 2;
    // Apply half acceleration (first half of midpoint formula)
    this.xVelocity += this.xAcceleration*d2;
    this.yVelocity += this.yAcceleration*d2;
    // Don't let diagonal movement be faster than axial movement
    var xV = this.xVelocity, yV = this.yVelocity;
    if (xV !== 0 && yV !== 0 && !this.GRAVITY) {
      var magnitude = Math.max(Math.abs(xV), Math.abs(yV));
      var origMag = Math.sqrt(xV*xV + yV*yV);
      var scale = magnitude / origMag;
      this.xVelocity *= scale;
      this.yVelocity *= scale;
    }
    // Apply thrust
    this.x += this.xVelocity*delta;
    this.y += this.yVelocity*delta;
    // Apply half acceleration (second half of midpoint formula)
    this.xVelocity += this.xAcceleration*d2;
    this.yVelocity += this.yAcceleration*d2;
    // Clip
    this.stayInWorld();
  },

  /**
   * Force the Actor to stay inside the world.
   */
  stayInWorld: function() {
    if (this.STAY_IN_WORLD) {
      if (this.x < 0) {
        this.x = 0;
      }
      else if (this.x + this.width > world.width) {
        this.x = world.width - this.width;
      }
      if (this.y < 0) {
        this.y = 0;
      }
      else if (this.y + this.height > world.height) {
        this.y = world.height - this.height;
        this.stopFalling();
      }
    }
  },

  /**
   * Damp the Actor's velocity.
   *
   * This affects how much control the Actor has over its movement, i.e.
   * whether the Actor can stop and turn on a dime or whether it slides around
   * with momentum.
   */
  dampVelocity: function() {
    if (this.DAMPING_FACTOR !== null &&
        !App.Utils.almostEqual(this.xVelocity, 0, 0.0001)) {
      this.xVelocity *= 1 - this.DAMPING_FACTOR * App.physicsDelta;
      if (!this.GRAVITY && !App.Utils.almostEqual(this.yVelocity, 0, 0.0001)) {
        this.yVelocity *= 1 - this.DAMPING_FACTOR * App.physicsDelta;
      }
      return;
    }
    this.xVelocity = 0;
    if (!this.GRAVITY) {
      this.yVelocity = 0;
    }
  },

  /**
   * Add velocity as a vector.
   *
   * See also Actor#setVelocityVector() and Actor#getVelocityVector().
   *
   * @param {Number} radialDir The direction of the vector to add, in radians.
   * @param {Number} magnitude The magnitude ot the vector to add, in pixels.
   */
  addVelocityVector: function(radialDir, magnitude) {
    this.xVelocity += magnitude * Math.cos(radialDir);
    this.yVelocity += magnitude * Math.sin(radialDir);
  },

  /**
   * Set velocity as a vector.
   *
   * See also Actor#addVelocityVector() and Actor#getVelocityVector().
   *
   * @param {Number} radialDir The direction of the vector to set, in radians.
   * @param {Number} magnitude The magnitude ot the vector to set, in pixels.
   */
  setVelocityVector: function(radialDir, magnitude) {
    this.xVelocity = magnitude * Math.cos(radialDir);
    this.yVelocity = magnitude * Math.sin(radialDir);
  },

  /**
   * Get the velocity vector.
   *
   * See also Actor#addVelocityVector() and Actor#setVelocityVector().
   *
   * @return {Object}
   *   An object with `magnitude` and `direction` attributes indicating the
   *   velocity of the Actor.
   */
  getVelocityVector: function() {
    return {
      magnitude: Math.sqrt(this.xVelocity*this.xVelocity + this.yVelocity*this.yVelocity),
      direction: Math.atan2(this.yVelocity, this.xVelocity),
    };
  },

  /**
   * Add acceleration as a vector.
   *
   * See also Actor#setAccelerationVector() and Actor#getAccelerationVector().
   *
   * @param {Number} radialDir The direction of the vector to add, in radians.
   * @param {Number} magnitude The magnitude ot the vector to add, in pixels.
   */
  addAccelerationVector: function(radialDir, magnitude) {
    this.xAcceleration += magnitude * Math.cos(radialDir);
    this.yAcceleration += magnitude * Math.sin(radialDir);
  },

  /**
   * Set acceleration as a vector.
   *
   * See also Actor#addAccelerationVector() and Actor#getAccelerationVector().
   *
   * @param {Number} radialDir The direction of the vector to set, in radians.
   * @param {Number} magnitude The magnitude ot the vector to set, in pixels.
   */
  setAccelerationVector: function(radialDir, magnitude) {
    this.xAcceleration = magnitude * Math.cos(radialDir);
    this.yAcceleration = magnitude * Math.sin(radialDir);
  },

  /**
   * Get the acceleration vector.
   *
   * See also Actor#addAccelerationVector() and Actor#setAccelerationVector().
   *
   * @return {Object}
   *   An object with `magnitude` and `direction` attributes indicating the
   *   acceleration of the Actor.
   */
  getAccelerationVector: function() {
    return {
      magnitude: Math.sqrt(this.xVelocity*this.xVelocity + this.yVelocity*this.yVelocity),
      direction: Math.atan2(this.yVelocity, this.xVelocity), // yes, this order is correct
    };
  },

  /**
   * Move this Actor outside of another Box so that it no longer overlaps.
   *
   * This is called as part of Actor#collideSolid().
   *
   * See also Actor#moveOutsideX() and Actor#moveOutsideY().
   *
   * @param {Box} other
   *   The other Box that this Actor should be moved outside of.
   *
   * @return {Object}
   *   An object with `x` and `y` properties indicating how far this Actor
   *   moved in order to be outside of the other Box, in pixels.
   */
  moveOutside: function(other) {
    var overlapsX = Math.min(this.x + this.width - other.x, other.x + other.width - this.x),
        overlapsY = Math.min(this.y + this.height - other.y, other.y + other.height - this.y);

    // It matters which axis we move first.
    if (overlapsX <= overlapsY) {
      return {
        x: this.moveOutsideX(other),
        y: this.moveOutsideY(other),
      };
    }
    return {
      y: this.moveOutsideY(other),
      x: this.moveOutsideX(other),
    };
  },

  /**
   * Move this Actor outside of another Box on the x-axis to avoid overlap.
   *
   * See also Actor#moveOutside().
   *
   * @param {Box} other
   *   The other Box that this Actor should be moved outside of.
   *
   * @return {Number}
   *   The distance in pixels that this Actor moved on the x-axis.
   */
  moveOutsideX: function(other) {
    var moved = 0, movedTo;
    // Only adjust if we're intersecting
    if (this.overlaps(other)) {
      // If our center is left of their center, move to the left side
      if (this.x + this.width / 2 < other.x + other.width / 2) {
        movedTo = other.x - this.width - 0.01;
      }
      // If our center is right of their center, move to the right side
      else {
        movedTo = other.x + other.width + 0.01;
      }
      moved = movedTo - this.x;
      this.x = movedTo;
    }
    return moved;
  },

  /**
   * Move this Actor outside of another Box on the y-axis to avoid overlap.
   *
   * See also Actor#moveOutside().
   *
   * @param {Box} other
   *   The other Box that this Actor should be moved outside of.
   *
   * @return {Number}
   *   The distance in pixels that this Actor moved on the y-axis.
   */
  moveOutsideY: function(other) {
    var moved = 0, movedTo;
    // Only adjust if we're intersecting
    if (this.overlaps(other)) {
      // If our center is above their center, move to the top
      if (this.y + this.height / 2 <= other.y + other.height / 2) {
        movedTo = other.y - this.height - 1;
      }
      // If our center is below their center, move to the bottom
      else {
        movedTo = other.y + other.height + 1;
      }
      moved = movedTo - this.y;
      this.y = movedTo;
    }
    return moved;
  },

  /**
   * Start falling.
   *
   * This method has no meaning if {@link Actor#GRAVITY GRAVITY} is false.
   *
   * Related: Actor#stopFalling(), Actor#isInAir(), Actor#isJumping(),
   * Actor#isFalling(), Actor#hasAirMomentum()
   */
  startFalling: function() {
    // Keep going at the same horizontal speed when walking off a ledge.
    if (!this.inAir && this.fallLeft !== null) {
      this.jumpDirection.left = this.fallLeft;
      this.jumpDirection.right = !this.fallLeft;
    }
    this.inAir = true;
  },

  /**
   * Notify the Actor that it has landed.
   *
   * This method has no meaning if GRAVITY is false.
   *
   * Related: Actor#startFalling(), Actor#isInAir(), Actor#isJumping(),
   * Actor#isFalling(), Actor#hasAirMomentum()
   */
  stopFalling: function() {
    if (this.yAcceleration > 0) {
      this.yAcceleration = 0;
    }
    if (this.yVelocity > 0) {
      this.yVelocity = 0;
    }
    this.numJumps = 0;
    this.inAir = false;
  },

  /**
   * Check whether the Actor is in the air or not.
   *
   * Related: Actor#startFalling(), Actor#stopFalling(), Actor#isJumping(),
   * Actor#isFalling(), Actor#hasAirMomentum()
   */
  isInAir: function() {
    return this.inAir;
  },

  /**
   * Check whether the Actor is jumping or not.
   *
   * Related: Actor#startFalling(), Actor#stopFalling(), Actor#isInAir(),
   * Actor#isFalling(), Actor#hasAirMomentum()
   */
  isJumping: function() {
    return this.numJumps > 0;
  },

  /**
   * Check whether the Actor is in the air from falling (as opposed to jumping).
   *
   * Related: Actor#startFalling(), Actor#stopFalling(), Actor#isInAir(),
   * Actor#isJumping(), Actor#hasAirMomentum()
   */
  isFalling: function() {
    return this.isInAir() && this.numJumps === 0;
  },

  /**
   * Check whether the Actor has air momentum (as opposed to air control).
   *
   * Related: Actor#startFalling(), Actor#stopFalling(), Actor#isInAir(),
   * Actor#isJumping(), Actor#isFalling()
   */
  hasAirMomentum: function() {
    return this.fallLeft !== null ||
      this.jumpDirection.left ||
      this.jumpDirection.right;
  },

  /**
   * Check whether this Actor is standing on top of a Box.
   *
   * @param {Box/Collection/TileMap} box The Box or set of Boxes to check.
   */
  standingOn: function(box) {
    if ((typeof Collection !== 'undefined' && box instanceof Collection) ||
        (typeof TileMap !== 'undefined' && box instanceof TileMap)) {
      var items = box.getAll();
      for (var i = 0, l = items.length; i < l; i++) {
        if (this.standingOn(items[i])) {
          return true;
        }
      }
      return false;
    }
    return this.overlapsX(box) &&
      App.Utils.almostEqual(this.y + this.height, box.y, 1);
  },

  /**
   * Check collision with solids and adjust the Actor's position as necessary.
   *
   * This method has the side-effect that it will stop the Actor from falling
   * if it is standing on a Box.
   *
   * The collision checking here uses AABB detection, meaning it uses upright
   * rectangles. It is accurate, but not as fast as it could be if there are
   * many objects to check against. If you need faster collision checking
   * against many objects and you're willing to do it manually, consider
   * implementing
   * [spatial partitioning](http://buildnewgames.com/broad-phase-collision-detection/).
   * If you need different collision models (for example with rotated
   * rectangles or different shapes) or if you need more advanced physics
   * simulation (for example to model springs) consider using the
   * [Box2D library](https://github.com/kripken/box2d.js/).
   *
   * @param {Box/Collection/TileMap} collideWith
   *   A Box, Collection, or TileMap of objects with which to check collision.
   *
   * @return {Object}
   *   An Object with `x` and `y` properties, both Booleans indicating whether
   *   the Actor collided with something in the respective direction.
   */
  collideSolid: function(collideWith) {
    var collided = {x: 0, y: 0};
    if (collideWith instanceof Box || collideWith instanceof ImageWrapper) {
      collided = this._collideSolidBox(collideWith);
    }
    else if ((typeof Collection !== 'undefined' && collideWith instanceof Collection) ||
        (typeof TileMap !== 'undefined' && collideWith instanceof TileMap)) {
      var items = collideWith.getAll();
      for (var i = 0, l = items.length; i < l; i++) {
        var c = this._collideSolidBox(items[i]);
        if (c.x) {
          collided.x = c.x;
        }
        if (c.y) {
          collided.y = c.y;
        }
      }
    }
    collided.x = !!collided.x;
    collided.y = !!collided.y;
    this.updateAnimation(collided);
    return collided;
  },

  /**
   * Check collision with a single solid and adjust the Actor's position.
   *
   * This method has the side-effect that it will stop the Actor from falling if
   * it is standing on the Box.
   *
   * See also Actor#collideSolid().
   *
   * @param {Box} collideWith
   *   A Box with which to check collision.
   *
   * @return {Object}
   *   An object with `x` and `y` properties indicating how far this Actor
   *   moved in order to be outside of the Box, in pixels. If both properties
   *   are `0` then the Actor and Box do not overlap.
   *
   * @ignore
   */
  _collideSolidBox: function(collideWith) {
    // "Falling" here really just means "not standing on top of this Box."
    var falling = true, collided = {x: 0, y: 0};
    // If we moved a little too far and now intersect a solid, back out.
    if (this.overlaps(collideWith)) {
      collided = this.moveOutside(collideWith);
    }
    // If gravity is on, check standing/falling behavior.
    if (this.GRAVITY) {
      // We actually want to check if the last X-position would have been
      // standing, so move back there, check, and then move back to the current
      // position. This is because if a player jumps while moving towards a
      // wall, they could match the standing condition as they just barely
      // reach the top, which will stop their jump arc. If their x-position
      // from the last frame would have been standing, though, we can assume
      // they were already standing rather than jumping.
      var x = this.x;
      this.x = this.lastX;
      if (this.standingOn(collideWith)) {
        this.stopFalling();
        falling = false;
        if (typeof collideWith.stoodOn == 'function') {
          collideWith.stoodOn(this);
        }
      }
      this.x = x;
      // If we're in the air and we hit something, stop the momentum.
      if (falling && (collided.x || collided.y)) {
        // If we hit the bottom, stop rising.
        if (App.Utils.almostEqual(this.y, collideWith.y + collideWith.height, 1)) {
          if (this.yAcceleration < 0) {
            this.yAcceleration = 0;
          }
          if (this.yVelocity < 0) {
            this.yVelocity = 0;
          }
        }
        // If we hit a side, stop horizontal momentum.
        else {
          this.jumpDirection.left = false;
          this.jumpDirection.right = false;
        }
      }
    }
    return collided;
  },

  /**
   * Change the Actor's animation sequence if it uses a {@link SpriteMap}.
   *
   * All animations fall back to the "stand" animation if they are not
   * available. The "jumpRight" and "jumpLeft" sequences will try to fall back
   * to the "lookRight" and "lookLeft" sequences first, respectively, if they
   * are not available. Animations that will play by default if they are
   * available include:
   *
   * - stand (required)
   * - left
   * - right
   * - up
   * - down
   * - upRight
   * - upLeft
   * - downRight
   * - downLeft
   * - jump
   * - fall
   * - jumpRight
   * - jumpLeft
   * - lookRight
   * - lookLeft
   * - lookUp
   * - lookDown
   * - lookUpRight
   * - lookUpLeft
   * - lookDownRight
   * - lookDownLeft
   * - drag
   *
   * Override this function if you want to modify the custom rules for which
   * animations to play (or what the animations' names are).
   *
   * This function does nothing if the Actor's `src` attribute is not a
   * SpriteMap.
   *
   * See also Actor#useAnimation().
   *
   * @param {Object} [collided]
   *   An Object with `x` and `y` properties, both Booleans indicating whether
   *   the Actor collided with something in the respective direction.
   */
  updateAnimation: function(collided) {
    if (!(this.src instanceof SpriteMap)) {
      return;
    }
    var keys = this.keys || window.keys,
        lastDirection = this.lastDirection,
        anyIn = App.Utils.anyIn,
        keysIsDefined = typeof keys !== 'undefined'; // Don't fail if "keys" was removed
    // Don't let shooting make us change where we're looking.
    if (keysIsDefined &&
        typeof keys.shoot !== 'undefined' &&
        anyIn(keys.shoot, lastDirection)) {
      lastDirection = this.lastLooked;
    }
    if (this.isBeingDragged) {
      this.useAnimation('drag', 'stand');
    }
    else if (this.isInAir()) {
      if (this.x > this.lastX) {
        this.useAnimation('jumpRight', 'lookRight', 'stand');
      }
      else if (this.x < this.lastX) {
        this.useAnimation('jumpLeft', 'lookLeft', 'stand');
      }
      else if (this.isJumping()) {
        this.useAnimation('jump', 'stand');
      }
      else {
        this.useAnimation('fall', 'stand');
      }
    }
    else if (this.y > this.lastY) {
      if (this.x > this.lastX) {
        this.useAnimation('downRight', 'stand');
      }
      else if (this.x < this.lastX) {
        this.useAnimation('downLeft', 'stand');
      }
      else {
        this.useAnimation('down', 'stand');
      }
    }
    else if (this.y < this.lastY) {
      if (this.x > this.lastX) {
        this.useAnimation('upRight', 'stand');
      }
      else if (this.x < this.lastX) {
        this.useAnimation('upLeft', 'stand');
      }
      else {
        this.useAnimation('up', 'stand');
      }
    }
    else if (this.x > this.lastX) {
      this.useAnimation('right', 'stand');
    }
    else if (this.x < this.lastX) {
      this.useAnimation('left', 'stand');
    }
    else if (keysIsDefined && anyIn(keys.up, lastDirection)) {
      if (anyIn(keys.right, lastDirection)) {
        this.useAnimation('lookUpRight', 'stand');
      }
      else if (anyIn(keys.left, lastDirection)) {
        this.useAnimation('lookUpLeft', 'stand');
      }
      else {
        this.useAnimation('lookUp', 'stand');
      }
    }
    else if (keysIsDefined && anyIn(keys.down, lastDirection)) {
      if (anyIn(keys.right, lastDirection)) {
        this.useAnimation('lookDownRight', 'stand');
      }
      else if (anyIn(keys.left, lastDirection)) {
        this.useAnimation('lookDownLeft', 'stand');
      }
      else {
        this.useAnimation('lookDown', 'stand');
      }
    }
    else if (keysIsDefined && anyIn(keys.right, lastDirection)) {
      this.useAnimation(collided && collided.x ? 'right' : 'lookRight', 'stand');
    }
    else if (keysIsDefined && anyIn(keys.left, lastDirection)) {
      this.useAnimation(collided && collided.x ? 'left' : 'lookLeft', 'stand');
    }
    else {
      this.useAnimation('stand');
    }
  },

  /**
   * Try to switch to a different {@link SpriteMap} animation sequence.
   *
   * Takes animation sequence names as arguments as switches to the first named
   * sequence that exists in the SpriteMap. If you already know what animation
   * sequences you have available, you might as well just call `this.src.use()`
   * directly.
   *
   * See also Actor#updateAnimation().
   *
   * @param {Arguments} ...
   *   Animation sequence names. Switches to the first one that the SpriteMap
   *   defines.
   *
   * @return {String/Boolean}
   *   The name of the animation sequence to which the Actor switched, if
   *   successful; false otherwise.
   */
  useAnimation: function() {
    if (typeof this.src.maps === 'undefined') {
      return;
    }
    for (var i = 0; i < arguments.length; i++) {
      var a = arguments[i];
      if (this.src.maps[a]) {
        this.animLoop = a;
        return a;
      }
    }
    return false;
  },

  /**
   * Toggle whether the Actor can be dragged around by the mouse.
   *
   * Note that dragged Actors still follow collision rules. (It is possible to
   * drag an Actor through a wall, but Actors cannot be dropped inside of
   * something solid they collide with.)
   *
   * @param {Boolean} on Whether to enable or disable dragging.
   */
  setDraggable: function(on) {
    if (this.isDraggable && on) {
      return this;
    }
    else if (!on) {
      this.isDraggable = false;
      this.unlisten('.drag');
      return this;
    }
    else if (!App.Events) {
      if (window.console && console.warn) {
        console.warn('Actor#setDraggable called, but App.Events does not exist.');
      }
      return this;
    }
    else if (!window.Mouse) {
      if (window.console && console.warn) {
        console.warn('Actor#setDraggable called, but window.Mouse does not exist.');
      }
      return this;
    }
    this.isDraggable = true;
    this.listen('mousedown.drag touchstart.drag', function() {
      App.isSomethingBeingDragged = true;
      this.isBeingDragged = true;
      this.dragStartX = this.x;
      this.dragStartY = this.y;
      /**
       * @event canvasdragstart
       *   Fires on the document when the user begins dragging an object,
       *   i.e. when the player clicks on or touches an object. Multiple
       *   objects can be dragged at once if they overlap, and this event will
       *   be triggered once for each of them.
       * @param {Actor} obj The object being dragged.
       * @member global
       */
      jQuery(document).trigger('canvasdragstart', [this]);
    });
    this.listen('canvasdragstop.drag', function() {
      this.isBeingDragged = false;
      // If there are no drop targets, the Actor can be dropped anywhere.
      // If there are drop targets and we're not over one, snap back to the
      // starting point.
      var target = this.collides(this.dropTargets);
      if (this.dropTargets.length && !target) {
        this.x = this.dragStartX;
        this.y = this.dragStartY;
      }
      else if (target) {
        /**
         * @event canvasdrop
         *   Fires on the document when a draggable Actor is dropped onto a
         *   target. This is used **internally** to trigger the event on the
         *   target directly.
         * @param {Box} target The drop target.
         * @member global
         * @ignore
         */
        jQuery(document).trigger('canvasdrop', [target]);
      }
    });
    return this;
  },

  /**
   * Determine whether this Actor is draggable.
   */
  getDraggable: function() {
    return this.isDraggable;
  },

  /**
   * Notify the Actor that a direction is no longer being given.
   *
   * This is useful when Actors need to distinguish between directions being
   * given continuously (such as when holding down a key) and those being given
   * intermittently (such as a simple key press).
   *
   * @param {String[]} releasedDirections
   *   An Array containing directions that are no longer being given.
   */
  release: function(releasedDirections) {
    var keys = this.keys || window.keys;
    if (this.GRAVITY && typeof keys !== 'undefined' &&
        App.Utils.anyIn(keys.up, releasedDirections)) {
      this.jumpKeyDown = false;
    }
  },
});

/**
 * The Player object controlled by the user.
 *
 * Multiple Players are supported. If you instantiate multiple Players, you
 * will usually want to set different {@link Actor#keys keys} for them.
 *
 * If the world is bigger than the canvas, the viewport will shift as a Player
 * moves toward an edge of the viewport. Sane viewport shifting is not
 * guaranteed with multiple Players.
 *
 * @extends Actor
 */
var Player = Actor.extend({
  /**
   * The default threshold for how close a Player has to be to an edge before
   * the viewport shifts (in percent of canvas size). To have the viewport move
   * only when the Player is actually at its edge, try a value close to zero.
   * To have the viewport move with the Player, try a value close to 0.5.
   */
  MOVEWORLD: 0.45,

  /**
   * Whether to require that the jump key is released before jumping again.
   *
   * Specifically, this is a boolean which, when true, restricts the Actor from
   * jumping after being in the air until after the Actor is on the ground with
   * the jump key released. This limits the ability to "bounce" by holding down
   * the jump key. This behavior depends on being notified of when keys are
   * released via the release() method, which happens automatically for
   * Players. If you enable this for standard Actors, the meaning of a "key
   * press" is artificial, so you must make sure to call release() before you
   * make the Actor jump again.
   */
  JUMP_RELEASE: true,

  /**
   * @constructor
   *   Initialize a Player.
   *
   * Takes the same parameters as the Box constructor.
   * **Inherited documentation:**
   *
   * @inheritdoc Box#constructor
   */
  init: function() {
    this._super.apply(this, arguments);
    if (arguments.length > 0) {
      world.centerViewportAround(this.x, this.y);
    }
    var t = this;
    this.__keytracker = function() {
      // lastKeyPressed() actually contains all keys that were pressed at the
      // last key event, whereas event.keyPressed just holds the single key
      // that triggered the event.
      if (jQuery.hotkeys) {
        t.release([jQuery.hotkeys.lastKeyPressed()]);
      }
    };
    // Notify the Player object when keys are released.
    jQuery(document).on('keyup.release', this.__keytracker);
  },

  /**
   * Override Actor#processInput() to respond to keyboard input automatically.
   *
   * **Inherited documentation:**
   *
   * @inheritdoc Actor#processInput
   */
  processInput: function(direction) {
    if (typeof direction === 'undefined' && jQuery.hotkeys) {
      direction = jQuery.hotkeys.keysDown;
    }
    return this._super(direction);
  },

  /**
   * Override Actor#update() to move the viewport as the Player nears an edge.
   *
   * **Inherited documentation:**
   *
   * @inheritdoc Actor#update
   */
  update: function(direction) {
    this._super(direction);
    if (!this.isBeingDragged) {
      this.adjustViewport();
    }
  },

  /**
   * Toggle whether the Player can be dragged around by the mouse.
   *
   * **Inherited documentation:**
   *
   * @inheritdoc Actor#setDraggable
   */
  setDraggable: function(on) {
    if (on && !this.isDraggable && App.Events && window.Mouse) {
      this.listen('canvasdragstop.drag', function() {
        if (this.isBeingDragged &&
            (!this.dropTargets.length || this.collides(this.dropTargets))) {
          world.centerViewportAround(this.x, this.y);
        }
      }, -1);
    }
    return this._super.apply(this, arguments);
  },

  /**
   * Move the viewport when the Player gets near the edge.
   *
   * @return {Object}
   *   An object with `x` and `y` properties indicating the number of pixels
   *   this method caused the viewport to shift along each axis.
   */
  adjustViewport: function() {
    var xOffset = world.xOffset,
        yOffset = world.yOffset,
        changed = {x: 0, y: 0};
    // We should only have mouse or player scrolling, but not both.
    if (window.Mouse && Mouse.Scroll.isEnabled()) {
      return changed;
    }
    // left
    if (xOffset > 0 && this.x + this.width/2 - xOffset < canvas.width * this.MOVEWORLD) {
      world.xOffset = Math.max(xOffset + (this.x - this.lastX), 0);
      context.translate(xOffset - world.xOffset, 0);
      changed.x = xOffset - world.xOffset;
    }
    // right
    else if (xOffset < world.width - canvas.width &&
        this.x + this.width/2 - xOffset > canvas.width * (1-this.MOVEWORLD)) {
      world.xOffset = Math.min(xOffset + (this.x - this.lastX), world.width - canvas.width);
      context.translate(xOffset - world.xOffset, 0);
      changed.x = xOffset - world.xOffset;
    }
    // up
    if (yOffset > 0 && this.y + this.height/2 - yOffset < canvas.height * this.MOVEWORLD) {
      world.yOffset = Math.max(yOffset + (this.y - this.lastY), 0);
      context.translate(0, yOffset - world.yOffset);
      changed.y = yOffset - world.yOffset;
    }
    // down
    else if (yOffset < world.height - canvas.height &&
        this.y + this.height/2 - yOffset > canvas.height * (1-this.MOVEWORLD)) {
      world.yOffset = Math.min(yOffset + (this.y - this.lastY), world.height - canvas.height);
      context.translate(0, yOffset - world.yOffset);
      changed.y = yOffset - world.yOffset;
    }
    return changed;
  },

  /**
   * Clean up after ourselves.
   */
  destroy: function() {
    this._super.apply(this, arguments);
    jQuery(document).off('.release', this.__keytracker);
  },
});