var Y = require('Y').Y
-, Emitter = require('Y/modules/y.event').Emitter
+, getNested = require('Y/types/object').getNested
+, Emitter = require('Y/modules/y.event').Emitter
,
Config =
-exports.Config =
-Y.YObject.subclass('Config', function(){
+exports['Config'] =
+Y.YObject.subclass('Config', function(Config){
Y.core.extend(this, {
init : function initConfig(defaults){
- this._defaults = defaults;
- this._o = Y({}, defaults);
+ this._defaults = defaults || {};
+ this._o = Y({}, this._defaults);
this.__emitter__ = new Emitter(this);
},
+ clone : function clone(){
+ var c = new Config();
+ c._defaults = this._defaults;
+ c._o = c._o.clone();
+ return c;
+ },
+
set : function set(key, value, def){
if ( key !== undefined ){
- var meta = this.ensure(key).getNested(key, def, true)
+ var meta = this.ensure(key).getNestedMeta(key, def)
, old = meta.value ;
def = (def === undefined ? old : def);
value = (value === undefined ? def : value);
remove : function remove(key){
if ( key !== undefined ){
var sentinel = {}
- , meta = this.getNested(key, sentinel, true)
+ , meta = this.getNestedMeta(key, sentinel)
, old = (meta.value === sentinel ? undefined : old);
if ( meta.obj ) {
context = context || this;
var ret = Y.reduce(this._o, this._reducer, {
+ 'fn' : fn,
'acc' : acc,
'path' : new Y.YArray(),
'cxt' : context
// Normal value -- invoke iterator
else
- state.acc = fn.call(state.cxt, state.acc, v, chain, this);
+ state.acc = state.fn.call(state.cxt, state.acc, v, chain, this);
state.path.pop();
return state;
+ },
+
+ /**
+ * Iterates over both items and groups of the config object.
+ */
+ parts : function parts(groupFn, itemFn, acc, context){
+ context = context || this;
+
+ var path = new Y.YArray();
+ return Y.reduce(this._o,
+ function _parts(acc, v, k, o){
+ var chain = path.push(k).join('.');
+
+ // Nested object -- recurse
+ if ( Y.isPlainObject(v) ){
+ acc = groupFn.call(context, acc, v, chain, this);
+ acc = Y.reduce(v, _parts, acc, this);
+
+ // Normal value -- invoke iterator
+ } else
+ acc = itemFn.call(context, acc, v, chain, this);
+
+ path.pop();
+ return acc;
+ },
+ acc, this);
+ },
+
+ getDefault : function getDefault(k, def){
+ return getNested(this._defaults, k, def);
}
});
+ this['get'] = this.getNested;
+
});
+
+Y['config'] = exports;
--- /dev/null
+//#ensure "jquery"
+var Y = require('Y').Y
+, Emitter = require('Y/modules/y.event').Emitter
+, op = Y.op
+
+
+, upperPat = /^[A-Z]+$/
+, symbolPat = /^[^a-zA-Z]+$/
+,
+camelToSpaces =
+exports['camelToSpaces'] =
+function camelToSpaces(s){
+ return Y(s).reduce(
+ function(acc, ch, i){
+ return acc + (
+ symbolPat.test(ch) ? '' :
+ (upperPat.test(ch) ? ' '+ch : ch) );
+ }, '');
+}
+,
+
+type2parser = {
+ 'Boolean' : op.parseBool,
+ 'Number' : parseFloat,
+ 'String' : String
+}
+,
+
+type2el = {
+ 'Boolean' : 'checkbox',
+ 'Number' : 'text',
+ 'String' : 'text'
+ // 'Array' : 'select',
+ // 'Date' : 'datepicker',
+ // 'RegExp' : 'text'
+}
+,
+
+
+
+Field =
+exports['Field'] =
+Y.subclass('Field', {
+
+ init : function initField(chain, def, val, options){
+ options = options || {};
+ this.__emitter__ = new Emitter(this);
+
+ this.id = chain;
+ this.def = def;
+ this.val = this.old = val === undefined ? def : val;
+ this.key = chain.split('.').pop();
+ this.label = camelToSpaces(this.key);
+
+ var T = Y(Y.type(val)).getName();
+ this.cast = options.cast || type2parser[T];
+ this.type = options.type || type2el[T];
+
+ if (!this.cast)
+ throw new Error('No parser defined for type "'+T+'"');
+ if (!this.type)
+ throw new Error('No field element defined for type "'+T+'"');
+
+ this.build()
+ .update(this.val);
+
+ this.elField.bind('change', this.onChange.bind(this));
+ },
+
+ build : function build(){
+ var el =
+ this.el =
+ jQuery('<div/>')
+ .addClass('field');
+ this.elLabel =
+ jQuery('<label/>')
+ .attr('for', this.id)
+ .text(this.label)
+ .appendTo(el);
+ this.elField =
+ jQuery('<input/>')
+ .attr({
+ 'id' : this.id,
+ 'type' : this.type,
+ 'name' : this.key
+ })
+ .val(this.val)
+ .appendTo(el);
+ return this;
+ },
+
+ update : function update(val){
+ var el = this.elField;
+ if (val !== this.val) {
+ this.old = this.val;
+ this.val = val;
+ this.fire('change', this, {
+ 'key' : this.id,
+ 'oldval' : this.old,
+ 'newval' : this.val,
+ 'el' : this.elField
+ });
+ el.val(val);
+ }
+ if (this.type === 'checkbox')
+ el.attr('checked', !!this.val);
+ return this;
+ },
+
+ onChange : function onChange(evt){
+ this.update( this.cast(this.elField.val()) );
+ }
+
+})
+,
+
+
+create =
+exports['create'] =
+function create(config, el){
+ config.parts(
+ function createGroup(oldGroup, value, chain){
+ return jQuery('<fieldset/>')
+ .attr('id', chain)
+ .addClass('group')
+ .append( jQuery('<legend/>').text(chain.split('.').pop()) )
+ .appendTo(el);
+ },
+ function createField(group, value, chain){
+ var def = config.getDefault(chain)
+ , field = new Field(chain, def, value);
+
+ group.append(field.el);
+ config.addEventListener('set:'+chain, function onConfigSet(evt){
+ field.update(evt.data.newval);
+ });
+ field.addEventListener('change', function onFieldChange(evt){
+ config.set(evt.data.key, evt.data.newval);
+ });
+
+ return group;
+ },
+ el);
+ return el;
+}
+;
+
+Y.YString.fn['camelToSpaces'] = Y(camelToSpaces).methodize()
+
+
+Y['scaffold'] = exports;
};
},
- end : function end(o){ return ((o && o.__y__) ? o.end() : o); }
+ // misc
+ end : function end(o){ return ((o && o.__y__) ? o.end() : o); },
+ parseBool : function(s){
+ var i = parseInt(s);
+ return isNaN(i) ? (s && s.toLowerCase() !== 'false') : i;
+ }
};
, _Array = globals.Array
, _String = globals.String
, _Number = globals.Number
+, _Boolean = globals.Boolean
, FN = "constructor"
, PT = "prototype"
core.forEach({
- 'ensure' : ensure,
- 'metaGetter' : metaGetter,
- 'getNested' : getNested,
- 'setNested' : setNested
+ 'ensure' : ensure,
+ 'getNestedMeta' : getNestedMeta,
+ 'getNested' : getNested,
+ 'setNested' : setNested
}, function(fn, name){
fn = exports[name] = YFunction(fn);
YObject.fn[name] = fn.methodize();
, NUM_SAMPLES = 33
,
-methods = {
- // framerate : 0, // Target framerate
- // frametime : 0, // 1000 / framerate
- // samples : NUM_SAMPLES, // Number of frames to track for effective fps
- //
- // now : 0, // Last tick time (ms)
- // fps : 0, // Effective framerate
- // ticks : 0, // Number of ticks since start
- //
- // timer : null,
- // running : false,
- // times : null, // Last `samples` frame durations
+
+EventLoop =
+exports['EventLoop'] =
+Emitter.subclass('EventLoop', {
+ samples : NUM_SAMPLES, // Number of frames to track for effective fps
+ dilation : 1.0,
+
+ framerate : 0, // Target framerate
+ frametime : 0, // 1000 / framerate
+
+ now : 0, // Last tick time (ms)
+ fps : 0, // Effective framerate
+ ticks : 0, // Number of ticks since start
+
+ timer : null,
+ running : false,
+ times : null, // Last `samples` frame durations
/**
this.framerate = framerate;
this.targetTime = 1000 / framerate;
- this.samples = samples || NUM_SAMPLES;
- this.dilation = dilation || 1.0;
+
+ if (samples !== undefined)
+ this.samples = samples;
+ if (dilation !== undefined)
+ this.dilation = dilation;
this.reset();
},
return (this.realtimes.reduce(Y.op.add,0) / this.realtimes.length);
}
-},
-
-EventLoop =
-exports['EventLoop'] =
-Emitter.subclass('EventLoop', methods)
+})
;
+
function decorate(delegate){
if (!delegate) return;
- Emitter.prototype.decorate.call(this, delegate);
+ Emitter.fn.decorate.call(this, delegate);
['start', 'stop', 'reset']
.forEach(function(k){
- delegate[k] = methods[k].bind(this);
+ delegate[k] = EventLoop.fn[k].bind(this);
}, this);
return delegate;
}
var Y = require('Y').Y
+, getNested = require('Y/types/object').getNested
, Emitter = require('Y/modules/y.event').Emitter
,
Config =
-exports.Config =
-Y.YObject.subclass('Config', function(){
+exports['Config'] =
+Y.YObject.subclass('Config', function(Config){
Y.core.extend(this, {
init : function initConfig(defaults){
- this._defaults = defaults;
- this._o = Y({}, defaults);
+ this._defaults = defaults || {};
+ this._o = Y({}, this._defaults);
this.__emitter__ = new Emitter(this);
},
+ clone : function clone(){
+ var c = new Config();
+ c._defaults = this._defaults;
+ c._o = c._o.clone();
+ return c;
+ },
+
set : function set(key, value, def){
if ( key !== undefined ){
var meta = this.ensure(key).getNested(key, def, true)
state.path.pop();
return state;
+ },
+
+ getDefault : function getDefault(k, def){
+ return getNested(this._defaults, k, def);
}
});
+ this['get'] = this.getNested;
+
});
// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
var Y = require('Y').Y
+, Config = require('y/modules/y.config').Config
+,
-, defaults =
+defaults =
exports['defaults'] = {
game : {
timeDilation : 1.0,
gameoverDelay : 1000
},
ui : {
- createGridCanvas : 1,
- createGridTable : 0,
- showGridCoords : 0,
- showAttackCooldown : 0,
+ createGridCanvas : true,
+ createGridTable : false,
+ showGridCoords : false,
+ showAttackCooldown : true,
showCountdown : (document.location.host.toString() !== 'tanks.woo')
},
pathing : {
- overlayAIPaths : 0,
- overlayPathmap : 0,
- traceTrajectories : 0
+ overlayAiPaths : false,
+ overlayPathmap : false,
+ traceTrajectories : false
}
};
-exports['values'] = Y(defaults).clone().end();
+exports['values'] = new Config(defaults);
Game =
exports['Game'] =
Y.subclass('Game', {
- overlayPathmap : config.pathing.overlayPathmap,
- overlayAIPaths : config.pathing.overlayAIPaths,
- timeDilation : config.game.timeDilation,
- gameoverDelay : config.game.gameoverDelay,
+ overlayPathmap : config.get('pathing.overlayPathmap'),
+ overlayAiPaths : config.get('pathing.overlayAiPaths'),
+ gameoverDelay : config.get('game.gameoverDelay'),
gameover : false,
this.animations = new Y.YArray();
this.viewport = $(viewport || GRID_ELEMENT);
- this.loop = new EventLoop(FRAME_RATE, this, this.timeDilation);
+ this.loop = new EventLoop(FRAME_RATE, this);
this.root =
this.grid =
SECONDTH = ELAPSED / 1000;
SQUARETH = REF_SIZE * SECONDTH
- if (!this.overlayAIPaths)
+ if (!this.overlayAiPaths)
this.pathmap.hidePaths();
this.active.invoke('updateCooldowns', ELAPSED, NOW);
P =
game.player = game.addUnit(new PlayerTank(1), 5,9);
- game.addUnit(new Tank(1).colors('#4596FF', '#182B53', '#F25522'), 3,9);
+ // game.addUnit(new Tank(1).colors('#4596FF', '#182B53', '#F25522'), 3,9);
E =
game.addUnit(new Tank(2), 0,1);
game.addUnit(new Tank(2), 1,0);
- game.addUnit(new Tank(2), 8,1);
+ // game.addUnit(new Tank(2), 8,1);
},
addWall : function addWall(x,y, w,h, isBoundary){
// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
var Y = require('Y').Y
, math = require('ezl/math')
-, Vec = math.Vec
-, Line = math.Line
, QuadTree = require('ezl/util/tree/quadtree').QuadTree
-, Bullet = require('tanks/thing/bullet').Bullet
, astar = require('ezl/util/astar')
+, Bullet = require('tanks/thing/bullet').Bullet
+, config = require('tanks/config').values
+, Vec = math.Vec
+, Line = math.Line
,
PathMap =
exports['PathMap'] =
QuadTree.subclass('PathMap', {
+ overlayAiPaths : config.get('pathing.overlayAiPaths'),
+ overlayPathmap : config.get('pathing.overlayPathmap'),
+
gridSquareSize : REF_SIZE,
gridSquareMidPt : new Vec(REF_SIZE/2, REF_SIZE/2),
, path = Y(astar.search(grid, startN, endN))
;
- if (tanks.config.values.pathing.overlayAIPaths)
+ if (this.overlayAiPaths)
this.drawPath(id, startN, path);
return path
, Thing = require('tanks/thing/thing').Thing
, Trajectory = require('tanks/map/trajectory').Trajectory
, Explosion = require('tanks/fx/explosion').Explosion
+, config = require('tanks/config').values
, Line = shape.Line
, Circle = shape.Circle
,
render : function render(parent){
this.remove();
- if (tanks.config.values.pathing.traceTrajectories) {
+ if (config.get('pathing.traceTrajectories')) {
var t = this.trajectory;
this.tline = Line.fromPoints(t.x1,t.y1, t.x2,t.y2)
.attr('drawDefinitionPoints', true)
Thing =
exports['Thing'] =
new evt.Class('Thing', {
- showAttackCooldown : config.ui.showAttackCooldown,
+ showAttackCooldown : config.get('ui.showAttackCooldown'),
// Attributes
stats: {
+++ /dev/null
-//#ensure "jquery"
-
-
-// TODO
-// ====
-// - store at tanks.config.values, but clone at start to t.c.defaults
-// - need Config class to run triggers on changes
-// - button to clear saved config
-//
-// init
-// - For each item in tanks.config (recursive):
-// - Create a UI box based on type, with a name based on its dotted path
-// - Check cookies -- fill it with the value from cookies or default
-//
-// update
-// - For each config element:
-// - Extract value, typecast based on default's type
-// - If different from default, set cookie
-// - Update tanks.config.values
-
-var Y = require('Y').Y
-, config = require('tanks/config')
-, c = config.values
-, p = c.pathing
-;
-
-
-
-
-exports['init'] = function initConfigUi(){
- $('#config [name=pathmap]').attr('checked', p.overlayPathmap);
- $('#config [name=aipaths]').attr('checked', p.overlayAIPaths);
- $('#config [name=trajectories]').attr('checked', p.traceTrajectories);
-
- $('#config [name=gridCoords]').attr('checked', c.ui.showGridCoords);
- $('#viewport .layer.grid')[(c.ui.showGridCoords ? 'add' : 'remove')+'Class']('showGridCoords');
-};
-
-exports['update'] = function updateConfigUi(evt){
- p.overlayPathmap = $('#config [name=pathmap]').attr('checked');
- p.overlayAIPaths = $('#config [name=aipaths]').attr('checked');
- p.traceTrajectories = $('#config [name=trajectories]').attr('checked');
-
- c.ui.showGridCoords = $('#config [name=gridCoords]').attr('checked');
- $('#viewport .layer.grid')[(c.ui.showGridCoords ? 'add' : 'remove')+'Class']('showGridCoords');
-};
-
-// var templates = {
-// 'label' : { html:'<label for="{id}">{label}</label>' },
-// 'input' : { html:'<input id="{id}" type="{type}" name="{key}" value="{value}">' },
-// 'text' : { include:'input', type:'text' },
-// 'checkbox' : { include:'input', type:'checkbox', props:{ 'checked':false } },
-// 'radio' : { include:'input', type:'radio' }
-// // 'select' : '',
-// // 'textarea' : '',
-// // 'hidden' : '',
-// // 'button' : ''
-// };
-//
-// var type2el = {
-// 'Boolean'
-// 'Number' :
-// 'String' :