Rock on. Mixins work great.
authordsc <david.schoonover@gmail.com>
Sun, 2 Jan 2011 09:28:34 +0000 (01:28 -0800)
committerdsc <david.schoonover@gmail.com>
Sun, 2 Jan 2011 09:28:34 +0000 (01:28 -0800)
14 files changed:
src/Y/modules/y.scaffold.cjs
src/Y/type.cjs
src/Y/types/function.cjs
src/Y/types/string.cjs
src/evt.cjs
src/tanks/effects/buff.cjs
src/tanks/index.js
src/tanks/map/level.cjs
src/tanks/mixins/configurable.cjs
src/tanks/mixins/index.cjs [new file with mode: 0644]
src/tanks/mixins/informative.cjs
src/tanks/mixins/meronomic.cjs
src/tanks/mixins/speciated.cjs
www/deps.html

index 09973d7..3f85341 100644 (file)
@@ -5,7 +5,7 @@ var Y = require('Y').Y
 
 
 ,   upperPat = /^[A-Z]+$/
-,   symbolPat = /^[^a-zA-Z]+$/
+,   symbolPat = /^[^a-zA-Z0-9]+$/
 ,
 camelToSpaces =
 exports['camelToSpaces'] =
index bed850b..a075a8f 100644 (file)
@@ -42,7 +42,7 @@ function typeName(o){
     if (o === undefined || o === null)
         return String(o);
     var T = type(o);
-    return  T.className || (T !== Function ? T.name : basicTypeName(o));
+    return  T.className || (T !== Function ? T.name || "Function" : basicTypeName(o));
 }
 
 var arrayLike = isArray.types = [];
index 63ff66c..22143a7 100644 (file)
@@ -47,6 +47,27 @@ function unwrap(fn){
     return ( fn && isFunction(fn) ) ? unwrap(fn[WRAPS]) || fn : fn;
 }
 
