Resolves pathing bug.
authordsc <david.schoonover@gmail.com>
Fri, 24 Dec 2010 05:04:42 +0000 (21:04 -0800)
committerdsc <david.schoonover@gmail.com>
Fri, 24 Dec 2010 05:04:42 +0000 (21:04 -0800)
22 files changed:
src/Y/core.cjs
src/Y/delegate.cjs
src/Y/internal.cjs [new file with mode: 0644]
src/Y/types/array.cjs
src/Y/types/chain.cjs [new file with mode: 0644]
src/Y/types/collection.cjs
src/Y/types/function.cjs
src/Y/types/object.cjs
src/Y/types/string.cjs
src/Y/utils.cjs
src/ezl/loop/cooldown.cjs
src/ezl/loop/eventloop.cjs
src/ezl/util/tree/quadtree.cjs
src/tanks/map/level.cjs
src/tanks/map/pathmap.cjs
src/tanks/map/trajectory.cjs
src/tanks/map/traversal.cjs
src/tanks/thing/player.cjs
src/tanks/thing/tank.cjs
src/tanks/ui/main.cjs
src/tanks/ui/pathmapui.cjs
www/deps.html

index b13737c..d400314 100644 (file)
@@ -18,6 +18,8 @@ var undefined
 ,   type      = require('Y/type')
 ;
 
