Checkpoint on collision refactor to support zones.
authordsc <david.schoonover@gmail.com>
Sat, 18 Dec 2010 17:20:48 +0000 (09:20 -0800)
committerdsc <david.schoonover@gmail.com>
Sat, 18 Dec 2010 17:20:48 +0000 (09:20 -0800)
40 files changed:
src/Y/index.cjs
src/Y/modules/y.config.cjs
src/Y/modules/y.event.cjs
src/Y/modules/y.scaffold.cjs
src/Y/type.cjs
src/Y/types/function.cjs
src/Y/y.cjs
src/evt.cjs
src/ezl/layer.cjs
src/ezl/loc/boundingbox.cjs
src/ezl/loop/eventloop.cjs
src/ezl/math/line.cjs
src/ezl/shape/circle.cjs
src/ezl/shape/line.cjs
src/ezl/shape/polygon.cjs
src/ezl/shape/rect.cjs
src/ezl/util/configuration.cjs [deleted file]
src/ezl/widget/cooldown.cjs
src/tanks/abilities/buff.cjs [new file with mode: 0644]
src/tanks/abilities/index.cjs [new file with mode: 0644]
src/tanks/config.cjs
src/tanks/game.cjs
src/tanks/index.js
src/tanks/map/level.cjs
src/tanks/map/pathmap.cjs
src/tanks/map/trajectory.cjs
src/tanks/map/traversal.cjs [new file with mode: 0644]
src/tanks/map/wall.cjs
src/tanks/thing/bullet.cjs
src/tanks/thing/index.cjs
src/tanks/thing/item.cjs [new file with mode: 0644]
src/tanks/thing/player.cjs
src/tanks/thing/tank.cjs
src/tanks/thing/thing.cjs
src/tanks/ui/configui.cjs
src/tanks/ui/grid.cjs
src/tanks/ui/index.cjs
src/tanks/ui/main.cjs
src/tanks/ui/pathmapui.cjs [new file with mode: 0644]
www/deps.html

index dd9f2b5..cddbcd4 100644 (file)
@@ -50,11 +50,12 @@ addNames('Class subclass instantiate fabricate YBase', require('Y/class'));
 
 
 /// Now start assembling the normal sub-modules ///
-addNames('YCollection',      require('Y/types/collection'));
-addNames('YArray',           require('Y/types/array'));
-addNames('YObject deepcopy', require('Y/types/object'));
-addNames('YString',          require('Y/types/string'));
-addNames('YNumber range',    require('Y/types/number'));
+addNames('YCollection',   require('Y/types/collection'));
+addNames('YArray',        require('Y/types/array'));
+addNames('YObject getNested setNested deepcopy',
+                          require('Y/types/object'));
+addNames('YString',       require('Y/types/string'));
+addNames('YNumber range', require('Y/types/number'));
 
 var utils = require('Y/utils');
 Y['bindAll'] = utils.bindAll;
index 91a694c..f812fde 100644 (file)
@@ -1,5 +1,5 @@
 var Y = require('Y').Y
-,   getNested = require('Y/types/object').getNested
+,   getNested = Y.getNested
 ,   Emitter   = require('Y/modules/y.event').Emitter
 ,
 
