diff --git a/.gitignore b/.gitignore index 1294221a..bdb7bb54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules .DS_Store +kontra.min.js.map kontra.build.js kontra.build.min.js index.html diff --git a/.jshintrc b/.jshintrc index acb84245..c761d7e4 100644 --- a/.jshintrc +++ b/.jshintrc @@ -14,5 +14,6 @@ "undef" : false, // Require all non-global variables be declared before they are used. "strict" : true, // Require `use strict` pragma in every file. "trailing" : true, // Prohibit trailing whitespaces. - "browser" : true // Standard browser globals e.g. `window`, `document`. + "browser" : true, // Standard browser globals e.g. `window`, `document`. + "boss" : true // Suppress warnings about assignments in comparisons } \ No newline at end of file diff --git a/LICENSE b/LICENSE index d5cbf35b..bc52cc95 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Greg Thornton +Copyright (c) 2015 Steven Lambert Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/galaxian/js/galaxian.js b/examples/galaxian/js/galaxian.js index 3746aa4d..23019a53 100644 --- a/examples/galaxian/js/galaxian.js +++ b/examples/galaxian/js/galaxian.js @@ -341,7 +341,7 @@ kontra.loadAssets( kontra.audios.kick_shock.currentTime = 0; kontra.audios.kick_shock.play(); - player.position.init(280, 270); + player.position.init({x: 280, y: 270}); }; startGame(); diff --git a/examples/tileEngine/index.html b/examples/tileEngine/index.html index bf2d5f1c..6008f2cb 100644 --- a/examples/tileEngine/index.html +++ b/examples/tileEngine/index.html @@ -59,20 +59,20 @@ frameHeight: 64, animations: { walk_up: { - frames: '104..112', - frameSpeed: 4 + frames: '105..112', + frameRate: 12 }, walk_left: { - frames: '117..125', - frameSpeed: 4 + frames: '118..125', + frameRate: 12 }, walk_down: { - frames: '130..138', - frameSpeed: 4 + frames: '131..138', + frameRate: 12 }, walk_right: { - frames: '143..151', - frameSpeed: 4 + frames: '144..151', + frameRate: 12 } } }); @@ -81,8 +81,6 @@ var player = kontra.sprite({ x: 268, y: 268, - width: 64, - height: 64, speed: 3, startX: 268, startY: 268, @@ -179,7 +177,7 @@ }, }); - player.position.clamp(0, 0, kontra.canvas.width - player.width, kontra.canvas.height - player.height); + player.position.clamp(-16, -16, kontra.canvas.width - player.width + 16, kontra.canvas.height - player.height + 16); var loop = kontra.gameLoop({ update: function() { diff --git a/gulpfile.js b/gulpfile.js index eac0603a..dbca81af 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -8,7 +8,7 @@ var rename = require('gulp-rename'); var size = require('gulp-size'); var sourcemaps = require('gulp-sourcemaps'); var uglify = require('gulp-uglify'); -var karma = require('karma').server; +var Server = require('karma').Server; var package = require('./package.json'); @@ -139,9 +139,17 @@ gulp.task('watch', function() { }); gulp.task('test', function(done) { - karma.start({ - configFile: __dirname + '/karma.conf.js' - }, done); + new Server({ + basePath: '', + frameworks: ['mocha', 'chai', 'sinon'], + files: [ + // assets + 'test/phantom.polyfill.js', + 'src/*.js', + 'test/*.js' + ], + browsers: ['Chrome', 'Firefox', 'Safari', 'IE'] + }, done).start(); }); gulp.task('default', ['lint', 'scripts', 'connect', 'watch']); \ No newline at end of file diff --git a/kontra.js b/kontra.js index fd7b92e7..191c50b7 100644 --- a/kontra.js +++ b/kontra.js @@ -990,7 +990,7 @@ var kontra = (function(kontra, window) { * Initialize properties on the game loop. * @memberof kontra.gameLoop * - * @param {object} properties - Configure the game loop. + * @param {object} properties - Properties of the game loop. * @param {number} [properties.fps=60] - Desired frame rate. * @param {function} properties.update - Function called to update the game. * @param {function} properties.render - Function called to render the game. @@ -1010,6 +1010,7 @@ var kontra = (function(kontra, window) { // animation variables this._accumulator = 0; this._delta = 1E3 / (properties.fps || 60); + this._step = 1 / (properties.fps || 60); this.update = properties.update; this.render = properties.render; @@ -1056,7 +1057,7 @@ var kontra = (function(kontra, window) { _this._accumulator += _this._dt; while (_this._accumulator >= _this._delta) { - _this.update(_this._delta / 1E3); + _this.update(_this._step); _this._accumulator -= _this._delta; } @@ -1067,8 +1068,6 @@ var kontra = (function(kontra, window) { return kontra; })(kontra || {}, window); -/*jshint -W084 */ - var kontra = (function(kontra, window) { 'use strict'; @@ -1373,8 +1372,6 @@ var kontra = (function(kontra, window) { return kontra; })(kontra || {}, window); -/*jshint -W084 */ - var kontra = (function(kontra) { 'use strict'; @@ -1581,8 +1578,6 @@ var kontra = (function(kontra) { return kontra; })(kontra || {}); -/*jshint -W084 */ - var kontra = (function(kontra, undefined) { 'use strict'; @@ -1613,11 +1608,12 @@ var kontra = (function(kontra, undefined) { * Initialize properties on the quadtree. * @memberof kontra.quadtree * - * @param {number} [depth=0] - Current node depth. - * @param {number} [maxDepth=3] - Maximum node depths the quadtree can have. - * @param {number} [maxObjects=25] - Maximum number of objects a node can support before splitting. - * @param {object} [parentNode] - The node that contains this node. - * @param {object} [bounds] - The 2D space this node occupies. + * @param {object} properties - Properties of the quadtree. + * @param {number} [properties.depth=0] - Current node depth. + * @param {number} [properties.maxDepth=3] - Maximum node depths the quadtree can have. + * @param {number} [properties.maxObjects=25] - Maximum number of objects a node can support before splitting. + * @param {object} [properties.parentNode] - The node that contains this node. + * @param {object} [properties.bounds] - The 2D space this node occupies. */ init: function init(properties) { properties = properties || {}; @@ -1693,6 +1689,12 @@ var kontra = (function(kontra, undefined) { * the maximum number of objects allowed, it will split and move all objects to their * corresponding subnodes. * @memberof kontra.quadtree + * + * @param {...object|object[]} Objects to add to the quadtree + * + * @example + * kontra.quadtree().add({id:1}, {id:2}, {id:3}); + * kontra.quadtree().add([{id:1}, {id:2}], {id:3}); */ add: function add() { var _this = this; @@ -1763,16 +1765,12 @@ var kontra = (function(kontra, undefined) { var verticalMidpoint = this.bounds.x + this.bounds.width / 2; var horizontalMidpoint = this.bounds.y + this.bounds.height / 2; - // handle non-kontra.sprite objects as well as kontra.sprite objects - var x = (object.x !== undefined ? object.x : object.position.x); - var y = (object.y !== undefined ? object.y : object.position.y); - // save off quadrant checks for reuse - var intersectsTopQuadrants = y < horizontalMidpoint && y + object.height >= this.bounds.y; - var intersectsBottomQuadrants = y + object.height >= horizontalMidpoint && y < this.bounds.y + this.bounds.height; + var intersectsTopQuadrants = object.y < horizontalMidpoint && object.y + object.height >= this.bounds.y; + var intersectsBottomQuadrants = object.y + object.height >= horizontalMidpoint && object.y < this.bounds.y + this.bounds.height; // object intersects with the left quadrants - if (x < verticalMidpoint && x + object.width >= this.bounds.x) { + if (object.x < verticalMidpoint && object.x + object.width >= this.bounds.x) { if (intersectsTopQuadrants) { // top left indices.push(0); } @@ -1783,7 +1781,7 @@ var kontra = (function(kontra, undefined) { } // object intersects with the right quadrants - if (x + object.width >= verticalMidpoint && x < this.bounds.x + this.bounds.width) { // top right + if (object.x + object.width >= verticalMidpoint && object.x < this.bounds.x + this.bounds.width) { // top right if (intersectsTopQuadrants) { indices.push(1); } @@ -1811,14 +1809,12 @@ var kontra = (function(kontra, undefined) { var subWidth = this.bounds.width / 2 | 0; var subHeight = this.bounds.height / 2 | 0; - var x = this.bounds.x; - var y = this.bounds.y; for (var i = 0; i < 4; i++) { this.subnodes[i] = kontra.quadtree({ bounds: { - x: x + (i % 2 === 1 ? subWidth : 0), // nodes 1 and 3 - y: y + (i >= 2 ? subHeight : 0), // nodes 2 and 3 + x: this.bounds.x + (i % 2 === 1 ? subWidth : 0), // nodes 1 and 3 + y: this.bounds.y + (i >= 2 ? subHeight : 0), // nodes 2 and 3 width: subWidth, height: subHeight }, @@ -1835,6 +1831,7 @@ var kontra = (function(kontra, undefined) { * @memberof kontra.quadtree */ render: function() { + /* istanbul ignore next */ // don't draw empty leaf nodes, always draw branch nodes and the first node if (this.objects.length || this.depth === 0 || (this.parentNode && this.parentNode.isBranchNode)) { @@ -1879,9 +1876,9 @@ var kontra = (function(kontra, Math, undefined) { * * @see kontra.vector.prototype.init for list of parameters. */ - kontra.vector = function(x, y) { + kontra.vector = function(properties) { var vector = Object.create(kontra.vector.prototype); - vector.init(x, y); + vector.init(properties); return vector; }; @@ -1891,14 +1888,17 @@ var kontra = (function(kontra, Math, undefined) { * Initialize the vectors x and y position. * @memberof kontra.vector * - * @param {number} x=0 - Center x coordinate. - * @param {number} y=0 - Center y coordinate. + * @param {object} properties - Properties of the vector. + * @param {number} properties.x=0 - X coordinate. + * @param {number} properties.y=0 - Y coordinate. * * @returns {vector} */ - init: function init(x, y) { - this.x = x || 0; - this.y = y || 0; + init: function init(properties) { + properties = properties || {}; + + this.x = properties.x || 0; + this.y = properties.y || 0; return this; }, @@ -1976,80 +1976,6 @@ var kontra = (function(kontra, Math, undefined) { }; kontra.sprite.prototype = { - /** - * Move the sprite by its velocity. - * @memberof kontra.sprite - * - * @param {number} dt - Time since last update. - */ - advanceSprite: function advanceSprite(dt) { - this.velocity.add(this.acceleration, dt); - this.position.add(this.velocity, dt); - - this.timeToLive--; - }, - - /** - * Draw a simple rectangle. Useful for prototyping. - * @memberof kontra.sprite - */ - drawRect: function drawRect() { - this.context.fillStyle = this.color; - this.context.fillRect(this.position.x, this.position.y, this.width, this.height); - }, - - /** - * Draw the sprite. - * @memberof kontra.sprite - */ - drawImage: function drawImage() { - this.context.drawImage(this.image, this.position.x, this.position.y); - }, - - /** - * Update the currently playing animation. Used when animations are passed to the sprite. - * @memberof kontra.sprite - * - * @param {number} dt - Time since last update. - */ - advanceAnimation: function advanceAnimation(dt) { - this.advanceSprite(dt); - - this.currentAnimation.update(dt); - }, - - /** - * Draw the currently playing animation. Used when animations are passed to the sprite. - * @memberof kontra.sprite - */ - drawAnimation: function drawAnimation() { - this.currentAnimation.render({ - context: this.context, - x: this.position.x, - y: this.position.y - }); - }, - - /** - * Play an animation. - * @memberof kontra.sprite - * - * @param {string} name - Name of the animation to play. - */ - playAnimation: function playAnimation(name) { - this.currentAnimation = this.animations[name]; - }, - - /** - * Determine if the sprite is alive. - * @memberof kontra.sprite - * - * @returns {boolean} - */ - isAlive: function isAlive() { - return this.timeToLive > 0; - }, - /** * Initialize properties on the sprite. * @memberof kontra.sprite @@ -2085,9 +2011,18 @@ var kontra = (function(kontra, Math, undefined) { var _this = this; - _this.position = (_this.position || kontra.vector()).init(properties.x, properties.y); - _this.velocity = (_this.velocity || kontra.vector()).init(properties.dx, properties.dy); - _this.acceleration = (_this.acceleration || kontra.vector()).init(properties.ddx, properties.ddy); + _this.position = (_this.position || kontra.vector()).init({ + x: properties.x, + y: properties.y + }); + _this.velocity = (_this.velocity || kontra.vector()).init({ + x: properties.dx, + y: properties.dy + }); + _this.acceleration = (_this.acceleration || kontra.vector()).init({ + x: properties.ddx, + y: properties.ddy + }); _this.timeToLive = properties.timeToLive || 0; _this.context = properties.context || kontra.context; @@ -2099,8 +2034,8 @@ var kontra = (function(kontra, Math, undefined) { _this.height = properties.image.height; // change the advance and draw functions to work with images - _this.advance = _this.advanceSprite; - _this.draw = _this.drawImage; + _this.advance = _this._advanceSprite; + _this.draw = _this._drawImage; } // animation sprite else if (properties.animations) { @@ -2112,8 +2047,8 @@ var kontra = (function(kontra, Math, undefined) { _this.height = _this.currentAnimation.height; // change the advance and draw functions to work with animations - _this.advance = _this.advanceAnimation; - _this.draw = _this.drawAnimation; + _this.advance = _this._advanceAnimation; + _this.draw = _this._drawAnimation; } // rectangle sprite else { @@ -2122,8 +2057,8 @@ var kontra = (function(kontra, Math, undefined) { _this.height = properties.height; // change the advance and draw functions to work with rectangles - _this.advance = _this.advanceSprite; - _this.draw = _this.drawRect; + _this.advance = _this._advanceSprite; + _this.draw = _this._drawRect; } // loop through all other properties an add them to the sprite @@ -2216,6 +2151,16 @@ var kontra = (function(kontra, Math, undefined) { this.acceleration.y = value; }, + /** + * Determine if the sprite is alive. + * @memberof kontra.sprite + * + * @returns {boolean} + */ + isAlive: function isAlive() { + return this.timeToLive > 0; + }, + /** * Simple bounding box collision test. * @memberof kontra.sprite @@ -2225,14 +2170,10 @@ var kontra = (function(kontra, Math, undefined) { * @returns {boolean} True if the objects collide, false otherwise. */ collidesWith: function collidesWith(object) { - // handle non-kontra.sprite objects as well as kontra.sprite objects - var x = (object.x !== undefined ? object.x : object.position.x); - var y = (object.y !== undefined ? object.y : object.position.y); - - if (this.position.x < x + object.width && - this.position.x + this.width > x && - this.position.y < y + object.height && - this.position.y + this.height > y) { + if (this.x < object.x + object.width && + this.x + this.width > object.x && + this.y < object.y + object.height && + this.y + this.height > object.y) { return true; } @@ -2283,13 +2224,80 @@ var kontra = (function(kontra, Math, undefined) { */ render: function render() { this.draw(); - } + }, + + /** + * Play an animation. + * @memberof kontra.sprite + * + * @param {string} name - Name of the animation to play. + */ + playAnimation: function playAnimation(name) { + this.currentAnimation = this.animations[name]; + }, + + /** + * Move the sprite by its velocity. + * @memberof kontra.sprite + * @private + * + * @param {number} dt - Time since last update. + */ + _advanceSprite: function advanceSprite(dt) { + this.velocity.add(this.acceleration, dt); + this.position.add(this.velocity, dt); + + this.timeToLive--; + }, + + /** + * Update the currently playing animation. Used when animations are passed to the sprite. + * @memberof kontra.sprite + * @private + * + * @param {number} dt - Time since last update. + */ + _advanceAnimation: function advanceAnimation(dt) { + this._advanceSprite(dt); + + this.currentAnimation.update(dt); + }, + + /** + * Draw a simple rectangle. Useful for prototyping. + * @memberof kontra.sprite + * @private + */ + _drawRect: function drawRect() { + this.context.fillStyle = this.color; + this.context.fillRect(this.x, this.y, this.width, this.height); + }, + + /** + * Draw the sprite. + * @memberof kontra.sprite + * @private + */ + _drawImage: function drawImage() { + this.context.drawImage(this.image, this.x, this.y); + }, + + /** + * Draw the currently playing animation. Used when animations are passed to the sprite. + * @memberof kontra.sprite + * @private + */ + _drawAnimation: function drawAnimation() { + this.currentAnimation.render({ + context: this.context, + x: this.x, + y: this.y + }); + }, }; return kontra; })(kontra || {}, Math); -/*jshint -W084 */ - var kontra = (function(kontra, undefined) { 'use strict'; @@ -2314,22 +2322,56 @@ var kontra = (function(kontra, undefined) { * @param {object} properties - Properties of the animation. * @param {spriteSheet} properties.spriteSheet - Sprite sheet for the animation. * @param {number[]} properties.frames - List of frames of the animation. - * @param {number} properties.frameSpeed - Time to wait before transitioning the animation to the next frame. + * @param {number} properties.frameRate - Number of frames to display in one second. */ init: function init(properties) { properties = properties || {}; this.spriteSheet = properties.spriteSheet; this.frames = properties.frames; - this.frameSpeed = properties.frameSpeed; + this.frameRate = properties.frameRate; this.width = properties.spriteSheet.frame.width; this.height = properties.spriteSheet.frame.height; this.currentFrame = 0; this._accumulator = 0; - this.update = this.advance; - this.render = this.draw; + + // set update and render so we can noop them later to stop the animation + this.update = this._advance; + this.render = this._draw; + }, + + /** + * Play the animation. + * @memberof kontra.animation + */ + play: function play() { + // restore references to update and render functions only if overridden + this.update = this._advance; + this.render = this._draw; + }, + + /** + * Stop the animation and prevent update and render. + * @memberof kontra.animation + */ + stop: function stop() { + + // instead of putting an if statement in both render/update functions that checks + // a variable to determine whether to render or update, we can just reassign the + // functions to noop and save processing time in the game loop. + // @see http://jsperf.com/boolean-check-vs-noop + this.update = kontra.noop; + this.render = kontra.noop; + }, + + /** + * Pause the animation and prevent update. + * @memberof kontra.animation + */ + pause: function pause() { + this.update = kontra.noop; }, /** @@ -2337,19 +2379,18 @@ var kontra = (function(kontra, undefined) { * @memberof kontra.animation * @private * - * @param {number} dt=1 - Time since last update. + * @param {number} [dt=1/60] - Time since last update. */ - advance: function advance(dt) { - // normalize dt to work with milliseconds as a decimal or an integer - dt = (dt < 1 ? dt * 1E3 : dt) || 1; + _advance: function advance(dt) { + dt = dt || 1 / 60; this._accumulator += dt; // update to the next frame if it's time - while (this._accumulator >= this.frameSpeed) { + while (this._accumulator * this.frameRate >= 1) { this.currentFrame = ++this.currentFrame % this.frames.length; - this._accumulator -= this.frameSpeed; + this._accumulator -= 1 / this.frameRate; } }, @@ -2363,7 +2404,7 @@ var kontra = (function(kontra, undefined) { * @param {number} properties.y - Y position to draw. * @param {Context} [properties.context=kontra.context] - Provide a context for the sprite to draw on. */ - draw: function draw(properties) { + _draw: function draw(properties) { properties = properties || {}; var context = properties.context || kontra.context; @@ -2379,38 +2420,6 @@ var kontra = (function(kontra, undefined) { properties.x, properties.y, this.spriteSheet.frame.width, this.spriteSheet.frame.height ); - }, - - /** - * Play the animation. - * @memberof kontra.animation - */ - play: function play() { - // restore references to update and render functions only if overridden - this.update = this.advance; - this.render = this.draw; - }, - - /** - * Stop the animation and prevent update and render. - * @memberof kontra.animation - */ - stop: function stop() { - - // instead of putting an if statement in both render/update functions that checks - // a variable to determine whether to render or update, we can just reassign the - // functions to noop and save processing time in the game loop. - // @see http://jsperf.com/boolean-check-vs-noop - this.update = kontra.noop; - this.render = kontra.noop; - }, - - /** - * Pause the animation and prevent update. - * @memberof kontra.animation - */ - pause: function pause() { - this.update = kontra.noop; } }; @@ -2436,9 +2445,8 @@ var kontra = (function(kontra, undefined) { /** * Initialize properties on the spriteSheet. * @memberof kontra - * @constructor * - * @param {object} properties - Configure the sprite sheet. + * @param {object} properties - Properties of the sprite sheet. * @param {Image|Canvas} properties.image - Image for the sprite sheet. * @param {number} properties.frameWidth - Width (in px) of each frame. * @param {number} properties.frameHeight - Height (in px) of each frame. @@ -2475,7 +2483,7 @@ var kontra = (function(kontra, undefined) { * * @param {object} animations - List of named animations to create from the Image. * @param {number|string|number[]|string[]} animations.animationName.frames - A single frame or list of frames for this animation. - * @param {number} animations.animationName.frameSpeed=1 - Number of frames to wait before transitioning the animation to the next frame. + * @param {number} animations.animationName.frameRate - Number of frames to display in one second. * * @example * var sheet = kontra.spriteSheet({image: img, frameWidth: 16, frameHeight: 16}); @@ -2485,19 +2493,19 @@ var kontra = (function(kontra, undefined) { * }, * walk: { * frames: '2..6', // ascending consecutive frame animation (frames 2-6, inclusive) - * frameSpeed: 4 + * frameRate: 4 * }, * moonWalk: { * frames: '6..2', // descending consecutive frame animation - * frameSpeed: 4 + * frameRate: 4 * }, * jump: { * frames: [7, 12, 2], // non-consecutive frame animation - * frameSpeed: 3 + * frameRate: 3 * }, * attack: { * frames: ['8..10', 13, '10..8'], // you can also mix and match, in this case frames [8,9,10,13,10,9,8] - * frameSpeed: 2 + * frameRate: 2 * } * }); */ @@ -2511,7 +2519,7 @@ var kontra = (function(kontra, undefined) { } // create each animation by parsing the frames - var animation, frames, frameSpeed, sequence; + var animation, frames, frameRate, sequence; for (var name in animations) { if (!animations.hasOwnProperty(name)) { continue; @@ -2519,7 +2527,7 @@ var kontra = (function(kontra, undefined) { animation = animations[name]; frames = animation.frames; - frameSpeed = animation.frameSpeed; + frameRate = animation.frameRate; // array that holds the order of the animation sequence = []; @@ -2554,11 +2562,16 @@ var kontra = (function(kontra, undefined) { } } } + else { + error = new SyntaxError('Improper frames value'); + kontra.logError(error, 'The frames property must be a number, string, or array.'); + return; + } this.animations[name] = kontra.animation({ spriteSheet: this, frames: sequence, - frameSpeed: frameSpeed + frameRate: frameRate }); } }, @@ -2681,8 +2694,6 @@ var kontra = (function(kontra, window, localStorage, undefined) { return kontra; })(kontra || {}, window, window.localStorage); -/*jshint -W084 */ - var kontra = (function(kontra, Math, undefined) { 'use strict'; @@ -2774,7 +2785,7 @@ var kontra = (function(kontra, Math, undefined) { * @memberof kontra.tileEngine * * @param {object} properties - Properties of the image to add. - * @param {string|Image|Canvas} properties.image - Path to the image or Image object. + * @param {Image|Canvas} properties.image - Path to the image or Image object. * @param {number} properties.firstGrid - The first tile grid to start the image. */ addTileset: function addTileset(properties) { @@ -2792,7 +2803,7 @@ var kontra = (function(kontra, Math, undefined) { var tiles = (lastTileset.image.width / this.tileWidth | 0) * (lastTileset.image.height / this.tileHeight | 0); - firstGrid = lastTileset.firstGrid + tiles - 1; + firstGrid = lastTileset.firstGrid + tiles; } // otherwise this is the first tile added to the tile map else { @@ -2826,7 +2837,7 @@ var kontra = (function(kontra, Math, undefined) { * @param {string} properties.name - Name of the layer. * @param {number[]} properties.data - Tile layer data. * @param {boolean} [properties.render=true] - If the layer should be drawn. - * @param {number} properties.zIndex - Draw order for tile layer. Highest number is drawn last (i.e. on top of all other layers). + * @param {number} [properties.zIndex] - Draw order for tile layer. Highest number is drawn last (i.e. on top of all other layers). */ addLayer: function addLayer(properties) { properties = properties || {}; @@ -2879,16 +2890,12 @@ var kontra = (function(kontra, Math, undefined) { * @returns {boolean} True if the object collides with a tile, false otherwise. */ layerCollidesWith: function layerCollidesWith(name, object) { - // handle non-kontra.sprite objects as well as kontra.sprite objects - var x = (object.x !== undefined ? object.x : object.position.x); - var y = (object.y !== undefined ? object.y : object.position.y); - // calculate all tiles that the object can collide with - var row = this._getRow(y); - var col = this._getCol(x); + var row = this._getRow(object.y); + var col = this._getCol(object.x); - var endRow = this._getRow(y + object.height); - var endCol = this._getCol(x + object.width); + var endRow = this._getRow(object.y + object.height); + var endCol = this._getCol(object.x + object.width); // check all tiles var index; diff --git a/kontra.min.js b/kontra.min.js index 64a416fd..8ac2f6f9 100644 --- a/kontra.min.js +++ b/kontra.min.js @@ -1,2 +1,2 @@ -function qFactory(t,e){function i(t,e,n){var r;if(t)if(a(t))for(r in t)"prototype"==r||"length"==r||"name"==r||t.hasOwnProperty&&!t.hasOwnProperty(r)||e.call(n,t[r],r);else if(t.forEach&&t.forEach!==i)t.forEach(e,n);else if(h(t))for(r=0;re;e++)t=n[e],i.then(t[0],t[1],t[2])})}},reject:function(t){s.resolve(f(t))},notify:function(e){if(o){var i=o;o.length&&t(function(){for(var t,n=0,r=i.length;r>n;n++)t=i[n],t[2](e)})}},promise:{then:function(t,s,h){var u=c(),d=function(i){try{u.resolve((a(t)?t:n)(i))}catch(r){u.reject(r),e(r)}},f=function(t){try{u.resolve((a(s)?s:r)(t))}catch(i){u.reject(i),e(i)}},l=function(t){try{u.notify((a(h)?h:n)(t))}catch(i){e(i)}};return o?o.push([d,f,l]):i.then(d,f,l),u.promise},"catch":function(t){return this.then(null,t)},"finally":function(t){function e(t,e){var i=c();return e?i.resolve(t):i.reject(t),i.promise}function i(i,r){var s=null;try{s=(t||n)()}catch(o){return e(o,!1)}return s&&a(s.then)?s.then(function(){return e(i,r)},function(t){return e(t,!1)}):e(i,r)}return this.then(function(t){return i(t,!0)},function(t){return i(t,!1)})}}}},u=function(e){return e&&a(e.then)?e:{then:function(i){var n=c();return t(function(){n.resolve(i(e))}),n.promise}}},d=function(t){var e=c();return e.reject(t),e.promise},f=function(i){return{then:function(n,s){var o=c();return t(function(){try{o.resolve((a(s)?s:r)(i))}catch(t){o.reject(t),e(t)}}),o.promise}}},l=function(i,s,o,h){var f,l=c(),p=function(t){try{return(a(s)?s:n)(t)}catch(i){return e(i),d(i)}},m=function(t){try{return(a(o)?o:r)(t)}catch(i){return e(i),d(i)}},g=function(t){try{return(a(h)?h:n)(t)}catch(i){e(i)}};return t(function(){u(i).then(function(t){f||(f=!0,l.resolve(u(t).then(p,m,g)))},function(t){f||(f=!0,l.resolve(m(t)))},function(t){f||l.notify(g(t))})}),l.promise};return{defer:c,reject:d,when:l,all:s}}window.q=qFactory(function(t){setTimeout(function(){t()},0)},function(t){console.error("qLite: "+t.stack)});var kontra=function(t){var e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac|m4a)$/;t.images={},t.audios={},t.data={},t.assetPaths={images:"",audios:"",data:""};var n=new Audio;return t.canUse=t.canUse||{},t.canUse.wav="",t.canUse.mp3=n.canPlayType("audio/mpeg;").replace(/^no$/,""),t.canUse.ogg=n.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),t.canUse.aac=n.canPlayType("audio/aac;").replace(/^no$/,""),t.canUse.m4a=(n.canPlayType("audio/x-m4a;")||t.canUse.aac).replace(/^no$/,""),t.getAssetExtension=function(t){return t.substr((~-t.lastIndexOf(".")>>>0)+2)},t.getAssetType=function(t){var n=this.getAssetExtension(t);return n.match(e)?"Image":n.match(i)?"Audio":"Data"},t.getAssetName=function(t){return t.replace(/\.[^/.]+$/,"")},t}(kontra||{}),kontra=function(t,e){return t.loadAssets=function(){var i,n,r=e.defer(),s=[],o=0,a=arguments.length;arguments.length||r.resolve();for(var h,c=0;h=arguments[c];c++)n=Array.isArray(h)?h[0]:h,i=this.getAssetType(n),function(e){s.push(e.promise),t["load"+i](n).then(function(){e.resolve(),r.notify({loaded:++o,total:a})},function(t){e.reject(t)})}(e.defer());return e.all(s).then(function(){r.resolve()},function(t){r.reject(t)}),r.promise},t.loadImage=function(i){var n=e.defer(),r=this.getAssetName(i),s=new Image;return i=this.assetPaths.images+i,s.onload=function(){t.images[r]=t.images[i]=this,n.resolve(this)},s.onerror=function(){n.reject("Unable to load image "+i)},s.src=i,n.promise},t.loadAudio=function(i){var n,r,s,o,a=e.defer();Array.isArray(i)||(i=[i]);for(var h=0;n=i[h];h++)if(this.canUse[this.getAssetExtension(n)]){s=n;break}return s?(r=this.getAssetName(s),o=new Audio,n=this.assetPaths.audios+s,o.addEventListener("canplay",function(){t.audios[r]=t.audios[n]=this,a.resolve(this)}),o.onerror=function(){a.reject("Unable to load audio "+n)},o.src=n,o.preload="auto",o.load()):a.reject("Browser cannot play any of the audio formats provided"),a.promise},t.loadData=function(i){var n=e.defer(),r=new XMLHttpRequest,s=this.getAssetName(i),o=this.assetPaths.data+i;return r.addEventListener("load",function(){if(200!==r.status)return void n.reject(r.responseText);try{var e=JSON.parse(r.responseText);t.data[s]=t.data[o]=e,n.resolve(e)}catch(i){var a=r.responseText;t.data[s]=t.data[o]=a,n.resolve(a)}}),r.open("GET",o,!0),r.send(),n.promise},t}(kontra||{},q),kontra=function(t,e){return t.bundles={},t.createBundle=function(t,e){this.bundles[t]||(this.bundles[t]=e||[])},t.loadBundles=function(){for(var t,i,n=e.defer(),r=[],s=0,o=0,a=0;i=arguments[a];a++)(t=this.bundles[i])?(o+=t.length,r.push(this.loadAssets.apply(this,t))):n.reject("Bundle '"+i+"' has not been created.");return e.all(r).then(function(){n.resolve()},function(t){n.reject(t)},function(){n.notify({loaded:++s,total:o})}),n.promise},t}(kontra||{},q),kontra=function(t,e){return t.loadManifest=function(i){var n,r=e.defer();return t.loadData(i).then(function(e){t.assetPaths.images=e.imagePath||"",t.assetPaths.audios=e.audioPath||"",t.assetPaths.data=e.dataPath||"";for(var i,s=0;i=e.bundles[s];s++)t.createBundle(i.name,i.assets);return e.loadBundles?(n="all"===e.loadBundles?Object.keys(t.bundles||{}):Array.isArray(e.loadBundles)?e.loadBundles:[e.loadBundles],void t.loadBundles.apply(t,n).then(function(){r.resolve()},function(t){r.reject(t)},function(t){r.notify(t)})):void r.resolve()},function(t){r.reject(t)}),r.promise},t}(kontra||{},q),kontra=function(t,e){"use strict";return t.init=function(i){if(i=i||{},t.isString(i.canvas))this.canvas=e.getElementById(i.canvas);else if(t.isCanvas(i.canvas))this.canvas=i.canvas;else if(this.canvas=e.getElementsByTagName("canvas")[0],!this.canvas){var n=new ReferenceError("No canvas element found.");return void t.logError(n,"You must provide a canvas element for the game.")}this.context=this.canvas.getContext("2d"),this.game={width:this.canvas.width,height:this.canvas.height}},t.logError=function(t,e){console.error("Kontra: "+e+"\n "+t.stack)},t.noop=function(){},t.isArray=Array.isArray,t.isString=function(t){return"string"==typeof t},t.isNumber=function(t){return"number"==typeof t},t.isImage=function(t){return t instanceof HTMLImageElement},t.isCanvas=function(t){return t instanceof HTMLCanvasElement},t}(kontra||{},document),kontra=function(t,e){"use strict";return t.timestamp=function(){return e.performance&&e.performance.now?function(){return e.performance.now()}:function(){return(new Date).getTime()}}(),t.gameLoop=function(e){var i=Object.create(t.gameLoop.prototype);return i.init(e),i},t.gameLoop.prototype={init:function(e){if(e=e||{},"function"!=typeof e.update||"function"!=typeof e.render){var i=new ReferenceError("Required functions not found");return void t.logError(i,"You must provide update() and render() functions to create a game loop.")}this.isStopped=!1,this._accumulator=0,this._delta=1e3/(e.fps||60),this.update=e.update,this.render=e.render},start:function(){this._last=t.timestamp(),this.isStopped=!1,requestAnimationFrame(this._frame.bind(this))},stop:function(){this.isStopped=!0,cancelAnimationFrame(this._rAF)},_frame:function(){var e=this;if(e._rAF=requestAnimationFrame(e._frame.bind(e)),e._now=t.timestamp(),e._dt=e._now-e._last,e._last=e._now,!(e._dt>1e3)){for(e._accumulator+=e._dt;e._accumulator>=e._delta;)e.update(e._delta/1e3),e._accumulator-=e._delta;e.render()}}},t}(kontra||{},window),kontra=function(t,e){"use strict";function i(t){return"number"==typeof t.which?t.which:t.keyCode}function n(t){var e=[];t=t.trim().replace("++","+plus");for(var i,n=0;i=p[n];n++)-1!==t.indexOf(i)&&(e.push(i),t=t.replace(i,""));return t=t.replace(/\+/g,"").toLowerCase(),f[t]?e.push("shift+"+f[t]):t&&e.push(t),e.join("+")}function r(t){for(var e,n=[],r=0;e=p[r];r++)t[e+"Key"]&&n.push(e);var s=u[i(t)];return-1===n.indexOf(s)&&n.push(s),n.join("+")}function s(t){for(var e,i=r(t),n=0,s=i.split("+");e=s[n];n++)c[e]=!0;h[i]&&(h[i](t,i),t.preventDefault())}function o(t){var e=u[i(t)];c[e]=!1,l[e]&&(c[l[e]]=!1)}function a(t){c={}}for(var h={},c={},u={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",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:"delete",91:"leftwindow",92:"rightwindow",93:"select",144:"numlock",145:"scrolllock",106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},d=0;26>d;d++)u[65+d]=String.fromCharCode(65+d).toLowerCase();for(d=0;10>d;d++)u[48+d]=""+d;for(d=1;20>d;d++)u[111+d]="f"+d;for(d=0;10>d;d++)u[96+d]="numpad"+d;var f={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\",plus:"="},l={leftwindow:"meta",select:"meta"},p=["meta","ctrl","alt","shift"];return e.addEventListener("keydown",s),e.addEventListener("keyup",o),e.addEventListener("blur",a),t.keys={},t.keys.bind=function(e,i){if("function"!=typeof i){var r=new SyntaxError("Invalid function.");return void t.logError(r,"You must provide a function as the second parameter.")}e=t.isArray(e)?e:[e];for(var s,o=0;s=e[o];o++){var a=n(s);h[a]=i}},t.keys.unbind=function(e){e=t.isArray(e)?e:[e];for(var i,r=0;i=e[r];r++){var s=n(i);h[s]=void 0}},t.keys.pressed=function(t){var e=n(t),i=!0;t=e.split("+");for(var r,s=0;r=t[s];s++)i=i&&!!c[r];return i},t}(kontra||{},window),kontra=function(t){"use strict";return t.pool=function(e){var i=Object.create(t.pool.prototype);return i.init(e),i},t.pool.prototype={init:function(e){e=e||{};var i,n;if("function"!=typeof e.create)return i=new SyntaxError("Required function not found."),void t.logError(i,"Parameter 'create' must be a function that returns an object.");if(this.create=e.create.bind(this,e.createProperties||{}),n=this.create(),!n||"function"!=typeof n.render||"function"!=typeof n.update||"function"!=typeof n.init||"function"!=typeof n.isAlive)return i=new SyntaxError("Create object required functions not found."),void t.logError(i,"Objects to be pooled must implement render(), update(), init() and isAlive() functions.");if(this.objects=[n],this.size=1,this.maxSize=e.maxSize||1/0,this.lastIndex=0,this.inUse=0,e.fill){if(!e.maxSize)return i=new SyntaxError("Required property not found."),void t.logError(i,"Parameter 'maxSize' must be set before you can fill a pool.");for(;this.objects.length=n;)if(e=this.objects[i],e.update(t),e.isAlive())i--;else{for(var r=i;r>0;r--)this.objects[r]=this.objects[r-1];this.objects[0]=e,this.inUse--,n++}},render:function(){for(var t=Math.max(this.objects.length-this.inUse,0),e=this.lastIndex;e>=t;e--)this.objects[e].render()}},t}(kontra||{}),kontra=function(t,e){"use strict";return t.quadtree=function(e){var i=Object.create(t.quadtree.prototype);return i.init(e),i},t.quadtree.prototype={init:function(e){e=e||{},this.depth=e.depth||0,this.maxDepth=e.maxDepth||3,this.maxObjects=e.maxObjects||25,this.isBranchNode=!1,this.parentNode=e.parentNode,this.bounds=e.bounds||{x:0,y:0,width:t.game.width,height:t.game.height},this.objects=[],this.subnodes=[]},clear:function(){if(this.isBranchNode)for(var t=0;4>t;t++)this.subnodes[t].clear();this.isBranchNode=!1,this.objects.length=0},get:function(t){for(var e,i,n=this,r=[];n.subnodes.length&&this.isBranchNode;){e=this._getIndex(t);for(var s=0,o=e.length;o>s;s++)i=e[s],r.push.apply(r,this.subnodes[i].get(t));return r}return n.objects},add:function(){for(var e,i,n,r=this,s=0,o=arguments.length;o>s;s++)if(i=arguments[s],t.isArray(i))r.add.apply(this,i);else if(r.subnodes.length&&r.isBranchNode)r._addToSubnode(i);else if(r.objects.push(i),r.objects.length>r.maxObjects&&r.depthi;i++)this.subnodes[e[i]].add(t)},_getIndex:function(t){var i=[],n=this.bounds.x+this.bounds.width/2,r=this.bounds.y+this.bounds.height/2,s=t.x!==e?t.x:t.position.x,o=t.y!==e?t.y:t.position.y,a=r>o&&o+t.height>=this.bounds.y,h=o+t.height>=r&&os&&s+t.width>=this.bounds.x&&(a&&i.push(0),h&&i.push(2)),s+t.width>=n&&ss;s++)this.subnodes[s]=t.quadtree({bounds:{x:n+(s%2===1?e:0),y:r+(s>=2?i:0),width:e,height:i},depth:this.depth+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects,parentNode:this})},render:function(){if((this.objects.length||0===this.depth||this.parentNode&&this.parentNode.isBranchNode)&&(t.context.strokeStyle="red",t.context.strokeRect(this.bounds.x,this.bounds.y,this.bounds.width,this.bounds.height),this.subnodes.length))for(var e=0;4>e;e++)this.subnodes[e].render()}},t}(kontra||{}),kontra=function(t,e,i){"use strict";var n=["x","y","dx","dy","ddx","ddy","timeToLive","context","image","animations","color","width","height"];return t.vector=function(e,i){var n=Object.create(t.vector.prototype);return n.init(e,i),n},t.vector.prototype={init:function(t,e){return this.x=t||0,this.y=e||0,this},add:function(t,e){this.x+=(t.x||0)*(e||1),this.y+=(t.y||0)*(e||1)},clamp:function(t,n,r,s){this._xMin=t!==i?t:-(1/0),this._xMax=r!==i?r:1/0,this._yMin=n!==i?n:-(1/0),this._yMax=s!==i?s:1/0,this._x=this.x,this._y=this.y,Object.defineProperties(this,{x:{get:function(){return this._x},set:function(t){this._x=e.min(e.max(t,this._xMin),this._xMax)}},y:{get:function(){return this._y},set:function(t){this._y=e.min(e.max(t,this._yMin),this._yMax)}}})}},t.sprite=function(e){var i=Object.create(t.sprite.prototype);return i.init(e),i},t.sprite.prototype={advanceSprite:function(t){this.velocity.add(this.acceleration,t),this.position.add(this.velocity,t),this.timeToLive--},drawRect:function(){this.context.fillStyle=this.color,this.context.fillRect(this.position.x,this.position.y,this.width,this.height)},drawImage:function(){this.context.drawImage(this.image,this.position.x,this.position.y)},advanceAnimation:function(t){this.advanceSprite(t),this.currentAnimation.update(t)},drawAnimation:function(){this.currentAnimation.render({context:this.context,x:this.position.x,y:this.position.y})},playAnimation:function(t){this.currentAnimation=this.animations[t]},isAlive:function(){return this.timeToLive>0},init:function(e){e=e||{};var i=this;i.position=(i.position||t.vector()).init(e.x,e.y),i.velocity=(i.velocity||t.vector()).init(e.dx,e.dy),i.acceleration=(i.acceleration||t.vector()).init(e.ddx,e.ddy),i.timeToLive=e.timeToLive||0,i.context=e.context||t.context,t.isImage(e.image)||t.isCanvas(e.image)?(i.image=e.image,i.width=e.image.width,i.height=e.image.height,i.advance=i.advanceSprite,i.draw=i.drawImage):e.animations?(i.animations=e.animations,i.currentAnimation=e.animations[Object.keys(e.animations)[0]],i.width=i.currentAnimation.width,i.height=i.currentAnimation.height,i.advance=i.advanceAnimation,i.draw=i.drawAnimation):(i.color=e.color,i.width=e.width,i.height=e.height,i.advance=i.advanceSprite,i.draw=i.drawRect);for(var r in e)e.hasOwnProperty(r)&&-1===n.indexOf(r)&&(i[r]=e[r])},get x(){return this.position.x},get y(){return this.position.y},get dx(){return this.velocity.x},get dy(){return this.velocity.y},get ddx(){return this.acceleration.x},get ddy(){return this.acceleration.y},set x(t){this.position.x=t},set y(t){this.position.y=t},set dx(t){this.velocity.x=t},set dy(t){this.velocity.y=t},set ddx(t){this.acceleration.x=t},set ddy(t){this.acceleration.y=t},collidesWith:function(t){var e=t.x!==i?t.x:t.position.x,n=t.y!==i?t.y:t.position.y;return this.position.xe&&this.position.yn?!0:!1},update:function(t){this.advance(t)},render:function(){this.draw()}},t}(kontra||{},Math),kontra=function(t,e){"use strict";return t.animation=function(e){var i=Object.create(t.animation.prototype);return i.init(e),i},t.animation.prototype={init:function(t){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameSpeed=t.frameSpeed,this.width=t.spriteSheet.frame.width,this.height=t.spriteSheet.frame.height,this.currentFrame=0,this._accumulator=0,this.update=this.advance,this.render=this.draw},advance:function(t){for(t=(1>t?1e3*t:t)||1,this._accumulator+=t;this._accumulator>=this.frameSpeed;)this.currentFrame=++this.currentFrame%this.frames.length,this._accumulator-=this.frameSpeed},draw:function(e){e=e||{};var i=e.context||t.context,n=this.frames[this.currentFrame]/this.spriteSheet.framesPerRow|0,r=this.frames[this.currentFrame]%this.spriteSheet.framesPerRow|0;i.drawImage(this.spriteSheet.image,r*this.spriteSheet.frame.width,n*this.spriteSheet.frame.height,this.spriteSheet.frame.width,this.spriteSheet.frame.height,e.x,e.y,this.spriteSheet.frame.width,this.spriteSheet.frame.height)},play:function(){this.update=this.advance,this.render=this.draw},stop:function(){this.update=t.noop,this.render=t.noop},pause:function(){this.update=t.noop}},t.spriteSheet=function(e){var i=Object.create(t.spriteSheet.prototype);return i.init(e),i},t.spriteSheet.prototype={init:function(e){if(e=e||{},this.animations={},!t.isImage(e.image)&&!t.isCanvas(e.image)){var i=new SyntaxError("Invalid image.");return void t.logError(i,"You must provide an Image for the SpriteSheet.")}this.image=e.image,this.frame={width:e.frameWidth,height:e.frameHeight},this.framesPerRow=e.image.width/e.frameWidth|0,e.animations&&this.createAnimations(e.animations)},createAnimations:function(i){var n;if(!i||0===Object.keys(i).length)return n=new ReferenceError("No animations found."),void t.logError(n,"You must provide at least one named animation to create an Animation.");var r,s,o,a;for(var h in i)if(i.hasOwnProperty(h)){if(r=i[h],s=r.frames,o=r.frameSpeed,a=[],s===e)return n=new ReferenceError("No animation frames found."),void t.logError(n,"Animation "+h+" must provide a frames property.");if(t.isNumber(s))a.push(s);else if(t.isString(s))a=this._parseFrames(s);else if(t.isArray(s))for(var c,u=0;c=s[u];u++)t.isString(c)?a.push.apply(a,this._parseFrames(c)):a.push(c);this.animations[h]=t.animation({spriteSheet:this,frames:a,frameSpeed:o})}},_parseFrames:function(t){var e,i=[],n=t.split("..").map(Number),r=n[0]=n[1];e--)i.push(e);return i}},t}(kontra||{}),kontra=function(t,e,i,n){"use strict";return t.canUse=t.canUse||{},t.canUse.localStorage="localStorage"in e&&null!==e.localStorage,t.canUse.localStorage?(t.store={},t.store.set=function(t,e){e===n?this.remove(t):i.setItem(t,JSON.stringify(e))},t.store.get=function(t){var e=i.getItem(t);try{e=JSON.parse(e)}catch(n){}return e},t.store.remove=function(t){i.removeItem(t)},t.store.clear=function(){i.clear()},t):t}(kontra||{},window,window.localStorage),kontra=function(t,e,i){"use strict";return t.tileEngine=function(e){var i=Object.create(t.tileEngine.prototype);return i.init(e),i},t.tileEngine.prototype={init:function(e){e=e||{};var i=this;if(!e.width||!e.height){var n=new ReferenceError("Required parameters not found");return void t.logError(n,"You must provide width and height of the map to create a tile engine.")}i.width=e.width,i.height=e.height,i.tileWidth=e.tileWidth||32,i.tileHeight=e.tileHeight||32,i.context=e.context||t.context,i.canvasWidth=i.context.canvas.width,i.canvasHeight=i.context.canvas.height,i._offscreenCanvas=document.createElement("canvas"),i._offscreenContext=i._offscreenCanvas.getContext("2d"),i._offscreenCanvas.width=i.mapWidth=i.width*i.tileWidth,i._offscreenCanvas.height=i.mapHeight=i.height*i.tileHeight,i.sxMax=i.mapWidth-i.canvasWidth,i.syMax=i.mapHeight-i.canvasHeight,i.layers={},i._layerOrder=[],i.tilesets=[],i.x=e.x||0,i.y=e.y||0,i.sx=e.sx||0,i.sy=e.sy||0},addTileset:function(e){if(e=e||{},!t.isImage(e.image)&&!t.isCanvas(e.image)){var i=new SyntaxError("Invalid image.");return void t.logError(i,"You must provide an Image for the tile engine.")}var n=e.image,r=e.firstGrid,s=(n.width/this.tileWidth|0)*(n.height/this.tileHeight|0);if(!r)if(this.tilesets.length>0){var o=this.tilesets[this.tilesets.length-1],a=(o.image.width/this.tileWidth|0)*(o.image.height/this.tileHeight|0);r=o.firstGrid+a-1}else r=1;this.tilesets.push({firstGrid:r,lastGrid:r+s-1,image:n}),this.tilesets.sort(function(t,e){return t.firstGrid-e.firstGrid})},addLayer:function(e){e=e||{},e.render=e.render===i?!0:e.render;var n,r=this;if(t.isArray(e.data[0])){n=[];for(var s,o=0;s=e.data[o];o++)for(var a=0,h=s.length;h>a;a++)n.push(s[a])}else n=e.data;this.layers[e.name]=n,this.layers[e.name].zIndex=e.zIndex||0,this.layers[e.name].render=e.render,e.render&&(this._layerOrder.push(e.name),this._layerOrder.sort(function(t,e){return r.layers[t].zIndex-r.layers[e].zIndex}),this._preRenderImage())},layerCollidesWith:function(t,e){for(var n,r=e.x!==i?e.x:e.position.x,s=e.y!==i?e.y:e.position.y,o=this._getRow(s),a=this._getCol(r),h=this._getRow(s+e.height),c=this._getCol(r+e.width),u=o;h>=u;u++)for(var d=a;c>=d;d++)if(n=d+u*this.width,this.layers[t][n])return!0;return!1},tileAtLayer:function(t,e,i){var n=this._getRow(i),r=this._getCol(e),s=r+n*this.width;return this.layers[t][s]},render:function(){var t=this;t.sx=e.min(e.max(t.sx,0),t.sxMax),t.sy=e.min(e.max(t.sy,0),t.syMax),t.context.drawImage(t._offscreenCanvas,t.sx,t.sy,t.canvasWidth,t.canvasHeight,t.x,t.y,t.canvasWidth,t.canvasHeight)},renderLayer:function(t){for(var i,n,r,s,o,a,h,c,u,d=this,f=d.layers[t],l=d._getRow(),p=d._getCol(),m=p+l*d.width,g=p*d.tileWidth-d.sx,v=l*d.tileHeight-d.sy,y=e.ceil(d.canvasWidth/d.tileWidth)+1,x=e.ceil(d.canvasHeight/d.tileHeight)+1,b=y*x,w=0;b>w;)r=f[m],r&&(s=d._getTileset(r),o=s.image,i=g+w%y*d.tileWidth,n=v+(w/y|0)*d.tileHeight,a=r-s.firstGrid,h=o.width/d.tileWidth,c=a%h*d.tileWidth,u=(a/h|0)*d.tileHeight,d.context.drawImage(o,c,u,d.tileWidth,d.tileHeight,i,n,d.tileWidth,d.tileHeight)),++w%y===0?m=p+ ++l*d.width:m++},_getRow:function(t){return t=t||0,(this.sy+t)/this.tileHeight|0},_getCol:function(t){return t=t||0,(this.sx+t)/this.tileWidth|0},_getTileset:function(t){for(var e,i,n=0,r=this.tilesets.length-1;r>=n;){if(e=(n+r)/2|0,i=this.tilesets[e],t>=i.firstGrid&&t<=i.lastGrid)return i;t>i?n=e+1:r=e-1}},_preRenderImage:function(){for(var t,e,i,n,r,s,o,a,h,c,u=this,d=0;c=u.layers[u._layerOrder[d]];d++)for(var f=0,l=c.length;l>f;f++)t=c[f],t&&(e=u._getTileset(t),i=e.image,n=f%u.width*u.tileWidth,r=(f/u.width|0)*u.tileHeight,a=t-e.firstGrid,h=i.width/u.tileWidth,s=a%h*u.tileWidth,o=(a/h|0)*u.tileHeight,u._offscreenContext.drawImage(i,s,o,u.tileWidth,u.tileHeight,n,r,u.tileWidth,u.tileHeight))}},t}(kontra||{},Math); +function qFactory(t,e){function i(t,e,n){var r;if(t)if(o(t))for(r in t)"prototype"==r||"length"==r||"name"==r||t.hasOwnProperty&&!t.hasOwnProperty(r)||e.call(n,t[r],r);else if(t.forEach&&t.forEach!==i)t.forEach(e,n);else if(h(t))for(r=0;re;e++)t=n[e],i.then(t[0],t[1],t[2])})}},reject:function(t){s.resolve(f(t))},notify:function(e){if(a){var i=a;a.length&&t(function(){for(var t,n=0,r=i.length;r>n;n++)t=i[n],t[2](e)})}},promise:{then:function(t,s,h){var u=c(),d=function(i){try{u.resolve((o(t)?t:n)(i))}catch(r){u.reject(r),e(r)}},f=function(t){try{u.resolve((o(s)?s:r)(t))}catch(i){u.reject(i),e(i)}},l=function(t){try{u.notify((o(h)?h:n)(t))}catch(i){e(i)}};return a?a.push([d,f,l]):i.then(d,f,l),u.promise},"catch":function(t){return this.then(null,t)},"finally":function(t){function e(t,e){var i=c();return e?i.resolve(t):i.reject(t),i.promise}function i(i,r){var s=null;try{s=(t||n)()}catch(a){return e(a,!1)}return s&&o(s.then)?s.then(function(){return e(i,r)},function(t){return e(t,!1)}):e(i,r)}return this.then(function(t){return i(t,!0)},function(t){return i(t,!1)})}}}},u=function(e){return e&&o(e.then)?e:{then:function(i){var n=c();return t(function(){n.resolve(i(e))}),n.promise}}},d=function(t){var e=c();return e.reject(t),e.promise},f=function(i){return{then:function(n,s){var a=c();return t(function(){try{a.resolve((o(s)?s:r)(i))}catch(t){a.reject(t),e(t)}}),a.promise}}},l=function(i,s,a,h){var f,l=c(),m=function(t){try{return(o(s)?s:n)(t)}catch(i){return e(i),d(i)}},p=function(t){try{return(o(a)?a:r)(t)}catch(i){return e(i),d(i)}},g=function(t){try{return(o(h)?h:n)(t)}catch(i){e(i)}};return t(function(){u(i).then(function(t){f||(f=!0,l.resolve(u(t).then(m,p,g)))},function(t){f||(f=!0,l.resolve(p(t)))},function(t){f||l.notify(g(t))})}),l.promise};return{defer:c,reject:d,when:l,all:s}}window.q=qFactory(function(t){setTimeout(function(){t()},0)},function(t){console.error("qLite: "+t.stack)});var kontra=function(t){var e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac|m4a)$/;t.images={},t.audios={},t.data={},t.assetPaths={images:"",audios:"",data:""};var n=new Audio;return t.canUse=t.canUse||{},t.canUse.wav="",t.canUse.mp3=n.canPlayType("audio/mpeg;").replace(/^no$/,""),t.canUse.ogg=n.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),t.canUse.aac=n.canPlayType("audio/aac;").replace(/^no$/,""),t.canUse.m4a=(n.canPlayType("audio/x-m4a;")||t.canUse.aac).replace(/^no$/,""),t.getAssetExtension=function(t){return t.substr((~-t.lastIndexOf(".")>>>0)+2)},t.getAssetType=function(t){var n=this.getAssetExtension(t);return n.match(e)?"Image":n.match(i)?"Audio":"Data"},t.getAssetName=function(t){return t.replace(/\.[^/.]+$/,"")},t}(kontra||{}),kontra=function(t,e){return t.loadAssets=function(){var i,n,r=e.defer(),s=[],a=0,o=arguments.length;arguments.length||r.resolve();for(var h,c=0;h=arguments[c];c++)n=Array.isArray(h)?h[0]:h,i=this.getAssetType(n),function(e){s.push(e.promise),t["load"+i](n).then(function(){e.resolve(),r.notify({loaded:++a,total:o})},function(t){e.reject(t)})}(e.defer());return e.all(s).then(function(){r.resolve()},function(t){r.reject(t)}),r.promise},t.loadImage=function(i){var n=e.defer(),r=this.getAssetName(i),s=new Image;return i=this.assetPaths.images+i,s.onload=function(){t.images[r]=t.images[i]=this,n.resolve(this)},s.onerror=function(){n.reject("Unable to load image "+i)},s.src=i,n.promise},t.loadAudio=function(i){var n,r,s,a,o=e.defer();Array.isArray(i)||(i=[i]);for(var h=0;n=i[h];h++)if(this.canUse[this.getAssetExtension(n)]){s=n;break}return s?(r=this.getAssetName(s),a=new Audio,n=this.assetPaths.audios+s,a.addEventListener("canplay",function(){t.audios[r]=t.audios[n]=this,o.resolve(this)}),a.onerror=function(){o.reject("Unable to load audio "+n)},a.src=n,a.preload="auto",a.load()):o.reject("Browser cannot play any of the audio formats provided"),o.promise},t.loadData=function(i){var n=e.defer(),r=new XMLHttpRequest,s=this.getAssetName(i),a=this.assetPaths.data+i;return r.addEventListener("load",function(){if(200!==r.status)return void n.reject(r.responseText);try{var e=JSON.parse(r.responseText);t.data[s]=t.data[a]=e,n.resolve(e)}catch(i){var o=r.responseText;t.data[s]=t.data[a]=o,n.resolve(o)}}),r.open("GET",a,!0),r.send(),n.promise},t}(kontra||{},q),kontra=function(t,e){return t.bundles={},t.createBundle=function(t,e){this.bundles[t]||(this.bundles[t]=e||[])},t.loadBundles=function(){for(var t,i,n=e.defer(),r=[],s=0,a=0,o=0;i=arguments[o];o++)(t=this.bundles[i])?(a+=t.length,r.push(this.loadAssets.apply(this,t))):n.reject("Bundle '"+i+"' has not been created.");return e.all(r).then(function(){n.resolve()},function(t){n.reject(t)},function(){n.notify({loaded:++s,total:a})}),n.promise},t}(kontra||{},q),kontra=function(t,e){return t.loadManifest=function(i){var n,r=e.defer();return t.loadData(i).then(function(e){t.assetPaths.images=e.imagePath||"",t.assetPaths.audios=e.audioPath||"",t.assetPaths.data=e.dataPath||"";for(var i,s=0;i=e.bundles[s];s++)t.createBundle(i.name,i.assets);return e.loadBundles?(n="all"===e.loadBundles?Object.keys(t.bundles||{}):Array.isArray(e.loadBundles)?e.loadBundles:[e.loadBundles],void t.loadBundles.apply(t,n).then(function(){r.resolve()},function(t){r.reject(t)},function(t){r.notify(t)})):void r.resolve()},function(t){r.reject(t)}),r.promise},t}(kontra||{},q),kontra=function(t,e){"use strict";return t.init=function(i){if(i=i||{},t.isString(i.canvas))this.canvas=e.getElementById(i.canvas);else if(t.isCanvas(i.canvas))this.canvas=i.canvas;else if(this.canvas=e.getElementsByTagName("canvas")[0],!this.canvas){var n=new ReferenceError("No canvas element found.");return void t.logError(n,"You must provide a canvas element for the game.")}this.context=this.canvas.getContext("2d"),this.game={width:this.canvas.width,height:this.canvas.height}},t.logError=function(t,e){console.error("Kontra: "+e+"\n "+t.stack)},t.noop=function(){},t.isArray=Array.isArray,t.isString=function(t){return"string"==typeof t},t.isNumber=function(t){return"number"==typeof t},t.isImage=function(t){return t instanceof HTMLImageElement},t.isCanvas=function(t){return t instanceof HTMLCanvasElement},t}(kontra||{},document),kontra=function(t,e){"use strict";return t.timestamp=function(){return e.performance&&e.performance.now?function(){return e.performance.now()}:function(){return(new Date).getTime()}}(),t.gameLoop=function(e){var i=Object.create(t.gameLoop.prototype);return i.init(e),i},t.gameLoop.prototype={init:function(e){if(e=e||{},"function"!=typeof e.update||"function"!=typeof e.render){var i=new ReferenceError("Required functions not found");return void t.logError(i,"You must provide update() and render() functions to create a game loop.")}this.isStopped=!1,this._accumulator=0,this._delta=1e3/(e.fps||60),this._step=1/(e.fps||60),this.update=e.update,this.render=e.render},start:function(){this._last=t.timestamp(),this.isStopped=!1,requestAnimationFrame(this._frame.bind(this))},stop:function(){this.isStopped=!0,cancelAnimationFrame(this._rAF)},_frame:function(){var e=this;if(e._rAF=requestAnimationFrame(e._frame.bind(e)),e._now=t.timestamp(),e._dt=e._now-e._last,e._last=e._now,!(e._dt>1e3)){for(e._accumulator+=e._dt;e._accumulator>=e._delta;)e.update(e._step),e._accumulator-=e._delta;e.render()}}},t}(kontra||{},window),kontra=function(t,e){"use strict";function i(t){return"number"==typeof t.which?t.which:t.keyCode}function n(t){var e=[];t=t.trim().replace("++","+plus");for(var i,n=0;i=m[n];n++)-1!==t.indexOf(i)&&(e.push(i),t=t.replace(i,""));return t=t.replace(/\+/g,"").toLowerCase(),f[t]?e.push("shift+"+f[t]):t&&e.push(t),e.join("+")}function r(t){for(var e,n=[],r=0;e=m[r];r++)t[e+"Key"]&&n.push(e);var s=u[i(t)];return-1===n.indexOf(s)&&n.push(s),n.join("+")}function s(t){for(var e,i=r(t),n=0,s=i.split("+");e=s[n];n++)c[e]=!0;h[i]&&(h[i](t,i),t.preventDefault())}function a(t){var e=u[i(t)];c[e]=!1,l[e]&&(c[l[e]]=!1)}function o(t){c={}}for(var h={},c={},u={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",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:"delete",91:"leftwindow",92:"rightwindow",93:"select",144:"numlock",145:"scrolllock",106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},d=0;26>d;d++)u[65+d]=String.fromCharCode(65+d).toLowerCase();for(d=0;10>d;d++)u[48+d]=""+d;for(d=1;20>d;d++)u[111+d]="f"+d;for(d=0;10>d;d++)u[96+d]="numpad"+d;var f={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\",plus:"="},l={leftwindow:"meta",select:"meta"},m=["meta","ctrl","alt","shift"];return e.addEventListener("keydown",s),e.addEventListener("keyup",a),e.addEventListener("blur",o),t.keys={},t.keys.bind=function(e,i){if("function"!=typeof i){var r=new SyntaxError("Invalid function.");return void t.logError(r,"You must provide a function as the second parameter.")}e=t.isArray(e)?e:[e];for(var s,a=0;s=e[a];a++){var o=n(s);h[o]=i}},t.keys.unbind=function(e){e=t.isArray(e)?e:[e];for(var i,r=0;i=e[r];r++){var s=n(i);h[s]=void 0}},t.keys.pressed=function(t){var e=n(t),i=!0;t=e.split("+");for(var r,s=0;r=t[s];s++)i=i&&!!c[r];return i},t}(kontra||{},window),kontra=function(t){"use strict";return t.pool=function(e){var i=Object.create(t.pool.prototype);return i.init(e),i},t.pool.prototype={init:function(e){e=e||{};var i,n;if("function"!=typeof e.create)return i=new SyntaxError("Required function not found."),void t.logError(i,"Parameter 'create' must be a function that returns an object.");if(this.create=e.create.bind(this,e.createProperties||{}),n=this.create(),!n||"function"!=typeof n.render||"function"!=typeof n.update||"function"!=typeof n.init||"function"!=typeof n.isAlive)return i=new SyntaxError("Create object required functions not found."),void t.logError(i,"Objects to be pooled must implement render(), update(), init() and isAlive() functions.");if(this.objects=[n],this.size=1,this.maxSize=e.maxSize||1/0,this.lastIndex=0,this.inUse=0,e.fill){if(!e.maxSize)return i=new SyntaxError("Required property not found."),void t.logError(i,"Parameter 'maxSize' must be set before you can fill a pool.");for(;this.objects.length=n;)if(e=this.objects[i],e.update(t),e.isAlive())i--;else{for(var r=i;r>0;r--)this.objects[r]=this.objects[r-1];this.objects[0]=e,this.inUse--,n++}},render:function(){for(var t=Math.max(this.objects.length-this.inUse,0),e=this.lastIndex;e>=t;e--)this.objects[e].render()}},t}(kontra||{}),kontra=function(t,e){"use strict";return t.quadtree=function(e){var i=Object.create(t.quadtree.prototype);return i.init(e),i},t.quadtree.prototype={init:function(e){e=e||{},this.depth=e.depth||0,this.maxDepth=e.maxDepth||3,this.maxObjects=e.maxObjects||25,this.isBranchNode=!1,this.parentNode=e.parentNode,this.bounds=e.bounds||{x:0,y:0,width:t.game.width,height:t.game.height},this.objects=[],this.subnodes=[]},clear:function(){if(this.isBranchNode)for(var t=0;4>t;t++)this.subnodes[t].clear();this.isBranchNode=!1,this.objects.length=0},get:function(t){for(var e,i,n=this,r=[];n.subnodes.length&&this.isBranchNode;){e=this._getIndex(t);for(var s=0,a=e.length;a>s;s++)i=e[s],r.push.apply(r,this.subnodes[i].get(t));return r}return n.objects},add:function(){for(var e,i,n,r=this,s=0,a=arguments.length;a>s;s++)if(i=arguments[s],t.isArray(i))r.add.apply(this,i);else if(r.subnodes.length&&r.isBranchNode)r._addToSubnode(i);else if(r.objects.push(i),r.objects.length>r.maxObjects&&r.depthi;i++)this.subnodes[e[i]].add(t)},_getIndex:function(t){var e=[],i=this.bounds.x+this.bounds.width/2,n=this.bounds.y+this.bounds.height/2,r=t.y=this.bounds.y,s=t.y+t.height>=n&&t.y=this.bounds.x&&(r&&e.push(0),s&&e.push(2)),t.x+t.width>=i&&t.xn;n++)this.subnodes[n]=t.quadtree({bounds:{x:this.bounds.x+(n%2===1?e:0),y:this.bounds.y+(n>=2?i:0),width:e,height:i},depth:this.depth+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects,parentNode:this})},render:function(){if((this.objects.length||0===this.depth||this.parentNode&&this.parentNode.isBranchNode)&&(t.context.strokeStyle="red",t.context.strokeRect(this.bounds.x,this.bounds.y,this.bounds.width,this.bounds.height),this.subnodes.length))for(var e=0;4>e;e++)this.subnodes[e].render()}},t}(kontra||{}),kontra=function(t,e,i){"use strict";var n=["x","y","dx","dy","ddx","ddy","timeToLive","context","image","animations","color","width","height"];return t.vector=function(e){var i=Object.create(t.vector.prototype);return i.init(e),i},t.vector.prototype={init:function(t){return t=t||{},this.x=t.x||0,this.y=t.y||0,this},add:function(t,e){this.x+=(t.x||0)*(e||1),this.y+=(t.y||0)*(e||1)},clamp:function(t,n,r,s){this._xMin=t!==i?t:-(1/0),this._xMax=r!==i?r:1/0,this._yMin=n!==i?n:-(1/0),this._yMax=s!==i?s:1/0,this._x=this.x,this._y=this.y,Object.defineProperties(this,{x:{get:function(){return this._x},set:function(t){this._x=e.min(e.max(t,this._xMin),this._xMax)}},y:{get:function(){return this._y},set:function(t){this._y=e.min(e.max(t,this._yMin),this._yMax)}}})}},t.sprite=function(e){var i=Object.create(t.sprite.prototype);return i.init(e),i},t.sprite.prototype={init:function(e){e=e||{};var i=this;i.position=(i.position||t.vector()).init({x:e.x,y:e.y}),i.velocity=(i.velocity||t.vector()).init({x:e.dx,y:e.dy}),i.acceleration=(i.acceleration||t.vector()).init({x:e.ddx,y:e.ddy}),i.timeToLive=e.timeToLive||0,i.context=e.context||t.context,t.isImage(e.image)||t.isCanvas(e.image)?(i.image=e.image,i.width=e.image.width,i.height=e.image.height,i.advance=i._advanceSprite,i.draw=i._drawImage):e.animations?(i.animations=e.animations,i.currentAnimation=e.animations[Object.keys(e.animations)[0]],i.width=i.currentAnimation.width,i.height=i.currentAnimation.height,i.advance=i._advanceAnimation,i.draw=i._drawAnimation):(i.color=e.color,i.width=e.width,i.height=e.height,i.advance=i._advanceSprite,i.draw=i._drawRect);for(var r in e)e.hasOwnProperty(r)&&-1===n.indexOf(r)&&(i[r]=e[r])},get x(){return this.position.x},get y(){return this.position.y},get dx(){return this.velocity.x},get dy(){return this.velocity.y},get ddx(){return this.acceleration.x},get ddy(){return this.acceleration.y},set x(t){this.position.x=t},set y(t){this.position.y=t},set dx(t){this.velocity.x=t},set dy(t){this.velocity.y=t},set ddx(t){this.acceleration.x=t},set ddy(t){this.acceleration.y=t},isAlive:function(){return this.timeToLive>0},collidesWith:function(t){return this.xt.x&&this.yt.y?!0:!1},update:function(t){this.advance(t)},render:function(){this.draw()},playAnimation:function(t){this.currentAnimation=this.animations[t]},_advanceSprite:function(t){this.velocity.add(this.acceleration,t),this.position.add(this.velocity,t),this.timeToLive--},_advanceAnimation:function(t){this._advanceSprite(t),this.currentAnimation.update(t)},_drawRect:function(){this.context.fillStyle=this.color,this.context.fillRect(this.x,this.y,this.width,this.height)},_drawImage:function(){this.context.drawImage(this.image,this.x,this.y)},_drawAnimation:function(){this.currentAnimation.render({context:this.context,x:this.x,y:this.y})}},t}(kontra||{},Math),kontra=function(t,e){"use strict";return t.animation=function(e){var i=Object.create(t.animation.prototype);return i.init(e),i},t.animation.prototype={init:function(t){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.width=t.spriteSheet.frame.width,this.height=t.spriteSheet.frame.height,this.currentFrame=0,this._accumulator=0,this.update=this._advance,this.render=this._draw},play:function(){this.update=this._advance,this.render=this._draw},stop:function(){this.update=t.noop,this.render=t.noop},pause:function(){this.update=t.noop},_advance:function(t){for(t=t||1/60,this._accumulator+=t;this._accumulator*this.frameRate>=1;)this.currentFrame=++this.currentFrame%this.frames.length,this._accumulator-=1/this.frameRate},_draw:function(e){e=e||{};var i=e.context||t.context,n=this.frames[this.currentFrame]/this.spriteSheet.framesPerRow|0,r=this.frames[this.currentFrame]%this.spriteSheet.framesPerRow|0;i.drawImage(this.spriteSheet.image,r*this.spriteSheet.frame.width,n*this.spriteSheet.frame.height,this.spriteSheet.frame.width,this.spriteSheet.frame.height,e.x,e.y,this.spriteSheet.frame.width,this.spriteSheet.frame.height)}},t.spriteSheet=function(e){var i=Object.create(t.spriteSheet.prototype);return i.init(e),i},t.spriteSheet.prototype={init:function(e){if(e=e||{},this.animations={},!t.isImage(e.image)&&!t.isCanvas(e.image)){var i=new SyntaxError("Invalid image.");return void t.logError(i,"You must provide an Image for the SpriteSheet.")}this.image=e.image,this.frame={width:e.frameWidth,height:e.frameHeight},this.framesPerRow=e.image.width/e.frameWidth|0,e.animations&&this.createAnimations(e.animations)},createAnimations:function(i){var n;if(!i||0===Object.keys(i).length)return n=new ReferenceError("No animations found."),void t.logError(n,"You must provide at least one named animation to create an Animation.");var r,s,a,o;for(var h in i)if(i.hasOwnProperty(h)){if(r=i[h],s=r.frames,a=r.frameRate,o=[],s===e)return n=new ReferenceError("No animation frames found."),void t.logError(n,"Animation "+h+" must provide a frames property.");if(t.isNumber(s))o.push(s);else if(t.isString(s))o=this._parseFrames(s);else{if(!t.isArray(s))return n=new SyntaxError("Improper frames value"),void t.logError(n,"The frames property must be a number, string, or array.");for(var c,u=0;c=s[u];u++)t.isString(c)?o.push.apply(o,this._parseFrames(c)):o.push(c)}this.animations[h]=t.animation({spriteSheet:this,frames:o,frameRate:a})}},_parseFrames:function(t){var e,i=[],n=t.split("..").map(Number),r=n[0]=n[1];e--)i.push(e);return i}},t}(kontra||{}),kontra=function(t,e,i,n){"use strict";return t.canUse=t.canUse||{},t.canUse.localStorage="localStorage"in e&&null!==e.localStorage,t.canUse.localStorage?(t.store={},t.store.set=function(t,e){e===n?this.remove(t):i.setItem(t,JSON.stringify(e))},t.store.get=function(t){var e=i.getItem(t);try{e=JSON.parse(e)}catch(n){}return e},t.store.remove=function(t){i.removeItem(t)},t.store.clear=function(){i.clear()},t):t}(kontra||{},window,window.localStorage),kontra=function(t,e,i){"use strict";return t.tileEngine=function(e){var i=Object.create(t.tileEngine.prototype);return i.init(e),i},t.tileEngine.prototype={init:function(e){e=e||{};var i=this;if(!e.width||!e.height){var n=new ReferenceError("Required parameters not found");return void t.logError(n,"You must provide width and height of the map to create a tile engine.")}i.width=e.width,i.height=e.height,i.tileWidth=e.tileWidth||32,i.tileHeight=e.tileHeight||32,i.context=e.context||t.context,i.canvasWidth=i.context.canvas.width,i.canvasHeight=i.context.canvas.height,i._offscreenCanvas=document.createElement("canvas"),i._offscreenContext=i._offscreenCanvas.getContext("2d"),i._offscreenCanvas.width=i.mapWidth=i.width*i.tileWidth,i._offscreenCanvas.height=i.mapHeight=i.height*i.tileHeight,i.sxMax=i.mapWidth-i.canvasWidth,i.syMax=i.mapHeight-i.canvasHeight,i.layers={},i._layerOrder=[],i.tilesets=[],i.x=e.x||0,i.y=e.y||0,i.sx=e.sx||0,i.sy=e.sy||0},addTileset:function(e){if(e=e||{},!t.isImage(e.image)&&!t.isCanvas(e.image)){var i=new SyntaxError("Invalid image.");return void t.logError(i,"You must provide an Image for the tile engine.")}var n=e.image,r=e.firstGrid,s=(n.width/this.tileWidth|0)*(n.height/this.tileHeight|0);if(!r)if(this.tilesets.length>0){var a=this.tilesets[this.tilesets.length-1],o=(a.image.width/this.tileWidth|0)*(a.image.height/this.tileHeight|0);r=a.firstGrid+o}else r=1;this.tilesets.push({firstGrid:r,lastGrid:r+s-1,image:n}),this.tilesets.sort(function(t,e){return t.firstGrid-e.firstGrid})},addLayer:function(e){e=e||{},e.render=e.render===i?!0:e.render;var n,r=this;if(t.isArray(e.data[0])){n=[];for(var s,a=0;s=e.data[a];a++)for(var o=0,h=s.length;h>o;o++)n.push(s[o])}else n=e.data;this.layers[e.name]=n,this.layers[e.name].zIndex=e.zIndex||0,this.layers[e.name].render=e.render,e.render&&(this._layerOrder.push(e.name),this._layerOrder.sort(function(t,e){return r.layers[t].zIndex-r.layers[e].zIndex}),this._preRenderImage())},layerCollidesWith:function(t,e){for(var i,n=this._getRow(e.y),r=this._getCol(e.x),s=this._getRow(e.y+e.height),a=this._getCol(e.x+e.width),o=n;s>=o;o++)for(var h=r;a>=h;h++)if(i=h+o*this.width,this.layers[t][i])return!0;return!1},tileAtLayer:function(t,e,i){var n=this._getRow(i),r=this._getCol(e),s=r+n*this.width;return this.layers[t][s]},render:function(){var t=this;t.sx=e.min(e.max(t.sx,0),t.sxMax),t.sy=e.min(e.max(t.sy,0),t.syMax),t.context.drawImage(t._offscreenCanvas,t.sx,t.sy,t.canvasWidth,t.canvasHeight,t.x,t.y,t.canvasWidth,t.canvasHeight)},renderLayer:function(t){for(var i,n,r,s,a,o,h,c,u,d=this,f=d.layers[t],l=d._getRow(),m=d._getCol(),p=m+l*d.width,g=m*d.tileWidth-d.sx,v=l*d.tileHeight-d.sy,y=e.ceil(d.canvasWidth/d.tileWidth)+1,x=e.ceil(d.canvasHeight/d.tileHeight)+1,b=y*x,w=0;b>w;)r=f[p],r&&(s=d._getTileset(r),a=s.image,i=g+w%y*d.tileWidth,n=v+(w/y|0)*d.tileHeight,o=r-s.firstGrid,h=a.width/d.tileWidth,c=o%h*d.tileWidth,u=(o/h|0)*d.tileHeight,d.context.drawImage(a,c,u,d.tileWidth,d.tileHeight,i,n,d.tileWidth,d.tileHeight)),++w%y===0?p=m+ ++l*d.width:p++},_getRow:function(t){return t=t||0,(this.sy+t)/this.tileHeight|0},_getCol:function(t){return t=t||0,(this.sx+t)/this.tileWidth|0},_getTileset:function(t){for(var e,i,n=0,r=this.tilesets.length-1;r>=n;){if(e=(n+r)/2|0,i=this.tilesets[e],t>=i.firstGrid&&t<=i.lastGrid)return i;t>i?n=e+1:r=e-1}},_preRenderImage:function(){for(var t,e,i,n,r,s,a,o,h,c,u=this,d=0;c=u.layers[u._layerOrder[d]];d++)for(var f=0,l=c.length;l>f;f++)t=c[f],t&&(e=u._getTileset(t),i=e.image,n=f%u.width*u.tileWidth,r=(f/u.width|0)*u.tileHeight,o=t-e.firstGrid,h=i.width/u.tileWidth,s=o%h*u.tileWidth,a=(o/h|0)*u.tileHeight,u._offscreenContext.drawImage(i,s,a,u.tileWidth,u.tileHeight,n,r,u.tileWidth,u.tileHeight))}},t}(kontra||{},Math); //# sourceMappingURL=kontra.min.js.map \ No newline at end of file diff --git a/kontra.min.js.map b/kontra.min.js.map deleted file mode 100644 index 0d60e9ed..00000000 --- a/kontra.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["kontraAssetLoader.js","core.js","gameLoop.js","keyboard.js","pool.js","quadtree.js","sprite.js","spriteSheet.js","store.js","tileEngine.js"],"names":["qFactory","nextTick","exceptionHandler","forEach","obj","iterator","context","key","isFunction","hasOwnProperty","call","isArray","length","defaultCallback","value","defaultErrback","reason","reject","all","promises","deferred","defer","counter","results","promise","ref","then","resolve","notify","toString","pending","val","callbacks","undefined","callback","i","ii","createInternalRejectedPromise","progress","errback","progressback","result","wrappedCallback","e","wrappedErrback","wrappedProgressback","push","catch","this","finally","makePromise","resolved","handleCallback","isResolved","callbackOutput","error","when","done","window","q","setTimeout","console","stack","kontra","isImage","isAudio","images","audios","data","assetPaths","audio","Audio","canUse","wav","mp3","canPlayType","replace","ogg","aac","m4a","getAssetExtension","url","substr","lastIndexOf","getAssetType","extension","match","getAssetName","loadAssets","type","numLoaded","numAssets","arguments","asset","Array","assetDeferred","loaded","total","loadImage","name","image","Image","onload","onerror","src","loadAudio","source","playableSource","addEventListener","preload","load","loadData","req","XMLHttpRequest","dataUrl","status","responseText","json","JSON","parse","open","send","bundles","createBundle","bundle","assets","loadBundles","apply","loadManifest","manifest","imagePath","audioPath","dataPath","Object","keys","document","init","properties","isString","canvas","getElementById","isCanvas","getElementsByTagName","ReferenceError","logError","getContext","game","width","height","message","noop","isNumber","HTMLImageElement","HTMLCanvasElement","timestamp","performance","now","Date","getTime","gameLoop","create","prototype","update","render","isStopped","_accumulator","_delta","fps","start","_last","requestAnimationFrame","_frame","bind","stop","cancelAnimationFrame","_rAF","_this","_now","_dt","normalizeKeyCode","which","keyCode","normalizeKeys","combination","trim","modifier","modifierOrder","indexOf","toLowerCase","shiftKeys","join","getKeyCombination","keyMap","keydownEventHandler","split","pressedKeys","preventDefault","keyupEventHandler","aliases","blurEventHandler",8,9,13,16,17,18,20,27,32,33,34,35,36,37,38,39,40,45,46,91,92,93,144,145,106,107,109,110,111,186,187,188,189,190,191,192,219,220,221,222,"String","fromCharCode","~","!","@","#","$","%","^","&","*","(",")","_","+",":","\"","<",">","?","|","plus","leftwindow","select","SyntaxError","unbind","pressed","pool","createProperties","isAlive","objects","size","maxSize","Infinity","lastIndex","inUse","fill","unshift","get","x","getAliveObjects","slice","clear","dt","index","Math","max","j","quadtree","depth","maxDepth","maxObjects","isBranchNode","parentNode","bounds","y","subnodes","object","indices","node","_getIndex","add","_addToSubnode","_split","verticalMidpoint","horizontalMidpoint","position","intersectsTopQuadrants","intersectsBottomQuadrants","subWidth","subHeight","strokeStyle","strokeRect","excludedProperties","vector","clamp","xMin","yMin","xMax","yMax","_xMin","_xMax","_yMin","_yMax","_x","_y","defineProperties","set","min","sprite","advanceSprite","velocity","acceleration","timeToLive","drawRect","fillStyle","color","fillRect","drawImage","advanceAnimation","currentAnimation","drawAnimation","playAnimation","animations","dx","dy","ddx","ddy","advance","draw","prop",{"end":{"file":"?","comments_before":[],"nlb":false,"endpos":60138,"endcol":9,"endline":2146,"pos":60137,"col":8,"line":2146,"value":"x","type":"name"},"start":{"file":"?","comments_before":[],"nlb":false,"endpos":60138,"endcol":9,"endline":2146,"pos":60137,"col":8,"line":2146,"value":"x","type":"name"},"name":"x"},"collidesWith","animation","spriteSheet","frames","frameSpeed","frame","currentFrame","row","framesPerRow","col","play","pause","frameWidth","frameHeight","createAnimations","sequence","_parseFrames","consecutiveFrames","map","Number","direction","localStorage","store","remove","setItem","stringify","getItem","removeItem","tileEngine","tileWidth","tileHeight","canvasWidth","canvasHeight","_offscreenCanvas","createElement","_offscreenContext","mapWidth","mapHeight","sxMax","syMax","layers","_layerOrder","tilesets","sx","sy","addTileset","firstGrid","numTiles","lastTileset","tiles","lastGrid","sort","a","b","addLayer","r","c","zIndex","_preRenderImage","layerCollidesWith","_getRow","_getCol","endRow","endCol","tileAtLayer","renderLayer","tile","tileset","tileOffset","layer","startX","startY","viewWidth","ceil","viewHeight","count","_getTileset","currTile","len"],"mappings":"AAuCA,QAAAA,UAAAC,EAAAC,GAKA,QAAAC,GAAAC,EAAAC,EAAAC,GACA,GAAAC,EACA,IAAAH,EACA,GAAAI,EAAAJ,GACA,IAAAG,IAAAH,GAGA,aAAAG,GAAA,UAAAA,GAAA,QAAAA,GAAAH,EAAAK,iBAAAL,EAAAK,eAAAF,IACAF,EAAAK,KAAAJ,EAAAF,EAAAG,GAAAA,OAGA,IAAAH,EAAAD,SAAAC,EAAAD,UAAAA,EACAC,EAAAD,QAAAE,EAAAC,OACA,IAAAK,EAAAP,GACA,IAAAG,EAAA,EAAAA,EAAAH,EAAAQ,OAAAL,IACAF,EAAAK,KAAAJ,EAAAF,EAAAG,GAAAA,OAEA,KAAAA,IAAAH,GACAA,EAAAK,eAAAF,IACAF,EAAAK,KAAAJ,EAAAF,EAAAG,GAAAA,EAKA,OAAAH,GA0RA,QAAAS,GAAAC,GACA,MAAAA,GAIA,QAAAC,GAAAC,GACA,MAAAC,GAAAD,GAmBA,QAAAE,GAAAC,GACA,GAAAC,GAAAC,IACAC,EAAA,EACAC,EAAAZ,EAAAQ,QAqBA,OAnBAhB,GAAAgB,EAAA,SAAAK,EAAAjB,GACAe,IACAG,EAAAD,GAAAE,KAAA,SAAAZ,GACAS,EAAAd,eAAAF,KACAgB,EAAAhB,GAAAO,IACAQ,GAAAF,EAAAO,QAAAJ,KACA,SAAAP,GACAO,EAAAd,eAAAF,IACAa,EAAAH,OAAAD,IACA,SAAAA,GACAO,EAAAd,eAAAF,IACAa,EAAAQ,OAAAZ,OAIA,IAAAM,GACAF,EAAAO,QAAAJ,GAGAH,EAAAI,QAvWA,GAAAK,MAAAA,SACArB,EAAA,SAAAM,GAAA,MAAA,kBAAAA,IACAH,EAAA,SAAAG,GAAA,MAAA,mBAAAe,EAAAnB,KAAAI,IAuCAO,EAAA,WACA,GACAP,GAAAM,EADAU,IAgIA,OA7HAV,IAEAO,QAAA,SAAAI,GACA,GAAAD,EAAA,CACA,GAAAE,GAAAF,CACAA,GAAAG,OACAnB,EAAAW,EAAAM,GAEAC,EAAApB,QACAX,EAAA,WAEA,IAAA,GADAiC,GACAC,EAAA,EAAAC,EAAAJ,EAAApB,OAAAwB,EAAAD,EAAAA,IACAD,EAAAF,EAAAG,GACArB,EAAAY,KAAAQ,EAAA,GAAAA,EAAA,GAAAA,EAAA,QAQAjB,OAAA,SAAAD,GACAI,EAAAO,QAAAU,EAAArB,KAIAY,OAAA,SAAAU,GACA,GAAAR,EAAA,CACA,GAAAE,GAAAF,CAEAA,GAAAlB,QACAX,EAAA,WAEA,IAAA,GADAiC,GACAC,EAAA,EAAAC,EAAAJ,EAAApB,OAAAwB,EAAAD,EAAAA,IACAD,EAAAF,EAAAG,GACAD,EAAA,GAAAI,OAQAd,SACAE,KAAA,SAAAQ,EAAAK,EAAAC,GACA,GAAAC,GAAApB,IAEAqB,EAAA,SAAA5B,GACA,IACA2B,EAAAd,SAAAnB,EAAA0B,GAAAA,EAAArB,GAAAC,IACA,MAAA6B,GACAF,EAAAxB,OAAA0B,GACAzC,EAAAyC,KAIAC,EAAA,SAAA5B,GACA,IACAyB,EAAAd,SAAAnB,EAAA+B,GAAAA,EAAAxB,GAAAC,IACA,MAAA2B,GACAF,EAAAxB,OAAA0B,GACAzC,EAAAyC,KAIAE,EAAA,SAAAP,GACA,IACAG,EAAAb,QAAApB,EAAAgC,GAAAA,EAAA3B,GAAAyB,IACA,MAAAK,GACAzC,EAAAyC,IAUA,OANAb,GACAA,EAAAgB,MAAAJ,EAAAE,EAAAC,IAEA/B,EAAAY,KAAAgB,EAAAE,EAAAC,GAGAJ,EAAAjB,SAGAuB,QAAA,SAAAb,GACA,MAAAc,MAAAtB,KAAA,KAAAQ,IAGAe,UAAA,SAAAf,GAEA,QAAAgB,GAAApC,EAAAqC,GACA,GAAAV,GAAApB,GAMA,OALA8B,GACAV,EAAAd,QAAAb,GAEA2B,EAAAxB,OAAAH,GAEA2B,EAAAjB,QAGA,QAAA4B,GAAAtC,EAAAuC,GACA,GAAAC,GAAA,IACA,KACAA,GAAApB,GAAArB,KACA,MAAA8B,GACA,MAAAO,GAAAP,GAAA,GAEA,MAAAW,IAAA9C,EAAA8C,EAAA5B,MACA4B,EAAA5B,KAAA,WACA,MAAAwB,GAAApC,EAAAuC,IACA,SAAAE,GACA,MAAAL,GAAAK,GAAA,KAGAL,EAAApC,EAAAuC,GAIA,MAAAL,MAAAtB,KAAA,SAAAZ,GACA,MAAAsC,GAAAtC,GAAA,IACA,SAAAyC,GACA,MAAAH,GAAAG,GAAA,SAUA9B,EAAA,SAAAX,GACA,MAAAA,IAAAN,EAAAM,EAAAY,MAAAZ,GAEAY,KAAA,SAAAQ,GACA,GAAAO,GAAApB,GAIA,OAHApB,GAAA,WACAwC,EAAAd,QAAAO,EAAApB,MAEA2B,EAAAjB,WA0CAP,EAAA,SAAAD,GACA,GAAAyB,GAAApB,GAEA,OADAoB,GAAAxB,OAAAD,GACAyB,EAAAjB,SAGAa,EAAA,SAAArB,GACA,OACAU,KAAA,SAAAQ,EAAAK,GACA,GAAAE,GAAApB,GASA,OARApB,GAAA,WACA,IACAwC,EAAAd,SAAAnB,EAAA+B,GAAAA,EAAAxB,GAAAC,IACA,MAAA2B,GACAF,EAAAxB,OAAA0B,GACAzC,EAAAyC,MAGAF,EAAAjB,WAmBAgC,EAAA,SAAA1C,EAAAoB,EAAAK,EAAAC,GACA,GACAiB,GADAhB,EAAApB,IAGAqB,EAAA,SAAA5B,GACA,IACA,OAAAN,EAAA0B,GAAAA,EAAArB,GAAAC,GACA,MAAA6B,GAEA,MADAzC,GAAAyC,GACA1B,EAAA0B,KAIAC,EAAA,SAAA5B,GACA,IACA,OAAAR,EAAA+B,GAAAA,EAAAxB,GAAAC,GACA,MAAA2B,GAEA,MADAzC,GAAAyC,GACA1B,EAAA0B,KAIAE,EAAA,SAAAP,GACA,IACA,OAAA9B,EAAAgC,GAAAA,EAAA3B,GAAAyB,GACA,MAAAK,GACAzC,EAAAyC,IAmBA,OAfA1C,GAAA,WACAwB,EAAAX,GAAAY,KAAA,SAAAZ,GACA2C,IACAA,GAAA,EACAhB,EAAAd,QAAAF,EAAAX,GAAAY,KAAAgB,EAAAE,EAAAC,MACA,SAAA7B,GACAyC,IACAA,GAAA,EACAhB,EAAAd,QAAAiB,EAAA5B,MACA,SAAAsB,GACAmB,GACAhB,EAAAb,OAAAiB,EAAAP,QAIAG,EAAAjB,QAwDA,QACAH,MAAAA,EACAJ,OAAAA,EACAuC,KAAAA,EACAtC,IAAAA,GA/XAwC,OAAAC,EAAA3D,SAAA,SAAAkC,GACA0B,WAAA,WACA1B,KACA,IACA,SAAAS,GACAkB,QAAAN,MAAA,UAAAZ,EAAAmB,QA6XA,IAAAC,QAAA,SAAAA,GACA,GAAAC,GAAA,sBACAC,EAAA,wBAIAF,GAAAG,UACAH,EAAAI,UACAJ,EAAAK,QAGAL,EAAAM,YACAH,OAAA,GACAC,OAAA,GACAC,KAAA,GAKA,IAAAE,GAAA,GAAAC,MAuDA,OAtDAR,GAAAS,OAAAT,EAAAS,WACAT,EAAAS,OAAAC,IAAA,GACAV,EAAAS,OAAAE,IAAAJ,EAAAK,YAAA,eAAAC,QAAA,OAAA,IACAb,EAAAS,OAAAK,IAAAP,EAAAK,YAAA,8BAAAC,QAAA,OAAA,IACAb,EAAAS,OAAAM,IAAAR,EAAAK,YAAA,cAAAC,QAAA,OAAA,IACAb,EAAAS,OAAAO,KAAAT,EAAAK,YAAA,iBAAAZ,EAAAS,OAAAM,KAAAF,QAAA,OAAA,IAWAb,EAAAiB,kBAAA,SAAAC,GACA,MAAAA,GAAAC,UAAAD,EAAAE,YAAA,OAAA,GAAA,IAWApB,EAAAqB,aAAA,SAAAH,GACA,GAAAI,GAAArC,KAAAgC,kBAAAC,EAEA,OAAAI,GAAAC,MAAAtB,GACA,QAEAqB,EAAAC,MAAArB,GACA,QAGA,QAYAF,EAAAwB,aAAA,SAAAN,GACA,MAAAA,GAAAL,QAAA,YAAA,KAGAb,GACAA,YAGAA,OAAA,SAAAA,EAAAJ,GAsNA,MAvMAI,GAAAyB,WAAA,WACA,GAIAC,GAAAR,EAJA7D,EAAAuC,EAAAtC,QACAF,KACAuE,EAAA,EACAC,EAAAC,UAAAhF,MAGAgF,WAAAhF,QACAQ,EAAAO,SAGA,KAAA,GAAAkE,GAAA1D,EAAA,EAAA0D,EAAAD,UAAAzD,GAAAA,IAKA8C,EAJAa,MAAAnF,QAAAkF,GAIAA,EAAA,GAHAA,EAMAJ,EAAAzC,KAAAoC,aAAAH,GAGA,SAAAc,GACA5E,EAAA2B,KAAAiD,EAAAvE,SAEAuC,EAAA,OAAA0B,GAAAR,GAAAvD,KACA,WACAqE,EAAApE,UACAP,EAAAQ,QAAAoE,SAAAN,EAAAO,MAAAN,KAEA,SAAApC,GACAwC,EAAA9E,OAAAsC,MAEAI,EAAAtC,QAWA,OARAsC,GAAAzC,IAAAC,GAAAO,KACA,WACAN,EAAAO,WAEA,SAAA4B,GACAnC,EAAAH,OAAAsC,KAGAnC,EAAAI,SAeAuC,EAAAmC,UAAA,SAAAjB,GACA,GAAA7D,GAAAuC,EAAAtC,QACA8E,EAAAnD,KAAAuC,aAAAN,GACAmB,EAAA,GAAAC,MAeA,OAbApB,GAAAjC,KAAAqB,WAAAH,OAAAe,EAEAmB,EAAAE,OAAA,WACAvC,EAAAG,OAAAiC,GAAApC,EAAAG,OAAAe,GAAAjC,KACA5B,EAAAO,QAAAqB,OAGAoD,EAAAG,QAAA,WACAnF,EAAAH,OAAA,wBAAAgE,IAGAmB,EAAAI,IAAAvB,EAEA7D,EAAAI,SAmCAuC,EAAA0C,UAAA,SAAAxB,GACA,GACAyB,GAAAP,EAAAQ,EAAArC,EADAlD,EAAAuC,EAAAtC,OAGAyE,OAAAnF,QAAAsE,KACAA,GAAAA,GAIA,KAAA,GAAA9C,GAAA,EAAAuE,EAAAzB,EAAA9C,GAAAA,IACA,GAAAa,KAAAwB,OAAAxB,KAAAgC,kBAAA0B,IAAA,CACAC,EAAAD,CACA,OA2BA,MAvBAC,IAIAR,EAAAnD,KAAAuC,aAAAoB,GACArC,EAAA,GAAAC,OAEAmC,EAAA1D,KAAAqB,WAAAF,OAAAwC,EAEArC,EAAAsC,iBAAA,UAAA,WACA7C,EAAAI,OAAAgC,GAAApC,EAAAI,OAAAuC,GAAA1D,KACA5B,EAAAO,QAAAqB,QAGAsB,EAAAiC,QAAA,WACAnF,EAAAH,OAAA,wBAAAyF,IAGApC,EAAAkC,IAAAE,EACApC,EAAAuC,QAAA,OACAvC,EAAAwC,QAnBA1F,EAAAH,OAAA,yDAsBAG,EAAAI,SAgBAuC,EAAAgD,SAAA,SAAA9B,GACA,GAAA7D,GAAAuC,EAAAtC,QACA2F,EAAA,GAAAC,gBACAd,EAAAnD,KAAAuC,aAAAN,GACAiC,EAAAlE,KAAAqB,WAAAD,KAAAa,CAyBA,OAvBA+B,GAAAJ,iBAAA,OAAA,WACA,GAAA,MAAAI,EAAAG,OAEA,WADA/F,GAAAH,OAAA+F,EAAAI,aAIA,KACA,GAAAC,GAAAC,KAAAC,MAAAP,EAAAI,aACArD,GAAAK,KAAA+B,GAAApC,EAAAK,KAAA8C,GAAAG,EAEAjG,EAAAO,QAAA0F,GAEA,MAAA1E,GACA,GAAAyB,GAAA4C,EAAAI,YACArD,GAAAK,KAAA+B,GAAApC,EAAAK,KAAA8C,GAAA9C,EAEAhD,EAAAO,QAAAyC,MAIA4C,EAAAQ,KAAA,MAAAN,GAAA,GACAF,EAAAS,OAEArG,EAAAI,SAGAuC,GACAA,WAAAJ,GAGAI,OAAA,SAAAA,EAAAJ,GAiEA,MAhEAI,GAAA2D,WAYA3D,EAAA4D,aAAA,SAAAC,EAAAC,GACA7E,KAAA0E,QAAAE,KAIA5E,KAAA0E,QAAAE,GAAAC,QAeA9D,EAAA+D,YAAA,WAOA,IAAA,GAFAD,GAEAD,EANAxG,EAAAuC,EAAAtC,QACAF,KACAuE,EAAA,EACAC,EAAA,EAGAxD,EAAA,EAAAyF,EAAAhC,UAAAzD,GAAAA,KACA0F,EAAA7E,KAAA0E,QAAAE,KAKAjC,GAAAkC,EAAAjH,OAEAO,EAAA2B,KAAAE,KAAAwC,WAAAuC,MAAA/E,KAAA6E,KANAzG,EAAAH,OAAA,WAAA2G,EAAA,0BAoBA,OAXAjE,GAAAzC,IAAAC,GAAAO,KACA,WACAN,EAAAO,WAEA,SAAA4B,GACAnC,EAAAH,OAAAsC,IAEA,WACAnC,EAAAQ,QAAAoE,SAAAN,EAAAO,MAAAN,MAGAvE,EAAAI,SAGAuC,GACAA,WAAAJ,GAGAI,OAAA,SAAAA,EAAAJ,GA4DA,MAnDAI,GAAAiE,aAAA,SAAA/C,GACA,GACAyC,GADAtG,EAAAuC,EAAAtC,OA+CA,OA5CA0C,GAAAgD,SAAA9B,GAAAvD,KACA,SAAAuG,GACAlE,EAAAM,WAAAH,OAAA+D,EAAAC,WAAA,GACAnE,EAAAM,WAAAF,OAAA8D,EAAAE,WAAA,GACApE,EAAAM,WAAAD,KAAA6D,EAAAG,UAAA,EAGA,KAAA,GAAAR,GAAAzF,EAAA,EAAAyF,EAAAK,EAAAP,QAAAvF,GAAAA,IACA4B,EAAA4D,aAAAC,EAAAzB,KAAAyB,EAAAC,OAGA,OAAAI,GAAAH,aAOAJ,EADA,QAAAO,EAAAH,YACAO,OAAAC,KAAAvE,EAAA2D,aAGA5B,MAAAnF,QAAAsH,EAAAH,aAKAG,EAAAH,aAJAG,EAAAH,iBAOA/D,GAAA+D,YAAAC,MAAAhE,EAAA2D,GAAAhG,KACA,WACAN,EAAAO,WAEA,SAAA4B,GACAnC,EAAAH,OAAAsC,IAEA,SAAAjB,GACAlB,EAAAQ,OAAAU,UAzBAlB,GAAAO,WA4BA,SAAA4B,GACAnC,EAAAH,OAAAsC,KAGAnC,EAAAI,SAGAuC,GACAA,WAAAJ,GCj0BAI,OAAA,SAAAA,EAAAwE,GACA,YA8GA,OArGAxE,GAAAyE,KAAA,SAAAC,GAGA,GAFAA,EAAAA,MAEA1E,EAAA2E,SAAAD,EAAAE,QACA3F,KAAA2F,OAAAJ,EAAAK,eAAAH,EAAAE,YAEA,IAAA5E,EAAA8E,SAAAJ,EAAAE,QACA3F,KAAA2F,OAAAF,EAAAE,WAKA,IAFA3F,KAAA2F,OAAAJ,EAAAO,qBAAA,UAAA,IAEA9F,KAAA2F,OAAA,CACA,GAAApF,GAAA,GAAAwF,gBAAA,2BAEA,YADAhF,GAAAiF,SAAAzF,EAAA,mDAKAP,KAAA1C,QAAA0C,KAAA2F,OAAAM,WAAA,MACAjG,KAAAkG,MACAC,MAAAnG,KAAA2F,OAAAQ,MACAC,OAAApG,KAAA2F,OAAAS,SAWArF,EAAAiF,SAAA,SAAAzF,EAAA8F,GACAxF,QAAAN,MAAA,WAAA8F,EAAA,MAAA9F,EAAAO,QAOAC,EAAAuF,KAAA,aAUAvF,EAAApD,QAAAmF,MAAAnF,QAUAoD,EAAA2E,SAAA,SAAA5H,GACA,MAAA,gBAAAA,IAWAiD,EAAAwF,SAAA,SAAAzI,GACA,MAAA,gBAAAA,IAWAiD,EAAAC,QAAA,SAAAlD,GACA,MAAAA,aAAA0I,mBAWAzF,EAAA8E,SAAA,SAAA/H,GACA,MAAAA,aAAA2I,oBAGA1F,GACAA,WAAAwE,UClHAxE,OAAA,SAAAA,EAAAL,GACA,YAmHA,OA1GAK,GAAA2F,UAAA,WACA,MAAAhG,GAAAiG,aAAAjG,EAAAiG,YAAAC,IACA,WACA,MAAAlG,GAAAiG,YAAAC,OAIA,WACA,OAAA,GAAAC,OAAAC,cAWA/F,EAAAgG,SAAA,SAAAtB,GACA,GAAAsB,GAAA1B,OAAA2B,OAAAjG,EAAAgG,SAAAE,UAGA,OAFAF,GAAAvB,KAAAC,GAEAsB,GAGAhG,EAAAgG,SAAAE,WAUAzB,KAAA,SAAAC,GAIA,GAHAA,EAAAA,MAGA,kBAAAA,GAAAyB,QAAA,kBAAAzB,GAAA0B,OAAA,CACA,GAAA5G,GAAA,GAAAwF,gBAAA,+BAEA,YADAhF,GAAAiF,SAAAzF,EAAA,2EAIAP,KAAAoH,WAAA,EAGApH,KAAAqH,aAAA,EACArH,KAAAsH,OAAA,KAAA7B,EAAA8B,KAAA,IAEAvH,KAAAkH,OAAAzB,EAAAyB,OACAlH,KAAAmH,OAAA1B,EAAA0B,QAOAK,MAAA,WACAxH,KAAAyH,MAAA1G,EAAA2F,YACA1G,KAAAoH,WAAA,EACAM,sBAAA1H,KAAA2H,OAAAC,KAAA5H,QAMA6H,KAAA,WACA7H,KAAAoH,WAAA,EACAU,qBAAA9H,KAAA+H,OAQAJ,OAAA,WACA,GAAAK,GAAAhI,IAUA,IARAgI,EAAAD,KAAAL,sBAAAM,EAAAL,OAAAC,KAAAI,IAEAA,EAAAC,KAAAlH,EAAA2F,YACAsB,EAAAE,IAAAF,EAAAC,KAAAD,EAAAP,MACAO,EAAAP,MAAAO,EAAAC,OAIAD,EAAAE,IAAA,KAAA,CAMA,IAFAF,EAAAX,cAAAW,EAAAE,IAEAF,EAAAX,cAAAW,EAAAV,QACAU,EAAAd,OAAAc,EAAAV,OAAA,KAEAU,EAAAX,cAAAW,EAAAV,MAGAU,GAAAb,YAIApG,GACAA,WAAAL,QCnHAK,OAAA,SAAAA,EAAAL,GACA,YAoLA,SAAAyH,GAAAxI,GACA,MAAA,gBAAAA,GAAAyI,MAAAzI,EAAAyI,MAAAzI,EAAA0I,QAeA,QAAAC,GAAAhD,GACA,GAAAiD,KAGAjD,GAAAA,EAAAkD,OAAA5G,QAAA,KAAA,QAGA,KAAA,GAAA6G,GAAAtJ,EAAA,EAAAsJ,EAAAC,EAAAvJ,GAAAA,IAGA,KAAAmG,EAAAqD,QAAAF,KACAF,EAAAzI,KAAA2I,GACAnD,EAAAA,EAAA1D,QAAA6G,EAAA,IAeA,OAVAnD,GAAAA,EAAA1D,QAAA,MAAA,IAAAgH,cAGAC,EAAAvD,GACAiD,EAAAzI,KAAA,SAAA+I,EAAAvD,IAEAA,GACAiD,EAAAzI,KAAAwF,GAGAiD,EAAAO,KAAA,KAWA,QAAAC,GAAApJ,GAIA,IAAA,GAAA8I,GAHAF,KAGApJ,EAAA,EAAAsJ,EAAAC,EAAAvJ,GAAAA,IACAQ,EAAA8I,EAAA,QACAF,EAAAzI,KAAA2I,EAIA,IAAAlL,GAAAyL,EAAAb,EAAAxI,GASA,OAJA,KAAA4I,EAAAI,QAAApL,IACAgL,EAAAzI,KAAAvC,GAGAgL,EAAAO,KAAA,KASA,QAAAG,GAAAtJ,GAIA,IAAA,GAAApC,GAHAgL,EAAAQ,EAAApJ,GAGAR,EAAA,EAAAmG,EAAAiD,EAAAW,MAAA,KAAA3L,EAAA+H,EAAAnG,GAAAA,IACAgK,EAAA5L,IAAA,CAGAyB,GAAAuJ,KACAvJ,EAAAuJ,GAAA5I,EAAA4I,GACA5I,EAAAyJ,kBAUA,QAAAC,GAAA1J,GACA,GAAApC,GAAAyL,EAAAb,EAAAxI,GACAwJ,GAAA5L,IAAA,EAEA+L,EAAA/L,KACA4L,EAAAG,EAAA/L,KAAA,GAUA,QAAAgM,GAAA5J,GACAwJ,KAtPA,IAAA,GAlDAnK,MACAmK,KAEAH,GAEAQ,EAAA,YACAC,EAAA,MACAC,GAAA,QACAC,GAAA,QACAC,GAAA,OACAC,GAAA,MACAC,GAAA,WACAC,GAAA,MACAC,GAAA,QACAC,GAAA,SACAC,GAAA,WACAC,GAAA,MACAC,GAAA,OACAC,GAAA,OACAC,GAAA,KACAC,GAAA,QACAC,GAAA,OACAC,GAAA,SACAC,GAAA,SACAC,GAAA,aACAC,GAAA,cACAC,GAAA,SACAC,IAAA,UACAC,IAAA,aAGAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,KACAC,IAAA,IACAC,IAAA,KAIA5M,EAAA,EAAA,GAAAA,EAAAA,IACA6J,EAAA,GAAA7J,GAAA6M,OAAAC,aAAA,GAAA9M,GAAAyJ,aAGA,KAAAzJ,EAAA,EAAA,GAAAA,EAAAA,IACA6J,EAAA,GAAA7J,GAAA,GAAAA,CAGA,KAAAA,EAAA,EAAA,GAAAA,EAAAA,IACA6J,EAAA,IAAA7J,GAAA,IAAAA,CAGA,KAAAA,EAAA,EAAA,GAAAA,EAAAA,IACA6J,EAAA,GAAA7J,GAAA,SAAAA,CAIA,IAAA0J,IACAqD,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,EAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,EAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,IACAC,IAAA,KACAC,KAAA,KAIA/D,GACAgE,WAAA,OACAC,OAAA,QAIA7E,GAAA,OAAA,OAAA,MAAA,QA0MA,OAxMAhI,GAAAkD,iBAAA,UAAAqF,GACAvI,EAAAkD,iBAAA,QAAAyF,GACA3I,EAAAkD,iBAAA,OAAA2F,GAKAxI,EAAAuE,QAWAvE,EAAAuE,KAAAsC,KAAA,SAAAtC,EAAApG,GACA,GAAA,kBAAAA,GAAA,CACA,GAAAqB,GAAA,GAAAiN,aAAA,oBAEA,YADAzM,GAAAiF,SAAAzF,EAAA,wDAIA+E,EAAAvE,EAAApD,QAAA2H,GAAAA,GAAAA,EAEA,KAAA,GAAA/H,GAAA4B,EAAA,EAAA5B,EAAA+H,EAAAnG,GAAAA,IAAA,CACA,GAAAoJ,GAAAD,EAAA/K,EAEAyB,GAAAuJ,GAAArJ,IAUA6B,EAAAuE,KAAAmI,OAAA,SAAAnI,GACAA,EAAAvE,EAAApD,QAAA2H,GAAAA,GAAAA,EAEA,KAAA,GAAA/H,GAAA4B,EAAA,EAAA5B,EAAA+H,EAAAnG,GAAAA,IAAA,CACA,GAAAoJ,GAAAD,EAAA/K,EAEAyB,GAAAuJ,GAAAtJ,SAYA8B,EAAAuE,KAAAoI,QAAA,SAAApI,GACA,GAAAiD,GAAAD,EAAAhD,GACAoI,GAAA,CAGApI,GAAAiD,EAAAW,MAAA,IACA,KAAA,GAAA3L,GAAA4B,EAAA,EAAA5B,EAAA+H,EAAAnG,GAAAA,IACAuO,EAAAA,KAAAvE,EAAA5L,EAGA,OAAAmQ,IAoIA3M,GACAA,WAAAL,QC/SAK,OAAA,SAAAA,GACA,YA2MA,OAlMAA,GAAA4M,KAAA,SAAAlI,GACA,GAAAkI,GAAAtI,OAAA2B,OAAAjG,EAAA4M,KAAA1G,UAGA,OAFA0G,GAAAnI,KAAAC,GAEAkI,GAGA5M,EAAA4M,KAAA1G,WAcAzB,KAAA,SAAAC,GACAA,EAAAA,KAEA,IAAAlF,GAAAnD,CAIA,IAAA,kBAAAqI,GAAAuB,OAGA,MAFAzG,GAAA,GAAAiN,aAAA,oCACAzM,GAAAiF,SAAAzF,EAAA,gEAUA,IALAP,KAAAgH,OAAAvB,EAAAuB,OAAAY,KAAA5H,KAAAyF,EAAAmI,sBAGAxQ,EAAA4C,KAAAgH,UAEA5J,GAAA,kBAAAA,GAAA+J,QAAA,kBAAA/J,GAAA8J,QACA,kBAAA9J,GAAAoI,MAAA,kBAAApI,GAAAyQ,QAGA,MAFAtN,GAAA,GAAAiN,aAAA,mDACAzM,GAAAiF,SAAAzF,EAAA,0FAYA,IAPAP,KAAA8N,SAAA1Q,GACA4C,KAAA+N,KAAA,EACA/N,KAAAgO,QAAAvI,EAAAuI,SAAAC,EAAAA,EACAjO,KAAAkO,UAAA,EACAlO,KAAAmO,MAAA,EAGA1I,EAAA2I,KAAA,CACA,IAAA3I,EAAAuI,QAWA,MAFAzN,GAAA,GAAAiN,aAAA,oCACAzM,GAAAiF,SAAAzF,EAAA,8DATA,MAAAP,KAAA8N,QAAAlQ,OAAAoC,KAAAgO,SACAhO,KAAA8N,QAAAO,QAAArO,KAAAgH,SAGAhH,MAAA+N,KAAA/N,KAAAgO,QACAhO,KAAAkO,UAAAlO,KAAAgO,QAAA,IAgBAM,IAAA,SAAA7I,GACAA,EAAAA,KAEA,IAAAuC,GAAAhI,IAGA,IAAAgI,EAAA8F,QAAA,GAAAD,UAAA,CACA,GAAA7F,EAAA+F,OAAA/F,EAAAgG,QACA,MAIA,KAAA,GAAAO,GAAA,EAAAA,EAAAvG,EAAA+F,MAAA/F,EAAA8F,QAAAlQ,OAAAoK,EAAAgG,QAAAO,IACAvG,EAAA8F,QAAAO,QAAArG,EAAAhB,SAGAgB,GAAA+F,KAAA/F,EAAA8F,QAAAlQ,OACAoK,EAAAkG,UAAAlG,EAAA+F,KAAA,EAKA,GAAA3Q,GAAA4K,EAAA8F,QAAA,EACA1Q,GAAAoI,KAAAC,EAGA,KAAA,GAAAtG,GAAA,EAAAA,EAAA6I,EAAA+F,KAAA5O,IACA6I,EAAA8F,QAAA3O,EAAA,GAAA6I,EAAA8F,QAAA3O,EAGA6I,GAAA8F,QAAA9F,EAAAkG,WAAA9Q,EACA4K,EAAAmG,SASAK,gBAAA,WACA,MAAAxO,MAAA8N,QAAAW,MAAAzO,KAAA8N,QAAAlQ,OAAAoC,KAAAmO,QAOAO,MAAA,WACA1O,KAAAmO,MAAA,EACAnO,KAAA+N,KAAA,EACA/N,KAAAkO,UAAA,EACAlO,KAAA8N,QAAAlQ,OAAA,EACAoC,KAAA8N,QAAAhO,KAAAE,KAAAgH,WASAE,OAAA,SAAAyH,GAeA,IAdA,GACAvR,GADA+B,EAAAa,KAAAkO,UAWAU,EAAAC,KAAAC,IAAA9O,KAAA8N,QAAAlQ,OAAAoC,KAAAmO,MAAA,GAGAhP,GAAAyP,GAMA,GALAxR,EAAA4C,KAAA8N,QAAA3O,GAEA/B,EAAA8J,OAAAyH,GAGAvR,EAAAyQ,UAeA1O,QAfA,CAMA,IAAA,GAAA4P,GAAA5P,EAAA4P,EAAA,EAAAA,IACA/O,KAAA8N,QAAAiB,GAAA/O,KAAA8N,QAAAiB,EAAA,EAGA/O,MAAA8N,QAAA,GAAA1Q,EACA4C,KAAAmO,QACAS,MAYAzH,OAAA,WAGA,IAAA,GAFAyH,GAAAC,KAAAC,IAAA9O,KAAA8N,QAAAlQ,OAAAoC,KAAAmO,MAAA,GAEAhP,EAAAa,KAAAkO,UAAA/O,GAAAyP,EAAAzP,IACAa,KAAA8N,QAAA3O,GAAAgI,WAKApG,GACAA,YC7MAA,OAAA,SAAAA,EAAA9B,GACA,YA2QA,OA1PA8B,GAAAiO,SAAA,SAAAvJ,GACA,GAAAuJ,GAAA3J,OAAA2B,OAAAjG,EAAAiO,SAAA/H,UAGA,OAFA+H,GAAAxJ,KAAAC,GAEAuJ,GAGAjO,EAAAiO,SAAA/H,WAWAzB,KAAA,SAAAC,GACAA,EAAAA,MAEAzF,KAAAiP,MAAAxJ,EAAAwJ,OAAA,EACAjP,KAAAkP,SAAAzJ,EAAAyJ,UAAA,EACAlP,KAAAmP,WAAA1J,EAAA0J,YAAA,GAIAnP,KAAAoP,cAAA,EAEApP,KAAAqP,WAAA5J,EAAA4J,WAEArP,KAAAsP,OAAA7J,EAAA6J,SACAf,EAAA,EACAgB,EAAA,EACApJ,MAAApF,EAAAmF,KAAAC,MACAC,OAAArF,EAAAmF,KAAAE,QAGApG,KAAA8N,WACA9N,KAAAwP,aAOAd,MAAA,WACA,GAAA1O,KAAAoP,aACA,IAAA,GAAAjQ,GAAA,EAAA,EAAAA,EAAAA,IACAa,KAAAwP,SAAArQ,GAAAuP,OAIA1O,MAAAoP,cAAA,EACApP,KAAA8N,QAAAlQ,OAAA,GAYA0Q,IAAA,SAAAmB,GAMA,IALA,GAEAC,GAAAd,EAFAe,EAAA3P,KACA8N,KAIA6B,EAAAH,SAAA5R,QAAAoC,KAAAoP,cAAA,CACAM,EAAA1P,KAAA4P,UAAAH,EAEA,KAAA,GAAAtQ,GAAA,EAAAvB,EAAA8R,EAAA9R,OAAAA,EAAAuB,EAAAA,IACAyP,EAAAc,EAAAvQ,GAEA2O,EAAAhO,KAAAiF,MAAA+I,EAAA9N,KAAAwP,SAAAZ,GAAAN,IAAAmB,GAGA,OAAA3B,GAGA,MAAA6B,GAAA7B,SASA+B,IAAA,WAIA,IAAA,GAFA1Q,GAAAsQ,EAAArS,EADA4K,EAAAhI,KAGA+O,EAAA,EAAAnR,EAAAgF,UAAAhF,OAAAA,EAAAmR,EAAAA,IAIA,GAHAU,EAAA7M,UAAAmM,GAGAhO,EAAApD,QAAA8R,GACAzH,EAAA6H,IAAA9K,MAAA/E,KAAAyP,OAMA,IAAAzH,EAAAwH,SAAA5R,QAAAoK,EAAAoH,aACApH,EAAA8H,cAAAL,OASA,IAHAzH,EAAA8F,QAAAhO,KAAA2P,GAGAzH,EAAA8F,QAAAlQ,OAAAoK,EAAAmH,YAAAnH,EAAAiH,MAAAjH,EAAAkH,SAAA,CAIA,IAHAlH,EAAA+H,SAGA5Q,EAAA,EAAA/B,EAAA4K,EAAA8F,QAAA3O,GAAAA,IACA6I,EAAA8H,cAAA1S,EAGA4K,GAAA8F,QAAAlQ,OAAA,IAYAkS,cAAA,SAAAL,GAIA,IAAA,GAHAC,GAAA1P,KAAA4P,UAAAH,GAGAtQ,EAAA,EAAAvB,EAAA8R,EAAA9R,OAAAA,EAAAuB,EAAAA,IACAa,KAAAwP,SAAAE,EAAAvQ,IAAA0Q,IAAAJ,IAaAG,UAAA,SAAAH,GACA,GAAAC,MAEAM,EAAAhQ,KAAAsP,OAAAf,EAAAvO,KAAAsP,OAAAnJ,MAAA,EACA8J,EAAAjQ,KAAAsP,OAAAC,EAAAvP,KAAAsP,OAAAlJ,OAAA,EAGAmI,EAAAkB,EAAAlB,IAAAtP,EAAAwQ,EAAAlB,EAAAkB,EAAAS,SAAA3B,EACAgB,EAAAE,EAAAF,IAAAtQ,EAAAwQ,EAAAF,EAAAE,EAAAS,SAAAX,EAGAY,EAAAF,EAAAV,GAAAA,EAAAE,EAAArJ,QAAApG,KAAAsP,OAAAC,EACAa,EAAAb,EAAAE,EAAArJ,QAAA6J,GAAAV,EAAAvP,KAAAsP,OAAAC,EAAAvP,KAAAsP,OAAAlJ,MAwBA,OArBA4J,GAAAzB,GAAAA,EAAAkB,EAAAtJ,OAAAnG,KAAAsP,OAAAf,IACA4B,GACAT,EAAA5P,KAAA,GAGAsQ,GACAV,EAAA5P,KAAA,IAKAyO,EAAAkB,EAAAtJ,OAAA6J,GAAAzB,EAAAvO,KAAAsP,OAAAf,EAAAvO,KAAAsP,OAAAnJ,QACAgK,GACAT,EAAA5P,KAAA,GAGAsQ,GACAV,EAAA5P,KAAA,IAIA4P,GAQAK,OAAA,WAIA,GAHA/P,KAAAoP,cAAA,GAGApP,KAAAwP,SAAA5R,OASA,IAAA,GALAyS,GAAArQ,KAAAsP,OAAAnJ,MAAA,EAAA,EACAmK,EAAAtQ,KAAAsP,OAAAlJ,OAAA,EAAA,EACAmI,EAAAvO,KAAAsP,OAAAf,EACAgB,EAAAvP,KAAAsP,OAAAC,EAEApQ,EAAA,EAAA,EAAAA,EAAAA,IACAa,KAAAwP,SAAArQ,GAAA4B,EAAAiO,UACAM,QACAf,EAAAA,GAAApP,EAAA,IAAA,EAAAkR,EAAA,GACAd,EAAAA,GAAApQ,GAAA,EAAAmR,EAAA,GACAnK,MAAAkK,EACAjK,OAAAkK,GAEArB,MAAAjP,KAAAiP,MAAA,EACAC,SAAAlP,KAAAkP,SACAC,WAAAnP,KAAAmP,WACAE,WAAArP,QASAmH,OAAA,WAEA,IAAAnH,KAAA8N,QAAAlQ,QAAA,IAAAoC,KAAAiP,OACAjP,KAAAqP,YAAArP,KAAAqP,WAAAD,gBAEArO,EAAAzD,QAAAiT,YAAA,MACAxP,EAAAzD,QAAAkT,WAAAxQ,KAAAsP,OAAAf,EAAAvO,KAAAsP,OAAAC,EAAAvP,KAAAsP,OAAAnJ,MAAAnG,KAAAsP,OAAAlJ,QAEApG,KAAAwP,SAAA5R,QACA,IAAA,GAAAuB,GAAA,EAAA,EAAAA,EAAAA,IACAa,KAAAwP,SAAArQ,GAAAgI,WAOApG,GACAA,YC/QAA,OAAA,SAAAA,EAAA8N,EAAA5P,GACA,YAGA,IAAAwR,IACA,IACA,IACA,KACA,KACA,MACA,MACA,aACA,UACA,QACA,aACA,QACA,QACA,SAgaA,OAvZA1P,GAAA2P,OAAA,SAAAnC,EAAAgB,GACA,GAAAmB,GAAArL,OAAA2B,OAAAjG,EAAA2P,OAAAzJ,UAGA,OAFAyJ,GAAAlL,KAAA+I,EAAAgB,GAEAmB,GAGA3P,EAAA2P,OAAAzJ,WAUAzB,KAAA,SAAA+I,EAAAgB,GAIA,MAHAvP,MAAAuO,EAAAA,GAAA,EACAvO,KAAAuP,EAAAA,GAAA,EAEAvP,MAUA6P,IAAA,SAAAa,EAAA/B,GACA3O,KAAAuO,IAAAmC,EAAAnC,GAAA,IAAAI,GAAA,GACA3O,KAAAuP,IAAAmB,EAAAnB,GAAA,IAAAZ,GAAA,IAaAgC,MAAA,SAAAC,EAAAC,EAAAC,EAAAC,GACA/Q,KAAAgR,MAAAJ,IAAA3R,EAAA2R,IAAA3C,EAAAA,GACAjO,KAAAiR,MAAAH,IAAA7R,EAAA6R,EAAA7C,EAAAA,EACAjO,KAAAkR,MAAAL,IAAA5R,EAAA4R,IAAA5C,EAAAA,GACAjO,KAAAmR,MAAAJ,IAAA9R,EAAA8R,EAAA9C,EAAAA,EAGAjO,KAAAoR,GAAApR,KAAAuO,EACAvO,KAAAqR,GAAArR,KAAAuP,EAGAlK,OAAAiM,iBAAAtR,MACAuO,GACAD,IAAA,WACA,MAAAtO,MAAAoR,IAEAG,IAAA,SAAAzT,GACAkC,KAAAoR,GAAAvC,EAAA2C,IAAA3C,EAAAC,IAAAhR,EAAAkC,KAAAgR,OAAAhR,KAAAiR,SAGA1B,GACAjB,IAAA,WACA,MAAAtO,MAAAqR,IAEAE,IAAA,SAAAzT,GACAkC,KAAAqR,GAAAxC,EAAA2C,IAAA3C,EAAAC,IAAAhR,EAAAkC,KAAAkR,OAAAlR,KAAAmR,aAkBApQ,EAAA0Q,OAAA,SAAAhM,GACA,GAAAgM,GAAApM,OAAA2B,OAAAjG,EAAA0Q,OAAAxK,UAGA,OAFAwK,GAAAjM,KAAAC,GAEAgM,GAGA1Q,EAAA0Q,OAAAxK,WAOAyK,cAAA,SAAA/C,GACA3O,KAAA2R,SAAA9B,IAAA7P,KAAA4R,aAAAjD,GACA3O,KAAAkQ,SAAAL,IAAA7P,KAAA2R,SAAAhD,GAEA3O,KAAA6R,cAOAC,SAAA,WACA9R,KAAA1C,QAAAyU,UAAA/R,KAAAgS,MACAhS,KAAA1C,QAAA2U,SAAAjS,KAAAkQ,SAAA3B,EAAAvO,KAAAkQ,SAAAX,EAAAvP,KAAAmG,MAAAnG,KAAAoG,SAOA8L,UAAA,WACAlS,KAAA1C,QAAA4U,UAAAlS,KAAAoD,MAAApD,KAAAkQ,SAAA3B,EAAAvO,KAAAkQ,SAAAX,IASA4C,iBAAA,SAAAxD,GACA3O,KAAA0R,cAAA/C,GAEA3O,KAAAoS,iBAAAlL,OAAAyH,IAOA0D,cAAA,WACArS,KAAAoS,iBAAAjL,QACA7J,QAAA0C,KAAA1C,QACAiR,EAAAvO,KAAAkQ,SAAA3B,EACAgB,EAAAvP,KAAAkQ,SAAAX,KAUA+C,cAAA,SAAAnP,GACAnD,KAAAoS,iBAAApS,KAAAuS,WAAApP,IASA0K,QAAA,WACA,MAAA7N,MAAA6R,WAAA,GAiCArM,KAAA,SAAAC,GACAA,EAAAA,KAEA,IAAAuC,GAAAhI,IAEAgI,GAAAkI,UAAAlI,EAAAkI,UAAAnP,EAAA2P,UAAAlL,KAAAC,EAAA8I,EAAA9I,EAAA8J,GACAvH,EAAA2J,UAAA3J,EAAA2J,UAAA5Q,EAAA2P,UAAAlL,KAAAC,EAAA+M,GAAA/M,EAAAgN,IACAzK,EAAA4J,cAAA5J,EAAA4J,cAAA7Q,EAAA2P,UAAAlL,KAAAC,EAAAiN,IAAAjN,EAAAkN,KAEA3K,EAAA6J,WAAApM,EAAAoM,YAAA,EACA7J,EAAA1K,QAAAmI,EAAAnI,SAAAyD,EAAAzD,QAGAyD,EAAAC,QAAAyE,EAAArC,QAAArC,EAAA8E,SAAAJ,EAAArC,QACA4E,EAAA5E,MAAAqC,EAAArC,MACA4E,EAAA7B,MAAAV,EAAArC,MAAA+C,MACA6B,EAAA5B,OAAAX,EAAArC,MAAAgD,OAGA4B,EAAA4K,QAAA5K,EAAA0J,cACA1J,EAAA6K,KAAA7K,EAAAkK,WAGAzM,EAAA8M,YACAvK,EAAAuK,WAAA9M,EAAA8M,WAGAvK,EAAAoK,iBAAA3M,EAAA8M,WAAAlN,OAAAC,KAAAG,EAAA8M,YAAA,IACAvK,EAAA7B,MAAA6B,EAAAoK,iBAAAjM,MACA6B,EAAA5B,OAAA4B,EAAAoK,iBAAAhM,OAGA4B,EAAA4K,QAAA5K,EAAAmK,iBACAnK,EAAA6K,KAAA7K,EAAAqK,gBAIArK,EAAAgK,MAAAvM,EAAAuM,MACAhK,EAAA7B,MAAAV,EAAAU,MACA6B,EAAA5B,OAAAX,EAAAW,OAGA4B,EAAA4K,QAAA5K,EAAA0J,cACA1J,EAAA6K,KAAA7K,EAAA8J,SAIA,KAAA,GAAAgB,KAAArN,GACAA,EAAAhI,eAAAqV,IAAA,KAAArC,EAAA9H,QAAAmK,KACA9K,EAAA8K,GAAArN,EAAAqN,KAcAC,GAAAxE,KACA,MAAAvO,MAAAkQ,SAAA3B,GASAwE,GAAAxD,KACA,MAAAvP,MAAAkQ,SAAAX,GASAwD,GAAAP,MACA,MAAAxS,MAAA2R,SAAApD,GASAwE,GAAAN,MACA,MAAAzS,MAAA2R,SAAApC,GASAwD,GAAAL,OACA,MAAA1S,MAAA4R,aAAArD,GASAwE,GAAAJ,OACA,MAAA3S,MAAA4R,aAAArC,GAGAwD,GAAAxE,GAAAzQ,GACAkC,KAAAkQ,SAAA3B,EAAAzQ,GAEAiV,GAAAxD,GAAAzR,GACAkC,KAAAkQ,SAAAX,EAAAzR,GAEAiV,GAAAP,IAAA1U,GACAkC,KAAA2R,SAAApD,EAAAzQ,GAEAiV,GAAAN,IAAA3U,GACAkC,KAAA2R,SAAApC,EAAAzR,GAEAiV,GAAAL,KAAA5U,GACAkC,KAAA4R,aAAArD,EAAAzQ,GAEAiV,GAAAJ,KAAA7U,GACAkC,KAAA4R,aAAArC,EAAAzR,GAWAkV,aAAA,SAAAvD,GAEA,GAAAlB,GAAAkB,EAAAlB,IAAAtP,EAAAwQ,EAAAlB,EAAAkB,EAAAS,SAAA3B,EACAgB,EAAAE,EAAAF,IAAAtQ,EAAAwQ,EAAAF,EAAAE,EAAAS,SAAAX,CAEA,OAAAvP,MAAAkQ,SAAA3B,EAAAA,EAAAkB,EAAAtJ,OACAnG,KAAAkQ,SAAA3B,EAAAvO,KAAAmG,MAAAoI,GACAvO,KAAAkQ,SAAAX,EAAAA,EAAAE,EAAArJ,QACApG,KAAAkQ,SAAAX,EAAAvP,KAAAoG,OAAAmJ,GACA,GAGA,GAuBArI,OAAA,SAAAyH,GACA3O,KAAA4S,QAAAjE,IAqBAxH,OAAA,WACAnH,KAAA6S,SAIA9R,GACAA,WAAA8N,MChbA9N,OAAA,SAAAA,EAAA9B,GACA,YAkTA,OA1SA8B,GAAAkS,UAAA,SAAAxN,GACA,GAAAwN,GAAA5N,OAAA2B,OAAAjG,EAAAkS,UAAAhM,UAGA,OAFAgM,GAAAzN,KAAAC,GAEAwN,GAGAlS,EAAAkS,UAAAhM,WAUAzB,KAAA,SAAAC,GACAA,EAAAA,MAEAzF,KAAAkT,YAAAzN,EAAAyN,YACAlT,KAAAmT,OAAA1N,EAAA0N,OACAnT,KAAAoT,WAAA3N,EAAA2N,WAEApT,KAAAmG,MAAAV,EAAAyN,YAAAG,MAAAlN,MACAnG,KAAAoG,OAAAX,EAAAyN,YAAAG,MAAAjN,OAEApG,KAAAsT,aAAA,EACAtT,KAAAqH,aAAA,EACArH,KAAAkH,OAAAlH,KAAA4S,QACA5S,KAAAmH,OAAAnH,KAAA6S,MAUAD,QAAA,SAAAjE,GAOA,IALAA,GAAA,EAAAA,EAAA,IAAAA,EAAAA,IAAA,EAEA3O,KAAAqH,cAAAsH,EAGA3O,KAAAqH,cAAArH,KAAAoT,YACApT,KAAAsT,eAAAtT,KAAAsT,aAAAtT,KAAAmT,OAAAvV,OAEAoC,KAAAqH,cAAArH,KAAAoT,YAcAP,KAAA,SAAApN,GACAA,EAAAA,KAEA,IAAAnI,GAAAmI,EAAAnI,SAAAyD,EAAAzD,QAGAiW,EAAAvT,KAAAmT,OAAAnT,KAAAsT,cAAAtT,KAAAkT,YAAAM,aAAA,EACAC,EAAAzT,KAAAmT,OAAAnT,KAAAsT,cAAAtT,KAAAkT,YAAAM,aAAA,CAEAlW,GAAA4U,UACAlS,KAAAkT,YAAA9P,MACAqQ,EAAAzT,KAAAkT,YAAAG,MAAAlN,MAAAoN,EAAAvT,KAAAkT,YAAAG,MAAAjN,OACApG,KAAAkT,YAAAG,MAAAlN,MAAAnG,KAAAkT,YAAAG,MAAAjN,OACAX,EAAA8I,EAAA9I,EAAA8J,EACAvP,KAAAkT,YAAAG,MAAAlN,MAAAnG,KAAAkT,YAAAG,MAAAjN,SAQAsN,KAAA,WAEA1T,KAAAkH,OAAAlH,KAAA4S,QACA5S,KAAAmH,OAAAnH,KAAA6S,MAOAhL,KAAA,WAMA7H,KAAAkH,OAAAnG,EAAAuF,KACAtG,KAAAmH,OAAApG,EAAAuF,MAOAqN,MAAA,WACA3T,KAAAkH,OAAAnG,EAAAuF,OAeAvF,EAAAmS,YAAA,SAAAzN,GACA,GAAAyN,GAAA7N,OAAA2B,OAAAjG,EAAAmS,YAAAjM,UAGA,OAFAiM,GAAA1N,KAAAC,GAEAyN,GAGAnS,EAAAmS,YAAAjM,WAYAzB,KAAA,SAAAC,GAKA,GAJAA,EAAAA,MAEAzF,KAAAuS,eAEAxR,EAAAC,QAAAyE,EAAArC,SAAArC,EAAA8E,SAAAJ,EAAArC,OASA,CACA,GAAA7C,GAAA,GAAAiN,aAAA,iBAEA,YADAzM,GAAAiF,SAAAzF,EAAA,kDAVAP,KAAAoD,MAAAqC,EAAArC,MACApD,KAAAqT,OACAlN,MAAAV,EAAAmO,WACAxN,OAAAX,EAAAoO,aAGA7T,KAAAwT,aAAA/N,EAAArC,MAAA+C,MAAAV,EAAAmO,WAAA,EAQAnO,EAAA8M,YACAvS,KAAA8T,iBAAArO,EAAA8M,aAoCAuB,iBAAA,SAAAvB,GACA,GAAAhS,EAEA,KAAAgS,GAAA,IAAAlN,OAAAC,KAAAiN,GAAA3U,OAGA,MAFA2C,GAAA,GAAAwF,gBAAA,4BACAhF,GAAAiF,SAAAzF,EAAA,wEAKA,IAAA0S,GAAAE,EAAAC,EAAAW,CACA,KAAA,GAAA5Q,KAAAoP,GACA,GAAAA,EAAA9U,eAAA0F,GAAA,CAWA,GAPA8P,EAAAV,EAAApP,GACAgQ,EAAAF,EAAAE,OACAC,EAAAH,EAAAG,WAGAW,KAEAZ,IAAAlU,EAGA,MAFAsB,GAAA,GAAAwF,gBAAA,kCACAhF,GAAAiF,SAAAzF,EAAA,aAAA4C,EAAA,mCAKA,IAAApC,EAAAwF,SAAA4M,GACAY,EAAAjU,KAAAqT,OAGA,IAAApS,EAAA2E,SAAAyN,GACAY,EAAA/T,KAAAgU,aAAAb,OAGA,IAAApS,EAAApD,QAAAwV,GACA,IAAA,GAAAE,GAAAlU,EAAA,EAAAkU,EAAAF,EAAAhU,GAAAA,IAGA4B,EAAA2E,SAAA2N,GAGAU,EAAAjU,KAAAiF,MAAAgP,EAAA/T,KAAAgU,aAAAX,IAIAU,EAAAjU,KAAAuT,EAKArT,MAAAuS,WAAApP,GAAApC,EAAAkS,WACAC,YAAAlT,KACAmT,OAAAY,EACAX,WAAAA,MAcAY,aAAA,SAAAb,GACA,GAKAhU,GALA4U,KACAE,EAAAd,EAAAjK,MAAA,MAAAgL,IAAAC,QAGAC,EAAAH,EAAA,GAAAA,EAAA,GAAA,EAAA,EAIA,IAAA,IAAAG,EACA,IAAAjV,EAAA8U,EAAA,GAAA9U,GAAA8U,EAAA,GAAA9U,IACA4U,EAAAjU,KAAAX,OAKA,KAAAA,EAAA8U,EAAA,GAAA9U,GAAA8U,EAAA,GAAA9U,IACA4U,EAAAjU,KAAAX,EAIA,OAAA4U,KAIAhT,GACAA,YC3SAA,OAAA,SAAAA,EAAAL,EAAA2T,EAAApV,GACA,YAMA,OAHA8B,GAAAS,OAAAT,EAAAS,WACAT,EAAAS,OAAA6S,aAAA,gBAAA3T,IAAA,OAAAA,EAAA2T,aAEAtT,EAAAS,OAAA6S,cAOAtT,EAAAuT,SASAvT,EAAAuT,MAAA/C,IAAA,SAAAhU,EAAAO,GACAA,IAAAmB,EACAe,KAAAuU,OAAAhX,GAGA8W,EAAAG,QAAAjX,EAAA+G,KAAAmQ,UAAA3W,KAYAiD,EAAAuT,MAAAhG,IAAA,SAAA/Q,GACA,GAAAO,GAAAuW,EAAAK,QAAAnX,EAEA,KACAO,EAAAwG,KAAAC,MAAAzG,GAEA,MAAA6B,IAEA,MAAA7B,IASAiD,EAAAuT,MAAAC,OAAA,SAAAhX,GACA8W,EAAAM,WAAApX,IAOAwD,EAAAuT,MAAA5F,MAAA,WACA2F,EAAA3F,SAGA3N,GA7DAA,GA8DAA,WAAAL,OAAAA,OAAA2T,cC/EAtT,OAAA,SAAAA,EAAA8N,EAAA5P,GACA,YAoaA,OA5ZA8B,GAAA6T,WAAA,SAAAnP,GACA,GAAAmP,GAAAvP,OAAA2B,OAAAjG,EAAA6T,WAAA3N,UAGA,OAFA2N,GAAApP,KAAAC,GAEAmP,GAGA7T,EAAA6T,WAAA3N,WAgBAzB,KAAA,SAAAC,GACAA,EAAAA,KAEA,IAAAuC,GAAAhI,IAGA,KAAAyF,EAAAU,QAAAV,EAAAW,OAAA,CACA,GAAA7F,GAAA,GAAAwF,gBAAA,gCAEA,YADAhF,GAAAiF,SAAAzF,EAAA,yEAIAyH,EAAA7B,MAAAV,EAAAU,MACA6B,EAAA5B,OAAAX,EAAAW,OAIA4B,EAAA6M,UAAApP,EAAAoP,WAAA,GACA7M,EAAA8M,WAAArP,EAAAqP,YAAA,GAEA9M,EAAA1K,QAAAmI,EAAAnI,SAAAyD,EAAAzD,QAEA0K,EAAA+M,YAAA/M,EAAA1K,QAAAqI,OAAAQ,MACA6B,EAAAgN,aAAAhN,EAAA1K,QAAAqI,OAAAS,OAIA4B,EAAAiN,iBAAA1P,SAAA2P,cAAA,UACAlN,EAAAmN,kBAAAnN,EAAAiN,iBAAAhP,WAAA,MAGA+B,EAAAiN,iBAAA9O,MAAA6B,EAAAoN,SAAApN,EAAA7B,MAAA6B,EAAA6M,UACA7M,EAAAiN,iBAAA7O,OAAA4B,EAAAqN,UAAArN,EAAA5B,OAAA4B,EAAA8M,WAKA9M,EAAAsN,MAAAtN,EAAAoN,SAAApN,EAAA+M,YACA/M,EAAAuN,MAAAvN,EAAAqN,UAAArN,EAAAgN,aAEAhN,EAAAwN,UAGAxN,EAAAyN,eAGAzN,EAAA0N,YAEA1N,EAAAuG,EAAA9I,EAAA8I,GAAA,EACAvG,EAAAuH,EAAA9J,EAAA8J,GAAA,EACAvH,EAAA2N,GAAAlQ,EAAAkQ,IAAA,EACA3N,EAAA4N,GAAAnQ,EAAAmQ,IAAA,GAWAC,WAAA,SAAApQ,GAGA,GAFAA,EAAAA,OAEA1E,EAAAC,QAAAyE,EAAArC,SAAArC,EAAA8E,SAAAJ,EAAArC,OA+BA,CACA,GAAA7C,GAAA,GAAAiN,aAAA,iBAEA,YADAzM,GAAAiF,SAAAzF,EAAA,kDAhCA,GAAA6C,GAAAqC,EAAArC,MACA0S,EAAArQ,EAAAqQ,UACAC,GAAA3S,EAAA+C,MAAAnG,KAAA6U,UAAA,IAAAzR,EAAAgD,OAAApG,KAAA8U,WAAA,EAEA,KAAAgB,EAEA,GAAA9V,KAAA0V,SAAA9X,OAAA,EAAA,CACA,GAAAoY,GAAAhW,KAAA0V,SAAA1V,KAAA0V,SAAA9X,OAAA,GACAqY,GAAAD,EAAA5S,MAAA+C,MAAAnG,KAAA6U,UAAA,IACAmB,EAAA5S,MAAAgD,OAAApG,KAAA8U,WAAA,EAEAgB,GAAAE,EAAAF,UAAAG,EAAA,MAIAH,GAAA,CAIA9V,MAAA0V,SAAA5V,MACAgW,UAAAA,EACAI,SAAAJ,EAAAC,EAAA,EACA3S,MAAAA,IAIApD,KAAA0V,SAAAS,KAAA,SAAAC,EAAAC,GACA,MAAAD,GAAAN,UAAAO,EAAAP,aAoBAQ,SAAA,SAAA7Q,GACAA,EAAAA,MACAA,EAAA0B,OAAA1B,EAAA0B,SAAAlI,GAAA,EAAAwG,EAAA0B,MAEA,IACA/F,GADA4G,EAAAhI,IAIA,IAAAe,EAAApD,QAAA8H,EAAArE,KAAA,IAAA,CACAA,IAEA,KAAA,GAAAmS,GAAAgD,EAAA,EAAAhD,EAAA9N,EAAArE,KAAAmV,GAAAA,IACA,IAAA,GAAAC,GAAA,EAAA5Y,EAAA2V,EAAA3V,OAAAA,EAAA4Y,EAAAA,IACApV,EAAAtB,KAAAyT,EAAAiD,QAKApV,GAAAqE,EAAArE,IAGApB,MAAAwV,OAAA/P,EAAAtC,MAAA/B,EACApB,KAAAwV,OAAA/P,EAAAtC,MAAAsT,OAAAhR,EAAAgR,QAAA,EACAzW,KAAAwV,OAAA/P,EAAAtC,MAAAgE,OAAA1B,EAAA0B,OAGA1B,EAAA0B,SACAnH,KAAAyV,YAAA3V,KAAA2F,EAAAtC,MAEAnD,KAAAyV,YAAAU,KAAA,SAAAC,EAAAC,GACA,MAAArO,GAAAwN,OAAAY,GAAAK,OAAAzO,EAAAwN,OAAAa,GAAAI,SAGAzW,KAAA0W,oBAiBAC,kBAAA,SAAAxT,EAAAsM,GAcA,IAAA,GADAb,GAXAL,EAAAkB,EAAAlB,IAAAtP,EAAAwQ,EAAAlB,EAAAkB,EAAAS,SAAA3B,EACAgB,EAAAE,EAAAF,IAAAtQ,EAAAwQ,EAAAF,EAAAE,EAAAS,SAAAX,EAGAgE,EAAAvT,KAAA4W,QAAArH,GACAkE,EAAAzT,KAAA6W,QAAAtI,GAEAuI,EAAA9W,KAAA4W,QAAArH,EAAAE,EAAArJ,QACA2Q,EAAA/W,KAAA6W,QAAAtI,EAAAkB,EAAAtJ,OAIAoQ,EAAAhD,EAAAuD,GAAAP,EAAAA,IACA,IAAA,GAAAC,GAAA/C,EAAAsD,GAAAP,EAAAA,IAGA,GAFA5H,EAAA4H,EAAAD,EAAAvW,KAAAmG,MAEAnG,KAAAwV,OAAArS,GAAAyL,GACA,OAAA,CAKA,QAAA,GAaAoI,YAAA,SAAA7T,EAAAoL,EAAAgB,GACA,GAAAgE,GAAAvT,KAAA4W,QAAArH,GACAkE,EAAAzT,KAAA6W,QAAAtI,GACAK,EAAA6E,EAAAF,EAAAvT,KAAAmG,KAEA,OAAAnG,MAAAwV,OAAArS,GAAAyL,IAOAzH,OAAA,WACA,GAAAa,GAAAhI,IAGAgI,GAAA2N,GAAA9G,EAAA2C,IAAA3C,EAAAC,IAAA9G,EAAA2N,GAAA,GAAA3N,EAAAsN,OACAtN,EAAA4N,GAAA/G,EAAA2C,IAAA3C,EAAAC,IAAA9G,EAAA4N,GAAA,GAAA5N,EAAAuN,OAEAvN,EAAA1K,QAAA4U,UACAlK,EAAAiN,iBACAjN,EAAA2N,GAAA3N,EAAA4N,GAAA5N,EAAA+M,YAAA/M,EAAAgN,aACAhN,EAAAuG,EAAAvG,EAAAuH,EAAAvH,EAAA+M,YAAA/M,EAAAgN,eAUAiC,YAAA,SAAA9T,GAuBA,IAtBA,GAmBAoL,GAAAgB,EAAA2H,EAAAC,EAAA/T,EAAAgU,EAAAjR,EAAAwP,EAAAC,EAnBA5N,EAAAhI,KAEAqX,EAAArP,EAAAwN,OAAArS,GAGAoQ,EAAAvL,EAAA4O,UACAnD,EAAAzL,EAAA6O,UACAjI,EAAA6E,EAAAF,EAAAvL,EAAA7B,MAGAmR,EAAA7D,EAAAzL,EAAA6M,UAAA7M,EAAA2N,GACA4B,EAAAhE,EAAAvL,EAAA8M,WAAA9M,EAAA4N,GAGA4B,EAAA3I,EAAA4I,KAAAzP,EAAA+M,YAAA/M,EAAA6M,WAAA,EACA6C,EAAA7I,EAAA4I,KAAAzP,EAAAgN,aAAAhN,EAAA8M,YAAA,EACAiB,EAAAyB,EAAAE,EAEAC,EAAA,EAIA5B,EAAA4B,GACAT,EAAAG,EAAAzI,GAEAsI,IACAC,EAAAnP,EAAA4P,YAAAV,GACA9T,EAAA+T,EAAA/T,MAEAmL,EAAA+I,EAAAK,EAAAH,EAAAxP,EAAA6M,UACAtF,EAAAgI,GAAAI,EAAAH,EAAA,GAAAxP,EAAA8M,WAEAsC,EAAAF,EAAAC,EAAArB,UACA3P,EAAA/C,EAAA+C,MAAA6B,EAAA6M,UAEAc,EAAAyB,EAAAjR,EAAA6B,EAAA6M,UACAe,GAAAwB,EAAAjR,EAAA,GAAA6B,EAAA8M,WAEA9M,EAAA1K,QAAA4U,UACA9O,EACAuS,EAAAC,EAAA5N,EAAA6M,UAAA7M,EAAA8M,WACAvG,EAAAgB,EAAAvH,EAAA6M,UAAA7M,EAAA8M,eAIA6C,EAAAH,IAAA,EACA5I,EAAA6E,KAAAF,EAAAvL,EAAA7B,MAGAyI,KAcAgI,QAAA,SAAArH,GAGA,MAFAA,GAAAA,GAAA,GAEAvP,KAAA4V,GAAArG,GAAAvP,KAAA8U,WAAA,GAYA+B,QAAA,SAAAtI,GAGA,MAFAA,GAAAA,GAAA,GAEAvO,KAAA2V,GAAApH,GAAAvO,KAAA6U,UAAA,GAYA+C,YAAA,SAAAV,GAMA,IALA,GAEAtI,GACAiJ,EAHArG,EAAA,EACA1C,EAAA9O,KAAA0V,SAAA9X,OAAA,EAIAkR,GAAA0C,GAAA,CAIA,GAHA5C,GAAA4C,EAAA1C,GAAA,EAAA,EACA+I,EAAA7X,KAAA0V,SAAA9G,GAEAsI,GAAAW,EAAA/B,WAAAoB,GAAAW,EAAA3B,SACA,MAAA2B,EAEAX,GAAAW,EACArG,EAAA5C,EAAA,EAGAE,EAAAF,EAAA,IAUA8H,gBAAA,WAKA,IAAA,GAHAQ,GAAAC,EAAA/T,EAAAmL,EAAAgB,EAAAoG,EAAAC,EAAAwB,EAAAjR,EAGAkR,EAJArP,EAAAhI,KAIAb,EAAA,EAAAkY,EAAArP,EAAAwN,OAAAxN,EAAAyN,YAAAtW,IAAAA,IACA,IAAA,GAAA4P,GAAA,EAAA+I,EAAAT,EAAAzZ,OAAAka,EAAA/I,EAAAA,IACAmI,EAAAG,EAAAtI,GAGAmI,IAIAC,EAAAnP,EAAA4P,YAAAV,GACA9T,EAAA+T,EAAA/T,MAEAmL,EAAAQ,EAAA/G,EAAA7B,MAAA6B,EAAA6M,UACAtF,GAAAR,EAAA/G,EAAA7B,MAAA,GAAA6B,EAAA8M,WAEAsC,EAAAF,EAAAC,EAAArB,UACA3P,EAAA/C,EAAA+C,MAAA6B,EAAA6M,UAEAc,EAAAyB,EAAAjR,EAAA6B,EAAA6M,UACAe,GAAAwB,EAAAjR,EAAA,GAAA6B,EAAA8M,WAEA9M,EAAAmN,kBAAAjD,UACA9O,EACAuS,EAAAC,EAAA5N,EAAA6M,UAAA7M,EAAA8M,WACAvG,EAAAgB,EAAAvH,EAAA6M,UAAA7M,EAAA8M,eAOA/T,GACAA,WAAA8N","file":"kontra.min.js","sourcesContent":["/**\n * The MIT License\n *\n * Copyright (c) 2010-2012 Google, Inc. http://angularjs.org\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\nwindow.q = qFactory(function(callback) {\n setTimeout(function() {\n callback();\n }, 0);\n}, function(e) {\n console.error('qLite: ' + e.stack);\n});\n\n/**\n * Constructs a promise manager.\n *\n * @param {function(Function)} nextTick Function for executing functions in the next turn.\n * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for\n * debugging purposes.\n * @returns {object} Promise manager.\n */\nfunction qFactory(nextTick, exceptionHandler) {\n var toString = ({}).toString;\n var isFunction = function isFunction(value){return typeof value == 'function';};\n var isArray = function isArray(value) {return toString.call(value) === '[object Array]';};\n\n function forEach(obj, iterator, context) {\n var key;\n if (obj) {\n if (isFunction(obj)) {\n for (key in obj) {\n // Need to check if hasOwnProperty exists,\n // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function\n if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {\n iterator.call(context, obj[key], key);\n }\n }\n } else if (obj.forEach && obj.forEach !== forEach) {\n obj.forEach(iterator, context);\n } else if (isArray(obj)) {\n for (key = 0; key < obj.length; key++)\n iterator.call(context, obj[key], key);\n } else {\n for (key in obj) {\n if (obj.hasOwnProperty(key)) {\n iterator.call(context, obj[key], key);\n }\n }\n }\n }\n return obj;\n }\n\n /**\n * @ngdoc method\n * @name $q#defer\n * @function\n *\n * @description\n * Creates a `Deferred` object which represents a task which will finish in the future.\n *\n * @returns {Deferred} Returns a new instance of deferred.\n */\n var defer = function() {\n var pending = [],\n value, deferred;\n\n deferred = {\n\n resolve: function(val) {\n if (pending) {\n var callbacks = pending;\n pending = undefined;\n value = ref(val);\n\n if (callbacks.length) {\n nextTick(function() {\n var callback;\n for (var i = 0, ii = callbacks.length; i < ii; i++) {\n callback = callbacks[i];\n value.then(callback[0], callback[1], callback[2]);\n }\n });\n }\n }\n },\n\n\n reject: function(reason) {\n deferred.resolve(createInternalRejectedPromise(reason));\n },\n\n\n notify: function(progress) {\n if (pending) {\n var callbacks = pending;\n\n if (pending.length) {\n nextTick(function() {\n var callback;\n for (var i = 0, ii = callbacks.length; i < ii; i++) {\n callback = callbacks[i];\n callback[2](progress);\n }\n });\n }\n }\n },\n\n\n promise: {\n then: function(callback, errback, progressback) {\n var result = defer();\n\n var wrappedCallback = function(value) {\n try {\n result.resolve((isFunction(callback) ? callback : defaultCallback)(value));\n } catch(e) {\n result.reject(e);\n exceptionHandler(e);\n }\n };\n\n var wrappedErrback = function(reason) {\n try {\n result.resolve((isFunction(errback) ? errback : defaultErrback)(reason));\n } catch(e) {\n result.reject(e);\n exceptionHandler(e);\n }\n };\n\n var wrappedProgressback = function(progress) {\n try {\n result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress));\n } catch(e) {\n exceptionHandler(e);\n }\n };\n\n if (pending) {\n pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]);\n } else {\n value.then(wrappedCallback, wrappedErrback, wrappedProgressback);\n }\n\n return result.promise;\n },\n\n \"catch\": function(callback) {\n return this.then(null, callback);\n },\n\n \"finally\": function(callback) {\n\n function makePromise(value, resolved) {\n var result = defer();\n if (resolved) {\n result.resolve(value);\n } else {\n result.reject(value);\n }\n return result.promise;\n }\n\n function handleCallback(value, isResolved) {\n var callbackOutput = null;\n try {\n callbackOutput = (callback ||defaultCallback)();\n } catch(e) {\n return makePromise(e, false);\n }\n if (callbackOutput && isFunction(callbackOutput.then)) {\n return callbackOutput.then(function() {\n return makePromise(value, isResolved);\n }, function(error) {\n return makePromise(error, false);\n });\n } else {\n return makePromise(value, isResolved);\n }\n }\n\n return this.then(function(value) {\n return handleCallback(value, true);\n }, function(error) {\n return handleCallback(error, false);\n });\n }\n }\n };\n\n return deferred;\n };\n\n\n var ref = function(value) {\n if (value && isFunction(value.then)) return value;\n return {\n then: function(callback) {\n var result = defer();\n nextTick(function() {\n result.resolve(callback(value));\n });\n return result.promise;\n }\n };\n };\n\n\n /**\n * @ngdoc method\n * @name $q#reject\n * @function\n *\n * @description\n * Creates a promise that is resolved as rejected with the specified `reason`. This api should be\n * used to forward rejection in a chain of promises. If you are dealing with the last promise in\n * a promise chain, you don't need to worry about it.\n *\n * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of\n * `reject` as the `throw` keyword in JavaScript. This also means that if you \"catch\" an error via\n * a promise error callback and you want to forward the error to the promise derived from the\n * current promise, you have to \"rethrow\" the error by returning a rejection constructed via\n * `reject`.\n *\n * ```js\n * promiseB = promiseA.then(function(result) {\n * // success: do something and resolve promiseB\n * // with the old or a new result\n * return result;\n * }, function(reason) {\n * // error: handle the error if possible and\n * // resolve promiseB with newPromiseOrValue,\n * // otherwise forward the rejection to promiseB\n * if (canHandle(reason)) {\n * // handle the error and recover\n * return newPromiseOrValue;\n * }\n * return $q.reject(reason);\n * });\n * ```\n *\n * @param {*} reason Constant, message, exception or an object representing the rejection reason.\n * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.\n */\n var reject = function(reason) {\n var result = defer();\n result.reject(reason);\n return result.promise;\n };\n\n var createInternalRejectedPromise = function(reason) {\n return {\n then: function(callback, errback) {\n var result = defer();\n nextTick(function() {\n try {\n result.resolve((isFunction(errback) ? errback : defaultErrback)(reason));\n } catch(e) {\n result.reject(e);\n exceptionHandler(e);\n }\n });\n return result.promise;\n }\n };\n };\n\n\n /**\n * @ngdoc method\n * @name $q#when\n * @function\n *\n * @description\n * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.\n * This is useful when you are dealing with an object that might or might not be a promise, or if\n * the promise comes from a source that can't be trusted.\n *\n * @param {*} value Value or a promise\n * @returns {Promise} Returns a promise of the passed value or promise\n */\n var when = function(value, callback, errback, progressback) {\n var result = defer(),\n done;\n\n var wrappedCallback = function(value) {\n try {\n return (isFunction(callback) ? callback : defaultCallback)(value);\n } catch (e) {\n exceptionHandler(e);\n return reject(e);\n }\n };\n\n var wrappedErrback = function(reason) {\n try {\n return (isFunction(errback) ? errback : defaultErrback)(reason);\n } catch (e) {\n exceptionHandler(e);\n return reject(e);\n }\n };\n\n var wrappedProgressback = function(progress) {\n try {\n return (isFunction(progressback) ? progressback : defaultCallback)(progress);\n } catch (e) {\n exceptionHandler(e);\n }\n };\n\n nextTick(function() {\n ref(value).then(function(value) {\n if (done) return;\n done = true;\n result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback));\n }, function(reason) {\n if (done) return;\n done = true;\n result.resolve(wrappedErrback(reason));\n }, function(progress) {\n if (done) return;\n result.notify(wrappedProgressback(progress));\n });\n });\n\n return result.promise;\n };\n\n\n function defaultCallback(value) {\n return value;\n }\n\n\n function defaultErrback(reason) {\n return reject(reason);\n }\n\n\n /**\n * @ngdoc method\n * @name $q#all\n * @function\n *\n * @description\n * Combines multiple promises into a single promise that is resolved when all of the input\n * promises are resolved.\n *\n * @param {Array.|Object.} promises An array or hash of promises.\n * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,\n * each value corresponding to the promise at the same index/key in the `promises` array/hash.\n * If any of the promises is resolved with a rejection, this resulting promise will be rejected\n * with the same rejection value.\n */\n function all(promises) {\n var deferred = defer(),\n counter = 0,\n results = isArray(promises) ? [] : {};\n\n forEach(promises, function(promise, key) {\n counter++;\n ref(promise).then(function(value) {\n if (results.hasOwnProperty(key)) return;\n results[key] = value;\n if (!(--counter)) deferred.resolve(results);\n }, function(reason) {\n if (results.hasOwnProperty(key)) return;\n deferred.reject(reason);\n }, function(reason) {\n if (results.hasOwnProperty(key)) return;\n deferred.notify(reason);\n });\n });\n\n if (counter === 0) {\n deferred.resolve(results);\n }\n\n return deferred.promise;\n }\n\n return {\n defer: defer,\n reject: reject,\n when: when,\n all: all\n };\n}\nvar kontra = (function(kontra) {\n var isImage = /(jpeg|jpg|gif|png)$/;\n var isAudio = /(wav|mp3|ogg|aac|m4a)$/;\n var folderSeparator = /(\\\\|\\/)/g;\n\n // all assets are stored by name as well as by URL\n kontra.images = {};\n kontra.audios = {};\n kontra.data = {};\n\n // base asset path for determining asset URLs\n kontra.assetPaths = {\n images: '',\n audios: '',\n data: '',\n };\n\n // audio playability\n // @see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/audio.js\n var audio = new Audio();\n kontra.canUse = kontra.canUse || {};\n kontra.canUse.wav = '';\n kontra.canUse.mp3 = audio.canPlayType('audio/mpeg;').replace(/^no$/,'');\n kontra.canUse.ogg = audio.canPlayType('audio/ogg; codecs=\"vorbis\"').replace(/^no$/,'');\n kontra.canUse.aac = audio.canPlayType('audio/aac;').replace(/^no$/,'');\n kontra.canUse.m4a = (audio.canPlayType('audio/x-m4a;') || kontra.canUse.aac).replace(/^no$/,'');\n\n /**\n * Get the extension of an asset.\n * @see http://jsperf.com/extract-file-extension\n * @memberOf kontra\n *\n * @param {string} url - The URL to the asset.\n *\n * @returns {string}\n */\n kontra.getAssetExtension = function getAssetExtension(url) {\n return url.substr((~-url.lastIndexOf(\".\") >>> 0) + 2);\n };\n\n /**\n * Get the type of asset based on its extension.\n * @memberOf kontra\n *\n * @param {string} url - The URL to the asset.\n *\n * @returns {string} Image, Audio, Data.\n */\n kontra.getAssetType = function getAssetType(url) {\n var extension = this.getAssetExtension(url);\n\n if (extension.match(isImage)) {\n return 'Image';\n }\n else if (extension.match(isAudio)) {\n return 'Audio';\n }\n else {\n return 'Data';\n }\n };\n\n /**\n * Get the name of an asset.\n * @memberOf kontra\n *\n * @param {string} url - The URL to the asset.\n *\n * @returns {string}\n */\n kontra.getAssetName = function getAssetName(url) {\n return url.replace(/\\.[^/.]+$/, \"\");\n };\n\n return kontra;\n})(kontra || {});\n/*jshint -W084 */\n\nvar kontra = (function(kontra, q) {\n /**\n * Load an Image, Audio, or data file.\n * @memberOf kontra\n *\n * @param {string|string[]} - Comma separated list of assets to load.\n *\n * @returns {Promise} A deferred promise.\n *\n * @example\n * kontra.loadAsset('car.png');\n * kontra.loadAsset(['explosion.mp3', 'explosion.ogg']);\n * kontra.loadAsset('bio.json');\n * kontra.loadAsset('car.png', ['explosion.mp3', 'explosion.ogg'], 'bio.json');\n */\n kontra.loadAssets = function loadAsset() {\n var deferred = q.defer();\n var promises = [];\n var numLoaded = 0;\n var numAssets = arguments.length;\n var type, name, url;\n\n if (!arguments.length) {\n deferred.resolve();\n }\n\n for (var i = 0, asset; asset = arguments[i]; i++) {\n if (!Array.isArray(asset)) {\n url = asset;\n }\n else {\n url = asset[0];\n }\n\n type = this.getAssetType(url);\n\n // create a closure for event binding\n (function(assetDeferred) {\n promises.push(assetDeferred.promise);\n\n kontra['load' + type](url).then(\n function loadAssetSuccess() {\n assetDeferred.resolve();\n deferred.notify({'loaded': ++numLoaded, 'total': numAssets});\n },\n function loadAssetError(error) {\n assetDeferred.reject(error);\n });\n })(q.defer());\n }\n\n q.all(promises).then(\n function loadAssetsSuccess() {\n deferred.resolve();\n },\n function loadAssetsError(error) {\n deferred.reject(error);\n });\n\n return deferred.promise;\n };\n\n /**\n * Load an Image file. Uses assetPaths.images to resolve URL.\n * @memberOf kontra\n *\n * @param {string} url - The URL to the Image file.\n *\n * @returns {Promise} A deferred promise. Promise resolves with the Image.\n *\n * @example\n * kontra.loadImage('car.png');\n * kontra.loadImage('autobots/truck.png');\n */\n kontra.loadImage = function(url) {\n var deferred = q.defer();\n var name = this.getAssetName(url);\n var image = new Image();\n\n url = this.assetPaths.images + url;\n\n image.onload = function loadImageOnLoad() {\n kontra.images[name] = kontra.images[url] = this;\n deferred.resolve(this);\n };\n\n image.onerror = function loadImageOnError() {\n deferred.reject('Unable to load image ' + url);\n };\n\n image.src = url;\n\n return deferred.promise;\n };\n\n /**\n * Load an Audio file. Supports loading multiple audio formats which will be resolved by\n * the browser in the order listed. Uses assetPaths.audios to resolve URL.\n * @memberOf kontra\n *\n * @param {string|string[]} url - The URL to the Audio file.\n *\n * @returns {Promise} A deferred promise. Promise resolves with the Audio.\n *\n * @example\n * kontra.loadAudio('sound_effects/laser.mp3');\n * kontra.loadAudio(['explosion.mp3', 'explosion.m4a', 'explosion.ogg']);\n *\n * There are two ways to load Audio in the web: HTML5 Audio or the Web Audio API.\n * HTML5 Audio has amazing browser support, including back to IE9\n * (http://caniuse.com/#feat=audio). However, the web Audio API isn't supported in\n * IE nor Android Browsers (http://caniuse.com/#search=Web%20Audio%20API).\n *\n * To support the most browsers we'll use HTML5 Audio. However, doing so means we'll\n * have to work around mobile device limitations as well as Audio implementation\n * limitations.\n *\n * Android browsers require playing Audio through user interaction whereas iOS 6+ can\n * play through normal JavaScript. Moreover, Android can only play one sound source at\n * a time whereas iOS 6+ can handle more than one. See this article for more details\n * (http://pupunzi.open-lab.com/2013/03/13/making-html5-audio-actually-work-on-mobile/)\n *\n * Both iOS and Android will download an Audio through JavaScript, but neither will play\n * it until user interaction. You can get around this issue by having a splash screen\n * that requires user interaction to start the game and using that event to play the audio.\n * (http://jsfiddle.net/straker/5dsm6jgt/)\n */\n kontra.loadAudio = function(url) {\n var deferred = q.defer();\n var source, name, playableSource, audio;\n\n if (!Array.isArray(url)) {\n url = [url];\n }\n\n // determine which audio format the browser can play\n for (var i = 0; source = url[i]; i++) {\n if ( this.canUse[this.getAssetExtension(source)] ) {\n playableSource = source;\n break;\n }\n }\n\n if (!playableSource) {\n deferred.reject('Browser cannot play any of the audio formats provided');\n }\n else {\n name = this.getAssetName(playableSource);\n audio = new Audio();\n\n source = this.assetPaths.audios + playableSource;\n\n audio.addEventListener('canplay', function loadAudioOnLoad() {\n kontra.audios[name] = kontra.audios[source] = this;\n deferred.resolve(this);\n });\n\n audio.onerror = function loadAudioOnError() {\n deferred.reject('Unable to load audio ' + source);\n };\n\n audio.src = source;\n audio.preload = 'auto';\n audio.load();\n }\n\n return deferred.promise;\n };\n\n\n /**\n * Load a data file (be it text or JSON). Uses assetPaths.data to resolve URL.\n * @memberOf kontra\n *\n * @param {string} url - The URL to the data file.\n *\n * @returns {Promise} A deferred promise. Resolves with the data or parsed JSON.\n *\n * @example\n * kontra.loadData('bio.json');\n * kontra.loadData('dialog.txt');\n */\n kontra.loadData = function(url) {\n var deferred = q.defer();\n var req = new XMLHttpRequest();\n var name = this.getAssetName(url);\n var dataUrl = this.assetPaths.data + url;\n\n req.addEventListener('load', function loadDataOnLoad() {\n if (req.status !== 200) {\n deferred.reject(req.responseText);\n return;\n }\n\n try {\n var json = JSON.parse(req.responseText);\n kontra.data[name] = kontra.data[dataUrl] = json;\n\n deferred.resolve(json);\n }\n catch(e) {\n var data = req.responseText;\n kontra.data[name] = kontra.data[dataUrl] = data;\n\n deferred.resolve(data);\n }\n });\n\n req.open('GET', dataUrl, true);\n req.send();\n\n return deferred.promise;\n };\n\n return kontra;\n})(kontra || {}, q);\n/*jshint -W084 */\n\nvar kontra = (function(kontra, q) {\n kontra.bundles = {};\n\n /**\n * Create a group of assets that can be loaded using kontra.loadBundle().\n * @memberOf kontra\n *\n * @param {string} bundle - The name of the bundle.\n * @param {string[]} assets - Assets to add to the bundle.\n *\n * @example\n * kontra.createBundle('myBundle', ['car.png', ['explosion.mp3', 'explosion.ogg']]);\n */\n kontra.createBundle = function createBundle(bundle, assets) {\n if (this.bundles[bundle]) {\n return;\n }\n\n this.bundles[bundle] = assets || [];\n };\n\n /**\n * Load all assets that are part of a bundle.\n * @memberOf kontra\n *\n * @param {string|string[]} - Comma separated list of bundles to load.\n *\n * @returns {Promise} A deferred promise.\n *\n * @example\n * kontra.loadBundles('myBundle');\n * kontra.loadBundles('myBundle', 'myOtherBundle');\n */\n kontra.loadBundles = function loadBundles() {\n var deferred = q.defer();\n var promises = [];\n var numLoaded = 0;\n var numAssets = 0;\n var assets;\n\n for (var i = 0, bundle; bundle = arguments[i]; i++) {\n if (!(assets = this.bundles[bundle])) {\n deferred.reject('Bundle \\'' + bundle + '\\' has not been created.');\n continue;\n }\n\n numAssets += assets.length;\n\n promises.push(this.loadAssets.apply(this, assets));\n }\n\n q.all(promises).then(\n function loadBundlesSuccess() {\n deferred.resolve();\n },\n function loadBundlesError(error) {\n deferred.reject(error);\n },\n function loadBundlesNofity() {\n deferred.notify({'loaded': ++numLoaded, 'total': numAssets});\n });\n\n return deferred.promise;\n };\n\n return kontra;\n})(kontra || {}, q);\n/*jshint -W084 */\n\nvar kontra = (function(kontra, q) {\n /**\n * Load an asset manifest file.\n * @memberOf kontra\n *\n * @param {string} url - The URL to the asset manifest file.\n *\n * @returns {Promise} A deferred promise.\n */\n kontra.loadManifest = function loadManifest(url) {\n var deferred = q.defer();\n var bundles;\n\n kontra.loadData(url).then(\n function loadManifestSuccess(manifest) {\n kontra.assetPaths.images = manifest.imagePath || '';\n kontra.assetPaths.audios = manifest.audioPath || '';\n kontra.assetPaths.data = manifest.dataPath || '';\n\n // create bundles and add assets\n for (var i = 0, bundle; bundle = manifest.bundles[i]; i++) {\n kontra.createBundle(bundle.name, bundle.assets);\n }\n\n if (!manifest.loadBundles) {\n deferred.resolve();\n return;\n }\n\n // load all bundles\n if (manifest.loadBundles === 'all') {\n bundles = Object.keys(kontra.bundles || {});\n }\n // load a single bundle\n else if (!Array.isArray(manifest.loadBundles)) {\n bundles = [manifest.loadBundles];\n }\n // load multiple bundles\n else {\n bundles = manifest.loadBundles;\n }\n\n kontra.loadBundles.apply(kontra, bundles).then(\n function loadBundlesSuccess() {\n deferred.resolve();\n },\n function loadBundlesError(error) {\n deferred.reject(error);\n },\n function loadBundlesNotify(progress) {\n deferred.notify(progress);\n });\n },\n function loadManifestError(error) {\n deferred.reject(error);\n });\n\n return deferred.promise;\n };\n\n return kontra;\n})(kontra || {}, q);","/* global console */\n\nvar kontra = (function(kontra, document) {\n 'use strict';\n\n /**\n * Initialize the canvas.\n * @memberof kontra\n *\n * @param {object} properties - Properties for the game.\n * @param {string|Canvas} properties.canvas - Main canvas ID or Element for the game.\n */\n kontra.init = function init(properties) {\n properties = properties || {};\n\n if (kontra.isString(properties.canvas)) {\n this.canvas = document.getElementById(properties.canvas);\n }\n else if (kontra.isCanvas(properties.canvas)) {\n this.canvas = properties.canvas;\n }\n else {\n this.canvas = document.getElementsByTagName('canvas')[0];\n\n if (!this.canvas) {\n var error = new ReferenceError('No canvas element found.');\n kontra.logError(error, 'You must provide a canvas element for the game.');\n return;\n }\n }\n\n this.context = this.canvas.getContext('2d');\n this.game = {\n width: this.canvas.width,\n height: this.canvas.height\n };\n };\n\n /**\n * Throw an error message to the user with readable formating.\n * @memberof kontra\n *\n * @param {Error} error - Error object.\n * @param {string} message - Error message.\n */\n kontra.logError = function logError(error, message) {\n console.error('Kontra: ' + message + '\\n\\t' + error.stack);\n };\n\n /**\n * Noop function.\n * @memberof kontra\n */\n kontra.noop = function noop() {};\n\n /**\n * Determine if a value is an Array.\n * @memberof kontra\n *\n * @param {*} value - Value to test.\n *\n * @returns {boolean}\n */\n kontra.isArray = Array.isArray;\n\n /**\n * Determine if a value is a String.\n * @memberof kontra\n *\n * @param {*} value - Value to test.\n *\n * @returns {boolean}\n */\n kontra.isString = function isString(value) {\n return typeof value === 'string';\n };\n\n /**\n * Determine if a value is a Number.\n * @memberof kontra\n *\n * @param {*} value - Value to test.\n *\n * @returns {boolean}\n */\n kontra.isNumber = function isNumber(value) {\n return typeof value === 'number';\n };\n\n /**\n * Determine if a value is an Image.\n * @memberof kontra\n *\n * @param {*} value - Value to test.\n *\n * @returns {boolean}\n */\n kontra.isImage = function isImage(value) {\n return value instanceof HTMLImageElement;\n };\n\n /**\n * Determine if a value is a Canvas.\n * @memberof kontra\n *\n * @param {*} value - Value to test.\n *\n * @returns {boolean}\n */\n kontra.isCanvas = function isCanvas(value) {\n return value instanceof HTMLCanvasElement;\n };\n\n return kontra;\n})(kontra || {}, document);","var kontra = (function(kontra, window) {\n 'use strict';\n\n /**\n * Get the current time. Uses the User Timing API if it's available or defaults to using\n * Date().getTime()\n * @memberof kontra\n *\n * @returns {number}\n */\n kontra.timestamp = (function() {\n if (window.performance && window.performance.now) {\n return function timestampPerformance() {\n return window.performance.now();\n };\n }\n else {\n return function timestampDate() {\n return new Date().getTime();\n };\n }\n })();\n\n /**\n * Game loop that updates and renders the game every frame.\n * @memberof kontra\n *\n * @see kontra.gameLoop.prototype.init for list of parameters.\n */\n kontra.gameLoop = function(properties) {\n var gameLoop = Object.create(kontra.gameLoop.prototype);\n gameLoop.init(properties);\n\n return gameLoop;\n };\n\n kontra.gameLoop.prototype = {\n /**\n * Initialize properties on the game loop.\n * @memberof kontra.gameLoop\n *\n * @param {object} properties - Configure the game loop.\n * @param {number} [properties.fps=60] - Desired frame rate.\n * @param {function} properties.update - Function called to update the game.\n * @param {function} properties.render - Function called to render the game.\n */\n init: function init(properties) {\n properties = properties || {};\n\n // check for required functions\n if (typeof properties.update !== 'function' || typeof properties.render !== 'function') {\n var error = new ReferenceError('Required functions not found');\n kontra.logError(error, 'You must provide update() and render() functions to create a game loop.');\n return;\n }\n\n this.isStopped = false;\n\n // animation variables\n this._accumulator = 0;\n this._delta = 1E3 / (properties.fps || 60);\n\n this.update = properties.update;\n this.render = properties.render;\n },\n\n /**\n * Start the game loop.\n * @memberof kontra.gameLoop\n */\n start: function start() {\n this._last = kontra.timestamp();\n this.isStopped = false;\n requestAnimationFrame(this._frame.bind(this));\n },\n\n /**\n * Stop the game loop.\n */\n stop: function stop() {\n this.isStopped = true;\n cancelAnimationFrame(this._rAF);\n },\n\n /**\n * Called every frame of the game loop.\n * @memberof kontra.gameLoop\n * @private\n */\n _frame: function frame() {\n var _this = this;\n\n _this._rAF = requestAnimationFrame(_this._frame.bind(_this));\n\n _this._now = kontra.timestamp();\n _this._dt = _this._now - _this._last;\n _this._last = _this._now;\n\n // prevent updating the game with a very large dt if the game were to lose focus\n // and then regain focus later\n if (_this._dt > 1E3) {\n return;\n }\n\n _this._accumulator += _this._dt;\n\n while (_this._accumulator >= _this._delta) {\n _this.update(_this._delta / 1E3);\n\n _this._accumulator -= _this._delta;\n }\n\n _this.render();\n }\n };\n\n return kontra;\n})(kontra || {}, window);","/*jshint -W084 */\n\nvar kontra = (function(kontra, window) {\n 'use strict';\n\n var callbacks = {};\n var pressedKeys = {};\n\n var keyMap = {\n // named keys\n 8: 'backspace',\n 9: 'tab',\n 13: 'enter',\n 16: 'shift',\n 17: 'ctrl',\n 18: 'alt',\n 20: 'capslock',\n 27: 'esc',\n 32: 'space',\n 33: 'pageup',\n 34: 'pagedown',\n 35: 'end',\n 36: 'home',\n 37: 'left',\n 38: 'up',\n 39: 'right',\n 40: 'down',\n 45: 'insert',\n 46: 'delete',\n 91: 'leftwindow',\n 92: 'rightwindow',\n 93: 'select',\n 144: 'numlock',\n 145: 'scrolllock',\n\n // special characters\n 106: '*',\n 107: '+',\n 109: '-',\n 110: '.',\n 111: '/',\n 186: ';',\n 187: '=',\n 188: ',',\n 189: '-',\n 190: '.',\n 191: '/',\n 192: '`',\n 219: '[',\n 220: '\\\\',\n 221: ']',\n 222: '\\''\n };\n\n // alpha keys\n for (var i = 0; i < 26; i++) {\n keyMap[65+i] = String.fromCharCode(65+i).toLowerCase();\n }\n // numeric keys\n for (i = 0; i < 10; i++) {\n keyMap[48+i] = ''+i;\n }\n // f keys\n for (i = 1; i < 20; i++) {\n keyMap[111+i] = 'f'+i;\n }\n // keypad\n for (i = 0; i < 10; i++) {\n keyMap[96+i] = 'numpad'+i;\n }\n\n // shift keys mapped to their non-shift equivalent\n var shiftKeys = {\n '~': '`',\n '!': '1',\n '@': '2',\n '#': '3',\n '$': '4',\n '%': '5',\n '^': '6',\n '&': '7',\n '*': '8',\n '(': '9',\n ')': '0',\n '_': '-',\n '+': '=',\n ':': ';',\n '\"': '\\'',\n '<': ',',\n '>': '.',\n '?': '/',\n '|': '\\\\',\n 'plus': '='\n };\n\n // aliases modifier keys to their actual key for keyup event\n var aliases = {\n 'leftwindow': 'meta', // mac\n 'select': 'meta' // mac\n };\n\n // modifier order for combinations\n var modifierOrder = ['meta', 'ctrl', 'alt', 'shift'];\n\n window.addEventListener('keydown', keydownEventHandler);\n window.addEventListener('keyup', keyupEventHandler);\n window.addEventListener('blur', blurEventHandler);\n\n /**\n * Object for using the keyboard.\n */\n kontra.keys = {};\n\n /**\n * Register a function to be called on a keyboard keys.\n * Please note that not all keyboard combinations can be executed due to ghosting.\n * @memberof kontra.keys\n *\n * @param {string|string[]} keys - keys combination string(s).\n *\n * @throws {SyntaxError} If callback is not a function.\n */\n kontra.keys.bind = function bindKey(keys, callback) {\n if (typeof callback !== 'function') {\n var error = new SyntaxError('Invalid function.');\n kontra.logError(error, 'You must provide a function as the second parameter.');\n return;\n }\n\n keys = (kontra.isArray(keys) ? keys : [keys]);\n\n for (var i = 0, key; key = keys[i]; i++) {\n var combination = normalizeKeys(key);\n\n callbacks[combination] = callback;\n }\n };\n\n /**\n * Remove the callback function for a key combination.\n * @memberof kontra.keys\n *\n * @param {string|string[]} keys - keys combination string.\n */\n kontra.keys.unbind = function unbindKey(keys) {\n keys = (kontra.isArray(keys) ? keys : [keys]);\n\n for (var i = 0, key; key = keys[i]; i++) {\n var combination = normalizeKeys(key);\n\n callbacks[combination] = undefined;\n }\n };\n\n /**\n * Returns whether a key is pressed.\n * @memberof kontra.keys\n *\n * @param {string} keys - Keys combination string.\n *\n * @returns {boolean}\n */\n kontra.keys.pressed = function keyPressed(keys) {\n var combination = normalizeKeys(keys);\n var pressed = true;\n\n // loop over each key in the combination and verify that it is pressed\n keys = combination.split('+');\n for (var i = 0, key; key = keys[i]; i++) {\n pressed = pressed && !!pressedKeys[key];\n }\n\n return pressed;\n };\n\n /**\n * Normalize the event keycode\n * @private\n *\n * @param {Event} e\n *\n * @returns {number}\n */\n function normalizeKeyCode(e) {\n return (typeof e.which === 'number' ? e.which : e.keyCode);\n }\n\n /**\n * Normalize keys combination order.\n * @private\n *\n * @param {string} keys - keys combination string.\n *\n * @returns {string} Normalized combination.\n *\n * @example\n * normalizeKeys('c+ctrl'); //=> 'ctrl+c'\n * normalizeKeys('shift+++meta+alt'); //=> 'meta+alt+shift+plus'\n */\n function normalizeKeys(keys) {\n var combination = [];\n\n // handle '++' combinations\n keys = keys.trim().replace('++', '+plus');\n\n // put modifiers in the correct order\n for (var i = 0, modifier; modifier = modifierOrder[i]; i++) {\n\n // check for the modifier\n if (keys.indexOf(modifier) !== -1) {\n combination.push(modifier);\n keys = keys.replace(modifier, '');\n }\n }\n\n // remove all '+'s to leave only the last key\n keys = keys.replace(/\\+/g, '').toLowerCase();\n\n // check for shift key\n if (shiftKeys[keys]) {\n combination.push('shift+'+shiftKeys[keys]);\n }\n else if (keys) {\n combination.push(keys);\n }\n\n return combination.join('+');\n }\n\n /**\n * Get the key combination from an event.\n * @private\n *\n * @param {Event} e\n *\n * @return {string} normalized combination.\n */\n function getKeyCombination(e) {\n var combination = [];\n\n // check for modifiers\n for (var i = 0, modifier; modifier = modifierOrder[i]; i++) {\n if (e[modifier+'Key']) {\n combination.push(modifier);\n }\n }\n\n var key = keyMap[normalizeKeyCode(e)];\n\n // prevent duplicate keys from being added to the combination\n // for example 'ctrl+ctrl' since ctrl is both a modifier and\n // a regular key\n if (combination.indexOf(key) === -1) {\n combination.push(key);\n }\n\n return combination.join('+');\n }\n\n /**\n * Execute a function that corresponds to a keyboard combination.\n * @private\n *\n * @param {Event} e\n */\n function keydownEventHandler(e) {\n var combination = getKeyCombination(e);\n\n // set pressed keys\n for (var i = 0, keys = combination.split('+'), key; key = keys[i]; i++) {\n pressedKeys[key] = true;\n }\n\n if (callbacks[combination]) {\n callbacks[combination](e, combination);\n e.preventDefault();\n }\n }\n\n /**\n * Set the released key to not being pressed.\n * @private\n *\n * @param {Event} e\n */\n function keyupEventHandler(e) {\n var key = keyMap[normalizeKeyCode(e)];\n pressedKeys[key] = false;\n\n if (aliases[key]) {\n pressedKeys[ aliases[key] ] = false;\n }\n }\n\n /**\n * Reset pressed keys.\n * @private\n *\n * @param {Event} e\n */\n function blurEventHandler(e) {\n pressedKeys = {};\n }\n\n return kontra;\n})(kontra || {}, window);","/*jshint -W084 */\n\nvar kontra = (function(kontra) {\n 'use strict';\n\n /**\n * Object pool. The pool will grow in size to accommodate as many objects as are needed.\n * Unused items are at the front of the pool and in use items are at the of the pool.\n * @memberof kontra\n *\n * @see kontra.pool.prototype.init for list of parameters.\n */\n kontra.pool = function(properties) {\n var pool = Object.create(kontra.pool.prototype);\n pool.init(properties);\n\n return pool;\n };\n\n kontra.pool.prototype = {\n /**\n * Initialize properties on the pool.\n * @memberof kontra.pool\n *\n * @param {object} properties - Properties of the pool.\n * @param {object} properties.create - Function that returns the object to use in the pool.\n * @param {object} properties.createProperties - Properties that will be passed to the create function.\n * @param {number} properties.maxSize - The maximum size that the pool will grow to.\n * @param {boolean} properties.fill - Fill the pool to max size instead of slowly growing.\n *\n * Objects inside the pool must implement render(), update(),\n * init(), and isAlive() functions.\n */\n init: function init(properties) {\n properties = properties || {};\n\n var error, obj;\n\n // check for the correct structure of the objects added to pools so we know that the\n // rest of the pool code will work without errors\n if (typeof properties.create !== 'function') {\n error = new SyntaxError('Required function not found.');\n kontra.logError(error, 'Parameter \\'create\\' must be a function that returns an object.');\n return;\n }\n\n // bind the create function to always use the create properties\n this.create = properties.create.bind(this, properties.createProperties || {});\n\n // ensure objects for the pool have required functions\n obj = this.create();\n\n if (!obj || typeof obj.render !== 'function' || typeof obj.update !== 'function' ||\n typeof obj.init !== 'function' || typeof obj.isAlive !== 'function') {\n error = new SyntaxError('Create object required functions not found.');\n kontra.logError(error, 'Objects to be pooled must implement render(), update(), init() and isAlive() functions.');\n return;\n }\n\n // start the pool with an object\n this.objects = [obj];\n this.size = 1;\n this.maxSize = properties.maxSize || Infinity;\n this.lastIndex = 0;\n this.inUse = 0;\n\n // fill the pool\n if (properties.fill) {\n if (properties.maxSize) {\n while (this.objects.length < this.maxSize) {\n this.objects.unshift(this.create());\n }\n\n this.size = this.maxSize;\n this.lastIndex = this.maxSize - 1;\n }\n else {\n error = new SyntaxError('Required property not found.');\n kontra.logError(error, 'Parameter \\'maxSize\\' must be set before you can fill a pool.');\n return;\n }\n }\n },\n\n /**\n * Get an object from the pool.\n * @memberof kontra.pool\n *\n * @param {object} properties - Properties to pass to object.init().\n */\n get: function get(properties) {\n properties = properties || {};\n\n var _this = this;\n\n // the pool is out of objects if the first object is in use and it can't grow\n if (_this.objects[0].isAlive()) {\n if (_this.size === _this.maxSize) {\n return;\n }\n // 'double' the size of the array by filling it with twice as many objects\n else {\n for (var x = 0; x < _this.size && _this.objects.length < _this.maxSize; x++) {\n _this.objects.unshift(_this.create());\n }\n\n _this.size = _this.objects.length;\n _this.lastIndex = _this.size - 1;\n }\n }\n\n // save off first object in pool to reassign to last object after unshift\n var obj = _this.objects[0];\n obj.init(properties);\n\n // unshift the array\n for (var i = 1; i < _this.size; i++) {\n _this.objects[i-1] = _this.objects[i];\n }\n\n _this.objects[_this.lastIndex] = obj;\n _this.inUse++;\n },\n\n /**\n * Return all objects that are alive from the pool.\n * @memberof kontra.pool\n *\n * @returns {object[]}\n */\n getAliveObjects: function getAliveObjects() {\n return this.objects.slice(this.objects.length - this.inUse);\n },\n\n /**\n * Clear the object pool.\n * @memberof kontra.pool\n */\n clear: function clear() {\n this.inUse = 0;\n this.size = 1;\n this.lastIndex = 0;\n this.objects.length = 0;\n this.objects.push(this.create());\n },\n\n /**\n * Update all alive pool objects.\n * @memberof kontra.pool\n *\n * @param {number} dt - Time since last update.\n */\n update: function update(dt) {\n var i = this.lastIndex;\n var obj;\n\n // If the user kills an object outside of the update cycle, the pool won't know of\n // the change until the next update and inUse won't be decremented. If the user then\n // gets an object when inUse is the same size as objects.length, inUse will increment\n // and this statement will evaluate to -1.\n //\n // I don't like having to go through the pool to kill an object as it forces you to\n // know which object came from which pool. Instead, we'll just prevent the index from\n // going below 0 and accept the fact that inUse may be out of sync for a frame.\n var index = Math.max(this.objects.length - this.inUse, 0);\n\n // only iterate over the objects that are alive\n while (i >= index) {\n obj = this.objects[i];\n\n obj.update(dt);\n\n // if the object is dead, move it to the front of the pool\n if (!obj.isAlive()) {\n\n // push an object from the middle of the pool to the front of the pool\n // without returning a new array through Array#splice to avoid garbage\n // collection of the old array\n // @see http://jsperf.com/object-pools-array-vs-loop\n for (var j = i; j > 0; j--) {\n this.objects[j] = this.objects[j-1];\n }\n\n this.objects[0] = obj;\n this.inUse--;\n index++;\n }\n else {\n i--;\n }\n }\n },\n\n /**\n * render all alive pool objects.\n * @memberof kontra.pool\n */\n render: function render() {\n var index = Math.max(this.objects.length - this.inUse, 0);\n\n for (var i = this.lastIndex; i >= index; i--) {\n this.objects[i].render();\n }\n }\n };\n\n return kontra;\n})(kontra || {});","/*jshint -W084 */\n\nvar kontra = (function(kontra, undefined) {\n 'use strict';\n\n /**\n * A quadtree for 2D collision checking. The quadtree acts like an object pool in that it\n * will create subnodes as objects are needed but it won't clean up the subnodes when it\n * collapses to avoid garbage collection.\n * @memberof kontra\n *\n * @see kontra.quadtree.prototype.init for list of parameters.\n *L\n * The quadrant indices are numbered as follows (following a z-order curve):\n * |\n * 0 | 1\n * ----+----\n * 2 | 3\n * |\n */\n kontra.quadtree = function(properties) {\n var quadtree = Object.create(kontra.quadtree.prototype);\n quadtree.init(properties);\n\n return quadtree;\n };\n\n kontra.quadtree.prototype = {\n /**\n * Initialize properties on the quadtree.\n * @memberof kontra.quadtree\n *\n * @param {number} [depth=0] - Current node depth.\n * @param {number} [maxDepth=3] - Maximum node depths the quadtree can have.\n * @param {number} [maxObjects=25] - Maximum number of objects a node can support before splitting.\n * @param {object} [parentNode] - The node that contains this node.\n * @param {object} [bounds] - The 2D space this node occupies.\n */\n init: function init(properties) {\n properties = properties || {};\n\n this.depth = properties.depth || 0;\n this.maxDepth = properties.maxDepth || 3;\n this.maxObjects = properties.maxObjects || 25;\n\n // since we won't clean up any subnodes, we need to keep track of which nodes are\n // currently the leaf node so we know which nodes to add objects to\n this.isBranchNode = false;\n\n this.parentNode = properties.parentNode;\n\n this.bounds = properties.bounds || {\n x: 0,\n y: 0,\n width: kontra.game.width,\n height: kontra.game.height\n };\n\n this.objects = [];\n this.subnodes = [];\n },\n\n /**\n * Clear the quadtree\n * @memberof kontra.quadtree\n */\n clear: function clear() {\n if (this.isBranchNode) {\n for (var i = 0; i < 4; i++) {\n this.subnodes[i].clear();\n }\n }\n\n this.isBranchNode = false;\n this.objects.length = 0;\n },\n\n /**\n * Find the leaf node the object belongs to and get all objects that are part of\n * that node.\n * @memberof kontra.quadtree\n *\n * @param {object} object - Object to use for finding the leaf node.\n *\n * @returns {object[]} A list of objects in the same leaf node as the object.\n */\n get: function get(object) {\n var node = this;\n var objects = [];\n var indices, index;\n\n // traverse the tree until we get to a leaf node\n while (node.subnodes.length && this.isBranchNode) {\n indices = this._getIndex(object);\n\n for (var i = 0, length = indices.length; i < length; i++) {\n index = indices[i];\n\n objects.push.apply(objects, this.subnodes[index].get(object));\n }\n\n return objects;\n }\n\n return node.objects;\n },\n\n /**\n * Add an object to the quadtree. Once the number of objects in the node exceeds\n * the maximum number of objects allowed, it will split and move all objects to their\n * corresponding subnodes.\n * @memberof kontra.quadtree\n */\n add: function add() {\n var _this = this;\n var i, object, obj, indices, index;\n\n for (var j = 0, length = arguments.length; j < length; j++) {\n object = arguments[j];\n\n // add a group of objects separately\n if (kontra.isArray(object)) {\n _this.add.apply(this, object);\n\n continue;\n }\n\n // current node has subnodes, so we need to add this object into a subnode\n if (_this.subnodes.length && _this.isBranchNode) {\n _this._addToSubnode(object);\n\n continue;\n }\n\n // this node is a leaf node so add the object to it\n _this.objects.push(object);\n\n // split the node if there are too many objects\n if (_this.objects.length > _this.maxObjects && _this.depth < _this.maxDepth) {\n _this._split();\n\n // move all objects to their corresponding subnodes\n for (i = 0; obj = _this.objects[i]; i++) {\n _this._addToSubnode(obj);\n }\n\n _this.objects.length = 0;\n }\n }\n },\n\n /**\n * Add an object to a subnode.\n * @memberof kontra.quadtree\n * @private\n *\n * @param {object} object - Object to add into a subnode\n */\n _addToSubnode: function _addToSubnode(object) {\n var indices = this._getIndex(object);\n\n // add the object to all subnodes it intersects\n for (var i = 0, length = indices.length; i < length; i++) {\n this.subnodes[ indices[i] ].add(object);\n }\n },\n\n /**\n * Determine which subnodes the object intersects with.\n * @memberof kontra.quadtree\n * @private\n *\n * @param {object} object - Object to check.\n *\n * @returns {number[]} List of all subnodes object intersects.\n */\n _getIndex: function getIndex(object) {\n var indices = [];\n\n var verticalMidpoint = this.bounds.x + this.bounds.width / 2;\n var horizontalMidpoint = this.bounds.y + this.bounds.height / 2;\n\n // handle non-kontra.sprite objects as well as kontra.sprite objects\n var x = (object.x !== undefined ? object.x : object.position.x);\n var y = (object.y !== undefined ? object.y : object.position.y);\n\n // save off quadrant checks for reuse\n var intersectsTopQuadrants = y < horizontalMidpoint && y + object.height >= this.bounds.y;\n var intersectsBottomQuadrants = y + object.height >= horizontalMidpoint && y < this.bounds.y + this.bounds.height;\n\n // object intersects with the left quadrants\n if (x < verticalMidpoint && x + object.width >= this.bounds.x) {\n if (intersectsTopQuadrants) { // top left\n indices.push(0);\n }\n\n if (intersectsBottomQuadrants) { // bottom left\n indices.push(2);\n }\n }\n\n // object intersects with the right quadrants\n if (x + object.width >= verticalMidpoint && x < this.bounds.x + this.bounds.width) { // top right\n if (intersectsTopQuadrants) {\n indices.push(1);\n }\n\n if (intersectsBottomQuadrants) { // bottom right\n indices.push(3);\n }\n }\n\n return indices;\n },\n\n /**\n * Split the node into four subnodes.\n * @memberof kontra.quadtree\n * @private\n */\n _split: function split() {\n this.isBranchNode = true;\n\n // only split if we haven't split before\n if (this.subnodes.length) {\n return;\n }\n\n var subWidth = this.bounds.width / 2 | 0;\n var subHeight = this.bounds.height / 2 | 0;\n var x = this.bounds.x;\n var y = this.bounds.y;\n\n for (var i = 0; i < 4; i++) {\n this.subnodes[i] = kontra.quadtree({\n bounds: {\n x: x + (i % 2 === 1 ? subWidth : 0), // nodes 1 and 3\n y: y + (i >= 2 ? subHeight : 0), // nodes 2 and 3\n width: subWidth,\n height: subHeight\n },\n depth: this.depth+1,\n maxDepth: this.maxDepth,\n maxObjects: this.maxObjects,\n parentNode: this\n });\n }\n },\n\n /**\n * Draw the quadtree. Useful for visual debugging.\n * @memberof kontra.quadtree\n */\n render: function() {\n // don't draw empty leaf nodes, always draw branch nodes and the first node\n if (this.objects.length || this.depth === 0 ||\n (this.parentNode && this.parentNode.isBranchNode)) {\n\n kontra.context.strokeStyle = 'red';\n kontra.context.strokeRect(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height);\n\n if (this.subnodes.length) {\n for (var i = 0; i < 4; i++) {\n this.subnodes[i].render();\n }\n }\n }\n }\n };\n\n return kontra;\n})(kontra || {});","var kontra = (function(kontra, Math, undefined) {\n 'use strict';\n\n // prevent these properties from being set at the end of kontra.sprite.init()\n var excludedProperties = [\n 'x',\n 'y',\n 'dx',\n 'dy',\n 'ddx',\n 'ddy',\n 'timeToLive',\n 'context',\n 'image',\n 'animations',\n 'color',\n 'width',\n 'height'\n ];\n\n /**\n * A vector for 2D space.\n * @memberof kontra\n *\n * @see kontra.vector.prototype.init for list of parameters.\n */\n kontra.vector = function(x, y) {\n var vector = Object.create(kontra.vector.prototype);\n vector.init(x, y);\n\n return vector;\n };\n\n kontra.vector.prototype = {\n /**\n * Initialize the vectors x and y position.\n * @memberof kontra.vector\n *\n * @param {number} x=0 - Center x coordinate.\n * @param {number} y=0 - Center y coordinate.\n *\n * @returns {vector}\n */\n init: function init(x, y) {\n this.x = x || 0;\n this.y = y || 0;\n\n return this;\n },\n\n /**\n * Add a vector to this vector.\n * @memberof kontra.vector\n *\n * @param {vector} vector - Vector to add.\n * @param {number} dt=1 - Time since last update.\n */\n add: function add(vector, dt) {\n this.x += (vector.x || 0) * (dt || 1);\n this.y += (vector.y || 0) * (dt || 1);\n },\n\n /**\n * Clamp the vector between two points that form a rectangle.\n * Please note that clamping will only work if the add function is called.\n * @memberof kontra.vector\n *\n * @param {number} [xMin=-Infinity] - Min x value.\n * @param {number} [yMin=Infinity] - Min y value.\n * @param {number} [xMax=-Infinity] - Max x value.\n * @param {number} [yMax=Infinity] - Max y value.\n */\n clamp: function clamp(xMin, yMin, xMax, yMax) {\n this._xMin = (xMin !== undefined ? xMin : -Infinity);\n this._xMax = (xMax !== undefined ? xMax : Infinity);\n this._yMin = (yMin !== undefined ? yMin : -Infinity);\n this._yMax = (yMax !== undefined ? yMax : Infinity);\n\n // rename x and y so we can use them as getters and setters\n this._x = this.x;\n this._y = this.y;\n\n // define getters to return the renamed x and y and setters to clamp their value\n Object.defineProperties(this, {\n x: {\n get: function() {\n return this._x;\n },\n set: function(value) {\n this._x = Math.min( Math.max(value, this._xMin), this._xMax );\n }\n },\n y: {\n get: function() {\n return this._y;\n },\n set: function(value) {\n this._y = Math.min( Math.max(value, this._yMin), this._yMax );\n }\n }\n });\n }\n };\n\n\n\n\n\n /**\n * A sprite with a position, velocity, and acceleration.\n * @memberof kontra\n * @requires kontra.vector\n *\n * @see kontra.sprite.prototype.init for list of parameters.\n */\n kontra.sprite = function(properties) {\n var sprite = Object.create(kontra.sprite.prototype);\n sprite.init(properties);\n\n return sprite;\n };\n\n kontra.sprite.prototype = {\n /**\n * Move the sprite by its velocity.\n * @memberof kontra.sprite\n *\n * @param {number} dt - Time since last update.\n */\n advanceSprite: function advanceSprite(dt) {\n this.velocity.add(this.acceleration, dt);\n this.position.add(this.velocity, dt);\n\n this.timeToLive--;\n },\n\n /**\n * Draw a simple rectangle. Useful for prototyping.\n * @memberof kontra.sprite\n */\n drawRect: function drawRect() {\n this.context.fillStyle = this.color;\n this.context.fillRect(this.position.x, this.position.y, this.width, this.height);\n },\n\n /**\n * Draw the sprite.\n * @memberof kontra.sprite\n */\n drawImage: function drawImage() {\n this.context.drawImage(this.image, this.position.x, this.position.y);\n },\n\n /**\n * Update the currently playing animation. Used when animations are passed to the sprite.\n * @memberof kontra.sprite\n *\n * @param {number} dt - Time since last update.\n */\n advanceAnimation: function advanceAnimation(dt) {\n this.advanceSprite(dt);\n\n this.currentAnimation.update(dt);\n },\n\n /**\n * Draw the currently playing animation. Used when animations are passed to the sprite.\n * @memberof kontra.sprite\n */\n drawAnimation: function drawAnimation() {\n this.currentAnimation.render({\n context: this.context,\n x: this.position.x,\n y: this.position.y\n });\n },\n\n /**\n * Play an animation.\n * @memberof kontra.sprite\n *\n * @param {string} name - Name of the animation to play.\n */\n playAnimation: function playAnimation(name) {\n this.currentAnimation = this.animations[name];\n },\n\n /**\n * Determine if the sprite is alive.\n * @memberof kontra.sprite\n *\n * @returns {boolean}\n */\n isAlive: function isAlive() {\n return this.timeToLive > 0;\n },\n\n /**\n * Initialize properties on the sprite.\n * @memberof kontra.sprite\n *\n * @param {object} properties - Properties of the sprite.\n * @param {number} properties.x - X coordinate of the sprite.\n * @param {number} properties.y - Y coordinate of the sprite.\n * @param {number} [properties.dx] - Change in X position.\n * @param {number} [properties.dy] - Change in Y position.\n * @param {number} [properties.ddx] - Change in X velocity.\n * @param {number} [properties.ddy] - Change in Y velocity.\n *\n * @param {number} [properties.timeToLive=0] - How may frames the sprite should be alive.\n * @param {Context} [properties.context=kontra.context] - Provide a context for the sprite to draw on.\n *\n * @param {Image|Canvas} [properties.image] - Image for the sprite.\n *\n * @param {object} [properties.animations] - Animations for the sprite instead of an image.\n *\n * @param {string} [properties.color] - If no image or animation is provided, use color to draw a rectangle for the sprite.\n * @param {number} [properties.width] - Width of the sprite for drawing a rectangle.\n * @param {number} [properties.height] - Height of the sprite for drawing a rectangle.\n *\n * @param {function} [properties.update] - Function to use to update the sprite.\n * @param {function} [properties.render] - Function to use to render the sprite.\n *\n * If you need the sprite to live forever, or just need it to stay on screen until you\n * decide when to kill it, you can set timeToLive to Infinity.\n * Just be sure to set timeToLive to 0 when you want the sprite to die.\n */\n init: function init(properties) {\n properties = properties || {};\n\n var _this = this;\n\n _this.position = (_this.position || kontra.vector()).init(properties.x, properties.y);\n _this.velocity = (_this.velocity || kontra.vector()).init(properties.dx, properties.dy);\n _this.acceleration = (_this.acceleration || kontra.vector()).init(properties.ddx, properties.ddy);\n\n _this.timeToLive = properties.timeToLive || 0;\n _this.context = properties.context || kontra.context;\n\n // image sprite\n if (kontra.isImage(properties.image) || kontra.isCanvas(properties.image)) {\n _this.image = properties.image;\n _this.width = properties.image.width;\n _this.height = properties.image.height;\n\n // change the advance and draw functions to work with images\n _this.advance = _this.advanceSprite;\n _this.draw = _this.drawImage;\n }\n // animation sprite\n else if (properties.animations) {\n _this.animations = properties.animations;\n\n // default the current animation to the first one in the list\n _this.currentAnimation = properties.animations[ Object.keys(properties.animations)[0] ];\n _this.width = _this.currentAnimation.width;\n _this.height = _this.currentAnimation.height;\n\n // change the advance and draw functions to work with animations\n _this.advance = _this.advanceAnimation;\n _this.draw = _this.drawAnimation;\n }\n // rectangle sprite\n else {\n _this.color = properties.color;\n _this.width = properties.width;\n _this.height = properties.height;\n\n // change the advance and draw functions to work with rectangles\n _this.advance = _this.advanceSprite;\n _this.draw = _this.drawRect;\n }\n\n // loop through all other properties an add them to the sprite\n for (var prop in properties) {\n if (properties.hasOwnProperty(prop) && excludedProperties.indexOf(prop) === -1) {\n _this[prop] = properties[prop];\n }\n }\n },\n\n // define getter and setter shortcut functions to make it easier to work work with the\n // position, velocity, and acceleration vectors.\n\n /**\n * Sprite position.x\n * @memberof kontra.sprite\n *\n * @property {number} x\n */\n get x() {\n return this.position.x;\n },\n\n /**\n * Sprite position.y\n * @memberof kontra.sprite\n *\n * @property {number} y\n */\n get y() {\n return this.position.y;\n },\n\n /**\n * Sprite velocity.x\n * @memberof kontra.sprite\n *\n * @property {number} dx\n */\n get dx() {\n return this.velocity.x;\n },\n\n /**\n * Sprite velocity.y\n * @memberof kontra.sprite\n *\n * @property {number} dy\n */\n get dy() {\n return this.velocity.y;\n },\n\n /**\n * Sprite acceleration.x\n * @memberof kontra.sprite\n *\n * @property {number} ddx\n */\n get ddx() {\n return this.acceleration.x;\n },\n\n /**\n * Sprite acceleration.y\n * @memberof kontra.sprite\n *\n * @property {number} ddy\n */\n get ddy() {\n return this.acceleration.y;\n },\n\n set x(value) {\n this.position.x = value;\n },\n set y(value) {\n this.position.y = value;\n },\n set dx(value) {\n this.velocity.x = value;\n },\n set dy(value) {\n this.velocity.y = value;\n },\n set ddx(value) {\n this.acceleration.x = value;\n },\n set ddy(value) {\n this.acceleration.y = value;\n },\n\n /**\n * Simple bounding box collision test.\n * @memberof kontra.sprite\n *\n * @param {object} object - Object to check collision against.\n *\n * @returns {boolean} True if the objects collide, false otherwise.\n */\n collidesWith: function collidesWith(object) {\n // handle non-kontra.sprite objects as well as kontra.sprite objects\n var x = (object.x !== undefined ? object.x : object.position.x);\n var y = (object.y !== undefined ? object.y : object.position.y);\n\n if (this.position.x < x + object.width &&\n this.position.x + this.width > x &&\n this.position.y < y + object.height &&\n this.position.y + this.height > y) {\n return true;\n }\n\n return false;\n },\n\n /**\n * Update the sprites velocity and position.\n * @memberof kontra.sprite\n * @abstract\n *\n * @param {number} dt - Time since last update.\n *\n * This function can be overridden on a per sprite basis if more functionality\n * is needed in the update step. Just call this.advance() when you need\n * the sprite to update its position.\n *\n * @example\n * sprite = kontra.sprite({\n * update: function update(dt) {\n * // do some logic\n *\n * this.advance(dt);\n * }\n * });\n */\n update: function update(dt) {\n this.advance(dt);\n },\n\n /**\n * Render the sprite.\n * @memberof kontra.sprite.\n * @abstract\n *\n * This function can be overridden on a per sprite basis if more functionality\n * is needed in the render step. Just call this.draw() when you need the\n * sprite to draw its image.\n *\n * @example\n * sprite = kontra.sprite({\n * render: function render() {\n * // do some logic\n *\n * this.draw();\n * }\n * });\n */\n render: function render() {\n this.draw();\n }\n };\n\n return kontra;\n})(kontra || {}, Math);","/*jshint -W084 */\n\nvar kontra = (function(kontra, undefined) {\n 'use strict';\n\n /**\n * Single animation from a sprite sheet.\n * @memberof kontra\n *\n * @see kontra.pool.prototype.init for list of parameters.\n */\n kontra.animation = function(properties) {\n var animation = Object.create(kontra.animation.prototype);\n animation.init(properties);\n\n return animation;\n };\n\n kontra.animation.prototype = {\n /**\n * Initialize properties on the animation.\n * @memberof kontra.animation\n *\n * @param {object} properties - Properties of the animation.\n * @param {spriteSheet} properties.spriteSheet - Sprite sheet for the animation.\n * @param {number[]} properties.frames - List of frames of the animation.\n * @param {number} properties.frameSpeed - Time to wait before transitioning the animation to the next frame.\n */\n init: function init(properties) {\n properties = properties || {};\n\n this.spriteSheet = properties.spriteSheet;\n this.frames = properties.frames;\n this.frameSpeed = properties.frameSpeed;\n\n this.width = properties.spriteSheet.frame.width;\n this.height = properties.spriteSheet.frame.height;\n\n this.currentFrame = 0;\n this._accumulator = 0;\n this.update = this.advance;\n this.render = this.draw;\n },\n\n /**\n * Update the animation. Used when the animation is not paused or stopped.\n * @memberof kontra.animation\n * @private\n *\n * @param {number} dt=1 - Time since last update.\n */\n advance: function advance(dt) {\n // normalize dt to work with milliseconds as a decimal or an integer\n dt = (dt < 1 ? dt * 1E3 : dt) || 1;\n\n this._accumulator += dt;\n\n // update to the next frame if it's time\n while (this._accumulator >= this.frameSpeed) {\n this.currentFrame = ++this.currentFrame % this.frames.length;\n\n this._accumulator -= this.frameSpeed;\n }\n },\n\n /**\n * Draw the current frame. Used when the animation is not stopped.\n * @memberof kontra.animation\n * @private\n *\n * @param {object} properties - How to draw the animation.\n * @param {number} properties.x - X position to draw.\n * @param {number} properties.y - Y position to draw.\n * @param {Context} [properties.context=kontra.context] - Provide a context for the sprite to draw on.\n */\n draw: function draw(properties) {\n properties = properties || {};\n\n var context = properties.context || kontra.context;\n\n // get the row and col of the frame\n var row = this.frames[this.currentFrame] / this.spriteSheet.framesPerRow | 0;\n var col = this.frames[this.currentFrame] % this.spriteSheet.framesPerRow | 0;\n\n context.drawImage(\n this.spriteSheet.image,\n col * this.spriteSheet.frame.width, row * this.spriteSheet.frame.height,\n this.spriteSheet.frame.width, this.spriteSheet.frame.height,\n properties.x, properties.y,\n this.spriteSheet.frame.width, this.spriteSheet.frame.height\n );\n },\n\n /**\n * Play the animation.\n * @memberof kontra.animation\n */\n play: function play() {\n // restore references to update and render functions only if overridden\n this.update = this.advance;\n this.render = this.draw;\n },\n\n /**\n * Stop the animation and prevent update and render.\n * @memberof kontra.animation\n */\n stop: function stop() {\n\n // instead of putting an if statement in both render/update functions that checks\n // a variable to determine whether to render or update, we can just reassign the\n // functions to noop and save processing time in the game loop.\n // @see http://jsperf.com/boolean-check-vs-noop\n this.update = kontra.noop;\n this.render = kontra.noop;\n },\n\n /**\n * Pause the animation and prevent update.\n * @memberof kontra.animation\n */\n pause: function pause() {\n this.update = kontra.noop;\n }\n };\n\n\n\n\n\n\n /**\n * Create a sprite sheet from an image.\n * @memberof kontra\n *\n * @see kontra.spriteSheet.prototype.init for list of parameters.\n */\n kontra.spriteSheet = function(properties) {\n var spriteSheet = Object.create(kontra.spriteSheet.prototype);\n spriteSheet.init(properties);\n\n return spriteSheet;\n };\n\n kontra.spriteSheet.prototype = {\n /**\n * Initialize properties on the spriteSheet.\n * @memberof kontra\n * @constructor\n *\n * @param {object} properties - Configure the sprite sheet.\n * @param {Image|Canvas} properties.image - Image for the sprite sheet.\n * @param {number} properties.frameWidth - Width (in px) of each frame.\n * @param {number} properties.frameHeight - Height (in px) of each frame.\n * @param {object} properties.animations - Animations to create from the sprite sheet.\n */\n init: function init(properties) {\n properties = properties || {};\n\n this.animations = {};\n\n if (kontra.isImage(properties.image) || kontra.isCanvas(properties.image)) {\n this.image = properties.image;\n this.frame = {\n width: properties.frameWidth,\n height: properties.frameHeight\n };\n\n this.framesPerRow = properties.image.width / properties.frameWidth | 0;\n }\n else {\n var error = new SyntaxError('Invalid image.');\n kontra.logError(error, 'You must provide an Image for the SpriteSheet.');\n return;\n }\n\n if (properties.animations) {\n this.createAnimations(properties.animations);\n }\n },\n\n /**\n * Create animations from the sprite sheet.\n * @memberof kontra.spriteSheet\n *\n * @param {object} animations - List of named animations to create from the Image.\n * @param {number|string|number[]|string[]} animations.animationName.frames - A single frame or list of frames for this animation.\n * @param {number} animations.animationName.frameSpeed=1 - Number of frames to wait before transitioning the animation to the next frame.\n *\n * @example\n * var sheet = kontra.spriteSheet({image: img, frameWidth: 16, frameHeight: 16});\n * sheet.createAnimations({\n * idle: {\n * frames: 1 // single frame animation\n * },\n * walk: {\n * frames: '2..6', // ascending consecutive frame animation (frames 2-6, inclusive)\n * frameSpeed: 4\n * },\n * moonWalk: {\n * frames: '6..2', // descending consecutive frame animation\n * frameSpeed: 4\n * },\n * jump: {\n * frames: [7, 12, 2], // non-consecutive frame animation\n * frameSpeed: 3\n * },\n * attack: {\n * frames: ['8..10', 13, '10..8'], // you can also mix and match, in this case frames [8,9,10,13,10,9,8]\n * frameSpeed: 2\n * }\n * });\n */\n createAnimations: function createAnimations(animations) {\n var error;\n\n if (!animations || Object.keys(animations).length === 0) {\n error = new ReferenceError('No animations found.');\n kontra.logError(error, 'You must provide at least one named animation to create an Animation.');\n return;\n }\n\n // create each animation by parsing the frames\n var animation, frames, frameSpeed, sequence;\n for (var name in animations) {\n if (!animations.hasOwnProperty(name)) {\n continue;\n }\n\n animation = animations[name];\n frames = animation.frames;\n frameSpeed = animation.frameSpeed;\n\n // array that holds the order of the animation\n sequence = [];\n\n if (frames === undefined) {\n error = new ReferenceError('No animation frames found.');\n kontra.logError(error, 'Animation ' + name + ' must provide a frames property.');\n return;\n }\n\n // single frame\n if (kontra.isNumber(frames)) {\n sequence.push(frames);\n }\n // consecutive frames\n else if (kontra.isString(frames)) {\n sequence = this._parseFrames(frames);\n }\n // non-consecutive frames\n else if (kontra.isArray(frames)) {\n for (var i = 0, frame; frame = frames[i]; i++) {\n\n // consecutive frames\n if (kontra.isString(frame)) {\n\n // add new frames to the end of the array\n sequence.push.apply(sequence, this._parseFrames(frame));\n }\n // single frame\n else {\n sequence.push(frame);\n }\n }\n }\n\n this.animations[name] = kontra.animation({\n spriteSheet: this,\n frames: sequence,\n frameSpeed: frameSpeed\n });\n }\n },\n\n /**\n * Parse a string of consecutive frames.\n * @memberof kontra.spriteSheet\n * @private\n *\n * @param {string} frames - Start and end frame.\n *\n * @returns {number[]} List of frames.\n */\n _parseFrames: function parseFrames(frames) {\n var sequence = [];\n var consecutiveFrames = frames.split('..').map(Number);\n\n // determine which direction to loop\n var direction = (consecutiveFrames[0] < consecutiveFrames[1] ? 1 : -1);\n var i;\n\n // ascending frame order\n if (direction === 1) {\n for (i = consecutiveFrames[0]; i <= consecutiveFrames[1]; i++) {\n sequence.push(i);\n }\n }\n // descending order\n else {\n for (i = consecutiveFrames[0]; i >= consecutiveFrames[1]; i--) {\n sequence.push(i);\n }\n }\n\n return sequence;\n }\n };\n\n return kontra;\n})(kontra || {});","/**\n * localStorage can be a bit of a pain to work with since it stores everything as strings:\n * localStorage.setItem('item', 1); //=> '1'\n * localStorage.setItem('item', false); //=> 'false'\n * localStorage.setItem('item', [1,2,3]); //=> '1,2,3'\n * localStorage.setItem('item', {a:'b'}); //=> '[object Object]'\n * localStorage.setItem('item', undefinedVariable); //=> 'undefined'\n *\n * @fileoverview A simple wrapper for localStorage to make it easier to work with.\n * Based on store.js {@see https://github.com/marcuswestin/store.js}\n */\nvar kontra = (function(kontra, window, localStorage, undefined) {\n 'use strict';\n\n // check if the browser can use localStorage\n kontra.canUse = kontra.canUse || {};\n kontra.canUse.localStorage = 'localStorage' in window && window.localStorage !== null;\n\n if (!kontra.canUse.localStorage) {\n return kontra;\n }\n\n /**\n * Object for using localStorage.\n */\n kontra.store = {};\n\n /**\n * Save an item to localStorage.\n * @memberof kontra.store\n *\n * @param {string} key - Name to store the item as.\n * @param {*} value - Item to store.\n */\n kontra.store.set = function setStoreItem(key, value) {\n if (value === undefined) {\n this.remove(key);\n }\n else {\n localStorage.setItem(key, JSON.stringify(value));\n }\n };\n\n /**\n * Retrieve an item from localStorage and convert it back to it's original type.\n * @memberof kontra.store\n *\n * @param {string} key - Name of the item.\n *\n * @returns {*}\n */\n kontra.store.get = function getStoreItem(key) {\n var value = localStorage.getItem(key);\n\n try {\n value = JSON.parse(value);\n }\n catch(e) {}\n\n return value;\n };\n\n /**\n * Remove an item from localStorage.\n * @memberof kontra.store\n *\n * @param {string} key - Name of the item.\n */\n kontra.store.remove = function removeStoreItem(key) {\n localStorage.removeItem(key);\n };\n\n /**\n * Clear all keys from localStorage.\n * @memberof kontra.store\n */\n kontra.store.clear = function clearStore() {\n localStorage.clear();\n };\n\n return kontra;\n})(kontra || {}, window, window.localStorage);","/*jshint -W084 */\n\nvar kontra = (function(kontra, Math, undefined) {\n 'use strict';\n\n /**\n * A tile engine for rendering tilesets. Works well with the tile engine program Tiled.\n * @memberof kontra\n *\n * @see kontra.tileEngine.prototype.init for list of parameters.\n */\n kontra.tileEngine = function(properties) {\n var tileEngine = Object.create(kontra.tileEngine.prototype);\n tileEngine.init(properties);\n\n return tileEngine;\n };\n\n kontra.tileEngine.prototype = {\n /**\n * Initialize properties on the tile engine.\n * @memberof kontra.tileEngine\n *\n * @param {object} properties - Properties of the tile engine.\n * @param {number} [properties.tileWidth=32] - Width of a tile.\n * @param {number} [properties.tileHeight=32] - Height of a tile.\n * @param {number} properties.width - Width of the map (in tiles).\n * @param {number} properties.height - Height of the map (in tiles).\n * @param {number} [properties.x=0] - X position to draw.\n * @param {number} [properties.y=0] - Y position to draw.\n * @param {number} [properties.sx=0] - X position to clip the tileset.\n * @param {number} [properties.sy=0] - Y position to clip the tileset.\n * @param {Context} [properties.context=kontra.context] - Provide a context for the tile engine to draw on.\n */\n init: function init(properties) {\n properties = properties || {};\n\n var _this = this;\n\n // size of the map (in tiles)\n if (!properties.width || !properties.height) {\n var error = new ReferenceError('Required parameters not found');\n kontra.logError(error, 'You must provide width and height of the map to create a tile engine.');\n return;\n }\n\n _this.width = properties.width;\n _this.height = properties.height;\n\n // size of the tiles. Most common tile size on opengameart.org seems to be 32x32,\n // followed by 16x16\n _this.tileWidth = properties.tileWidth || 32;\n _this.tileHeight = properties.tileHeight || 32;\n\n _this.context = properties.context || kontra.context;\n\n _this.canvasWidth = _this.context.canvas.width;\n _this.canvasHeight = _this.context.canvas.height;\n\n // create an off-screen canvas for pre-rendering the map\n // @see http://jsperf.com/render-vs-prerender\n _this._offscreenCanvas = document.createElement('canvas');\n _this._offscreenContext = _this._offscreenCanvas.getContext('2d');\n\n // make the off-screen canvas the full size of the map\n _this._offscreenCanvas.width = _this.mapWidth = _this.width * _this.tileWidth;\n _this._offscreenCanvas.height = _this.mapHeight = _this.height * _this.tileHeight;\n\n // when clipping an image, sx and sy must within the image region, otherwise\n // Firefox and Safari won't draw it.\n // @see http://stackoverflow.com/questions/19338032/canvas-indexsizeerror-index-or-size-is-negative-or-greater-than-the-allowed-a\n _this.sxMax = _this.mapWidth - _this.canvasWidth;\n _this.syMax = _this.mapHeight - _this.canvasHeight;\n\n _this.layers = {};\n\n // draw order of layers (by name)\n _this._layerOrder = [];\n\n // each tileset will hold the first and the last grid as well as the image for the tileset\n _this.tilesets = [];\n\n _this.x = properties.x || 0;\n _this.y = properties.y || 0;\n _this.sx = properties.sx || 0;\n _this.sy = properties.sy || 0;\n },\n\n /**\n * Add an tileset for the tile engine to use.\n * @memberof kontra.tileEngine\n *\n * @param {object} properties - Properties of the image to add.\n * @param {string|Image|Canvas} properties.image - Path to the image or Image object.\n * @param {number} properties.firstGrid - The first tile grid to start the image.\n */\n addTileset: function addTileset(properties) {\n properties = properties || {};\n\n if (kontra.isImage(properties.image) || kontra.isCanvas(properties.image)) {\n var image = properties.image;\n var firstGrid = properties.firstGrid;\n var numTiles = (image.width / this.tileWidth | 0) * (image.height / this.tileHeight | 0);\n\n if (!firstGrid) {\n // only calculate the first grid if the tile map has a tileset already\n if (this.tilesets.length > 0) {\n var lastTileset = this.tilesets[this.tilesets.length - 1];\n var tiles = (lastTileset.image.width / this.tileWidth | 0) *\n (lastTileset.image.height / this.tileHeight | 0);\n\n firstGrid = lastTileset.firstGrid + tiles - 1;\n }\n // otherwise this is the first tile added to the tile map\n else {\n firstGrid = 1;\n }\n }\n\n this.tilesets.push({\n firstGrid: firstGrid,\n lastGrid: firstGrid + numTiles - 1,\n image: image\n });\n\n // sort the tile map so we can perform a binary search when drawing\n this.tilesets.sort(function(a, b) {\n return a.firstGrid - b.firstGrid;\n });\n }\n else {\n var error = new SyntaxError('Invalid image.');\n kontra.logError(error, 'You must provide an Image for the tile engine.');\n return;\n }\n },\n\n /**\n * Add a layer to the tile engine.\n * @memberof kontra.tileEngine\n *\n * @param {object} properties - Properties of the layer to add.\n * @param {string} properties.name - Name of the layer.\n * @param {number[]} properties.data - Tile layer data.\n * @param {boolean} [properties.render=true] - If the layer should be drawn.\n * @param {number} properties.zIndex - Draw order for tile layer. Highest number is drawn last (i.e. on top of all other layers).\n */\n addLayer: function addLayer(properties) {\n properties = properties || {};\n properties.render = (properties.render === undefined ? true : properties.render);\n\n var _this = this;\n var data;\n\n // flatten a 2D array into a single array\n if (kontra.isArray(properties.data[0])) {\n data = [];\n\n for (var r = 0, row; row = properties.data[r]; r++) {\n for (var c = 0, length = row.length; c < length; c++) {\n data.push(row[c]);\n }\n }\n }\n else {\n data = properties.data;\n }\n\n this.layers[properties.name] = data;\n this.layers[properties.name].zIndex = properties.zIndex || 0;\n this.layers[properties.name].render = properties.render;\n\n // only add the layer to the layer order if it should be drawn\n if (properties.render) {\n this._layerOrder.push(properties.name);\n\n this._layerOrder.sort(function(a, b) {\n return _this.layers[a].zIndex - _this.layers[b].zIndex;\n });\n\n this._preRenderImage();\n }\n },\n\n /**\n * Simple bounding box collision test for layer tiles.\n * @memberof kontra.tileEngine\n *\n * @param {string} name - Name of the layer.\n * @param {object} object - Object to check collision against.\n * @param {number} object.x - X coordinate of the object.\n * @param {number} object.y - Y coordinate of the object.\n * @param {number} object.width - Width of the object.\n * @param {number} object.height - Height of the object.\n *\n * @returns {boolean} True if the object collides with a tile, false otherwise.\n */\n layerCollidesWith: function layerCollidesWith(name, object) {\n // handle non-kontra.sprite objects as well as kontra.sprite objects\n var x = (object.x !== undefined ? object.x : object.position.x);\n var y = (object.y !== undefined ? object.y : object.position.y);\n\n // calculate all tiles that the object can collide with\n var row = this._getRow(y);\n var col = this._getCol(x);\n\n var endRow = this._getRow(y + object.height);\n var endCol = this._getCol(x + object.width);\n\n // check all tiles\n var index;\n for (var r = row; r <= endRow; r++) {\n for (var c = col; c <= endCol; c++) {\n index = c + r * this.width;\n\n if (this.layers[name][index]) {\n return true;\n }\n }\n }\n\n return false;\n },\n\n /**\n * Get the tile from the specified layer at x, y.\n * @memberof kontra.tileEngine\n *\n * @param {string} name - Name of the layer.\n * @param {number} x - X coordinate of the tile.\n * @param {number} y - Y coordinate of the tile.\n *\n * @returns {number}\n */\n tileAtLayer: function tileAtLayer(name, x, y) {\n var row = this._getRow(y);\n var col = this._getCol(x);\n var index = col + row * this.width;\n\n return this.layers[name][index];\n },\n\n /**\n * Render the pre-rendered canvas.\n * @memberof kontra.tileEngine\n */\n render: function render() {\n var _this = this;\n\n // ensure sx and sy are within the image region\n _this.sx = Math.min( Math.max(_this.sx, 0), _this.sxMax );\n _this.sy = Math.min( Math.max(_this.sy, 0), _this.syMax );\n\n _this.context.drawImage(\n _this._offscreenCanvas,\n _this.sx, _this.sy, _this.canvasWidth, _this.canvasHeight,\n _this.x, _this.y, _this.canvasWidth, _this.canvasHeight\n );\n },\n\n /**\n * Render a specific layer.\n * @memberof kontra.tileEngine\n *\n * @param {string} name - Name of the layer to render.\n */\n renderLayer: function renderLayer(name) {\n var _this = this;\n\n var layer = _this.layers[name];\n\n // calculate the starting tile\n var row = _this._getRow();\n var col = _this._getCol();\n var index = col + row * _this.width;\n\n // calculate where to start drawing the tile relative to the drawing canvas\n var startX = col * _this.tileWidth - _this.sx;\n var startY = row * _this.tileHeight - _this.sy;\n\n // calculate how many tiles the drawing canvas can hold\n var viewWidth = Math.ceil(_this.canvasWidth / _this.tileWidth) + 1;\n var viewHeight = Math.ceil(_this.canvasHeight / _this.tileHeight) + 1;\n var numTiles = viewWidth * viewHeight;\n\n var count = 0;\n var x, y, tile, tileset, image, tileOffset, width, sx, sy;\n\n // draw just enough of the layer to fit inside the drawing canvas\n while (count < numTiles) {\n tile = layer[index];\n\n if (tile) {\n tileset = _this._getTileset(tile);\n image = tileset.image;\n\n x = startX + (count % viewWidth) * _this.tileWidth;\n y = startY + (count / viewWidth | 0) * _this.tileHeight;\n\n tileOffset = tile - tileset.firstGrid;\n width = image.width / _this.tileWidth;\n\n sx = (tileOffset % width) * _this.tileWidth;\n sy = (tileOffset / width | 0) * _this.tileHeight;\n\n _this.context.drawImage(\n image,\n sx, sy, _this.tileWidth, _this.tileHeight,\n x, y, _this.tileWidth, _this.tileHeight\n );\n }\n\n if (++count % viewWidth === 0) {\n index = col + (++row * _this.width);\n }\n else {\n index++;\n }\n }\n },\n\n /**\n * Get the row from the y coordinate.\n * @memberof kontra.tileEngine\n * @private\n *\n * @param {number} y - Y coordinate.\n *\n * @return {number}\n */\n _getRow: function getRow(y) {\n y = y || 0;\n\n return (this.sy + y) / this.tileHeight | 0;\n },\n\n /**\n * Get the col from the x coordinate.\n * @memberof kontra.tileEngine\n * @private\n *\n * @param {number} x - X coordinate.\n *\n * @return {number}\n */\n _getCol: function getCol(x) {\n x = x || 0;\n\n return (this.sx + x) / this.tileWidth | 0;\n },\n\n /**\n * Modified binary search that will return the tileset associated with the tile\n * @memberof kontra.tileEngine\n * @private\n *\n * @param {number} tile - Tile grid.\n *\n * @return {object}\n */\n _getTileset: function getTileset(tile) {\n var min = 0;\n var max = this.tilesets.length - 1;\n var index;\n var currTile;\n\n while (min <= max) {\n index = (min + max) / 2 | 0;\n currTile = this.tilesets[index];\n\n if (tile >= currTile.firstGrid && tile <= currTile.lastGrid) {\n return currTile;\n }\n else if (currTile < tile) {\n min = index + 1;\n }\n else {\n max = index - 1;\n }\n }\n },\n\n /**\n * Pre-render the tiles to make drawing fast.\n * @memberof kontra.tileEngine\n * @private\n */\n _preRenderImage: function preRenderImage() {\n var _this = this;\n var tile, tileset, image, x, y, sx, sy, tileOffset, width;\n\n // draw each layer in order\n for (var i = 0, layer; layer = _this.layers[_this._layerOrder[i]]; i++) {\n for (var j = 0, len = layer.length; j < len; j++) {\n tile = layer[j];\n\n // skip empty tiles (0)\n if (!tile) {\n continue;\n }\n\n tileset = _this._getTileset(tile);\n image = tileset.image;\n\n x = (j % _this.width) * _this.tileWidth;\n y = (j / _this.width | 0) * _this.tileHeight;\n\n tileOffset = tile - tileset.firstGrid;\n width = image.width / _this.tileWidth;\n\n sx = (tileOffset % width) * _this.tileWidth;\n sy = (tileOffset / width | 0) * _this.tileHeight;\n\n _this._offscreenContext.drawImage(\n image,\n sx, sy, _this.tileWidth, _this.tileHeight,\n x, y, _this.tileWidth, _this.tileHeight\n );\n }\n }\n }\n };\n\n return kontra;\n})(kontra || {}, Math);"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/src/gameLoop.js b/src/gameLoop.js index e2128a12..57cf3459 100644 --- a/src/gameLoop.js +++ b/src/gameLoop.js @@ -39,7 +39,7 @@ var kontra = (function(kontra, window) { * Initialize properties on the game loop. * @memberof kontra.gameLoop * - * @param {object} properties - Configure the game loop. + * @param {object} properties - Properties of the game loop. * @param {number} [properties.fps=60] - Desired frame rate. * @param {function} properties.update - Function called to update the game. * @param {function} properties.render - Function called to render the game. @@ -59,6 +59,7 @@ var kontra = (function(kontra, window) { // animation variables this._accumulator = 0; this._delta = 1E3 / (properties.fps || 60); + this._step = 1 / (properties.fps || 60); this.update = properties.update; this.render = properties.render; @@ -105,7 +106,7 @@ var kontra = (function(kontra, window) { _this._accumulator += _this._dt; while (_this._accumulator >= _this._delta) { - _this.update(_this._delta / 1E3); + _this.update(_this._step); _this._accumulator -= _this._delta; } diff --git a/src/keyboard.js b/src/keyboard.js index 02b5508c..7f88197a 100644 --- a/src/keyboard.js +++ b/src/keyboard.js @@ -1,5 +1,3 @@ -/*jshint -W084 */ - var kontra = (function(kontra, window) { 'use strict'; diff --git a/src/pool.js b/src/pool.js index bd5435f1..96d4f417 100644 --- a/src/pool.js +++ b/src/pool.js @@ -1,5 +1,3 @@ -/*jshint -W084 */ - var kontra = (function(kontra) { 'use strict'; diff --git a/src/quadtree.js b/src/quadtree.js index 3a46a1bf..f40131f8 100644 --- a/src/quadtree.js +++ b/src/quadtree.js @@ -1,5 +1,3 @@ -/*jshint -W084 */ - var kontra = (function(kontra, undefined) { 'use strict'; @@ -30,11 +28,12 @@ var kontra = (function(kontra, undefined) { * Initialize properties on the quadtree. * @memberof kontra.quadtree * - * @param {number} [depth=0] - Current node depth. - * @param {number} [maxDepth=3] - Maximum node depths the quadtree can have. - * @param {number} [maxObjects=25] - Maximum number of objects a node can support before splitting. - * @param {object} [parentNode] - The node that contains this node. - * @param {object} [bounds] - The 2D space this node occupies. + * @param {object} properties - Properties of the quadtree. + * @param {number} [properties.depth=0] - Current node depth. + * @param {number} [properties.maxDepth=3] - Maximum node depths the quadtree can have. + * @param {number} [properties.maxObjects=25] - Maximum number of objects a node can support before splitting. + * @param {object} [properties.parentNode] - The node that contains this node. + * @param {object} [properties.bounds] - The 2D space this node occupies. */ init: function init(properties) { properties = properties || {}; @@ -110,6 +109,12 @@ var kontra = (function(kontra, undefined) { * the maximum number of objects allowed, it will split and move all objects to their * corresponding subnodes. * @memberof kontra.quadtree + * + * @param {...object|object[]} Objects to add to the quadtree + * + * @example + * kontra.quadtree().add({id:1}, {id:2}, {id:3}); + * kontra.quadtree().add([{id:1}, {id:2}], {id:3}); */ add: function add() { var _this = this; @@ -180,16 +185,12 @@ var kontra = (function(kontra, undefined) { var verticalMidpoint = this.bounds.x + this.bounds.width / 2; var horizontalMidpoint = this.bounds.y + this.bounds.height / 2; - // handle non-kontra.sprite objects as well as kontra.sprite objects - var x = (object.x !== undefined ? object.x : object.position.x); - var y = (object.y !== undefined ? object.y : object.position.y); - // save off quadrant checks for reuse - var intersectsTopQuadrants = y < horizontalMidpoint && y + object.height >= this.bounds.y; - var intersectsBottomQuadrants = y + object.height >= horizontalMidpoint && y < this.bounds.y + this.bounds.height; + var intersectsTopQuadrants = object.y < horizontalMidpoint && object.y + object.height >= this.bounds.y; + var intersectsBottomQuadrants = object.y + object.height >= horizontalMidpoint && object.y < this.bounds.y + this.bounds.height; // object intersects with the left quadrants - if (x < verticalMidpoint && x + object.width >= this.bounds.x) { + if (object.x < verticalMidpoint && object.x + object.width >= this.bounds.x) { if (intersectsTopQuadrants) { // top left indices.push(0); } @@ -200,7 +201,7 @@ var kontra = (function(kontra, undefined) { } // object intersects with the right quadrants - if (x + object.width >= verticalMidpoint && x < this.bounds.x + this.bounds.width) { // top right + if (object.x + object.width >= verticalMidpoint && object.x < this.bounds.x + this.bounds.width) { // top right if (intersectsTopQuadrants) { indices.push(1); } @@ -228,14 +229,12 @@ var kontra = (function(kontra, undefined) { var subWidth = this.bounds.width / 2 | 0; var subHeight = this.bounds.height / 2 | 0; - var x = this.bounds.x; - var y = this.bounds.y; for (var i = 0; i < 4; i++) { this.subnodes[i] = kontra.quadtree({ bounds: { - x: x + (i % 2 === 1 ? subWidth : 0), // nodes 1 and 3 - y: y + (i >= 2 ? subHeight : 0), // nodes 2 and 3 + x: this.bounds.x + (i % 2 === 1 ? subWidth : 0), // nodes 1 and 3 + y: this.bounds.y + (i >= 2 ? subHeight : 0), // nodes 2 and 3 width: subWidth, height: subHeight }, @@ -252,6 +251,7 @@ var kontra = (function(kontra, undefined) { * @memberof kontra.quadtree */ render: function() { + /* istanbul ignore next */ // don't draw empty leaf nodes, always draw branch nodes and the first node if (this.objects.length || this.depth === 0 || (this.parentNode && this.parentNode.isBranchNode)) { diff --git a/src/sprite.js b/src/sprite.js index 784b36a9..038cb187 100644 --- a/src/sprite.js +++ b/src/sprite.js @@ -24,9 +24,9 @@ var kontra = (function(kontra, Math, undefined) { * * @see kontra.vector.prototype.init for list of parameters. */ - kontra.vector = function(x, y) { + kontra.vector = function(properties) { var vector = Object.create(kontra.vector.prototype); - vector.init(x, y); + vector.init(properties); return vector; }; @@ -36,14 +36,17 @@ var kontra = (function(kontra, Math, undefined) { * Initialize the vectors x and y position. * @memberof kontra.vector * - * @param {number} x=0 - Center x coordinate. - * @param {number} y=0 - Center y coordinate. + * @param {object} properties - Properties of the vector. + * @param {number} properties.x=0 - X coordinate. + * @param {number} properties.y=0 - Y coordinate. * * @returns {vector} */ - init: function init(x, y) { - this.x = x || 0; - this.y = y || 0; + init: function init(properties) { + properties = properties || {}; + + this.x = properties.x || 0; + this.y = properties.y || 0; return this; }, @@ -121,80 +124,6 @@ var kontra = (function(kontra, Math, undefined) { }; kontra.sprite.prototype = { - /** - * Move the sprite by its velocity. - * @memberof kontra.sprite - * - * @param {number} dt - Time since last update. - */ - advanceSprite: function advanceSprite(dt) { - this.velocity.add(this.acceleration, dt); - this.position.add(this.velocity, dt); - - this.timeToLive--; - }, - - /** - * Draw a simple rectangle. Useful for prototyping. - * @memberof kontra.sprite - */ - drawRect: function drawRect() { - this.context.fillStyle = this.color; - this.context.fillRect(this.position.x, this.position.y, this.width, this.height); - }, - - /** - * Draw the sprite. - * @memberof kontra.sprite - */ - drawImage: function drawImage() { - this.context.drawImage(this.image, this.position.x, this.position.y); - }, - - /** - * Update the currently playing animation. Used when animations are passed to the sprite. - * @memberof kontra.sprite - * - * @param {number} dt - Time since last update. - */ - advanceAnimation: function advanceAnimation(dt) { - this.advanceSprite(dt); - - this.currentAnimation.update(dt); - }, - - /** - * Draw the currently playing animation. Used when animations are passed to the sprite. - * @memberof kontra.sprite - */ - drawAnimation: function drawAnimation() { - this.currentAnimation.render({ - context: this.context, - x: this.position.x, - y: this.position.y - }); - }, - - /** - * Play an animation. - * @memberof kontra.sprite - * - * @param {string} name - Name of the animation to play. - */ - playAnimation: function playAnimation(name) { - this.currentAnimation = this.animations[name]; - }, - - /** - * Determine if the sprite is alive. - * @memberof kontra.sprite - * - * @returns {boolean} - */ - isAlive: function isAlive() { - return this.timeToLive > 0; - }, - /** * Initialize properties on the sprite. * @memberof kontra.sprite @@ -230,9 +159,18 @@ var kontra = (function(kontra, Math, undefined) { var _this = this; - _this.position = (_this.position || kontra.vector()).init(properties.x, properties.y); - _this.velocity = (_this.velocity || kontra.vector()).init(properties.dx, properties.dy); - _this.acceleration = (_this.acceleration || kontra.vector()).init(properties.ddx, properties.ddy); + _this.position = (_this.position || kontra.vector()).init({ + x: properties.x, + y: properties.y + }); + _this.velocity = (_this.velocity || kontra.vector()).init({ + x: properties.dx, + y: properties.dy + }); + _this.acceleration = (_this.acceleration || kontra.vector()).init({ + x: properties.ddx, + y: properties.ddy + }); _this.timeToLive = properties.timeToLive || 0; _this.context = properties.context || kontra.context; @@ -244,8 +182,8 @@ var kontra = (function(kontra, Math, undefined) { _this.height = properties.image.height; // change the advance and draw functions to work with images - _this.advance = _this.advanceSprite; - _this.draw = _this.drawImage; + _this.advance = _this._advanceSprite; + _this.draw = _this._drawImage; } // animation sprite else if (properties.animations) { @@ -257,8 +195,8 @@ var kontra = (function(kontra, Math, undefined) { _this.height = _this.currentAnimation.height; // change the advance and draw functions to work with animations - _this.advance = _this.advanceAnimation; - _this.draw = _this.drawAnimation; + _this.advance = _this._advanceAnimation; + _this.draw = _this._drawAnimation; } // rectangle sprite else { @@ -267,8 +205,8 @@ var kontra = (function(kontra, Math, undefined) { _this.height = properties.height; // change the advance and draw functions to work with rectangles - _this.advance = _this.advanceSprite; - _this.draw = _this.drawRect; + _this.advance = _this._advanceSprite; + _this.draw = _this._drawRect; } // loop through all other properties an add them to the sprite @@ -361,6 +299,16 @@ var kontra = (function(kontra, Math, undefined) { this.acceleration.y = value; }, + /** + * Determine if the sprite is alive. + * @memberof kontra.sprite + * + * @returns {boolean} + */ + isAlive: function isAlive() { + return this.timeToLive > 0; + }, + /** * Simple bounding box collision test. * @memberof kontra.sprite @@ -370,14 +318,10 @@ var kontra = (function(kontra, Math, undefined) { * @returns {boolean} True if the objects collide, false otherwise. */ collidesWith: function collidesWith(object) { - // handle non-kontra.sprite objects as well as kontra.sprite objects - var x = (object.x !== undefined ? object.x : object.position.x); - var y = (object.y !== undefined ? object.y : object.position.y); - - if (this.position.x < x + object.width && - this.position.x + this.width > x && - this.position.y < y + object.height && - this.position.y + this.height > y) { + if (this.x < object.x + object.width && + this.x + this.width > object.x && + this.y < object.y + object.height && + this.y + this.height > object.y) { return true; } @@ -428,7 +372,76 @@ var kontra = (function(kontra, Math, undefined) { */ render: function render() { this.draw(); - } + }, + + /** + * Play an animation. + * @memberof kontra.sprite + * + * @param {string} name - Name of the animation to play. + */ + playAnimation: function playAnimation(name) { + this.currentAnimation = this.animations[name]; + }, + + /** + * Move the sprite by its velocity. + * @memberof kontra.sprite + * @private + * + * @param {number} dt - Time since last update. + */ + _advanceSprite: function advanceSprite(dt) { + this.velocity.add(this.acceleration, dt); + this.position.add(this.velocity, dt); + + this.timeToLive--; + }, + + /** + * Update the currently playing animation. Used when animations are passed to the sprite. + * @memberof kontra.sprite + * @private + * + * @param {number} dt - Time since last update. + */ + _advanceAnimation: function advanceAnimation(dt) { + this._advanceSprite(dt); + + this.currentAnimation.update(dt); + }, + + /** + * Draw a simple rectangle. Useful for prototyping. + * @memberof kontra.sprite + * @private + */ + _drawRect: function drawRect() { + this.context.fillStyle = this.color; + this.context.fillRect(this.x, this.y, this.width, this.height); + }, + + /** + * Draw the sprite. + * @memberof kontra.sprite + * @private + */ + _drawImage: function drawImage() { + this.context.drawImage(this.image, this.x, this.y); + }, + + /** + * Draw the currently playing animation. Used when animations are passed to the sprite. + * @memberof kontra.sprite + * @private + */ + _drawAnimation: function drawAnimation() { + this.currentAnimation.render({ + context: this.context, + x: this.x, + y: this.y + }); + }, }; return kontra; diff --git a/src/spriteSheet.js b/src/spriteSheet.js index 615cdd3c..ad188708 100644 --- a/src/spriteSheet.js +++ b/src/spriteSheet.js @@ -1,5 +1,3 @@ -/*jshint -W084 */ - var kontra = (function(kontra, undefined) { 'use strict'; @@ -24,22 +22,56 @@ var kontra = (function(kontra, undefined) { * @param {object} properties - Properties of the animation. * @param {spriteSheet} properties.spriteSheet - Sprite sheet for the animation. * @param {number[]} properties.frames - List of frames of the animation. - * @param {number} properties.frameSpeed - Time to wait before transitioning the animation to the next frame. + * @param {number} properties.frameRate - Number of frames to display in one second. */ init: function init(properties) { properties = properties || {}; this.spriteSheet = properties.spriteSheet; this.frames = properties.frames; - this.frameSpeed = properties.frameSpeed; + this.frameRate = properties.frameRate; this.width = properties.spriteSheet.frame.width; this.height = properties.spriteSheet.frame.height; this.currentFrame = 0; this._accumulator = 0; - this.update = this.advance; - this.render = this.draw; + + // set update and render so we can noop them later to stop the animation + this.update = this._advance; + this.render = this._draw; + }, + + /** + * Play the animation. + * @memberof kontra.animation + */ + play: function play() { + // restore references to update and render functions only if overridden + this.update = this._advance; + this.render = this._draw; + }, + + /** + * Stop the animation and prevent update and render. + * @memberof kontra.animation + */ + stop: function stop() { + + // instead of putting an if statement in both render/update functions that checks + // a variable to determine whether to render or update, we can just reassign the + // functions to noop and save processing time in the game loop. + // @see http://jsperf.com/boolean-check-vs-noop + this.update = kontra.noop; + this.render = kontra.noop; + }, + + /** + * Pause the animation and prevent update. + * @memberof kontra.animation + */ + pause: function pause() { + this.update = kontra.noop; }, /** @@ -47,19 +79,18 @@ var kontra = (function(kontra, undefined) { * @memberof kontra.animation * @private * - * @param {number} dt=1 - Time since last update. + * @param {number} [dt=1/60] - Time since last update. */ - advance: function advance(dt) { - // normalize dt to work with milliseconds as a decimal or an integer - dt = (dt < 1 ? dt * 1E3 : dt) || 1; + _advance: function advance(dt) { + dt = dt || 1 / 60; this._accumulator += dt; // update to the next frame if it's time - while (this._accumulator >= this.frameSpeed) { + while (this._accumulator * this.frameRate >= 1) { this.currentFrame = ++this.currentFrame % this.frames.length; - this._accumulator -= this.frameSpeed; + this._accumulator -= 1 / this.frameRate; } }, @@ -73,7 +104,7 @@ var kontra = (function(kontra, undefined) { * @param {number} properties.y - Y position to draw. * @param {Context} [properties.context=kontra.context] - Provide a context for the sprite to draw on. */ - draw: function draw(properties) { + _draw: function draw(properties) { properties = properties || {}; var context = properties.context || kontra.context; @@ -89,38 +120,6 @@ var kontra = (function(kontra, undefined) { properties.x, properties.y, this.spriteSheet.frame.width, this.spriteSheet.frame.height ); - }, - - /** - * Play the animation. - * @memberof kontra.animation - */ - play: function play() { - // restore references to update and render functions only if overridden - this.update = this.advance; - this.render = this.draw; - }, - - /** - * Stop the animation and prevent update and render. - * @memberof kontra.animation - */ - stop: function stop() { - - // instead of putting an if statement in both render/update functions that checks - // a variable to determine whether to render or update, we can just reassign the - // functions to noop and save processing time in the game loop. - // @see http://jsperf.com/boolean-check-vs-noop - this.update = kontra.noop; - this.render = kontra.noop; - }, - - /** - * Pause the animation and prevent update. - * @memberof kontra.animation - */ - pause: function pause() { - this.update = kontra.noop; } }; @@ -146,9 +145,8 @@ var kontra = (function(kontra, undefined) { /** * Initialize properties on the spriteSheet. * @memberof kontra - * @constructor * - * @param {object} properties - Configure the sprite sheet. + * @param {object} properties - Properties of the sprite sheet. * @param {Image|Canvas} properties.image - Image for the sprite sheet. * @param {number} properties.frameWidth - Width (in px) of each frame. * @param {number} properties.frameHeight - Height (in px) of each frame. @@ -185,7 +183,7 @@ var kontra = (function(kontra, undefined) { * * @param {object} animations - List of named animations to create from the Image. * @param {number|string|number[]|string[]} animations.animationName.frames - A single frame or list of frames for this animation. - * @param {number} animations.animationName.frameSpeed=1 - Number of frames to wait before transitioning the animation to the next frame. + * @param {number} animations.animationName.frameRate - Number of frames to display in one second. * * @example * var sheet = kontra.spriteSheet({image: img, frameWidth: 16, frameHeight: 16}); @@ -195,19 +193,19 @@ var kontra = (function(kontra, undefined) { * }, * walk: { * frames: '2..6', // ascending consecutive frame animation (frames 2-6, inclusive) - * frameSpeed: 4 + * frameRate: 4 * }, * moonWalk: { * frames: '6..2', // descending consecutive frame animation - * frameSpeed: 4 + * frameRate: 4 * }, * jump: { * frames: [7, 12, 2], // non-consecutive frame animation - * frameSpeed: 3 + * frameRate: 3 * }, * attack: { * frames: ['8..10', 13, '10..8'], // you can also mix and match, in this case frames [8,9,10,13,10,9,8] - * frameSpeed: 2 + * frameRate: 2 * } * }); */ @@ -221,7 +219,7 @@ var kontra = (function(kontra, undefined) { } // create each animation by parsing the frames - var animation, frames, frameSpeed, sequence; + var animation, frames, frameRate, sequence; for (var name in animations) { if (!animations.hasOwnProperty(name)) { continue; @@ -229,7 +227,7 @@ var kontra = (function(kontra, undefined) { animation = animations[name]; frames = animation.frames; - frameSpeed = animation.frameSpeed; + frameRate = animation.frameRate; // array that holds the order of the animation sequence = []; @@ -264,11 +262,16 @@ var kontra = (function(kontra, undefined) { } } } + else { + error = new SyntaxError('Improper frames value'); + kontra.logError(error, 'The frames property must be a number, string, or array.'); + return; + } this.animations[name] = kontra.animation({ spriteSheet: this, frames: sequence, - frameSpeed: frameSpeed + frameRate: frameRate }); } }, diff --git a/src/tileEngine.js b/src/tileEngine.js index 5225c6f3..2c0c3cbb 100644 --- a/src/tileEngine.js +++ b/src/tileEngine.js @@ -1,5 +1,3 @@ -/*jshint -W084 */ - var kontra = (function(kontra, Math, undefined) { 'use strict'; @@ -91,7 +89,7 @@ var kontra = (function(kontra, Math, undefined) { * @memberof kontra.tileEngine * * @param {object} properties - Properties of the image to add. - * @param {string|Image|Canvas} properties.image - Path to the image or Image object. + * @param {Image|Canvas} properties.image - Path to the image or Image object. * @param {number} properties.firstGrid - The first tile grid to start the image. */ addTileset: function addTileset(properties) { @@ -109,7 +107,7 @@ var kontra = (function(kontra, Math, undefined) { var tiles = (lastTileset.image.width / this.tileWidth | 0) * (lastTileset.image.height / this.tileHeight | 0); - firstGrid = lastTileset.firstGrid + tiles - 1; + firstGrid = lastTileset.firstGrid + tiles; } // otherwise this is the first tile added to the tile map else { @@ -143,7 +141,7 @@ var kontra = (function(kontra, Math, undefined) { * @param {string} properties.name - Name of the layer. * @param {number[]} properties.data - Tile layer data. * @param {boolean} [properties.render=true] - If the layer should be drawn. - * @param {number} properties.zIndex - Draw order for tile layer. Highest number is drawn last (i.e. on top of all other layers). + * @param {number} [properties.zIndex] - Draw order for tile layer. Highest number is drawn last (i.e. on top of all other layers). */ addLayer: function addLayer(properties) { properties = properties || {}; @@ -196,16 +194,12 @@ var kontra = (function(kontra, Math, undefined) { * @returns {boolean} True if the object collides with a tile, false otherwise. */ layerCollidesWith: function layerCollidesWith(name, object) { - // handle non-kontra.sprite objects as well as kontra.sprite objects - var x = (object.x !== undefined ? object.x : object.position.x); - var y = (object.y !== undefined ? object.y : object.position.y); - // calculate all tiles that the object can collide with - var row = this._getRow(y); - var col = this._getCol(x); + var row = this._getRow(object.y); + var col = this._getCol(object.x); - var endRow = this._getRow(y + object.height); - var endCol = this._getCol(x + object.width); + var endRow = this._getRow(object.y + object.height); + var endCol = this._getCol(object.x + object.width); // check all tiles var index; diff --git a/test/core.spec.js b/test/core.spec.js index bace4b31..0bb2176b 100644 --- a/test/core.spec.js +++ b/test/core.spec.js @@ -1,13 +1,25 @@ // -------------------------------------------------- -// init +// kontra.init // -------------------------------------------------- describe('kontra.init', function() { - var canvas = document.createElement('canvas'); - canvas.width = 600; - canvas.height = 600; - document.body.appendChild(canvas); + var canvas; + + it('should log an error if no canvas element exists', function() { + sinon.stub(kontra, 'logError', kontra.noop); + + kontra.init(); + + expect(kontra.logError.called).to.be.ok; + + kontra.logError.restore(); + }); it('should select the first canvas element on the page when no query parameters are passed', function() { + canvas = document.createElement('canvas'); + canvas.width = 600; + canvas.height = 600; + document.body.appendChild(canvas); + kontra.init(); expect(kontra.canvas).to.equal(canvas); @@ -53,7 +65,7 @@ describe('kontra.init', function() { // -------------------------------------------------- -// isArray +// kontra.isArray // -------------------------------------------------- describe('kontra.isArray', function() { @@ -79,7 +91,7 @@ describe('kontra.isArray', function() { // -------------------------------------------------- -// isString +// kontra.isString // -------------------------------------------------- describe('kontra.isString', function() { @@ -105,7 +117,7 @@ describe('kontra.isString', function() { // -------------------------------------------------- -// isNumber +// kontra.isNumber // -------------------------------------------------- describe('kontra.isNumber', function() { @@ -131,7 +143,7 @@ describe('kontra.isNumber', function() { // -------------------------------------------------- -// isImage +// kontra.isImage // -------------------------------------------------- describe('kontra.isImage', function() { @@ -157,7 +169,7 @@ describe('kontra.isImage', function() { // -------------------------------------------------- -// isCanvas +// kontra.isCanvas // -------------------------------------------------- describe('kontra.isCanvas', function() { diff --git a/test/keyboard.spec.js b/test/keyboard.spec.js index cbe3e4a0..43023c2e 100644 --- a/test/keyboard.spec.js +++ b/test/keyboard.spec.js @@ -137,6 +137,20 @@ describe('', function() { expect(kontra.keys.pressed('shift++')).to.be.true; }); + it('should handle e.which and e.keyCode', function() { + simulateEvent('keydown', {which: 65}); + simulateEvent('keydown', {keyCode: 66}); + + expect(kontra.keys.pressed('a')).to.be.true; + expect(kontra.keys.pressed('b')).to.be.true; + }); + + it('should handle special key combination ctrl+ctrl', function() { + simulateEvent('keydown', {keyCode: 17, ctrlKey: true}); + + expect(kontra.keys.pressed('ctrl')).to.be.true; + }); + @@ -146,6 +160,16 @@ describe('', function() { // -------------------------------------------------- describe('kontra.keys.bind', function() { + it('should log an error if a callback is not provided', function() { + sinon.stub(kontra, 'logError', kontra.noop); + + kontra.keys.bind('a'); + + expect(kontra.logError.called).to.be.ok; + + kontra.logError.restore(); + }); + it('should call the callback when a single key combination is pressed', function(done) { kontra.keys.bind('a', function() { done(); diff --git a/test/phantom.polyfill.js b/test/phantom.polyfill.js index dd581888..5807e9e9 100644 --- a/test/phantom.polyfill.js +++ b/test/phantom.polyfill.js @@ -1,5 +1,5 @@ /** - * Polyfill function.prototype.bind in PhantomJS. + * function.prototype.bind polyfill for PhantomJS. * @see https://github.com/ariya/phantomjs/issues/10522#issuecomment-39248521 */ if (typeof Function.prototype.bind != 'function') { @@ -22,7 +22,7 @@ if (typeof Function.prototype.bind != 'function') { } /** - * Simple Audio shim for PhantomJS tests. + * Simple Audio shim for PhantomJS. */ (function() { // only load when Audio is not defined diff --git a/test/pool.spec.js b/test/pool.spec.js index 12aa2c0a..16115a59 100644 --- a/test/pool.spec.js +++ b/test/pool.spec.js @@ -20,7 +20,7 @@ describe('', function() { return this.alive || this.timeToLive > 0; }, }; - } + }; @@ -34,7 +34,7 @@ describe('', function() { it('should log an error if the create function is not passed', function() { sinon.stub(kontra, 'logError', kontra.noop); - kontra.pool({}); + kontra.pool(); expect(kontra.logError.called).to.be.ok; diff --git a/test/quadtree.spec.js b/test/quadtree.spec.js new file mode 100644 index 00000000..4ebd1874 --- /dev/null +++ b/test/quadtree.spec.js @@ -0,0 +1,284 @@ +// -------------------------------------------------- +// kontra.quadtree +// -------------------------------------------------- +describe('', function() { + + // -------------------------------------------------- + // kontra.quadtree.init + // -------------------------------------------------- + describe('kontra.quadtree.init', function() { + + it('should create an initial bounding box', function() { + var quadtree = kontra.quadtree(); + + expect(typeof quadtree.bounds).to.equal('object'); + expect(quadtree.bounds.x).to.equal(0); + expect(quadtree.bounds.y).to.equal(0); + expect(quadtree.bounds.width).to.equal(kontra.game.width); + expect(quadtree.bounds.height).to.equal(kontra.game.height); + }); + + it('should allow you to set the maxDepth and maxObject counts', function() { + var quadtree = kontra.quadtree({ + maxDepth: 1, + maxObjects: 10 + }); + + expect(quadtree.maxDepth).to.equal(1); + expect(quadtree.maxObjects).to.equal(10); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.quadtree.add + // -------------------------------------------------- + describe('kontra.quadtree.add', function() { + var quadtree; + + beforeEach(function() { + quadtree = kontra.quadtree({ + maxObjects: 5, + bounds: { + x: 0, + y: 0, + width: 100, + height: 100 + } + }); + }); + + afterEach(function() { + quadtree.clear(); + }); + + it('should add an object to the quadtree', function() { + quadtree.add({id: 1}); + + expect(quadtree.objects.length).to.equal(1); + }); + + it('should take multiple objects and add them to the quadtree', function() { + quadtree.add({id: 1}, {id: 2}, {id: 3}); + + expect(quadtree.objects.length).to.equal(3); + }); + + it('should take an array of objects and add them to the quadtree', function() { + quadtree.add([{id: 1}, {id: 2}], {id: 3}); + + expect(quadtree.objects.length).to.equal(3); + }); + + it('should split the quadtree if there are too many objects', function() { + for (var i = 0; i < 5; i++) { + quadtree.add({id: i}); + } + + expect(quadtree.objects.length).to.equal(5); + expect(quadtree.subnodes.length).to.equal(0); + + quadtree.add({id: 4}); + + expect(quadtree.objects.length).to.equal(0); + expect(quadtree.subnodes.length).to.equal(4); + }); + + it('should make each subnode 1/4 the size of the bounds when split', function() { + for (var i = 0; i < 6; i++) { + quadtree.add({id: i}); + } + + expect(quadtree.subnodes[0].bounds).to.eql({x: 0, y: 0, width: 50, height: 50}); + expect(quadtree.subnodes[1].bounds).to.eql({x: 50, y: 0, width: 50, height: 50}); + expect(quadtree.subnodes[2].bounds).to.eql({x: 0, y: 50, width: 50, height: 50}); + expect(quadtree.subnodes[3].bounds).to.eql({x: 50, y: 50, width: 50, height: 50}); + }); + + it('should add split objects to their correct subnodes', function() { + var subnode; + var objects = [ + {id: 0, x: 15, y: 10, width: 10, height: 10}, // quadrant 0 + {id: 1, x: 30, y: 20, width: 10, height: 10}, // quadrant 0 + {id: 2, x: 45, y: 30, width: 10, height: 10}, // quadrant 0,1 + {id: 3, x: 60, y: 40, width: 10, height: 10}, // quadrant 1,3 + {id: 4, x: 75, y: 50, width: 10, height: 10}, // quadrant 3 + {id: 5, x: 90, y: 60, width: 10, height: 10} // quadrant 3 + ]; + + quadtree.add(objects); + + // quadrant 0 + subnode = quadtree.subnodes[0].objects; + + expect(subnode.length).to.equal(3); + expect(subnode.indexOf(objects[0])).to.not.equal(-1); + expect(subnode.indexOf(objects[1])).to.not.equal(-1); + expect(subnode.indexOf(objects[2])).to.not.equal(-1); + + // quadrant 1 + subnode = quadtree.subnodes[1].objects; + + expect(subnode.length).to.equal(2); + expect(subnode.indexOf(objects[2])).to.not.equal(-1); + expect(subnode.indexOf(objects[3])).to.not.equal(-1); + + // quadrant 2 + subnode = quadtree.subnodes[2].objects; + + expect(subnode.length).to.equal(0); + + // quadrant 3 + subnode = quadtree.subnodes[3].objects; + + expect(subnode.length).to.equal(3); + expect(subnode.indexOf(objects[3])).to.not.equal(-1); + expect(subnode.indexOf(objects[4])).to.not.equal(-1); + expect(subnode.indexOf(objects[5])).to.not.equal(-1); + }); + + it('should add an object to a subnode if the quadtree is already split', function() { + for (var i = 0; i < 6; i++) { + quadtree.add({id: i}); + } + + var object = {x: 10, y: 10, width: 10, height: 10}; + var subnode = quadtree.subnodes[0].objects; + + quadtree.add(object); + + expect(subnode.length).to.equal(1); + expect(subnode[0]).to.equal(object); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.quadtree.clear + // -------------------------------------------------- + describe('kontra.quadtree.clear', function() { + var quadtree; + + beforeEach(function() { + quadtree = kontra.quadtree({ + maxObjects: 5, + bounds: { + x: 0, + y: 0, + width: 100, + height: 100 + } + }); + }); + + it('should clear all objects of the quadtree', function() { + for (var i = 0; i < 4; i++) { + quadtree.add({id: i, x: i*15, y: i*10, width: 10, height: 10}); + } + + quadtree.clear(); + + expect(quadtree.objects.length).to.equal(0); + }); + + it('should clear all objects in subnodes of the quadtree', function() { + for (var i = 0; i < 9; i++) { + quadtree.add({id: i, x: i*10, y: i*10, width: 10, height: 10}); + } + + quadtree.clear(); + + expect(quadtree.objects.length).to.equal(0); + expect(quadtree.subnodes[0].objects.length).to.equal(0); + expect(quadtree.subnodes[1].objects.length).to.equal(0); + expect(quadtree.subnodes[2].objects.length).to.equal(0); + expect(quadtree.subnodes[3].objects.length).to.equal(0); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.quadtree.get + // -------------------------------------------------- + describe('kontra.quadtree.get', function() { + + beforeEach(function() { + quadtree = kontra.quadtree({ + maxObjects: 5, + bounds: { + x: 0, + y: 0, + width: 100, + height: 100 + } + }); + }); + + afterEach(function() { + quadtree.clear(); + }); + + it('should return an object in the same node as the passed object', function() { + var object = {x: 10, y: 10, width: 10, height: 10}; + + quadtree.add(object); + + var getObjects = quadtree.get({x: 30, y: 30, width: 20, height: 20}); + + expect(getObjects.length).to.equal(1); + expect(getObjects[0]).to.equal(object); + }); + + it('should return all objects in multiple subnodes if the object intercepts multiple subnodes', function() { + var objects = [ + {x: 10, y: 10, width: 10, height: 10}, + {x: 60, y: 10, width: 10, height: 10} + ]; + + quadtree.add(objects); + + var getObjects = quadtree.get({x: 45, y: 25, width: 20, height: 20}); + + expect(getObjects.length).to.equal(2); + expect(getObjects[0]).to.equal(objects[0]); + expect(getObjects[1]).to.equal(objects[1]); + }); + + it('should return objects from leaf nodes', function() { + var objects = [ + {x: 0, y: 0, width: 10, height: 10}, + {x: 10, y: 10, width: 10, height: 10}, + {x: 20, y: 20, width: 10, height: 10}, + {x: 30, y: 30, width: 10, height: 10}, + {x: 40, y: 40, width: 10, height: 10}, + {x: 50, y: 50, width: 10, height: 10}, + {x: 60, y: 10, width: 10, height: 10} + ]; + + quadtree.add(objects); + + var getObjects = quadtree.get({x: 5, y: 25, width: 20, height: 20}); + + expect(getObjects.length).to.equal(5); + expect(getObjects[0]).to.equal(objects[0]); + expect(getObjects[1]).to.equal(objects[1]); + expect(getObjects[2]).to.equal(objects[2]); + expect(getObjects[3]).to.equal(objects[3]); + expect(getObjects[4]).to.equal(objects[4]); + }); + + }); + +}); \ No newline at end of file diff --git a/test/sprite.spec.js b/test/sprite.spec.js new file mode 100644 index 00000000..5d0f742a --- /dev/null +++ b/test/sprite.spec.js @@ -0,0 +1,476 @@ +// -------------------------------------------------- +// kontra.vector +// -------------------------------------------------- +describe('', function() { + + // -------------------------------------------------- + // kontra.vector.init + // -------------------------------------------------- + describe('kontra.vector.init', function() { + + it('should set x and y', function() { + var vec = kontra.vector({x: 10, y: 20}); + + expect(vec.x).to.equal(10); + expect(vec.y).to.equal(20); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.vector.add + // -------------------------------------------------- + describe('kontra.vector.add', function() { + + it('should add one vector to another', function() { + var vec1 = kontra.vector({x: 10, y: 20}); + var vec2 = kontra.vector({x: 5, y: 10}); + + vec1.add(vec2) + + expect(vec1.x).to.eql(15); + expect(vec1.y).to.eql(30); + }); + + }); + + it('should incorporate dt if passed', function() { + var vec1 = kontra.vector({x: 10, y: 20}); + var vec2 = kontra.vector({x: 5, y: 10}); + + vec1.add(vec2, 2) + + expect(vec1.x).to.eql(20); + expect(vec1.y).to.eql(40); + }); + + + + + + // -------------------------------------------------- + // kontra.vector.clamp + // -------------------------------------------------- + describe('kontra.vector.clamp', function() { + var vec; + + beforeEach(function() { + vec = kontra.vector({x: 10, y: 20}); + vec.clamp(0, 10, 50, 75); + }) + + it('should clamp the vectors x value', function() { + vec.x = -10; + + expect(vec.x).to.equal(0); + + vec.x = 100; + + expect(vec.x).to.equal(50); + }); + + it('should clamp the vectors y value', function() { + vec.y = -10; + + expect(vec.y).to.equal(10); + + vec.y = 100; + + expect(vec.y).to.equal(75); + }); + + }); + +}); + + + + + +// -------------------------------------------------- +// kontra.sprite +// -------------------------------------------------- +describe('', function() { + + // -------------------------------------------------- + // kontra.sprite.init + // -------------------------------------------------- + describe('kontra.sprite.init', function() { + + it('should set default properties on the sprite when passed no arguments', function() { + var sprite = kontra.sprite(); + + expect(sprite.context).to.equal(kontra.context); + expect(sprite.position instanceof kontra.vector).to.be.true; + expect(sprite.velocity instanceof kontra.vector).to.be.true; + expect(sprite.acceleration instanceof kontra.vector).to.be.true; + }); + + it('should set basic properties of width, height, color, x, and y', function() { + var sprite = kontra.sprite({ + x: 10, + y: 20, + color: 'red', + width: 5, + height: 15 + }); + + expect(sprite.x).to.equal(10); + expect(sprite.y).to.equal(20); + expect(sprite.color).to.equal('red'); + expect(sprite.width).to.equal(5); + expect(sprite.height).to.equal(15); + expect(sprite.advance).to.equal(sprite._advanceSprite); + expect(sprite.draw).to.equal(sprite._drawRect); + }); + + it('should set properties of velocity, acceleration, and a different context', function() { + var context = {foo: 'bar'}; + + var sprite = kontra.sprite({ + dx: 2, + dy: 1, + ddx: 0.5, + ddy: 0.2, + context: context + }); + + expect(sprite.dx).to.equal(2); + expect(sprite.dy).to.equal(1); + expect(sprite.ddx).to.equal(0.5); + expect(sprite.ddy).to.equal(0.2); + expect(sprite.context).to.equal(context); + }); + + it('should keep the position, velocity, and acceleration vectors in sync', function() { + var sprite = kontra.sprite(); + + sprite.x = 10; + sprite.y = 20; + sprite.dx = 2; + sprite.dy = 1; + sprite.ddx = 0.5; + sprite.ddy = 0.2; + + expect(sprite.position.x).to.equal(10); + expect(sprite.position.y).to.equal(20); + expect(sprite.velocity.x).to.equal(2); + expect(sprite.velocity.y).to.equal(1); + expect(sprite.acceleration.x).to.equal(0.5); + expect(sprite.acceleration.y).to.equal(0.2); + }); + + it('should set the width and height of the sprite to an image if passed', function() { + var img = new Image(); + img.width = 10; + img.height = 20; + + var sprite = kontra.sprite({ + image: img + }); + + expect(sprite.image).to.equal(img); + expect(sprite.width).to.equal(10); + expect(sprite.height).to.equal(20); + expect(sprite.advance).to.equal(sprite._advanceSprite); + expect(sprite.draw).to.equal(sprite._drawImage); + }); + + it('should set the width and height of the sprite to an animation if passed', function() { + // simple animation object from kontra.spriteSheet + var animations = { + 'walk': { + width: 10, + height: 20 + } + }; + + var sprite = kontra.sprite({ + animations: animations + }); + + expect(sprite.animations).to.equal(animations); + expect(sprite.currentAnimation).to.equal(animations.walk); + expect(sprite.width).to.equal(10); + expect(sprite.height).to.equal(20); + expect(sprite.advance).to.equal(sprite._advanceAnimation); + expect(sprite.draw).to.equal(sprite._drawAnimation); + }); + + it('should set all additional properties on the sprite', function() { + var sprite = kontra.sprite({ + foo: 'bar', + alive: true + }); + + expect(sprite.foo).to.equal('bar'); + expect(sprite.alive).to.be.true; + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.sprite.update + // -------------------------------------------------- + describe('kontra.sprite.update', function() { + + it('should move a rect sprite by its velocity and acceleration', function() { + var sprite = kontra.sprite({ + x: 10, + y: 20, + dx: 2, + dy: 1, + ddx: 0.5, + ddy: 0.2 + }); + + sprite.update(); + + expect(sprite.dx).to.equal(2.5); + expect(sprite.dy).to.equal(1.2); + expect(sprite.x).to.equal(12.5); + expect(sprite.y).to.equal(21.2); + }); + + it('should move an image sprite by its velocity and acceleration', function() { + var img = new Image(); + img.width = 10; + img.height = 20; + + var sprite = kontra.sprite({ + x: 10, + y: 20, + dx: 2, + dy: 1, + ddx: 0.5, + ddy: 0.2, + image: img + }); + + sprite.update(); + + expect(sprite.dx).to.equal(2.5); + expect(sprite.dy).to.equal(1.2); + expect(sprite.x).to.equal(12.5); + expect(sprite.y).to.equal(21.2); + }); + + it('should move an animation sprite by its velocity and acceleration', function() { + // simple animation object from kontra.spriteSheet + var animations = { + 'walk': { + width: 10, + height: 20, + update: kontra.noop + } + }; + + var sprite = kontra.sprite({ + x: 10, + y: 20, + dx: 2, + dy: 1, + ddx: 0.5, + ddy: 0.2, + animations: animations + }); + + sprite.update(); + + expect(sprite.dx).to.equal(2.5); + expect(sprite.dy).to.equal(1.2); + expect(sprite.x).to.equal(12.5); + expect(sprite.y).to.equal(21.2); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.sprite.render + // -------------------------------------------------- + describe('kontra.sprite.render', function() { + + it('should draw a rect sprite', function() { + var sprite = kontra.sprite({ + x: 10, + y: 20, + }); + + sinon.stub(sprite.context, 'fillRect', kontra.noop); + + sprite.render(); + + expect(sprite.context.fillRect.called).to.be.ok; + + sprite.context.fillRect.restore(); + }); + + it('should draw an image sprite', function() { + var img = new Image(); + img.width = 10; + img.height = 20; + + var sprite = kontra.sprite({ + x: 10, + y: 20, + image: img + }); + + sinon.stub(sprite.context, 'drawImage', kontra.noop); + + sprite.render(); + + expect(sprite.context.drawImage.called).to.be.ok; + + sprite.context.drawImage.restore(); + }); + + it('should draw an animation sprite', function() { + // simple animation object from kontra.spriteSheet + var animations = { + 'walk': { + width: 10, + height: 20, + update: kontra.noop, + render: kontra.noop + } + }; + + var sprite = kontra.sprite({ + x: 10, + y: 20, + animations: animations + }); + + sinon.stub(sprite.currentAnimation, 'render', kontra.noop); + + sprite.render(); + + expect(sprite.currentAnimation.render.called).to.be.ok; + + sprite.currentAnimation.render.restore(); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.sprite.collidesWith + // -------------------------------------------------- + describe('kontra.sprite.collidesWith', function() { + + it('should correctly detect collision between two objects', function() { + var sprite1 = kontra.sprite({ + x: 10, + y: 20, + width: 10, + height: 20 + }); + + var sprite2 = kontra.sprite({ + x: 19, + y: 39, + width: 10, + height: 20 + }); + + expect(sprite1.collidesWith(sprite2)).to.be.true; + + sprite2.x = 10; + sprite2.y = 20; + + expect(sprite1.collidesWith(sprite2)).to.be.true; + + sprite2.x = 1; + sprite2.y = 1; + + expect(sprite1.collidesWith(sprite2)).to.be.true; + + sprite2.x = 20; + sprite2.y = 40; + + expect(sprite1.collidesWith(sprite2)).to.be.false; + + sprite2.x = 0; + sprite2.y = 0; + + expect(sprite1.collidesWith(sprite2)).to.be.false; + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.sprite.isAlive + // -------------------------------------------------- + describe('kontra.sprite.isAlive', function() { + + it('should return true when the sprite is alive', function() { + var sprite = kontra.sprite(); + + expect(sprite.isAlive()).to.be.false; + + sprite.timeToLive = 1; + + expect(sprite.isAlive()).to.be.true; + + sprite.timeToLive = -0; + + expect(sprite.isAlive()).to.be.false; + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.sprite.playAnimation + // -------------------------------------------------- + describe('kontra.sprite.playAnimation', function() { + + it('should set the animation to play', function() { + var animations = { + 'walk': { + width: 10, + height: 20 + }, + 'idle': { + width: 10, + height: 20 + } + }; + + var sprite = kontra.sprite({ + animations: animations + }); + + expect(sprite.currentAnimation).to.equal(animations.walk); + + sprite.playAnimation('idle'); + + expect(sprite.currentAnimation).to.equal(animations.idle); + }); + + }); + +}); \ No newline at end of file diff --git a/test/spriteSheet.spec.js b/test/spriteSheet.spec.js new file mode 100644 index 00000000..550fb76c --- /dev/null +++ b/test/spriteSheet.spec.js @@ -0,0 +1,379 @@ +// -------------------------------------------------- +// kontra.animation +// -------------------------------------------------- +describe('', function() { + var animation; + + beforeEach(function() { + animation = kontra.animation({ + frames: [1,2,3,4], + frameRate: 30, + spriteSheet: { + image: new Image(), + framesPerRow: 2, + frame: { + width: 5, + height: 5 + } + } + }); + }); + + + // -------------------------------------------------- + // kontra.animation.init + // -------------------------------------------------- + describe('kontra.animation.init', function() { + + it('should set properties on the animation', function() { + expect(animation.frames).to.eql([1,2,3,4]); + expect(animation.frameRate).to.equal(30); + expect(animation.width).to.equal(5); + expect(animation.height).to.equal(5); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.animation.update + // -------------------------------------------------- + describe('kontra.animation.update', function() { + + it('should not update the current frame if not enough time has passed', function() { + animation.update(); + + expect(animation.currentFrame).to.equal(0); + }); + + it('should take no parameter and update the current frame correctly', function() { + for (var i = 0; i < 3; i++) { + animation.update(); + } + + expect(animation.currentFrame).to.equal(1); + }); + + it('should take dt as a parameter and update the current frame correctly', function() { + animation.update(1/30); + + expect(animation.currentFrame).to.equal(1); + }); + + it('should restart the animation when finished', function() { + for (var i = 0; i < 7; i++) { + animation.update(); + } + + expect(animation.currentFrame).to.equal(3); + + animation.update(); + + expect(animation.currentFrame).to.equal(0); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.animation.draw + // -------------------------------------------------- + describe('kontra.animation.draw', function() { + + it('should draw the spriteSheet at its initial frame', function() { + var context = {drawImage: sinon.stub()}; + + animation.render({ + x: 10, + y: 10, + context: context + }); + + expect(context.drawImage.called).to.be.ok; + expect(context.drawImage.calledWith( + animation.spriteSheet.image, + 5, 0, 5, 5, 10, 10, 5, 5 + )).to.be.ok; + }); + + it('should use the default context', function() { + sinon.stub(kontra.context, 'drawImage'); + + animation.render({ + x: 10, + y: 10 + }); + + expect(kontra.context.drawImage.called).to.be.ok; + + kontra.context.drawImage.restore(); + }); + + it('should draw the spriteSheet in the middle of the animation', function() { + var context = {drawImage: sinon.stub()}; + + animation.currentFrame = 2; + + animation.render({ + x: 10, + y: 10, + context: context + }); + + expect(context.drawImage.called).to.be.ok; + expect(context.drawImage.calledWith( + animation.spriteSheet.image, + 5, 5, 5, 5, 10, 10, 5, 5 + )).to.be.ok; + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.animation.stop + // -------------------------------------------------- + describe('kontra.animation.stop', function() { + + it('should continue the animation', function() { + animation.stop(); + + expect(animation.update).to.equal(kontra.noop); + expect(animation.render).to.equal(kontra.noop); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.animation.pause + // -------------------------------------------------- + describe('kontra.animation.pause', function() { + + it('should continue the animation', function() { + animation.pause(); + + expect(animation.update).to.equal(kontra.noop); + expect(animation.render).to.equal(animation._draw); + }); + + }); + + + + + // -------------------------------------------------- + // kontra.animation.play + // -------------------------------------------------- + describe('kontra.animation.play', function() { + + it('should continue the animation', function() { + animation.stop(); + animation.play(); + + expect(animation.update).to.equal(animation._advance); + expect(animation.render).to.equal(animation._draw); + }); + + }); + +}); + + + + + +// -------------------------------------------------- +// kontra.spriteSheet +// -------------------------------------------------- +describe('', function() { + + // -------------------------------------------------- + // kontra.spriteSheet.init + // -------------------------------------------------- + describe('kontra.spriteSheet.init', function() { + + it('should log an error if no image is provided', function() { + sinon.stub(kontra, 'logError', kontra.noop); + + kontra.spriteSheet(); + + expect(kontra.logError.called).to.be.ok; + + kontra.logError.restore(); + }); + + it('should initialize properties on the spriteSheet when passed an image', function() { + var spriteSheet = kontra.spriteSheet({ + image: new Image(100, 200), + frameWidth: 10, + frameHeight: 10 + }); + + expect(spriteSheet.frame.width).to.equal(10); + expect(spriteSheet.frame.height).to.equal(10); + expect(spriteSheet.framesPerRow).to.equal(10); + }); + + it('should create animations if passed an animation object', function() { + sinon.stub(kontra.spriteSheet.prototype, 'createAnimations', kontra.noop); + + var spriteSheet = kontra.spriteSheet({ + image: new Image(100, 200), + frameWidth: 10, + frameHeight: 10, + animations: {} + }); + + expect(kontra.spriteSheet.prototype.createAnimations.called).to.be.ok; + + kontra.spriteSheet.prototype.createAnimations.restore(); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.spriteSheet.createAnimations + // -------------------------------------------------- + describe('kontra.spriteSheet.createAnimations', function() { + var spriteSheet; + + beforeEach(function() { + spriteSheet = kontra.spriteSheet({ + image: new Image(100, 200), + frameWidth: 10, + frameHeight: 10 + }); + }) + + it('should log an error if no animations object was passed', function() { + sinon.stub(kontra, 'logError', kontra.noop); + + spriteSheet.createAnimations(); + + expect(kontra.logError.called).to.be.ok; + + kontra.logError.restore(); + }); + + it('should log an error if the animations object was empty', function() { + sinon.stub(kontra, 'logError', kontra.noop); + + spriteSheet.createAnimations({}); + + expect(kontra.logError.called).to.be.ok; + + kontra.logError.restore(); + }); + + it('should log an error if no frames property was passed', function() { + sinon.stub(kontra, 'logError', kontra.noop); + + spriteSheet.createAnimations({ + 'walk': {} + }); + + expect(kontra.logError.called).to.be.ok; + + kontra.logError.restore(); + }); + + it('should accept a single frame', function() { + spriteSheet.createAnimations({ + walk: { + frames: 1 + } + }); + + expect(spriteSheet.animations.walk).to.exist; + expect(spriteSheet.animations.walk.frames).to.eql([1]); + }); + + it('should accept a string of ascending consecutive frames', function() { + spriteSheet.createAnimations({ + walk: { + frames: '1..5' + } + }); + + expect(spriteSheet.animations.walk).to.exist; + expect(spriteSheet.animations.walk.frames).to.eql([1,2,3,4,5]); + }); + + it('should accept a string of descending consecutive frames', function() { + spriteSheet.createAnimations({ + walk: { + frames: '5..1' + } + }); + + expect(spriteSheet.animations.walk).to.exist; + expect(spriteSheet.animations.walk.frames).to.eql([5,4,3,2,1]); + }); + + it('should accept an array of consecutive frames', function() { + spriteSheet.createAnimations({ + walk: { + frames: [1,2,3] + } + }); + + expect(spriteSheet.animations.walk).to.exist; + expect(spriteSheet.animations.walk.frames).to.eql([1,2,3]); + }); + + it('should accept an array of non-consecutive frames', function() { + spriteSheet.createAnimations({ + walk: { + frames: [1,3,5] + } + }); + + expect(spriteSheet.animations.walk).to.exist; + expect(spriteSheet.animations.walk.frames).to.eql([1,3,5]); + }); + + it('should accept a mixture of numbers, strings, and arrays', function() { + spriteSheet.createAnimations({ + walk: { + frames: [1, '2..3', 4, 5, '4..1'] + } + }); + + expect(spriteSheet.animations.walk).to.exist; + expect(spriteSheet.animations.walk.frames).to.eql([1,2,3,4,5,4,3,2,1]); + }); + + it('should log an error if passed an improper frames property', function() { + sinon.stub(kontra, 'logError', kontra.noop); + + spriteSheet.createAnimations({ + walk: { + frames: {} + } + }); + + expect(kontra.logError.called).to.be.ok; + + kontra.logError.restore(); + }); + + }); + +}); \ No newline at end of file diff --git a/test/tileEngine.spec.js b/test/tileEngine.spec.js new file mode 100644 index 00000000..fe111d46 --- /dev/null +++ b/test/tileEngine.spec.js @@ -0,0 +1,313 @@ +// -------------------------------------------------- +// kontra.tileEngine +// -------------------------------------------------- +describe('', function() { + + // -------------------------------------------------- + // kontra.tileEngine.init + // -------------------------------------------------- + describe('kontra.tileEngine.init', function() { + + it('should log an error if no dimensions are passed', function() { + sinon.stub(kontra, 'logError', kontra.noop); + + kontra.tileEngine(); + + expect(kontra.logError.called).to.be.ok; + + kontra.logError.restore(); + }); + + it('should initialize properties on the tile engine', function() { + var tileEngine = kontra.tileEngine({ + tileWidth: 10, + tileHeight: 10, + width: 100, + height: 150 + }); + + expect(tileEngine.mapWidth).to.equal(1000); + expect(tileEngine.mapHeight).to.equal(1500); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.tileEngine.addTileset + // -------------------------------------------------- + describe('kontra.tileEngine.addTileset', function() { + var tileEngine; + + beforeEach(function() { + tileEngine = kontra.tileEngine({ + tileWidth: 10, + tileHeight: 10, + width: 50, + height: 50 + }); + }); + + it('should log an error if no image is provided', function() { + sinon.stub(kontra, 'logError', kontra.noop); + + tileEngine.addTileset(); + + expect(kontra.logError.called).to.be.ok; + + kontra.logError.restore(); + }); + + it('should use firstGrid if passed', function() { + tileEngine.addTileset({ + image: new Image(100, 100), + firstGrid: 17 + }); + + expect(tileEngine.tilesets[0].firstGrid).to.equal(17); + expect(tileEngine.tilesets[0].lastGrid).to.equal(116); + }); + + it('should calculate first and last grid number correctly', function() { + tileEngine.addTileset({ + image: new Image(100, 100) + }); + + expect(tileEngine.tilesets[0].firstGrid).to.equal(1); + expect(tileEngine.tilesets[0].lastGrid).to.equal(100); + }); + + it('should calculate first and last grid number when there is a tileset already added', function() { + tileEngine.addTileset({ + image: new Image(100, 100) + }); + + tileEngine.addTileset({ + image: new Image(100, 100) + }); + + expect(tileEngine.tilesets[1].firstGrid).to.equal(101); + expect(tileEngine.tilesets[1].lastGrid).to.equal(200); + }); + + it('should calculate first and last grid number correctly for multiple tilesets', function() { + tileEngine.addTileset({ + image: new Image(200, 200) + }); + + tileEngine.addTileset({ + image: new Image(150, 150) + }); + + tileEngine.addTileset({ + image: new Image(32, 64) + }); + + expect(tileEngine.tilesets[0].firstGrid).to.equal(1); + expect(tileEngine.tilesets[0].lastGrid).to.equal(400); + + expect(tileEngine.tilesets[1].firstGrid).to.equal(401); + expect(tileEngine.tilesets[1].lastGrid).to.equal(625); + + expect(tileEngine.tilesets[2].firstGrid).to.equal(626); + expect(tileEngine.tilesets[2].lastGrid).to.equal(643); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.tileEngine.addLayer + // -------------------------------------------------- + describe('kontra.tileEngine.addLayer', function() { + var tileEngine; + + beforeEach(function() { + tileEngine = kontra.tileEngine({ + tileWidth: 10, + tileHeight: 10, + width: 50, + height: 50 + }); + + sinon.stub(kontra.tileEngine.prototype, '_preRenderImage', kontra.noop); + }); + + afterEach(function() { + kontra.tileEngine.prototype._preRenderImage.restore(); + }); + + it('should set default properties on the layer', function() { + tileEngine.addLayer({ + name: 'test', + data: [0,0,1,0,0], + }); + + expect(tileEngine.layers.test[0]).to.equal(0); + expect(tileEngine.layers.test[1]).to.equal(0); + expect(tileEngine.layers.test[2]).to.equal(1); + expect(tileEngine.layers.test[3]).to.equal(0); + expect(tileEngine.layers.test[4]).to.equal(0); + }); + + it('should flatten a 2d tile data array', function() { + tileEngine.addLayer({ + name: 'test', + data: [[0,0],[1,0,0]], + }); + + expect(tileEngine.layers.test[0]).to.equal(0); + expect(tileEngine.layers.test[1]).to.equal(0); + expect(tileEngine.layers.test[2]).to.equal(1); + expect(tileEngine.layers.test[3]).to.equal(0); + expect(tileEngine.layers.test[4]).to.equal(0); + }); + + it('should add the tileset in the correct order', function() { + tileEngine.addLayer({ + name: 'test', + data: [0,0,1,0,0], + zIndex: 1 + }); + + tileEngine.addLayer({ + name: 'test2', + data: [0,0,1,0,0], + zIndex: -1 + }); + + tileEngine.addLayer({ + name: 'test3', + data: [0,0,1,0,0], + zIndex: 10 + }); + + expect(tileEngine._layerOrder[0]).to.equal('test2'); + expect(tileEngine._layerOrder[1]).to.equal('test'); + expect(tileEngine._layerOrder[2]).to.equal('test3'); + }); + + it('should not order the tileset if it is not being rendered', function() { + tileEngine.addLayer({ + name: 'test', + data: [0,0,1,0,0], + render: false + }); + + expect(tileEngine._layerOrder.length).to.equal(0); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.tileEngine.layerCollidesWith + // -------------------------------------------------- + describe('kontra.tileEngine.layerCollidesWith', function() { + var tileEngine; + + beforeEach(function() { + tileEngine = kontra.tileEngine({ + tileWidth: 10, + tileHeight: 10, + width: 50, + height: 50 + }); + + sinon.stub(kontra.tileEngine.prototype, '_preRenderImage', kontra.noop); + + tileEngine.addLayer({ + name: 'test', + data: [0,0,1,0,0] + }); + }); + + afterEach(function() { + kontra.tileEngine.prototype._preRenderImage.restore(); + }); + + it('should return false if the object does not collide', function() { + var collides = tileEngine.layerCollidesWith('test', { + x: 10, + y: 10, + height: 10, + width: 10 + }); + + expect(collides).to.equal(false); + }); + + it('should return true if the object collides', function() { + var collides = tileEngine.layerCollidesWith('test', { + x: 25, + y: 5, + height: 10, + width: 10 + }); + + expect(collides).to.equal(true); + }); + + it('should handle sprites off the map', function() { + var collides = tileEngine.layerCollidesWith('test', { + x: 100, + y: 100, + height: 100, + width: 100 + }); + + expect(collides).to.equal(false); + }); + + }); + + + + + + // -------------------------------------------------- + // kontra.tileEngine.tileAtLayer + // -------------------------------------------------- + describe('kontra.tileEngine.tileAtLayer', function() { + var tileEngine; + + beforeEach(function() { + tileEngine = kontra.tileEngine({ + tileWidth: 10, + tileHeight: 10, + width: 50, + height: 50 + }); + + sinon.stub(kontra.tileEngine.prototype, '_preRenderImage', kontra.noop); + + tileEngine.addLayer({ + name: 'test', + data: [0,0,1,0,0] + }); + }); + + afterEach(function() { + kontra.tileEngine.prototype._preRenderImage.restore(); + }); + + it('should return the correct tile', function() { + expect(tileEngine.tileAtLayer('test', 0, 0)).to.equal(0); + expect(tileEngine.tileAtLayer('test', 10, 5)).to.equal(0); + expect(tileEngine.tileAtLayer('test', 20, 9)).to.equal(1); + expect(tileEngine.tileAtLayer('test', 30, 10)).to.equal(undefined); + expect(tileEngine.tileAtLayer('test', 40, 1)).to.equal(0); + }); + + }); + +}); \ No newline at end of file