+
+
 function reduce(o, fn, acc){
     if ( !o )
         return acc;
@@ -93,51 +95,120 @@ function attr(o, key, value, def){
         return o[key];
 }
 
+/**
+ * Copies all properties from all arguments after the first onto the first.
+ * Accepts any number of arguments greater than one.
+ * 
+ * @param {Object} target Target object for the copied properties.
+ * @param {Object...} donors Donor object(s).
+ * @return {Object} A
+ * 
+ * nb. "Properties" are only value descriptors. To copy accessor descriptors, use
+ * `core.accessors(A,B)`. To copy everything, use `core.descriptors(A,B)`.
+ * 
+ */
 function extend( A, B ){
     var args = slice.call(arguments,1);
     if ( type.isArray(A) )
         return A.concat.apply(A, args);
     else
-        return args.reduce(extendall, A);
+        return args.reduce(_extend, A);
 }
-function extendall(A, donor){ return reduce(donor, attrvk, A); }
-function attrvk(o, v, k){     return attr(o, k, v, o[k]); }
+function _extend(A, donor){ return reduce(donor, attrvk, A); }
+function attrvk(o, v, k){   return attr(o, k, v, o[k]); }
 
 /**
  * Fetches the descriptor for each property that is an accessor (getter/setter).
- * @param {Object} o Object for inspection.
- * @return {Object} A map of property to descriptor.
- * nb. An Array is safer, but:
- *  1) Object.defineProperties takes an Object
- *  2) That'd be much worse to work with
+ * @param {Object} target Object for inspection.
+ * @return {Object} Map from property to accessor descriptor.
  */
-function accessors(o){
-    if ( !(o && typeof o === "object") )
-        return {};
-    return reduce(o, function(acc, v, k, obj){
-        
-        // Ignore inherited properties
-        if ( !hasOwn.call(obj,k) )
-            return acc;
-        
-        // Only yield accessors
-        var desc = getDesc(obj,k);
-        if ( !hasOwn.call(desc,'value') )
-            acc[k] = desc;
-        
+/**
+ * Copies descriptors from all arguments onto the first.
+ * Accepts any number of arguments greater than one.
+ * 
+ * @param {Object} target Target object for the copied accessors.
+ * @param {Object...} donors Donor object(s).
+ * @return {Object} A.
+ */
+function accessors(A, B){
+    if ( !(A && typeof A === "object") )
+        return A;
+    
+    if (arguments.length < 2)
+        return reduce(A, _getAccessors, {});
+    
+    var desc = slice.call(arguments,1).reduce(_copyAccessors, {});
+    Object.defineProperties(A, desc);
+    return A;
+}
+function _getAccessors(acc, v, k, obj){
+    
+    // Ignore inherited properties
+    if ( !hasOwn.call(obj,k) )
         return acc;
-    }, {});
+    
+    // Only yield accessors
+    var desc = getDesc(obj,k);
+    if ( !hasOwn.call(desc,'value') )
+        acc[k] = desc;
+    
+    return acc;
+}
+function _copyAccessors(desc, donor){
+    return reduce(donor, _getAccessors, desc);
 }
 
+/**
+ * Fetches the descriptor for each own property of the object.
+ * 
+ * @param {Object} target Object for inspection.
+ * @return {Object} Map from property to descriptor.
+ */
+/**
+ * Copies descriptors from all arguments onto the first.
+ * Accepts any number of arguments greater than one.
+ * 
+ * @param {Object} target Target object for the copied accessors.
+ * @param {Object...} donors Donor object.
+ * @return {Object} A.
+ */
+function descriptors(A, B){
+    if ( !(A && typeof A === "object") )
+        return A;
+    
+    if (arguments.length < 2)
+        return reduce(A, _getDescriptors, {});
+    
+    var desc = slice.call(arguments,1).reduce(_copyDescriptors, {});
+    Object.defineProperties(A, desc);
+    return A;
+}
+function _getDescriptors(acc, v, k, obj){
+    
+    // Ignore inherited properties
+    if ( !hasOwn.call(obj,k) )
+        return acc;
+    
+    acc[k] = getDesc(obj,k);
+    return acc;
+}
+function _copyDescriptors(desc, donor){
+    return reduce(donor, _getDescriptors, desc);
+}
+
+
+
+exports['slice']       = slice;
+
+exports['reduce']      = reduce;
+exports['map']         = map;
+exports['forEach']     = forEach;
+exports['filter']      = filter;
 
-exports['reduce']    = reduce;
-exports['map']       = map;
-exports['forEach']   = forEach;
-exports['filter']    = filter;
+exports['set']         = set;
+exports['attr']        = attr;
+exports['extend']      = extend;
 
-exports['set']       = set;
-exports['attr']      = attr;
-exports['extend']    = extend;
+exports['accessors']   = accessors;
+exports['descriptors'] = descriptors;
 
-exports['accessors'] = accessors;
-exports['slice']     = slice;
\ No newline at end of file
index 469a1e0..7a68061 100644 (file)
@@ -108,24 +108,34 @@ function extend( A, B ){
 function extendall(A, donor){ return reduce(donor, attrvk, A); }
 function attrvk(o, v, k){     return attr(o, k, v, o[k]); }
 
-function accessors(o){
-    if ( !(o && typeof o === "object") )
-        return {};
+function accessors(A, B){
+    if ( !(A && typeof A === "object") )
+        return A;
     
-    if ( notWrapped(o.accessors) )
-        return o.accessors.apply(o, slice.call(arguments,1));
+    if ( notWrapped(A.accessors) )
+        return A.accessors.apply(A, slice.call(arguments,1));
     
-    return core.accessors(o);
+    return core.accessors.apply(this, arguments);
 }
 
-exports['reduce']  = reduce;
-exports['map']     = map;
-exports['forEach'] = forEach;
-exports['filter']  = filter;
+function descriptors(A,B){
+    if ( !(A && typeof A === "object") )
+        return A;
+    
+    if ( notWrapped(A.descriptors) )
+        return A.descriptors.apply(A, slice.call(arguments,1));
+    
+    return core.descriptors.apply(this, arguments);
+}
 
-exports['set']     = set;
-exports['attr']    = attr;
-exports['extend']  = extend;
+exports['reduce']      = reduce;
+exports['map']         = map;
+exports['forEach']     = forEach;
+exports['filter']      = filter;
 
-exports['accessors'] = accessors;
+exports['set']         = set;
+exports['attr']        = attr;
+exports['extend']      = extend;
 
+exports['accessors']   = accessors;
+exports['descriptors'] = descriptors;
diff --git a/src/Y/internal.cjs b/src/Y/internal.cjs
new file mode 100644 (file)
index 0000000..1486e1a
--- /dev/null
@@ -0,0 +1,11 @@
+var
+Blank =
+exports['Blank'] =
+function Blank(v){ this.value = v; };
+
+exports['_'] = _ =
+exports['BLANK'] = BLANK = new Blank();
+
+exports['break_'] =
+function break_(v){ return new Blank(v); };
+
index f4a8733..8b6fc0a 100644 (file)
@@ -81,6 +81,13 @@ YCollection.subclass('YArray', function(YArray){
         return ( L ? A[L-1] : undefined );
     };
     
+    this['clear'] =
+    function clear(){
+        var A = this._o;
+        while (A.length) A.pop();
+        return this;
+    };
+    
     function unwrapY(o){
         return (o instanceof YArray ? o.end() : o);
     }
diff --git a/src/Y/types/chain.cjs b/src/Y/types/chain.cjs
new file mode 100644 (file)
index 0000000..98fc812
--- /dev/null
@@ -0,0 +1,8 @@
+var YCollection = require('Y/types/collection').YCollection
+,   YFunction   = require('Y/types/function').YFunction
+,   core        = require('Y/core')
+,   del         = require('Y/delegate')
+,   op          = require('Y/op')
+,   type        = require('Y/type')
+;
+
index beb799e..8611eb7 100644 (file)
@@ -1,14 +1,11 @@
-/**
- * A generic collection (which also provides the majority of core functionality for YObject).
- * Specializations are provided by subclasses. All subclasses must implement reduce().
- */
-
 var YBase  = require('Y/class').YBase
 // ,   type   = require('Y/type')
 ,   core   = require('Y/core')
 ,   del    = require('Y/delegate')
 ,   op     = require('Y/op')
 ,   slice  = core.slice
+
+,   internal = require('Y/internal')
 ;
 
 
@@ -17,58 +14,94 @@ function extendY(){
     return this;
 }
 
+/**
+ * A generic collection (which also provides the majority of core functionality for YObject).
+ * Specializations are provided by subclasses. 
+ * 
+ * All subclasses must implement `reduce()`.
+ * 
+ * There are several optional core methods for customization and optimization. By default:
+ *  - `attr()` calls `Y.delegate.attr()` on the wrapped object, `this._o`. If the result is not the
+ *      wrapped object, it is returned, otherwise `this` is returned.
+ *  - `clone()` invokes the constructor with nothing, and then calls `extend()` on the new object passing itself.
+ *  - `end()` returns this._o and does nothing else.
+ *  - `remove()` must iterate the whole collection to ensure all values are removed.
+ *  - `find()` is a variant of reduce that uses the break-wrapper `Y.internal.break_()`, as a sentinel to stop iteration.
+ */
+var YCollection =
 exports['YCollection'] =
 YBase.subclass('YCollection', {
-    'init' : function(o){
+    'init' : function initYCollection(o){
         this._o = o || {};
     },
     
-    'attr'   : function attr(k, v, def){
+    'end' : function end(){ return this._o; },
+    
+    'clone' : function clone(){
+        return new this.constructor().extend(this);
+    },
+    
+    'attr' : function attr(k, v, def){
         var r = del.attr(this._o, k, v, def);
         if (r === this._o)
             return this;
         else
             return r;
     },
-    'extend' : extendY,
-    'merge'  : extendY,
-    'concat' : extendY,
     
-    'end' : function end(){ return this._o; },
+    /**
+     * Removes all instances of the given values from the collection.
+     * 
+     * @param {Any...} value Value(s) to be removed from the collection.
+     * @return {this}
+     */
+    'remove' : function remove(v){
+        var o = this._o
+        ,   values = slice.call(arguments,0);
+        this.forEach(function(v, k){
+            if ( values.indexOf(v) !== -1 )
+                delete o[k];
+        });
+        return this;
+    },    
+    
     
     'reduce' : function reduce( fn, acc, context ){
-        var o = this._o || this,
-            acc = acc || new o.constructor();
-        for ( var name in o )
-            acc = fn.call( context || this, acc, o[name], name, o );
-        return acc;
+        throw new UnimplementedError("Reduce is unimplemented!");
     },
     
     'map' : function map( fn ){
-        var o = this._o
-        ,   cxt = arguments[1] || this
-        ,   acc = new o.constructor()
-        ;
-        for ( var name in o )
-            acc[name] = fn.call(cxt, o[name], name, o);
-        return acc;
+        var cxt = arguments[1] || this;
+        return this.reduce(function(acc, v, k, o){
+            var _v = fn.call(cxt, v, k, o);
+            return acc.attr(k, _v);
+        }, new this.__class__() );
     },
     
     'forEach' : function forEach( fn, context ){
-        var o = this._o, cxt = arguments[1] || this;
-        for ( var name in o )
-            fn.call(cxt, o[name], name, o);
+        this.reduce(function(cxt, v, k, o){
+            fn.call(cxt, v, k, o);
+        }, arguments[1] || this);
         return this;
     },
     
     'filter' : function filter( fn, context ){
-        var o = this._o, acc = new o.constructor();
-        for ( var name in o )
-            if ( fn.call( context || this, o[name], name, o ) )
-                acc[name] = o[name];
-        return acc;
+        var cxt = arguments[1] || this;
+        return this.reduce(function(acc, v, k, o){
+            if ( fn.call(cxt, v, k, o) )
+                acc.attr(k, v);
+            return acc;
+        }, new this.__class__() );
     },
     
+    // 'find' : function find( fn ){
+    //     var cxt = arguments[1] || this;
+    //     
+    // },
+    
+    /**
+     * FIXME
+     */
     'indexOf' : function indexOf( value ){
         var o = this._o;
         for ( var name in o )
@@ -81,41 +114,6 @@ YBase.subclass('YCollection', {
         return ( this.indexOf(value) !== -1 );
     },
     
-    'clone' : function clone(){
-        return new this.constructor().extend(this);
-    },
-    
-    // Dependence on YArray closes the loop
-    // 'remove' : function remove(v){
-    //     var o = this._o
-    //     ,   toRemove = new Y(arguments);
-    //     
-    //     for (var k in o) {
-    //         var v = o[k];
-    //         if ( toRemove.has(v) ) {
-    //             delete o[k];
-    //             toRemove.remove(v);
-    //         }
-    //     }
-    //     return this;
-    // },
-    
-    'remove' : function remove(v){
-        var o = this._o
-        ,   toRemove = slice.call(arguments);
-        
-        for (var k in o) {
-            var v = o[k], idx = toRemove.indexOf(v);
-            if ( idx !== -1 ) {
-                delete o[k];
-                toRemove.splice(idx,1);
-            }
-            if (!toRemove.length)
-                break;
-        }
-        return this;
-    },
-    
     'every' : function every( fn ){
         var self = this, fn = fn || op.bool;
         return this.reduce(function(acc, v, k, o){
@@ -131,7 +129,7 @@ YBase.subclass('YCollection', {
     },
     
     'zip' : function zip(){
-        var sequences = slice.call(arguments);
+        var sequences = slice.call(arguments,0);
         sequences.unshift(this);
         return this.map(function(_, k){
             return sequences.map(op.curried.kget(k));
@@ -141,10 +139,10 @@ YBase.subclass('YCollection', {
     'pluck' : function pluck(key){
         return this.map(function(v){
             return del.attr(v, key);
-            // return v && (type.isFunction(v.attr) ? v.attr(key) : v[key]);
         });
     },
     
+    // FIXME: del.map
     'invoke' : function invoke(name){
         var args = slice.call(arguments,1);
         return del.map(this, function(o){
@@ -152,6 +150,7 @@ YBase.subclass('YCollection', {
         });
     },
     
+    // FIXME: this._o[name].apply
     'apply' : function apply(name, args){
         return this[name].apply(this, args);
     }
index e11e31b..63ff66c 100644 (file)
@@ -10,7 +10,6 @@ var undefined
 ,   slice         = core.slice
 
 ,   YF  = YFunction
-,   _   = YF._ = {}
 ,   YFP = YF.prototype
 ;
 
@@ -284,4 +283,3 @@ function getName( fn ){
 
 // Export these last to avoid methodizing them
 exports['YFunction']  = YF(YF);
-exports['_'] = _;
index 63e89f7..b06d0e3 100644 (file)
@@ -117,14 +117,69 @@ YObject =
 exports['YObject'] =
 YCollection.subclass('YObject', {
     
-    init : function initYObject(o){
+    'init' : function initYObject(o){
         this._o = o || {};
     },
     
-    clone : function clone(){
+    'clone' : function clone(){
         return new YObject( deepcopy(this._o) );
+    },
+    
+    'remove' : function remove(v){
+        var o = this._o
+        ,   values = slice.call(arguments,0);
+        
+        for (var k in o) {
+            var v = o[k];
+            if ( values.indexOf(v) !== -1 )
+                delete o[k];
+        }
+        return this;
+    },
+    
+    
+    'reduce' : function reduce( fn, acc, context ){
+        var o = this._o || this,
+            acc = acc || new o.constructor();
+        for ( var name in o )
+            acc = fn.call( context || this, acc, o[name], name, o );
+        return acc;
+    },
+    
+    'map' : function map( fn ){
+        var o = this._o
+        ,   cxt = arguments[1] || this
+        ,   acc = new o.constructor()
+        ;
+        for ( var name in o )
+            acc[name] = fn.call(cxt, o[name], name, o);
+        return acc;
+    },
+    
+    'forEach' : function forEach( fn, context ){
+        var o = this._o, cxt = arguments[1] || this;
+        for ( var name in o )
+            fn.call(cxt, o[name], name, o);
+        return this;
+    },
+    
+    'filter' : function filter( fn, context ){
+        var o = this._o, acc = new o.constructor();
+        for ( var name in o )
+            if ( fn.call( context || this, o[name], name, o ) )
+                acc[name] = o[name];
+        return acc;
+    },
+    
+    'indexOf' : function indexOf( value ){
+        var o = this._o;
+        for ( var name in o )
+            if ( o[name] === value )
+                return name;
+        return -1;
     }
     
+    
 });
 
 
index 8acefdb..4339f9a 100644 (file)
@@ -36,7 +36,7 @@ YCollection.subclass('YString', function(YString){
         },
         
         'clone' : function clone(){
-            return YString(this._o);
+            return YString(this._o); // strings are immutable
         },
         
         'size' : size,
index b55e8b2..a27df65 100644 (file)
@@ -5,9 +5,14 @@ var YFunction  = require('Y/types/function').YFunction
 ,   slice      = core.slice
 ,   isFunction = type.isFunction
 ,   isArray    = type.isArray
-;
 
 
+,   UE =
+exports['UnimplementedError'] =
+function UnimplementedError(){ Error.call(this, arguments); }
+;
+UE.prototype = new Error('Unimplemented!');
+
 function bindName(k){
     var v = this[k];
     if ( isFunction(v) )
index fbf4d45..f0ebf8a 100644 (file)
@@ -19,7 +19,7 @@ Y.subclass('Cooldown', {
         return false;
     },
     
-    'tick' : function(elapsed){
+    'tick' : function(elapsed, now){
         if (this.ready) return true;
         
         this.elapsed += elapsed || 0;
index 6d2895f..5bc35dd 100644 (file)
@@ -17,7 +17,8 @@ Emitter.subclass('EventLoop', {
     framerate : 0, // Target framerate
     frametime : 0, // 1000 / framerate
     
-    now       : 0, // Last tick time (ms)
+    clock     : 0, // Subjective clock
+    now       : 0, // Last tick time (real ms)
     fps       : 0, // Effective framerate
     ticks     : 0, // Number of ticks since start
     
@@ -55,6 +56,7 @@ Emitter.subclass('EventLoop', {
         this.stop();
         this.elapsedAtStop = 0;
         this.ticks = 0;
+        this.clock = 0;
         this.times = [];
         this.realtimes = [];
     },
@@ -97,12 +99,13 @@ Emitter.subclass('EventLoop', {
         this.now     = new Date().getTime();
         this.elapsed = this.now - lastTick;
         this.perceived = this.elapsed/this.timeDilation;
+        this.clock += this.perceived;
         
         clearTimeout(this.timer);
         this.timer = null;
         
         this.fire('tick', this, {
-            'now'     : this.now,
+            'now'     : this.clock,
             'elapsed' : this.perceived,
             'ticks'   : ++this.ticks
         });
index 14e3713..6f1b489 100644 (file)
@@ -1,9 +1,10 @@
+
+
 var Y = require('Y').Y
 ,   CAPACITY  = 8
 ,   REGION_ID = 0
 ,
 
-
 Region =
 exports['Region'] =
 Y.subclass('Region', {
@@ -13,11 +14,16 @@ Y.subclass('Region', {
         this.value = value;
     },
     
-    // Expects caller will have ordered x1 < x2, y1 < y2
-    overlaps : function overlaps(x1,y1, x2,y2){
-        return !( x1 >  this.x2 || y1 >  this.y2
-               || x2 <= this.x1 || y2 <= this.y1 );
-    },
+    /**
+     * Determines if the given Rect overlaps with this tree,
+     * including the top/left edges, but excluding the bottom/right.
+     */
+    overlaps : orderThen('_overlaps'),
+    
+    /**
+     * As overlaps, but presumes input is sorted x1 &lt; x2 and y1 &lt; y2.
+     */
+    _overlaps : overlaps,
     
     toString : function toString(){
         return this.className+'(id='+this.id+', rect='+rectToString(this)+', value='+this.value+')';
@@ -47,12 +53,17 @@ Y.subclass('QuadTree', {
         this.clear();
     },
     
-    // Expects caller will have ordered x1 < x2, y1 < y2
-    overlaps : function overlaps(x1,y1, x2,y2){
-        return !( x1 >  this.x2 || y1 >  this.y2
-               || x2 <= this.x1 || y2 <= this.y1 );
-    },
     
+    /**
+     * Determines if the given Rect overlaps with this tree,
+     * including the top/left edges, but excluding the bottom/right.
+     */
+    overlaps : orderThen('_overlaps'),
+    
+    /**
+     * As overlaps, but presumes input is sorted x1 &lt; x2 and y1 &lt; y2.
+     */
+    _overlaps : overlaps,
     
     // TODO: Add optional filter
     get : function get(x1,y1, x2,y2){
@@ -76,7 +87,7 @@ Y.subclass('QuadTree', {
     //     ,   x1 = Math.min(_x1,_x2), x2 = Math.max(_x1,_x2)
     //     ,   y1 = Math.min(_y1,_y2), y2 = Math.max(_y1,_y2) ;
     //     
-    //     if ( !self.overlaps(x1,y1, x2,y2) )
+    //     if ( !self._overlaps(x1,y1, x2,y2) )
     //         return acc;
     //     
     //     // Implies this is a leaf: check its values
@@ -137,36 +148,56 @@ Y.subclass('QuadTree', {
         });
     },
     
-    leaves : function leaves(x1,y1, x2,y2, fn, acc, context){
-        var self = this
-        ,   cxt = context || self
-        ,   _x1 = Math.min(x1,x2), _x2 = Math.max(x1,x2)
-        ,   _y1 = Math.min(y1,y2), _y2 = Math.max(y1,y2) ;
-        
-        if ( !self.overlaps(_x1,_y1, _x2,_y2) )
+    /**
+     * @protected
+     * Invokes iterator on each matching subtree (passing the accumulator, the tree's regions, the tree, and the Rect query)
+     * to acquire the new accumulator:
+     * 
+     *      acc = fn.call(context||tree, acc, tree.regions, tree, query);
+     * 
+     * Where the Rect query is passed as an object with properties (x1,y1, x2,y2).
+     * 
+     * For more friendly iterators, useful to public callers, @see this.collect(), @see this.reduce().
+     */
+    leaves : orderThen('_leaves'),
+     
+    // leaves : function leaves(x1,y1, x2,y2, fn, acc, context){
+    //     var _x1 = Math.min(x1,x2), _x2 = Math.max(x1,x2)
+    //     ,   _y1 = Math.min(y1,y2), _y2 = Math.max(y1,y2);
+    //     return this._leaves(_x1,_y1, _x2,_y2, fn, acc, context);
+    // },
+    
+    _leaves : function _leaves(x1,y1, x2,y2, fn, acc, context){
+        if ( !this._overlaps(x1,y1, x2,y2) )
             return acc;
         
         // Implies this is a leaf: check its values
-        if ( self.regions ) acc = fn.call(cxt, acc, self.regions, self, self);
+        if ( this.regions ) acc = fn.call(context || this, acc, this.regions, this);
         
         // Recurse into any children -- Note we do not place this in an else-block,
         // as mutation during the above call might cause the tree to split.
-        if (self.nw) acc = self.nw.leaves(_x1,_y1, _x2,_y2, fn, acc, context);
-        if (self.ne) acc = self.ne.leaves(_x1,_y1, _x2,_y2, fn, acc, context);
-        if (self.sw) acc = self.sw.leaves(_x1,_y1, _x2,_y2, fn, acc, context);
-        if (self.se) acc = self.se.leaves(_x1,_y1, _x2,_y2, fn, acc, context);
+        if (this.nw) acc = this.nw._leaves(x1,y1, x2,y2, fn, acc, context);
+        if (this.ne) acc = this.ne._leaves(x1,y1, x2,y2, fn, acc, context);
+        if (this.sw) acc = this.sw._leaves(x1,y1, x2,y2, fn, acc, context);
+        if (this.se) acc = this.se._leaves(x1,y1, x2,y2, fn, acc, context);
         
         return acc;
     },
     
-    collect : function collect(x1,y1, x2,y2, fn, acc, context){ // XXX: unique=false, limit=Infinity, depth=Infinity
-        var _x1 = Math.min(x1,x2), _x2 = Math.max(x1,x2)
-        ,   _y1 = Math.min(y1,y2), _y2 = Math.max(y1,y2);
+    collect : orderThen('_collect'),
+    
+    // collect : function collect(x1,y1, x2,y2, fn, acc, context){ // XXX: unique=false, limit=Infinity, depth=Infinity
+    //     var _x1 = Math.min(x1,x2), _x2 = Math.max(x1,x2)
+    //     ,   _y1 = Math.min(y1,y2), _y2 = Math.max(y1,y2);
+    //     return this._collect(_x1,_y1, _x2,_y2, fn, acc, context);
+    // },
+    
+    _collect : function _collect(x1,y1, x2,y2, fn, acc, context){ // XXX: unique=false, limit=Infinity, depth=Infinity
         return this.leaves(
-            _x1,_y1, _x2,_y2,
+            x1,y1, x2,y2,
             function(acc, rs, tree){
                 for (var i=0, L=rs.length, r=rs[i]; i<L; r=rs[++i])
-                    if ( r.overlaps(_x1,_y1, _x2,_y2) )
+                    if ( r._overlaps(x1,y1, x2,y2) )
                         acc = fn.call(this, acc, r.value, r, tree);
                 return acc;
             },
@@ -224,6 +255,25 @@ Y.subclass('QuadTree', {
     }
 });
 
+/**
+ * Orders x1,y1, x2,y2 correctly then calls method.
+ */
+function orderThen(method){
+    return function _orderThen(x1,y1, x2,y2){
+        var _x1 = Math.min(x1,x2), _x2 = Math.max(x1,x2)
+        ,   _y1 = Math.min(y1,y2), _y2 = Math.max(y1,y2)
+        ,   args = [_x1,_y1, _x2,_y2];
+        if (arguments.length > 4)
+            args = args.concat(Y(arguments,4));
+        return this[method].apply(this, args);
+    };
+}
+
+function overlaps(x1,y1, x2,y2){
+    return !( x1 >= this.x2 || y1 >= this.y2
+           || x2 <= this.x1 || y2 <= this.y1 );
+}
+
 
 
 function Rect(x1,y1, x2,y2){
@@ -247,3 +297,14 @@ function rectToString(o){
     return '['+x1+','+y1+', '+x2+','+y2+']';
 }
 
+
+function moveArgs(args, st, by){
+    if (by === undefined) by = 1;
+    var i = (args.length += by)
+    ,   gap = st+by
+    ;
+    while (--i >= st) {
+        args[i] = (i < gap ? undefined : args[i-by]);
+    }
+    return args;
+}
index 3da05b1..fc4f88b 100644 (file)
@@ -42,7 +42,7 @@ Rect.subclass('Level', {
         // game.addThing(new Tank(1).colors('#4596FF', '#182B53', '#F25522'), 3,9);
         
         E = 
-        game.addThing(new Tank(2), 0,0);
+        game.addThing(new Tank(2), 0,5);
         // game.addThing(new Tank(2), 1,0);
         // game.addThing(new Tank(2), 8,1);
         
index 7c6cc28..a522cbb 100644 (file)
@@ -69,11 +69,11 @@ Y.subclass('Square', new Vec(0,0), {
     _blocked : function blocked(){
         var pm    = this.pathmap
         ,   agent = pm._agent
-        ,   bb    = agent.boundingBox
+        // ,   bb    = agent.boundingBox
         
-        ,   origin = bb.relOrigin
-        ,   left = origin.x,  right  = bb.width - left
-        ,   top  = origin.y,  bottom = bb.height - top
+        // ,   origin = bb.relOrigin
+        // ,   left = origin.x,  right  = bb.width - left
+        // ,   top  = origin.y,  bottom = bb.height - top
         
         ,   SIZE = pm.gridSquare
         ,   x  = Math.floor(this.x)
@@ -267,7 +267,7 @@ Map.subclass('PathMap', {
                     path.push( vec.sum(node,mid) );
                     node = node.prev;
                 }
-                return path.reverse();
+                return Y(path.reverse());
             }
             
             // Normal case -- move current from open to closed, process each of its neighbors
index f66dbfd..f11de2a 100644 (file)
@@ -14,20 +14,20 @@ var Y     = require('Y').Y
 Trajectory =
 exports['Trajectory'] =
 Line.subclass('Trajectory', {
-    elapsed : 0,
+    tCurrent : 0,
     
     
-    init : function initTrajectory(owner, x1,y1, x2,y2, tdist, elapsed){
+    init : function initTrajectory(owner, x1,y1, x2,y2, tdist, tCurrent){
         Y.bindAll(this, 'compare', 'closer', 'intersects');
         
         this.owner   = owner;
         this.game    = owner.game;
         this.pathmap = this.game.pathmap;
         
-        this.reset(x1,y1, x2,y2, tdist, elapsed);
+        this.reset(x1,y1, x2,y2, tdist, tCurrent);
     },
     
-    reset : function reset(x1,y1, x2,y2, tdist, elapsed){
+    reset : function reset(x1,y1, x2,y2, tdist, tCurrent){
         if (x1 instanceof Array && y1 instanceof Array) {
             tdist = x2;
             y2 = y1[1]; x2 = y1[0];
@@ -50,7 +50,7 @@ Line.subclass('Trajectory', {
         this.p2 = new Vec(x2,y2);
         Vec.init.call(this, x2-x1, y2-y1);
         
-        this.elapsed = elapsed || 0;
+        this.tCurrent = tCurrent || 0;
         this.resetBound();
         return this;
     },
@@ -66,7 +66,7 @@ Line.subclass('Trajectory', {
     },
     
     clone : function clone(){
-        return new Trajectory(this.owner, this.x1,this.y1, this.x2,this.y2, this.tdist, this.elapsed);
+        return new Trajectory(this.owner, this.x1,this.y1, this.x2,this.y2, this.tdist, this.tCurrent);
     },
     
     
@@ -93,7 +93,7 @@ Line.subclass('Trajectory', {
         if (b instanceof Thing) b = b.loc;
         
         var abs = Math.abs
-        ,   t   = this.elapsed
+        ,   t   = this.tCurrent
         
         ,   xa  = this.calcX(a.y), ya = this.calcY(a.x)
         ,   ta  = this.iparametric(xa,ya) - t
@@ -162,80 +162,6 @@ Line.subclass('Trajectory', {
         return (blocker === obj ? false : blocker);
     },
     
-    
-    // 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)+']';
     }
index 7fe1e34..c449437 100644 (file)
@@ -4,8 +4,6 @@ var Y = require('Y').Y
 Traversal =
 exports['Traversal'] =
 Y.subclass('Traversal', {
-    elapsed   : 0,
-    
     isBlocked : false,
     isCorner  : false,
     remaining : 0,
@@ -22,17 +20,16 @@ Y.subclass('Traversal', {
         this.bbox       = thing.boundingBox.clone();
         
         this.trajectory = trajectory || thing.trajectory;
-        this.elapsed    = this.trajectory.elapsed;
     },
     
     step : function step(dt, tx,ty){
         var t, traj = this.trajectory;
+        // console.group('tvsl.step(dt='+dt+', tCurrent='+traj.tCurrent+', target=('+tx+','+ty+'))');
         
         do {
             t = this.stepTo( Math.min(traj.tBound,dt), tx,ty);
-            this.elapsed += t;
-            traj.elapsed += t;
-            dt -= t;
+            traj.tCurrent += t;
+            dt = Math.max(0, dt-t);
             
             if (this.isBlocked) {
                 this.remaining = dt;
@@ -44,6 +41,7 @@ Y.subclass('Traversal', {
             
         } while (dt > 0);
         
+        // console.groupEnd();
         return this.to;
     },
     
@@ -62,7 +60,7 @@ Y.subclass('Traversal', {
         ,   x2 = bb.x2,  y2 = bb.y2
         ,   o  = bb.absOrigin
         
-        ,   to = this.to = traj.parametric(this.elapsed+t)
+        ,   to = traj.parametric(traj.tCurrent+t)
         ;
         
         // Don't overshoot the target
@@ -74,10 +72,13 @@ Y.subclass('Traversal', {
         
         // nb: BoundingBox.relocate() is in-place, so this.bbox are updated
         bb = bb.relocate(to);
+        var oto = this.to = to;
         blockers = this.blockers = this.pathmap.get(bb).remove(thing).end();
         
-        if (!blockers.length)
+        if (!blockers.length) {
+            // console.log('stepTo('+t+') --> '+to+' consuming '+t);
             return t; // All time consumed, yay
+        }
         
         this.isBlocked = true;
         
@@ -121,7 +122,10 @@ Y.subclass('Traversal', {
         bb.relocate(to);
         
         // Return how much time consumed
-        return traj.timeToMove(to.x-o.x, to.y-o.y);
+        var consumed = traj.timeToMove(to.x-o.x, to.y-o.y);
+        // console.log('stepTo('+t+') --> wanted '+oto+' but BLOCKED! by '+this.blocker+', so ending at '+to+', consuming '+consumed);
+        
+        return consumed;
     }
     
 })
index 619cf4b..fdd177a 100644 (file)
@@ -92,27 +92,6 @@ Tank.subclass('PlayerTank', {
         this.move( this.loc.moveByDir(dir, this.stats.move * SQUARETH) );
     },
     
-    // _move : function _move(){
-    //     var dir = this.activeKeys
-    //         .unique()
-    //         .sort()
-    //         .join(' ');
-    //     
-    //     if (!dir) return;
-    //     
-    //     var toLoc = this.loc.moveByDir(dir, (this.stats.move * SQUARETH))
-    //     
-    //     ,   x = toLoc.x, y = toLoc.y
-    //     ,   bb = this.boundingBox.relocated(x,y)
-    //     
-    //     ,   blockers = this.game.pathmap.get(bb.x1,bb.y1, bb.x2,bb.y2).remove(this)
-    //     ;
-    //     
-    //     if ( !blockers.size() )
-    //         this.game.moveThingTo(this, x,y);
-    // },
-    
-    
     
     
     updateMeta : function updateMeta(evt){
index fbbb7fd..a110d75 100644 (file)
@@ -26,7 +26,7 @@ Tank =
 exports['Tank'] =
 Thing.subclass('Tank', function(Tank){
     
-    Y.core.extend(this, {
+    Y.core.descriptors(this, {
         projectile : Bullet,
         blocking   : true,
         
@@ -60,12 +60,12 @@ Thing.subclass('Tank', function(Tank){
         buffs : null,
         
         nShots : 0,
-        currentMove : null,
         forceCurrentMove : false,
-        currentMoveLimit : -1
+        currentMoveLimit : -1,
+        
+        trajectory : null
     });
     
-    
     this['init'] =
     function initTank(align){
         Thing.init.call(this, align);
@@ -85,8 +85,12 @@ Thing.subclass('Tank', function(Tank){
         
         var ai = this.ai;
         
+        this.continueMove();
+        return;
+        
         // Check to see if we should obey our last decision, and not recalc
-        if (this.forceCurrentMove && this.forceCurrentMove() && this.currentMoveLimit > NOW) {
+        if (this.forceCurrentMove && this.forceCurrentMove() && this.currentMoveLimit > now) {
+            // console.log('forced!', this.currentMove);
             this.continueMove();
             return this;
         } else
@@ -98,7 +102,7 @@ Thing.subclass('Tank', function(Tank){
             var bs = this.willCollide( this.findNearLike(25, isBullet) );
             // console.log('['+TICKS+':'+this.id, this, '] Shoot down bullets?', bs.size() && bs);
             if ( bs.size() ) {
-                ai.shootIncoming.activate(NOW);
+                ai.shootIncoming.activate(now);
                 var b = this.closestOf(bs);
                 // console.log('  --> Incoming! Shoot it down!', b);
                 this.shoot(b.loc.x, b.loc.y);
@@ -111,7 +115,7 @@ Thing.subclass('Tank', function(Tank){
             var bs = this.willCollide(this.findNearLike(71, isBullet), 5);
             // console.log('['+TICKS+':'+this.id, this, '] Dodge bullets?', bs.size() && bs);
             if (bs.size()) {
-                ai.dodge.activate(NOW);
+                ai.dodge.activate(now);
                 var bullet = this.closestOf(bs);
                 this.moveAwayFrom(bullet);
                 return this;
@@ -123,7 +127,7 @@ Thing.subclass('Tank', function(Tank){
             var t = this.findNearEnemies(71, true).shift();
             // console.log('['+TICKS+':'+this.id, this, '] Shoot at enemies?', t);
             if (t) {
-                ai.shootEnemy.activate(NOW);
+                ai.shootEnemy.activate(now);
                 // console.log('  --> I gotcha!', t);
                 this.shoot(t.loc.x, t.loc.y);
                 return this;
@@ -137,6 +141,65 @@ Thing.subclass('Tank', function(Tank){
     
     
     /**
+     * @return {this}
+     */
+    this['move'] =
+    function move(x,y){
+        if (x instanceof Array) { y=x[_Y]; x=x[_X]; }
+        
+        var loc = this.loc;
+        this.trajectory = new Trajectory(this, loc.x,loc.y, x,y, this.movePerMs);
+        
+        var tvsl = new Traversal(this)
+        ,   to = tvsl.step(this.elapsed, x,y) ;
+        
+        this.game.moveThingTo(this, to.x,to.y);
+        
+        return this;
+    };
+    
+    this['continueMove'] =
+    function continueMove(){
+        if ( !this.currentMove ){ //|| this.ai.path.ready ){
+            var target = this.findNearEnemies(10000).shift();
+            if (target)
+                this.calculatePath(target.loc);
+            else
+                this.currentMove = null;
+        }
+        
+        var to   = this.currentMove
+        ,   path = this.currentPath
+        if (!to) return;
+        
+        this.move(to);
+        if ( this.loc.equals(to) ){
+            // console.log('--> destination reached!', 'to:', to, 'loc:', this.loc);
+            this.currentMove = ((path && path.length) ? path.shift() : null);
+            // console.log('--> next move:', this.currentMove, 'path: ['+path.invoke('toString')+']');
+        }
+    };
+    
+    this['calculatePath'] =
+    function calculatePath(end){
+        if ( !end || !this.ai.path.activate() ) return;
+        
+        // this.currentMove = null;
+        this.forceCurrentMove = false;
+        this.currentMoveLimit = -1;
+        
+        // console.log('calculatePath() moving toward:', end);
+        var pm    = this.game.pathmap
+        ,   start = this.loc
+        
+        ,   path  = this.currentPath = pm.path(this, end)
+        ,   to    = this.currentMove = path.shift()
+        ;
+        // console.log('--> next move:', this.currentMove);
+    };
+    
+    
+    /**
      * TODO: Get this.currentMove etc out of the calc methods.
      */
     this['moveAwayFrom'] =
@@ -153,7 +216,7 @@ Thing.subclass('Tank', function(Tank){
         ,   to  = this.currentMove = trj.near(x,y);
         
         this.forceCurrentMove = this.willCollide.bind(this, [agent], 5);
-        this.currentMoveLimit = NOW + 1000;
+        this.currentMoveLimit = this.now + 1000;
         
         this.move(to.x, to.y);
         
@@ -243,7 +306,7 @@ Thing.subclass('Tank', function(Tank){
         
         if (xydef) this.rotateBarrel(x,y);
         
-        if ( this.nShots >= this.stats.shots || !this.cooldowns.attack.activate(NOW) )
+        if ( this.nShots >= this.stats.shots || !this.cooldowns.attack.activate(this.now) )
             return null;
         
         var tloc = this.getTurretLoc()
@@ -286,93 +349,6 @@ Thing.subclass('Tank', function(Tank){
         return p;
     };
     
-    /**
-     * @return {this}
-     */
-    this['move'] =
-    function move(x,y){
-        if (x instanceof Array) { y=x[_Y]; x=x[_X]; }
-        var loc = this.loc
-        ,   cur = this.currentMove
-        ;
-        
-        if ( !(this.trajectory && cur && cur.x === x && cur.y === y) )
-            this.trajectory = new Trajectory(this, loc.x,loc.y, x,y, this.movePerMs);
-        
-        var tvsl = new Traversal(this)
-        ,   to = tvsl.step(this.elapsed, x,y)
-        ;
-        
-        this.game.moveThingTo(this, to.x,to.y);
-        return this;
-    };
-    
-    /** 
-     * @protected
-     * This method does not update this.trajectory -- call this.move(x,y) instead.
-     */
-    // this['moveByAngle'] =
-    // function moveByAngle(theta, targetX,targetY){
-    //     var abs = Math.abs
-    //     ,   bb  = this.boundingBox
-    //     ,   loc = this.loc
-    //     ,   to  = loc.moveByAngle(theta, this.stats.move * SQUARETH)
-    //     ,   x = to.x, y = to.y
-    //     ;
-    //     
-    //     // Don't overshoot the target
-    //     if ( targetX !== undefined && abs(loc.x-x) > abs(loc.x-targetX) )
-    //         x = targetX;
-    //     
-    //     if ( targetY !== undefined && abs(loc.y-y) > abs(loc.y-targetY) )
-    //         y = targetY;
-    //     
-    //     var nbb = bb.relocated(x,y)
-    //     ,   blockers = this.game.pathmap.get(nbb.x1,nbb.y1, nbb.x2,nbb.y2).remove(this);
-    //     
-    //     if ( !blockers.size() )
-    //         this.game.moveThingTo(this, x,y);
-    //     
-    //     return this;
-    // };
-    
-    
-    this['continueMove'] =
-    function continueMove(){
-        if ( !this.currentMove || this.ai.path.ready ){
-            var target = this.findNearEnemies(10000).shift();
-            if (target) this.calculatePath(target.loc);
-        }
-        
-        var to = this.currentMove;
-        if (!to) return;
-        
-        this.move(to);
-        if ( this.loc.equals(to) ) {
-            this.forceCurrentMove = false;
-            this.currentMoveLimit = -1;
-            this.currentMove = (this.lastPath ? this.lastPath.shift() : null);
-        }
-    };
-    
-    this['calculatePath'] =
-    function calculatePath(end){
-        if ( !(end && this.ai.path.activate(NOW)) ) return;
-        
-        // this.forceCurrentMove = Y.op.K(true);
-        // this.currentMoveLimit = NOW + 750;
-        this.forceCurrentMove = false;
-        this.currentMoveLimit = -1;
-        
-        // console.log(this, 'moving toward', t);
-        var pm    = this.game.pathmap
-        ,   start = this.loc
-        
-        ,   path  = this.lastPath    = pm.path(this, end)
-        ,   to    = this.currentMove = path.shift()
-        ;
-    };
-    
     
     this['ableToShoot'] =
     function ableToShoot(){
index d0ac852..dbdcb8a 100644 (file)
@@ -19,6 +19,7 @@ qkv = Y(window.location.search.slice(1)).fromKV();
 // Main method is only executed once, so we'll setup things
 // that don't change between games.
 function main(){
+    $('#welcome').center();
     
     /// Debug ///
     if (qkv.ai) {
@@ -56,6 +57,7 @@ function main(){
     $('#welcome').clone()
         .attr('id', 'pause')
         .hide()
+        .css({ 'top':'1em', 'left':'1em', 'margin':0, 'width':'auto' })
         .appendTo( $('body') )
         .find('.box').html('<h1>Paused!</h1>');
     
@@ -151,7 +153,7 @@ function startGame(evt){
 }
 
 function pauseGame(evt){
-    $('#overlay').toggle();
+    // $('#overlay').toggle();
     $('#pause').toggle();
     toggleGame();
 }
@@ -266,6 +268,19 @@ function updateInfo(){
     return false;
 }
 
+jQuery.fn.center = function centerJQ(){
+    var body = $('body');
+    this.css({
+        'left' : '50%', 'top': '50%',
+        'margin-left'     : -0.5*this.width(),
+        'margin-top'      : -0.5*this.height(),
+        // 'left'     : 0.5*(body.width()  - this.width()),
+        // 'top'      : 0.5*(body.height() - this.height()),
+        'position' :'absolute'
+    });
+    return this;
+};
+
 
 exports['main']         = main;
 exports['gameExists']   = gameExists;
index a4c658c..b2af103 100644 (file)
@@ -1,6 +1,6 @@
 var Y       = require('Y').Y
 ,   Rect    = require('ezl/shape').Rect
-,   astar   = require('ezl/util/astar')
+,   vec     = require('ezl/math/vec')
 ,   PathMap = require('tanks/map/pathmap').PathMap
 ,   config  = require('tanks/config').config
 ,
@@ -26,12 +26,16 @@ Rect.subclass('PathMapUI', {
     init : function initPathMapUI(game, pathmap){
         this.game = game;
         this.pathmap = pathmap;
+        this.highlights = Y([]);
         
         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);
+        // Store ref to original path function
+        this._path = pathmap.path;
+        pathmap.path = this.pathWithUI.bind(this);
+        
+        this.cleanUpAgent  = this.cleanUpAgent.bind(this);
+        this.drawPathStep = this.drawPathStep.bind(this);
     },
     
     render : function render(ctx){
@@ -56,27 +60,16 @@ Rect.subclass('PathMapUI', {
         }, {});
     },
     
-    
     /**
-     * Wraps PathMap.gridPath() to draw AI pathes.
+     * Wraps PathMap.path() 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))
+    pathWithUI : function pathWithUI(agent, x,y){
+        var pm    = this.pathmap
+        ,   path  = this._path.call(pm, agent, x,y)
+        ,   start = vec.sum(pm._getSquare(agent.loc), pm.gridSquareMid)
         ;
-        
         if (this.overlayAiPaths)
-            this.drawPath(agent, startN, path);
+            this.drawPath(agent, start, Y(path));
         
         return path;
     },
@@ -119,36 +112,60 @@ Rect.subclass('PathMapUI', {
         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());
-        
+        // Don't draw anything if no path exists
+        if ( path && path.length ) {
+            
+            // 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 ;
+    drawPathStep : function drawPathStep(ctx, p, idx){
+        var SIZE = this.gridSquare
+        ,   off  = SIZE*(idx === -1 ? 0.15 : 0.3)
+        ,   r    = SIZE/2 - off
+        ;
         
         ctx.beginPath();
-        ctx.arc(x,y, r, 0, Math.PI*2, false);
+        ctx.arc(p.x,p.y, r, 0, Math.PI*2, false);
         ctx.fill();
         ctx.closePath();
         
         return ctx;
-    }
+    },
+    
+    hl : function highlight(x,y){
+        if (x instanceof Array) { y=x.y;  x=x.x; }
+        var SIZE = REF_SIZE
+        ,   x1 = Math.floor(x/SIZE)*SIZE
+        ,   y1 = Math.floor(y/SIZE)*SIZE
+        
+        ,   H  = new Rect(SIZE,SIZE)
+                .position(x1,y1)
+                .fill('rgba(166,217,255, 0.1)')
+                .stroke('rgba(166,217,255, 0.75)', 1)
+                .appendTo(this)
+        ;
+        this.highlights.push(H);
+        this.draw();
+        return H;
+    },
+    
+    clearhl : function clearHL(){
+        this.highlights.invoke('remove').clear();
+        return this.draw();
+    },
     
     
 });
index e7393c6..7b1d73f 100644 (file)
@@ -2,6 +2,7 @@
 <script src="build/future.js" type="text/javascript"></script>
 <script src="build/functional/to-function.js" type="text/javascript"></script>
 <script src="build/Y/type.js" type="text/javascript"></script>
+<script src="build/Y/internal.js" type="text/javascript"></script>
 <script src="build/Y/core.js" type="text/javascript"></script>
 <script src="build/Y/delegate.js" type="text/javascript"></script>
 <script src="build/Y/types/function.js" type="text/javascript"></script>
@@ -25,8 +26,8 @@
 <script src="build/ezl/loop/eventloop.js" type="text/javascript"></script>
 <script src="build/evt.js" type="text/javascript"></script>
 <script src="build/ezl/loc/loc.js" type="text/javascript"></script>
-<script src="build/ezl/math/rect.js" type="text/javascript"></script>
 <script src="build/ezl/math/line.js" type="text/javascript"></script>
+<script src="build/ezl/math/rect.js" type="text/javascript"></script>
 <script src="build/ezl/math.js" type="text/javascript"></script>
 <script src="build/ezl/loop/fx.js" type="text/javascript"></script>
 <script src="build/ezl/loc/boundingbox.js" type="text/javascript"></script>