+/**
+ * @return {Function} A function which flips the first and second arguments
+ *  before calling the original.
+ */
+function flip(fn){
+    fn = _Function.toFunction(fn);
+    
+    var f = fn.__flipped__;
+    if (f) return f;
+    
+    f = fn.__flipped__ = 
+        function flipped(){
+            var args = arguments
+            ,   hd = args[0];
+            args[0] = args[1];
+            args[1] = hd;
+            return fn.apply(this, args);
+        };
+    return wraps(f, fn);
+}
+
 function curry(fn){
     if (fn.__curried__)
         return fn.apply(this, slice.call(arguments,1));
@@ -72,7 +93,7 @@ function curry(fn){
 
 
 function methodize(fn) {
-    fn = fn.toFunction();
+    fn = _Function.toFunction(fn);
     var g = fn.__genericized__
     ,   m = fn.__methodized__  ;
     if (m)             return m;
@@ -86,7 +107,7 @@ function methodize(fn) {
 }
 
 function genericize(fn) {
-    fn = fn.toFunction();
+    fn = _Function.toFunction(fn);
     var g = fn.__genericized__
     ,   m = fn.__methodized__  ;
     if (g)             return g;
@@ -245,11 +266,7 @@ function limit(fn, n){
 }
 
 
-/**
- * Filter the arguments passed to the wrapper function
- */
-// YF.prototype.mask = mask;
-// function mask(){
+// function guard(test, otherwise){
 //     
 // }
 
@@ -270,7 +287,7 @@ function getName( fn ){
 
 
 
-[   wraps, unwrap, getName,
+[   wraps, unwrap, getName, flip,
     curry, compose, chain, bind, partial,
     methodize, genericize, memoize, aritize, limit
 ].map(function(fn){
index 5a82ff5..4680f40 100644 (file)
@@ -4,6 +4,9 @@ var YCollection = require('Y/types/collection').YCollection
 ,   core  = require('Y/core')
 ,   del   = require('Y/delegate')
 ,   slice = core.slice
+
+,   upperPat = /^[A-Z]+$/
+,   symbolPat = /^[^a-zA-Z0-9_\$]+$/
 ;
 
 exports['YString'] =
@@ -103,6 +106,56 @@ YCollection.subclass('YString', function(YString){
             return s.lastIndexOf(val) === (s.length - val.length);
         },
         
+        'capitalize' : function capitalize(){
+            var s = this._o+'';
+            return s.charAt(0).toUpperCase()+s.slice(1);
+        },
+        
+        /**
+         * Converts a camelCase string into a snake_case string.
+         * @param {Boolean} nameSafe If true, keeps only characters valid in function names.
+         * @return {String} The snake_case string.
+         * 
+         *      Y('petGrumpyCamels').camelToSnake() === 'pet_grumpy_camels'
+         */
+        'camelToSnake' : function camelToSnake(nameUnsafe){
+            return this.reduce(
+                    function(out, ch, i){
+                        if ( !nameUnsafe && symbolPat.test(ch) )
+                            return out;
+                        if ( upperPat.test(ch) )
+                            out += '_';
+                        return out + ch.toLowerCase();
+                    }, '');
+        },
+        
+        /**
+         * Converts a snake_case string into a camelCase string. (All non-alphanumeric
+         * characters are treated as separators for the purposes of camelizing.)
+         * @param {Boolean} [nameUnsafe=false] Does not strip characters invalid in function names.
+         *  (Underscores are always removed.)
+         * @return {String} The camelCase string.
+         * 
+         *      Y('feed_happy_snakes').snakeToCamel() === 'feedHappySnakes'
+         */
+        'snakeToCamel' : function snakeToCamel(nameUnsafe){
+            return this.reduce(
+                    function(state, ch, i){
+                        var isSymbol = symbolPat.test(ch);
+                        if ( ch === '_' )
+                            isSymbol = true;
+                        else if (!isSymbol || nameUnsafe)
+                            state.out += (state.prev ? ch.toUpperCase() : ch);
+                        state.prev = isSymbol;
+                        return state;
+                    },
+                    {
+                        out  : '',
+                        prev : false
+                    
+                    }).out;
+        },
+        
         'compare' : function compare(n){
             var m = this._o;
             return (m > n ?  1 :
index 100a51a..f5e84ba 100644 (file)
@@ -40,9 +40,14 @@ var Y       = require('Y').Y
 
 ,   objToString   = _Object[P].toString
 ,   classToString = function toString(){ return this.className+"()"; }
-,   classMagic    = [ '__static__', '__mixins__', '__emitter__', '__bases__', '__initialise__' ]
-,   mixinSkip     = [ '__mixin_skip__', 'onMixin', 'onInit', 'init' ].concat(classMagic)
-,   classStatics  = [ 'instantiate', 'fabricate', 'subclass' ]
+
+,   classStatics = [ 'instantiate', 'fabricate', 'subclass' ]
+,   classMagic = [
+            '__bases__', '__initialise__', '__class__', 'constructor', 'className',
+            '__emitter__', '__static__', '__mixins__'
+        ] //.concat(classStatics)
+,   mixinSkip = [ '__mixin_skip__', '__mixin_skip', 'onMixin', 'onInit', 'init'
+        ].concat(classMagic)
 ,
 
 
@@ -189,10 +194,10 @@ function Class(className, Parent, members){
     eval(constructor);
     
     // Copy Class statics
-    for (var i=0, L=classStatics.length; i<L; ++i) {
-        var k = classStatics[i];
-        NewClass[k] = ClassFactory[k];
-    }
+    // for (var i=0, L=classStatics.length; i<L; ++i) {
+    //     var k = classStatics[i];
+    //     NewClass[k] = ClassFactory[k];
+    // }
     
     // Copy parent methods, then add new instance methods
     for (var k in parentMembers)
@@ -237,20 +242,29 @@ function Class(className, Parent, members){
         // prototype.constructor = prototype.__class__ = NewClass;
     }
     
-    var mixins  = members.__mixins__
-    ,   statics = members.__static__
+    var mixins   = NewClass.__mixins__ = Y([]).concat(members.__mixins__||[], SuperClass.__mixins__||[]).unique()
+    ,   statics  = NewClass.__static__ = {}
+    ,   pstatics = SuperClass.__static__
+    ,   mstatics = members.__static__
     ;
     
-    if (mixins && hasOwn.call(members,'__mixins__'))
+    for (var k in pstatics)
+        if ( hasOwn.call(pstatics,k) && classMagic.indexOf(k) === -1 ) {
+            var desc = getDesc(pstatics,k);
+            setDesc(NewClass, k, desc);
+            setDesc(statics, k, desc);
+        }
+    
+    if (mixins.length)
         mixin(NewClass, mixins);
     
-    if (statics)
-        for (var k in statics) {
-            if ( hasOwn.call(statics,k) && classMagic.indexOf(k) === -1 )
-                setDesc(NewClass, k, getDesc(statics,k));
+    for (var k in mstatics)
+        if ( hasOwn.call(mstatics,k) && classMagic.indexOf(k) === -1 ) {
+            var desc = getDesc(mstatics,k);
+            setDesc(NewClass, k, desc);
+            setDesc(statics, k, desc);
         }
     
-    
     // Notify parent of the subclass
     ParentEmitter.fire('subclass',
         NewClass, {
@@ -286,12 +300,14 @@ function instantiate(cls){
     return instance;
 }
 
-Class.instantiate = Y(instantiate).methodize();
-Class.fabricate   = Y.Class.fabricate;
+var CST = Class.__static__ = {};
+CST.instantiate = Class.instantiate = Y(instantiate).methodize();
+CST.fabricate   = Class.fabricate   = Y.Class.fabricate;
 
 /**
  * Class/Instance method of Classes, not to be confused with Evt.subclass, which is a static method.
  */
+CST.subclass =
 Class.subclass =
 Class.fn.subclass =
     Y(function subclass(className, members){
@@ -329,19 +345,17 @@ new Class('Mixin', Class, {
         }
     }
     
-})
-,
+});
+
 
 /**
  * Mixes a Mixin into another Class.
  */
-mixin =
-exports['mixin'] =
-function mixin(cls, mxn){
+function mixin(cls, _mxn){
     var proto = cls.fn
-    ,   mxns = (Y.isArray(mxn) ? mxn : Y(arguments, 1))
+    ,   mxns = (Y.isArray(_mxn) ? _mxn.slice(0) : Y(arguments, 1))
     ;
-    mxns.forEach(function(mxn){
+    mxns.reverse().forEach(function(mxn){
         mxn = (typeof mxn === "string") ? lookupClass(mxn) : mxn;
         
         if ( !mxn )
@@ -349,30 +363,37 @@ function mixin(cls, mxn){
         
         var mproto = (typeof mxn === "function") ? mxn.fn : mxn
         ,   bases   = cls.__bases__
-        ,   statics = mproto.__static__
+        ,   cstatic = cls.__static__
+        ,   statics = mxn.__static__
         ,   onInit  = mproto.onInit
         ;
         
         core.extend(proto, core.filter(mproto, mproto.__mixin_skip, mproto));
         
-        if (bases)
-            bases.push(mxn);
+        if (bases && bases.indexOf(mxn) === -1)
+            bases.unshift(mxn);
         
         // Add mixin statics to class constructor
-        if (statics)
-            core.extend(cls, statics);
+        if (statics){
+            for (var k in statics)
+                if ( hasOwn.call(statics,k) && classMagic.indexOf(k) === -1 ) {
+                    var desc = getDesc(statics,k);
+                    setDesc(cls,     k, desc);
+                    setDesc(cstatic, k, desc);
+                }
+        }
         
         // Register onInit to fire whenever a new instance is initialized
         if ( hasOwn.call(mproto,'onInit') && isFunction(onInit) )
             cls.addEventListener('init', onInit);
         
         // Fire the mixin event on this Mixin
-        if (mxn instanceof Mixin)
-            mxn.fire('mixin', cls, { 'mixin':mxn, 'into':cls });
+        if ( isFunction(mxn.fire) )
+            mxn.fire('mixin', cls, { 'mixin':mxn, 'cls':cls });
     });
     return cls;
 }
-;
+
 
 Mixin.addEventListener('subclass',
     function onMixinSubclass(evt){
@@ -382,8 +403,10 @@ Mixin.addEventListener('subclass',
         ,   onMixin = members.onMixin
         ;
         
-        if ( hasOwn.call(members,'onMixin') && isFunction(onMixin) )
+        if ( isFunction(onMixin) )
             mxn.addEventListener('mixin', onMixin);
+        
+        console.log('Mixin.subclass()', mxn, '<', d.parent, 'onMixin:', onMixin);
     });
 
 
@@ -393,3 +416,4 @@ exports['subclass']    = Class;
 exports['instantiate'] = instantiate;
 exports['fabricate']   = Y.fabricate;
 exports['lookupClass'] = lookupClass;
+exports['mixin']       = mixin;
index c7253dd..8baf546 100644 (file)
@@ -1,5 +1,9 @@
 var Y = require('Y').Y
+,   evt = require('evt')
 ,   mul = Y.op.curried.mul
+
+,   Speciated = require('tanks/mixins/speciated').Speciated
+,   Meronomic = require('tanks/mixins/meronomic').Meronomic
 ,
 
 
@@ -8,8 +12,8 @@ var Y = require('Y').Y
 //  - die.{expire,dismiss,dispel}
 Buff =
 exports['Buff'] =
-Y.subclass('Buff', {
-    // __mixins__: [ Informative, Speciated, Meronomic, Usable ], // Configurable?
+evt.subclass('Buff', {
+    __mixins__: [ Speciated, Meronomic ], // Configurable, Usable
     
     
     /// Configurable ///
@@ -19,7 +23,11 @@ Y.subclass('Buff', {
         stacked : 1,
         threat  : 1
     },
-    stat_mods : {}, // {stat : Number|Function} Modifications to stats // TODO: functions
+    // {stat : Number|Function} Modifications to stats
+    // TODO: functions
+    stat_mods : {
+        move : 2
+    },
     // effects   : [], // {Function...} Effects to trigger on affect // XXX: not sure why i need this...
     // triggers  : {}, // {event : Effect} // maybe
     
index f2abafe..b5e13a5 100644 (file)
@@ -11,6 +11,7 @@ require('tanks/globals');
 tanks = {
     'config' : require('tanks/config').config,
     'ui'     : require('tanks/ui'),
+    'mixins' : require('tanks/mixins'),
     'Game'   : require('tanks/game').Game,
     
     'game' : null,
index d45bde0..7524739 100644 (file)
@@ -51,30 +51,6 @@ Rect.subclass('Level', {
         game.addThing(new Tank(2), 8,1);
         
         I = game.addThing(new Item(), 8,8);
-        
-        // DATA = $('<pre id="data" style="position:absolute;top:0;left:0;width:200px;"></pre>').appendTo('body');
-        // E = game.addThing(new Thing(2), 0,0);
-        // var i = 0;
-        // function testBulletSpeed(){
-        //     B = P.shoot(0,475);
-        //     var start = new Date().getTime()
-        //     ,   startClock = NOW
-        //     ,   startX = B.loc.x;
-        //     B.bounces = 1;
-        //     console.log(i+' B.movePerMs='+B.movePerMs+', move='+B.stats.move);
-        //     
-        //     B.addEventListener('destroy', function(evt){
-        //         var elapsed = (new Date().getTime() - start)/1000
-        //         ,   clock = (NOW - startClock)/1000
-        //         ,   distance = startX - B.loc.x ;
-        //         DATA.text(DATA.text()+elapsed.toFixed(3)+'\t'+clock.toFixed(3)+'\t'+distance.toFixed(3)+'\t'+(distance/elapsed).toFixed(3)+'\n');
-        //         if (++i < 20) testBulletSpeed();
-        //     });
-        // }
-        // game.addEventListener('start', function(evt){
-        //     DATA.text('elapsed\tclock\tpx\tpx/s\n');
-        //     testBulletSpeed();
-        // });
     },
     
     addWall : function addWall(x,y, w,h, isBoundary){
index e69de29..2d0f617 100644 (file)
@@ -0,0 +1,13 @@
+var Y = require('Y').Y
+,   Mixin = require('evt').Mixin
+,
+
+Configurable =
+exports['Configurable'] =
+Mixin.subclass('Configurable', {
+    
+    onInit : function initConfigurable(evt){
+        
+    }
+    
+});
diff --git a/src/tanks/mixins/index.cjs b/src/tanks/mixins/index.cjs
new file mode 100644 (file)
index 0000000..da70058
--- /dev/null
@@ -0,0 +1,4 @@
+require('Y').Y.extend(exports, {
+    'Speciated' : require('tanks/mixins/speciated').Speciated,
+    'Meronomic' : require('tanks/mixins/meronomic').Meronomic
+});
index e69de29..d3e2268 100644 (file)
@@ -0,0 +1,19 @@
+var Y = require('Y').Y
+,   Mixin = require('evt').Mixin
+,
+
+Informative =
+exports['Informative'] =
+Mixin.subclass('Informative', {
+    
+    
+    onInit : function initInformative(evt){
+        var instance = evt.data.instance;
+        
+    },
+    
+    toString : function(){
+        return this.className+'()';
+    }
+    
+});
index e69de29..0f21048 100644 (file)
@@ -0,0 +1,13 @@
+var Y = require('Y').Y
+,   Mixin = require('evt').Mixin
+,
+
+Meronomic =
+exports['Meronomic'] =
+Mixin.subclass('Meronomic', {
+    
+    onInit : function initMeronomic(evt){
+        
+    }
+    
+});
index e69de29..bb183cd 100644 (file)
@@ -0,0 +1,64 @@
+var Y = require('Y').Y
+,   Mixin = require('evt').Mixin
+,
+
+Speciated =
+exports['Speciated'] =
+Mixin.subclass('Speciated', {
+    
+    lol : 1,
+    
+    __static__ : {
+        __known__ : null,
+        
+        speciate : function speciate(id, props){
+            props = props || {};
+            props.__species__ = id;
+            delete props.id;
+            
+            var cls = this
+            ,   speciesName = Y(Y(id+'').capitalize()).snakeToCamel()
+            ,   Species = cls.__known__[id] = cls.subclass(speciesName, props)
+            ;
+            Species.__species_props__ = props;
+            return Species;
+        },
+        
+        lookupSpecies : function lookupSpecies(id){
+            var cls = this;
+            return cls.__known__[id];
+        },
+        
+        createSpeciesInstance : function createSpeciesInstance(id){
+            var args = Y(arguments,1)
+            ,   cls = this
+            ,   Species = cls.__known__[id];
+            return Species.instantiate.apply(Species, args);
+        }
+        
+    },
+    
+    onMixin : function mixSpeciated(evt){
+        var cls = evt.data.cls;
+        cls.__known__ = {};
+        console.log('mixSpeciated()', 'cls:', cls, 'mxn:', evt.data.mixin);
+    },
+    
+    // onInit : function initSpeciated(evt){
+    //     var d = evt.data
+    //     ,   instance = d.instance
+    //     ,   Species = d.cls
+    //     ,   props = Species.__species_props__
+    //     ;
+    //     // for (var k in props) {
+    //     //     var v = props[k];
+    //     //     if ( Y.isArray(v) || typeof v === "object" )
+    //     //         instance[k] = Y(v).clone();
+    //     // }
+    // },
+    
+    toString : function(){
+        return this.className+'(name='+this.name+', id='+this.id+', tags=['+this.tags+'])';
+    }
+    
+});
index a8d24b2..6ebf6f0 100644 (file)
 <script src="build/Y/modules/y.cookies.js" type="text/javascript"></script>
 <script src="build/tanks/map/map.js" type="text/javascript"></script>
 <script src="build/tanks/effects/stat.js" type="text/javascript"></script>
+<script src="build/tanks/mixins/speciated.js" type="text/javascript"></script>
+<script src="build/tanks/mixins/meronomic.js" type="text/javascript"></script>
 <script src="build/tanks/map/traversal.js" type="text/javascript"></script>
+<script src="build/tanks/mixins.js" type="text/javascript"></script>
 <script src="build/tanks/config.js" type="text/javascript"></script>
 <script src="build/tanks/map/pathmap.js" type="text/javascript"></script>
 <script src="build/tanks/ui/configui.js" type="text/javascript"></script>