Fixes teleporting bullet issue.
authordsc <david.schoonover@gmail.com>
Wed, 17 Nov 2010 06:55:09 +0000 (22:55 -0800)
committerdsc <david.schoonover@gmail.com>
Wed, 17 Nov 2010 06:55:09 +0000 (22:55 -0800)
16 files changed:
notes.md
src/portal/layer.js
src/portal/math/line.js
src/portal/math/math.js
src/portal/math/oldline.js [new file with mode: 0644]
src/portal/util/tree/quadtree.js
src/tanks/config.js
src/tanks/main-ui.js [deleted file]
src/tanks/main.js
src/tanks/map/collision.js [new file with mode: 0644]
src/tanks/map/loc.js
src/tanks/map/pathmap.js
src/tanks/map/trajectory.js [new file with mode: 0644]
src/tanks/thing/bullet.js
src/tanks/thing/thing.js
tanks.php

index fcc68f9..4961760 100644 (file)
--- a/notes.md
+++ b/notes.md
@@ -9,6 +9,7 @@
 # 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
index a3e2523..ebf2f50 100644 (file)
@@ -43,7 +43,7 @@ Layer = new Y.Class('Layer', {
         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
@@ -348,9 +348,13 @@ Layer = new Y.Class('Layer', {
     
     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;
     },
     
index e213436..e6b5172 100644 (file)
@@ -1,6 +1,7 @@
 (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);
@@ -11,21 +12,23 @@ 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);
         
@@ -36,32 +39,7 @@ math.Line = new Y.Class('Line', math.Vec, {
         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) {
@@ -81,13 +59,10 @@ math.Line = new Y.Class('Line', math.Vec, {
                              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) )
@@ -96,12 +71,39 @@ math.Line = new Y.Class('Line', math.Vec, {
             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(){
index bb8a8a1..882cdbb 100644 (file)
@@ -10,7 +10,7 @@ math = {
     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);
diff --git a/src/portal/math/oldline.js b/src/portal/math/oldline.js
new file mode 100644 (file)
index 0000000..b13b4bf
--- /dev/null
@@ -0,0 +1,134 @@
+(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
index 47b9fa7..d1ac1c8 100644 (file)
@@ -10,6 +10,16 @@ function Rect(x1,y1, x2,y2){
     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,
@@ -29,7 +39,7 @@ Region = new Y.Class('Region', {
     },
     
     toString : function toString(){
-        return this.className+'(id='+this.id+', value='+this.value+')';
+        return this.className+'(id='+this.id+', rect='+rectToString(this)+', value='+this.value+')';
     }
 }),
 
@@ -71,6 +81,28 @@ QuadTree = new Y.Class('QuadTree', {
         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));
     },
index b1b958f..7092331 100644 (file)
@@ -1,6 +1,10 @@
+//  -*- 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
diff --git a/src/tanks/main-ui.js b/src/tanks/main-ui.js
deleted file mode 100644 (file)
index c0f014e..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-
-// 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');
-// }
index 5ed561d..3d3035b 100644 (file)
@@ -1,12 +1,21 @@
+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),
@@ -15,8 +24,174 @@ function main(){
     );
     
     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(' ');
+}
diff --git a/src/tanks/map/collision.js b/src/tanks/map/collision.js
new file mode 100644 (file)
index 0000000..b1694f2
--- /dev/null
@@ -0,0 +1,5 @@
+Collision = new Y.Class('Collision', {
+    
+    
+    
+});
\ No newline at end of file
index ae77649..edc840a 100644 (file)
@@ -1,5 +1,5 @@
 // [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);
@@ -93,10 +93,10 @@ Y(Loc).extend({
     
 });
 
-
 // [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;
@@ -113,6 +113,31 @@ Loc.Rect = new Y.Class('Rect', [], {
         
         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),
@@ -162,36 +187,21 @@ Loc.Rect = new Y.Class('Rect', [], {
     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;
index 06f6927..9aeb818 100644 (file)
@@ -1,3 +1,4 @@
+//  -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
 PathMap = new Y.Class('PathMap', QuadTree, {
     
     init : function init(game, x1,y1, x2,y2, capacity) {
@@ -5,20 +6,20 @@ PathMap = new Y.Class('PathMap', QuadTree, {
         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){
@@ -43,40 +44,40 @@ PathMap = new Y.Class('PathMap', QuadTree, {
         ,   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);
@@ -93,7 +94,7 @@ PathMap = new Y.Class('PathMap', QuadTree, {
             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;
@@ -101,7 +102,7 @@ PathMap = new Y.Class('PathMap', QuadTree, {
             } 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;
diff --git a/src/tanks/map/trajectory.js b/src/tanks/map/trajectory.js
new file mode 100644 (file)
index 0000000..fae22dd
--- /dev/null
@@ -0,0 +1,155 @@
+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
index 425b086..3711d74 100644 (file)
@@ -1,3 +1,4 @@
+//  -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
 Bullet = new Y.Class('Bullet', Thing, {
     traceTrajectories : true,
     
@@ -6,17 +7,25 @@ Bullet = new Y.Class('Bullet', Thing, {
      * @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)
@@ -38,45 +47,30 @@ Bullet = new Y.Class('Bullet', Thing, {
     
     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)
@@ -123,7 +117,7 @@ Bullet = new Y.Class('Bullet', Thing, {
             if (this._depth++ < 5)
                 return this; // this.move();
             
-            console.log('Reflection limit reached!');
+            console.error('Reflection limit reached!');
             this._depth = 0;
             return this;
         }
@@ -148,10 +142,13 @@ Bullet = new Y.Class('Bullet', Thing, {
                 .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;
     },
index c2119b8..79aff1c 100644 (file)
@@ -32,7 +32,9 @@ Thing = new Evt.Class('Thing', {
     // 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,
     
@@ -52,15 +54,45 @@ Thing = new Evt.Class('Thing', {
     },
     
     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;
+    },
     
     
     
index 15f8e17..fbc90d6 100644 (file)
--- a/tanks.php
+++ b/tanks.php
@@ -11,8 +11,8 @@ class Tanks {
     
     
     static $mainScripts = array(
-        "src/tanks/main.js",
-        "src/tanks/main-ui.js"
+        "src/tanks/main.js"
+        // "src/tanks/main-ui.js"
     );
     
     static $srcScripts = array(
@@ -22,6 +22,7 @@ class Tanks {
         "src/tanks/calc.js",
         
         "src/tanks/map/loc.js",
+        "src/tanks/map/trajectory.js",
         "src/tanks/map/level.js",
         "src/tanks/map/pathmap.js",