The tank moves!
authordsc <david.schoonover@gmail.com>
Sun, 31 Oct 2010 10:23:23 +0000 (03:23 -0700)
committerdsc <david.schoonover@gmail.com>
Sun, 31 Oct 2010 10:23:23 +0000 (03:23 -0700)
32 files changed:
css/lttl.css
index.php
src/Y/core.js
src/Y/modules/simpleclass.js [moved from src/portal/simpleclass.js with 100% similarity]
src/Y/y-function.js
src/Y/y.js.php
src/js/_intro.js [deleted file]
src/js/_outro.js [deleted file]
src/js/array.js [deleted file]
src/js/core.js [deleted file]
src/js/function.js [deleted file]
src/js/js.js.php [deleted file]
src/js/number.js [deleted file]
src/js/object.js [deleted file]
src/js/regexp.js [deleted file]
src/js/string.js [deleted file]
src/js/type.js [deleted file]
src/portal/layer.js
src/portal/shape.js
src/portal/util/loc.js
src/portal/util/quadtree.js
src/tanks/game.js [deleted file]
src/tanks/game/game.js [new file with mode: 0644]
src/tanks/game/map.js [new file with mode: 0644]
src/tanks/game/player.js [new file with mode: 0644]
src/tanks/lttl.js
src/tanks/map.js [deleted file]
src/tanks/tank.js [deleted file]
src/tanks/ui.js
src/tanks/unit/tank.js [new file with mode: 0644]
src/tanks/util/grid.js [new file with mode: 0644]
src/tanks/util/pathmap.js [moved from src/simoon/grid/grid.js with 96% similarity]

index 2a24362..1d2321e 100644 (file)
@@ -7,13 +7,12 @@ ul, ol, li { list-style: none ! important; margin:0; padding:0; }
 .rounded { border-radius:1em; -moz-border-radius:1em; -webkit-border-radius:1em; }
 
 #viewport { position:relative; top:1em; width:500px; height:500px; margin:0 auto;
-    outline:1px solid #ccc; }
+    /* outline:1px solid #ccc; */ }
 
 #howto { position:fixed; top:3em; right:1em; color:#BFBFBF; }
 
 #info { position:fixed; bottom:10px; right:10px; padding:0.5em; background-color:rgba(0,0,0, 0.1); color:#787878; }
     #info label { display:block; float:left; width:3em; margin-right:0.5em; color:#787878; }
     #info input { border:0; background-color:transparent; min-width:5em; width:5em; color:#5c5c5c; }
-
-#log { position:fixed; top:auto; bottom:0; left:0; width:100%; height:30%; border-top:1px solid #bbb; }
+    #info .sep { opacity:0.1; background-color:#999; margin:5px 0; height:1px; }
 
index 8806461..04f9df7 100644 (file)
--- a/index.php
+++ b/index.php
     <li id="state"></li>
     <li><label for="fps">fps</label> <input id="fps" name="fps" value="" type="text"></li>
     <li><label for="frame">frame</label> <input id="frame" name="frame" value="" type="text"></li>
-    <li><label for="agents">agents</label> <input id="agents" name="agents" value="" type="text"></li>
+    <li><div class="sep"></div></li>
+    <li><label for="objects">objects</label> <input id="objects" name="objects" value="" type="text"></li>
     <li><label for="units">units</label> <input id="units" name="units" value="" type="text"></li>
     <li><label for="bullets">bullets</label> <input id="bullets" name="bullets" value="" type="text"></li>
 </ul>
 
-<div id="log" style="display:none"></div>
-
 <div id="scripts">
     <!--[if IE]><script type="text/javascript" src="lib/excanvas.min.js"></script><![endif]-->
 <?php
