1 /*
  2   ___     _    _ 
  3  / __|_ _(_)__| |
  4 | (_ | '_| / _` |
  5  \___|_| |_\__,_|
  6                  
  7 */
  8 /**
  9  * A derpy HTML5/JS engine for side-scrolling brawler / beat 'em up / punchy-fighty games.
 10  * https://github.com/magicmarkers/Grid
 11  * @author magicmarkers
 12  *
 13  */
 14 // TODO: change collision engine, make pixel perfect. keep depth in mind.
 15 // TODO: get audio stuff working
 16 // TODO: parallax backgrounds
 17 // TODO: boilerplate baddy AI
 18 // TODO: boilerplate post-hit temporary invulnerability/blinkage for player
 19 // TODO: boilerplate talk bubbles
 20 
 21 var Grid = function(args) {
 22 
 23     // construction
 24     this.screens = args['screens']; // required, dict of screen name => Grid.Screen instances
 25     this.screen = args['screen']; // required, string starting screen name
 26     this.w = args['w'] || 960;
 27     this.h = args['h'] || 540; // viewport height
 28     this.fps = args['fps'] || 60; // frames per second
 29 
 30     // rw
 31     this.idle = false; // for debugging, idles the main loop
 32 
 33     // ro
 34     this.frame = 0; // update counter, increments once every FPS.
 35     this.ctx; // canvas context, the actual viewport
 36     this.ictx; // imaginary canvas context, used for collision detection etc.
 37 
 38     // private
 39     this._canvas; // DOM element of viewport
 40     this._icanvas; // DOM node for imaginary (framebuffer) canvas. never inserted
 41     this._loop; // _update() interval
 42 
 43     // creates the canvases and injects the viewport one into the DOM
 44     this.inject = function(parentElement) {
 45         // viewport canvas
 46         this._canvas = document.createElement('canvas');
 47         this._canvas.setAttribute('width',this.w);
 48         this._canvas.setAttribute('height',this.h);
 49         this._canvas.setAttribute('tabindex',0);
 50         this._canvas.addEventListener('keydown',Grid.Keyboard._keydown,true);
 51         this._canvas.addEventListener('keyup',Grid.Keyboard._keyup,true);
 52         this._ctx = this._canvas.getContext('2d');
 53         this._ctx.imageSmoothingEnabled = false;
 54         this._ctx.mozImageSmoothingEnabled = false;
 55         this._ctx.webkitImageSmoothingEnabled = false;
 56         (parentElement||document.body).appendChild(this._canvas);
 57         this._canvas.focus();
 58 
 59         // imaginary canvas
 60         this._icanvas = document.createElement('canvas');
 61         this._icanvas.setAttribute('width',this.w);
 62         this._icanvas.setAttribute('height',this.h);
 63         this.ictx = this._icanvas.getContext('2d');
 64         this.ictx.imageSmoothingEnabled = false;
 65         this.ictx.mozImageSmoothingEnabled = false;
 66         this.ictx.webkitImageSmoothingEnabled = false;
 67     };
 68 
 69     // start the main loop
 70     this.start = function() {
 71         this._loop = window.setInterval(this._update.bind(this),1000/this.fps);
 72     };
 73 
 74     // the main loop calls this once every FPS.
 75     // kills the loop upon error. does nothing if idle is true
 76     this._update = function() {
 77         if (this.idle) return;
 78         try {
 79             this.screen.update();
 80             this.frame = ++this.frame % this.fps;
 81         }
 82         catch (e) {
 83             console.log(e.stack);
 84             window.clearInterval(this._loop);
 85         }
 86     };
 87 
 88 };
 89 /*
 90   ___     _    _       _   _ _   _ _ 
 91  / __|_ _(_)__| |     | | | | |_(_) |
 92 | (_ | '_| / _` |  _  | |_| |  _| | |
 93  \___|_| |_\__,_| (_)  \___/ \__|_|_|
 94                                      
 95 */
 96 // Utilities
 97 Grid.Util = new function() {
 98 
 99     this.PHI = (1+Math.sqrt(5))/2; // golden ratio
100     this.PHI_A = 1 - this.PHI;
101     this.PHI_B = 1 - this.PHI_A;
102 
103     // determines if two boxes, a and b, are intersecting
104     this.AABB = function(a,b) {
105         return (!(
106             a.x + a.w < b.x || // a too left
107             a.x > b.x + b.w || // a too right
108             a.y + a.h < b.y || // a too bg (y)
109             a.y > b.y + b.h    // a too fg (y)
110         ));
111     };
112 
113     // returns a box for the intersection of boxes a and b
114     // this may return strange things if they aren't actually intersecting. use AABB first.
115     this.AABBC = function(a,b) {
116         return new function() {
117             this.x = a.x < b.x ? b.x : a.x;
118             this.y = a.y < b.y ? b.y : a.y;
119             this.w = (a.x + a.w < b.x + b.w ? a.x + a.w : b.x + b.w) - this.x;
120             this.h = (a.y + a.h < b.y + b.h ? a.y + a.h : b.y + b.h) - this.y;
121         };
122     };
123 
124     // imagines a box and returns a list of sprites it AABB intersects.
125     // use this for instantaneous projectiles or other things from afar.
126     // sprites is a list of sprite instances, and box is an object with x,y,w,h
127     this.scan = function(box,sprites) {
128         var intersections = [];
129         for (var i in sprites) {
130             if (Grid.Util.AABB(box,sprites[i])) intersections.append(sprites[i]);
131         }
132         return intersections;
133     };
134 
135     // returns a sorted list of sprites according to their proximity to the origin x
136     this.xSort = function(sprites,x) {
137         x=x||0;
138         return sprites.sort(function(a,b) {
139             if (a.x + a.w < x) { // a is left
140                 if (b.x + b.w < x) { // b is left
141                     return (a.x + a.w > b.x + b.w ? -1 : 1);
142                 }
143                 else { // b is right
144                     return (x - (a.x + a.w) < b.x - x ? -1 : 1);
145                 }
146             }
147             else { // a is right
148                 if (b.x > x) { // b is right
149                     return (a.x < b.x ? -1 : 1);
150                 }
151                 else { // b is left
152                     return (a.x - x < x - (b.x + b.w) ? -1 : 1);
153                 }
154             }
155         });
156     };
157 
158     // returns a sorted list of sprites according to their proximity to the origin y
159     this.ySort = function(sprites,y) {
160         y=y||0;
161         return sprites.sort(function(a,b) {
162             if (a.y + a.h < y) { // a is above
163                 if (b.y + b.h < y) { // b is above
164                     return (a.y + a.h > b.y + b.h ? -1 : 1);
165                 }
166                 else { // b is below
167                     return (y - (a.y + a.h) < b.y - y ? -1 : 1);
168                 }
169             }
170             else { // a is below
171                 if (b.y > y) { // b is below
172                     return (a.y < b.y ? -1 : 1);
173                 }
174                 else { // b is above
175                     return (a.y - y < y - (b.y + b.h) ? -1 : 1);
176                 }
177             }
178         });
179     };
180 
181 };
182 
183 /*
184   ___     _    _       _  __         
185  / __|_ _(_)__| |     | |/ /___ _  _ 
186 | (_ | '_| / _` |  _  | ' </ -_) || |
187  \___|_| |_\__,_| (_) |_|\_\___|\_, |
188                                 |__/ 
189 */
190 // keyboard stuff
191 
192 Grid.Key = new function() {
193 
194     // key name => bool state
195     // sprites should poll this during update() and act accordingly.
196     // sprites should use internal flags to determine if they can be interrupted.
197     // example: if (Grid.Key.state['left'] && !this.busy) ...
198     this.state = {};
199 
200     // keyCode => key name
201     this._keys = {
202         '9':'tab',
203         '13':'enter',
204         '27':'escape',
205         '32':'space',
206         '37':'left',
207         '38':'up',
208         '39':'right',
209         '40':'down'
210     };
211     for (var i=48;i<=57;i++) { this._keys[i] = String.fromCharCode(i); } // numbers
212     for (var i=65;i<=90;i++) { this._keys[i] = String.fromCharCode(i).toLowerCase(); } // letters
213 
214     // document's keydown listener
215     this._keydown = function(e) {
216         e=e||window.event;
217         if (e.altGraphKey || e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) return;
218         var key = this._keys[e.keyCode];
219         if (!key) return;
220         e.preventDefault();
221         e.stopPropagation();
222         this.state[key] = true;
223     }.bind(this); // event handlers are wacky and need "this" explicitly bound
224 
225     // document's keyup listener
226     this._keyup = function(e) {
227         e=e||window.event;
228         var key = this._keys[e.keyCode];
229         if (!key) return;
230         e.preventDefault();
231         e.stopPropagation();
232         this.state[key] = false;
233     }.bind(this); // event handlers are wacky and need "this" explicitly bound
234 
235 };
236 
237 /*
238   ___     _    _       ___                   _ 
239  / __|_ _(_)__| |     / __| ___ _  _ _ _  __| |
240 | (_ | '_| / _` |  _  \__ \/ _ \ || | ' \/ _` |
241  \___|_| |_\__,_| (_) |___/\___/\_,_|_||_\__,_|
242                                                
243 */
244 // in progress
245 Audio.prototype.fadeTo = function(fadeTo,callback) {
246     if (fadeTo < 0) fadeTo = 0;
247     else if (fadeTo > 1) fadeTo = 1;
248     var that = this;
249     var setVolume = function(setTo) {
250         return function() {
251             that.volume = setTo;
252             if (setTo == fadeTo && callback) callback();
253         };
254     };
255     var delay = 0;
256     if (this.volume < fadeTo) {
257         for (var v=this.volume; v<=fadeTo; v+=0.1) {
258             setTimeout(setVolume(v),delay);
259             delay+=100;
260         }
261     }
262     else if (volume > fadeTo) {
263         for (var v=this.volume; v>=fadeTo; v-=0.1) {
264             setTimeout(setVolume(v),delay);
265             delay+=100;
266         }
267     }
268     else if (callback) callback();
269 };
270 
271 Grid.Sound = new function() {
272 
273     this.music = new Audio(); // swap out for one in Library
274 
275     this.muted = false;
276     this.mute = function() {
277         this.muted ^= 1;
278         this.music.muted = this.muted;
279     };
280 
281 };
282 
283 /*
284   ___     _    _       ___                      
285  / __|_ _(_)__| |     / __| __ _ _ ___ ___ _ _  
286 | (_ | '_| / _` |  _  \__ \/ _| '_/ -_) -_) ' \ 
287  \___|_| |_\__,_| (_) |___/\__|_| \___\___|_||_|
288                                                 
289 */
290 // a screen is a collection of sprites, such as a menu. levels are stages and extend this.
291 // only one screen is drawn to the canvas.
292 
293 Grid.Screen = function(args) {
294 
295     // required
296     this.grid = args['grid']; // grid reference
297 
298     // optional
299     this.sprites = args['sprites']||[]; // list of Grid.Sprite instances
300 
301     // called by the grid. screens should draw themselves to the grid canvas context if they need to.
302     // this notifies sprites that they should "step" or otherwise incrementally change themselves if needed.
303     // sprites should return boolean of whether or not they changed
304     this.update = function() {
305         var updated = false;
306         for (var i in this.sprites) {
307             updated |= this.sprites[i].update();
308         }
309         if (updated) this._draw();
310     };
311 
312     // draw the screen
313     // sprites are drawn from "back to front", that is, sprites with more "depth" are drawn first as to mimic layering.
314     this._draw = function() {
315         this.grid.ctx.clearRect(0,0,this.grid.w,this.grid.h);
316         for (var i in this.sprites.sort(Grid.Sprite.sort)) {
317             this.sprites[i].draw(this.grid.ctx);
318         }
319     };
320 };
321 
322 /*
323   ___     _    _       ___                            ___ _                 
324  / __|_ _(_)__| |     / __| __ _ _ ___ ___ _ _       / __| |_ __ _ __ _ ___ 
325 | (_ | '_| / _` |  _  \__ \/ _| '_/ -_) -_) ' \   _  \__ \  _/ _` / _` / -_)
326  \___|_| |_\__,_| (_) |___/\__|_| \___\___|_||_| (_) |___/\__\__,_\__, \___|
327                                                                   |___/     
328 */
329 // stages are screens that are game levels. they have "curtains" and a "floor".
330 // imagine looking upon a theater stage from an ideal balcony seat.
331 // curtains are used for backgrounds and parallax.
332 // the floor is the "depth" where actors move away from and toward the "front" of the stage.
333 // the defaults define the curtains and floor in terms of the golden ratio:
334 // http://en.wikipedia.org/wiki/Golden_ratio
335 // where the longer portion represents the curtains and the shorter portion is the floor.
336 
337 Grid.Screen.Stage = function(args) {
338     Grid.Screen.call(this,args);
339 
340     // required
341     this.player = args['player']; // Grid.Sprite.Actor.Player instance
342 
343     // optional
344     this.w = args['w'] || 960; // width of the stage (can go beyond viewport for panning)
345     this.y = args['y'] || 334; // height of curtains
346     this.h = args['h'] || 206; // height ("depth") of floor.
347     this.x = args['x'] || 0; // starting pan amount from stage left
348     this.panMargin = 200; // player distance from frame edge to start panning
349     this.pannable = args['pannable']||true; // whether to pan
350 
351     // private stuff temporary to update()
352     this._sprites = []; // all in-frame sprites
353     this._actors = []; // in-frame actors
354     this._walls = []; // in-frame walls
355 
356     this.update = function() {
357         var updated = false;
358         this._sprites = [];
359         this._walls = [];
360         this._actors = [];
361 
362         // pan
363         if (this.pannable) with (this) {
364             // pan left if: frame left > stage left && player left < frame left margin
365             if (x > 0 && player.x < x + panMargin) {
366                 // amount: margin - (player left - frame left)
367                 x -= panMargin - (player.x - x);
368                 if (x < 0) x = 0; // stop at min left
369             }
370             // pan right if: frame right < stage right && player right > frame right margin
371             else if (x + grid.w < w && player.x + player.w > x + grid.w - panMargin) {
372                 // amount: margin - ((frame right) - (player right))
373                 x += panMargin - ((x + grid.w) - (player.x + player.w));
374                 if (x > w - grid.w) x = w - grid.w; // stop at max right
375             }
376         }
377 
378         // determine active sprites, in-frame and frame-edged
379         for (var i in this.sprites) {
380             var s = this.sprites[i];
381             if (s.x + s.w < this.x || s.x > this.x + this.grid.w) continue; // sprite is out of frame
382             this._sprites.push(s);
383             if (s instanceof Grid.Sprite.Wall) this._walls.push(s);
384             else if (s instanceof Grid.Sprite.Actor) this._actors.push(s);
385         }
386 
387         // update active sprites. sprites should return boolean of whether they updated
388         for (var i in this._sprites.sort(Grid.Sprite.sort)) {
389             updated |= this._sprites[i].update();
390         }
391 
392         if (updated) {
393             this._draw(); // draw the screen
394             this._collisions(); // detect collisions and notify sprites after drawing so "reactions" occur next update cycle
395         }
396 
397     };
398 
399     // returns boolean. actors should call this during their update() to determine if they can move to a given position
400     this.clipping = function(actor,x,y) {
401         // ensure actor stays on stage floor vertically. their "depth" is determined by the PHI_B of their height
402         if (y < this.y - (actor.h * Grid.Const.PHI_B) || y + actor.h > this.y + this.h) return true;
403 
404         // ensure actor can only walk into frame horizontally, not out of frame
405         if (x < this.x || x + actor.w > this.x + this.grid.w) return true;
406 
407         // ensure actor doesn't walk through walls
408         for (var i in this._walls) {
409             if (this._walls[i].clipping(x,y,actor.w,actor.h)) return true;
410         }
411     };
412 
413     // draws the screen
414     this._draw = function() {
415         this.grid.ctx.clearRect(0,0,this.grid.w,this.grid.h);
416         for (var i in this._sprites.sort(Grid.Sprite.sort)) {
417             this._sprites[i].draw(this.grid.ctx);
418         }
419     };
420 
421     this._collisions = function() {
422         // check in-frame actors for collision
423         // this performs n(n-1)/2 AABB checks, subchecking pixels in overlapping areas
424         // a pixel is only considered collidable if its opacity is >=127
425         // when two actors collide, both are notified via actor.collide(stage,other actor)
426         // the order of notification is undefined. actors should only "be collided", not do things to effect the other actor as it might not be notified yet.
427         // TODO "z" axis
428         var _actorsLength = this._actors.length;
429         for (var i=0;i<_actorsLength;i++) {
430             var a = this._actors[i];
431             for (var j=i+1;j<_actorsLength;j++) {
432                 var b = this._actors[j];
433                 if (!Grid.Util.AABB(a,b)) continue; // no box collision, move on.
434 
435                 // triggers don't need pixel detection, bounding box is enough
436                 if (a instanceof Grid.Sprite.Actor.Trigger || b instanceof Grid.Sprite.Actor.Trigger) {
437                     a.collide(b);
438                     b.collide(a);
439                     continue;
440                 }
441 
442                 // moving on to pixel detection,
443                 // determine intersection
444                 var c = Grid.Util.AABBC(a,b);
445 
446                 // get separate slices of a and b at the intersection using the scratch canvas
447                 this.grid.ictx.clearRect(a.x-this.x,a.y,a.w,a.h); // clear scratch space
448                 this.grid.draw(a,this.grid.ictx); // draw scratch a
449                 c.a = this.grid.ictx.getImageData(c.x-this.x,c.y,c.w,c.h); // slice of a @ intersect
450                 this.grid.ictx.clearRect(b.x-this.x,b.y,b.w,b.h); // clear scratch space
451                 this.grid.draw(b,this.grid.ictx); // draw scratch b
452                 c.b = this.grid.ictx.getImageData(c.x-this.x,c.y,c.w,c.h); // slice of b @ intersect
453 
454                 // compare slices on a pixel-by-pixel basis according to a resolution
455                 var resolution = 5; // jump this many pixels for each check in order to speed it up.
456                                     // if set to 1, all pixels are checked.
457                 resolution *= 4;    // each pixel actually takes up 4 elements in the data.
458 
459                 var sliceLength = c.a.data.length;
460                 for (var p=0;p<sliceLength;p+=resolution) {
461                     // notify and break when two pixels collide
462                     // pixels are collidable when their opacity is >= 127
463                     if (c.a.data[p+3] >= 127 && c.b.data[p+3] >= 127) {
464                         a.collide(b);
465                         b.collide(a);
466                         break;
467                     }
468                 }
469             }
470         }
471     };
472 
473 }; Grid.Screen.Stage.prototype = new Grid.Screen();
474 
475 /*
476   ___     _    _       ___          _ _       
477  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___ 
478 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)
479  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___|
480                           |_|                 
481 */
482 // sprites are stage elements that have a drawable face
483 
484 Grid.Sprite = function(args) {
485 
486     // required
487     this.screen = args['screen']; // Grid.Screen reference
488     this.sheet = new Image();
489     this.sheet.src = args['sheet']; // sprite sheet url relative to html
490     this.frames = args['frames']; // { group name : [ [ x,y ] , ... ] , ... } where x,y is sheet cropping origin point
491     this.group = args['group']; // current frame group name
492 
493     // optional
494     this.rate = v['rate']||10; // updates per animation step
495     this.name = args['name']||'Sprite';
496     this.x = args['x']||0;
497     this.y = args['y']||0;
498     this.z = args['z']||1; // 0 rug, 1 standing, 2 in air, 3 sticky
499     this.w = args['w']||64;
500     this.h = args['h']||64;
501 
502     this._step = 0; // animation step index, increments once per rated update prior to step() being called, wraps at length of current group
503 
504     this.setGroup = function(name) {
505         this.group = name;
506         this._step %= this.frames[this.group].length; // wrap animation step index
507     };
508 
509     this.update = function() {
510         var updated = false;
511 
512         // issue a tick. this is the sprite's custom logic every grid fps.
513         updated |= this.tick();
514 
515         // rate-limited steps
516         if (this.screen.grid.frame % this.rate == 0) {
517             if (this.frames[this.group].length > 1) {
518                 updated = true;
519                 this._step = ++this._step % this.frames[this.group].length; // increment and wrap animation step index
520             }
521             updated |= this.step();
522         }
523 
524         return updated;
525     };
526 
527     // called whenever the screen calls update() on the sprite.
528     // use this to perform logic per grid frame, return boolean if the sprite is updated
529     this.tick = function() {
530     };
531 
532     // called every rated amount of grid frames
533     // 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.
534     // return boolean of whether the sprite is updated
535     this.step = function() {
536     };
537 
538     this.draw = function(ctx) {
539         var frame = this.frames[this.group][this._step];
540         ctx.drawImage(this.sheet,this.frame[0],this.frame[1],this.w,this.h,this.x - (this.screen.x||0),this.y,this.w,this.h);
541     }
542 };
543 
544 Grid.Sprite.sort = function(a,b) {
545     // sorting function that prioritizes sprites with more depth
546     // this is mainly used when figuring out the drawing order.
547     if (a instanceof Grid.Sprite.Wall ^ b instanceof Grid.Sprite.Wall) return (a instanceof Grid.Sprite.Wall ? -1 : 1); // walls always behind
548     else if (a.z != b.z) return (a.z < b.z ? -1 : 1);
549     else return (a.y + a.h < b.y + b.h ? -1 : 1); // sprite's bottom is further away
550 };
551 
552 /*
553   ___     _    _       ___          _ _            __      __    _ _ 
554  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___      \ \    / /_ _| | |
555 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   \ \/\/ / _` | | |
556  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_)   \_/\_/\__,_|_|_|
557                           |_|                                        
558 */
559 // a wall is a stage element that actors cannot clip.
560 // walls are not actors, they are inert.
561 
562 Grid.Sprite.Wall = function(args) {
563     Grid.Sprite.call(this,args);
564     
565     // clip check an area
566     this.clipping = function(x,y,h,w) {
567         return !(
568         );
569     };
570 
571 }; Grid.Sprite.Wall.prototype = new Grid.Sprite();
572 
573 /*
574   ___     _    _       ___          _ _               _      _           
575  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___        /_\  __| |_ ___ _ _ 
576 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   / _ \/ _|  _/ _ \ '_|
577  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_|  
578                           |_|                                            
579 */
580 // actors are any non-inert stage element. these include baddies, powerups, and props.
581 // actors interact, such as via collision.
582 
583 Grid.Sprite.Actor = function(args) {
584     Grid.Sprite.call(this,args);
585 
586     this.name = args['name']||'Actor';
587 
588     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
589 
590     // get collided by another actor, react to it.
591     // this is meant to be overloaded.
592     // dont modify the other actor, as notification order is undefined. it's only provided for context so you can react appropriately.
593     // 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
594     // collisions are checked every screen update.
595     // set flags etc accordingly to "debounce" collision notifications.
596     // for example, when the player is collided by a baddy, inflict self-damage and set a flag and timeout for recover-time invulnerability.
597     this.collide = function(actor) {
598     };
599 
600     this.move = function(stage,direction,amount) {
601         amount = amount || 5;
602         this.setGroup(direction);
603         var x = this.x;
604         var y = this.y;
605         switch (direction) {
606             case 'left' : { x -= amount; break; }
607             case 'right' : { x += amount; break; }
608             case 'up' : { y -= amount; break; }
609             case 'down' : { y += amount; break; }
610         }
611         if (!stage.clipping(this,x,y)) {
612             this.x = x;
613             this.y = y;
614         }
615         return true;
616     }
617 
618 }; Grid.Sprite.Actor.prototype = new Grid.Sprite();
619 
620 /*
621   ___     _    _       ___          _ _               _      _                 ___ _                   
622  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___        /_\  __| |_ ___ _ _      | _ \ |__ _ _  _ ___ _ _ 
623 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   / _ \/ _|  _/ _ \ '_|  _  |  _/ / _` | || / -_) '_|
624  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_|   (_) |_| |_\__,_|\_, \___|_|  
625                           |_|                                                             |__/         
626 */
627 // the user
628 
629 Grid.Sprite.Actor.Player = function(v) { 
630     Grid.Sprite.Actor.call(this,v=v||{});
631     this.name = 'Player';
632     this.update = function(stage) {
633         if (!this.busy) {
634             with (Grid.Key) {
635                 if (key['left'] || key['a']) this.move(stage,'left');
636                 else if (key['right'] || key['d']) this.move(stage,'right');
637                 if (key['down'] || key['s']) this.move(stage,'down');
638                 else if (key['up'] || key['w']) this.move(stage,'up');
639             }
640         }
641         Grid.Sprite.Actor.Player.prototype.update.call(this,stage);
642     };
643 }; Grid.Sprite.Actor.Player.prototype = new Grid.Sprite.Actor();
644 
645 /*
646   ___     _    _       ___          _ _               _      _                 _____    _                   
647  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___        /_\  __| |_ ___ _ _      |_   _| _(_)__ _ __ _ ___ _ _ 
648 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   / _ \/ _|  _/ _ \ '_|  _    | || '_| / _` / _` / -_) '_|
649  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_|   (_)   |_||_| |_\__, \__, \___|_|  
650                           |_|                                                            |___/|___/         
651 */
652 // triggers are actors that collide with just bounding box
653 
654 Grid.Sprite.Actor.Trigger = function(v) {
655     Grid.Sprite.Actor.call(this,v=v||{});
656     this.name = 'Trigger';
657 }; Grid.Sprite.Actor.Trigger.prototype = new Grid.Sprite.Actor();
658