var args = slice.call(arguments,1);
return function(obj){
if (obj && obj[name])
- return obj[name].apply( obj, args.concat(slice.call(arguments,0)) );
+ return obj[name].apply( obj, args.concat(slice.call(arguments,1)) );
else
return obj;
};
var newYArray = YArray.instantiate.bind(YArray);
- mixin(YArray, { donor:_Array,
+ mixin({ target:YArray, donor:_Array, context:'_o',
names:'indexOf lastIndexOf shift pop join'.split(' ') });
- mixin(YArray, { donor:_Array, chain:true,
+ mixin({ target:YArray, donor:_Array, context:'_o', chain:true,
names:'push unshift sort splice reverse'.split(' ') });
- mixin(YArray, { donor:_Array, fnFirst:true, wrap:newYArray,
+ mixin({ target:YArray, donor:_Array, context:'_o', fnFirst:true, wrap:newYArray,
names:'map forEach filter'.split(' ') });
- mixin(YArray, { donor:_Array, fnFirst:true, names:['reduce', 'some', 'every'] });
- mixin(YArray, { donor:_Array, wrap:newYArray, names:['slice'] });
+ mixin({ target:YArray, donor:_Array, context:'_o', fnFirst:true,
+ names:['reduce', 'some', 'every'] });
+ mixin({ target:YArray, donor:_Array, context:'_o', wrap:newYArray,
+ names:['slice'] });
exports['YString'] =
YCollection.subclass('YString', function(YString){
- mixin(YString, { donor:String, wrap:YString.instantiate.bind(YString),
+ var newYString = YString.instantiate.bind(YString);
+ mixin({ target:YString, donor:String, context:'_o', wrap:newYString,
names:'slice substr substring concat replace toLowerCase toUpperCase'.split(' ') });
- mixin(YString, { donor:String,
+ mixin({ target:YString, donor:String, context:'_o',
names:'split indexOf lastIndexOf charAt charCodeAt'.split(' ') });
function trim(val){
var YFunction = require('Y/types/function').YFunction
-, isFunction = require('Y/type').isFunction
+, type = require('Y/type')
, core = require('Y/core')
, op = require('Y/op')
, slice = core.slice
+, isFunction = type.isFunction
+, isArray = type.isArray
;
}
-var defaults = {
- 'donor' : null,
- 'names' : null,
+var
+This = mixin['This'] = {},
+Target = mixin['Target'] = {},
+defaults = {
+ /**
+ * {Object|Function} target Required. Target object onto which methods will be added. If this a Function, it's prototype will be used.
+ */
+ 'target' : null,
+
+ /**
+ * {Object|Function} donor Required. Donor object on which to look up names. If this a Function, it's prototype will be used.
+ */
+ 'donor' : null,
+
+ /**
+ * {Array} names Required. Names to mix into `target`.
+ */
+ 'names' : null,
+
+ /**
+ * {mixin.This|mixin.Target|String} [context=mixin.This]
+ * Sets the call context for the function.
+ * - {mixin.This}: (Default) Use runtime `this` as context.
+ * - {mixin.Target}: Use `target` as context.
+ * - {String}: Names a property on `this` to use. If specified and property is false-y, `this` is used.
+ */
+ 'context' : This,
+
+ /**
+ * {Boolean} [override=true] Whether to override existing methods of the same name.
+ */
'override' : true,
- 'wrap' : false,
- 'chain' : false,
- 'fnFirst' : false
+
+ /**
+ * {Boolean} [chain=false] If true, the method always returns `this`. Supercedes `wrap`.
+ */
+ 'chain' : false,
+
+ /**
+ * {Function} [wrap] If supplied, this function is called on the result of the delegate-call. The result
+ * from that call is then returned. This option is superceded by `chain`.
+ */
+ 'wrap' : false,
+
+ /**
+ * {Boolean} [fnFirst=false] If true, `Function.toFunction()` will be called on the first argument
+ * received by the method.
+ */
+ 'fnFirst' : false
};
-function mixin(o, options){
- var opt = core.extend({}, defaults, options), Donor = opt.donor
- , target = ( isFunction(o) ? o.prototype : o )
- , proto = ( isFunction(Donor) ? Donor.prototype : Donor )
- , names = opt.names
+function mixin(options){
+ var o = core.extend({}, defaults, options)
+ , target = o.target
+ , donor = o.donor
+ , names = o.names
+ , cxt = o.context
;
+ // TODO: Throw errors on invalid values
+
+ if (isFunction(target)) target = target.prototype;
+ if (isFunction(donor)) donor = donor.prototype;
+ if (!isArray(names)) names = [names];
+
// Need a closure to capture the name
names.forEach(function(name){
- var fn = proto[name];
- if ( isFunction(fn) && (opt.override || !(name in target)) )
- target[name] = function(){
- var r, A = arguments;
- if (opt.fnFirst) A[0] = Function.toFunction(A[0]);
- r = fn.apply(this._o || target, A);
- return (opt.chain ? this : (opt.wrap ? opt.wrap(r) : r));
- };
+ var fn = donor[name];
+ if ( !(isFunction(fn) && (o.override || !(name in target))) )
+ return;
+
+ target[name] = function(){
+ var A = arguments, r, context;
+
+ if (o.fnFirst)
+ A[0] = Function.toFunction(A[0]);
+
+ if (cxt === This)
+ context = this;
+ else if (cxt === Target)
+ context = target;
+ else
+ context = this[cxt] || this;
+
+ r = fn.apply(context, A);
+ return (o.chain ? this : (o.wrap ? o.wrap(r) : r));
+ };
});
return o;
}
exports['bindAll'] = bindAll;
-exports['mixin'] = mixin;
+exports['mixin'] = mixin;
},
/**
- * Changes origin definition without moving the bounds.
+ * Changes origin position without moving the bounds.
* Use relocate() to change the position by moving the origin.
*/
- set originX(v){ this._origin.x = v; return v; },
- get originX(){ return this._origin.absolute(this.width,this.height, this.x1,this.y1).x; },
+ set origin(o){ this._origin = o; return o; },
+ get origin(){ return this._origin; },
- set originY(v){ this._origin.y = v; return v; },
- get originY(){ return this._origin.absolute(this.width,this.height, this.x1,this.y1).y; },
+ /**
+ * Changes origin x-position without moving the bounds.
+ * Use relocate() to change the position by moving the origin.
+ */
+ set originX(v){ this._origin.x = v - this.x1; return v; },
+ get originX(){ return this.absOrigin.x; },
/**
- * Accessor for the realized numeric origin.
+ * Changes origin y-position without moving the bounds.
+ * Use relocate() to change the position by moving the origin.
*/
- get absOrigin() { return this._origin.absolute(this.width,this.height, this.x1,this.y1); },
- get relOrigin() { return this._origin.absolute(this.width,this.height); },
+ set originY(v){ this._origin.y = v - this.y1; return v; },
+ get originY(){ return this.absOrigin.y; },
- get origin(){ return this._origin; },
+ // Accessors for the realized numeric origin.
+ get absOrigin() { return this._origin.absolute(this.width,this.height, this.x1,this.y1); }, // TODO: cache absolute and invalidate
+ set absOrigin(v) { this._origin.setXY(v.x - this.x1, v.y - this.y1); return this._origin; },
+ get relOrigin() { return this._origin.absolute(this.width,this.height); }, // TODO: cache absolute and invalidate
+ set relOrigin(v) { this._origin.setXY(v.x, v.y); return this._origin; },
+
+
+ // Accessors for distance from origin to requested bound.
+ get originLeft(){ return this.relOrigin.x; },
+ get originTop(){ return this.relOrigin.y; },
+ get originRight(){ return this.width - this.relOrigin.x; },
+ get originBottom(){ return this.height - this.relOrigin.y; },
+ /**
+ * Calculates realized distance from origin to requested bound.
+ * @param {String} which Bound to use in calculation: left|right|bottom|top.
+ * @return {Number} Distance from origin to requested bound.
+ */
+ originDist : function originDist(which){
+ switch (which) {
+ case 'top' : return this.originTop;
+ case 'bottom' : return this.originBottom;
+ case 'left' : return this.originLeft;
+ case 'right' : return this.originRight;
+ default : throw new Error('WTF kind of side is '+which+'???');
+ }
+ },
+
+ /**
+ * Determines the point and line of intersection for the specified bounding boxes
+ * along the given trajectory.
+ */
+ // collision : function collision(trj, fromBB, toBB){
+ //
+ // if (bb.x2 <= B.x1 && x2 >= B.x1) {
+ // side = B.leftSide;
+ // to = trj.pointAtX(B.x1-offX-1);
+ //
+ // } else if (bb.x1 >= B.x2 && x1 <= B.x2) {
+ // side = B.rightSide;
+ // to = trj.pointAtX(B.x2+ro.x+1);
+ //
+ // } else if (bb.y2 <= B.y1 && y2 >= B.y1) {
+ // side = B.topSide;
+ // to = trj.pointAtY(B.y1-offY-1);
+ //
+ // } else if (bb.y1 >= B.y2 && y1 <= B.y2) {
+ // side = B.bottomSide;
+ // to = trj.pointAtY(B.y2+ro.y+1);
+ // }
+ // },
+
+ /**
+ * Moves absolute location of the origin and retains its relative position of the bounds.
+ * This is an in-place modification of the BoundingBox.
+ */
relocate : function relocate(x,y){
if (x instanceof Array) { y=x[1]; x=x[0]; }
var _x1 = this[X1], _y1 = this[Y1]
return this.set4(x1,y1, x2,y2);
},
+ /**
+ * As relocate(), but returns a new BoundingBox.
+ */
relocated : function relocated(x,y){
return this.clone().relocate(x,y);
},
+ /**
+ * Resizes bounds to proportionally maintain their distance from the origin.
+ * This is an in-place modification of the BoundingBox.
+ */
resize : function resize(w,h){
if (w instanceof Array) { h=w[1]; w=w[0]; }
var x1 = this[X1], y1 = this[Y1]
, xp = o.xPercentage, yp = o.yPercentage
;
if ( xp !== null ) {
- // diff = w - wOld;
abs = x1 + xp*wOld;
x1 = abs - xp*w;
x2 = abs + (1-xp)*w;
x2 = x1 + w;
if ( yp !== null ) {
- // diff = h - hOld;
abs = y1 + yp*hOld;
y1 = abs - yp*h;
y2 = abs + (1-yp)*h;
return this.set4(x1,y1, x2,y2);
},
+ /**
+ * As resize, but returns a new BoundingBox.
+ */
resized : function resized(w,h){
return this.clone().resize(w,h);
},
clone : function clone(){
var o = this._origin;
return new BoundingBox(this[X1],this[Y1], this[X2],this[Y2], o[X1], o[Y1]);
- },
-
-
- get topSide(){ return new Line(this[X1],this[Y1], this[X2],this[Y1]); },
- get bottomSide(){ return new Line(this[X1],this[Y2], this[X2],this[Y2]); },
- get leftSide(){ return new Line(this[X1],this[Y1], this[X1],this[Y2]); },
- get rightSide(){ return new Line(this[X2],this[Y1], this[X2],this[Y2]); },
-
- side : function side(which){
- switch (which) {
- case 'top' : return this.topSide;
- case 'bottom' : return this.bottomSide;
- case 'left' : return this.leftSide;
- case 'right' : return this.rightSide;
- default : throw new Error('Unknown side: '+which);
- }
}
});
var Y = require('Y').Y
-, Vec = require('ezl/math/vec').Vec
+, vec = require('ezl/math/vec')
;
Y(exports, {
- 'Vec' : Vec,
+ 'Vec' : vec.Vec,
'Line' : require('ezl/math/line').Line,
'Rect' : require('ezl/math/rect').Rect,
},
'reflect' : function reflect(v, line){
- var dot = Vec.dot
- , basev = Vec.difference(v, line.p1);
+ var dot = vec.dot
+ , basev = vec.difference(v, line.p1);
return line.vec()
.scale(2 * dot(basev,line) / dot(line,line))
.subtract(basev)
* @return {Float} Elapsed parametric time needed to move the given delta-(x,y).
*/
timeToMove : function timeToMove(dx,dy){
- // see note at iparametric
- return (dx/this.pa + dy/this.pb) * 0.5;
+ if (dx instanceof Array) { dy=dx[1]; dx=dx[0]; }
+ return (Math.abs(dx/this.pa) + Math.abs(dy/this.pb)) * 0.5; // see note at iparametric
},
parametric : function parametric(t){
|| ( (cy2 = line.calcY(x2)) >= y1 && cy2 <= y2 ) );
},
+ get topSide(){ return new Line(this[X1],this[Y1], this[X2],this[Y1]); },
+ get bottomSide(){ return new Line(this[X1],this[Y2], this[X2],this[Y2]); },
+ get leftSide(){ return new Line(this[X1],this[Y1], this[X1],this[Y2]); },
+ get rightSide(){ return new Line(this[X2],this[Y1], this[X2],this[Y2]); },
+
+ side : function side(which){
+ switch (which) {
+ case 'top' : return this.topSide;
+ case 'bottom' : return this.bottomSide;
+ case 'left' : return this.leftSide;
+ case 'right' : return this.rightSide;
+ default : throw new Error('WTF kind of side is '+which+'???');
+ }
+ },
+
clone : function clone(){
return new this.__class__(this[X1],this[Y1], this[X2],this[Y2]);
},
});
+// Can't import from math due to deps
function lerp(x, a, b) { return a + x*(b - a); }
-Y.extend(Vec, {
+Y.extend(exports, {
sum : function sum(a, b) {
return new Vec(a[_X]+b[_X], a[_Y]+b[_Y]);
},
-function BinaryHeap(score){
+/**
+ * A Binary Heap.
+ * @param {String|Function} score Scoring mechanism for contents:
+ * - {String} Property on values which contains the numeric score
+ * - {Function} Invoked with the value to get a numeric score
+ * @param {Array} [vals] Initial values.
+ */
+function BinaryHeap(score, vals){
if (typeof score === 'string')
this.key = score;
else
this.score = score;
+
+ if (vals)
+ for (var i=0, L=vals.length; i<L; ++i)
+ this.push(vals[i]);
}
var AP = Array.prototype
, methods = {
_push : AP.push,
- _pop: AP.pop,
+ _pop : AP.pop,
// Replaced by score function if supplied
score : function score(el){
return el[this.key];
},
- push: function(el) {
+ push : function heapPush(el) {
// Add the new element to the end of the array.
this._push(el);
return this;
},
- pop: function() {
+ pop : function heapPop() {
// Store the first element so we can return it later.
var result = this[0]
// If there are any elements left, put the end element at the
// start, and let it bubble up.
- if ( this.length > 0 ) {
+ if ( this.length ) {
this[0] = end;
this.heapUp(0);
}
return result;
},
- remove: function(node) {
+ remove : function remove(node) {
var key = this.key
, i = this.indexOf(node)
if ( i !== this.length-1 ) {
this[i] = end;
- var endScore = (key ? end[key] : this.score(end))
+ var endScore = (key ? end[key] : this.score(end))
, nodeScore = (key ? node[key] : this.score(node)) ;
if ( endScore < nodeScore )
this.heapDown(i);
return this;
},
- rescore: function(node) {
+ rescore : function rescore(node) {
this.heapDown(this.indexOf(node));
return this;
},
- heapDown: function(n) {
+ heapDown : function heapDown(n) {
var key = this.key
, el = this[n]
, elScore = (key ? el[key] : this.score(el))
}
},
- heapUp: function(n) {
+ heapUp : function heapUp(n) {
var key = this.key
, len = this.length
, el = this[n]
SQUARETH = REF_SIZE * SECONDTH
this.active.invoke('updateCooldowns', ELAPSED, NOW);
- this.active.invoke('act');
+ this.active.invoke('act', ELAPSED, NOW);
this.animations = this.animations.filter(this.tickAnimations, this);
this.draw();
var Y = require('Y').Y;
Y.extend(exports, {
- 'Level' : require('tanks/map/level').Level,
- 'Wall' : require('tanks/map/wall').Wall,
+ 'Map' : require('tanks/map/map').Map,
'PathMap' : require('tanks/map/pathmap').PathMap,
+ 'Wall' : require('tanks/map/wall').Wall,
+ 'Level' : require('tanks/map/level').Level,
+ 'Traversal' : require('tanks/map/traversal').Traversal,
'Trajectory' : require('tanks/map/trajectory').Trajectory
});
--- /dev/null
+// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
+var Y = require('Y').Y
+
+, math = require('ezl/math')
+, vec = require('ezl/math/vec')
+, QuadTree = require('ezl/util/tree/quadtree').QuadTree
+, astar = require('ezl/util/astar')
+
+, config = require('tanks/config').config
+, Bullet = require('tanks/thing/bullet').Bullet
+
+, Vec = vec.Vec
+, Line = math.Line
+,
+
+
+Map =
+exports['Map'] =
+QuadTree.subclass('Map', {
+
+ addBlocker : function addBlocker(obj){
+ this.removeBlocker(obj);
+ var bb = obj.boundingBox;
+ if (obj.blocking && bb)
+ obj.region = this.set(bb.x1,bb.y1, bb.x2,bb.y2, obj);
+ return obj;
+ },
+
+ removeBlocker : function removeBlocker(obj){
+ if (obj.region) {
+ this.remove(obj.region);
+ delete obj.region;
+ }
+ return obj;
+ },
+
+ // moveBlocked : function moveBlocked(agent, trj, to, bb){
+ // bb = bb || agent.boundingBox;
+ // var blockers, blocker, msg
+ // , side = null
+ // , tobb = bb.relocated(to.x,to.y)
+ // , x1 = tobb.x1, y1 = tobb.y1
+ // , x2 = tobb.x2, y2 = tobb.y2
+ //
+ // , ro = bb.relOrigin
+ // , bw = bb.width, bh = bb.height
+ // , offX = bw - ro.x, offY = bh = ro.y
+ //
+ // // , x1 = to.x+offX, y1 = to.y+offY
+ // // , x2 = x1+bw, y2 = y1+bh
+ // ;
+ //
+ // blockers = this.get(x1,y1, x2,y2).remove(agent).end();
+ // blocker = blockers[0];
+ //
+ // // Not blocked
+ // if (!blocker) return false;
+ //
+ // // Literal corner case :P
+ // // XXX: needs better detection of corner
+ // if (blockers.length > 1) {
+ // msg = 'corner of '+blockers.slice(0);
+ // to = trj.p1;
+ //
+ // // Normal reflection against one line
+ // } else {
+ // msg = blocker+' on the ';
+ //
+ // var B = blocker.boundingBox;
+ // if (bb.x2 <= B.x1 && x2 >= B.x1) {
+ // msg += 'left';
+ // side = B.leftSide;
+ // to = trj.pointAtX(B.x1-offX-1);
+ //
+ // } else if (bb.x1 >= B.x2 && x1 <= B.x2) {
+ // msg += 'right';
+ // side = B.rightSide;
+ // to = trj.pointAtX(B.x2+ro.x+1);
+ //
+ // } else if (bb.y2 <= B.y1 && y2 >= B.y1) {
+ // msg += 'top';
+ // side = B.topSide;
+ // to = trj.pointAtY(B.y1-offY-1);
+ //
+ // } else if (bb.y1 >= B.y2 && y1 <= B.y2) {
+ // msg += 'bottom';
+ // side = B.bottomSide;
+ // to = trj.pointAtY(B.y2+ro.y+1);
+ // }
+ //
+ // msg += ' side';
+ // }
+ //
+ // return { 'msg':msg, 'blockers':blockers, 'side':side, 'to':to };
+ // },
+
+
+
+});
// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
-var Y = require('Y').Y
-, math = require('ezl/math')
-, QuadTree = require('ezl/util/tree/quadtree').QuadTree
-, astar = require('ezl/util/astar')
-, config = require('tanks/config').config
-, Bullet = require('tanks/thing/bullet').Bullet
-, Vec = math.Vec
-, Line = math.Line
+var Y = require('Y').Y
+, math = require('ezl/math')
+, vec = require('ezl/math/vec')
+, QuadTree = require('ezl/util/tree/quadtree').QuadTree
+, BinaryHeap = require('ezl/util/binaryheap').BinaryHeap
+
+, config = require('tanks/config').config
+, Bullet = require('tanks/thing/bullet').Bullet
+, Map = require('tanks/map/map').Map
+, Vec = vec.Vec
+, Line = math.Line
+
+
+, SQRT_TWO = Math.sqrt(2)
, PASSABLE = 1 // Does not obstruct other objects
, BLOCKING = 2 // Objstructs other blockers
, ZONE = 3 // Does not obstruct other objects, but still collides with them
,
+// Would be nice if I could make this an inner class (for once)
+Square =
+exports['Square'] =
+Vec.subclass('Square', {
+ pathId : null, // instance of A* being run, to track when to reset
+
+ // resetable:
+ dist : 0, // estimated distance
+ startDist : 0, // distance from start
+ endDist : 0, // estimated distance to end
+ visited : false,
+ closed : false,
+ prev : null,
+
+
+ /**
+ * @param x Left coord of square.
+ * @param y Top coord of square.
+ */
+ init : function initSquare(pathmap, x,y){
+ Vec.init.call(this, x,y);
+ this.pathmap = pathmap;
+ this.reset();
+ },
+
+ reset : function reset(){
+ var pathId = this.pathmap._pathId;
+ if (this.pathId === pathId)
+ return this;
+
+ this.pathId = pathId;
+ this._blocked();
+
+ this.dist = 0;
+ this.startDist = 0;
+ this.endDist = 0;
+ this.visited = false;
+ this.closed = false;
+ this.prev = null;
+
+ return this;
+ },
+
+ /**
+ * @private Caches this.blocked value. Should only be called by reset().
+ */
+ _blocked : function blocked(){
+ var pm = this.pathmap
+ , agent = pm._agent
+ , bb = agent.boundingBox
+
+ , 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)
+ , y = Math.floor(this.y)
+ , x1 = x - left, x2 = x + SIZE + right
+ , y1 = y - top, y2 = y + SIZE + bottom
+
+ , blockers = pm.get(x1,y1, x2,y2)
+ .remove(agent)
+ .filter(this._filterBlocked)
+ ;
+
+ this.blocked = !!blockers.length;
+ },
+
+ /**
+ * @private
+ */
+ _filterBlocked : function filterBlocked(v, r){
+ return !(v.isBoundary || v instanceof Bullet); // XXX: use PASSABLE
+ },
+
+ getNeighbors : function getNeighbors(){
+ var neighbors = []
+ , abs = Math.abs
+ , pm = this.pathmap
+ , agent = pm._agent
+ , SIZE = pm.gridSquare
+
+ , x = this.x, y = this.y
+ , sq, cost, ix, iy
+ ;
+
+ for (ix=-1; ix<2; ix++) {
+ for (iy=-1; iy<2; iy++) {
+ // if (ix === 0 && iy === 0) // skipping diagonals
+ if ( (ix === 0 && iy === 0) || (abs(ix) === 1 && abs(iy) === 1) )
+ continue;
+ // TODO: handle diagonals: need to ensure we don't try to move through a corner
+ // cost = ( abs(ix) === 1 && abs(iy) === 1 ) ? SQRT_TWO : 1;
+ cost = 1;
+ sq = pm._getSquare(x + ix*SIZE, y + iy*SIZE);
+ if ( !sq.blocked ) neighbors.push([ sq, cost*SIZE ]);
+ }
+ }
+
+ return neighbors;
+ }
+
+})
+
+
+
/**
* A QuadTree which aids in pathing for AI.
- * - QuadTree methods which took rect coords (x1,y1, x2,y2) will accept
- * anything Rect-like (having properties x1,y1, x2,y2).
- * - Pathing methods will account for the unit's size when calculating paths,
- * intersections, blocking, and similar tasks.
+ * - QuadTree methods which took rect coords (x1,y1, x2,y2) will accept
+ * anything Rect-like (having properties x1,y1, x2,y2).
+ * - Pathing methods will account for the unit's size when calculating paths,
+ * intersections, blocking, and similar tasks.
*/
PathMap =
exports['PathMap'] =
-QuadTree.subclass('PathMap', {
+Map.subclass('PathMap', {
// Config
gridSquare : null,
gridSquareMid : null,
+ // State
+ _squares : null, // Cache of Square objects
+ _pathId : -1, // Instance of A* being run, to track when to reset Square instances
+
+
init : function init(level, x1,y1, x2,y2, capacity) {
x1 -= 1; y1 -= 1; x2 += 1; y2 += 1;
QuadTree.init.call(this, x1,y1, x2,y2, capacity);
+
this.level = level;
this.game = level.game;
this.walls = level.walls;
this.allWalls = level.allWalls;
+ this._squares = {};
+
this.game.addEventListener('ready', this.setup.bind(this, x1,y1, x2,y2));
},
}, this);
},
- addBlocker : function addBlocker(obj){
- this.removeBlocker(obj);
- var bb = obj.boundingBox;
- if (obj.blocking && bb)
- obj.region = this.set(bb.x1,bb.y1, bb.x2,bb.y2, obj);
- return obj;
- },
- removeBlocker : function removeBlocker(obj){
- if (obj.region) {
- this.remove(obj.region);
- delete obj.region;
- }
- return obj;
+ /**
+ * Takes normal (not gridSquare-relative) coordinates.
+ */
+ _getSquare : function getSquare(x,y){
+ if (x instanceof Array) { y=x[1]; x=x[0]; }
+ x = Math.floor(x); y = Math.floor(y);
+
+ var cache = this._squares
+ , key = x+"_"+y
+ , sq = cache[key] ;
+
+ if (sq)
+ sq.reset();
+ else
+ sq = cache[key] = new Square(this, x,y);
+
+ return sq;
},
-
-
- // moveBlocked : function moveBlocked(agent, trj, to, bb){
- // bb = bb || agent.boundingBox;
- // var blockers, blocker, msg
- // , side = null
- // , tobb = bb.relocated(to.x,to.y)
- // , x1 = tobb.x1, y1 = tobb.y1
- // , x2 = tobb.x2, y2 = tobb.y2
- //
- // , ro = bb.relOrigin
- // , bw = bb.width, bh = bb.height
- // , offX = bw - ro.x, offY = bh = ro.y
- //
- // // , x1 = to.x+offX, y1 = to.y+offY
- // // , x2 = x1+bw, y2 = y1+bh
- // ;
- //
- // blockers = this.get(x1,y1, x2,y2).remove(agent).end();
- // blocker = blockers[0];
- //
- // // Not blocked
- // if (!blocker) return false;
- //
- // // Literal corner case :P
- // // XXX: needs better detection of corner
- // if (blockers.length > 1) {
- // msg = 'corner of '+blockers.slice(0);
- // to = trj.p1;
- //
- // // Normal reflection against one line
- // } else {
- // msg = blocker+' on the ';
- //
- // var B = blocker.boundingBox;
- // if (bb.x2 <= B.x1 && x2 >= B.x1) {
- // msg += 'left';
- // side = B.leftSide;
- // to = trj.pointAtX(B.x1-offX-1);
- //
- // } else if (bb.x1 >= B.x2 && x1 <= B.x2) {
- // msg += 'right';
- // side = B.rightSide;
- // to = trj.pointAtX(B.x2+ro.x+1);
- //
- // } else if (bb.y2 <= B.y1 && y2 >= B.y1) {
- // msg += 'top';
- // side = B.topSide;
- // to = trj.pointAtY(B.y1-offY-1);
- //
- // } else if (bb.y1 >= B.y2 && y1 <= B.y2) {
- // msg += 'bottom';
- // side = B.bottomSide;
- // to = trj.pointAtY(B.y2+ro.y+1);
- // }
- //
- // msg += ' side';
- // }
- //
- // return { 'msg':msg, 'blockers':blockers, 'side':side, 'to':to };
- // },
-
- // TODO: Fold A* into this class.
- // TODO: Deal with bbox
/**
- * @protected Creates a pathing grid suitable for A* search.
- * @param {Thing|BoundingBox} bbox Denotes the bounds of object under pathing:
- * - An object with a `boundingBox` property
- * - A BoundingBox-like object (has properties relOrigin, width, height)
+ * Finds the shortest path to a destination for the given agent.
+ * @param {Thing} agent Agent whose path is being generated.
+ * @param {Vec} end End destination.
+ */
+ /**
+ * Finds the shortest path to a destination for the given agent.
+ * @param {Thing} agent Agent whose path is being generated.
+ * @param {Integer} x End destination x-coordinate.
+ * @param {Integer} y End destination y-coordinate.
*/
- grid : function grid(bbox){
- if ( !this._grid ) {
- var size = this.gridSquare
- , floor = Math.floor, ceil = Math.ceil
- , cols = ceil((this.width-2) /size)
- , rows = ceil((this.height-2)/size)
- , grid = new Array(cols);
- ;
+ // * @param {Function} [passable] Function which tests whether a node is passable.
+ path : function path(agent, x,y){
+ ++this._pathId; // Current pathId for Square calculation
+ this._agent = agent; // Current agent for Square calculation
+
+ var heuristic = vec.manhattan
+ , start = this._getSquare(agent.loc)
+ , end = this._getSquare(x,y)
+ , open = new BinaryHeap('dist') // 'dist' is the scoring key on heap objects
+ ;
+
+ open.push(start);
+ while (open.length) {
- for (var x=0; x<cols; x++) {
- var col = grid[x] = new Array(rows);
- for (var y=0; y<rows; y++) {
- var node = col[y] = new Vec(x,y);
-
- node.clone = function cloneNode(){
- var n = Vec.prototype.clone.call(this);
- n.blocked = this.blocked;
- n.value = this.value;
- return n;
- };
+ // Grab the lowest f(x) to process next. Heap keeps this sorted for us.
+ var current = open.pop();
+
+ // End case -- result has been found, return the traced path
+ if (current === end) {
+ var path = []
+ , node = current;
+ while( node.prev ) {
+ path.push( node );
+ node = node.prev;
}
+ return path.reverse();
}
- // cache result
- this._grid = this.reduce(function(grid, v, r){
+ // Normal case -- move current from open to closed, process each of its neighbors
+ current.closed = true;
+
+ var neighbors = current.getNeighbors();
+ for (var i=0, il = neighbors.length; i < il; i++) {
+ var n = neighbors[i]
+ , node = n[0]
+ , weight = n[1]
+ ;
- // Ignore bullets and boundary-walls in pathing
- if (v.isBoundary || v instanceof Bullet)
- return grid;
+ // Move on if we've done this already, or if it's blocker (ignoring start)
+ if ( node.closed || (node.blocked && node !== start && node !== end) )
+ continue;
- var rowMin = floor( r.y1/size )
- , colMin = floor( r.x1/size )
- , rowMax = ceil( r.y2/size )
- , colMax = ceil( r.x2/size )
- ;
+ // startDist score is the shortest distance from start to current node, we need to check if
+ // the path we have arrived at this neighbor is the shortest one we have seen yet.
+ // We define the distance from a node to it's neighbor as 1, but this could be variable for weighted paths.
+ // --> In fact! Weight is determined by Square.getNeighbors()
+ var startDist = current.startDist + weight;
- for (var x=colMin; x<colMax; x++) {
- var col = grid[x];
- for (var y=rowMin; y<rowMax; y++) {
- var node = col[y];
- node.blocked = true;
- node.value = v;
- }
+ if ( !node.visited || startDist < node.startDist ) {
+
+ // Found an optimal (so far) path to this node. Take score for node to see how good it is.
+ node.prev = current;
+ node.endDist = node.endDist || heuristic(node, end);
+ node.startDist = startDist;
+ node.dist = startDist + node.endDist;
+
+ // New? Add to the set of nodes to process
+ if ( !node.visited ) {
+ node.visited = true;
+ open.push(node);
+
+ // Seen, but since it has been rescored we need to reorder it in the heap
+ } else
+ open.rescore(node);
}
-
- return grid;
- }, grid);
+ }
}
- return this._grid;
- },
-
- path : function path(agent, start, end){
- return this.gridPath(start, end, agent)
- .invoke('scale', this.gridSquare)
- .invoke('add', this.gridSquareMid)
- .end();
- },
-
- /**
- * @protected
- * Generates grid-sized path from start to end.
- */
- _gridPath : function gridPath(agent, start, end){
- var size = this.gridSquare, floor = Math.floor
- , grid = this.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]
- ;
- return Y(astar.search(grid, startN, endN))
+ // No result was found -- empty array signifies failure to find path
+ return [];
},
vec2Square : function vec2Square(x,y){
if (x instanceof Array){ y = x.y; x = x.x; }
var floor = Math.floor, size = this.gridSquare;
return new Vec(floor(x)*size, floor(y)*size);
- },
-
-
+ }
});
+
+
// Wrap QuadTree Methods to accept Rect-like objects
'overlaps get set removeAll leaves collect'
.split(' ')
.forEach(function(name){
var method = PathMap.fn[name] || QuadTree.fn[name];
- PathMap.fn[name] = function(o){
+ PathMap.fn[name] = function pmHandleRect(o){
var args = arguments, xy;
- if ('x1' in o){
+ if (o.x1 !== undefined){
xy = [o.x1,o.y1, o.x2,o.y2];
args = (args.length === 1) ? xy : xy.concat(Y(args,1));
}
};
});
-// Invalidate grid cache
-// TODO: Goes away when I fix A*
-'set remove removeAll clear'
- .split(' ')
- .forEach(function(name){
- var method = PathMap.fn[name] || QuadTree.fn[name];
- PathMap.fn[name] = function(){
- delete this._grid;
- return method.apply(this, arguments);
- };
- });
-
// Stay sync'd with config
config.updateOnChange(
['pathing.gridSquare', 'pathing.gridSquareMid'],
Trajectory =
exports['Trajectory'] =
Line.subclass('Trajectory', {
- halt : false,
elapsed : 0,
- bounces : 0,
- init : function initTrajectory(owner, x1,y1, x2,y2, tdist){
+ init : function initTrajectory(owner, x1,y1, x2,y2, tdist, elapsed){
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);
+ this.reset(x1,y1, x2,y2, tdist, elapsed);
},
- reset : function reset(x1,y1, x2,y2, tdist){
+ reset : function reset(x1,y1, x2,y2, tdist, elapsed){
if (x1 instanceof Array && y1 instanceof Array) {
tdist = x2;
y2 = y1[1]; x2 = y1[0];
this.p2 = new Vec(x2,y2);
Vec.init.call(this, x2-x1, y2-y1);
- this.elapsed = 0;
+ this.elapsed = elapsed || 0;
this.resetBound();
return this;
},
},
clone : function clone(){
- return new Trajectory(this.owner, this.x1,this.y1, this.x2,this.y2, this.tdist);
+ return new Trajectory(this.owner, this.x1,this.y1, this.x2,this.y2, this.tdist, this.elapsed);
},
},
closest : function closest(o1, o2){
- return new Y(arguments).sort( this.compare ).shift();
+ var things;
+ if (o1 instanceof Y.YArray)
+ things = o1;
+ else if (o1 instanceof Array)
+ things = Y(o1);
+ else
+ things = new Y(arguments);
+
+ return things.sort( this.compare ).shift();
},
comesWithin : function comesWithin(pt, w,h){
pathBlocked : function pathBlocked(obj, ignore){
var blockers =
this.pathmap.walls
- .concat( this.game.units )
- .apply('remove', Y(ignore || []).concat([this.owner]).end())
- .filter( this.intersects )
- .sort( this.compare )
+ .concat( this.game.units )
+ .apply('remove', [this.owner].concat(ignore || []) )
+ .filter( this.intersects )
+ .sort( this.compare )
, blocker = blockers.shift()
;
Traversal =
exports['Traversal'] =
Y.subclass('Traversal', {
- elapsed : 0,
- halt : false,
+ elapsed : 0,
isBlocked : false,
+ isCorner : false,
+ remaining : 0,
+ blocker : null,
+ to : null,
+ side : null,
init : function initTraversal(thing, trajectory){
this.thing = thing;
+ this.game = thing.game;
this.pathmap = thing.pathmap;
+ this.bbox = thing.boundingBox.clone();
+
this.trajectory = trajectory || thing.trajectory;
+ this.elapsed = this.trajectory.elapsed;
},
- step : function step(dt){
- this.halt = false;
-
- var traj = this.trajectory
- , thing = this.thing
- , bb = thing.boundingBox.clone()
- , to, t;
+ step : function step(dt, tx,ty){
+ var t, traj = this.trajectory;
do {
- t = Math.min(traj.tBound, dt);
- dt -= t;
+ t = this.stepTo( Math.min(traj.tBound,dt), tx,ty);
this.elapsed += t;
+ traj.elapsed += t;
+ dt -= t;
- to = this.stepTo(t, bb);
- if (this.halt) break;
-
- bb = bb.relocate(to);
+ if (this.isBlocked) {
+ this.remaining = dt;
+ this.thing.fire('collide', this.blocker);
+ this.blocker.fire('collide', this.thing);
+ break;
+ }
} while (dt > 0);
- return to;
+ return this.to;
},
- stepTo : function stepTo(t, bb){
- var ng, traj = this.trajectory, og = traj.p2, thing = this.thing
- , to = traj.parametric(this.elapsed), _to = to
- , test = this.pathmap.moveBlocked(thing, this, to, bb)
+ /**
+ * Checks for blockers and moves traversal bounds forward as much as possible.
+ * @param {Number} t Time units to move this traversal forward.
+ * @return {Number} Time units consumed by this step.
+ */
+ stepTo : function stepTo(t, tx,ty){
+ var blockers, blocker, B
+ , traj = this.trajectory, thing = this.thing
+
+ , bb = this.bbox
+ , x1 = bb.x1, y1 = bb.y1
+ , x2 = bb.x2, y2 = bb.y2
+ , o = bb.absOrigin
+
+ , to = this.to = traj.parametric(this.elapsed+t)
;
- // Blocked! Reflect trajectory
- if ( test ) {
- to = test.to;
- this.bounces++;
-
- var blocker = test.blockers[0];
- thing.fire('collide', blocker);
- blocker.fire('collide', thing);
-
- // Potentially set by responders to collision event
- if (this.halt) return to;
-
- if (!test.side) {
- console.error('Null reflection line!', 'to:', to, 'blockers:', test.blockers);
- return to;
- }
+ // Don't overshoot the target
+ if ( tx !== undefined && abs(o.x-to.x) > abs(o.x-tx) )
+ to.x = tx;
+
+ if ( ty !== undefined && abs(o.y-to.y) > abs(o.y-ty) )
+ to.y = ty;
+
+ // nb: BoundingBox.relocate() is in-place, so this.bbox are updated
+ bb = bb.relocate(to);
+ blockers = this.blockers = this.pathmap.get(bb).remove(thing).end();
+
+ if (!blockers.length)
+ return t; // All time consumed, yay
+
+ this.isBlocked = true;
+
+ // TODO: Either: this never happens (current belief) OR it needs better corner-detection
+ // Literal corner case :P
+ if (blockers.length > 1) {
+ console.error('Corner! WHO KNEW.', 'to:', to, 'blockers:', blockers);
+ this.isCorner = true;
+ // to = trj.p1; // Turn around, go back whence you came
+ }
+
+ // blocker = traj.closest(blockers)
+ blocker = this.blocker = blockers[0];
+ B = blocker.boundingBox;
+
+ // Figure out which boundary of the blocker we crossed and calculate
+ // the furthest non-overlapping point.
+ if (bb.x2 <= B.x1 && x2 >= B.x1) {
+ this.to = to = trj.pointAtX(B.x1 - 1 - bb.originRight);
+ this.side = B.leftSide;
- 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);
- }
+ } else if (bb.x1 >= B.x2 && x1 <= B.x2) {
+ this.to = to = trj.pointAtX(B.x2 + 1 + bb.originLeft);
+ this.side = B.rightSide;
- this.reset(to.x,to.y, ng.x,ng.y);
- thing.render(this.game.level);
+ } else if (bb.y2 <= B.y1 && y2 >= B.y1) {
+ this.to = to = trj.pointAtY(B.y1 - 1 - bb.originTop);
+ this.side = B.topSide;
- // console.log([
- // '['+TICKS+' ('+this.depth+')] '+thing+' reflected!',
- // ' wanted: '+_to+' x ('+(_to.x+bb.width)+','+(_to.y+bb.height)+')',
- // ' blocker: '+test.msg,
- // ' old:',
- // ' loc: '+bb.p1,
- // ' goal: '+og,
- // ' new:',
- // ' loc: '+to,
- // ' goal: '+ng,
- // ' --> trajectory: '+this
- // ].join('\n'));
+ } else if (bb.y1 >= B.y2 && y1 <= B.y2) {
+ this.to = to = trj.pointAtY(B.y2 + 1 + bb.originBottom );
+ this.side = B.bottomSide;
+ } else {
+ console.error('Null reflection line!', 'to:', to, 'blockers:', blockers);
}
- return to;
- },
-
+ // Move bounding box to furthest non-overlapping point
+ bb.relocate(to);
+
+ // Return how much time consumed
+ return trj.timeToMove(to.x-o.x, to.y-o.y);
+ }
})
, thing = require('tanks/thing/thing')
, Wall = require('tanks/map/wall').Wall
, Trajectory = require('tanks/map/trajectory').Trajectory
+, Traversal = require('tanks/map/traversal').Traversal
, Explosion = require('tanks/fx/explosion').Explosion
, fillStats = thing.fillStats
end : 1.5 // ratio of explosion end radius to obj size
},
+ // Instance
+ blocking : true,
+ bounces : 0,
+ bounceLimit : 1,
+
+ width : 6,
+ height : 6,
+
+ stats : {
+ move : 2.0 // move speed (squares/sec)
+ },
+
/**
, x1 = loc.x, y1 = loc.y;
this.position(x1,y1);
- this.trajectory = new Trajectory(this, x1,y1, x2,y2, this.stats.move*REF_SIZE/1000);
+ this.trajectory = new Trajectory(this, x1,y1, x2,y2, this.movePerMs);
this.addEventListener('collide', this.onCollide.bind(this));
},
-
- blocking : true,
- bounces : 0,
- bounceLimit : 1,
-
- width : 6,
- height : 6,
-
- stats : {
- move : 2.0 // move speed (squares/sec)
- },
-
createStats : function createStats(){
this.stats = Y({}, fillStats(this.owner.stats), fillStats(this.stats) );
},
return this;
},
- act : function act(){
- if (!this.dead) this.move();
+ act : function act(elapsed, now){
+ if (!this.dead) this.move(elapsed, now);
return this;
},
- move : function move(){
- // We test twice because the process of calculating the trajectory
- // could result in a collision, killing the bullet
- if (!this.dead) var to = this.trajectory.step( ELAPSED );
- if (!this.dead) this.game.moveThingTo(this, to.x, to.y);
+ move : function move(elapsed, now){
+ if (this.dead) return this;
+
+ var tvsl = new Traversal(this, this.trajectory)
+ , to = tvsl.step(elapsed)
+ ;
+
+ if (tvsl.isBlocked && !this.dead) {
+ tvsl = new Traversal(this, this.trajectory);
+ to = tvsl.step(tvsl.remaining);
+ }
+
+ if (!this.dead)
+ this.game.moveThingTo(this, to.x, to.y);
+
return this;
},
onCollide : function onCollide(evt){
- var unit = evt.trigger
+ if ( this.dead ) return;
+
+ var ng
+ , tvsl = evt.target
+ , traj = this.trajectory
+ , to = tvsl.to
+
+ , unit = evt.trigger
, hitAWall = unit instanceof Wall
- , trj = this.trajectory;
+ ;
- if ( this.dead || hitAWall && trj.bounces <= this.bounceLimit )
+ // Reflection!
+ if ( hitAWall && this.bounceLimit >= ++this.bounces ) {
+ if ( tvsl.isCorner ) {
+ ng = to;
+ } else {
+ if (!tvsl.side) return console.error('Null reflection line!', 'to:', to, 'blockers:', tvsl.blockers);
+ ng = math.reflect(traj.p2, tvsl.side);
+ }
+ traj.reset(to.x,to.y, ng.x,ng.y);
+ this.render(this.game.level); // to render the new reflection line
return;
+ }
unit.dealDamage(this.stats.power, this);
this.destroy();
- trj.halt = true;
// Trigger explosion
- var x, y, loc = this.loc, uloc = unit.loc
+ var x, y, loc = to, uloc = unit.loc
, bsize = Math.max(this.width, this.height)
, asize = Math.max(unit.width, unit.height)
.origin('50%', '50%')
.position(loc.x, loc.y)
.fill('#83BB32')
- .stroke('#1C625B', 2.0)
+ .stroke('#1C625B', 3.0)
.appendTo( parent );
this.shape.layer.attr('title', ''+loc);
- act : function act(){
+ act : function act(elapsed, now){
if (this.dead)
return this;
var Y = require('Y').Y
, op = require('Y/op')
+
+, vec = require('ezl/math/vec')
+, shape = require('ezl/shape')
+, Cooldown = require('ezl/loop').Cooldown
+, CooldownGauge = require('ezl/widget').CooldownGauge
+
, Thing = require('tanks/thing/thing').Thing
, Bullet = require('tanks/thing/bullet').Bullet
, Trajectory = require('tanks/map/trajectory').Trajectory
-, Vec = require('ezl/math/vec').Vec
-, Cooldown = require('ezl/loop').Cooldown
-, CooldownGauge = require('ezl/widget').CooldownGauge
-, shape = require('ezl/shape')
+, Traversal = require('tanks/map/traversal').Traversal
+
+, Vec = vec.Vec
+, manhattan = vec.manhattan
, Rect = shape.Rect
, Circle = shape.Circle
this['init'] =
function initTank(align){
Thing.init.call(this, align);
- this.coolgauge = new CooldownGauge(this.cooldowns.attack, this.width+1,this.height+1);
+ this.atkGauge = new CooldownGauge(this.cooldowns.attack, this.width+1,this.height+1);
this.onBulletDeath = this.onBulletDeath.bind(this);
};
this['act'] =
- function act(allotment){
+ function act(elapsed, now){
var ai = this.ai;
// Check to see if we should obey our last decision, and not recalc
if ( !(agents && agents.size()) )
return null;
- var manhattan = Vec.manhattan
- , cmp = op.cmp
+ var cmp = op.cmp
, loc = this.loc ;
agents.sort(function(a,b){
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.stats.move*REF_SIZE/1000);
- return this.moveByAngle(this.angleTo(x,y), x,y);
+ var loc = this.loc
+ , cur = this.currentMove
+ ;
+
+ if (cur.x !== x || cur.y !== y)
+ this.trajectory = new Trajectory(this, loc.x,loc.y, x,y, this.movePerMs);
+
+ var tvsl = new Traversal(this, this.trajectory)
+ , to = tvsl.step(elapsed, x,y)
+ ;
+
+ this.game.moveThingTo(this, to.x,to.y);
+ return this;
};
/**
this['continueMove'] =
function continueMove(){
if ( !this.currentMove || this.currentMoveLimit <= NOW ){
- var t = this.findNearEnemies(10000).shift();
- if (t) this.calculatePath(t.midpoint);
+ var target = this.findNearEnemies(10000).shift();
+ if (target) this.calculatePath(target.loc);
}
var to = this.currentMove;
var pm = this.game.pathmap
, start = this.loc
- , path = this.lastPath = pm.path(start, end, this)
+ , path = this.lastPath = pm.path(this, end)
, to = this.currentMove = path.shift()
;
};
.appendTo( parent ) ;
if (this.showAttackCooldown)
- this.shape.append(this.coolgauge);
+ this.shape.append(this.atkGauge);
this.turret =
new Circle(r)
set : op.set.methodize(),
attr : op.attr.methodize(),
+ get movePerMs(){ return this.stats.move*REF_SIZE/1000; },
+
init : function init(align){
if (x === undefined && y === undefined)
return this.loc;
- var bb = this.boundingBox.relocate(x,y)
- , loc = this.loc = bb.absOrigin;
- this.midpoint = bb.midpoint;
- if (this.shape) this.shape.position(loc.x,loc.y); // XXX: eh?
+ var bb = this.boundingBox.relocate(x,y) // in-place
+ , loc = this.loc = bb.absOrigin
+ , mid = this.midpoint = bb.midpoint
+ ;
+ if (this.shape) this.shape.position(loc.x,loc.y);
return this;
},
/**
* Determines what the creep should do -- move, attack, etc.
*/
- act : function act(){
+ act : function act(elapsed, now){
return this;
},
cookies.remove(d.key);
});
+// As EventLoop is in ezl, it should not know about config
config.updateOnChange('game.timeDilation', EventLoop.fn);
Rect.init.call(this, pathmap.width-2, pathmap.height-2);
- pathmap.gridPath = this.gridPathWithUI.bind(this);
+ pathmap._gridPath = this.gridPathWithUI.bind(this);
this.cleanUpAgent = this.cleanUpAgent.bind(this);
this._drawPathStep = this._drawPathStep.bind(this);
},
<script src="build/Y/modules/y.kv.js" type="text/javascript"></script>
<script src="build/ezl/util/binaryheap.js" type="text/javascript"></script>
<script src="build/ezl/util/astar.js" type="text/javascript"></script>
+<script src="build/tanks/map/traversal.js" type="text/javascript"></script>
<script src="build/ezl/util/tree/quadtree.js" type="text/javascript"></script>
<script src="build/Y/modules/y.scaffold.js" type="text/javascript"></script>
<script src="build/Y/modules/y.config.js" type="text/javascript"></script>
<script src="build/tanks/fx/explosion.js" type="text/javascript"></script>
<script src="build/tanks/thing/bullet.js" type="text/javascript"></script>
<script src="build/tanks/thing/tank.js" type="text/javascript"></script>
+<script src="build/tanks/map/map.js" type="text/javascript"></script>
<script src="build/tanks/map/pathmap.js" type="text/javascript"></script>
-<script src="build/tanks/ui/pathmapui.js" type="text/javascript"></script>
<script src="build/tanks/thing/player.js" type="text/javascript"></script>
<script src="build/tanks/map/level.js" type="text/javascript"></script>
+<script src="build/tanks/ui/pathmapui.js" type="text/javascript"></script>
<script src="build/tanks/thing.js" type="text/javascript"></script>
<script src="build/tanks/map.js" type="text/javascript"></script>
<script src="build/tanks/game.js" type="text/javascript"></script>