1 /*
  2  ___               
  3 | _ \_  _ _ _ __ __
  4 |  _/ || | ' \\ \ /
  5 |_|  \_,_|_||_/_\_\
  6                    
  7 */
  8 /**
  9  * @author magicmarkers
 10  * @description A derpy HTML5/JS engine for side-scrolling brawler / beat 'em up / punchy-fighty games.
 11  * @see https://github.com/magicmarkers/Punx
 12  */
 13 
 14 // TODO: implement elevation
 15 // TODO: get audio stuff working
 16 // TODO: multi-layered background, all panning according to their width vs screen/stage width
 17 //       this accounts for mutliple parallaxes and a 1:1 "immediate bg" that is synced with panning.
 18 // TODO: boilerplate baddy AI
 19 // TODO: boilerplate post-hit temporary invulnerability/blinkage for player
 20 // TODO: boilerplate talk bubbles
 21 
 22 /**
 23  * @namespace Punx
 24  */
 25 var Punx = {};
 26 /*
 27  ___                     _   _ _   _ _ 
 28 | _ \_  _ _ _ __ __     | | | | |_(_) |
 29 |  _/ || | ' \\ \ /  _  | |_| |  _| | |
 30 |_|  \_,_|_||_/_\_\ (_)  \___/ \__|_|_|
 31                                        
 32 */
 33 
 34 Punx.Util = new function() {
 35 
 36     this.PHI = (1+Math.sqrt(5))/2; // golden ratio
 37     this.PHI_A = 1 - this.PHI;
 38     this.PHI_B = 1 - this.PHI_A;
 39 
 40     /**
 41      * Determines if two boxes intersect.
 42      * @public
 43      * @param {Punx.Box} a
 44      * @param {Punx.Box} b
 45      * @returns boolean
 46      */
 47     this.AABB = function(a,b) {
 48         return (!(
 49             a.x + a.w < b.x || // a too left
 50             a.x > b.x + b.w || // a too right
 51             a.y + a.h < b.y || // a too bg (y)
 52             a.y > b.y + b.h    // a too fg (y)
 53         ));
 54     };
 55 
 56     /**
 57      * Returns a box describing the intersection of two boxes.
 58      * This may return strange things if they aren't actually intersecting. Use AABB() first.
 59      * @public
 60      * @param {Punx.Box} a
 61      * @param {Punx.Box} b
 62      * @returns Punx.Box
 63      */
 64     this.AABBC = function(a,b) {
 65         var c = {
 66             x : (a.x < b.x ? b.x : a.x),
 67             y : (a.y < b.y ? b.y : a.y)
 68         };
 69         c.w = (a.x + a.w < b.x + b.w ? a.x + a.w : b.x + b.w) - c.x;
 70         c.h = (a.y + a.h < b.y + b.h ? a.y + a.h : b.y + b.h) - c.y;
 71         return new Punx.Box(c);
 72     };
 73 
 74     /**
 75      * Scans a box for all AABB intersecting sprites.
 76      * @public
 77      * @param {Punx.Sprite[]} sprites Needles
 78      * @param {Punx.Box} box Haystack
 79      * @returns {Punx.Sprite[]} An array of sprites that collide with the box
 80      */
 81     this.scan = function(sprites,box) {
 82         var intersections = [];
 83         for (var i in sprites) {
 84             if (this.AABB(box,sprites[i])) intersections.push(sprites[i]);
 85         }
 86         return intersections;
 87     };
 88 
 89     /**
 90      * Returns an array of sprites, sorted according to their proximity to the origin x.
 91      * @param {number} x Integer x origin point
 92      * @param {Punx.Sprite[]} sprites Sprites to sort
 93      * @returns {Punx.Sprite[]} Sorted array
 94      */
 95     this.xSort = function(x,sprites) {
 96         return sprites.sort(function(a,b) {
 97             var A = (a.x + a.w < x ? x - (a.x + a.w) : a.x - x);
 98             var B = (b.x + b.w < x ? x - (b.x + b.w) : b.x - x);
 99             return (A < B ? -1 : (A > B ? 1 : 0));
100         });
101     };
102 
103     /**
104      * Returns an array of sprites, sorted according to their proximity to the origin y.
105      * @param {number} y Integer y origin point
106      * @param {Punx.Sprite[]} sprites Sprites to sort
107      * @returns {Punx.Sprite[]} Sorted array
108      */
109     this.ySort = function(y,sprites) {
110         return sprites.sort(function(a,b) {
111             var A = (a.y + a.h < y ? y - (a.y + a.h) : a.y - y);
112             var B = (b.y + b.h < y ? y - (b.y + b.h) : b.y - y);
113             return (A < B ? -1 : (A > B ? 1 : 0));
114         });
115     };
116 
117 };
118 
119 /*
120  ___                     ___          _ _       
121 | _ \_  _ _ _ __ __     / __|_ __ _ _(_) |_ ___ 
122 |  _/ || | ' \\ \ /  _  \__ \ '_ \ '_| |  _/ -_)
123 |_|  \_,_|_||_/_\_\ (_) |___/ .__/_| |_|\__\___|
124                             |_|                 
125 */
126 
127 /**
128  * A screen object.
129  * @class
130  * @param {object} args Construction values for public properties.
131  */
132 
133 Punx.Sprite = function(args) {
134 
135     args = args||{};
136 
137     /**
138      * Required: Screen reference.
139      * @public
140      * @type Punx.Screen
141      */
142     this.screen = args.screen;
143 
144     /**
145      * Sprite sheet to draw from.
146      * @public
147      * @type Image
148      */
149     this.sheet = args.sheet;
150 
151     /**
152      * Keys animation frame group names to an array of integer arrays [x,y,w,h], the cropping information of the sheet for each animation frame.
153      * Required if sheet is set.
154      * @public
155      * @type object<string,int[][]>
156      */
157     this.frames = args.frames;
158 
159     /**
160      * Current/initial animation frame group name.
161      * Required if frames is set. Automatically wraps the animation frame index when set.
162      * @public
163      * @type string
164      */
165     this.group = args.group;
166 
167     set group(group) {
168         this.group = group;
169         this.frame %= this.frames[this.group].length;
170     }
171 
172     /**
173      * Sprite's framerate.
174      * @public
175      * @type int
176      * @defaults 10
177      */
178     this.fps = args.fps || 10;
179 
180     /**
181      * Sprite's X offset.
182      * @public
183      * @type int
184      * @defaults 0
185      */
186     this.x = args.x || 0;
187 
188     /**
189      * Sprite's Y offset.
190      * @public
191      * @type int
192      * @defaults 0
193      */
194     this.y = args.y || 0;
195 
196     /**
197      * Sprite's "elevation".
198      * 0 = floor tile, 1 = standing on ground, 2 = in air, 3 = sticky (health bar, etc)
199      * @public
200      * @type int
201      * @defaults 1
202      */
203     this.z = args.z || 1;
204 
205     /**
206      * Sprite's width.
207      * @public
208      * @type int
209      * @defaults 64
210      */
211     this.w = args.w || 64;
212 
213     /**
214      * Sprite's height.
215      * @public
216      * @type int
217      * @defaults 64
218      */
219     this.h = args.h || 64;
220 
221     /**
222      * Animation frame index.
223      * Increments once per update, prior to step(). Automatically wraps at the length of the current group.
224      * @public
225      * @type int
226      */
227     this.frame = 0;
228 
229     set frame(frame) {
230         if (this.group) this.frame = frame % this.frames[this.group].length;
231     }
232 
233     /**
234      * Called by the screen. The sprite should prepare itself to be drawn.
235      * Returns boolean of whether it was updated.
236      * Every update, tick() is called. Every frame-rated update, step() is called after the animation frame increments.
237      * @public
238      * @returns bool
239      */
240     this.update = function() {
241         var updated = false;
242         updated |= this._tick();
243         if (this.screen.grid.frame % this.fps == 0) {
244             updated = true;
245             this.frame++;
246             this._step();
247         }
248         return updated;
249     };
250 
251     /**
252      * Called upon every update.
253      * Returns boolean of whether the tick caused an update.
254      * This is a stub.
255      * @private
256      * @returns ?boolean
257      */
258     this._tick = function() {
259     };
260 
261     /**
262      * Called every frame-rated update.
263      * Unlike tick(), the return value is ignored as the sprite is considered updated on every frame-rated update.
264      * This is a stub.
265      * @private
266      */
267     // called every rated amount of grid frames
268     // use this to perform rated logic. it may be called whether or not an animation step was made, as groups can have a single frame.
269     // return boolean of whether the sprite is updated
270     this._step = function() {
271     };
272 
273     /**
274      * Sprite draws itself to the canvas context provided.
275      * This is called by the screen once all sprites are updated and accounted for, such that when the sprite draws itself it will automatically layer properly.
276      * Automatically adjusts for Punx.Screen.Stage.x (panning).
277      * Does nothing if group isn't set.
278      * The sprite may be drawn multiple times during graphics analysis. Drawing should not change the sprite in any way.
279      * @public
280      * @param ctx Canvas context.
281      */
282     this.draw = function(ctx) {
283         if (!this.group) return;
284         var frame = this.frames[this.group][this.frame];
285         ctx.drawImage(this.sheet,frame[0],frame[1],frame[2],frame[3],this.x - (this.screen.x||0),this.y,this.w,this.h);
286     };
287 };
288 
289 Punx.Sprite.sort = function(a,b) {
290     // sorting function that prioritizes sprites with more depth
291     // this is mainly used when figuring out the drawing order.
292     if (a instanceof Punx.Sprite.Wall ^ b instanceof Punx.Sprite.Wall) return (a instanceof Punx.Sprite.Wall ? -1 : 1); // walls always behind
293     else if (a.z != b.z) return (a.z < b.z ? -1 : 1);
294     else return (a.y + a.h < b.y + b.h ? -1 : 1); // sprite's bottom is further away
295 };
296 
297 
298 /*
299   ___     _    _       ___          _ _            __      __    _ _ 
300  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___      \ \    / /_ _| | |
301 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   \ \/\/ / _` | | |
302  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_)   \_/\_/\__,_|_|_|
303                           |_|                                        
304 */
305 // a wall is a stage element that actors cannot clip.
306 // walls are not actors, they are inert.
307 
308 Punx.Sprite.Wall = function(args) {
309     Punx.Sprite.call(this,args);
310     
311 }; Punx.Sprite.Wall.prototype = new Punx.Sprite();
312 
313 
314 /*
315   ___     _    _       ___          _ _               _      _           
316  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___        /_\  __| |_ ___ _ _ 
317 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   / _ \/ _|  _/ _ \ '_|
318  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_|  
319                           |_|                                            
320 */
321 // actors are any non-inert stage element. these include baddies, powerups, and props.
322 // actors interact, such as via collision.
323 
324 Punx.Sprite.Actor = function(args) {
325     Punx.Sprite.call(this,args);
326 
327     this.name = args['name']||'Actor';
328 
329     this.collidable = true;
330 
331     this.busy = false; // set this to disable user input or to denote that a multi-cycle process is taking place and you dont want to be interrupted
332 
333     // get collided by another actor, react to it.
334     // this is meant to be overloaded.
335     // dont modify the other actor, as notification order is undefined. it's only provided for context so you can react appropriately.
336     // chances are if its the initial collision then there will be a large swath of calls to this function within a short period of time, as
337     // collisions are checked every screen update.
338     // set flags etc accordingly to "debounce" collision notifications.
339     // for example, when the player is collided by a baddy, inflict self-damage and set a flag and timeout for recover-time invulnerability.
340     this.collide = function(actor) {
341     };
342 
343     this.move = function(stage,direction,amount) {
344         amount = amount || 5;
345         this.setGroup(direction);
346         var x = this.x;
347         var y = this.y;
348         switch (direction) {
349             case 'left' : { x -= amount; break; }
350             case 'right' : { x += amount; break; }
351             case 'up' : { y -= amount; break; }
352             case 'down' : { y += amount; break; }
353         }
354         if (!stage.clipping(this,x,y)) {
355             this.x = x;
356             this.y = y;
357         }
358         return true;
359     }
360 
361 }; Punx.Sprite.Actor.prototype = new Punx.Sprite();
362 
363 
364 /*
365   ___     _    _       ___          _ _               _      _                 _____    _                   
366  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___        /_\  __| |_ ___ _ _      |_   _| _(_)__ _ __ _ ___ _ _ 
367 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   / _ \/ _|  _/ _ \ '_|  _    | || '_| / _` / _` / -_) '_|
368  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_|   (_)   |_||_| |_\__, \__, \___|_|  
369                           |_|                                                            |___/|___/         
370 */
371 // triggers are actors that collide with just bounding box
372 
373 Punx.Sprite.Actor.Trigger = function(v) {
374     Punx.Sprite.Actor.call(this,v=v||{});
375     this.name = 'Trigger';
376 }; Punx.Sprite.Actor.Trigger.prototype = new Punx.Sprite.Actor();
377 
378 /*
379   ___     _    _       ___          _ _               _      _                 ___ _                   
380  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___        /_\  __| |_ ___ _ _      | _ \ |__ _ _  _ ___ _ _ 
381 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   / _ \/ _|  _/ _ \ '_|  _  |  _/ / _` | || / -_) '_|
382  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_|   (_) |_| |_\__,_|\_, \___|_|  
383                           |_|                                                             |__/         
384 */
385 // the user
386 
387 Punx.Sprite.Actor.Player = function(v) { 
388     Punx.Sprite.Actor.call(this,v=v||{});
389     this.name = 'Player';
390     this.update = function(stage) {
391         if (!this.busy) {
392             with (Punx.Key) {
393                 if (key['left'] || key['a']) this.move(stage,'left');
394                 else if (key['right'] || key['d']) this.move(stage,'right');
395                 if (key['down'] || key['s']) this.move(stage,'down');
396                 else if (key['up'] || key['w']) this.move(stage,'up');
397             }
398         }
399         Punx.Sprite.Actor.Player.prototype.update.call(this,stage);
400     };
401 }; Punx.Sprite.Actor.Player.prototype = new Punx.Sprite.Actor();
402 
403 /*
404  ___                     ___                   _ 
405 | _ \_  _ _ _ __ __     / __| ___ _  _ _ _  __| |
406 |  _/ || | ' \\ \ /  _  \__ \/ _ \ || | ' \/ _` |
407 |_|  \_,_|_||_/_\_\ (_) |___/\___/\_,_|_||_\__,_|
408                                                  
409 */
410 // under construction
411 Audio.prototype.fadeTo = function(fadeTo,callback) {
412     if (fadeTo < 0) fadeTo = 0;
413     else if (fadeTo > 1) fadeTo = 1;
414     var that = this;
415     var setVolume = function(setTo) {
416         return function() {
417             that.volume = setTo;
418             if (setTo == fadeTo && callback) callback();
419         };
420     };
421     var delay = 0;
422     if (this.volume < fadeTo) {
423         for (var v=this.volume; v<=fadeTo; v+=0.1) {
424             setTimeout(setVolume(v),delay);
425             delay+=100;
426         }
427     }
428     else if (volume > fadeTo) {
429         for (var v=this.volume; v>=fadeTo; v-=0.1) {
430             setTimeout(setVolume(v),delay);
431             delay+=100;
432         }
433     }
434     else if (callback) callback();
435 };
436 
437 Punx.Sound = new function() {
438 
439     this.music = new Audio(); // swap out for one in Library
440 
441     this.muted = false;
442     this.mute = function() {
443         this.muted ^= 1;
444         this.music.muted = this.muted;
445     };
446 
447 };
448 
449 /*
450  ___                     ___                      
451 | _ \_  _ _ _ __ __     / __| __ _ _ ___ ___ _ _  
452 |  _/ || | ' \\ \ /  _  \__ \/ _| '_/ -_) -_) ' \ 
453 |_|  \_,_|_||_/_\_\ (_) |___/\__|_| \___\___|_||_|
454                                                   
455 */
456 /**
457  * Represents a collection of sprites to be drawn to the canvas. Only the active screen is drawn.
458  * @class
459  * @param {object} args Construction values for public properties.
460  */
461 
462 Punx.Screen = function(args) {
463 
464     /**
465      * Required: {Punx.Grid} reference
466      * @type Punx.Grid
467      */
468     this.grid = args.grid;
469 
470     /**
471      * List of {Punx.Sprite} instances
472      * @type Punx.Sprites[]
473      * @default []
474      */
475     this.sprites = args.sprites || [];
476 
477     /**
478      * Optional background image
479      * @type Image
480      */
481     this.bg = args.bg;
482 
483     /**
484      * Called by the game loop. Update all sprites and redraw if needed.
485      */
486     this.update = function() {
487         var updated = false;
488         for (var i in this.sprites) {
489             updated |= this.sprites[i].update();
490         }
491         if (updated) this._draw();
492     };
493 
494     /**
495      * Called by update() if needed.
496      * @private
497      */
498     this._draw = function() {
499         this.grid.ctx.clearRect(0,0,this.grid.w,this.grid.h);
500         if (this.bg) this.grix.ctx.drawImage(this.bg,0,0);
501         for (var i in this.sprites.sort(Punx.Sprite.sort)) {
502             this.sprites[i].draw(this.grid.ctx);
503         }
504     };
505 
506 };
507 
508 /*
509   ___     _    _       ___                            ___ _                 
510  / __|_ _(_)__| |     / __| __ _ _ ___ ___ _ _       / __| |_ __ _ __ _ ___ 
511 | (_ | '_| / _` |  _  \__ \/ _| '_/ -_) -_) ' \   _  \__ \  _/ _` / _` / -_)
512  \___|_| |_\__,_| (_) |___/\__|_| \___\___|_||_| (_) |___/\__\__,_\__, \___|
513                                                                   |___/     
514 */
515 
516 /**
517  * stages are screens that are game levels. they have "curtains" and a "floor".
518  * imagine looking upon a theater stage from an ideal balcony seat.
519  * curtains are used for backgrounds and parallax.
520  * the floor is the "depth" where actors move away from and toward the "front" of the stage.
521  * the defaults define the curtains and floor in terms of the golden ratio:
522  * http://en.wikipedia.org/wiki/Golden_ratio
523  * where the longer portion represents the curtains and the shorter portion is the floor.
524  * @class
525  */
526 Punx.Screen.Stage = function(args) {
527     Punx.Screen.call(this,args);
528 
529     /**
530      * Required. This is the actor which the camera pans for.
531      * @public
532      * @type Punx.Sprite.Actor.Player
533      */
534     this.player = args.player;
535 
536     /**
537      * Width of the stage. This can go beyond the viewport, the camera will pan with the player.
538      * @public
539      * @type int
540      * @default 960
541      */
542     this.w = args.w || 960;
543 
544     /**
545      * Stage floor Y offset. This is the length of the "curtains", the bottom of which is up-stage.
546      * This is the height of the background area inaccessible to actors.
547      * This defaults to Phi-A of the default canvas height (540).
548      * @public
549      * @type int
550      * @default 334
551      */
552     this.y = args.y || 334;
553 
554     /**
555      * Stage floor "depth". The amount of freedom to move along the Y axis, relative to the bottom of the curtains, that actors have.
556      * This defaults to the remainder of the canvas height, but can be made shorter to prevent actors from moving too down-stage.
557      * @public
558      * @type int
559      * default 206
560      */
561     this.h = args.h || (this.grid.h - this.y);
562 
563     /**
564      * Pan offset from house-left.
565      * @public
566      * @type int
567      * @default 0
568      */
569     this.x = args.x || 0;
570 
571     /**
572      * How close the player can get to the edge of the frame before the camera pans.
573      * @public
574      * @type int
575      * @default 200
576      */
577     this.panMargin = 200;
578 
579     /**
580      * All in-frame and frame-edge sprites, populated at the beginning of each update().
581      * @private
582      * @type Punx.Sprite[]
583      */
584     this._sprites = [];
585 
586     /**
587      * All in-frame and frame-edge actors, populated at the beginning of each update().
588      * @private
589      * @type Punx.Sprite.Actor[]
590      */
591     this._actors = [];
592 
593     /**
594      * All in-frame and frame-edge walls, populated at the beginning of each update().
595      * @private
596      * @type Punx.Sprite.Wall[]
597      */
598     this._walls = [];
599 
600     /**
601      * Determines in-frame and frame-edged sprites, pans, and draws and checks for collisions if needed.
602      * @public
603      */
604     this.update = function() {
605         var updated = false;
606         this._sprites = [];
607         this._walls = [];
608         this._actors = [];
609         this._collidable = [];
610 
611         // pan
612         with (this) {
613             // pan left
614             if (x > 0 && player.x < x + panMargin) {
615                 x -= panMargin - (player.x - x);
616                 if (x < 0) x = 0;
617             }
618             // pan right
619             else if (x + grid.w < w && player.x + player.w > x + grid.w - panMargin) {
620                 x += (player.x + player.w) - (x + grid.w - panMargin);
621                 if (x > w - grid.w) x = w - grid.w;
622             }
623         }
624 
625         // determine active sprites, in-frame and frame-edged
626         for (var i in this.sprites) {
627             var sprite = this.sprites[i];
628             if (sprite.x + sprite.w < this.x || sprite.x > this.x + this.grid.w) continue; // sprite is out of frame
629             this._sprites.push(sprite);
630             if (sprite instanceof Punx.Sprite.Wall) this._walls.push(sprite);
631             else if (sprite instanceof Punx.Sprite.Actor) this._actors.push(sprite);
632         }
633 
634         // update active sprites. sprites should return boolean of whether they updated
635         for (var i in this._sprites.sort(Punx.Sprite.sort)) {
636             updated |= this._sprites[i].update();
637         }
638 
639         if (updated) {
640             this._draw(); // draw the screen
641             this._collisions(); // detect collisions and notify sprites after drawing so "reactions" occur next update cycle
642         }
643 
644     };
645 
646     /**
647      * Actors should call this to determine if they can move to a given position.
648      * This will ensure that actors stay on the stage floor and don't hit any walls.
649      * An actor can move close to the back of the stage, according to the Phi-B of their height. This is used as the actor's "depth".
650      * @public
651      * @param {Punx.Sprite.Actor} actor
652      * @param {int} x Destination X coord
653      * @param {int} y Destination Y coord
654      * @returns bool
655      */
656     this.clips = function(actor,x,y) {
657         // stay on stage floor vertically
658         if (y + (actor.h * Punx.Util.PHI_B) < this.y || y + actor.h > this.y + this.h) return true;
659 
660         // stay in frame horizontally
661         if (x < this.x || x + actor.w > this.x + this.grid.w) return true;
662 
663         // don't clip walls
664         for (var i in this._walls) {
665             if (Punx.Util.AABB(this._walls[i],{x:x,y:y,w:actor.w,h:actor.h})) return true;
666         }
667 
668         // nothing clipped
669         return false;
670     };
671 
672     /**
673      * Draws the screen.
674      * @private
675      */
676     this._draw = function() {
677         this.grid.ctx.clearRect(0,0,this.grid.w,this.grid.h);
678         for (var i in this._sprites.sort(Punx.Sprite.sort)) {
679             this._sprites[i].draw(this.grid.ctx);
680         }
681     };
682 
683     /**
684      * Checks in-frame actors for collision and notifies them.
685      * This performs n(n-2)/2 AABB checks, subchecking pixels in intersecting areas to a semi-fine resolution (5 pixels), using the imaginary canvas.
686      * An actor is only collidable if its collidable flag is true.
687      * If an actor is collided and sets its collidable flag to false, any further checks on it are skipped.
688      * A pixel is only considered collidable if its opacity is >= 127, however, triggers (Punx.Sprite.Actor.Trigger) collide with just AABB.
689      * When two actors collide, both are notified via their collide() methods and given a reference to each other.
690      * The order of notification is undefined, so actors should only "be collided" and treat each other as read-only.
691      * @private
692      * @param {number} [resolution=5] Integer pixel collision resolution, the number of pixels to skip per pixel check.
693      */
694     this._collisions = function(resolution) {
695         resolution = (resolution || 5) * 4;
696         var n = this._actors.length;
697         for (var i=0;i<n;i++) {
698             var a = this._actors[i];
699             for (var j=i+1;j<n;j++) {
700                 var b = this._actors[j];
701                 if (!a.collidable) break;
702                 if (!b.collidable) continue;
703                 if (!Punx.Util.AABB(a,b)) continue; // no box collision, move on.
704 
705                 // triggers don't need pixel detection, bounding box is enough
706                 if (a instanceof Punx.Sprite.Actor.Trigger || b instanceof Punx.Sprite.Actor.Trigger) {
707                     a.collide(b);
708                     b.collide(a);
709                     continue;
710                 }
711 
712                 // proceed with pixel detection
713 
714                 var c = Punx.Util.AABBC(a,b); // determine intersection
715 
716                 // get separate slices of a and b at the intersection using the imaginary canvas
717 
718                 // imagine a
719                 this.grid.ictx.clearRect(0,0,this.grid.w,this.grid.h);
720                 a.draw(this.grid.ictx);
721                 c.a = this.grid.ictx.getImageData(c.x-this.x,c.y,c.w,c.h); // slice a @ intersect
722 
723                 // imagine b
724                 this.grid.ictx.clearRect(0,0,this.grid.w,this.grid.h);
725                 b.draw(this.grid.ictx);
726                 c.b = this.grid.ictx.getImageData(c.x-this.x,c.y,c.w,c.h); // slice b @ intersect
727 
728                 // compare slice pixels according to resolution
729                 var m = c.a.data.length; // since the slices are the same size we just use a here
730                 for (var p=0;p<m;p+=resolution) {
731                     if (c.a.data[p+3] >= 127 && c.b.data[p+3] >= 127) { // two pixels occupy the same space and are opaque enough
732                         a.collide(b);
733                         b.collide(a);
734                         break;
735                     }
736                 }
737             }
738         }
739     };
740 
741 }; Punx.Screen.Stage.prototype = new Punx.Screen();
742 /*
743  ___                     _  __         
744 | _ \_  _ _ _ __ __     | |/ /___ _  _ 
745 |  _/ || | ' \\ \ /  _  | ' </ -_) || |
746 |_|  \_,_|_||_/_\_\ (_) |_|\_\___|\_, |
747                                   |__/ 
748 */
749 /**
750  * Keyboard interface.
751  */
752 Punx.Key = new function() {
753 
754     /**
755      * Object keyed by button name and its current press-state, so that sprites et al. can poll the keyboard.
756      * @public
757      * @type object<string,boolean>
758      */
759     this.state = {};
760 
761     /**
762      * Internal map between keyCode and button name.
763      * @private
764      * @type object<integer,string>
765      */
766     this._map = {
767         9:'tab',
768         13:'enter',
769         27:'escape',
770         32:'space',
771         37:'left',
772         38:'up',
773         39:'right',
774         40:'down'
775     };
776     for (var i=48;i<=57;i++) { this._map[i] = String.fromCharCode(i); } // numbers
777     for (var i=65;i<=90;i++) { this._map[i] = String.fromCharCode(i).toLowerCase(); } // letters
778 
779     /**
780      * Document's onkeydown listener, attached to the document by the grid during create().
781      * Sets the key's state to true. Keypresses that are combined with special keys like CTRL, ALT, Shift, Meta/Windows, Menu, etc are ignored.
782      * @public
783      * @param {event} [e=window.event]
784      */
785     this.onkeydown = function(e) {
786         e=e||window.event;
787         if (e.altGraphKey || e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) return;
788         var key = this._map[e.keyCode];
789         if (!key) return;
790         e.preventDefault();
791         e.stopPropagation();
792         this.state[key] = true;
793     }.bind(this);
794 
795     /**
796      * Document's keyup listener, attached to the document by the grid during create().
797      * Sets the key's state to false.
798      * @public
799      * @param {event} [e=window.event]
800      */
801     this.onkeyup = function(e) {
802         e=e||window.event;
803         var key = this._map[e.keyCode];
804         if (!key) return;
805         e.preventDefault();
806         e.stopPropagation();
807         this.state[key] = false;
808     }.bind(this);
809 
810 };
811 
812 /*
813  ___                      ___     _    _ 
814 | _ \_  _ _ _ __ __      / __|_ _(_)__| |
815 |  _/ || | ' \\ \ /  _  | (_ | '_| / _` |
816 |_|  \_,_|_||_/_\_\ (_)  \___|_| |_\__,_|
817                                          
818 */
819 /**
820  * @class Represents the game engine. Houses the main loop and canvas DOM node reference.
821  * @param {object} args Public field construction values.
822  */
823 var Punx.Grid = function(args) {
824 
825     /**
826      * Required: Object holding all screens, keyed by name.
827      * @type object<string,Punx.Screen>
828      * @public
829      */
830     this.screens = args.screens;
831 
832     /**
833      * Required: Key of the initial screen.
834      * @public
835      * @type string
836      */
837     this.screen = args.screen
838 
839     /**
840      * Integer canvas width.
841      * @public
842      * @type number
843      * @default 960
844      */
845     this.w = args.w || 960;
846 
847     /**
848      * Integer canvas height.
849      * @public
850      * @type number
851      * @default 540
852      */
853     this.h = args.h || 540;
854 
855     /**
856      * Integer frames-per-second.
857      * @public
858      * @type number
859      * @default 60
860      */
861     this.fps = args.fps || 60;
862 
863     /**
864      * For debugging, causes the main loop to idle when true.
865      * @public
866      * @type boolean
867      * @default false
868      */
869     this.idle = false;
870 
871     /**
872      * Internal frame counter, increments once after every update cycle, wraps at fps. This should be treated as read-only.
873      * @public
874      * @type number
875      * @default 0
876      */
877     this.frame = 0;
878 
879     /**
880      * Viewport canvas context.
881      * @public
882      */
883     this.ctx = null;
884 
885     /**
886      * Imaginary canvas context.
887      * @public
888      */
889     this.ictx = null;
890 
891     /**
892      * Viewport canvas DOM element. This is displayed.
893      * @private
894      */
895     this._canvas = null;
896 
897     /**
898      * Imaginary canvas DOM element used as a frame-buffer for collision detection and other graphics analysis. Scratch-space. Never shown.
899      * @private
900      */
901     this._icanvas = null;
902 
903     /**
904      * The game loop, an interval that calls update() on the current screen, timed according to fps.
905      * @private
906      */
907     this._loop = null;
908 
909     /**
910      * Creates the canvases and injects the main canvas into the viewport.
911      * @public
912      * @param [parentElement=document.body] DOM element to make the canvas a child of.
913      */
914     this.create = function(parentElement) {
915         // viewport canvas
916         this._canvas = document.createElement('canvas');
917         this._canvas.setAttribute('width',this.w);
918         this._canvas.setAttribute('height',this.h);
919         this._canvas.setAttribute('tabindex',0);
920         this._canvas.addEventListener('keydown',Punx.Key.onkeydown,true);
921         this._canvas.addEventListener('keyup',Punx.Key.onkeyup,true);
922         this.ctx = this._canvas.getContext('2d');
923         this.ctx.imageSmoothingEnabled = false;
924         this.ctx.mozImageSmoothingEnabled = false;
925         this.ctx.webkitImageSmoothingEnabled = false;
926         (parentElement||document.body).appendChild(this._canvas);
927         this._canvas.focus();
928 
929         // imaginary canvas
930         this._icanvas = document.createElement('canvas');
931         this._icanvas.setAttribute('width',this.w);
932         this._icanvas.setAttribute('height',this.h);
933         this.ictx = this._icanvas.getContext('2d');
934         this.ictx.imageSmoothingEnabled = false;
935         this.ictx.mozImageSmoothingEnabled = false;
936         this.ictx.webkitImageSmoothingEnabled = false;
937     };
938 
939     /**
940      * Starts the main loop.
941      * @public
942      */
943     this.start = function() {
944         this._loop = window.setInterval(this._update.bind(this),1000/this.fps);
945     };
946 
947     /**
948      * The main loop calls this once every fps. The loop is killed if any errors occur. Does nothing if idle is true.
949      * @private
950      */
951     this._update = function() {
952         if (this.idle) return;
953         try {
954             this.screen.update();
955             this.frame = ++this.frame % this.fps;
956         }
957         catch (e) {
958             console.log(e.stack);
959             window.clearInterval(this._loop);
960         }
961     };
962 
963 };
964 /*
965  ___                 ___                 _   _          
966 | _ \_  _ _ _ __ __ | __|_ ____ ___ _ __| |_(_)___ _ _  
967 |  _/ || | ' \\ \ /_| _|\ \ / _/ -_) '_ \  _| / _ \ ' \ 
968 |_|  \_,_|_||_/_\_(_)___/_\_\__\___| .__/\__|_\___/_||_|
969                                    |_|                  
970 */
971 
972 /**
973  * Generic exception.
974  * @class
975  * @param {string} message Exception message.
976  */
977 
978 Punx.Exception = function(message) {
979 
980     this.message = message;
981 
982 }
983 /*
984  ___                 ___          
985 | _ \_  _ _ _ __ __ | _ ) _____ __
986 |  _/ || | ' \\ \ /_| _ \/ _ \ \ /
987 |_|  \_,_|_||_/_\_(_)___/\___/_\_\
988                                   
989 */
990 
991 Punx.Box = function(x,y,w,h) {
992 
993     this.x = x;
994     this.y = y;
995     this.w = w;
996     this.h = h;
997 
998     this.__defineGetter__('X',function(){
999         return this.x+this.w;
1000     });
1001 
1002     this.__defineGetter__('Y',function(){
1003         return this.y+this.h;
1004     });
1005 
1006 }
1007