@@ -142,7 +142,7 @@ Y.YObject.subclass('Config', function(Config){
          */
         updateOnChange : function updateOnChange(events, obj){
             if ( !Y.isArray(events) )
-                events = events.split();
+                events = events.split(/\s+/);
             events = events.map(function events(key){
                 obj[key.split('.').pop()] = this.get(key);
                 if ( !Y(key).startsWith('set:') )
index 7421fb8..64859d9 100644 (file)
@@ -37,7 +37,7 @@ Y.YObject.subclass('Event', {
     addEventListener : function addEventListener(evts, fn){
         fn = fn.toFunction();
         if ( !Y.isArray(evts) )
-            evts = evts.split();
+            evts = evts.split(/\s+/);
         evts.forEach(function addEvent(evt){
             this.getQueue(evt).push(fn);
         }, this);
@@ -57,7 +57,11 @@ Y.YObject.subclass('Event', {
         return evt;
     },
     
-    // XXX: does not handle degenerate or recursive event dispatch
+    /**
+     * Dispatches the given event to all listeners in the appropriate queue.
+     * Listeners are invoked with the event, and with context set to the target.
+     * XXX: does not handle degenerate or recursive event dispatch
+     */
     dispatchEvent : function dispatchEvent(evt){
         this.getQueue(evt.type).invoke('call', evt.target, evt);
         if (this.parent) this.parent.fire(evt.type, evt.trigger, evt.data);
index 9a499df..717b7c9 100644 (file)
@@ -37,6 +37,14 @@ type2el = {
 ,
 
 
+// addTypeSupport =
+// exports['addTypeSupport'] =
+// function addTypeSupport(type, parser, builder){
+//     var T = Y.typeName(type);
+//     type2parser[T] = parser;
+// }
+// ,
+
 
 Field =
 exports['Field'] =
@@ -52,7 +60,7 @@ Y.subclass('Field', {
         this.key   = chain.split('.').pop();
         this.label = camelToSpaces(this.key);
         
-        var T = Y(Y.type(val)).getName();
+        var T = Y.typeName(def);
         this.cast = options.cast || type2parser[T];
         this.type = options.type || type2el[T];
         
@@ -133,8 +141,12 @@ function create(config, el){
         },
         function createField(group, value, chain){
             var def = config.getDefault(chain)
-            ,   field = new Field(chain, def, value);
+            ,   T = Y.typeName(def);
+            
+            if (!type2parser[T])
+                return group;
             
+            var field = new Field(chain, def, value);
             fields[chain] = field;
             group.append(field.el);
             config.addEventListener('set:'+chain, function onConfigSet(evt){
index 18c7cb8..bed850b 100644 (file)
@@ -28,46 +28,53 @@ var class2name =
     "Boolean Number String Function Array Date RegExp Object"
         .split(" ")
         .reduce(function(class2name, name) {
-            class2name[ "[object "+name+"]" ] = name.toLowerCase();
+            class2name[ "[object "+name+"]" ] = name;
             return class2name;
         }, {});
 
-function type_of(obj){
-    return obj == null ?
-        String( obj ) :
-        class2name[ toString.call(obj) ] || "object";
+function basicTypeName(o){
+    return o == null ?
+        String(o) :
+        class2name[ toString.call(o) ] || "Object";
+}
+
+function typeName(o){
+    if (o === undefined || o === null)
+        return String(o);
+    var T = type(o);
+    return  T.className || (T !== Function ? T.name : basicTypeName(o));
 }
 
 var arrayLike = isArray.types = [];
-function isArray(obj)    { return type_of(obj) === "array" || (arrayLike.indexOf(type(obj)) !== -1); }
-function isFunction(obj) { return type_of(obj) === "function"; }
-function isString(obj)   { return type_of(obj) === "string"; }
-function isNumber(obj)   { return type_of(obj) === "number"; }
-function isWindow(obj)   { return obj && typeof obj === "object" && "setInterval" in obj; }
+function isArray(o)    { return basicTypeName(o) === "Array" || (arrayLike.indexOf(type(o)) !== -1); }
+function isFunction(o) { return basicTypeName(o) === "Function"; }
+function isString(o)   { return basicTypeName(o) === "String"; }
+function isNumber(o)   { return basicTypeName(o) === "Number"; }
+function isWindow(o)   { return o && typeof o === "object" && "setInterval" in o; }
 
-function isPlainObject( obj ){
+function isPlainObject(o){
     // Must be an Object.
     // Because of IE, we also have to check the presence of the constructor property.
     // Make sure that DOM nodes and window objects don't pass through, as well
-    if ( !obj || type_of(obj) !== "object" || obj.nodeType || isWindow(obj) )
+    if ( !o || basicTypeName(o) !== "Object" || o.nodeType || isWindow(o) )
         return false;
     
     // Not own constructor property must be Object
-    if ( obj[FN] &&
-        !hasOwn.call(obj, FN) &&
-        !hasOwn.call(obj[FN][PT], "isPrototypeOf") )
+    if ( o[FN] &&
+        !hasOwn.call(o, FN) &&
+        !hasOwn.call(o[FN][PT], "isPrototypeOf") )
             return false;
     
     
     // Own properties are enumerated firstly, so to speed up,
     // if last one is own, then all properties are own.
     var key;
-    for ( key in obj ) {}
+    for ( key in o ) {}
     
-    return key === undefined || hasOwn.call( obj, key );
+    return key === undefined || hasOwn.call( o, key );
 }
 
-function type( o ) {
+function type(o) {
     if ( o === null || o === undefined )
         return o;
     
@@ -95,7 +102,7 @@ function type( o ) {
     }
 }
 
-function is( A, B ){
+function is(A, B){
     if ( isArray(B) )
         return B.some( is.bind(this,A) );
     else {
@@ -106,7 +113,8 @@ function is( A, B ){
 
 exports['is']            = is;
 exports['type']          = type;
-exports['type_of']       = type_of;
+exports['basicTypeName'] = basicTypeName;
+exports['typeName']      = typeName;
 
 exports['isArray']       = isArray;
 exports['isFunction']    = isFunction;
@@ -115,5 +123,5 @@ exports['isNumber']      = isNumber;
 exports['isWindow']      = isWindow;
 exports['isPlainObject'] = isPlainObject;
 
-type['KNOWN_CLASSES'] = KNOWN_CLASSES;
+type['KNOWN_CLASSES']    = KNOWN_CLASSES;
 
index b332334..e11e31b 100644 (file)
@@ -284,4 +284,4 @@ function getName( fn ){
 
 // Export these last to avoid methodizing them
 exports['YFunction']  = YF(YF);
-exports._ = _;
+exports['_'] = _;
index 57bb49d..5a22487 100644 (file)
@@ -71,8 +71,7 @@ function Y(o){
     }
     
     // Do we have a type-specific wrapper?
-    var name  = type.type_of(o)
-    ,   yname = 'Y' + name.charAt(0).toUpperCase() + name.slice(1)
+    var yname = 'Y' + type.basicTypeName(o)
     ,   YType = Y[yname]
     ;
     
index 7981385..61462cf 100644 (file)
@@ -39,12 +39,19 @@ var Y       = require('Y').Y
 
 ,   objToString   = _Object[P].toString
 ,   classToString = function toString(){ return this.className+"()"; }
-;
+,
+
 
-// Private delegating constructor -- must be defined for every
-// new class to prevent shared state. All construction is
-// actually done in the init method.
-exports['ConstructorTemplate'] = ConstructorTemplate;
+/**
+ * @private
+ * Private delegating constructor. This function must be redefined for every
+ * new class to prevent shared state. All construction is actually done in
+ * the methods initialise and init.
+ * 
+ * Fires `create` event prior to initialisation if not subclassing.
+ */
+ConstructorTemplate =
+exports['ConstructorTemplate'] =
 function ConstructorTemplate() {
     var cls = arguments.callee
     ,   instance = this;
@@ -64,7 +71,39 @@ function ConstructorTemplate() {
     
     return instance;
 }
+,
 
+/**
+ * Fires `init` event after calling init(). Note that events are fired
+ * for proper instances of the class and instances of subclasses (but
+ * not for the instance created when subclassing).
+ */
+createInitialise =
+exports['createInitialise'] =
+function createInitialise(NewClass){
+    function initialise(){
+        var instance = this
+        ,   init     = NewClass.prototype.init
+        ;
+        
+        instance.__emitter__ = new Emitter(instance, NewClass);
+        
+        if (init) {
+            var result = init.apply(instance, arguments);
+            if (result) instance = result;
+        }
+        
+        instance.fire('init', instance, {
+            'instance' : instance,
+            'cls'      : NewClass,
+            'args'     : Y(arguments)
+        });
+        
+        return instance;
+    }
+    return Y(initialise);
+}
+;
 
 
 
@@ -156,28 +195,7 @@ function Class(className, Parent, members){
     NewClass.__super__    = SuperClass; // don't override NewClass.constructor -- it should be Function
     prototype.constructor = prototype.__class__ = NewClass;
     
-    NewClass.init = // XXX: This means subclasses will fire events.
-    prototype.initialise =
-        Y(function initialise(){
-            var instance = this
-            ,   init     = NewClass.prototype.init
-            ;
-            
-            instance.__emitter__ = new Emitter(instance, NewClass);
-            
-            if (init) {
-                var result = init.apply(instance, arguments);
-                if (result) instance = result;
-            }
-            
-            instance.fire('init', instance, {
-                'instance' : instance,
-                'cls'      : NewClass,
-                'args'     : Y(arguments)
-            });
-            
-            return instance;
-        });
+    NewClass.init = prototype.initialise = createInitialise(NewClass);
     
     // Add class emitter
     var ParentEmitter = (Parent.__emitter__ ? Parent : ClassFactory)
index 6857efb..21f150f 100644 (file)
@@ -21,7 +21,6 @@ Y.subclass('Layer', {
     _cssClasses : 'ezl layer',
     
     canvas     : null,
-    
     parent     : null,
     children   : null,
     
@@ -43,10 +42,14 @@ Y.subclass('Layer', {
     // Transforms
     _origin : null, // rotational origin
     transform : null, // Object
-    useCanvasScaling : false, // default to CSS3 scaling
     
     /// Defaults ///
     
+    useCanvasScaling   : false, // Default to CSS3 scaling
+    alwaysClearDrawing : true,  // Whether to clear the canvas content before redraw
+    alwaysClearAttrs   : false, // Whether to remove all canvas attributes (CONTEXT_ATTRS)
+                                // and transforms (scale, rotate, translate) and reset defaults before redraw
+    
     originX : 0,
     originY : 0,
     
@@ -129,7 +132,7 @@ Y.subclass('Layer', {
      */
     empty : function empty(ctx){
         this.children.invoke('remove');
-        this.clear();
+        this.clear(ctx);
         return this;
     },
     
@@ -469,48 +472,55 @@ Y.subclass('Layer', {
      * @param {Boolean} [force=false] Forces redraw.
      */
     draw : function draw(ctx, force){
+        var _ctx = ctx || this.ctx;
+        
         if ( this.dirty || force ){
-            var _ctx = ctx || this.ctx;
+            this.dirty = false;
             this._openPath(_ctx);
-            this.drawShape(_ctx);
+            this.render(_ctx);
             this._closePath(_ctx);
         }
         
-        this.dirty = false;
         this.children.invoke('draw', ctx, force);
         return this;
     },
     
     _openPath : function _openPath(ctx){
         var self = this
-        ,   alwaysClear = this.alwaysClear
         ,   neg = this.negBleed
         ,   t = this.transform
         ,   w = this.canvasWidth, h = this.canvasHeight ;
         
         ctx.beginPath();
-        ctx.setTransform(1,0,0,1,0,0);
-        ctx.clearRect(-w,-h, 2*w,2*h);
         
+        // TODO: only alwaysClearAttrs should reset transforms?
+        // if (this.alwaysClearAttrs)
+            ctx.setTransform(1,0,0,1,0,0);
+        
+        if (this.alwaysClearDrawing)
+            ctx.clearRect(-w,-h, 2*w,2*h);
+        
+        // if (this.alwaysClearAttrs) {
         if (this.useCanvasScaling && (t.scale.x !== 1 || t.scale.y !== 1))
             ctx.scale(t.scale.x,t.scale.y);
         
         ctx.translate(neg.x, neg.y);
         ctx.translate(t.translate.x, t.translate.y);
+        // }
         
         // Set context attributes
         CONTEXT_ATTRS.forEach(function(name){
-            if (self[name] !== undefined)
-                ctx[name] = self[name];
-            else if (alwaysClear)
+            if (this[name] !== undefined)
+                ctx[name] = this[name];
+            else if (this.alwaysClearAttrs)
                 delete ctx[name];
-        });
+        }, this);
         
         return this;
     },
     
     /** To be implemented by subclasses. */
-    drawShape : function drawShape(ctx){ return this; },
+    render : function render(ctx){ return this; },
     
     _closePath : function _closePath(ctx){
         ctx.closePath();
@@ -518,9 +528,9 @@ Y.subclass('Layer', {
     },
     
     /**
-     * Clears this layer and all children.
+     * Clears this layer and optionally all children.
      */
-    clear : function clear(ctx){
+    clear : function clear(ctx, clearChildren){
         var w = this.canvas.width()
         ,   h = this.canvas.height();
         ctx = ctx || this.ctx;
@@ -528,7 +538,8 @@ Y.subclass('Layer', {
         ctx.setTransform(1,0,0,1,0,0);
         ctx.clearRect(-w,-h, 2*w,2*h);
         ctx.closePath();
-        this.children.invoke('clear');
+        if (clearChildren)
+            this.children.invoke('clear');
         return this;
     },
     
index 5de767d..973757f 100644 (file)
@@ -41,6 +41,7 @@ Rect.subclass('BoundingBox', {
     get origin(){ return this._origin; },
     
     relocate : function relocate(x,y){
+        if (x instanceof Array) { y=x[1]; x=x[0]; }
         var _x1 = this[X1], _y1 = this[Y1]
         ,   _x2 = this[X2], _y2 = this[Y2]
         
@@ -55,11 +56,11 @@ Rect.subclass('BoundingBox', {
     },
     
     relocated : function relocated(x,y){
-        var bb = this.clone();
-        return bb.relocate(x,y);
+        return this.clone().relocate(x,y);
     },
     
     resize : function resize(w,h){
+        if (w instanceof Array) { h=w[1]; w=w[0]; }
         var x1 = this[X1], y1 = this[Y1]
         ,   x2 = this[X2], y2 = this[Y2]
         ,   wOld = x2-x1, hOld = y2-y1
@@ -87,13 +88,28 @@ Rect.subclass('BoundingBox', {
     },
     
     resized : function resized(w,h){
-        var bb = this.clone();
-        return bb.resize(w,h);
+        return this.clone().resize(w,h);
     },
     
     clone : function clone(){
         var o = this._origin;
         return new BoundingBox(this[X1],this[Y1], this[X2],this[Y2], o[X1], o[Y1]);
+    },
+    
+    
+    get topSide(){    return new Line(this[X1],this[Y1], this[X2],this[Y1]); },
+    get bottomSide(){ return new Line(this[X1],this[Y2], this[X2],this[Y2]); },
+    get leftSide(){   return new Line(this[X1],this[Y1], this[X1],this[Y2]); },
+    get rightSide(){  return new Line(this[X2],this[Y1], this[X2],this[Y2]); },
+    
+    side : function side(which){
+        switch (which) {
+            case 'top'    : return this.topSide;
+            case 'bottom' : return this.bottomSide;
+            case 'left'   : return this.leftSide;
+            case 'right'  : return this.rightSide;
+            default : throw new Error('Unknown side: '+which);
+        }
     }
     
 });
index d744531..c93e2e5 100644 (file)
@@ -11,8 +11,8 @@ var Y = require('Y').Y
 EventLoop =
 exports['EventLoop'] =
 Emitter.subclass('EventLoop', {
-    samples   : NUM_SAMPLES, // Number of frames to track for effective fps
-    dilation  : 1.0,
+    samples      : NUM_SAMPLES, // Number of frames to track for effective fps
+    timeDilation : 1.0,
     
     framerate : 0, // Target framerate
     frametime : 0, // 1000 / framerate
@@ -46,7 +46,7 @@ Emitter.subclass('EventLoop', {
         if (samples !== undefined)
             this.samples = samples;
         if (dilation !== undefined)
-            this.dilation = dilation;
+            this.timeDilation = dilation;
         
         this.reset();
     },
@@ -140,3 +140,4 @@ function decorate(delegate){
     return delegate;
 }
 
+
index bdf54ee..1daa9c8 100644 (file)
@@ -54,14 +54,34 @@ Vec.subclass('Line', {
         return this;
     },
     
+    /**
+     * @return {Vec} Delta-(x,y) on this line after given elapsed parametric time.
+     */
+    wouldMove : function wouldMove(t){
+        return new Vec( t*this.pa, t*this.pb );
+    },
+    
+    /**
+     * @return {Float} Elapsed parametric time needed to move the given delta-(x,y).
+     */
+    timeToMove : function timeToMove(dx,dy){
+        // see note at iparametric
+        return (dx/this.pa + dy/this.pb) * 0.5;
+    },
+    
     parametric : function parametric(t){
+        // return this.wouldMove(t).add(this.p1);
         return new Vec( this.x1 + t*this.pa ,
                         this.y1 + t*this.pb );
     },
     
     iparametric : function iparametric(x,y){
-        return new Vec( (x-this.x1)/this.pa ,
-                        (y-this.y1)/this.pa );
+        var tx = (x - this.x1)/this.pa
+        ,   ty = (y - this.y1)/this.pb;
+        // tx and ty should be the same number provided (x,y) is on the line
+        // we average to provide a reasonable answer when that's not true,
+        // rather than failing outright or requiring the user to deal with a Vec.
+        return (tx + ty) * 0.5;
     },
     
     pointAtX : function pointAtX(x){ return new Vec(x, this.calcY(x)); },
index e02eab7..733148a 100644 (file)
@@ -25,7 +25,7 @@ Shape.subclass('Circle', {
         return this;
     },
     
-    drawShape : function drawShape(ctx){
+    render : function render(ctx){
         var r = this._radius;
         ctx.arc(r,r, r, 0, Math.PI*2, false);
         ctx.fill();
index 34bc660..e007377 100644 (file)
@@ -80,7 +80,7 @@ Shape.subclass('Line', {
         return this;
     },
     
-    drawShape : function drawShape(ctx){
+    render : function render(ctx){
         this._fixSize();
         var x1,y1, x2,y2
         ,   line = this.line, p1 = line.p1, p2 = line.p2
@@ -112,7 +112,7 @@ Shape.subclass('Line', {
             ctx.closePath();
         // } catch(e) {
         //     if (window.console) {
-        //         console.error(this+'.drawShape()');
+        //         console.error(this+'.render()');
         //         console.log('  points:', x1,y1, ' ', x2,y2);
         //         console.log('  bounds:', minW,minH, ' ', maxW,maxH);
         //         console.log('  ::', this, line);
index b0308b8..8601c16 100644 (file)
@@ -43,7 +43,7 @@ Shape.subclass('Polygon', {
         return values.map(function(v, i){ return (self[which+i] = v); });
     },
     
-    drawShape : function drawShape(ctx){
+    render : function render(ctx){
         this.points.forEach(function(loc, i){
             ctx.lineTo(loc.x, loc.y);
         });
index 845688d..d88a45c 100644 (file)
@@ -13,9 +13,10 @@ Shape.subclass('Rect', {
         this.size(w,h);
     },
     
-    drawShape : function drawShape(ctx){
+    render : function render(ctx){
         ctx.rect(0,0, this.canvasWidth,this.canvasHeight);
         ctx.fill();
+        ctx.stroke();
     },
     
     toString : function(){
diff --git a/src/ezl/util/configuration.cjs b/src/ezl/util/configuration.cjs
deleted file mode 100644 (file)
index 539bd0a..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-var Y = require('Y').Y
-,   getNested = require('Y/types/object').getNested
-,   Emitter = require('Y/modules/y.event').Emitter
-,
-
-
-Config =
-exports['Config'] =
-Y.YObject.subclass('Config', function(Config){
-    
-    Y.core.extend(this, {
-        
-        init : function initConfig(defaults){
-            this._defaults = defaults || {};
-            this._o = Y({}, this._defaults);
-            this.__emitter__ = new Emitter(this);
-        },
-        
-        clone : function clone(){
-            var c = new Config();
-            c._defaults = this._defaults;
-            c._o = c._o.clone();
-            return c;
-        },
-        
-        set : function set(key, value, def){
-            if ( key !== undefined ){
-                var meta = this.ensure(key).getNested(key, def, true)
-                ,   old = meta.value ;
-                def   = (def   === undefined ? old : def);
-                value = (value === undefined ? def : value);
-                
-                meta.obj[meta.key] = value;
-                this._fireMutation('set', key, old, value, meta.obj);
-            }
-            return this;
-        },
-        
-        remove : function remove(key){
-            if ( key !== undefined ){
-                var sentinel = {}
-                ,   meta = this.getNested(key, sentinel, true)
-                ,   old = (meta.value === sentinel ? undefined : old);
-                
-                if ( meta.obj ) {
-                    delete meta.obj[meta.key];
-                    this._fireMutation('remove', key, old, undefined, meta.obj);
-                }
-            }
-            return this;
-        },
-        
-        /**
-         * @private
-         */
-        _fireMutation : function _fireMutation(evt, key, oldval, newval, obj){
-            if (newval !== oldval){
-                var data = {
-                    'key'    : key,
-                    'oldval' : oldval,
-                    'newval' : newval,
-                    'obj'    : obj,
-                    'root'   : this
-                };
-                this.fire(evt+':'+key, this, data);
-                this.fire(evt, this, data);
-            }
-            return this;
-        },
-        
-        /**
-         * Recursively iterates the hierarchy of the Config, depth-first.
-         */
-        reduce : function reduce(fn, acc, context){
-            context = context || this;
-            
-            var ret = Y.reduce(this._o, this._reducer, {
-                'acc'  : acc,
-                'path' : new Y.YArray(),
-                'cxt'  : context
-            }, this);
-            
-            return ret.acc;
-        },
-        
-        /**
-         * @private
-         * XXX: Not going to call the iterator on root objects. ok?
-         * XXX: Always passing the Config object as obj to iterator. ok?
-         */
-        _reducer : function _reducer(state, v, k, o){
-            var chain = state.path.push(k).join('.');
-            
-            // Nested object -- recurse
-            if ( Y.isPlainObject(v) )
-                state = Y.reduce(v, this._reducer, state, this);
-                
-            // Normal value -- invoke iterator
-            else
-                state.acc = fn.call(state.cxt, state.acc, v, chain, this);
-            
-            state.path.pop();
-            return state;
-        },
-        
-        getDefault : function getDefault(k, def){
-            return getNested(this._defaults, k, def);
-        }
-        
-    });
-    
-    this['get'] = this.getNested;
-    
-});
index dba3caa..c7f492a 100644 (file)
@@ -36,8 +36,8 @@ Layer.subclass('CooldownGauge', function setupCooldownGauge(CooldownGauge){
         return Layer.fn.draw.apply(this, arguments);
     };
     
-    this['drawShape'] =
-    function drawShape(ctx){
+    this['render'] =
+    function render(ctx){
         var cool = this.cooldown;
         
         if (cool.ready)
diff --git a/src/tanks/abilities/buff.cjs b/src/tanks/abilities/buff.cjs
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/tanks/abilities/index.cjs b/src/tanks/abilities/index.cjs
new file mode 100644 (file)
index 0000000..e69de29
index 03a5308..06ec09f 100644 (file)
@@ -1,6 +1,7 @@
 //  -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
-var Y = require('Y').Y
+var Y      = require('Y').Y
 ,   Config = require('Y/modules/y.config').Config
+,   Vec    = require('ezl/math').Vec
 ,
 
 defaults =
@@ -16,11 +17,22 @@ exports['defaults'] = {
         showAttackCooldown : false,
         showCountdown      : (document.location.host.toString() !== 'tanks.woo')
     },
-    pathing : { 
-        overlayAiPaths     : false,
-        overlayPathmap     : false,
-        traceTrajectories  : false
+    pathing : {
+        gridSquare        : REF_SIZE,
+        gridSquareMid     : new Vec(REF_SIZE/2, REF_SIZE/2),
+        overlayAiPaths    : false,
+        overlayPathmap    : false,
+        traceTrajectories : false
     }
-};
+}
+,
+
+config =
+exports['config'] = new Config(defaults)
+;
+
+config.addEventListener('set:pathing.gridSquare', function(evt){
+    var sq = evt.data.newval;
+    config.set('pathing.gridSquareMid', new Vec(sq/2, sq/2));
+});
 
-exports['values'] = new Config(defaults);
index 135f487..3c5ac9f 100644 (file)
@@ -6,10 +6,11 @@ var Y         = require('Y').Y
 ,   EventLoop = require('ezl/loop').EventLoop
 ,   Circle    = require('ezl/shape').Circle
 
-,   config = require('tanks/config').values
-,   map    = require('tanks/map')
-,   thing  = require('tanks/thing')
-,   Grid   = require('tanks/ui/grid').Grid
+,   config    = require('tanks/config').config
+,   map       = require('tanks/map')
+,   thing     = require('tanks/thing')
+,   Grid      = require('tanks/ui/grid').Grid
+,   PathMapUI = require('tanks/ui/pathmapui').PathMapUI
 
 ,   Level  = map.Level
 ,   Thing  = thing.Thing, Tank = thing.Tank, Bullet = thing.Bullet
@@ -19,11 +20,12 @@ var Y         = require('Y').Y
 Game =
 exports['Game'] = 
 Y.subclass('Game', {
-    overlayPathmap : null,
-    overlayAiPaths : null,
-    gameoverDelay  : null,
+    // Config
+    gameoverDelay : null,
+    
+    // Defaults
+    gameover : false,
     
-    gameover      : false,
     
     
     init : function initGame(viewport){
@@ -45,10 +47,13 @@ Y.subclass('Game', {
         this.level =
             new Level(this, COLUMNS*REF_SIZE, ROWS*REF_SIZE)
                 .appendTo(this.root);
-        this.pathmap = this.level.pathmap;
         
-        Thing.addEventListener('init',    this.addUnit);
-        Thing.addEventListener('destroy', this.killUnit);
+        var pathmap = this.pathmap = this.level.pathmap;
+        pathmap.ui = new PathMapUI(this, pathmap);
+        this.level.append(pathmap.ui);
+        
+        Thing.addEventListener('init',    this.addThing);
+        Thing.addEventListener('destroy', this.killThing);
         
         this.addEventListener('tick', this.tick);
         
@@ -56,8 +61,8 @@ Y.subclass('Game', {
     },
     
     destroy : function destroy(){
-        Thing.removeEventListener('init',    this.addUnit);
-        Thing.removeEventListener('destroy', this.killUnit);
+        Thing.removeEventListener('init',    this.addThing);
+        Thing.removeEventListener('destroy', this.killThing);
         this.stop();
         this.resetGlobals();
     },
@@ -72,10 +77,6 @@ Y.subclass('Game', {
     
     draw : function draw(){
         this.root.draw();
-        
-        this.pathmap.removeOverlay(this.viewport);
-        if (this.overlayPathmap)
-            this.pathmap.overlay(this.viewport);
     },
     
     /**
@@ -91,9 +92,6 @@ Y.subclass('Game', {
         SECONDTH = ELAPSED / 1000;
         SQUARETH = REF_SIZE * SECONDTH
         
-        if (!this.overlayAiPaths)
-            this.pathmap.hidePaths();
-        
         this.active.invoke('updateCooldowns', ELAPSED, NOW);
         this.active.invoke('act');
         this.animations = this.animations.filter(this.tickAnimations, this);
@@ -133,7 +131,7 @@ Y.subclass('Game', {
     
     // *** Agent Management *** //
     
-    addUnit : function addUnit(unit, col,row){
+    addThing : function addThing(unit, col,row){
         if (unit instanceof Event)
             unit = unit.trigger;
         
@@ -165,7 +163,7 @@ Y.subclass('Game', {
         return unit;
     },
     
-    killUnit : function killUnit(unit){
+    killThing : function killThing(unit){
         if (unit instanceof Event)
             unit = unit.trigger;
         
@@ -181,7 +179,7 @@ Y.subclass('Game', {
         return unit;
     },
     
-    moveUnitTo : function moveUnitTo(agent, x,y){
+    moveThingTo : function moveThingTo(agent, x,y){
         this.pathmap.removeBlocker(agent);
         agent.position(x,y);
         this.pathmap.addBlocker(agent);
@@ -228,7 +226,5 @@ Y.subclass('Game', {
     
 });
 
-config.updateOnChange(
-    ['pathing.overlayPathmap', 'pathing.overlayAiPaths', 'game.gameoverDelay'], 
-    Game.fn);
+config.updateOnChange('game.gameoverDelay', Game.fn);
 
index 1083c58..455172d 100644 (file)
@@ -9,7 +9,7 @@ require('tanks/globals');
 
 // exports.tanks =
 tanks = {
-    'config' : require('tanks/config'),
+    'config' : require('tanks/config').config,
     'ui'     : require('tanks/ui'),
     'Game'   : require('tanks/game').Game,
     
index ac86997..a3cc320 100644 (file)
@@ -2,6 +2,7 @@ var Y          = require('Y').Y
 ,   Rect       = require('ezl/shape').Rect
 ,   Thing      = require('tanks/thing/thing').Thing
 ,   Tank       = require('tanks/thing/tank').Tank
+,   Item       = require('tanks/thing/item').Item
 ,   PlayerTank = require('tanks/thing/player').PlayerTank
 ,   PathMap    = require('tanks/map/pathmap').PathMap
 ,   Wall       = require('tanks/map/wall').Wall
@@ -37,13 +38,15 @@ Rect.subclass('Level', {
             this );
         
         P = 
-        game.player = game.addUnit(new PlayerTank(1), 5,9);
-        // game.addUnit(new Tank(1).colors('#4596FF', '#182B53', '#F25522'), 3,9);
+        game.player = game.addThing(new PlayerTank(1), 5,9);
+        // game.addThing(new Tank(1).colors('#4596FF', '#182B53', '#F25522'), 3,9);
         
         E = 
-        game.addUnit(new Tank(2), 0,1);
-        game.addUnit(new Tank(2), 1,0);
-        // game.addUnit(new Tank(2), 8,1);
+        game.addThing(new Tank(2), 0,1);
+        game.addThing(new Tank(2), 1,0);
+        // game.addThing(new Tank(2), 8,1);
+        
+        I = game.addThing(new Item(), 8,8);
     },
     
     addWall : function addWall(x,y, w,h, isBoundary){
@@ -57,6 +60,6 @@ Rect.subclass('Level', {
         return wall;
     },
     
-    drawShape : Y.op.nop
+    render : Y.op.nop
     
 });
\ No newline at end of file
index 71abcfe..b1957a9 100644 (file)
@@ -3,13 +3,14 @@ var Y        = require('Y').Y
 ,   math     = require('ezl/math')
 ,   QuadTree = require('ezl/util/tree/quadtree').QuadTree
 ,   astar    = require('ezl/util/astar')
+,   config   = require('tanks/config').config
 ,   Bullet   = require('tanks/thing/bullet').Bullet
-,   config   = require('tanks/config').values
 ,   Vec      = math.Vec
 ,   Line     = math.Line
 
-,   GRID_SQUARE_SIZE = REF_SIZE
-,   GRID_SQUARE_MID_PT = new Vec(REF_SIZE/2, REF_SIZE/2)
+,   PASSABLE = 1 // Does not obstruct other objects
+,   BLOCKING = 2 // Objstructs other blockers
+,   ZONE     = 3 // Does not obstruct other objects, but still collides with them
 ,
 
 
@@ -18,8 +19,8 @@ PathMap =
 exports['PathMap'] =
 QuadTree.subclass('PathMap', {
     // Config
-    overlayAiPaths : null,
-    overlayPathmap : null,
+    gridSquare    : null,
+    gridSquareMid : null,
     
     
     
@@ -114,22 +115,22 @@ QuadTree.subclass('PathMap', {
             var B = blocker.boundingBox;
             if (bb.x2 <= B.x1 && x2 >= B.x1) {
                 msg += 'left';
-                side = new Line(B.x1,B.y1, B.x1,B.y2);
+                side = B.leftSide;
                 to   = trj.pointAtX(B.x1-offX-1);
                 
             } else if (bb.x1 >= B.x2 && x1 <= B.x2) {
                 msg += 'right';
-                side = new Line(B.x2,B.y1, B.x2,B.y2);
+                side = B.rightSide;
                 to   = trj.pointAtX(B.x2+ro.x+1);
                 
             } else if (bb.y2 <= B.y1 && y2 >= B.y1) {
                 msg += 'top';
-                side = new Line(B.x1,B.y1, B.x2,B.y1);
+                side = B.topSide;
                 to   = trj.pointAtY(B.y1-offY-1);
                 
             } else if (bb.y1 >= B.y2 && y1 <= B.y2) {
                 msg += 'bottom';
-                side = new Line(B.x1,B.y2, B.x2,B.y2);
+                side = B.bottomSide;
                 to   = trj.pointAtY(B.y2+ro.y+1);
             }
             
@@ -146,7 +147,7 @@ QuadTree.subclass('PathMap', {
      */
     grid : function grid(){
         if ( !this._grid ) {
-            var size = GRID_SQUARE_SIZE
+            var size = this.gridSquare
             ,   floor = Math.floor, ceil = Math.ceil
             ,   cols = ceil((this.width-2) /size)
             ,   rows = ceil((this.height-2)/size)
@@ -195,25 +196,30 @@ QuadTree.subclass('PathMap', {
     },
     
     vec2Square : function vec2Square(x,y){
-        if (x instanceof Array){
-            y = x.y;
-            x = x.x;
-        }
-        var floor = Math.floor, size = GRID_SQUARE_SIZE;
+        if (x instanceof Array){ y = x.y; x = x.x; }
+        var floor = Math.floor, size = this.gridSquare;
         return new Vec(floor(x/size), floor(y/size));
     },
     
     square2Vec : function square2Vec(x,y){
-        if (x instanceof Array){
-            y = x.y;
-            x = x.x;
-        }
-        var floor = Math.floor, size = GRID_SQUARE_SIZE;
+        if (x instanceof Array){ y = x.y; x = x.x; }
+        var floor = Math.floor, size = this.gridSquare;
         return new Vec(floor(x)*size, floor(y)*size);
     },
     
-    path : function path(start, end, id){
-        var size = GRID_SQUARE_SIZE, floor = Math.floor
+    path : function path(start, end, agent){
+        return this.gridPath(start, end, agent)
+                .invoke('scale', this.gridSquare)
+                .invoke('add', this.gridSquareMid)
+                .end();
+    },
+    
+    /**
+     * @protected
+     * Generates grid-sized path from start to end.
+     */
+    gridPath : function gridPath(start, end, agent){
+        var size = this.gridSquare, floor = Math.floor
         ,   grid = this.grid()
         
         ,   startX = floor(start.x/size)
@@ -223,169 +229,12 @@ QuadTree.subclass('PathMap', {
         ,   endX   = floor(end.x/size)
         ,   endY   = floor(end.y/size)
         ,   endN   = grid[endX][endY]
-        
-        ,   path = Y(astar.search(grid, startN, endN))
         ;
-        
-        if (this.overlayAiPaths)
-            this.drawPath(id, startN, path);
-        
-        return path
-                .invoke('scale', size)
-                .invoke('add', GRID_SQUARE_MID_PT)
-                .end();
-    },
-    
-    drawPath : function drawPath(id, start, path){
-        var size   = GRID_SQUARE_SIZE, off
-        ,   w      = this.width-2,  h = this.height-2
-        ,   el     = this.game.viewport
-        ,   grid   = this.grid()
-        ,   canvas = $('#path'+id, el)[0]
-        ;
-        
-        if (!canvas) {
-            canvas = $('<canvas id="path'+id+'" class="path" style="position:absolute"/>').appendTo(el)[0];
-            $(canvas).width(w).height(h);
-            canvas.width  = w;
-            canvas.height = h;
-        }
-        
-        var ctx = canvas.getContext('2d');
-        ctx.lineWidth = 0;
-        
-        // Clear the canvas
-        ctx.beginPath();
-        ctx.clearRect(this.x,this.y, w,h);
-        ctx.closePath();
-        
-        
-        // Draw blockers
-        // var s = size/2;
-        // ctx.fillStyle = 'rgba(255,0,0,0.2)';
-        // 
-        // Y(grid).invoke('forEach', function(node){
-        //     if ( !node.blocked ) return;
-        //     
-        //     var x = node.x*size + s
-        //     ,   y = node.y*size + s ;
-        //     
-        //     ctx.beginPath();
-        //     ctx.moveTo( x,   y-s );
-        //     ctx.lineTo( x+s, y   );
-        //     ctx.lineTo( x,   y+s );
-        //     ctx.lineTo( x-s, y   );
-        //     ctx.fill();
-        //     ctx.closePath();
-        // });
-        
-        
-        // Draw path
-        off = size*0.3;
-        
-        function drawStep(p){
-            var r = size/2 - off
-            ,   x = p.x*size + off + r
-            ,   y = p.y*size + off + r ;
-            
-            ctx.beginPath();
-            ctx.arc(x,y, r, 0, Math.PI*2, false);
-            ctx.fill();
-            ctx.closePath();
-        }
-        // this.drawStep = drawStep;
-        
-        ctx.fillStyle = 'rgba(0,0,0,0.1)';
-        drawStep( start );
-        path.forEach(drawStep);
-        
-        
-        // Draw start
-        off = size*0.15;
-        
-        ctx.fillStyle = 'rgba(0,255,0,0.05)';
-        drawStep( start );
-        
-        // Draw finish
-        ctx.fillStyle = 'rgba(0,0,255,0.05)';
-        drawStep( path.last() );
-        
-        $(canvas).show();
-    },
-    
-    destroyPath : function destroyPath(id){
-        $('#path'+id, this.game.viewport).remove();
-    },
-    
-    hidePath : function hidePath(id){
-        $('#path'+id, this.game.viewport).hide();
-    },
-    
-    hidePaths : function hidePaths(){
-        $('.path', this.game.viewport).hide();
-    },
-    
-    
-    
-    _overlayBG : $('<img src="img/pathmap-bg.png" />')[0],
-    overlay : function overlay(gridEl){
-        var w = this.width-2
-        ,   h = this.height-2
-        ,   canvas = $('.overlay', gridEl)[0]
-        ;
-        
-        if ( !canvas ) {
-            canvas = $('<canvas class="overlay" style="position:absolute"/>').prependTo(gridEl)[0];
-            $(canvas).width(w).height(h);
-            canvas.width = w;
-            canvas.height = h;
-        }
-        
-        var ctx = canvas.getContext('2d');
-        // ctx.scale(SCALE, SCALE);
-        
-        // Clear the canvas
-        ctx.beginPath();
-        ctx.clearRect(this.x,this.y, w,h);
-        ctx.closePath();
-        
-        // ctx.fillStyle   = ctx.createPattern(this._overlayBG, 'repeat');
-        ctx.fillStyle   = 'rgba(255,255,255,0.1)';
-        ctx.lineWidth   = 1;
-        ctx.strokeStyle = 'rgba(255,255,255,0.2)';
-        
-        
-        // Draw regions
-        this.reduce(function(acc, v, r, tree){
-            if ( acc[r.id] || v.isBoundary )
-                return acc;
-            
-            acc[r.id] = r;
-            
-            ctx.beginPath();
-            // ctx.globalAlpha = 0.1;
-            ctx.rect(r.x1,r.y1, r.width,r.height);
-            ctx.fill();
-            // ctx.globalAlpha = 1;
-            ctx.stroke();
-            ctx.closePath();
-            
-            return acc;
-        }, {});
-        
-        $(canvas).show();
-    },
-    
-    removeOverlay : function removeOverlay(gridEl){
-        $('.overlay', gridEl).hide();
+        return Y(astar.search(grid, startN, endN))
     }
     
-    
 });
 
-config.updateOnChange(
-    ['pathing.overlayPathmap', 'pathing.overlayAiPaths'],
-    PathMap.fn);
 
 'set remove removeAll clear'
     .split(' ')
@@ -396,3 +245,7 @@ config.updateOnChange(
         };
     });
 
+config.updateOnChange(
+    ['pathing.gridSquare', 'pathing.gridSquareMid'],
+    PathMap.fn);
+
index edace1b..67d014c 100644 (file)
@@ -1,12 +1,16 @@
 var Y     = require('Y').Y
+,   op    = require('Y/op')
 ,   math  = require('ezl/math')
 ,   Vec   = math.Vec
 ,   Line  = math.Line
 ,   Rect  = math.Rect
 ,   BoundingBox = require('ezl/loc').BoundingBox
 ,   Thing = require('tanks/thing/thing').Thing
+
+,   BOUND_SIZE_RATIO = 0.75
 ,
 
+
 Trajectory =
 exports['Trajectory'] =
 Line.subclass('Trajectory', {
@@ -35,13 +39,14 @@ Line.subclass('Trajectory', {
         // init with raw numbers to do calculations
         Line.init.call(this, x1,y1, x2,y2, tdist || this.tdist);
         
+        // Find appropriate edge in direction of 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)
         ;
         
-        // Move goal point beyond far wall to avoid rotations
+        // Move goal point beyond far wall to avoid rotations post-reflection
         x2 = this.x2 = edge.x;
         y2 = this.y2 = edge.y;
         this.p2 = new Vec(x2,y2);
@@ -55,14 +60,17 @@ Line.subclass('Trajectory', {
     // 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
+        var p = BOUND_SIZE_RATIO
         ,   abs = Math.abs
         ,   w = this.owner.width, h = this.owner.height;
-        this.tBound = Math.min( abs(w / this.pa) * BOUND_SIZE_RATIO,
-                                abs(h / this.pb) * BOUND_SIZE_RATIO  );
+        this.tBound = Math.min(abs(w/this.pa) * p, abs(h/this.pb) * p);
         return this;
     },
     
+    clone : function clone(){
+        return new Trajectory(this.owner, this.x1,this.y1, this.x2,this.y2, this.tdist);
+    },
+    
     
     intersects : function intersects(x,y){
         var o = x;
@@ -72,7 +80,7 @@ Line.subclass('Trajectory', {
         if (o instanceof Rect)
             return o.intersects(this);
         
-        return Line.prototype.intersects.call(this, x,y);
+        return Line.fn.intersects.call(this, x,y);
     },
     
     
@@ -83,34 +91,26 @@ Line.subclass('Trajectory', {
      * @return -1 if a closer b, 1 if a further b, 0 if a same as b
      */
     compare : function compare(a, b){
-        if (a instanceof Thing) a = a.midpoint;
-        if (b instanceof Thing) b = b.midpoint;
+        if (a instanceof Thing) a = a.loc;
+        if (b instanceof Thing) b = b.loc;
         
         var abs = Math.abs
-        // ,   cur = this.owner.midpoint
-        // ,   t   = this.iparametric(cur.x, cur.y)
         ,   t   = this.elapsed
         
         ,   xa  = this.calcX(a.y), ya = this.calcY(a.x)
-        ,   ta  = this.iparametric(xa,ya)
-        ,   da  = (ta.x + ta.y)/2 - t
-        // ,   da  = (ta.x - t.x + ta.y - t.y)/2
-        // ,   dxa = ta.x - t.x,   dya = ta.y - t.y
+        ,   ta  = this.iparametric(xa,ya) - t
         
         ,   xb  = this.calcX(b.y), yb = this.calcY(b.x)
-        ,   tb  = this.iparametric(xb,yb)
-        ,   db  = (tb.x + tb.y)/2 - t
-        // ,   db  = (tb.x - t.x + tb.y - t.y)/2
-        // ,   dxb = tb.x - t.x,   dyb = tb.y - t.y
+        ,   tb  = this.iparametric(xb,yb) - t
         ;
         
         // If one has passed, return the other
-        if ( da < 0 && db >= 0 )
+        if ( ta < 0 && tb >= 0 )
             return 1;
-        if ( db < 0 && da >= 0 )
+        if ( tb < 0 && ta >= 0 )
             return -1;
         
-        return Y.op.cmp(abs(da), abs(db));
+        return op.cmp(abs(ta), abs(tb));
     },
     
     closer : function closer(o1, o2){
@@ -122,10 +122,10 @@ Line.subclass('Trajectory', {
     },
     
     comesWithin : function comesWithin(pt, w,h){
-        if ( !this.owner.midpoint )
+        if ( !this.owner.loc )
             return false;
         
-        if (pt instanceof Thing) pt = pt.midpoint;
+        if (pt instanceof Thing) pt = pt.loc;
         
         if ( w === undefined ){
             w = 0; h = 0;
@@ -133,14 +133,13 @@ Line.subclass('Trajectory', {
             h = w.height; w = w.width;
         }
         
-        var cur = this.owner.midpoint
+        var cur = this.owner.loc
         ,   fx = this.calcX(pt.y),       fy = this.calcY(pt.x)
         ,   t  = this.iparametric(cur.x, cur.y)
         ,   ft = this.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 ) );
+        return ( t <= ft && (dw <= w || dh <= h) );
     },
     
     pathBlocked : function pathBlocked(obj, ignore){
@@ -158,78 +157,78 @@ Line.subclass('Trajectory', {
     },
     
     
-    step : function step(dt){
-        this.halt = false;
-        
-        var _dt = dt = (dt === undefined ? ELAPSED : dt)
-        ,   _bb = bb = this.owner.boundingBox, bw = bb.width, bh = bb.height
-        ,   to, t, r;
-        
-        do {
-            t = Math.min(this.tBound, dt);
-            dt -= t;
-            this.elapsed += t;
-            
-            to = this.stepTo(t, bb);
-            if (this.halt) break;
-            
-            bb = new BoundingBox(to.x,to.y, to.x+bw,to.y+bh);
-            
-        } while (dt > 0);
-        
-        return to;
-    },
-    
-    stepTo : function stepTo(t, bb){
-        var ng, og = this.p2, owner = this.owner
-        ,   to = this.parametric(this.elapsed), _to = to
-        ,   test = this.pathmap.moveBlocked(owner, this, to, bb)
-        ;
-        
-        // Blocked! Reflect trajectory
-        if ( test ) {
-            to = test.to;
-            this.bounces++;
-            
-            var blocker = test.blockers[0];
-            owner.fire('collide', blocker);
-            blocker.fire('collide', owner);
-            
-            // Potentially set by responders to collision event
-            if (this.halt) return to;
-            
-            if (!test.side) {
-                console.error('Null reflection line!', 'to:', to, 'blockers:', test.blockers);
-                return to;
-            }
-            
-            if ( test.blockers.length > 1 ) {
-                to = bb.p1;
-                ng = this.p1; // XXX: recalculate?
-                console.log('corner!', this, 'to:',to, 'ng:',ng);
-            } else {
-                ng = math.reflect(this.p2, test.side);
-            }
-            
-            this.reset(to.x,to.y, ng.x,ng.y);
-            owner.render(this.game.level);
-            
-            // 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;
-    },
+    // step : function step(dt){
+    //     this.halt = false;
+    //     
+    //     var _dt = dt = (dt === undefined ? ELAPSED : dt)
+    //     ,   _bb = bb = this.owner.boundingBox, bw = bb.width, bh = bb.height
+    //     ,   to, t, r;
+    //     
+    //     do {
+    //         t = Math.min(this.tBound, dt);
+    //         dt -= t;
+    //         this.elapsed += t;
+    //         
+    //         to = this.stepTo(t, bb);
+    //         if (this.halt) break;
+    //         
+    //         bb = new BoundingBox(to.x,to.y, to.x+bw,to.y+bh);
+    //         
+    //     } while (dt > 0);
+    //     
+    //     return to;
+    // },
+    // 
+    // stepTo : function stepTo(t, bb){
+    //     var ng, og = this.p2, owner = this.owner
+    //     ,   to = this.parametric(this.elapsed), _to = to
+    //     ,   test = this.pathmap.moveBlocked(owner, this, to, bb)
+    //     ;
+    //     
+    //     // Blocked! Reflect trajectory
+    //     if ( test ) {
+    //         to = test.to;
+    //         this.bounces++;
+    //         
+    //         var blocker = test.blockers[0];
+    //         owner.fire('collide', blocker);
+    //         blocker.fire('collide', owner);
+    //         
+    //         // Potentially set by responders to collision event
+    //         if (this.halt) return to;
+    //         
+    //         if (!test.side) {
+    //             console.error('Null reflection line!', 'to:', to, 'blockers:', test.blockers);
+    //             return to;
+    //         }
+    //         
+    //         if ( test.blockers.length > 1 ) {
+    //             to = bb.p1;
+    //             ng = this.p1; // XXX: recalculate?
+    //             console.log('corner!', this, 'to:',to, 'ng:',ng);
+    //         } else {
+    //             ng = math.reflect(this.p2, test.side);
+    //         }
+    //         
+    //         this.reset(to.x,to.y, ng.x,ng.y);
+    //         owner.render(this.game.level);
+    //         
+    //         // 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;
+    // },
     
     toString : function toString(){
         return 'T['+this.p1+', '+this.p2+', slope='+this.slope.toFixed(3)+']';
diff --git a/src/tanks/map/traversal.cjs b/src/tanks/map/traversal.cjs
new file mode 100644 (file)
index 0000000..bf95699
--- /dev/null
@@ -0,0 +1,95 @@
+var Y = require('Y').Y
+,
+
+Traversal =
+exports['Traversal'] =
+Y.subclass('Traversal', {
+    elapsed : 0,
+    halt : false,
+    
+    isBlocked : false,
+    
+    
+    
+    init : function initTraversal(thing, trajectory){
+        this.thing      = thing;
+        this.pathmap    = thing.pathmap;
+        this.trajectory = trajectory || thing.trajectory;
+    },
+    
+    step : function step(dt){
+        this.halt = false;
+        
+        var traj  = this.trajectory
+        ,   thing = this.thing
+        ,   bb = thing.boundingBox.clone()
+        ,   to, t;
+        
+        do {
+            t = Math.min(traj.tBound, dt);
+            dt -= t;
+            this.elapsed += t;
+            
+            to = this.stepTo(t, bb);
+            if (this.halt) break;
+            
+            bb = bb.relocate(to);
+            
+        } while (dt > 0);
+        
+        return to;
+    },
+    
+    stepTo : function stepTo(t, bb){
+        var ng, traj = this.trajectory, og = traj.p2, thing = this.thing
+        ,   to = traj.parametric(this.elapsed), _to = to
+        ,   test = this.pathmap.moveBlocked(thing, this, to, bb)
+        ;
+        
+        // Blocked! Reflect trajectory
+        if ( test ) {
+            to = test.to;
+            this.bounces++;
+            
+            var blocker = test.blockers[0];
+            thing.fire('collide', blocker);
+            blocker.fire('collide', thing);
+            
+            // Potentially set by responders to collision event
+            if (this.halt) return to;
+            
+            if (!test.side) {
+                console.error('Null reflection line!', 'to:', to, 'blockers:', test.blockers);
+                return to;
+            }
+            
+            if ( test.blockers.length > 1 ) {
+                to = bb.p1;
+                ng = this.p1; // XXX: recalculate?
+                console.log('corner!', this, 'to:',to, 'ng:',ng);
+            } else {
+                ng = math.reflect(this.p2, test.side);
+            }
+            
+            this.reset(to.x,to.y, ng.x,ng.y);
+            thing.render(this.game.level);
+            
+            // console.log([
+            //     '['+TICKS+' ('+this.depth+')] '+thing+' 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 8e3f3a3..03c5b3f 100644 (file)
@@ -1,6 +1,7 @@
-var Y = require('Y').Y
-,   Rect       = require('ezl/shape').Rect
-,   Thing      = require('tanks/thing/thing').Thing
+var Y     = require('Y').Y
+,   op    = require('Y/op')
+,   Rect  = require('ezl/shape').Rect
+,   Thing = require('tanks/thing/thing').Thing
 ,
 
 
@@ -32,10 +33,10 @@ Thing.subclass('Wall', {
     },
     
     // inactive
-    createCooldowns : Y.op.nop,
+    createCooldowns : op.nop,
     
     // indestructable
-    dealDamage : Y.op.nop,
+    dealDamage : op.nop,
     
     
     render : function render(parent){
index a6ed869..8377ef6 100644 (file)
@@ -1,16 +1,21 @@
 //  -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
 var Y          = require('Y').Y
+,   op         = require('Y/op')
 ,   math       = require('ezl/math')
 ,   shape      = require('ezl/shape')
+,   config     = require('tanks/config').config
+,   thing      = require('tanks/thing/thing')
 ,   Wall       = require('tanks/map/wall').Wall
-,   Thing      = require('tanks/thing/thing').Thing
 ,   Trajectory = require('tanks/map/trajectory').Trajectory
 ,   Explosion  = require('tanks/fx/explosion').Explosion
-,   config     = require('tanks/config').values
+
+,   fillStats  = thing.fillStats
+,   Thing      = thing.Thing
 ,   Line       = shape.Line
 ,   Circle     = shape.Circle
 ,
 
+
 Bullet =
 exports['Bullet'] =
 Thing.subclass('Bullet', {
@@ -57,10 +62,8 @@ Thing.subclass('Bullet', {
         move : 2.0 // move speed (squares/sec)
     },
     
-    fillStats : function(){
-        this.stats = Y({},
-            Thing.fillStats(this.owner.stats),
-            Thing.fillStats(this.stats) );
+    createStats : function createStats(){
+        this.stats = Y({}, fillStats(this.owner.stats), fillStats(this.stats) );
     },
     
     remove : function remove(){
@@ -69,8 +72,8 @@ Thing.subclass('Bullet', {
         return this;
     },
     
-    createCooldowns : Y.op.nop,
-    updateCooldowns : Y.op.nop,
+    createCooldowns : op.nop,
+    updateCooldowns : op.nop,
     
     setTarget : function setTarget(x,y){
         var loc = this.loc
@@ -88,7 +91,7 @@ Thing.subclass('Bullet', {
         // We test twice because the process of calculating the trajectory
         // could result in a collision, killing the bullet
         if (!this.dead) var to = this.trajectory.step( ELAPSED );
-        if (!this.dead) this.game.moveUnitTo(this, to.x, to.y);
+        if (!this.dead) this.game.moveThingTo(this, to.x, to.y);
         return this;
     },
     
index 43a7d85..93d9be7 100644 (file)
@@ -2,4 +2,5 @@ exports['Thing']      = require('tanks/thing/thing').Thing;
 exports['Bullet']     = require('tanks/thing/bullet').Bullet;
 exports['Tank']       = require('tanks/thing/tank').Tank;
 exports['PlayerTank'] = require('tanks/thing/player').PlayerTank;
+exports['Item']       = require('tanks/thing/item').Item;
 // exports['CustomTank'] = require('tanks/thing/customtank').CustomTank;
diff --git a/src/tanks/thing/item.cjs b/src/tanks/thing/item.cjs
new file mode 100644 (file)
index 0000000..ea4b167
--- /dev/null
@@ -0,0 +1,58 @@
+var Y     = require('Y').Y
+,   op    = require('Y/op')
+,   Thing = require('tanks/thing/thing').Thing
+,   shape = require('ezl/shape')
+,   Rect  = shape.Rect
+
+,   SIZE = REF_SIZE * 0.5
+,
+
+
+Item =
+exports['Item'] =
+Thing.subclass('Item', {
+    blocking : true,
+    active   : false,
+    
+    width  : SIZE,
+    height : SIZE,
+    
+    stats : { hp:1, move:0, power:0 },
+    
+    // inactive
+    createCooldowns : op.nop,
+    updateCooldowns : op.nop,
+    act : op.nop,
+    
+    // indestructable
+    dealDamage : op.nop,
+    
+    
+    init : function initItem(){
+        Thing.init.call(this, 0);
+        
+        this.addEventListener('collide', this.onCollide.bind(this));
+    },
+    
+    onCollide : function onCollide(evt){
+        console.log('collide!', this);
+        this.destroy();
+    },
+    
+    render : function render(parent){
+        this.remove();
+        
+        var loc = this.loc;
+        this.shape = new Rect(this.width, this.height)
+            .origin('50%', '50%')
+            .position(loc.x, loc.y)
+            .fill('#83BB32')
+            .stroke('#1C625B', 2.0)
+            .appendTo( parent );
+        this.shape.layer.attr('title', ''+loc);
+        
+        return this;
+    }
+    
+})
+;
index f7768ca..b50713d 100644 (file)
@@ -106,7 +106,7 @@ Tank.subclass('PlayerTank', {
     //     ;
     //     
     //     if ( !blockers.size() )
-    //         this.game.moveUnitTo(this, x,y);
+    //         this.game.moveThingTo(this, x,y);
     // },
     
     
index 2ab69e1..1b33591 100644 (file)
@@ -1,4 +1,5 @@
 var Y             = require('Y').Y
+,   op            = require('Y/op')
 ,   Thing         = require('tanks/thing/thing').Thing
 ,   Bullet        = require('tanks/thing/bullet').Bullet
 ,   Trajectory    = require('tanks/map/trajectory').Trajectory
@@ -31,6 +32,7 @@ Thing.subclass('Tank', function(Tank){
         width  : REF_SIZE*0.55,
         height : REF_SIZE*0.55,
         
+        
         // Attributes
         stats : {
             hp        : 1,          // health
@@ -49,6 +51,8 @@ Thing.subclass('Tank', function(Tank){
             shootEnemy    : 0.75    // shoot at enemy tank if in range
         },
         
+        buffs : null,
+        
         nShots : 0,
         currentMove : null,
         forceCurrentMove : false,
@@ -61,13 +65,8 @@ Thing.subclass('Tank', function(Tank){
         Thing.init.call(this, align);
         this.coolgauge = new CooldownGauge(this.cooldowns.attack, this.width+1,this.height+1);
         this.onBulletDeath = this.onBulletDeath.bind(this);
-        this.addEventListener('destroy', destroyPath);
     };
     
-    function destroyPath(){
-        this.game.pathmap.destroyPath(this.id);
-    }
-    
     this['onBulletDeath'] =
     function onBulletDeath(evt){ this.nShots--; };
     
@@ -175,13 +174,13 @@ Thing.subclass('Tank', function(Tank){
     };
     
     this['findNearEnemies'] =
-    function findNearEnemies(ticks, needLineOfSight){
+    function findNearEnemies(ticks, needLineOfSight){ // TODO: Split off LOS version
         return this.findNearLike(ticks, function nearEnemyFilter(agent){
-            var am = agent.midpoint;
+            var aLoc = agent.loc;
             return ( agent.align !== this.align
-                  && Y.is(Tank, agent)
+                  && (agent instanceof Tank)
                   && !(needLineOfSight 
-                        && new Trajectory(this,this.midpoint,am).pathBlocked(agent))
+                        && new Trajectory(this,this.loc,aLoc).pathBlocked(agent))
                 );
         });
     };
@@ -192,12 +191,13 @@ Thing.subclass('Tank', function(Tank){
             return null;
         
         var manhattan = Vec.manhattan
-        ,   bb = this.boundingBox, mid = this.midpoint ;
+        ,   cmp = op.cmp
+        ,   loc = this.loc ;
         
         agents.sort(function(a,b){
-            return Y.op.cmp(
-                manhattan(a.midpoint,mid),
-                manhattan(b.midpoint,mid) ); // FIXME: midpoint-to-midpoint is wrong -- should use pt on closest boundary-side of object
+            return cmp(
+                manhattan(a.midpoint,loc),
+                manhattan(b.midpoint,loc) );
         });
         
         return agents.attr(0);
@@ -274,7 +274,7 @@ Thing.subclass('Tank', function(Tank){
         this.nShots++;
         p.addEventListener('destroy', this.onBulletDeath);
         
-        this.game.addUnit(p).render(this.game.level);
+        this.game.addThing(p).render(this.game.level);
         return p;
     };
     
@@ -313,7 +313,7 @@ Thing.subclass('Tank', function(Tank){
         ,   blockers = this.game.pathmap.get(nbb.x1,nbb.y1, nbb.x2,nbb.y2).remove(this);
         
         if ( !blockers.size() )
-            this.game.moveUnitTo(this, x,y);
+            this.game.moveThingTo(this, x,y);
         
         return this;
     };
@@ -348,9 +348,9 @@ Thing.subclass('Tank', function(Tank){
         
         // console.log(this, 'moving toward', t);
         var pm    = this.game.pathmap
-        ,   start = this.midpoint
+        ,   start = this.loc
         
-        ,   path  = this.lastPath    = pm.path(start, end, this.id)
+        ,   path  = this.lastPath    = pm.path(start, end, this)
         ,   to    = this.currentMove = path.shift()
         ;
     };
index 3931c44..4eba9dd 100644 (file)
@@ -1,13 +1,33 @@
 //#ensure "evt"
-
 var Y           = require('Y').Y
+,   op          = require('Y/op')
 ,   evt         = require('evt')
-
 ,   Loc         = require('ezl/loc/loc').Loc
 ,   BoundingBox = require('ezl/loc/boundingbox').BoundingBox
 ,   Cooldown    = require('ezl/loop').Cooldown
+,   config      = require('tanks/config').config
+
+,   THING_ID = 0
+,
 
-,   config      = require('tanks/config').values
+fillStats =
+exports['fillStats'] =
+function fillStats(stats){
+    return Y.reduce(stats, function(_stats, v, k){
+        k = Y(k);
+        var k_    = k.rtrim('_max')
+        ,   k_max = k+'_max'
+        ;
+        if ( k.endsWith('_max') ) {
+            if ( _stats[k_] === undefined )
+                _stats[k_] = v;
+            
+        } else if ( _stats[k_max] === undefined )
+            _stats[k_max] = v;
+        
+        return _stats;
+    }, Y.extend({}, stats));
+}
 ,
 
 
@@ -21,7 +41,7 @@ new evt.Class('Thing', {
     stats: {
         hp        : 1,          // health
         move      : 1.0,        // move speed (squares/sec)
-        rotate    : HALF_PI,    // rotation speed (radians/sec)
+        rotate    : HALF_PI,    // rotation speed (radians/sec) [UNUSED]
         power     : 1,          // attack power
         speed     : 0.5,        // attack cool (sec)
         shots     : 5           // max projectiles in the air at once
@@ -36,6 +56,7 @@ new evt.Class('Thing', {
     align   : 0, // 0 reserved for neutral units
     dead    : false,
     
+    pathing  : null, 
     blocking : true, // Whether the agent obstructs pathing
     active   : true, // Whether the agent takes actions
     
@@ -52,18 +73,46 @@ new evt.Class('Thing', {
     width  : REF_SIZE,
     height : REF_SIZE,
     
+    // Accessors
+    set  : op.set.methodize(),
+    attr : op.attr.methodize(),
+    
+    
     
     init : function init(align){
-        this.id    = Thing.THING_ID++;
+        this.id = THING_ID++;
         this.align = align || 0;
+        this.createBoundingBox();
+        this.createStats();
+        this.createCooldowns();
+    },
+    
+    
+    createBoundingBox : function createBoundingBox(){
         this.boundingBox = new BoundingBox(0,0, this.width,this.height, this.originX,this.originY);
+    },
+    
+    createStats : function createStats(){
+        this.stats = fillStats(this.stats);
+    },
+    
+    createCooldowns : function createCooldowns(){
+        this.cooldowns = {
+            'attack': new Cooldown(1000 * this.stats.speed)
+        };
+        this.ai = Y(this.ai).map(function(freq, k){
+            return new Cooldown(1000 * freq);
+        });
         
-        this.fillStats();
-        this.createCooldowns();
+        this._cooldowns = Y(this.cooldowns);
+        this._ai = Y(this.ai);
     },
     
-    set  : Y.op.set.methodize(),
-    attr : Y.op.attr.methodize(),
+    updateCooldowns : function updateCooldowns(elapsed, now){
+        this._cooldowns.invoke('tick', elapsed, now);
+        this._ai.invoke('tick', elapsed, now);
+        return this;
+    },
     
     
     position : function position(x,y){
@@ -73,7 +122,7 @@ new evt.Class('Thing', {
         var bb  = this.boundingBox.relocate(x,y)
         ,   loc = this.loc = bb.absOrigin;
         this.midpoint = bb.midpoint;
-        if (this.shape) this.shape.position(loc.x,loc.y);
+        if (this.shape) this.shape.position(loc.x,loc.y); // XXX: eh?
         return this;
     },
     
@@ -88,38 +137,13 @@ new evt.Class('Thing', {
         return this;
     },
     
-    fillStats : function fillStats(){ 
-        this.stats = Thing.fillStats(this.stats);
-    },
-    
-    createCooldowns : function createCooldowns(){
-        this.cooldowns = {
-            'attack': new Cooldown(1000 * this.stats.speed)
-        };
-        this.ai = Y(this.ai).map(function(freq, k){
-            return new Cooldown(1000 * freq);
-        });
-        
-        this._cooldowns = Y(this.cooldowns);
-        this._ai = Y(this.ai);
-    },
-    
-    updateCooldowns : function updateCooldowns(elapsed, now){
-        this._cooldowns.invoke('tick', elapsed, now);
-        this._ai.invoke('tick', elapsed, now);
-        return this;
-    },
-    
     dealDamage : function dealDamage(d, source){
         this.stats.hp -= d;
-        
         if (this.stats.hp <= 0)
             this.destroy();
-        
         return this;
     },
     
-    
     /**
      * Calculates the location of the bullet-spawning point on this Thing.
      */
@@ -157,7 +181,7 @@ new evt.Class('Thing', {
      * Sets up unit appearance for minimal updates. Called once at start,
      * or when the world needs to be redrawn from scratch.
      */
-    render : function render( parent ){
+    render : function render(){
         return this;
     },
     
@@ -174,29 +198,6 @@ new evt.Class('Thing', {
     
 });
 
-Y(Thing).extend({
-    THING_ID : 0,
-    
-    fillStats : function fillStats(stats){
-        var st    = Y(stats)
-        ,   stats = st.clone().end() ;
-        
-        st.forEach(function(v, k){
-            k = Y(k);
-            var k_ = k.rtrim('_max');
-            
-            if ( k.endsWith('_max') ) {
-                if ( stats[k_] === undefined )
-                    stats[k_] = v;
-                
-            } else if ( stats[k+'_max'] === undefined )
-                stats[k+'_max'] = v;
-            
-        });
-        
-        return stats;
-    }
-});
 
 config.updateOnChange('ui.showAttackCooldown', Thing.fn);
 
index 750d923..fcf7d57 100644 (file)
@@ -3,7 +3,7 @@ var Y = require('Y').Y
 ,   cookies   = require('Y/modules/y.cookies')
 ,   scaffold  = require('Y/modules/y.scaffold')
 ,   EventLoop = require('ezl/loop/eventloop').EventLoop
-,   config    = require('tanks/config').values
+,   config    = require('tanks/config').config
 ,   configUI, fields
 ;
 
@@ -17,11 +17,9 @@ function initConfigUi(){
     });
     
     configUI.append( jQuery('<div class="clearer"></div>') );
-    config.addEventListener('set:game.timeDilation', function(evt){
-        EventLoop.fn.dilation = evt.data.newval;
-    });
 };
 
+// Persist config via cookies
 config.addEventListener('set', function(evt){
     var d = evt.data;
     if (d.newval !== d.defval)
@@ -30,6 +28,9 @@ config.addEventListener('set', function(evt){
         cookies.remove(d.key);
 });
 
+config.updateOnChange('game.timeDilation', EventLoop.fn);
+
+
 // exports['init'] =
 // function initConfigUi(){
 //     $('#config [name=pathmap]').attr('checked',         config.get('pathing.overlayPathmap'));
index ed8424f..458a1e9 100644 (file)
@@ -1,6 +1,6 @@
 var Y = require('Y').Y
 ,   Rect = require('ezl/shape').Rect
-,   config = require('tanks/config').values
+,   config = require('tanks/config').config
 ,
 
 Grid =
@@ -10,8 +10,8 @@ Rect.subclass('Grid', {
     createGridTable  : null,
     createGridCanvas : null,
     
-    // Defaults
-    _cssClasses : 'ezl layer shape rect grid',
+    // Shape Config
+    _cssClasses : 'grid rect shape layer ezl',
     
     strokeStyle : '#6E6E6E',
     lineWidth   : 0.5,
@@ -26,7 +26,7 @@ Rect.subclass('Grid', {
         Rect.init.call(this, cols*side, rows*side);
     },
     
-    drawShape : function drawShape(ctx){
+    render : function render(ctx){
         if (this.table)  this.table.remove();
         // if (this.canvas) this.canvas.remove();
         
index d2a9874..2a41016 100644 (file)
@@ -1,3 +1,6 @@
 var Y = require('Y').Y;
+
 Y.extend(exports, require('tanks/ui/configui'));
+
+exports['PathMapUI'] = require('tanks/ui/pathmapui').PathMapUI;
 exports['main'] = require('tanks/ui/main');
index 105a4e3..d0ac852 100644 (file)
@@ -8,7 +8,7 @@ var Y            = require('Y').Y
 ,   Game         = require('tanks/game').Game
 ,   Tank         = require('tanks/thing').Tank
 ,   FpsSparkline = require('ezl/loop').FpsSparkline
-,   config       = require('tanks/config').values
+,   config       = require('tanks/config').config
 
 ,   updateTimer  = null
 ;
diff --git a/src/tanks/ui/pathmapui.cjs b/src/tanks/ui/pathmapui.cjs
new file mode 100644 (file)
index 0000000..73323ca
--- /dev/null
@@ -0,0 +1,160 @@
+var Y       = require('Y').Y
+,   Rect    = require('ezl/shape').Rect
+,   astar   = require('ezl/util/astar')
+,   PathMap = require('tanks/map/pathmap').PathMap
+,   config  = require('tanks/config').config
+,
+
+
+PathMapUI =
+exports['PathMapUI'] =
+Rect.subclass('PathMapUI', {
+    // Config
+    gridSquare     : null,
+    overlayPathmap : null,
+    overlayAiPaths : null,
+    
+    // Shape Config
+    _cssClasses : 'pathmap rect shape layer ezl',
+    
+    fillStyle   : 'rgba(255,255,255,0.1)',
+    strokeStyle : 'rgba(255,255,255,0.2)',
+    lineWidth   : 1,
+    
+    
+    
+    init : function initPathMapUI(game, pathmap){
+        this.game = game;
+        this.pathmap = pathmap;
+        
+        Rect.init.call(this, pathmap.width-2, pathmap.height-2);
+        
+        pathmap.gridPath = this.gridPathWithUI.bind(this);
+        this.cleanUpAgent = this.cleanUpAgent.bind(this);
+        this._drawPathStep = this._drawPathStep.bind(this);
+    },
+    
+    render : function render(ctx){
+        if (this.overlayPathmap)
+            this.overlay(ctx);
+        // AI Paths automatically show up (and are cleaned up)
+        // as they are created (and destroyed).
+        this.dirty = true;
+    },
+    
+    overlay : function overlay(ctx){
+        this.pathmap.reduce(function(acc, v, r, tree){
+            if ( acc[r.id] || v.isBoundary )
+                return acc;
+            acc[r.id] = r;
+            ctx.beginPath();
+            ctx.rect(r.x1,r.y1, r.width,r.height);
+            ctx.fill();
+            ctx.stroke();
+            ctx.closePath();
+            return acc;
+        }, {});
+    },
+    
+    
+    /**
+     * Wraps PathMap.gridPath() to draw AI pathes.
+     */
+    gridPathWithUI : function gridPathWithUI(start, end, agent){
+        var size = this.gridSquare, floor = Math.floor
+        ,   grid = this.pathmap.grid()
+        
+        ,   startX = floor(start.x/size)
+        ,   startY = floor(start.y/size)
+        ,   startN = grid[startX][startY]
+        
+        ,   endX   = floor(end.x/size)
+        ,   endY   = floor(end.y/size)
+        ,   endN   = grid[endX][endY]
+        
+        ,   path   = Y(astar.search(grid, startN, endN))
+        ;
+        
+        if (this.overlayAiPaths)
+            this.drawPath(agent, startN, path);
+        
+        return path;
+    },
+    
+    monitorAgent : function monitorAgent(agent){
+        if ( agent && !this.hasPath(agent.id) )
+            agent.addEventListener('destroy', this.cleanUpAgent);
+    },
+    
+    cleanUpAgent : function cleanUpAgent(evt){ this.destroyPath(evt.target.id); },
+    
+    getPath     : function getPath(id){     return $('.path.agent_'+id, this.layer); },
+    hasPath     : function hasPath(id){     return !!this.getPath(id).length; },
+    showPaths   : function showPaths(){     $('.path', this.layer).show(); },
+    hidePaths   : function hidePaths(){     $('.path', this.layer).hide(); },
+    destroyPath : function destroyPath(id){ this.getPath(id).remove(); },
+    
+    drawPath : function drawPath(agent, start, path){
+        this.monitorAgent(agent);
+        
+        var id = agent.id
+        ,   w = this.layerWidth, h = this.layerHeight
+        ,   canvas = this.getPath(id)
+        ;
+        
+        if (!canvas.length) {
+            canvas = $('<canvas class="path agent_'+id+'" style="position:absolute;top:0;left:0;"/>').appendTo(this.layer);
+            canvas.width(w).height(h);
+            canvas[0].width  = w;
+            canvas[0].height = h;
+        }
+        
+        canvas.hide();
+        
+        var ctx = canvas[0].getContext('2d');
+        ctx.lineWidth = 0;
+        
+        // Clear the canvas
+        ctx.beginPath();
+        ctx.clearRect(0,0, w,h);
+        ctx.closePath();
+        
+        // Draw the path
+        ctx.fillStyle = 'rgba(0,0,0,0.1)';
+        this._drawPathStep(ctx, start);
+        path.reduce(this._drawPathStep, ctx);
+        
+        // Draw start (concentric ring)
+        ctx.fillStyle = 'rgba(0,255,0,0.05)';
+        this._drawPathStep(ctx, start, -1);
+        
+        // Draw finish
+        ctx.fillStyle = 'rgba(0,0,255,0.05)';
+        this._drawPathStep(ctx, path.last());
+        
+        canvas.show();
+    },
+    
+    _drawPathStep : function drawPathStep(ctx, p, idx){
+        var size = this.gridSquare
+        ,   off  = size*(idx === -1 ? 0.15 : 0.3)
+        ,   r    = size/2 - off
+        ,   x    = p.x*size + off + r
+        ,   y    = p.y*size + off + r ;
+        
+        ctx.beginPath();
+        ctx.arc(x,y, r, 0, Math.PI*2, false);
+        ctx.fill();
+        ctx.closePath();
+        
+        return ctx;
+    }
+    
+    
+});
+
+
+config.updateOnChange(
+    ['pathing.gridSquare', 'pathing.overlayPathmap', 'pathing.overlayAiPaths'],
+    PathMapUI.fn);
+
index e6b0aaa..f00d42a 100644 (file)
 <script src="build/ezl/util/binaryheap.js" type="text/javascript"></script>
 <script src="build/ezl/util/astar.js" type="text/javascript"></script>
 <script src="build/ezl/util/tree/quadtree.js" type="text/javascript"></script>
-<script src="build/Y/modules/y.config.js" type="text/javascript"></script>
 <script src="build/Y/modules/y.scaffold.js" type="text/javascript"></script>
+<script src="build/Y/modules/y.config.js" type="text/javascript"></script>
 <script src="build/Y/modules/y.cookies.js" type="text/javascript"></script>
 <script src="build/tanks/config.js" type="text/javascript"></script>
 <script src="build/tanks/ui/configui.js" type="text/javascript"></script>
 <script src="build/tanks/thing/thing.js" type="text/javascript"></script>
 <script src="build/tanks/map/trajectory.js" type="text/javascript"></script>
-<script src="build/tanks/ui/grid.js" type="text/javascript"></script>
 <script src="build/tanks/map/wall.js" type="text/javascript"></script>
+<script src="build/tanks/thing/item.js" type="text/javascript"></script>
+<script src="build/tanks/ui/grid.js" type="text/javascript"></script>
 <script src="build/tanks/fx/explosion.js" type="text/javascript"></script>
 <script src="build/tanks/thing/bullet.js" type="text/javascript"></script>
 <script src="build/tanks/thing/tank.js" type="text/javascript"></script>
 <script src="build/tanks/map/pathmap.js" type="text/javascript"></script>
+<script src="build/tanks/ui/pathmapui.js" type="text/javascript"></script>
 <script src="build/tanks/thing/player.js" type="text/javascript"></script>
 <script src="build/tanks/map/level.js" type="text/javascript"></script>
 <script src="build/tanks/thing.js" type="text/javascript"></script>