# TODOs
- Move game objects into namespace `tanks`
- Move portal into namespace (ideas: `portal`, canvas tools... `easel`, or `ezl`)
+- Classes should have generalize()-ed version of instance methods on them.
# Notes
this.negBleed = new Loc(0,0);
this.posBleed = new Loc(0,0);
- this.boundingBox = new Loc.Rect(0,0, 0,0);
+ this.boundingBox = new Loc.BoundingBox(0,0, 0,0);
this.transform = {
origin : new Loc('50%','50%'), // rotational origin
invoke : function invoke(name){
var args = Y(arguments,1);
-
+ // this[name].apply(this, args);
+ // this.children.invoke.apply(this.children, ['invoke', name].concat(args));
+ return this._invoke(name, args);
+ },
+ _invoke : function _invoke(name, args){
this[name].apply(this, args);
- this.children.invoke.apply(this.children, ['invoke', name].concat(args));
+ this.children.invoke('_invoke', name, args);
return this;
},
(function(){
-var
+var
+Vec = math.Vec,
HALF_PI = Math.PI/2,
SIN_HALF_PI = Math.sin(HALF_PI),
COS_HALF_PI = Math.cos(HALF_PI);
*/
math.Line = new Y.Class('Line', math.Vec, {
- init : function init(x1,y1, x2,y2, tdist){
+ init : function initLine(x1,y1, x2,y2, tdist){
this.x1 = x1; this.y1 = y1;
this.x2 = x2; this.y2 = y2;
- var xdelta = x2-x1, ydelta = y2-y1
- , m = this.slope = ydelta/xdelta
+ var x0 = x2-x1
+ , y0 = y2-y1
+
+ , m = this.slope = y0/x0
, yi = this.yint = -x1*m + y1
, xi = this.xint = -y1/m + x1
;
- math.Vec.init.call(this, xdelta, ydelta);
+ math.Vec.init.call(this, x0,y0);
this.p1 = new math.Vec(x1,y1);
this.p2 = new math.Vec(x2,y2);
- this.theta = Math.atan2(ydelta, xdelta);
+ this.theta = Math.atan2(y0, x0);
this._cos = Math.cos(this.theta);
this._sin = Math.sin(this.theta);
return new math.Line(this.x1,this.y1, this.x2,this.y2, this.tdist);
},
- equals : function equals(line){
- return ( this.slope === line.slope
- && this.x1 === line.x1 && this.y1 === line.y1
- && this.x2 === line.x2 && this.y2 === line.y2 );
- },
- intersects : function intersects(x,y){
- var o = x;
- if (o instanceof math.Line)
- return this.slope !== o.slope || this.equals(o);
-
- if (o instanceof math.Vec) {
- x = o.x;
- y = o.y;
- }
- return this.calcY(x) === y;
- },
-
- isWithin : function isWithin(pt, w,h){
- if ( !Y.isNumber(w) ){
- h = w.height; w = w.width;
- }
- var dw = Math.abs(this.calcX(pt.y) - pt.x)
- , dh = Math.abs(this.calcY(pt.x) - pt.y) ;
- return dw <= w || dh <= h ;
- },
setTScale : function setTScale(tdist){
if (tdist) {
this.y1 + t*this.pb );
},
- pointAtX : function pointAtX(x){
- return new math.Vec(x, this.calcY(x));
- },
-
- pointAtY : function pointAtY(y){
- return new math.Vec(this.calcX(y), y);
- },
+ 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); },
+ calcX : function calcX(y){ return (y === this.yint ? 0 : y/this.slope + this.xint); },
near : function near(x,y){
if ( !isFinite(this.slope) )
return this.pointAtX(x);
},
- calcY : function calcY(x){
- return (x === this.xint ? 0 : x*this.slope + this.yint);
+
+
+ equals : function equals(line){
+ return ( this.slope === line.slope
+ && this.x1 === line.x1 && this.y1 === line.y1
+ && this.x2 === line.x2 && this.y2 === line.y2 );
},
- calcX : function calcX(y){
- return (y === this.yint ? 0 : y/this.slope + this.xint);
+ intersects : function intersects(x,y){
+ var o = x;
+ if (o instanceof math.Line)
+ return this.slope !== o.slope || this.equals(o);
+
+ if (o instanceof math.Vec) {
+ x = o.x;
+ y = o.y;
+ }
+ return this.calcY(x) === y;
+ },
+
+ isWithin : function isWithin(pt, w,h){
+ if ( !Y.isNumber(w) ){
+ h = w.height; w = w.width;
+ }
+ var dw = Math.abs(this.calcX(pt.y) - pt.x)
+ , dh = Math.abs(this.calcY(pt.x) - pt.y) ;
+ return dw <= w || dh <= h ;
+ },
+
+
+
+ vec : function vec(){
+ return new math.Vec(this.x, this.y); // reset to vector from origin
},
base : function base(){
reflect : function reflect(v, line){
var dot = math.Vec.dot
, basev = math.Vec.difference(v, line.p1);
- return new math.Vec(line.x, line.y)
+ return line.vec()
.scale(2 * dot(basev,line) / dot(line,line))
.subtract(basev)
.add(line.p1);
--- /dev/null
+(function(){
+
+var
+HALF_PI = Math.PI/2,
+SIN_HALF_PI = Math.sin(HALF_PI),
+COS_HALF_PI = Math.cos(HALF_PI);
+
+
+/**
+ * A line in the cartesian plane.
+ */
+math.Line = new Y.Class('Line', math.Vec, {
+
+ init : function initLine(x1,y1, x2,y2, tdist){
+ this.x1 = x1; this.y1 = y1;
+ this.x2 = x2; this.y2 = y2;
+
+ var xdelta = x2-x1, ydelta = y2-y1
+ , m = this.slope = ydelta/xdelta
+ , yi = this.yint = -x1*m + y1
+ , xi = this.xint = -y1/m + x1
+ ;
+ math.Vec.init.call(this, xdelta, ydelta);
+
+ this.p1 = new math.Vec(x1,y1);
+ this.p2 = new math.Vec(x2,y2);
+
+ this.theta = Math.atan2(ydelta, xdelta);
+ this._cos = Math.cos(this.theta);
+ this._sin = Math.sin(this.theta);
+
+ this.setTScale(tdist);
+ },
+
+ clone : function clone(){
+ return new math.Line(this.x1,this.y1, this.x2,this.y2, this.tdist);
+ },
+
+ equals : function equals(line){
+ return ( this.slope === line.slope
+ && this.x1 === line.x1 && this.y1 === line.y1
+ && this.x2 === line.x2 && this.y2 === line.y2 );
+ },
+
+ intersects : function intersects(x,y){
+ var o = x;
+ if (o instanceof math.Line)
+ return this.slope !== o.slope || this.equals(o);
+
+ if (o instanceof math.Vec) {
+ x = o.x;
+ y = o.y;
+ }
+ return this.calcY(x) === y;
+ },
+
+ isWithin : function isWithin(pt, w,h){
+ if ( !Y.isNumber(w) ){
+ h = w.height; w = w.width;
+ }
+ var dw = Math.abs(this.calcX(pt.y) - pt.x)
+ , dh = Math.abs(this.calcY(pt.x) - pt.y) ;
+ return dw <= w || dh <= h ;
+ },
+
+ setTScale : function setTScale(tdist){
+ if (tdist) {
+ this.tdist = tdist;
+ this.pa = tdist * this._cos;
+ this.pb = tdist * this._sin;
+ } else {
+ this.tdist = this.y / this._sin;
+ this.pa = this.x;
+ this.pb = this.y;
+ }
+ return this;
+ },
+
+ parametric : function parametric(t){
+ return new math.Vec( this.x1 + t*this.pa ,
+ this.y1 + t*this.pb );
+ },
+
+ pointAtX : function pointAtX(x){
+ return new math.Vec(x, this.calcY(x));
+ },
+
+ pointAtY : function pointAtY(y){
+ return new math.Vec(this.calcX(y), y);
+ },
+
+ near : function near(x,y){
+ if ( !isFinite(this.slope) )
+ return this.pointAtY(y);
+ else
+ return this.pointAtX(x);
+ },
+
+ calcY : function calcY(x){
+ return (x === this.xint ? 0 : x*this.slope + this.yint);
+ },
+
+ calcX : function calcX(y){
+ return (y === this.yint ? 0 : y/this.slope + this.xint);
+ },
+
+ base : function base(){
+ if (!this._base)
+ this._base = new math.Line(0,0, this.x,this.y);
+ return this._base;
+ },
+
+ tangent : function tangent(at){
+ var slope = this.slope;
+
+ if ( slope === 0 )
+ return new math.Line(at.x,at.y, at.x,at.y+1, this.tdist);
+
+ if ( !isFinite(slope) )
+ return new math.Line(at.x,at.y, at.x+1,at.y, this.tdist);
+
+ var x1 = at.x, y1 = at.y
+ , x2 = at.x - at.y + (at.y !== this.y1 ? this.y1 : this.y2)
+ , y2 = at.y + at.x - (at.x !== this.x1 ? this.x1 : this.x2) ;
+ return new math.Line(x1,y1, x2,y2, this.tdist);
+ },
+
+ toString : function toString(){
+ return '['+this.p1+', '+this.p2+', slope='+this.slope.toFixed(3)+']';
+ }
+
+});
+
+})();
\ No newline at end of file
this.width = lg_x-sm_x;
this.height = lg_y-sm_y;
}
+function rectToString(o){
+ var p = 2
+ , x1 = o.x1, y1 = o.y1
+ , x2 = o.x2, y2 = o.y2 ;
+ x1 = ((x1 % 1 !== 0) ? x1.toFixed(p) : x1);
+ y1 = ((y1 % 1 !== 0) ? y1.toFixed(p) : y1);
+ x2 = ((x2 % 1 !== 0) ? x2.toFixed(p) : x2);
+ y2 = ((y2 % 1 !== 0) ? y2.toFixed(p) : y2);
+ return '['+x1+','+y1+', '+x2+','+y2+']';
+}
var
CAPACITY = 8,
},
toString : function toString(){
- return this.className+'(id='+this.id+', value='+this.value+')';
+ return this.className+'(id='+this.id+', rect='+rectToString(this)+', value='+this.value+')';
}
}),
return acc;
},
+ // _get : function _get(_x1,_y1, _x2,_y2, values){
+ // 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) )
+ // return acc;
+ //
+ // // Implies this is a leaf: check its values
+ // if ( self.regions ) acc = fn.call(cxt, acc, self.regions, self, self);
+ //
+ // // 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);
+ //
+ // return acc;
+ // },
+
set : function set(x1,y1, x2,y2, value){
return this._set(new Region(x1,y1, x2,y2, value));
},
+// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
tanks.config = {
pathing : {
overlayPathmap : false,
- traceTrajectories : false
+ traceTrajectories : true
+ },
+ debug : {
+ projectiles : 1
}
};
\ No newline at end of file
+++ /dev/null
-
-// Set up UI listeners
-function setupUI(){
- spark = LBT.loop.spark = new FpsSparkline(LBT.loop, '.fps-sparkline', 0,0);
-
- initConfig();
- $('#config input').bind('change', updateConfig);
-
- btank = new Tank(0);
- btank.act = function(){ return this; };
- LBT.addUnit(btank, 0,0);
- LBT.pathmap.removeBlocker(btank);
- btank.shape.hide();
-
- LBT.root.draw();
-
- // Start button (click or return key)
- $(document).bind('keydown', 'return', toggleGame);
- $(document).bind('keydown', 'ctrl+o', toggleOverlay);
-
- $('#bullets').bind('blur', function(evt){
- var n = parseInt($('#bullets').val() || 0);
- updateBullets(n);
- });
-
- $('#bullets').blur();
-
- LBT.root.draw();
- setInterval(updateInfo, 1000);
-
- // Start the simulation!
- // toggleGame();
- updateInfo();
-
- // Fix grid-size on resize
- // $(window).bind('resize', resizeGame);
-}
-
-function initConfig(){
- var p = tanks.config.pathing;
- $('#config [name=pathmap]').attr('checked', p.overlayPathmap);
- $('#config [name=trajectories]').attr('checked', p.traceTrajectories);
-}
-
-function updateConfig(evt){
- var p = tanks.config.pathing;
- p.overlayPathmap = $('#config [name=pathmap]').attr('checked');
- p.traceTrajectories = $('#config [name=trajectories]').attr('checked');
-}
-
-var BULLETS = new Y.YArray();
-
-function spawnBullet(x,y, atX,atY){
- if (x === undefined && y === undefined) {
- var loc = randOpenLoc();
- x = loc.x; y = loc.y;
- }
- if (atX === undefined && atY === undefined) do {
- atX = rand(0,COLUMNS*REF_SIZE);
- atY = rand(0,ROWS*REF_SIZE);
- } while ( atX === x && atY === y);
-
- btank.setLocation(x,y);
- return btank.fireProjectile(atX,atY);
-}
-
-function updateBullets(n){
- if ( !Y.isNumber(n) || isNaN(n) || n === BULLETS.size() ) return;
-
- while (n < BULLETS.size()) {
- var i = Math.round(rand(0, BULLETS.size()-1));
- BULLETS.remove( BULLETS.attr(i).remove() );
- }
-
- while (n > BULLETS.size())
- BULLETS.push(spawnBullet());
-}
-
-function rand(a,b){ return a + Math.random()*(b-a); }
-function randOpenLoc(){
- do {
- var x = rand(0,COLUMNS*REF_SIZE)
- , y = rand(0,ROWS*REF_SIZE);
- } while ( LBT.pathmap.get(x,y, x+6,y+6).size() );
- return new math.Vec(x,y);
-}
-
-
-
-// Update performance info periodically
-function updateInfo(){
- var loop = LBT.loop
- , fps = loop.fps()
- , n_units = LBT.units.size()
- , n_projs = LBT.bullets.size()
- ;
-
- $('#info [name=fps]').val( fps.toFixed(2) + " / " + loop.framerate );
- $('#info [name=frame]').val( loop.frametime().toFixed(3)+" ms" );
- $('#info #state').text( loop.running ? 'Running!' : ('Paused (tick '+TICKS+')') );
-
- $('#info [name=objects]').val( n_units+n_projs );
- $('#info [name=units]').val( n_units );
- $('#info [name=bullets]').val( n_projs );
-
- spark.drawTimes();
-
- return false;
-}
-
-function toggleGame(evt){
- if (LBT.loop.running)
- LBT.stop();
- else
- LBT.start();
-
- updateInfo();
-}
-
-function toggleOverlay(evt){
- LBT.showOverlay = !(LBT.showOverlay);
-}
-
-function resizeGame(evt){
- LBT.resize(evt);
-
- if (!LBT.loop.running) {
- LBT.start();
- LBT.stop();
- }
-}
-
-// function initUki(){
-// uki({ view:'Box', rect:'0 0 250 500', anchor:'top width', className:'box', childViews:[
-// { view:'Box', rect:'8 8 250 0', anchor:'top width height', childViews:[
-// { view:'HFlow', rect:'250 25', anchor:'width', childViews:[
-// { view:'Label', rect:'200 25', anchor:'top left', text:'Bullets' },
-// { view:'TextField', rect:'50 25', anchor:'top right', name:'bullets', value:'10', background:'' }
-// ]}
-// ]}
-// ]})
-// .attachTo(
-// $('<div/>').css({
-// 'position':'relative',
-// 'width':'100%',
-// 'top':0, 'left':0
-// }).appendTo('#controls')[0], '0 0');
-// // .attachTo(window, '10 10');
-// }
+var btank = null
+, bullets = new Y.YArray()
+;
+
+
+(function(){
+
jQuery(main);
+
function main(){
- v = $('#viewport');
+ var v = $('#viewport')
+ , sq = REF_SIZE
+ ;
+
LBT = new tanks.Game();
ctx = LBT.level.ctx;
-
- var sq = REF_SIZE;
LBT.level.append(
new Wall(6*sq,1*sq, 1*sq,4*sq),
new Wall(6*sq,7*sq, 1*sq,2*sq),
);
T = LBT.addUnit(new Tank(0), 1,2);
- P = new Player(LBT, T);
+ new Player(LBT, T);
setupUI();
+ B = bullets.attr(0);
+ T = B.trajectory;
+}
+
+
+// Set up UI listeners
+function setupUI(){
+ LBT.loop.spark = new FpsSparkline(LBT.loop, '.fps-sparkline', 0,0);
+
+ btank = new Tank(0);
+ btank.act = function(){ return this; };
+ LBT.addUnit(btank, 0,0);
+ LBT.pathmap.removeBlocker(btank);
+ btank.shape.hide();
+
+ initConfig();
+ $('#config input').bind('change', updateConfig);
+
+ LBT.root.draw();
+
+ // Start button (click or return key)
+ $(document).bind('keydown', 'return', toggleGame);
+ $(document).bind('keydown', 'ctrl+o', toggleOverlay);
+
+ $('#bullets').bind('blur', function(evt){
+ var n = parseInt($('#bullets').val() || 0);
+ updateBullets(n);
+ });
+
+ LBT.root.draw();
+ setInterval(updateInfo, 1000);
+
+ // Start the simulation!
+ // toggleGame();
+ updateInfo();
+
+ // Fix grid-size on resize
+ // $(window).bind('resize', resizeGame);
+}
+
+
+
+function toggleGame(evt){
+ if (LBT.loop.running)
+ LBT.stop();
+ else
+ LBT.start();
+
+ updateInfo();
+}
+
+function toggleOverlay(evt){
+ LBT.showOverlay = !(LBT.showOverlay);
}
+function resizeGame(evt){
+ LBT.resize(evt);
+
+ if (!LBT.loop.running) {
+ LBT.start();
+ LBT.stop();
+ }
+}
+
+
+function initConfig(){
+ var c = tanks.config, p = c.pathing;
+
+ $('#config [name=pathmap]').attr('checked', p.overlayPathmap);
+ $('#config [name=trajectories]').attr('checked', p.traceTrajectories);
+
+ $('#config [name=bullets]').val(c.debug.projectiles);
+ updateBullets( c.debug.projectiles );
+}
+
+function updateConfig(evt){
+ var p = tanks.config.pathing;
+ p.overlayPathmap = $('#config [name=pathmap]').attr('checked');
+ p.traceTrajectories = $('#config [name=trajectories]').attr('checked');
+}
+
+function spawnBullet(x,y, atX,atY){
+ if (x === undefined && y === undefined) {
+ var loc = randOpenLoc();
+ x = loc.x; y = loc.y;
+ }
+ if (atX === undefined && atY === undefined) do {
+ atX = rand(0,COLUMNS*REF_SIZE);
+ atY = rand(0,ROWS*REF_SIZE);
+ } while ( atX === x && atY === y);
+
+ btank.setLocation(x,y);
+ return btank.fireProjectile(atX,atY);
+}
+
+function updateBullets(n){
+ if ( !Y.isNumber(n) || isNaN(n) || n === bullets.size() ) return;
+
+ while (n < bullets.size()) {
+ var i = Math.round(rand(0, bullets.size()-1));
+ bullets.remove( bullets.attr(i).remove() );
+ }
+
+ while (n > bullets.size())
+ bullets.push(spawnBullet());
+}
+
+function rand(a,b){ return a + Math.random()*(b-a); }
+function randOpenLoc(){
+ do {
+ var x = rand(0,COLUMNS*REF_SIZE)
+ , y = rand(0,ROWS*REF_SIZE);
+ } while ( LBT.pathmap.get(x,y, x+6,y+6).size() );
+ return new math.Vec(x,y);
+}
+
+
+
+// Update performance info periodically
+function updateInfo(){
+ var loop = LBT.loop
+ , fps = loop.fps()
+ , n_units = LBT.units.size()
+ , n_projs = LBT.bullets.size()
+ ;
+
+ $('#info [name=fps]').val( fps.toFixed(2) + " / " + loop.framerate );
+ $('#info [name=frame]').val( loop.frametime().toFixed(3)+" ms" );
+ $('#info #state').text( loop.running ? 'Running!' : ('Paused (tick '+TICKS+')') );
+
+ $('#info [name=objects]').val( n_units+n_projs );
+ $('#info [name=units]').val( n_units );
+ $('#info [name=bullets]').val( n_projs );
+
+ loop.spark.drawTimes();
+
+ return false;
+}
+
+
+// function initUki(){
+// uki({ view:'Box', rect:'0 0 250 500', anchor:'top width', className:'box', childViews:[
+// { view:'Box', rect:'8 8 250 0', anchor:'top width height', childViews:[
+// { view:'HFlow', rect:'250 25', anchor:'width', childViews:[
+// { view:'Label', rect:'200 25', anchor:'top left', text:'Bullets' },
+// { view:'TextField', rect:'50 25', anchor:'top right', name:'bullets', value:'10', background:'' }
+// ]}
+// ]}
+// ]})
+// .attachTo(
+// $('<div/>').css({
+// 'position':'relative',
+// 'width':'100%',
+// 'top':0, 'left':0
+// }).appendTo('#controls')[0], '0 0');
+// // .attachTo(window, '10 10');
+// }
+
+})();
+
+function dumpPathmap(){
+ var pm = LBT.pathmap;
+ console.warn(new Date(), pm);
+ pm.collect(pm.x1,pm.y1, pm.x2,pm.y2, function(acc, v, r){
+ console.log(r+'');
+ });
+ console.log(' ');
+}
--- /dev/null
+Collision = new Y.Class('Collision', {
+
+
+
+});
\ No newline at end of file
// [x,y]
-Loc = new Y.Class('Loc', math.Vec, {
+Loc = new Y.Class('Loc', new math.Vec(0,0), {
// init : function init(x,y){
// math.Vec.init.call(this, x,y);
});
-
// [x1,y1, x2,y2]
Loc.Rect = new Y.Class('Rect', [], {
- init : function init(x1,y1, x2,y2){
+
+ init : function initRect(x1,y1, x2,y2){
if (x1 instanceof Loc && y1 instanceof Loc) {
y2 = y1.y; x2 = y1.x;
y1 = x1.y; x1 = x1.x;
this.p1 = new Loc(x1,y1);
this.p2 = new Loc(x2,y2);
+ },
+
+ contains : function contains(x,y){
+ return ( x >= this.x1 && x <= this.x2 &&
+ y >= this.y1 && y <= this.y2 );
+ },
+
+ midpoint : function midpoint(){
+ return new Loc( this.x1 + this.width /2
+ , this.y1 + this.height/2 );
+ },
+
+ clone : function clone(){
+ return new Loc.Rect( this.p1.clone()
+ , this.p2.clone() );
+ },
+
+ toString : function toString(){
+ return '['+this.p1+' '+this.p2+']';
+ }
+});
+
+Loc.BoundingBox = new Y.Class('BoundingBox', Loc.Rect, {
+ init : function initBoundingBox(x1,y1, x2,y2){
+ Loc.Rect.init.call(this, x1,y1, x2,y2);
this.sides = {
top : new math.Line(x1,y1, x2,y1),
attr : Y.attr.methodize(),
moveTo : function moveTo(x,y){
- return new Loc.Rect(x,y, x+this.width,y+this.height);
+ return new Loc.BoundingBox(x,y, x+this.width,y+this.height);
},
resize : function resize(w,h){
var x = this.x, y = this.y;
- return new Loc.Rect(x,y, x+w,y+h);
- },
-
- midpoint : function midpoint(){
- return new Loc( this.x1 + this.width /2
- , this.y1 + this.height/2 );
+ return new Loc.BoundingBox(x,y, x+w,y+h);
},
clone : function clone(){
- return new Loc.Rect( this.top.clone()
- , this.bottom.clone() );
- },
-
- contains : function contains(x,y){
- return ( x >= this.x1 && x <= this.x2 &&
- y >= this.y1 && y <= this.y2 );
- },
-
- toString : function toString(){
- return '['+this.top()+' '+this.bottom()+']';
+ return new Loc.BoundingBox( this.p1.clone(), this.p2.clone() );
}
});
-Loc.Square = new Y.Class('Square', Loc.Rect, {
+Loc.Square = new Y.Class('Square', Loc.BoundingBox, {
init : function init(col, row){
this.col = col;
this.row = row;
+// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
PathMap = new Y.Class('PathMap', QuadTree, {
init : function init(game, x1,y1, x2,y2, capacity) {
this.game = game;
var w = this.width, h = this.height;
- this.walls = {
- top : new math.Line(0,0, w,0),
- bottom : new math.Line(0,h, w,h),
- left : new math.Line(0,0, 0,h),
- right : new math.Line(w,0, w,h)
- };
-
// this.walls = {
- // top : new Wall(0,0, w,1),
- // bottom : new Wall(0,h, w,1),
- // left : new Wall(0,0, 1,h),
- // right : new Wall(w,0, 1,h)
+ // top : new math.Line(0,0, w,0),
+ // bottom : new math.Line(0,h, w,h),
+ // left : new math.Line(0,0, 0,h),
+ // right : new math.Line(w,0, w,h)
// };
- // Y(this.walls).forEach(this.addBlocker, this);
+
+ 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)
+ };
+ Y(this.walls).forEach(this.addBlocker, this);
},
addBlocker : function addBlocker(obj){
, bb = obj.boundingBox
, minW = 2, minH = 2
, maxW = this.width-2, maxH = this.height-2
- , w = bb.width, h = bb.height
+ , bw = bb.width, bh = bb.height
, x1 = to.x, y1 = to.y
- , x2 = x1+w, y2 = y1+h
+ , x2 = x1+bw, y2 = y1+bh
;
// Check for collision with the walls to prevent teleporting units
- if (x1 < minW || x2 > maxW || y1 < minH || y2 > maxH){
- blocker = this.game.level;
- what = 'LevelWall on the ';
-
- if (x1 < minW) {
- what = 'left';
- wall = this.walls.left;
- to = trj.pointAtX(minW);
-
- } else if (x2 > maxW) {
- what = 'right';
- wall = this.walls.right;
- to = trj.pointAtX(maxW-w);
-
- } else if (y1 < minH) {
- what = 'top';
- wall = this.walls.top;
- to = trj.pointAtY(minH);
-
- } else if (y2 > maxH) {
- what = 'bottom';
- wall = this.walls.bottom;
- to = trj.pointAtY(maxH-h);
- }
-
- what += ' at '+wall;
- return { 'ok':!wall, 'to':to, 'wall':wall, 'blocker':blocker, 'what':what };
- }
+ // if (x1 < minW || x2 > maxW || y1 < minH || y2 > maxH){
+ // blocker = this.game.level;
+ // what = 'LevelWall on the ';
+ //
+ // if (x1 < minW) {
+ // what = 'left';
+ // wall = this.walls.left;
+ // to = trj.pointAtX(minW);
+ //
+ // } else if (x2 > maxW) {
+ // what = 'right';
+ // wall = this.walls.right;
+ // to = trj.pointAtX(maxW-bw);
+ //
+ // } else if (y1 < minH) {
+ // what = 'top';
+ // wall = this.walls.top;
+ // to = trj.pointAtY(minH);
+ //
+ // } else if (y2 > maxH) {
+ // what = 'bottom';
+ // wall = this.walls.bottom;
+ // to = trj.pointAtY(maxH-bh);
+ // }
+ //
+ // what += ' at '+wall;
+ // return { 'ok':!wall, 'to':to, 'wall':wall, 'blocker':blocker, 'what':what };
+ // }
// Check for pathmap collisions
var blockers = this.get(x1,y1, x2,y2).remove(obj);
if (bb.x2 <= B.x1 && x2 > B.x1) {
what += 'left';
wall = B.sides.left;
- to = trj.pointAtX(B.x1-w);
+ to = trj.pointAtX(B.x1-bw);
} else if (bb.x1 >= B.x2 && x1 < B.x2) {
what += 'right';
wall = B.sides.right;
} else if (bb.y2 <= B.y1 && y2 > B.y1) {
what += 'top';
wall = B.sides.top;
- to = trj.pointAtY(B.y1-h);
+ to = trj.pointAtY(B.y1-bh);
} else if (bb.y1 >= B.y2 && y1 < B.y2) {
what += 'bottom';
wall = B.sides.bottom;
--- /dev/null
+Trajectory = new Y.Class('Trajectory', math.Line, {
+ elapsed : 0,
+ depth : 0,
+
+
+ init : function initTrajectory(owner, x1,y1, x2,y2, tdist){
+ this.owner = owner;
+ this.game = owner.game;
+ this.pathmap = this.game.pathmap;
+
+ this.reset(x1,y1, x2,y2, tdist);
+ },
+
+ reset : function reset(x1,y1, x2,y2, tdist){
+ // init with raw numbers to do calculations
+ math.Line.init.call(this, x1,y1, x2,y2, tdist || this.tdist);
+
+ 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);
+
+ // Move goal point beyond far wall to avoid rotations
+ x2 = this.x2 = edge.x;
+ y2 = this.y2 = edge.y;
+ this.p2 = new math.Vec(x2,y2);
+ math.Vec.init.call(this, x2-x1, y2-y1);
+
+ this.elapsed = 0;
+ this.resetBound();
+ return this;
+ },
+
+ // Determine how much time can pass before we risk teleporting
+ resetBound : function resetBound(){
+ var 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) );
+ return this;
+ },
+
+ step : function step(dt){
+ var _dt = dt = (dt === undefined ? ELAPSED : dt);
+
+ var o = this.owner, offX = o.offsetX, offY = o.offsetY, _bb
+ , bb = _bb = o.boundingBox, bw = bb.width, bh = bb.height
+ , loc = bb.p1
+ ;
+
+ do {
+ var t, to, ng, x1,y1, x2,y2, bs, blocker, blockers, what, wall;
+
+ t = Math.min(this.tBound, dt);
+ dt -= t;
+ this.elapsed += t;
+
+ var _to = to = this.parametric(this.elapsed);
+ x1 = to.x+offX; y1 = to.y+offY;
+ x2 = x1+bw; y2 = y1+bh;
+
+ bs = this.pathmap.get(x1,y1, x2,y2).remove(o).end();
+ blocker = bs[0];
+
+ // Literal corner case :P
+ if (bs.length > 1) {
+ console.log('multiple blockers! '+bs.slice(0));
+ what = 'corner of '+bs.slice(0);
+ to = loc;
+ ng = this.p1; // XXX: recalculate?
+
+ // Normal reflection against one line
+ } else if ( blocker ) {
+ what = blocker+' on the ';
+
+ var B = blocker.boundingBox;
+ if (bb.x2 <= B.x1 && x2 > B.x1) {
+ what += 'left';
+ wall = B.sides.left;
+ to = this.pointAtX(B.x1-bw-offX-1);
+
+ } else if (bb.x1 >= B.x2 && x1 < B.x2) {
+ what += 'right';
+ wall = B.sides.right;
+ to = this.pointAtX(B.x2-offX+1);
+
+ } else if (bb.y2 <= B.y1 && y2 > B.y1) {
+ what += 'top';
+ wall = B.sides.top;
+ to = this.pointAtY(B.y1-bh-offY-1);
+
+ } else if (bb.y1 >= B.y2 && y1 < B.y2) {
+ what += 'bottom';
+ wall = B.sides.bottom;
+ to = this.pointAtY(B.y2-offY+1);
+ }
+
+ ng = math.reflect(this.p2, wall);
+ }
+
+ // No collision: don't change trajectory
+ if (!ng) {
+ this.depth = 0;
+
+ // New goal-point: change trajectory
+ } else {
+ var og = this.p2;
+ this.reset(to.x,to.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: '+what,
+ ' old:',
+ ' loc: '+bb.p1,
+ ' goal: '+og,
+ ' new:',
+ ' loc: '+to,
+ ' goal: '+ng,
+ ' --> trajectory: '+this
+ ].join('\n'));
+ }
+
+ bb = new Loc.Rect(to.x,to.y, ng.x,ng.y);
+ ng = null;
+
+ this.owner.render(this.game.level);
+ // this.owner.render(this.game.level).draw();
+
+ 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!');
+ }
+ }
+
+ } while (dt > 0);
+
+ // console.log('['+TICKS+':'+_dt+' ('+this.depth+')]', loc, ' ~> ', to);
+ this.depth = 0;
+ return to;
+ },
+
+
+ toString : function toString(){
+ return 'T['+this.p1+', '+this.p2+', slope='+this.slope.toFixed(3)+']';
+ }
+});
\ No newline at end of file
+// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
Bullet = new Y.Class('Bullet', Thing, {
traceTrajectories : true,
* @param {tanks.Unit} owner
* @param {math.Line} trajectory
*/
- init : function initBullet(owner, x,y){
+ init : function initBullet(owner, x2,y2){
this.owner = owner;
- Thing.init.call(this, owner.game, owner.align);
- var loc = owner.loc;
- this.trajectory = new math.Line(loc.x,loc.y, x,y, this.stats.move*REF_SIZE/1000);
- this.setLocation(loc.x,loc.y);
+ this.game = owner.game;
+ Thing.init.call(this, owner.align);
+ var loc = owner.loc
+ , 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);
},
- blocking : true,
elapsed : 0,
- width : 6,
- height : 6,
+
+ blocking : true,
+
+ offsetX : -3,
+ offsetY : -3,
+ width : 6,
+ height : 6,
stats : {
move : 2.0, // move speed (squares/sec)
createCooldowns : Y.op.nop,
-
- setLocation : function setLocation(x,y){
- var loc = this.loc;
- if (loc && loc.x === x && loc.y === y)
- return loc;
-
- loc = this.loc = new Loc(x,y);
-
- if (this.shape) this.shape.position(x,y);
- var w2 = this.width/2, h2 = this.height/2;
- this.boundingBox = new Loc.Rect(x-w2,y-h2, x+w2,y+h2);
-
- return this;
- },
-
- // setLocation : function setLocation(x,y){
- // var trj = this.trajectory;
- // this.trajectory = new math.Line(x,y, trj.x2,trj.y2, trj.tdist);
- // this.elapsed = 0;
- // return Thing.prototype.setLocation.call(this, x,y);
- // },
-
setTarget : function setTarget(x,y){
var loc = this.loc
, trj = this.trajectory;
- this.trajectory = new math.Line(loc.x,loc.y, x,y, trj.tdist);
- this.elapsed = 0;
+ 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;
+ // this.elapsed += ELAPSED;
if (!this.dead)
this.move();
return this;
},
- _depth : 0,
move : function move(){
+ var to = this.trajectory.step( ELAPSED );
+ 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)
if (this._depth++ < 5)
return this; // this.move();
- console.log('Reflection limit reached!');
+ console.error('Reflection limit reached!');
this._depth = 0;
return this;
}
.appendTo( parent );
}
+ var loc = this.loc;
this.shape = new Circle(3)
- .position(this.loc.x+6, this.loc.y+6)
+ .position(loc.x, loc.y)
.fill('#EC5B38')
.appendTo( parent );
+ this.shape.layer.attr('title', ''+loc);
+
return this;
},
// Rotation (rads)
rotation : 0,
- // Bounding box dimensions
+ // Bounding box offsets/dimensions
+ offsetX : 0,
+ offsetY : 0,
width : REF_SIZE*0.7,
height : REF_SIZE*0.6,
},
setLocation : function setLocation(x,y){
- var loc = this.loc;
- if (loc && loc.x === x && loc.y === y)
+ var loc = this.loc
+ , x1 = x + this.offsetX
+ , y1 = y + this.offsetY
+ , x2 = x1 + this.width
+ , y2 = y1 + this.height
+ ;
+
+ if (!loc)
+ loc = this.loc = new Loc(x,y);
+ else if (loc.x === x && loc.y === y)
return loc;
- loc = this.loc = new Loc(x,y);
- if (this.shape) this.shape.position(x,y);
- this.boundingBox = new Loc.Rect(x,y, x+this.width,y+this.height);
+ else
+ this.loc.setXY(x,y);
+
+ if (this.shape)
+ this.shape.position(x,y);
+
+ // this.createBoundingBox(x,y);
+ this.boundingBox = new Loc.BoundingBox(x1,y1, x2,y2);
+
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;
+ },
static $mainScripts = array(
- "src/tanks/main.js",
- "src/tanks/main-ui.js"
+ "src/tanks/main.js"
+ // "src/tanks/main-ui.js"
);
static $srcScripts = array(
"src/tanks/calc.js",
"src/tanks/map/loc.js",
+ "src/tanks/map/trajectory.js",
"src/tanks/map/level.js",
"src/tanks/map/pathmap.js",