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