Bullets now deal damage, kill each other on collision.
authordsc <david.schoonover@gmail.com>
Thu, 18 Nov 2010 07:11:44 +0000 (23:11 -0800)
committerdsc <david.schoonover@gmail.com>
Thu, 18 Nov 2010 07:11:44 +0000 (23:11 -0800)
19 files changed:
notes.md
src/Y/core.js
src/Y/modules/y.event.js
src/Y/y-core.js
src/Y/y-function.js
src/portal/shape/rect.js
src/tanks/game/game-map.js [deleted file]
src/tanks/game/game.js
src/tanks/globals.js
src/tanks/main.js
src/tanks/map/collision.js [deleted file]
src/tanks/map/level.js
src/tanks/map/pathmap.js
src/tanks/map/trajectory.js
src/tanks/thing/bullet.js
src/tanks/thing/tank.js
src/tanks/thing/thing.js
src/tanks/ui/player.js
tanks.php

index de1c16c..04afe34 100644 (file)
--- a/notes.md
+++ b/notes.md
@@ -1,5 +1,9 @@
 # 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
index 493e4bf..3718818 100644 (file)
@@ -5,22 +5,56 @@ function notWrapped(fn){
     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);
index 7e085cf..d3d0be5 100644 (file)
@@ -36,12 +36,12 @@ Y.YObject.subclass('YEvent', {
     },
     
     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){
index 5bcfddb..a7abd34 100644 (file)
@@ -1,8 +1,11 @@
 
-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;
index f9d6984..dde3287 100644 (file)
@@ -287,6 +287,9 @@ YFunction.prototype.getName = Y.getName
 
 YFunction(Y);
 Y(Y.reduce);
+Y(Y.map);
+Y(Y.forEach);
+Y(Y.filter);
 Y(Y.set);
 Y(Y.attr);
 Y(Y.extend);
index 4bd8c76..0d84e51 100644 (file)
@@ -13,6 +13,13 @@ Rect = new Y.Class('Rect', Shape, {
     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+')';
     }
     
 });
diff --git a/src/tanks/game/game-map.js b/src/tanks/game/game-map.js
deleted file mode 100644 (file)
index 5986158..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-
-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);
-    }
-    
-});
index aefec21..a26567d 100644 (file)
@@ -1,20 +1,39 @@
 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);
@@ -34,12 +53,108 @@ tanks.Game = new Y.Class('Game', {
         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);
     }
     
+    
 });
 
 
index ad7eeb4..0f80622 100644 (file)
@@ -33,11 +33,11 @@ var undefined
 
 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
     };
 }
index 7e5e0b8..c8b3929 100644 (file)
@@ -6,21 +6,12 @@ jQuery(main);
 
 
 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();
@@ -35,7 +26,7 @@ function main(){
 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);
diff --git a/src/tanks/map/collision.js b/src/tanks/map/collision.js
deleted file mode 100644 (file)
index ee4ea47..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Collision = new Y.Class('Collision', {
-    
-    init : function initCollision(line, to){
-        
-    },
-    
-});
\ No newline at end of file
index 608102e..ce7602b 100644 (file)
@@ -1,41 +1,79 @@
-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+')';
     }
-    
 });
 
index 44151fb..bce6aca 100644 (file)
@@ -14,10 +14,10 @@ PathMap = new Y.Class('PathMap', QuadTree, {
         // };
         
         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);
     },
index e99fca0..d5433ae 100644 (file)
@@ -1,8 +1,9 @@
 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;
@@ -33,100 +34,84 @@ Trajectory = new Y.Class('Trajectory', math.Line, {
     
     // 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;
     },
     
index 83041c2..86e6d8a 100644 (file)
@@ -8,18 +8,21 @@ Bullet = Thing.subclass('Bullet', {
      * @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,
@@ -27,12 +30,9 @@ Bullet = Thing.subclass('Bullet', {
     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),
@@ -52,13 +52,10 @@ Bullet = Thing.subclass('Bullet', {
         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;
@@ -66,77 +63,28 @@ Bullet = Thing.subclass('Bullet', {
     
     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) {
@@ -154,7 +102,6 @@ Bullet = Thing.subclass('Bullet', {
             .appendTo( parent );
         this.shape.layer.attr('title', ''+loc);
         
-        
         return this;
     },
     
index 2d6314b..8650a5c 100644 (file)
@@ -1,5 +1,5 @@
 
-Tank = new Y.Class('Tank', Thing, {
+Tank = Thing.subclass('Tank', {
     projectile : Bullet,
     blocking : true,
     
@@ -9,12 +9,14 @@ Tank = new Y.Class('Tank', Thing, {
     
     // 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
     },
     
     
@@ -82,7 +84,7 @@ Tank = new Y.Class('Tank', Thing, {
                 .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 )
         ;
index 776d64d..c8482e0 100644 (file)
@@ -9,12 +9,14 @@ Thing = new Evt.Class('Thing', {
     },
     
     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
     },
     
     
@@ -22,12 +24,14 @@ Thing = new Evt.Class('Thing', {
     // *** 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,
@@ -39,16 +43,12 @@ Thing = new Evt.Class('Thing', {
     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;
     },
     
@@ -76,28 +76,19 @@ Thing = new Evt.Class('Thing', {
         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);
     },
     
@@ -108,26 +99,19 @@ Thing = new Evt.Class('Thing', {
     },
     
     /**
+     * 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;
     },
     
@@ -137,9 +121,9 @@ Thing = new Evt.Class('Thing', {
         return this;
     },
     
-    getTurretLoc : function getTurretLoc(){
-        return this.loc;
-    },
+    
+    
+    
     
     /// Rendering Methods ///
     
@@ -173,9 +157,8 @@ Y(Thing).extend({
         ;
         
         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 )
index 7d011a7..aa1be67 100644 (file)
@@ -60,7 +60,6 @@ Player = new Y.Class('Player', {
         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);
index fbc90d6..1ae6e32 100644 (file)
--- a/tanks.php
+++ b/tanks.php
@@ -21,20 +21,19 @@ class Tanks {
         "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(