@@ -40,19 +39,25 @@ $scripts = array(
     
     "src/portal/layer.js",
     "src/portal/shape.js",
+    "src/portal/util/quadtree.js",
+    "src/portal/util/rbtree.js",
+    "src/portal/util/eventloop.js",
+    "src/portal/util/cooldown.js",
+    "src/portal/util/loc.js",
+    
+    "src/tanks/util/pathmap.js",
+    "src/tanks/util/grid.js",
+    
+    "src/tanks/game/game.js",
+    "src/tanks/game/map.js",
+    
+    "src/tanks/unit/tank.js",
     
-    // "src/portal/util/quadtree.js",
-    // "src/portal/util/rbtree.js",
-    // "src/portal/util/eventloop.js",
-    // "src/portal/util/cooldown.js",
-    // "src/portal/util/loc.js",
+    "src/tanks/game/player.js",
     
-    "src/tanks/map.js",
-    "src/tanks/tank.js",
-    "src/tanks/game.js",
-    "src/tanks/ui.js",
+    "src/tanks/lttl.js",
+    "src/tanks/ui.js"
     
-    "src/tanks/lttl.js"
 );
 
 function js($src) {
index d7c0080..0a14fee 100644 (file)
@@ -9,6 +9,7 @@ function reduce(o, fn, acc, cxt){
     if ( !o )
         return acc;
     
+    fn = Function.toFunction(fn);
     if ( notSelfOrWrapped(o.reduce) )
         return o.reduce.apply(o, slice.call(arguments,1));
     
index d73707d..e729d92 100644 (file)
@@ -48,6 +48,7 @@ function curry(fn){
     if (fn.__curried__)
         return fn.apply(this, Y(arguments,1));
     
+    fn = Function.toFunction(fn);
     var args = Y(arguments, 1)
     ,   L = unwrap(fn).length;
     
@@ -87,7 +88,7 @@ Y.compose = compose;
 YFunction.prototype.compose = methodize(compose);
 function _composer(x,fn){ return fn.call(this, x); }
 function compose(f,g){
-    var fns = Y(arguments);
+    var fns = Y(arguments).map(Function.toFunction);
     return function(){
         return fns.reduce(_composer, Y(arguments), this);
     };
@@ -96,7 +97,7 @@ function compose(f,g){
 Y.chain = chain;
 YFunction.prototype.chain = methodize(chain);
 function chain(f,g){
-    var fns = Y(arguments);
+    var fns = Y(arguments).map(Function.toFunction);
     
     if ( g.__sequence__ )
         fns = g.__sequence__.concat( fns.slice(1) );
index e86a8af..73f48e7 100644 (file)
@@ -10,6 +10,7 @@ function dump_file($path, $add_newline=true){
 }
 
 $y_files = array(
+    'to-function',
     'alias',
     'type',
     'core',
diff --git a/src/js/_intro.js b/src/js/_intro.js
deleted file mode 100644 (file)
index 599c5c9..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-(function(){
-
-var undefined,
-globals  = this,
-_toString = _Object.prototype.toString,
-_hasOwn   = _Object.prototype.hasOwnProperty,
-_isArray  = _Array.isArray;
diff --git a/src/js/_outro.js b/src/js/_outro.js
deleted file mode 100644 (file)
index efe95da..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-
-globals.reduce = reduce;
-globals.extend = extend;
-globals.attr   = attr;
-
-})();
diff --git a/src/js/array.js b/src/js/array.js
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/src/js/core.js b/src/js/core.js
deleted file mode 100644 (file)
index 40c493e..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-// Generic Collection Functions
-
-function notSelfOrWrapped(fn){
-    var self = arguments.callee.caller;
-    return fn && fn !== self && fn.__wraps !== self;
-}
-
-function reduce(o, fn, acc, cxt){
-    if ( !o )
-        return acc;
-    
-    if ( notSelfOrWrapped(o.reduce) )
-        return o.reduce.apply(o, slice.call(arguments,1));
-    
-    cxt = cxt || o;
-    for ( var name in o )
-        acc = fn.call(cxt, acc, o[name], name, o);
-    
-    return acc;
-}
-
-function attr(o, key, value, def){
-    if ( o && notSelfOrWrapped(o.attr) )
-        return o.attr.apply(o, slice.call(arguments,1));
-    
-    if ( value !== undefined || def !== undefined ){
-        o[key] = (value !== undefined ? value : def);
-        return o;
-    } else
-        return o[key];
-}
-
-function extend( A, B ){
-    return slice.call(arguments,1).reduce(function(A, donor){
-        return reduce(donor, function(o, v, k){
-            return attr(o, k, v, o[k]);
-        }, A);
-    }, A);
-}
diff --git a/src/js/function.js b/src/js/function.js
deleted file mode 100644 (file)
index c52011f..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-var WR_P = "__wraps__";
-
-
-function unwrap(fn){
-    return ( fn && isFunction(fn) ) ? unwrap(fn[WR_P]) || fn : fn;
-}
-
-Function.prototype.curry = curry;
-function curry(){
-    var fn = this
-    ,   args = Array.slice(arguments,0)
-    ,   L = unwrap(fn).length;
-    
-    function curried(){
-        var _args = args.concat(Array.slice(arguments,0));
-        if ( _args.length >= L )
-            return fn.apply(this, _args);
-        else
-            return curry.apply(fn, _args);
-    }
-    curried[WR_P] = fn;
-    
-    return curried;
-}
-
-Function.prototype.methodize = methodize;
-function methodize() {
-    var fn = this;
-    if ( fn.__methodized__ )
-        return fn.__methodized__;
-    
-    var m = fn.__methodized__ =
-        function(){
-            return fn.apply(this, [this].concat(Array.slice(arguments, 0)));
-        };
-    m[WR_P] = fn;
-    return m;
-}
-
-/** Returns the declared name of a function. */
-Function.prototype.getName = getName;
-function getName(){
-    var fn = this;
-    return fn.className || fn.name || (fn+'').match( /function\s*([^\(]*)\(/ )[1] || '';
-}
-
diff --git a/src/js/js.js.php b/src/js/js.js.php
deleted file mode 100644 (file)
index 8868e17..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-function dump_file($path, $add_newline=true){
-    $size = filesize($path);
-    if ($size > 0) {
-        $f = fopen($path, "r");
-        echo fread($f, $size);
-        fclose($f);
-    }
-    if ($add_newline) echo "\n";
-}
-
-$jsjs_files = array(
-    'type',
-    'core',
-    'function',
-    'object',
-    'array',
-    'string',
-    'number'
-);
-
-function jsjs_list($path='') {
-    global $jsjs_files;
-    $path = $path ? $path : dirname($_SERVER["REQUEST_URI"]);
-    // echo $path;
-    foreach ($jsjs_files as $f) {
-        echo "<script src='$path/$f.js' type='text/javascript'></script>\n";
-    }
-}
-
-function jsjs_dump($expose=false) {
-    global $jsjs_files;
-    if (!$expose)
-        dump_file("./_intro.js");
-    foreach ($jsjs_files as $f)
-        dump_file("./$f.js");
-    if (!$expose)
-        dump_file("./_outro.js");
-}
-
-if ( basename($_SERVER["SCRIPT_FILENAME"]) == basename(__FILE__) ) {
-    if ( $_REQUEST["list"] )
-        jsjs_list();
-    else
-        jsjs_dump();
-}
\ No newline at end of file
diff --git a/src/js/number.js b/src/js/number.js
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/src/js/object.js b/src/js/object.js
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/src/js/regexp.js b/src/js/regexp.js
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/src/js/string.js b/src/js/string.js
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/src/js/type.js b/src/js/type.js
deleted file mode 100644 (file)
index 1fd1447..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-// Type Utilities //
-// Much borrowed from jQuery
-
-var class2type = "Boolean Number String Function Array Date RegExp Object"
-    .split(" ")
-    .reduce(function(class2type, name) {
-        class2type[ "[object "+name+"]" ] = name.toLowerCase();
-        return class2type;
-    }, {});
-
-function type_of(obj){
-    return obj == null ?
-        String( obj ) :
-        class2type[ toString.call(obj) ] || "object";
-}
-
-function isFunction(obj) { return type_of(obj) === "function"; }
-function isString(obj)   { return type_of(obj) === "string"; }
-function isNumber(obj)   { return type_of(obj) === "number"; }
-
-// A crude way of determining if an object is a window
-function isWindow( obj ) {
-       return obj && typeof obj === "object" && "setInterval" in obj;
-}
-
-function isPlainObject( obj ){
-    // Must be an Object.
-    // Because of IE, we also have to check the presence of the constructor property.
-    // Make sure that DOM nodes and window objects don't pass through, as well
-    if ( !obj || type_of(obj) !== "object" || obj.nodeType || isWindow(obj) )
-        return false;
-    
-    // Not own constructor property must be Object
-    if ( obj.constructor &&
-        !hasOwn.call(obj, "constructor") &&
-        !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") )
-            return false;
-    
-    
-    // Own properties are enumerated firstly, so to speed up,
-    // if last one is own, then all properties are own.
-    
-    var key;
-    for ( key in obj ) {}
-    
-    return key === undefined || hasOwn.call( obj, key );
-}
-
-
index a59db7a..31ac03d 100644 (file)
@@ -4,7 +4,7 @@
 $(function(){
     $('<style />')
         .text([
-            '.portal.layer { position:absolute; z-index:1; overflow:hidden; }',
+            '.portal.layer { position:absolute; z-index:1; overflow:hidden; top:0; left:0; }',
             '.portal.layer canvas { z-index:0; }'
         ].join('\n'))
         .appendTo('head');
@@ -177,9 +177,9 @@ Layer = new Y.Class('Layer', {
             return this;
         
         var _ctx = ctx || this.ctx;
-        this._openPath(_ctx)
-            .drawShape(_ctx)
-            ._closePath(_ctx);
+        this._openPath(_ctx);
+        this.drawShape(_ctx);
+        this._closePath(_ctx);
         
         this.dirty = false;
         this.children.invoke('draw', ctx);
index 0e225b7..364263a 100644 (file)
@@ -88,12 +88,12 @@ Rect = new Y.Class('Rect', Shape, {
     drawShape : function(ctx){
         ctx.rect(0,0, this._width,this._height);
         ctx.fill();
-        return this;
     }
     
 });
 
 Polygon = new Y.Class('Polygon', Shape, {
+    _cssClasses : 'portal layer shape polygon',
     
     /**
      * Expects two arrays of coordinate-halfs, which could be zipped
@@ -117,19 +117,40 @@ Polygon = new Y.Class('Polygon', Shape, {
             ctx.lineTo.apply(ctx, pair);
         });
         ctx.fill();
-        return this;
     }
 });
 
 Triangle = new Y.Class('Triangle', Polygon, {
+    _cssClasses : 'portal layer shape polygon triangle',
+    
     init : function(x1,y1, x2,y2){
         Polygon.init.call(this, [x1,x2], [y1,y2]);
     }
 });
 
 Quad = new Y.Class('Quad', Polygon, {
+    _cssClasses : 'portal layer shape polygon quad',
+    
     init : function(x1,y1, x2,y2, x3,y3){
         Polygon.init.call(this, [x1,x2,x3], [y1,y2,y3]);
     }
 });
 
+Circle = new Y.Class('Circle', Shape, {
+    _cssClasses : 'portal layer shape circle',
+    
+    init : function(radius){
+        Layer.init.call(this);
+        
+        var d = radius * 2;
+        this.width(d).height(d);
+        this.radius = radius;
+    },
+    
+    drawShape : function(ctx){
+        var r = this.radius;
+        ctx.arc(r,r, r, 0, Math.PI*2, false);
+        ctx.fill();
+    }
+    
+});
\ No newline at end of file
index ef44e23..88f66e2 100644 (file)
@@ -4,12 +4,25 @@ Loc = new Y.Class('Loc', {
         this.y = Math.max(y,0);
     },
     
+    moveBy : function(dir, amount){
+        var mod = 1;
+        switch (dir) {
+            case Loc.LEFT: mod = -1;
+            case Loc.RIGHT:
+                return new Loc(this.x+amount*mod, this.y);
+            
+            case Loc.UP: mod = -1;
+            case Loc.DOWN:
+                return new Loc(this.x, this.y+amount*mod);
+        }
+    },
+    
     clone : function(){
         return new Loc(this.x, this.y);
     },
     
     toSquare : function(){
-        return Square.fromLoc(this.x, this.y);
+        return Loc.Square.fromLoc(this.x, this.y);
     },
     
     toString : function(){
@@ -18,6 +31,8 @@ Loc = new Y.Class('Loc', {
     
 });
 Y(Loc).extend({
+    UP    : 'up',    DOWN : 'down',
+    RIGHT : 'right', LEFT : 'left',
     
     fromSquare : function(col, row){
         return new Loc(
@@ -32,7 +47,7 @@ Y(Loc).extend({
 });
 
 
-Rect = new Y.Class('Rect', {
+Loc.Rect = new Y.Class('Rect', {
     init : function(x1,y1, x2,y2){
         if (x1 instanceof Loc && y1 instanceof Loc) {
             var top    = x1,
@@ -62,7 +77,7 @@ Rect = new Y.Class('Rect', {
     },
     
     clone : function(){
-        return new Rect(
+        return new Loc.Rect(
             this.top.clone(),
             this.bottom.clone() );
     },
@@ -79,7 +94,7 @@ Rect = new Y.Class('Rect', {
     
 });
 
-Square = new Y.Class('Square', Rect, {
+Loc.Square = new Y.Class('Square', Rect, {
     init : function(col, row){
         col = this.col = Math.max(col,0);
         row = this.row = Math.max(row,0);
@@ -89,7 +104,7 @@ Square = new Y.Class('Square', Rect, {
         ,   x2 = x1 + REF_SIZE
         ,   y2 = y1 + REF_SIZE;
         
-        Rect.init.call(this, x1,y1, x2,y2);
+        Loc.Rect.init.call(this, x1,y1, x2,y2);
     },
     
     clone : function(){
@@ -105,8 +120,8 @@ Square = new Y.Class('Square', Rect, {
     }
     
 });
-Square.fromLoc = function(x, y){
-    return new Square(
+Loc.Square.fromLoc = function(x, y){
+    return new Loc.Square(
         Math.floor(x / REF_SIZE),
         Math.floor(y / REF_SIZE) );
 };
index 1637321..5a7cfc5 100644 (file)
@@ -199,7 +199,7 @@ QuadTree = new Y.Class('QuadTree', {
     }
 });
 
-this['QuadTree'] = QuadTree;
 QuadTree['Region'] = Region;
+this['QuadTree'] = QuadTree;
 
 })();
diff --git a/src/tanks/game.js b/src/tanks/game.js
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/src/tanks/game/game.js b/src/tanks/game/game.js
new file mode 100644 (file)
index 0000000..291326d
--- /dev/null
@@ -0,0 +1,68 @@
+var undefined
+,   GRID_ELEMENT  = '#viewport'
+
+,   REF_SIZE      = 50
+,   CELL_SIZE     = REF_SIZE
+,   SCALE         = CELL_SIZE / REF_SIZE
+,   GRID_OFFSET   = 0
+
+,   COLUMNS       = 10
+,   ROWS          = 10
+,   CAPACITY      = 32
+
+,   FRAME_RATE    = 30
+,   MS_PER_FRAME  = 1000 / FRAME_RATE
+
+,   NOW           = new Date().getTime() // Current tick's timestamp (ms)
+,   ELAPSED       = MS_PER_FRAME         // Time (ms) since previous tick
+,   FRAMETH       = ELAPSED / 1000       // Ratio of second completed
+,   TICKS         = 0                    // Ticks since start of game
+
+,   PI            = Math.PI
+,   TWO_PI        = PI*2
+,   HALF_PI       = PI/2
+
+;
+
+
+
+Game = new Y.Class('Game', {
+    
+    init : function(viewport){
+        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.viewport = $(viewport || GRID_ELEMENT);
+        
+        this.initMap();
+        
+        this.addEventListener('tick', this.tick);
+    },
+    showOverlay : false,
+    
+    /**
+     * Main Event Loop.
+     */
+    tick : function(evt){
+        var d = evt.data;
+        
+        NOW     = d.now;
+        ELAPSED = d.elapsed;
+        TICKS   = d.ticks;
+        
+        this.root.draw();
+        
+        this.units.invoke('act');
+        // XXX: Collect the dead
+        
+        // this.grid.removeOverlay(this.el);
+        // if (this.showOverlay) this.grid.overlay(this.el);
+    }
+    
+});
+
+
diff --git a/src/tanks/game/map.js b/src/tanks/game/map.js
new file mode 100644 (file)
index 0000000..96bd951
--- /dev/null
@@ -0,0 +1,161 @@
+
+Y(Game.prototype).extend({
+    
+    initMap : function(){
+        var self = this;
+        
+        this.pathmap = new PathMap(0,0, COLUMNS*REF_SIZE, ROWS*REF_SIZE, CAPACITY);
+        
+        var root = this.root = this.grid = 
+            new Grid(COLUMNS,ROWS, CELL_SIZE)
+                .appendTo(this.viewport);
+        this.level =
+            new Layer()
+                .width( root._width )
+                .height( root._height)
+                .appendTo(this.root);
+        
+        // this.root.ctx.scale(1.0, 1.0);
+        
+        this.byId = {};
+        this.units    = new Y.YArray();
+        this.bullets  = new Y.YArray();
+        this.blockers = new Y.YArray();
+        
+        // Agent.addEventListener('create', function(evt){
+        //     self.addAgent(evt.instance);
+        // });
+        // Agent.addEventListener('destroy', function(evt){
+        //     self.killAgent(evt.instance);
+        // });
+    },
+    
+    
+    // *** Path Map Management *** //
+    
+    addBlocker : function(agent){
+        var bb = agent.boundingBox;
+        if (agent.blocking && bb)
+            agent.region = this.grid.set(bb.x1,bb.y1, bb.x2,bb.y2, agent);
+        return agent;
+    },
+    
+    removeBlocker : function(agent){
+        if (agent.region)
+            this.grid.remove(agent.region);
+        return agent;
+    },
+    
+    updateBlocker : function(agent){
+        this.removeBlocker(agent);
+        this.addBlocker(agent);
+    },
+    
+    
+    // *** Agent Management *** //
+    
+    addUnit : function(unit, col,row){
+        unit.game = this;
+        
+        // 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.addBlocker(unit);
+        
+        if ( !this.byId[unit.id] ) {
+            this.byId[unit.id] = unit;
+            this.units.push(unit);
+        }
+        
+        return unit;
+    },
+    
+    addAgent : function(agent){
+        agent.game = this;
+        if (agent.id === undefined) return agent;
+        
+        this.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;
+    },
+    
+    killAgent : function(agent){
+        delete this.byId[agent.id];
+        if (agent instanceof Ability)
+            this.abilities.remove(agent);
+        else
+            this.units.remove(agent);
+        
+        this.removeBlocker(agent);
+        return agent;
+    },
+    
+    moveAgentTo : function(agent, x,y){
+        this.removeBlocker(agent);
+        agent.setLocation(x,y);
+        this.addBlocker(agent);
+        return agent;
+    },
+    
+    getUnitAt : function(x,y){
+        return this.grid.get(x,y);
+    },
+    
+    getUnitsAt : function(x1,y1, x2,y2){
+        return this.grid.get(x1,y1, x2,y2);
+    }
+    
+});
+
+Y(Game.prototype).extend({
+    
+    // Ehh.
+    resize : function(){
+        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);
+    }
+    
+});
+
+
diff --git a/src/tanks/game/player.js b/src/tanks/game/player.js
new file mode 100644 (file)
index 0000000..8ce715d
--- /dev/null
@@ -0,0 +1,99 @@
+Action = {
+    MOVE   : 'move',
+    ATTACK : 'attack',
+    
+    moveDirFromKey : {
+        37: "left", 38: "up", 39: "right", 40: "down",
+        65: "left", 87: "up", 83: "right", 68: "down"
+    }
+};
+
+Player = new Y.Class('Player', {
+    activeKeys : null,
+    shift : false, ctrl : false, meta : false, alt : false,
+    leftMouse : false, middleMouse : false, rightMouse : false,
+    
+    action : null,
+    target : null,
+    
+    
+    init : function(game, tank){
+        Y.bindAll(this);
+        
+        this.activeKeys = new Y.YArray();
+        
+        this.game = game;
+        this.tank = tank;
+        tank.act = this.act;
+        
+        $(window)
+            .bind('keydown',   this.keydown)
+            .bind('keyup',     this.keyup)
+            .bind('mousedown', this.mousedown)
+            .bind('mouseup',   this.mouseup);
+    },
+    
+    queueMove : function(dir){
+        var self = this;
+        return function(evt){
+            self.action = Actions.MOVE;
+            self.target = dir;
+            return false;
+        };
+    },
+    
+    updateMeta : function(evt){
+        this.shift = evt.shiftKey;
+        this.alt   = evt.altKey;
+        this.meta  = evt.metaKey;
+        this.ctrl  = evt.ctrlKey;
+    },
+    
+    keydown : function(evt){
+        this.activeKeys.push(evt.which+'');
+        this.updateMeta(evt);
+    },
+    
+    keyup : function(evt){
+        this.activeKeys.remove(evt.which+'');
+        this.updateMeta(evt);
+    },
+    
+    mousedown : function(evt){
+        switch (evt.which) {
+            case 1: evt.leftMouse   = true; break;
+            case 2: evt.rightMouse  = true; break;
+            case 3: evt.middleMouse = true; break;
+        }
+        this.updateMeta(evt);
+    },
+    
+    mouseup : function(evt){
+        switch (evt.which) {
+            case 1: evt.leftMouse   = false; break;
+            case 2: evt.rightMouse  = false; break;
+            case 3: evt.middleMouse = false; break;
+        }
+        this.updateMeta(evt);
+    },
+    
+    act : function(){
+        if (this.tank.dead) return this;
+        
+        var active = this.activeKeys;
+        for (var key in Action.moveDirFromKey)
+            if ( active.has(key) ) {
+                this.move( Action.moveDirFromKey[key] );
+                return this;
+            }
+        
+        return this;
+    },
+    
+    move : function(dir){
+        var tank  = this.tank
+        ,   toLoc = tank.loc.moveBy(dir, (tank.stats.move * REF_SIZE * FRAMETH));
+        this.game.moveAgentTo(tank, toLoc.x, toLoc.y);
+    }
+    
+});
\ No newline at end of file
index c011cd5..8578913 100644 (file)
@@ -1,31 +1,33 @@
 jQuery(function($){
 
 v = $('#viewport');
+LBT = new Game();
 
-L = new Layer(v)
-    .width(v.width())
-    .height(v.height())
-    .append(
-        new Rect(50,50)
-            .position(50,50)
-            .attr({
-                fillStyle : '#E73075'
-            })
-    ).append(
-        R = new Rect(250,250)
-            .position(150,50)
-            .attr({
-                fillStyle : '#E2EEF5'
-            })
-    ).append(
-        T = new Triangle(25,-25, 50,0)
-            .position(150,300)
-            .css('z-index', 2)
-            .attr({
-                fillStyle : '#E73075'
-            })
-    ).appendTo(v)
-    .draw()
-;
+T = new Tank(0);
+LBT.addUnit(T, 1,1);
+P = new Player(LBT, T);
+
+
+// L = new Layer(v)
+//     .width(v.width())
+//     .height(v.height())
+//     .append(
+//         new Rect(250,250)
+//             .position(150,50)
+//             .attr({
+//                 fillStyle : '#E2EEF5'
+//             }),
+//         new Triangle(25,-25, 50,0)
+//             .position(150,300)
+//             .attr('fillStyle', '#E73075'),
+//         new Quad(10,50, 50,50, 60,0)
+//             .position(50,50)
+//             .attr('fillStyle', '#E73075'),
+//         new Circle(30)
+//             .position(30,330)
+//             .attr('fillStyle', '#8DCDA1')
+//     ).appendTo(v)
+//     .draw()
+// ;
 
 });
\ No newline at end of file
diff --git a/src/tanks/map.js b/src/tanks/map.js
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/src/tanks/tank.js b/src/tanks/tank.js
deleted file mode 100644 (file)
index 139597f..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
index e69de29..4d71fde 100644 (file)
@@ -0,0 +1,86 @@
+function toggleGame(evt){
+    if (LBT.loop.running)
+        LBT.stop();
+    else
+        LBT.start();
+    
+    updateInfo();
+    return false;
+}
+
+// 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 );
+    
+    return false;
+}
+
+function fixStartText(){
+    var txt  = (LBT.loop.running ? 'pause' : 'start');
+    $('.start_btn').text(txt).attr('title', txt);
+    return false;
+}
+function fixOverlayText(){
+    var txt = (LBT.showOverlay ? 'hide' : 'show') + ' bounds';
+    $('.overlay_btn').text(txt).attr('title', txt);
+    return false;
+}
+
+
+
+jQuery(function($){
+    // Tick once to draw grid, initial units
+    LBT.start();
+    LBT.stop();
+    
+    // Add Buttons
+    // logger.menu.prepend('<a class="start_btn" />', ' | ', '<a class="overlay_btn"></a>', ' || ');
+    
+    setInterval(updateInfo, 1000);
+    updateInfo();
+    
+    // Update Text on Click
+    LBT.addEventListener('start', fixStartText);
+    LBT.addEventListener('stop',  fixStartText);
+    
+    // Start button (click or return key)
+    $('.start_btn').bind('click', toggleGame);
+    $(document).bind('keydown', 'return', toggleGame);
+    
+    $(document).bind('keydown', 'ctrl+o', function(evt){ LBT.showOverlay = !(LBT.showOverlay); });
+    
+    // Show Overlays
+    $('.overlay_btn').bind('click', function(evt){
+        LBT.showOverlay = !(LBT.showOverlay);
+        fixOverlayText();
+        return false;
+    });
+    
+    // Fix Starting text
+    fixStartText();
+    fixOverlayText();
+    
+    // Fix grid-size on resize
+    $(window).bind('resize', function(evt){
+        // LBT.resize(evt);
+        
+        if (!LBT.loop.running) {
+            LBT.start();
+            LBT.stop();
+        }
+    });
+    
+    // 
+});
\ No newline at end of file
diff --git a/src/tanks/unit/tank.js b/src/tanks/unit/tank.js
new file mode 100644 (file)
index 0000000..2790285
--- /dev/null
@@ -0,0 +1,198 @@
+(function(){
+var uid = 0;
+
+
+Tank = new Y.Class('Tank', {
+    
+    init : function(align){
+        this.id    = uid++;
+        this.align = align || 0;
+        
+        this.bullets = new Y.YArray();
+        
+        this.fillStats();
+        this.createCooldowns();
+    },
+    
+    // Attributes
+    stats: {
+        move      : 0.5, // move speed (squares/sec)
+        rotate    : HALF_PI, // rotation speed (radians/sec)
+        
+        power     : 1,   // attack power
+        speed     : 1.0, // attacks/sec
+        shots     : 5    // max projectiles in the air at once
+    },
+    
+    // *** Bookkeeping *** //
+    
+    id      : 0,
+    bullets : null,
+    align   : 0,
+    dead    : false,
+    
+    destroy : function(){
+        if (this.dead) return;
+        
+        this.dead = true;
+        this.fire('destroy', this);
+    },
+    
+    
+    loc : null,
+    boundingBox : null,
+    vec : 0,
+    
+    // Bounding box size
+    width  : REF_SIZE*0.7,
+    height : REF_SIZE*0.6,
+    
+    
+    setLocation : function(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);
+        this.boundingBox = new Loc.Rect(x,y, x+this.width,y+this.height);
+        return loc;
+    },
+    
+    // *** Gameplay Methods *** //
+    
+    fillStats : function(){
+        this.stats = Tank.fillStats(this.stats);
+    },
+    
+    createCooldowns : function(){
+        this.cooldowns = Y({
+            attack: new Cooldown(1000 * this.stats.speed)
+        });
+    },
+    
+    /**
+     * Determines what the creep should do -- move, attack, etc.
+     */
+    act : function(){
+        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();
+        
+        if (bb.x1 <= MIN_X_DIST || bb.x2 >= MAX_X_DIST)
+            this.destroy();
+        
+        return this;
+    },
+    
+    filterTarget : function(unit){ return unit.align !== this.align; },
+    
+    move : function(dir){
+        var toLoc = this.loc.moveBy(dir, (this.stats.move * REF_SIZE * FRAMETH));
+        this.game.moveAgentTo(this, toLoc.x, toLoc.y);
+        return this;
+    },
+    
+    attack : function(unit){
+        var atk_cool = this.cooldowns.attr('attack');
+        if ( atk_cool.activate(NOW) )
+            this.doAttack(unit);
+        return this;
+    },
+    
+    doAttack : function(target){
+        this.fireProjectile(target);
+    },
+    
+    fireProjectile : function(target){
+        var AbilityType = this.projectile
+        ,   p = new AbilityType(this, target);
+        this.bullets.push(p);
+        this.game.addAgent(p);
+        return p;
+    },
+    
+    
+    
+    /// Rendering Methods ///
+    
+    /**
+     * Sets up unit appearance for minimal updates. Called once at start,
+     * or when the world needs to be redrawn from scratch.
+     */
+    render : function( parent ){
+        if (this.shape) this.shape.remove();
+        
+        var w = this.width
+        ,   h = this.height
+        ,   tw = w / 4
+        ,   th = h * 0.9
+        ,   sw = w * 0.05
+        ;
+        
+        this.shape = 
+            new Rect(w,h)
+                .position(this.loc.x, this.loc.y)
+                .attr('fillStyle', '#E73075')
+                .append(
+                    new Triangle(0,th, tw,th/2)
+                        .position(w-tw-sw, (h-th)/2)
+                        .attr('fillStyle', '#980011')
+                )
+                .appendTo( parent );
+        
+        return this;
+    },
+    
+    draw : function(){
+        if (this.dead) 
+            this.shape.hide();
+        else
+            this.shape.draw();
+    },
+    
+    toString : function(){
+        return this.className+'(id='+this.id+', loc='+this.loc+')';
+    }
+});
+
+
+
+Y(Tank).extend({
+    
+    fillStats : function(stats){
+        var st = Y(stats)
+        ,   stats = st.clone().end()
+        ;
+        
+        st.forEach(function(v, k){
+            var k = Y(k)
+            ,   k_ = k.rtrim('_max')
+            ;
+            
+            if ( k.endsWith('_max') ) {
+                if ( stats[k_] === undefined )
+                    stats[k_] = v;
+                
+            } else if ( stats[k+'_max'] === undefined )
+                stats[k+'_max'] = v;
+            
+        });
+        
+        return stats;
+    }
+});
+
+})();
diff --git a/src/tanks/util/grid.js b/src/tanks/util/grid.js
new file mode 100644 (file)
index 0000000..bc71d9b
--- /dev/null
@@ -0,0 +1,34 @@
+Grid = new Y.Class('Grid', Rect, {
+    _cssClasses : 'portal layer shape rect grid',
+    
+    init : function(cols,rows, size){
+        this.cols = cols;
+        this.rows = rows;
+        this.size = size;
+        Rect.init.call(this, cols*size, rows*size);
+    },
+    
+    drawShape : function(ctx){
+        var size = this.size
+        ,   rows = this.rows
+        ,   cols = this.cols
+        ,   w = this._width
+        ,   h = this._height;
+        
+        ctx.lineWidth   = 0.5;
+        ctx.strokeStyle = '#6E6E6E';
+        
+        for (var row=0, y=0; row<=rows; y = (++row) * size){
+            ctx.moveTo(0,y);
+            ctx.lineTo(w,y);
+        }
+        
+        for (var col=0, x=0; col<=cols; x = (++col) * size){
+            ctx.moveTo(x,0);
+            ctx.lineTo(x,h);
+        }
+        
+        ctx.stroke();
+    }
+    
+});
\ No newline at end of file
similarity index 96%
rename from src/simoon/grid/grid.js
rename to src/tanks/util/pathmap.js
index 047f8f4..a8d1707 100644 (file)
@@ -1,4 +1,4 @@
-Grid = new Y.Class('Grid', QuadTree, {
+PathMap = new Y.Class('PathMap', QuadTree, {
     
     overlay : function(gridEl){
         var w = this.width  *SCALE