# Bugs
-- Turret can fire through walls
+- collision short-circuiting, collision events
+- bullet collisions should explode both
+- 5-shot limit
+
+
# TODOs
- Move game objects into namespace `tanks`
# Notes
-- TODO Replace *2 and /2 with shifts at compile-time
+- Replace *2 and /2 with shifts at compile-time
- Clipping will suck (masking is easy -- overflow:hidden)
-- Should use unit vectors for trajectories?
---> matrix math for reflections?
+
+
# The Tank AI Challenge
return fn && fn !== self && fn.__wraps__ !== self;
}
-function reduce(o, fn, acc, cxt){
+function reduce(o, fn, acc){
if ( !o )
return acc;
- // fn = Function.toFunction(fn);
+ fn = Function.toFunction(fn);
+ var cxt = arguments[3] || o;
if ( notWrapped(o.reduce) )
return o.reduce.apply(o, [fn, acc, cxt]);
- cxt = cxt || o;
for ( var name in o )
acc = fn.call(cxt, acc, o[name], name, o);
return acc;
}
+function map(o, fn){
+ if ( !o )
+ return o;
+
+ fn = Function.toFunction(fn);
+ var acc = {}, cxt = arguments[2] || o;
+ if ( notWrapped(o.map) )
+ return o.map.apply(o, [fn, cxt]);
+ for ( var name in o )
+ acc[name] = fn.call(cxt, o[name], name, o);
+
+ return acc;
+}
+
+function forEach(o, fn){
+ map(o, fn, cxt);
+}
+
+function filter(o, fn){
+ if ( !o )
+ return o;
+
+ fn = Function.toFunction(fn);
+ var acc = {}, cxt = arguments[2] || o;
+ if ( notWrapped(o.filter) )
+ return o.filter.apply(o, [fn, cxt]);
+
+ for ( var name in o )
+ if ( fn.call(cxt, o[name], name, o) )
+ acc[name] = o[name];
+
+ return acc;
+}
+
function set(o, key, value, def){
if ( o && key !== undefined )
o[key] = (value !== undefined ? value : def);
},
addEventListener : function addEventListener(evt, fn){
- this.getQueue(evt).push(fn);
+ this.getQueue(evt).push(fn.toFunction());
return this.target;
},
removeEventListener : function removeEventListener(evt, fn){
- this.getQueue(evt).remove(fn);
+ this.getQueue(evt).remove(fn.toFunction());
},
fire : function fire(evtname, trigger, data, async){
-Y.reduce = reduce;
-Y.set = dset;
-Y.attr = dattr;
-Y.extend = extend;
+Y.reduce = reduce;
+Y.map = map;
+Y.forEach = forEach;
+Y.filter = filter;
+Y.set = dset;
+Y.attr = dattr;
+Y.extend = extend;
Y.isA = isA;
Y.isString = isString;
YFunction(Y);
Y(Y.reduce);
+Y(Y.map);
+Y(Y.forEach);
+Y(Y.filter);
Y(Y.set);
Y(Y.attr);
Y(Y.extend);
drawShape : function drawShape(ctx){
ctx.rect(0,0, this.canvasWidth,this.canvasHeight);
ctx.fill();
+ },
+
+ toString : function(){
+ var loc = this.loc
+ , x1 = loc.x, y1 = loc.y
+ , x2 = x1+this.layerWidth, y2 = y1 + this.layerHeight;
+ return this.className+'['+x1+','+y1+', '+x2+','+y2+'](w='+this.layerWidth+', h='+this.layerHeight+')';
}
});
+++ /dev/null
-
-Y(tanks.Game.prototype).extend({
-
- initMap : function initMap(){
- var self = this;
-
- this.byId = {};
- this.units = new Y.YArray();
- this.bullets = new Y.YArray();
- this.blockers = new Y.YArray();
-
- this.root =
- this.grid =
- new Grid(COLUMNS,ROWS, CELL_SIZE)
- .appendTo(this.viewport);
-
- this.level =
- new Level(this, COLUMNS*REF_SIZE, ROWS*REF_SIZE)
- .appendTo(this.root);
-
- this.pathmap = this.level.pathmap;
-
- // Agent.addEventListener('create', function(evt){
- // self.addAgent(evt.instance);
- // });
- // Agent.addEventListener('destroy', function(evt){
- // self.killAgent(evt.instance);
- // });
- },
-
-
- // *** Agent Management *** //
-
- addUnit : function addUnit(unit, col,row){
- unit.game = this;
-
- if (col !== undefined) {
- // Center unit in square
- var sqX = (col || 0) * REF_SIZE
- , sqY = (row || 0) * REF_SIZE
- , x = sqX + (REF_SIZE-unit.width) /2
- , y = sqY + (REF_SIZE-unit.height)/2 ;
-
- unit.setLocation(x,y);
- unit.render( this.level );
- }
-
- this.pathmap.addBlocker(unit);
-
- if ( !this.byId[unit.id] ) {
- this.byId[unit.id] = unit;
- this.units.push(unit);
- }
-
-
- return unit;
- },
-
- addAgent : function addAgent(agent){
- agent.game = this;
- if (agent.id === undefined) return agent;
-
- this.pathmap.addBlocker(agent);
-
- if ( !this.byId[agent.id] ) {
- this.byId[agent.id] = agent;
- if (agent instanceof Ability)
- this.abilities.push(agent);
- else
- this.units.push(agent);
- }
-
- return agent;
- },
-
- killUnit : function killUnit(unit){
- delete this.byId[unit.id];
- this.units.remove(unit);
- this.pathmap.removeBlocker(unit);
- return unit;
- },
-
- moveAgentTo : function moveAgentTo(agent, x,y){
- this.pathmap.removeBlocker(agent);
- agent.setLocation(x,y);
- this.pathmap.addBlocker(agent);
- return agent;
- },
-
- getUnitAt : function getUnitAt(x,y){
- return this.grid.get(x,y);
- },
-
- getUnitsAt : function getUnitsAt(x1,y1, x2,y2){
- return this.grid.get(x1,y1, x2,y2);
- }
-
-});
-
-Y(tanks.Game.prototype).extend({
-
- resize : function resize(){
- var ratio = COLUMNS / ROWS
- , el = this.el
- , p = el.parent()
- , pw = p.width(), ph = p.height()
- , pRatio = pw / ph
- ;
-
- if ( ratio > pRatio )
- CELL_SIZE = Math.floor((pw-GRID_OFFSET*2) / COLUMNS);
- else
- CELL_SIZE = Math.floor((ph-GRID_OFFSET*2) / ROWS);
-
- SCALE = CELL_SIZE/REF_SIZE;
-
- var w = COLUMNS*CELL_SIZE
- , h = ROWS*CELL_SIZE
- , canvas = this.canvas[0];
-
- this.el.width(w).height(h);
- this.canvas.width(w).height(h);
- canvas.width = w;
- canvas.height = h;
-
- this.el.offset({
- top : (ph - h) / 2,
- left : (pw - w) / 2
- });
-
- this.ctx.scale(SCALE,SCALE);
- }
-
-});
tanks.Game = new Y.Class('Game', {
init : function init(viewport){
+ Y.bindAll(this); // Seal all methods
+
+ this.byId = {};
+ this.active = new Y.YArray();
+ this.units = new Y.YArray();
+ this.bullets = new Y.YArray();
+ this.viewport = $(viewport || GRID_ELEMENT);
+
this.loop = new EventLoop(this, FRAME_RATE);
- // Seal all methods
- // Y.bindAll(this);
- this.resize = this.resize.bind(this);
- this.tick = this.tick.bind(this);
+ // this.resize = this.resize.bind(this);
+ // this.tick = this.tick.bind(this);
- this.viewport = $(viewport || GRID_ELEMENT);
+ this.root =
+ this.grid =
+ new Grid(COLUMNS,ROWS, CELL_SIZE)
+ .appendTo(this.viewport);
+
+ this.level =
+ new Level(this, COLUMNS*REF_SIZE, ROWS*REF_SIZE)
+ .appendTo(this.root);
- this.initMap();
+ this.pathmap = this.level.pathmap;
+
+
+ Thing.addEventListener('create', this.addUnit);
+ Thing.addEventListener('destroy', this.killUnit);
this.addEventListener('tick', this.tick);
},
+
+
draw : function draw(){
this.root.draw();
this.pathmap.removeOverlay(this.viewport);
SECONDTH = ELAPSED / 1000;
SQUARETH = REF_SIZE * SECONDTH
- this.units.invoke('act');
- // XXX: Collect the dead
+ this.active.invoke('act');
this.draw();
+ },
+
+
+
+
+ // *** Agent Management *** //
+
+ addUnit : function addUnit(unit, col,row){
+ if (unit instanceof Y.event.YEvent)
+ unit = unit.instance;
+
+ unit.game = this;
+
+ if (col !== undefined) {
+ // Center unit in square
+ var sqX = (col || 0) * REF_SIZE
+ , sqY = (row || 0) * REF_SIZE
+ , x = sqX + (REF_SIZE-unit.width) /2
+ , y = sqY + (REF_SIZE-unit.height)/2 ;
+
+ unit.setLocation(x,y);
+ unit.render( this.level );
+ }
+
+ this.pathmap.addBlocker(unit);
+
+ if ( unit.active && !this.byId[unit.id] ) {
+ this.byId[unit.id] = unit;
+ this.active.push(unit);
+
+ if (unit instanceof Bullet)
+ this.bullets.push(unit);
+ else
+ this.units.push(unit);
+ }
+
+ return unit;
+ },
+
+ killUnit : function killUnit(unit){
+ if (unit instanceof Y.event.YEvent)
+ unit = unit.instance;
+
+ console.log('killUnit(', unit, ')');
+
+ delete this.byId[unit.id];
+ this.active.remove(unit);
+ this.pathmap.removeBlocker(unit);
+
+ if (unit instanceof Bullet)
+ this.bullets.remove(unit);
+ else
+ this.units.remove(unit);
+
+ return unit;
+ },
+
+ moveAgentTo : function moveAgentTo(agent, x,y){
+ this.pathmap.removeBlocker(agent);
+ agent.setLocation(x,y);
+ this.pathmap.addBlocker(agent);
+ return agent;
+ },
+
+
+
+ resize : function resize(){
+ var ratio = COLUMNS / ROWS
+ , el = this.el
+ , p = el.parent()
+ , pw = p.width(), ph = p.height()
+ , pRatio = pw / ph
+ ;
+
+ if ( ratio > pRatio )
+ CELL_SIZE = Math.floor((pw-GRID_OFFSET*2) / COLUMNS);
+ else
+ CELL_SIZE = Math.floor((ph-GRID_OFFSET*2) / ROWS);
+
+ SCALE = CELL_SIZE/REF_SIZE;
+
+ var w = COLUMNS*CELL_SIZE
+ , h = ROWS*CELL_SIZE
+ , canvas = this.canvas[0];
+
+ this.el.width(w).height(h);
+ this.canvas.width(w).height(h);
+ canvas.width = w;
+ canvas.height = h;
+
+ this.el.offset({
+ top : (ph - h) / 2,
+ left : (pw - w) / 2
+ });
+
+ this.ctx.scale(SCALE,SCALE);
}
+
});
if (!window.console) {
console = {
- log : Y.op.noop,
- info : Y.op.noop,
- warn : Y.op.noop,
- error : Y.op.noop,
- dir : Y.op.noop,
- debug : Y.op.noop
+ log : Y.op.nop,
+ info : Y.op.nop,
+ warn : Y.op.nop,
+ error : Y.op.nop,
+ dir : Y.op.nop,
+ debug : Y.op.nop
};
}
function main(){
- var v = $('#viewport')
- , sq = REF_SIZE
- ;
+ var v = $('#viewport');
LBT = new tanks.Game();
ctx = LBT.level.ctx;
- LBT.level.append(
- new Wall(6*sq,1*sq, 1*sq,4*sq),
- new Wall(6*sq,7*sq, 1*sq,2*sq),
- new Wall(1*sq,7*sq, 2*sq,2*sq),
- new Wall(3*sq,6*sq, 1*sq,1*sq)
- );
-
- T = LBT.addUnit(new Tank(0), 1,2);
+ T = LBT.addUnit(new Tank(1), 1,2);
new Player(LBT, T);
setupUI();
function setupUI(){
LBT.loop.spark = new FpsSparkline(LBT.loop, '.fps-sparkline', 0,0);
- btank = new Tank(0);
+ btank = new Tank(1);
btank.act = function(){ return this; };
LBT.addUnit(btank, 0,0);
LBT.pathmap.removeBlocker(btank);
+++ /dev/null
-Collision = new Y.Class('Collision', {
-
- init : function initCollision(line, to){
-
- },
-
-});
\ No newline at end of file
-Level = new Y.Class('Level', Rect, {
+Level = Rect.subclass('Level', {
init : function init(game, w,h){
Rect.init.call(this, w,h);
- this.game = game;
+ this.game = game;
this.pathmap = new PathMap(this, 0,0, w, h, CAPACITY);
+ this.walls = [];
+
+ this.setup();
+ },
+
+ setup : function setup(){
+ Y.map([ [6,1, 1,4],
+ [6,7, 1,2],
+ [1,7, 2,2],
+ [3,6, 1,1] ],
+ "this.addWall.apply(this, Y.map(_, '*50'))",
+ this );
},
- append : function append(children){
- Y(arguments).forEach(this.pathmap.addBlocker, this.pathmap);
- return Rect.prototype.append.apply(this, arguments);
+ addWall : function addWall(x,y, w,h){
+ var wall = new Level.Wall(x,y, w,h);
+ this.walls.push(wall);
+ this.pathmap.addBlocker(wall);
+
+ return this.append( wall.render(this).shape );
},
drawShape : Y.op.nop
});
-Wall = new Y.Class('Wall', Rect, {
- fillStyle : 'rgba(255,255,255, 0.25)',
+Level.Wall = Thing.subclass('Wall', {
blocking : true,
+ active : false,
- init : function init(x,y, w,h){
- Rect.init.call(this, w,h);
-
- var x1 = x, y1 = y
- , x2 = x+w, y2 = y+h;
- this.position(x,y);
+ stats : {
+ hp : Infinity,
+ move : 0,
+ power : 0,
+ speed : 0,
+ shots : 0
},
- appendTo : function appendTo(parent){
- if (parent instanceof Level)
- parent.pathmap.addBlocker(this);
- return Rect.prototype.appendTo.call(this, parent);
+
+ init : function initWall(x,y, w,h){
+ this.width = w;
+ this.height = h;
+ Thing.init.call(this);
+ this.setLocation(x,y);
+ },
+
+ // inactive
+ createCooldowns : Y.op.nop,
+
+ // indestructable
+ dealDamage : Y.op.nop,
+
+
+ render : function render(parent){
+ if (this.shape) this.shape.remove();
+
+ this.shape =
+ new Rect(this.width, this.height)
+ .position(this.loc.x, this.loc.y)
+ .fill('rgba(255,255,255, 0.25)')
+ .appendTo( parent );
+
+ return this;
},
toString : function(){
- return this.className+'('+this.loc+', w='+this.layerWidth+', h='+this.layerHeight+')';
+ var loc = this.loc
+ , x1 = loc.x, y1 = loc.y
+ , x2 = x1+this.width, y2 = y1 + this.height;
+ return this.className+'['+x1+','+y1+', '+x2+','+y2+'](w='+this.width+', h='+this.height+')';
}
-
});
// };
this.walls = {
- top : new Wall(0,0, w,1),
- bottom : new Wall(0,h-1, w,1),
- left : new Wall(0,0, 1,h),
- right : new Wall(w-1,0, 1,h)
+ top : new Level.Wall(0,0, w,1),
+ bottom : new Level.Wall(0,h-1, w,1),
+ left : new Level.Wall(0,0, 1,h),
+ right : new Level.Wall(w-1,0, 1,h)
};
Y(this.walls).forEach(this.addBlocker, this);
},
Trajectory = new Y.Class('Trajectory', math.Line, {
- depth : 0,
+ halt : false,
elapsed : 0,
bounces : 0,
+
init : function initTrajectory(owner, x1,y1, x2,y2, tdist){
this.owner = owner;
this.game = owner.game;
// Determine how much time can pass before we risk teleporting
resetBound : function resetBound(){
- var abs = Math.abs
+ var BOUND_SIZE_RATIO = 0.75
+ , abs = Math.abs
, bb = this.owner.boundingBox;
- // this.tBound = Math.max( (bb.width - this.x1) / this.pa,
- // (bb.height - this.y1) / this.pb );
- this.tBound = Math.min( abs(bb.width / this.pa),
- abs(bb.height / this.pb) );
+ this.tBound = Math.min( abs(bb.width / this.pa) * BOUND_SIZE_RATIO,
+ abs(bb.height / this.pb) * BOUND_SIZE_RATIO );
return this;
},
step : function step(dt){
- var _dt = dt = (dt === undefined ? ELAPSED : dt);
+ this.halt = false;
- var o = this.owner, offX = o.offsetX, offY = o.offsetY, _bb
- , bb = _bb = o.boundingBox, bw = bb.width, bh = bb.height
- , loc = bb.p1
- ;
+ var _dt = dt = (dt === undefined ? ELAPSED : dt)
+ , _bb = bb = this.owner.boundingBox, bw = bb.width, bh = bb.height
+ , to, t, r;
do {
- var t, to, _to, ng
- , test, blockers, side
- ;
-
t = Math.min(this.tBound, dt);
dt -= t;
this.elapsed += t;
- _to = to = this.parametric(this.elapsed);
- test = this.pathmap.moveBlocked(o, this, to, bb);
+ to = this.stepTo(t, bb);
+ if (this.halt) break;
+
+ bb = new Loc.Rect(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;
- // Blocked! Reflect trajectory
- if ( test ) {
- this.bounces++;
- to = test.to;
- side = test.side;
- blockers = test.blockers;
-
- if (!side) {
- console.error('Null reflection line!', 'to:', to, 'blockers:', blockers);
- continue;
- }
-
- if ( blockers.length > 1 ) {
- to = loc;
- ng = this.p1; // XXX: recalculate?
- console.log('corner!', this, 'to:', to, 'ng:', ng);
- } else {
- ng = math.reflect(this.p2, side);
- }
-
- var x = to.x, y = to.y
- , og = this.p2
- ;
- this.reset(x,y, ng.x,ng.y);
-
- if (this.depth) {
- console.log([
- '['+TICKS+':'+_dt+' ('+this.depth+')] '+this.owner+' reflected!',
- ' wanted: '+_to+' x ('+(_to.x+bw)+','+(_to.y+bh)+')',
- ' blocker: '+test.msg,
- ' old:',
- ' loc: '+bb.p1,
- ' goal: '+og,
- ' new:',
- ' loc: '+to,
- ' goal: '+ng,
- ' --> trajectory: '+this
- ].join('\n'));
- }
-
- bb = new Loc.Rect(x,y, x+bw,y+bh);
- ng = null;
-
- this.owner.render(this.game.level);
-
- if (this.depth++ < 5) {
- // dt += t; // do this step over again
- continue;
-
- } else {
- // console.error('Reflection limit reached!'); break;
- LBT.stop();
- throw new Error('Reflection limit reached!');
- }
-
-
- // No collision: don't change trajectory
+ 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 {
- this.depth = 0;
+ ng = math.reflect(this.p2, test.side);
}
- } 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+':'+_dt+' ('+this.depth+')]', loc, ' ~> ', to);
- this.depth = 0;
return to;
},
* @param {math.Line} trajectory
*/
init : function initBullet(owner, x2,y2){
+ var self = this;
this.owner = owner;
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));
},
- elapsed : 0,
-
- blocking : true,
+ blocking : true,
+ bounces : 0,
+ bounceLimit : 1,
offsetX : -3,
offsetY : -3,
height : 6,
stats : {
- move : 2.0, // move speed (squares/sec)
- range : 0.1 // attack range (squares)
+ move : 2.0 // move speed (squares/sec)
},
- bounces : 0,
-
fillStats : function(){
this.stats = Y({},
Thing.fillStats(this.owner.stats),
var loc = this.loc
, trj = this.trajectory;
this.trajectory.reset(loc.x,loc.y, x,y);
- // this.trajectory = new math.Line(loc.x,loc.y, x,y, trj.tdist);
- // this.elapsed = 0;
return this;
},
act : function act(){
- // this.elapsed += ELAPSED;
if (!this.dead)
this.move();
return this;
move : function move(){
var to = this.trajectory.step( ELAPSED );
- this.bounces = this.trajectory.bounces;
- if (this.bounces > 1)
- this.destroy();
- else
+
+ if (!this.dead)
this.game.moveAgentTo(this, to.x, to.y);
+
return this;
},
- _depth : 0,
- _move : function _move(){
- var trj = this.trajectory, p2 = trj.p2, goal = p2, _ng
- , to = trj.parametric(this.elapsed)
- , test = this.game.pathmap.attemptMove(this, trj, to)
- ;
+ onCollide : function onCollide(evt){
+ var agent = evt.trigger
+ , trj = this.trajectory;
- // Something obstructs us -- time to reflect!
- if (!test.ok) {
-
- // Literal corner case :P
- if (test.corner) {
- var to = this.loc, ng = _ng = trj.p1;
- // this.trajectory = new math.Line(loc.x,loc.y, p1.x,p1.y, trj.tdist);
-
-
- // Normal reflection against one line
- } else {
- to = test.to;
- var wall = test.wall
- , bb = this.boundingBox
- , goal = wall.isWithin(p2,bb) ? trj.near(p2.x+2*REF_SIZE, p2.y+2*REF_SIZE) : p2
- , ng = _ng = math.reflect(goal, wall)
- , x1 = bb.x1, y1 = bb.y1
- , tx = to.x, ty = to.y
- , cmp = Y.op.cmp
- ;
-
- // Are we pointed the wrong direction? Rotate 180 degrees around the bullet
- if ( cmp(tx,x1) !== cmp(goal.x,x1) || cmp(ty,y1) !== cmp(goal.y,y1) )
- ng = new math.Vec(tx - ng.x + tx, ty - ng.y + ty);
- }
-
- this.trajectory = new math.Line(to.x,to.y, ng.x,ng.y, trj.tdist);
-
- if (this._depth) {
- console.log('['+this._depth+' '+TICKS+'] '+this+' reflected by', (test.corner ? 'corner' : test.what));
- console.log(' to='+to+', goal='+goal+(p2.equals(goal) ? '' : ' <~ '+p2));
- console.log(' --> new goal='+ng+(ng.equals(_ng) ? '' : ' <~ '+_ng));
- console.log(' --> trajectory='+this.trajectory);
- }
-
- this.render(this.game.level).draw();
- this.elapsed = 0; //ELAPSED;
-
- if (this._depth++ < 5)
- return this; // this.move();
-
- console.error('Reflection limit reached!');
- this._depth = 0;
- return this;
- }
+ if ( agent instanceof Level.Wall && trj.bounces <= this.bounceLimit )
+ return;
- // if (this._depth > 0)
- // console.warn('['+this._depth+'@'+TICKS+'] ok!');
+ agent.dealDamage(this.stats.power, this);
- this._depth = 0;
- this.game.moveAgentTo(this, to.x, to.y);
- return this;
+ this.destroy();
+ trj.halt = true;
},
render : function render(parent){
- if (this.tline) this.tline.remove();
+ if (this.tline) { this.tline.remove(); delete this.tline; }
if (this.shape) this.shape.remove();
if (tanks.config.pathing.traceTrajectories) {
.appendTo( parent );
this.shape.layer.attr('title', ''+loc);
-
return this;
},
-Tank = new Y.Class('Tank', Thing, {
+Tank = Thing.subclass('Tank', {
projectile : Bullet,
blocking : true,
// Attributes
stats: {
- move : 1.0, // move speed (squares/sec)
- rotate : HALF_PI, // rotation speed (radians/sec)
+ hp : 1, // health
- power : 1, // attack power
- speed : 1.0, // attacks/sec
- shots : 5 // max projectiles in the air at once
+ move : 1.0, // move speed (squares/sec)
+ rotate : HALF_PI, // rotation speed (radians/sec)
+
+ power : 1, // attack power
+ speed : 0.5, // attack cool (sec)
+ shots : 5 // max projectiles in the air at once
},
.appendTo( parent );
this.cannon = new Circle(r, true)
- .position(w2-r+1, h2-r+1)
+ .position(w2-r, h2-r)
.fill('#A72F5B')
.appendTo( this.shape )
;
},
stats: {
- move : 0.5, // move speed (squares/sec)
- rotate : HALF_PI, // rotation speed (radians/sec)
+ hp : 1, // health
- power : 1, // attack power
- speed : 1.0, // attacks/sec
- shots : 5 // max projectiles in the air at once
+ move : 1.0, // move speed (squares/sec)
+ rotate : HALF_PI, // rotation speed (radians/sec)
+
+ power : 1, // attack power
+ speed : 0.5, // attack cool (sec)
+ shots : 5 // max projectiles in the air at once
},
// *** Bookkeeping *** //
id : 0,
- align : 0,
+ align : 0, // 0 reserved for neutral units
dead : false,
+ blocking : true, // Whether the agent obstructs pathing
+ active : true, // Whether the agent takes actions
+
loc : null,
boundingBox : null,
- blocking : true,
// Rotation (rads)
rotation : 0,
height : REF_SIZE*0.6,
- destroy : function destroy(){
- if (this.dead) return this;
+ dealDamage : function dealDamage(d, source){
+ this.stats.hp -= d;
+
+ if (this.stats.hp <= 0)
+ this.destroy();
- this.dead = true;
- return this.remove().fire('destroy', this);
- },
-
- remove : function remove(){
- if (this.shape) this.shape.remove();
- if (this.game) this.game.killUnit(this);
return this;
},
return this;
},
- /**
- * @protected
- * Regenerates the bounding box of this object.
- * Invoked with new values for position and/or size; unchanged values are undefined.
- */
- createBoundingBox : function createBoundingBox(x,y, w,h){
- var loc = this.loc, tw = this.width, th = this.height
- , dx = (x !== undefined), dy = (y !== undefined)
- , dw = (w !== undefined), dh = (h !== undefined)
- , x1 = (dx ? x : loc.x), y1 = (dy ? y : loc.y)
- , x2 = (dw ? w : tw)+x2, y2 = (dh ? h : th)+y1
- ;
- if (dx || dy || dw || dh)
- this.boundingBox = new Loc.BoundingBox(x1,y1, x2,y2);
- return this;
+ destroy : function destroy(){
+ if (this.dead) return this;
+ this.dead = true;
+ return this.remove().fire('destroy', this);
},
+ remove : function remove(){
+ if (this.shape) this.shape.remove();
+ if (this.game) this.game.killUnit(this);
+ return this;
+ },
-
- // *** Gameplay Methods *** //
-
- fillStats : function fillStats(){
+ fillStats : function fillStats(){
this.stats = Thing.fillStats(this.stats);
},
},
/**
+ * Calculates the location of the bullet-spawning point on this Thing.
+ */
+ getTurretLoc : function getTurretLoc(){ return this.loc; },
+
+
+
+
+ // *** Gameplay Methods *** //
+
+ /**
* Determines what the creep should do -- move, attack, etc.
*/
act : function act(){
- if (this.dead) return this;
-
- this.cooldowns.invoke('update', NOW);
-
- var bb = this.boundingBox
- , x1 = Calc.rangeX(this), x2 = bb.left.x
- , y1 = bb.top.y, y2 = bb.bottom.y
- , units = this.game
- .getUnitsAt(x1,y1, x2,y2)
- .filter(this.filterTarget, this)
- , target = this.closest(units)
- ;
- if (target)
- this.attack(target);
- else
- this.move();
-
return this;
},
return this;
},
- getTurretLoc : function getTurretLoc(){
- return this.loc;
- },
+
+
+
/// Rendering Methods ///
;
st.forEach(function(v, k){
- var k = Y(k)
- , k_ = k.rtrim('_max')
- ;
+ k = Y(k);
+ var k_ = k.rtrim('_max');
if ( k.endsWith('_max') ) {
if ( stats[k_] === undefined )
this.tank.cooldowns.invoke('update', NOW);
var action = this.queue.shift();
-
if (action && action.type === 'fire') {
// console.log('fire', [action.x, action.y], 'loc', this.tank.loc)
this.tank.fireProjectile(action.x, action.y);
"src/tanks/config.js",
"src/tanks/calc.js",
+ "src/tanks/thing/thing.js",
+ "src/tanks/thing/bullet.js",
+ "src/tanks/thing/tank.js",
+
"src/tanks/map/loc.js",
"src/tanks/map/trajectory.js",
"src/tanks/map/level.js",
"src/tanks/map/pathmap.js",
- "src/tanks/thing/thing.js",
- "src/tanks/thing/bullet.js",
- "src/tanks/thing/tank.js",
-
"src/tanks/ui/grid.js",
"src/tanks/ui/player.js",
- "src/tanks/game/game.js",
- "src/tanks/game/game-map.js"
+ "src/tanks/game/game.js"
);
static $libScripts = array(