Fixes bullet projection pathfinding to only consider future paths on the current...
authordsc <david.schoonover@gmail.com>
Tue, 23 Nov 2010 04:52:05 +0000 (20:52 -0800)
committerdsc <david.schoonover@gmail.com>
Tue, 23 Nov 2010 04:52:05 +0000 (20:52 -0800)
src/Y/y-collection.js
src/lessly/log.js
src/portal/math/line.js
src/portal/math/vec.js
src/portal/util/tree/quadtree.js
src/tanks/map/level.js
src/tanks/map/pathmap.js
src/tanks/map/trajectory.js
src/tanks/thing/bullet.js
src/tanks/thing/tank.js
src/tanks/thing/thing.js

index 5cc6068..9e7ee7b 100644 (file)
@@ -116,7 +116,7 @@ YBase.subclass('YCollection', {
         var args = Y(arguments),
             name = args.shift();
         return this.map(function(o){
-            return o[name].apply(o, args);
+            return o && o[name].apply(o, args);
         });
     }
     
index b1d85bb..3fa7703 100644 (file)
@@ -8,7 +8,8 @@ Log = new Y.Class('Log', {
     init : function init(el, options){
         this.el = el;
         var o = Y({}, this.DEFAULT_OPTIONS, options || {});
-        this.options = o.end();
+        // this.options = o.end();
+        this.options = o;
         this.prefix = (this.options.prefix ? '['+prefix+'] ' : '');
         this.id = Log._n++;
         
index e6b5172..11734b8 100644 (file)
@@ -59,6 +59,11 @@ math.Line = new Y.Class('Line', math.Vec, {
                              this.y1 + t*this.pb );
     },
     
+    iparametric : function iparametric(x,y){
+        return new math.Vec( (x-this.x1)/this.pa ,
+                             (y-this.y1)/this.pa );
+    },
+    
     pointAtX : function pointAtX(x){ return new math.Vec(x, this.calcY(x)); },
     pointAtY : function pointAtY(y){ return new math.Vec(this.calcX(y), y); },
     calcY : function calcY(x){ return (x === this.xint ? 0 : x*this.slope + this.yint); },
index 4154495..7d5a2e0 100644 (file)
@@ -64,6 +64,15 @@ math.Vec = new Y.Class('Vec', [], {
         return this.x*b.x + this.y*b.y;
     },
     
+    manhattan: function manhattan(x2,y2) {
+        if (x2 instanceof math.Vec) {
+            y2 = x2.y; x2 = x2.x;
+        }
+        var d1 = Math.abs(x2 - this.x)
+        ,   d2 = Math.abs(y2 - this.y) ;
+        return d1 + d2;
+    },
+    
     rotate : function rotate(theta){
         var sin = Math.sin(theta)
         ,   cos = Math.cos(theta)
@@ -96,6 +105,29 @@ Y.extend(math.Vec, {
     lerp : function lerp(x, a, b) {
         return new math.Vec( math.lerp(a.x, b.x, x),
                              math.lerp(a.y, b.y, x)  );
-    }
+    },
+    
+    // manhattan: function manhattan(p1, p2) {
+    //     var d1 = Math.abs(p2.x - p1.x)
+    //     ,   d2 = Math.abs(p2.y - p1.y) ;
+    //     return d1 + d2;
+    // },
+    
+    // manhattan4: function manhattan4(x1,y1, x2,y2) {
+    //     var d1 = Math.abs(x2 - x1)
+    //     ,   d2 = Math.abs(y2 - y1) ;
+    //     return d1 + d2;
+    // },
+    
+    manhattan: function manhattan(x1,y1, x2,y2) {
+        if (x1 instanceof math.Vec && y1 instanceof math.Vec) {
+            y2 = y1.y; x2 = y1.x;
+            y1 = x1.y; x1 = x1.x;
+        }
+        var d1 = Math.abs(x2 - x1)
+        ,   d2 = Math.abs(y2 - y1) ;
+        return d1 + d2;
+    },
+    
     
 });
index b7a5afb..8b23c47 100644 (file)
@@ -69,6 +69,7 @@ QuadTree = new Y.Class('QuadTree', {
     },
     
     
+    // TODO: Add optional filter
     get : function get(x1,y1, x2,y2){
         return this.collect(x1,y1, x2,y2, this._get, { vals:Y([]) }, this).vals;
     },
@@ -81,6 +82,9 @@ QuadTree = new Y.Class('QuadTree', {
         return acc;
     },
     
+    // XXX: Since this is by far the most commonly called function, it'd be good to
+    // collapse the recursion and reduce the number of function-calls. But it's not a
+    // bottleneck yet, so we'll wait.
     // _get : function _get(_x1,_y1, _x2,_y2, values){
     //     var self = this
     //     ,   cxt = context || self
index 413110b..8909eff 100644 (file)
@@ -3,11 +3,11 @@ Level = Rect.subclass('Level', {
     
     init : function init(game, w,h){
         Rect.init.call(this, w,h);
+        this.canvas.remove();
+        
         this.game    = game;
-        this.pathmap = new PathMap(this, 0,0, w, h, CAPACITY);
         this.walls   = [];
-        
-        this.canvas.remove();
+        this.pathmap = new PathMap(this, 0,0, w, h, CAPACITY);
         
         game.addEventListener('ready', this.setup.bind(this));
     },
@@ -20,7 +20,7 @@ Level = Rect.subclass('Level', {
                 [1,7, 2,2],
                 [3,6, 1,1],
                 [2,2, 1,1]  ],
-            "this.addWall.apply(this, Y.map(_, '*50'))",
+            "this.addWall.apply(this, Y.map(_, '*REF_SIZE'))",
             this );
         
         P = 
@@ -32,12 +32,13 @@ Level = Rect.subclass('Level', {
         game.addUnit(new Tank(2), 8,1);
     },
     
-    addWall : function addWall(x,y, w,h){
-        var wall = new Level.Wall(x,y, w,h);
+    addWall : function addWall(x,y, w,h, isBoundary){
+        var wall = new Level.Wall(x,y, w,h, isBoundary);
         this.walls.push(wall);
         this.pathmap.addBlocker(wall);
         
-        return this.append( wall.render(this).shape );
+        this.append( wall.render(this).shape );
+        return wall;
     },
     
     drawShape : Y.op.nop
@@ -45,6 +46,7 @@ Level = Rect.subclass('Level', {
 });
 
 Level.Wall = Thing.subclass('Wall', {
+    isBoundary : false,
     blocking : true,
     active   : false,
     
@@ -57,9 +59,10 @@ Level.Wall = Thing.subclass('Wall', {
     },
     
     
-    init : function initWall(x,y, w,h){
+    init : function initWall(x,y, w,h, isBoundary){
         this.width = w;
         this.height = h;
+        this.isBoundary = !!isBoundary;
         Thing.init.call(this);
         this.setLocation(x,y);
     },
@@ -72,7 +75,11 @@ Level.Wall = Thing.subclass('Wall', {
     
     
     render : function render(parent){
-        if (this.shape) this.shape.remove();
+        if (this.isBoundary)
+            return this;
+        
+        if (this.shape)
+            this.shape.remove();
         
         this.shape =
             new Rect(this.width, this.height)
index b57fe9b..51be52e 100644 (file)
@@ -1,7 +1,7 @@
 //  -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
 (function(){
 
-PathMap = new Y.Class('PathMap', QuadTree, {
+PathMap = QuadTree.subclass('PathMap', {
     gridSquareSize  : REF_SIZE,
     gridSquareMidPt : new math.Vec(REF_SIZE/2, REF_SIZE/2),
     
@@ -11,19 +11,23 @@ PathMap = new Y.Class('PathMap', QuadTree, {
         x1 -= 1; y1 -= 1; x2 += 1; y2 += 1;
         QuadTree.init.call(this, x1,y1, x2,y2, capacity);
         this.level = level;
-        this.game = level.game;
-        
-        var w = this.width, h = this.height;
-        this.walls = {
-            top    : new Level.Wall(x1,y1,   w,1),
-            bottom : new Level.Wall(x1,y2-1, w,1),
-            left   : new Level.Wall(x1,y1,   1,h),
-            right  : new Level.Wall(x2-1,y1, 1,h)
+        this.game  = level.game;
+        this.walls = level.walls;
+        
+        this.game.addEventListener('ready', this.setup.bind(this, x1,y1, x2,y2));
+    },
+    
+    setup : function setup(x1,y1, x2,y2){
+        var w = this.width, h = this.height, level = this.level
+        
+        ,   BWs = {
+            top    : [x1,y1,   w,1, true],
+            bottom : [x1,y2-1, w,1, true],
+            left   : [x1,y1,   1,h, true],
+            right  : [x2-1,y1, 1,h, true]
         };
-        Y(this.walls).forEach(function(wall){
-            wall.isBoundary = true;
-            this.addBlocker(wall);
-        }, this);
+        
+        this.boundaryWalls = Y.map(BWs, 'this.level.addWall.apply(this.level, _)', this);
     },
     
     addBlocker : function addBlocker(obj){
@@ -101,7 +105,10 @@ PathMap = new Y.Class('PathMap', QuadTree, {
     },
     
     
-    
+    /**
+     * @protected Creates a pathing grid suitable for A* search.
+     * TODO: Fold A* into this class.
+     */
     grid : function grid(){
         if ( !this._grid ) {
             var size = this.gridSquareSize
@@ -170,18 +177,6 @@ PathMap = new Y.Class('PathMap', QuadTree, {
         return new math.Vec(floor(x)*size, floor(y)*size);
     },
     
-    manhattan: function manhattan(p1, p2) {
-        var d1 = Math.abs(p2.x - p1.x)
-        ,   d2 = Math.abs(p2.y - p1.y) ;
-        return d1 + d2;
-    },
-    
-    manhattan4: function manhattan4(x1,y1, x2,y2) {
-        var d1 = Math.abs(x2 - x1)
-        ,   d2 = Math.abs(y2 - y1) ;
-        return d1 + d2;
-    },
-    
     path : function path(start, end, id){
         var size = this.gridSquareSize, floor = Math.floor
         ,   grid = this.grid()
@@ -263,7 +258,7 @@ PathMap = new Y.Class('PathMap', QuadTree, {
             ctx.fill();
             ctx.closePath();
         }
-        this.drawStep = drawStep;
+        // this.drawStep = drawStep;
         
         ctx.fillStyle = 'rgba(0,0,0,0.1)';
         drawStep( start );
@@ -301,9 +296,10 @@ PathMap = new Y.Class('PathMap', QuadTree, {
     overlay : function overlay(gridEl){
         var w = this.width-2
         ,   h = this.height-2
-        ,   canvas = $('.overlay', gridEl)[0];
+        ,   canvas = $('.overlay', gridEl)[0]
+        ;
         
-        if (!canvas) {
+        if ( !canvas ) {
             canvas = $('<canvas class="overlay" style="position:absolute"/>').prependTo(gridEl)[0];
             $(canvas).width(w).height(h);
             canvas.width = w;
index 344fe1b..6b35eea 100644 (file)
@@ -1,4 +1,4 @@
-Trajectory = new Y.Class('Trajectory', math.Line, {
+Trajectory = math.Line.subclass('Trajectory', {
     halt    : false,
     elapsed : 0,
     bounces : 0,
@@ -19,7 +19,8 @@ Trajectory = new Y.Class('Trajectory', math.Line, {
         var pm = this.pathmap
         ,   ex = (x1 > x2 ? -REF_SIZE : REF_SIZE+pm.width )
         ,   ey = (y1 > y2 ? -REF_SIZE : REF_SIZE+pm.height)
-        ,   edge = this.near(ex,ey);
+        ,   edge = this.near(ex,ey)
+        ;
         
         // Move goal point beyond far wall to avoid rotations
         x2 = this.x2 = edge.x;
@@ -33,6 +34,7 @@ Trajectory = new Y.Class('Trajectory', math.Line, {
     },
     
     // Determine how much time can pass before we risk teleporting
+    // We'll need to reset this whenever the bounding box changes size
     resetBound : function resetBound(){
         var BOUND_SIZE_RATIO = 0.75
         ,   abs = Math.abs
@@ -57,6 +59,7 @@ Trajectory = new Y.Class('Trajectory', math.Line, {
             to = this.stepTo(t, bb);
             if (this.halt) break;
             
+            // FIXME: bb.add?
             bb = new Loc.Rect(to.x,to.y, to.x+bw,to.y+bh);
             
         } while (dt > 0);
@@ -98,18 +101,18 @@ Trajectory = new Y.Class('Trajectory', math.Line, {
             this.reset(to.x,to.y, ng.x,ng.y);
             owner.render(this.game.level);
             
-            false && console.log([
-                '['+TICKS+' ('+this.depth+')] '+owner+' reflected!',
-                '  wanted:  '+_to+' x ('+(_to.x+bb.width)+','+(_to.y+bb.height)+')',
-                '  blocker: '+test.msg,
-                '  old:',
-                '    loc:   '+bb.p1,
-                '    goal:  '+og,
-                '  new:',
-                '    loc:   '+to,
-                '    goal:  '+ng,
-                '  --> trajectory: '+this
-            ].join('\n'));
+            // console.log([
+            //     '['+TICKS+' ('+this.depth+')] '+owner+' reflected!',
+            //     '  wanted:  '+_to+' x ('+(_to.x+bb.width)+','+(_to.y+bb.height)+')',
+            //     '  blocker: '+test.msg,
+            //     '  old:',
+            //     '    loc:   '+bb.p1,
+            //     '    goal:  '+og,
+            //     '  new:',
+            //     '    loc:   '+to,
+            //     '    goal:  '+ng,
+            //     '  --> trajectory: '+this
+            // ].join('\n'));
         }
         
         return to;
index 4e19d45..1d3dc9b 100644 (file)
@@ -8,17 +8,18 @@ Bullet = Thing.subclass('Bullet', {
      * @param {math.Line} trajectory
      */
     init : function initBullet(owner, x2,y2){
-        var self = this;
         this.owner = owner;
-        this.game = owner.game;
+        this.game  = owner.game;
+        
         Thing.init.call(this, owner.align);
         
         var loc = owner.getTurretLoc()
         ,   x1  = loc.x, y1  = loc.y;
+        
         this.setLocation(x1,y1);
         this.trajectory = new Trajectory(this, x1,y1, x2,y2, this.stats.move*REF_SIZE/1000);
         
-        this.addEventListener('collide', self.onCollide.bind(this));
+        this.addEventListener('collide', this.onCollide.bind(this));
     },
     
     
@@ -28,8 +29,8 @@ Bullet = Thing.subclass('Bullet', {
     
     offsetX : -3,
     offsetY : -3,
-    width   : 6,
-    height  : 6,
+    width   :  6,
+    height  :  6,
     
     stats : {
         move : 2.0 // move speed (squares/sec)
index 28034db..1948e5f 100644 (file)
@@ -30,6 +30,7 @@ Tank = Thing.subclass('Tank', {
     currentMoveLimit : -1,
     
     
+    
     init : function init(align){
         Thing.init.call(this, align);
         this.onBulletDeath = this.onBulletDeath.bind(this);
@@ -40,36 +41,14 @@ Tank = Thing.subclass('Tank', {
         });
     },
     
-    ableToShoot : function ableToShoot(){
-        return this.nShots < this.stats.shots && this.cooldowns.attack.ready;
-    },
+    onBulletDeath : function onBulletDeath(evt){ this.nShots--; },
+    
     
-    findClosest : function findClosest(agents){
-        if (!agents || !agents.size())
-            return null;
-        
-        var manhattan = this.game.pathmap.manhattan
-        ,   bb = this.boundingBox, mid = this.midpoint
-        
-        agents.sort(function(a,b){
-            return Y.op.cmp(
-                manhattan(a.loc,mid),
-                manhattan(b.loc,mid) );
-        });
-        
-        return agents.attr(0);
-    },
     
-    willCollide : function willCollide(bullets, wiggle){
-        wiggle = wiggle || 0;
-        var bb = this.boundingBox, mid = this.midpoint
-        ,   w = (bb.width+wiggle)/2, h = (bb.height+wiggle)/2
-        ;
-        return bullets.filter(function(b){ return !b.dead && b.trajectory.isWithin(mid, w,h); });
-    },
     
     act : function act(){
         
+        // Check to see if we should obey our last decision, and not recalc
         if ( this.forceCurrentMove && this.forceCurrentMove() && this.currentMoveLimit > NOW ) {
             this.continueMove();
             return this;
@@ -83,7 +62,7 @@ Tank = Thing.subclass('Tank', {
             // Try to shoot down nearby bullets
             var bs = this.willCollide( this.findNearLike(16, Y.is(Bullet)) );
             if ( bs.size() ) {
-                var b = this.findClosest(bs);
+                var b = this.closestOf(bs);
                 
                 // console.log('Incoming! Shoot it down!', b);
                 this.shoot(b.loc.x, b.loc.y);
@@ -93,7 +72,7 @@ Tank = Thing.subclass('Tank', {
             // Dodge incoming bullet
             var bs = this.willCollide( this.findNearLike(50, Y.is(Bullet)), 10 );
             if ( bs.size() ) {
-                var b   = this.findClosest(bs), bs = [b]
+                var b   = this.closestOf(bs), bs = [b]
                 ,   mid = this.midpoint
                 ,   trj = b.trajectory.tangent(mid)
                 
@@ -102,7 +81,7 @@ Tank = Thing.subclass('Tank', {
                 ,   y   = (mid.y > h/2 ? h : 0)
                 ,   to  = this.currentMove = trj.near(x,y);
                 
-                this.forceCurrentMove = function(){ return this.willCollide(bs, 10); };
+                this.forceCurrentMove = this.willCollide.bind(this, bs, 10);
                 this.currentMoveLimit = NOW + 750;
                 
                 this.move(to.x, to.y);
@@ -110,7 +89,7 @@ Tank = Thing.subclass('Tank', {
             }
             
             // Try to blow up nearby tanks
-            var t = this.findNearLike(66, 'Y.is(Tank, _) && _.align !== this.align').shift();
+            var t = this.findNearEnemies(66).shift();
             if (t) {
                 // console.log('I gotcha!', t);
                 this.shoot(t.loc.x, t.loc.y);
@@ -125,9 +104,39 @@ Tank = Thing.subclass('Tank', {
         return this;
     },
     
+    ableToShoot : function ableToShoot(){
+        return this.nShots < this.stats.shots && this.cooldowns.attack.ready;
+    },
+    
+    closestOf : function closestOf(agents){
+        if (!agents || !agents.size())
+            return null;
+        
+        var manhattan = math.Vec.manhattan
+        ,   bb = this.boundingBox, mid = this.midpoint
+        ;
+        agents.sort(function(a,b){
+            return Y.op.cmp(
+                manhattan(a.loc,mid),
+                manhattan(b.loc,mid) );
+        });
+        
+        return agents.attr(0);
+    },
+    
+    willCollide : function willCollide(bullets, wiggle){
+        wiggle = wiggle || 0;
+        var bb = this.boundingBox, mid = this.midpoint
+        ,   w = (bb.width+wiggle)/2, h = (bb.height+wiggle)/2
+        ;
+        return bullets.filter(function(b){
+            return !b.dead && b.willComeWithin(mid, w,h);
+        });
+    },
+    
     continueMove : function continueMove(){
         if ( !this.currentMove || this.currentMoveLimit <= NOW ){
-            var t = this.findNearLike(10000, 'Y.is(Tank, _) && _.align !== this.align').shift();
+            var t = this.findNearEnemies(10000).shift();
             this.calculatePath(t.midpoint);
         }
         
@@ -189,18 +198,22 @@ Tank = Thing.subclass('Tank', {
     },
     
     findNearLike : function findNearLike(ticks, fn){
-        fn = fn.toFunction();
+        fn = (fn || Y.op.K(true)).toFunction();
         var bMovePerTick = MS_PER_FRAME * Bullet.prototype.stats.move*REF_SIZE/1000
         ,   within = bMovePerTick*ticks
         ,   bb = this.boundingBox
         ,   x1 = bb.x1 - within, y1 = bb.y1 - within
         ,   x2 = bb.x2 + within, y2 = bb.y2 + within
         ;
-        return this.game.pathmap.get(x1,y1, x2,y2).filter(fn || Y.op.K(true), this);
+        return this.game.pathmap.get(x1,y1, x2,y2).filter(fn, this);
+    },
+    
+    findNearEnemies : function findNearEnemies(ticks){
+        return this.findNearLike(ticks, 'Y.is(Tank, _) && _.align !== this.align');
     },
     
     shoot : function shoot(x,y){
-        var WIGGLE = 4
+        var WIGGLE = 4 // additional space which must be clear in front of the barrel
         ,   xydef = (x !== undefined && y !== undefined)
         ;
         
@@ -239,29 +252,34 @@ Tank = Thing.subclass('Tank', {
         return p;
     },
     
-    onBulletDeath : function onBulletDeath(evt){
-        this.nShots--;
-    },
-    
     getTurretLoc : function getTurretLoc(){
         var loc    = this.loc
-        ,   barrel = this.barrel, bb = barrel.boundingBox
-        ,   theta  = barrel.transform.rotate, sin = Math.sin(theta), cos = Math.cos(theta)
-        ,   x0 = 3+bb.x2-bb.x1,   y0 = (bb.y2-bb.y1)/2
-        ,   x = loc.x + bb.x1 + x0*cos - y0*sin
-        ,   y = loc.y + bb.y1 + x0*sin + y0*cos
+        ,   barrel = this.barrel, bbb = barrel.boundingBox
+        
+        ,   theta  = barrel.transform.rotate
+        ,   sin = Math.sin(theta),  cos = Math.cos(theta)
+        
+        ,   x0 = 3+bbb.x2-bbb.x1,   y0 = (bbb.y2-bbb.y1)/2
+        
+        ,   x = loc.x + bbb.x1 + x0*cos - y0*sin
+        ,   y = loc.y + bbb.y1 + x0*sin + y0*cos
         ;
-        // console.log('getTurretLoc()', 'loc:', loc, 'bb.(x2,y2):', [bb.x2,bb.y2], '(x,y):', [x,y]);
+        // console.log('getTurretLoc()', 'loc:', loc, 'bbb.(x2,y2):', [bbb.x2,bbb.y2], '(x,y):', [x,y]);
         return new math.Vec(x,y);
     },
     
     move : function move(x,y){
         var bb = this.boundingBox
         ,   w2 = bb.width/2, h2 = bb.height/2
+        ,   x2 = x-w2,  y2 = y-h2
         ;
-        return this.moveByAngle( this.angleTo(x,y), x-w2,y-h2 );
+        this.trajectory = new Trajectory(this, bb.x1,bb.y1, x2,y2, this.stats.move*REF_SIZE/1000);
+        return this.moveByAngle( this.angleTo(x,y), x2,y2 );
     },
     
+    /**
+     * @protected This method does not update this.trajectory -- call this.move(x,y) instead.
+     */
     moveByAngle : function moveByAngle(theta, targetX,targetY){
         var abs = Math.abs
         ,   bb = this.boundingBox, w2 = bb.width/2, h2 = bb.height/2
index 7cf9418..4fcd2ab 100644 (file)
@@ -30,6 +30,7 @@ Thing = new Evt.Class('Thing', {
     blocking : true, // Whether the agent obstructs pathing
     active   : true, // Whether the agent takes actions
     
+    // Location
     loc : null,
     boundingBox : null,
     
@@ -42,7 +43,7 @@ Thing = new Evt.Class('Thing', {
     width  : REF_SIZE*0.7,
     height : REF_SIZE*0.6,
     
-    set : Y.op.set.methodize(),
+    set  : Y.op.set.methodize(),
     attr : Y.op.attr.methodize(),
     
     
@@ -122,6 +123,24 @@ Thing = new Evt.Class('Thing', {
         return Math.atan2(y0,x0);
     },
     
+    willComeWithin : function willComeWithin(pt, w,h){
+        if ( !(this.trajectory && this.midpoint) )
+            return false;
+        
+        if ( !Y.isNumber(w) ){
+            h = w.height; w = w.width;
+        }
+        var trj = this.trajectory
+        ,   cur = this.midpoint
+        ,   fx = trj.calcX(pt.y),       fy = trj.calcY(pt.x)
+        ,   t  = trj.iparametric(cur.x, cur.y)
+        ,   ft = trj.iparametric(fx,fy)
+        ,   dw = Math.abs(fx - pt.x),   dh = Math.abs(fy - pt.y)
+        ;
+        return (  t.x <= ft.x && t.y <= ft.y
+               && ( dw <= w || dh <= h )  );
+    },
+    
     
     
     // *** Gameplay Methods *** //
@@ -134,9 +153,6 @@ Thing = new Evt.Class('Thing', {
     },
     
     
-    
-    
-    
     /// Rendering Methods ///
     
     /**
@@ -164,9 +180,8 @@ Y(Thing).extend({
     THING_ID : 0,
     
     fillStats : function fillStats(stats){
-        var st = Y(stats)
-        ,   stats = st.clone().end()
-        ;
+        var st    = Y(stats)
+        ,   stats = st.clone().end() ;
         
         st.forEach(function(v, k){
             k = Y(k);