From 8278d2888757f9bdf14d51204a6dd0c13a6bd228 Mon Sep 17 00:00:00 2001 From: dsc Date: Thu, 23 Dec 2010 21:04:42 -0800 Subject: [PATCH] Resolves pathing bug. --- src/Y/core.cjs | 135 ++++++++++++++++++++++++-------- src/Y/delegate.cjs | 38 ++++++---- src/Y/internal.cjs | 11 +++ src/Y/types/array.cjs | 7 ++ src/Y/types/chain.cjs | 8 ++ src/Y/types/collection.cjs | 135 ++++++++++++++++---------------- src/Y/types/function.cjs | 2 - src/Y/types/object.cjs | 59 +++++++++++++- src/Y/types/string.cjs | 2 +- src/Y/utils.cjs | 7 ++- src/ezl/loop/cooldown.cjs | 2 +- src/ezl/loop/eventloop.cjs | 7 +- src/ezl/util/tree/quadtree.cjs | 119 +++++++++++++++++++++------- src/tanks/map/level.cjs | 2 +- src/tanks/map/pathmap.cjs | 10 +- src/tanks/map/trajectory.cjs | 88 ++------------------- src/tanks/map/traversal.cjs | 22 +++-- src/tanks/thing/player.cjs | 21 ----- src/tanks/thing/tank.cjs | 170 +++++++++++++++++----------------------- src/tanks/ui/main.cjs | 17 ++++- src/tanks/ui/pathmapui.cjs | 101 ++++++++++++++---------- www/deps.html | 3 +- 22 files changed, 556 insertions(+), 410 deletions(-) create mode 100644 src/Y/internal.cjs create mode 100644 src/Y/types/chain.cjs diff --git a/src/Y/core.cjs b/src/Y/core.cjs index b13737c..d400314 100644 --- a/src/Y/core.cjs +++ b/src/Y/core.cjs @@ -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 diff --git a/src/Y/delegate.cjs b/src/Y/delegate.cjs index 469a1e0..7a68061 100644 --- a/src/Y/delegate.cjs +++ b/src/Y/delegate.cjs @@ -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 index 0000000..1486e1a --- /dev/null +++ b/src/Y/internal.cjs @@ -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); }; + diff --git a/src/Y/types/array.cjs b/src/Y/types/array.cjs index f4a8733..8b6fc0a 100644 --- a/src/Y/types/array.cjs +++ b/src/Y/types/array.cjs @@ -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 index 0000000..98fc812 --- /dev/null +++ b/src/Y/types/chain.cjs @@ -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') +; + diff --git a/src/Y/types/collection.cjs b/src/Y/types/collection.cjs index beb799e..8611eb7 100644 --- a/src/Y/types/collection.cjs +++ b/src/Y/types/collection.cjs @@ -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); } diff --git a/src/Y/types/function.cjs b/src/Y/types/function.cjs index e11e31b..63ff66c 100644 --- a/src/Y/types/function.cjs +++ b/src/Y/types/function.cjs @@ -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['_'] = _; diff --git a/src/Y/types/object.cjs b/src/Y/types/object.cjs index 63e89f7..b06d0e3 100644 --- a/src/Y/types/object.cjs +++ b/src/Y/types/object.cjs @@ -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; } + }); diff --git a/src/Y/types/string.cjs b/src/Y/types/string.cjs index 8acefdb..4339f9a 100644 --- a/src/Y/types/string.cjs +++ b/src/Y/types/string.cjs @@ -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, diff --git a/src/Y/utils.cjs b/src/Y/utils.cjs index b55e8b2..a27df65 100644 --- a/src/Y/utils.cjs +++ b/src/Y/utils.cjs @@ -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) ) diff --git a/src/ezl/loop/cooldown.cjs b/src/ezl/loop/cooldown.cjs index fbf4d45..f0ebf8a 100644 --- a/src/ezl/loop/cooldown.cjs +++ b/src/ezl/loop/cooldown.cjs @@ -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; diff --git a/src/ezl/loop/eventloop.cjs b/src/ezl/loop/eventloop.cjs index 6d2895f..5bc35dd 100644 --- a/src/ezl/loop/eventloop.cjs +++ b/src/ezl/loop/eventloop.cjs @@ -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 }); diff --git a/src/ezl/util/tree/quadtree.cjs b/src/ezl/util/tree/quadtree.cjs index 14e3713..6f1b489 100644 --- a/src/ezl/util/tree/quadtree.cjs +++ b/src/ezl/util/tree/quadtree.cjs @@ -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 < x2 and y1 < 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 < x2 and y1 < 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 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; +} diff --git a/src/tanks/map/level.cjs b/src/tanks/map/level.cjs index 3da05b1..fc4f88b 100644 --- a/src/tanks/map/level.cjs +++ b/src/tanks/map/level.cjs @@ -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); diff --git a/src/tanks/map/pathmap.cjs b/src/tanks/map/pathmap.cjs index 7c6cc28..a522cbb 100644 --- a/src/tanks/map/pathmap.cjs +++ b/src/tanks/map/pathmap.cjs @@ -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 diff --git a/src/tanks/map/trajectory.cjs b/src/tanks/map/trajectory.cjs index f66dbfd..f11de2a 100644 --- a/src/tanks/map/trajectory.cjs +++ b/src/tanks/map/trajectory.cjs @@ -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)+']'; } diff --git a/src/tanks/map/traversal.cjs b/src/tanks/map/traversal.cjs index 7fe1e34..c449437 100644 --- a/src/tanks/map/traversal.cjs +++ b/src/tanks/map/traversal.cjs @@ -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; } }) diff --git a/src/tanks/thing/player.cjs b/src/tanks/thing/player.cjs index 619cf4b..fdd177a 100644 --- a/src/tanks/thing/player.cjs +++ b/src/tanks/thing/player.cjs @@ -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){ diff --git a/src/tanks/thing/tank.cjs b/src/tanks/thing/tank.cjs index fbbb7fd..a110d75 100644 --- a/src/tanks/thing/tank.cjs +++ b/src/tanks/thing/tank.cjs @@ -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(){ diff --git a/src/tanks/ui/main.cjs b/src/tanks/ui/main.cjs index d0ac852..dbdcb8a 100644 --- a/src/tanks/ui/main.cjs +++ b/src/tanks/ui/main.cjs @@ -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('

Paused!

'); @@ -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; diff --git a/src/tanks/ui/pathmapui.cjs b/src/tanks/ui/pathmapui.cjs index a4c658c..b2af103 100644 --- a/src/tanks/ui/pathmapui.cjs +++ b/src/tanks/ui/pathmapui.cjs @@ -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(); + }, }); diff --git a/www/deps.html b/www/deps.html index e7393c6..7b1d73f 100644 --- a/www/deps.html +++ b/www/deps.html @@ -2,6 +2,7 @@ + @@ -25,8 +26,8 @@ - + -- 1.7.0.4