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 {