/// Now start assembling the normal sub-modules ///
-addNames('YCollection', require('Y/types/collection'));
-addNames('YArray', require('Y/types/array'));
-addNames('YObject deepcopy', require('Y/types/object'));
-addNames('YString', require('Y/types/string'));
-addNames('YNumber range', require('Y/types/number'));
+addNames('YCollection', require('Y/types/collection'));
+addNames('YArray', require('Y/types/array'));
+addNames('YObject getNested setNested deepcopy',
+ require('Y/types/object'));
+addNames('YString', require('Y/types/string'));
+addNames('YNumber range', require('Y/types/number'));
var utils = require('Y/utils');
Y['bindAll'] = utils.bindAll;
var Y = require('Y').Y
-, getNested = require('Y/types/object').getNested
+, getNested = Y.getNested
, Emitter = require('Y/modules/y.event').Emitter
,
*/
updateOnChange : function updateOnChange(events, obj){
if ( !Y.isArray(events) )
- events = events.split();
+ events = events.split(/\s+/);
events = events.map(function events(key){
obj[key.split('.').pop()] = this.get(key);
if ( !Y(key).startsWith('set:') )
addEventListener : function addEventListener(evts, fn){
fn = fn.toFunction();
if ( !Y.isArray(evts) )
- evts = evts.split();
+ evts = evts.split(/\s+/);
evts.forEach(function addEvent(evt){
this.getQueue(evt).push(fn);
}, this);
return evt;
},
- // XXX: does not handle degenerate or recursive event dispatch
+ /**
+ * Dispatches the given event to all listeners in the appropriate queue.
+ * Listeners are invoked with the event, and with context set to the target.
+ * XXX: does not handle degenerate or recursive event dispatch
+ */
dispatchEvent : function dispatchEvent(evt){
this.getQueue(evt.type).invoke('call', evt.target, evt);
if (this.parent) this.parent.fire(evt.type, evt.trigger, evt.data);
,
+// addTypeSupport =
+// exports['addTypeSupport'] =
+// function addTypeSupport(type, parser, builder){
+// var T = Y.typeName(type);
+// type2parser[T] = parser;
+// }
+// ,
+
Field =
exports['Field'] =
this.key = chain.split('.').pop();
this.label = camelToSpaces(this.key);
- var T = Y(Y.type(val)).getName();
+ var T = Y.typeName(def);
this.cast = options.cast || type2parser[T];
this.type = options.type || type2el[T];
},
function createField(group, value, chain){
var def = config.getDefault(chain)
- , field = new Field(chain, def, value);
+ , T = Y.typeName(def);
+
+ if (!type2parser[T])
+ return group;
+ var field = new Field(chain, def, value);
fields[chain] = field;
group.append(field.el);
config.addEventListener('set:'+chain, function onConfigSet(evt){
"Boolean Number String Function Array Date RegExp Object"
.split(" ")
.reduce(function(class2name, name) {
- class2name[ "[object "+name+"]" ] = name.toLowerCase();
+ class2name[ "[object "+name+"]" ] = name;
return class2name;
}, {});
-function type_of(obj){
- return obj == null ?
- String( obj ) :
- class2name[ toString.call(obj) ] || "object";
+function basicTypeName(o){
+ return o == null ?
+ String(o) :
+ class2name[ toString.call(o) ] || "Object";
+}
+
+function typeName(o){
+ if (o === undefined || o === null)
+ return String(o);
+ var T = type(o);
+ return T.className || (T !== Function ? T.name : basicTypeName(o));
}
var arrayLike = isArray.types = [];
-function isArray(obj) { return type_of(obj) === "array" || (arrayLike.indexOf(type(obj)) !== -1); }
-function isFunction(obj) { return type_of(obj) === "function"; }
-function isString(obj) { return type_of(obj) === "string"; }
-function isNumber(obj) { return type_of(obj) === "number"; }
-function isWindow(obj) { return obj && typeof obj === "object" && "setInterval" in obj; }
+function isArray(o) { return basicTypeName(o) === "Array" || (arrayLike.indexOf(type(o)) !== -1); }
+function isFunction(o) { return basicTypeName(o) === "Function"; }
+function isString(o) { return basicTypeName(o) === "String"; }
+function isNumber(o) { return basicTypeName(o) === "Number"; }
+function isWindow(o) { return o && typeof o === "object" && "setInterval" in o; }
-function isPlainObject( obj ){
+function isPlainObject(o){
// 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) )
+ if ( !o || basicTypeName(o) !== "Object" || o.nodeType || isWindow(o) )
return false;
// Not own constructor property must be Object
- if ( obj[FN] &&
- !hasOwn.call(obj, FN) &&
- !hasOwn.call(obj[FN][PT], "isPrototypeOf") )
+ if ( o[FN] &&
+ !hasOwn.call(o, FN) &&
+ !hasOwn.call(o[FN][PT], "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 ) {}
+ for ( key in o ) {}
- return key === undefined || hasOwn.call( obj, key );
+ return key === undefined || hasOwn.call( o, key );
}
-function type( o ) {
+function type(o) {
if ( o === null || o === undefined )
return o;
}
}
-function is( A, B ){
+function is(A, B){
if ( isArray(B) )
return B.some( is.bind(this,A) );
else {
exports['is'] = is;
exports['type'] = type;
-exports['type_of'] = type_of;
+exports['basicTypeName'] = basicTypeName;
+exports['typeName'] = typeName;
exports['isArray'] = isArray;
exports['isFunction'] = isFunction;
exports['isWindow'] = isWindow;
exports['isPlainObject'] = isPlainObject;
-type['KNOWN_CLASSES'] = KNOWN_CLASSES;
+type['KNOWN_CLASSES'] = KNOWN_CLASSES;
// Export these last to avoid methodizing them
exports['YFunction'] = YF(YF);
-exports._ = _;
+exports['_'] = _;
}
// Do we have a type-specific wrapper?
- var name = type.type_of(o)
- , yname = 'Y' + name.charAt(0).toUpperCase() + name.slice(1)
+ var yname = 'Y' + type.basicTypeName(o)
, YType = Y[yname]
;
, objToString = _Object[P].toString
, classToString = function toString(){ return this.className+"()"; }
-;
+,
+
-// Private delegating constructor -- must be defined for every
-// new class to prevent shared state. All construction is
-// actually done in the init method.
-exports['ConstructorTemplate'] = ConstructorTemplate;
+/**
+ * @private
+ * Private delegating constructor. This function must be redefined for every
+ * new class to prevent shared state. All construction is actually done in
+ * the methods initialise and init.
+ *
+ * Fires `create` event prior to initialisation if not subclassing.
+ */
+ConstructorTemplate =
+exports['ConstructorTemplate'] =
function ConstructorTemplate() {
var cls = arguments.callee
, instance = this;
return instance;
}
+,
+/**
+ * Fires `init` event after calling init(). Note that events are fired
+ * for proper instances of the class and instances of subclasses (but
+ * not for the instance created when subclassing).
+ */
+createInitialise =
+exports['createInitialise'] =
+function createInitialise(NewClass){
+ function initialise(){
+ var instance = this
+ , init = NewClass.prototype.init
+ ;
+
+ instance.__emitter__ = new Emitter(instance, NewClass);
+
+ if (init) {
+ var result = init.apply(instance, arguments);
+ if (result) instance = result;
+ }
+
+ instance.fire('init', instance, {
+ 'instance' : instance,
+ 'cls' : NewClass,
+ 'args' : Y(arguments)
+ });
+
+ return instance;
+ }
+ return Y(initialise);
+}
+;
NewClass.__super__ = SuperClass; // don't override NewClass.constructor -- it should be Function
prototype.constructor = prototype.__class__ = NewClass;
- NewClass.init = // XXX: This means subclasses will fire events.
- prototype.initialise =
- Y(function initialise(){
- var instance = this
- , init = NewClass.prototype.init
- ;
-
- instance.__emitter__ = new Emitter(instance, NewClass);
-
- if (init) {
- var result = init.apply(instance, arguments);
- if (result) instance = result;
- }
-
- instance.fire('init', instance, {
- 'instance' : instance,
- 'cls' : NewClass,
- 'args' : Y(arguments)
- });
-
- return instance;
- });
+ NewClass.init = prototype.initialise = createInitialise(NewClass);
// Add class emitter
var ParentEmitter = (Parent.__emitter__ ? Parent : ClassFactory)
_cssClasses : 'ezl layer',
canvas : null,
-
parent : null,
children : null,
// Transforms
_origin : null, // rotational origin
transform : null, // Object
- useCanvasScaling : false, // default to CSS3 scaling
/// Defaults ///
+ useCanvasScaling : false, // Default to CSS3 scaling
+ alwaysClearDrawing : true, // Whether to clear the canvas content before redraw
+ alwaysClearAttrs : false, // Whether to remove all canvas attributes (CONTEXT_ATTRS)
+ // and transforms (scale, rotate, translate) and reset defaults before redraw
+
originX : 0,
originY : 0,
*/
empty : function empty(ctx){
this.children.invoke('remove');
- this.clear();
+ this.clear(ctx);
return this;
},
* @param {Boolean} [force=false] Forces redraw.
*/
draw : function draw(ctx, force){
+ var _ctx = ctx || this.ctx;
+
if ( this.dirty || force ){
- var _ctx = ctx || this.ctx;
+ this.dirty = false;
this._openPath(_ctx);
- this.drawShape(_ctx);
+ this.render(_ctx);
this._closePath(_ctx);
}
- this.dirty = false;
this.children.invoke('draw', ctx, force);
return this;
},
_openPath : function _openPath(ctx){
var self = this
- , alwaysClear = this.alwaysClear
, neg = this.negBleed
, t = this.transform
, w = this.canvasWidth, h = this.canvasHeight ;
ctx.beginPath();
- ctx.setTransform(1,0,0,1,0,0);
- ctx.clearRect(-w,-h, 2*w,2*h);
+ // TODO: only alwaysClearAttrs should reset transforms?
+ // if (this.alwaysClearAttrs)
+ ctx.setTransform(1,0,0,1,0,0);
+
+ if (this.alwaysClearDrawing)
+ ctx.clearRect(-w,-h, 2*w,2*h);
+
+ // if (this.alwaysClearAttrs) {
if (this.useCanvasScaling && (t.scale.x !== 1 || t.scale.y !== 1))
ctx.scale(t.scale.x,t.scale.y);
ctx.translate(neg.x, neg.y);
ctx.translate(t.translate.x, t.translate.y);
+ // }
// Set context attributes
CONTEXT_ATTRS.forEach(function(name){
- if (self[name] !== undefined)
- ctx[name] = self[name];
- else if (alwaysClear)
+ if (this[name] !== undefined)
+ ctx[name] = this[name];
+ else if (this.alwaysClearAttrs)
delete ctx[name];
- });
+ }, this);
return this;
},
/** To be implemented by subclasses. */
- drawShape : function drawShape(ctx){ return this; },
+ render : function render(ctx){ return this; },
_closePath : function _closePath(ctx){
ctx.closePath();
},
/**
- * Clears this layer and all children.
+ * Clears this layer and optionally all children.
*/
- clear : function clear(ctx){
+ clear : function clear(ctx, clearChildren){
var w = this.canvas.width()
, h = this.canvas.height();
ctx = ctx || this.ctx;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(-w,-h, 2*w,2*h);
ctx.closePath();
- this.children.invoke('clear');
+ if (clearChildren)
+ this.children.invoke('clear');
return this;
},
get origin(){ return this._origin; },
relocate : function relocate(x,y){
+ if (x instanceof Array) { y=x[1]; x=x[0]; }
var _x1 = this[X1], _y1 = this[Y1]
, _x2 = this[X2], _y2 = this[Y2]
},
relocated : function relocated(x,y){
- var bb = this.clone();
- return bb.relocate(x,y);
+ return this.clone().relocate(x,y);
},
resize : function resize(w,h){
+ if (w instanceof Array) { h=w[1]; w=w[0]; }
var x1 = this[X1], y1 = this[Y1]
, x2 = this[X2], y2 = this[Y2]
, wOld = x2-x1, hOld = y2-y1
},
resized : function resized(w,h){
- var bb = this.clone();
- return bb.resize(w,h);
+ return this.clone().resize(w,h);
},
clone : function clone(){
var o = this._origin;
return new BoundingBox(this[X1],this[Y1], this[X2],this[Y2], o[X1], o[Y1]);
+ },
+
+
+ get topSide(){ return new Line(this[X1],this[Y1], this[X2],this[Y1]); },
+ get bottomSide(){ return new Line(this[X1],this[Y2], this[X2],this[Y2]); },
+ get leftSide(){ return new Line(this[X1],this[Y1], this[X1],this[Y2]); },
+ get rightSide(){ return new Line(this[X2],this[Y1], this[X2],this[Y2]); },
+
+ side : function side(which){
+ switch (which) {
+ case 'top' : return this.topSide;
+ case 'bottom' : return this.bottomSide;
+ case 'left' : return this.leftSide;
+ case 'right' : return this.rightSide;
+ default : throw new Error('Unknown side: '+which);
+ }
}
});
EventLoop =
exports['EventLoop'] =
Emitter.subclass('EventLoop', {
- samples : NUM_SAMPLES, // Number of frames to track for effective fps
- dilation : 1.0,
+ samples : NUM_SAMPLES, // Number of frames to track for effective fps
+ timeDilation : 1.0,
framerate : 0, // Target framerate
frametime : 0, // 1000 / framerate
if (samples !== undefined)
this.samples = samples;
if (dilation !== undefined)
- this.dilation = dilation;
+ this.timeDilation = dilation;
this.reset();
},
return delegate;
}
+
return this;
},
+ /**
+ * @return {Vec} Delta-(x,y) on this line after given elapsed parametric time.
+ */
+ wouldMove : function wouldMove(t){
+ return new Vec( t*this.pa, t*this.pb );
+ },
+
+ /**
+ * @return {Float} Elapsed parametric time needed to move the given delta-(x,y).
+ */
+ timeToMove : function timeToMove(dx,dy){
+ // see note at iparametric
+ return (dx/this.pa + dy/this.pb) * 0.5;
+ },
+
parametric : function parametric(t){
+ // return this.wouldMove(t).add(this.p1);
return new Vec( this.x1 + t*this.pa ,
this.y1 + t*this.pb );
},
iparametric : function iparametric(x,y){
- return new Vec( (x-this.x1)/this.pa ,
- (y-this.y1)/this.pa );
+ var tx = (x - this.x1)/this.pa
+ , ty = (y - this.y1)/this.pb;
+ // tx and ty should be the same number provided (x,y) is on the line
+ // we average to provide a reasonable answer when that's not true,
+ // rather than failing outright or requiring the user to deal with a Vec.
+ return (tx + ty) * 0.5;
},
pointAtX : function pointAtX(x){ return new Vec(x, this.calcY(x)); },
return this;
},
- drawShape : function drawShape(ctx){
+ render : function render(ctx){
var r = this._radius;
ctx.arc(r,r, r, 0, Math.PI*2, false);
ctx.fill();
return this;
},
- drawShape : function drawShape(ctx){
+ render : function render(ctx){
this._fixSize();
var x1,y1, x2,y2
, line = this.line, p1 = line.p1, p2 = line.p2
ctx.closePath();
// } catch(e) {
// if (window.console) {
- // console.error(this+'.drawShape()');
+ // console.error(this+'.render()');
// console.log(' points:', x1,y1, ' ', x2,y2);
// console.log(' bounds:', minW,minH, ' ', maxW,maxH);
// console.log(' ::', this, line);
return values.map(function(v, i){ return (self[which+i] = v); });
},
- drawShape : function drawShape(ctx){
+ render : function render(ctx){
this.points.forEach(function(loc, i){
ctx.lineTo(loc.x, loc.y);
});
this.size(w,h);
},
- drawShape : function drawShape(ctx){
+ render : function render(ctx){
ctx.rect(0,0, this.canvasWidth,this.canvasHeight);
ctx.fill();
+ ctx.stroke();
},
toString : function(){
+++ /dev/null
-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(Config){
-
- Y.core.extend(this, {
-
- init : function initConfig(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)
- , old = meta.value ;
- def = (def === undefined ? old : def);
- value = (value === undefined ? def : value);
-
- meta.obj[meta.key] = value;
- this._fireMutation('set', key, old, value, meta.obj);
- }
- return this;
- },
-
- remove : function remove(key){
- if ( key !== undefined ){
- var sentinel = {}
- , meta = this.getNested(key, sentinel, true)
- , old = (meta.value === sentinel ? undefined : old);
-
- if ( meta.obj ) {
- delete meta.obj[meta.key];
- this._fireMutation('remove', key, old, undefined, meta.obj);
- }
- }
- return this;
- },
-
- /**
- * @private
- */
- _fireMutation : function _fireMutation(evt, key, oldval, newval, obj){
- if (newval !== oldval){
- var data = {
- 'key' : key,
- 'oldval' : oldval,
- 'newval' : newval,
- 'obj' : obj,
- 'root' : this
- };
- this.fire(evt+':'+key, this, data);
- this.fire(evt, this, data);
- }
- return this;
- },
-
- /**
- * Recursively iterates the hierarchy of the Config, depth-first.
- */
- reduce : function reduce(fn, acc, context){
- context = context || this;
-
- var ret = Y.reduce(this._o, this._reducer, {
- 'acc' : acc,
- 'path' : new Y.YArray(),
- 'cxt' : context
- }, this);
-
- return ret.acc;
- },
-
- /**
- * @private
- * XXX: Not going to call the iterator on root objects. ok?
- * XXX: Always passing the Config object as obj to iterator. ok?
- */
- _reducer : function _reducer(state, v, k, o){
- var chain = state.path.push(k).join('.');
-
- // Nested object -- recurse
- if ( Y.isPlainObject(v) )
- state = Y.reduce(v, this._reducer, state, this);
-
- // Normal value -- invoke iterator
- else
- state.acc = fn.call(state.cxt, state.acc, v, chain, this);
-
- state.path.pop();
- return state;
- },
-
- getDefault : function getDefault(k, def){
- return getNested(this._defaults, k, def);
- }
-
- });
-
- this['get'] = this.getNested;
-
-});
return Layer.fn.draw.apply(this, arguments);
};
- this['drawShape'] =
- function drawShape(ctx){
+ this['render'] =
+ function render(ctx){
var cool = this.cooldown;
if (cool.ready)
// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
-var Y = require('Y').Y
+var Y = require('Y').Y
, Config = require('Y/modules/y.config').Config
+, Vec = require('ezl/math').Vec
,
defaults =
showAttackCooldown : false,
showCountdown : (document.location.host.toString() !== 'tanks.woo')
},
- pathing : {
- overlayAiPaths : false,
- overlayPathmap : false,
- traceTrajectories : false
+ pathing : {
+ gridSquare : REF_SIZE,
+ gridSquareMid : new Vec(REF_SIZE/2, REF_SIZE/2),
+ overlayAiPaths : false,
+ overlayPathmap : false,
+ traceTrajectories : false
}
-};
+}
+,
+
+config =
+exports['config'] = new Config(defaults)
+;
+
+config.addEventListener('set:pathing.gridSquare', function(evt){
+ var sq = evt.data.newval;
+ config.set('pathing.gridSquareMid', new Vec(sq/2, sq/2));
+});
-exports['values'] = new Config(defaults);
, EventLoop = require('ezl/loop').EventLoop
, Circle = require('ezl/shape').Circle
-, config = require('tanks/config').values
-, map = require('tanks/map')
-, thing = require('tanks/thing')
-, Grid = require('tanks/ui/grid').Grid
+, config = require('tanks/config').config
+, map = require('tanks/map')
+, thing = require('tanks/thing')
+, Grid = require('tanks/ui/grid').Grid
+, PathMapUI = require('tanks/ui/pathmapui').PathMapUI
, Level = map.Level
, Thing = thing.Thing, Tank = thing.Tank, Bullet = thing.Bullet
Game =
exports['Game'] =
Y.subclass('Game', {
- overlayPathmap : null,
- overlayAiPaths : null,
- gameoverDelay : null,
+ // Config
+ gameoverDelay : null,
+
+ // Defaults
+ gameover : false,
- gameover : false,
init : function initGame(viewport){
this.level =
new Level(this, COLUMNS*REF_SIZE, ROWS*REF_SIZE)
.appendTo(this.root);
- this.pathmap = this.level.pathmap;
- Thing.addEventListener('init', this.addUnit);
- Thing.addEventListener('destroy', this.killUnit);
+ var pathmap = this.pathmap = this.level.pathmap;
+ pathmap.ui = new PathMapUI(this, pathmap);
+ this.level.append(pathmap.ui);
+
+ Thing.addEventListener('init', this.addThing);
+ Thing.addEventListener('destroy', this.killThing);
this.addEventListener('tick', this.tick);
},
destroy : function destroy(){
- Thing.removeEventListener('init', this.addUnit);
- Thing.removeEventListener('destroy', this.killUnit);
+ Thing.removeEventListener('init', this.addThing);
+ Thing.removeEventListener('destroy', this.killThing);
this.stop();
this.resetGlobals();
},
draw : function draw(){
this.root.draw();
-
- this.pathmap.removeOverlay(this.viewport);
- if (this.overlayPathmap)
- this.pathmap.overlay(this.viewport);
},
/**
SECONDTH = ELAPSED / 1000;
SQUARETH = REF_SIZE * SECONDTH
- if (!this.overlayAiPaths)
- this.pathmap.hidePaths();
-
this.active.invoke('updateCooldowns', ELAPSED, NOW);
this.active.invoke('act');
this.animations = this.animations.filter(this.tickAnimations, this);
// *** Agent Management *** //
- addUnit : function addUnit(unit, col,row){
+ addThing : function addThing(unit, col,row){
if (unit instanceof Event)
unit = unit.trigger;
return unit;
},
- killUnit : function killUnit(unit){
+ killThing : function killThing(unit){
if (unit instanceof Event)
unit = unit.trigger;
return unit;
},
- moveUnitTo : function moveUnitTo(agent, x,y){
+ moveThingTo : function moveThingTo(agent, x,y){
this.pathmap.removeBlocker(agent);
agent.position(x,y);
this.pathmap.addBlocker(agent);
});
-config.updateOnChange(
- ['pathing.overlayPathmap', 'pathing.overlayAiPaths', 'game.gameoverDelay'],
- Game.fn);
+config.updateOnChange('game.gameoverDelay', Game.fn);
// exports.tanks =
tanks = {
- 'config' : require('tanks/config'),
+ 'config' : require('tanks/config').config,
'ui' : require('tanks/ui'),
'Game' : require('tanks/game').Game,
, Rect = require('ezl/shape').Rect
, Thing = require('tanks/thing/thing').Thing
, Tank = require('tanks/thing/tank').Tank
+, Item = require('tanks/thing/item').Item
, PlayerTank = require('tanks/thing/player').PlayerTank
, PathMap = require('tanks/map/pathmap').PathMap
, Wall = require('tanks/map/wall').Wall
this );
P =
- game.player = game.addUnit(new PlayerTank(1), 5,9);
- // game.addUnit(new Tank(1).colors('#4596FF', '#182B53', '#F25522'), 3,9);
+ game.player = game.addThing(new PlayerTank(1), 5,9);
+ // game.addThing(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.addThing(new Tank(2), 0,1);
+ game.addThing(new Tank(2), 1,0);
+ // game.addThing(new Tank(2), 8,1);
+
+ I = game.addThing(new Item(), 8,8);
},
addWall : function addWall(x,y, w,h, isBoundary){
return wall;
},
- drawShape : Y.op.nop
+ render : Y.op.nop
});
\ No newline at end of file
, math = require('ezl/math')
, QuadTree = require('ezl/util/tree/quadtree').QuadTree
, astar = require('ezl/util/astar')
+, config = require('tanks/config').config
, Bullet = require('tanks/thing/bullet').Bullet
-, config = require('tanks/config').values
, Vec = math.Vec
, Line = math.Line
-, GRID_SQUARE_SIZE = REF_SIZE
-, GRID_SQUARE_MID_PT = new Vec(REF_SIZE/2, REF_SIZE/2)
+, PASSABLE = 1 // Does not obstruct other objects
+, BLOCKING = 2 // Objstructs other blockers
+, ZONE = 3 // Does not obstruct other objects, but still collides with them
,
exports['PathMap'] =
QuadTree.subclass('PathMap', {
// Config
- overlayAiPaths : null,
- overlayPathmap : null,
+ gridSquare : null,
+ gridSquareMid : null,
var B = blocker.boundingBox;
if (bb.x2 <= B.x1 && x2 >= B.x1) {
msg += 'left';
- side = new Line(B.x1,B.y1, B.x1,B.y2);
+ side = B.leftSide;
to = trj.pointAtX(B.x1-offX-1);
} else if (bb.x1 >= B.x2 && x1 <= B.x2) {
msg += 'right';
- side = new Line(B.x2,B.y1, B.x2,B.y2);
+ side = B.rightSide;
to = trj.pointAtX(B.x2+ro.x+1);
} else if (bb.y2 <= B.y1 && y2 >= B.y1) {
msg += 'top';
- side = new Line(B.x1,B.y1, B.x2,B.y1);
+ side = B.topSide;
to = trj.pointAtY(B.y1-offY-1);
} else if (bb.y1 >= B.y2 && y1 <= B.y2) {
msg += 'bottom';
- side = new Line(B.x1,B.y2, B.x2,B.y2);
+ side = B.bottomSide;
to = trj.pointAtY(B.y2+ro.y+1);
}
*/
grid : function grid(){
if ( !this._grid ) {
- var size = GRID_SQUARE_SIZE
+ var size = this.gridSquare
, floor = Math.floor, ceil = Math.ceil
, cols = ceil((this.width-2) /size)
, rows = ceil((this.height-2)/size)
},
vec2Square : function vec2Square(x,y){
- if (x instanceof Array){
- y = x.y;
- x = x.x;
- }
- var floor = Math.floor, size = GRID_SQUARE_SIZE;
+ if (x instanceof Array){ y = x.y; x = x.x; }
+ var floor = Math.floor, size = this.gridSquare;
return new Vec(floor(x/size), floor(y/size));
},
square2Vec : function square2Vec(x,y){
- if (x instanceof Array){
- y = x.y;
- x = x.x;
- }
- var floor = Math.floor, size = GRID_SQUARE_SIZE;
+ if (x instanceof Array){ y = x.y; x = x.x; }
+ var floor = Math.floor, size = this.gridSquare;
return new Vec(floor(x)*size, floor(y)*size);
},
- path : function path(start, end, id){
- var size = GRID_SQUARE_SIZE, floor = Math.floor
+ path : function path(start, end, agent){
+ return this.gridPath(start, end, agent)
+ .invoke('scale', this.gridSquare)
+ .invoke('add', this.gridSquareMid)
+ .end();
+ },
+
+ /**
+ * @protected
+ * Generates grid-sized path from start to end.
+ */
+ gridPath : function gridPath(start, end, agent){
+ var size = this.gridSquare, floor = Math.floor
, grid = this.grid()
, startX = floor(start.x/size)
, endX = floor(end.x/size)
, endY = floor(end.y/size)
, endN = grid[endX][endY]
-
- , path = Y(astar.search(grid, startN, endN))
;
-
- if (this.overlayAiPaths)
- this.drawPath(id, startN, path);
-
- return path
- .invoke('scale', size)
- .invoke('add', GRID_SQUARE_MID_PT)
- .end();
- },
-
- drawPath : function drawPath(id, start, path){
- var size = GRID_SQUARE_SIZE, off
- , w = this.width-2, h = this.height-2
- , el = this.game.viewport
- , grid = this.grid()
- , canvas = $('#path'+id, el)[0]
- ;
-
- if (!canvas) {
- canvas = $('<canvas id="path'+id+'" class="path" style="position:absolute"/>').appendTo(el)[0];
- $(canvas).width(w).height(h);
- canvas.width = w;
- canvas.height = h;
- }
-
- var ctx = canvas.getContext('2d');
- ctx.lineWidth = 0;
-
- // Clear the canvas
- ctx.beginPath();
- ctx.clearRect(this.x,this.y, w,h);
- ctx.closePath();
-
-
- // Draw blockers
- // var s = size/2;
- // ctx.fillStyle = 'rgba(255,0,0,0.2)';
- //
- // Y(grid).invoke('forEach', function(node){
- // if ( !node.blocked ) return;
- //
- // var x = node.x*size + s
- // , y = node.y*size + s ;
- //
- // ctx.beginPath();
- // ctx.moveTo( x, y-s );
- // ctx.lineTo( x+s, y );
- // ctx.lineTo( x, y+s );
- // ctx.lineTo( x-s, y );
- // ctx.fill();
- // ctx.closePath();
- // });
-
-
- // Draw path
- off = size*0.3;
-
- function drawStep(p){
- var r = size/2 - off
- , x = p.x*size + off + r
- , y = p.y*size + off + r ;
-
- ctx.beginPath();
- ctx.arc(x,y, r, 0, Math.PI*2, false);
- ctx.fill();
- ctx.closePath();
- }
- // this.drawStep = drawStep;
-
- ctx.fillStyle = 'rgba(0,0,0,0.1)';
- drawStep( start );
- path.forEach(drawStep);
-
-
- // Draw start
- off = size*0.15;
-
- ctx.fillStyle = 'rgba(0,255,0,0.05)';
- drawStep( start );
-
- // Draw finish
- ctx.fillStyle = 'rgba(0,0,255,0.05)';
- drawStep( path.last() );
-
- $(canvas).show();
- },
-
- destroyPath : function destroyPath(id){
- $('#path'+id, this.game.viewport).remove();
- },
-
- hidePath : function hidePath(id){
- $('#path'+id, this.game.viewport).hide();
- },
-
- hidePaths : function hidePaths(){
- $('.path', this.game.viewport).hide();
- },
-
-
-
- _overlayBG : $('<img src="img/pathmap-bg.png" />')[0],
- overlay : function overlay(gridEl){
- var w = this.width-2
- , h = this.height-2
- , canvas = $('.overlay', gridEl)[0]
- ;
-
- if ( !canvas ) {
- canvas = $('<canvas class="overlay" style="position:absolute"/>').prependTo(gridEl)[0];
- $(canvas).width(w).height(h);
- canvas.width = w;
- canvas.height = h;
- }
-
- var ctx = canvas.getContext('2d');
- // ctx.scale(SCALE, SCALE);
-
- // Clear the canvas
- ctx.beginPath();
- ctx.clearRect(this.x,this.y, w,h);
- ctx.closePath();
-
- // ctx.fillStyle = ctx.createPattern(this._overlayBG, 'repeat');
- ctx.fillStyle = 'rgba(255,255,255,0.1)';
- ctx.lineWidth = 1;
- ctx.strokeStyle = 'rgba(255,255,255,0.2)';
-
-
- // Draw regions
- this.reduce(function(acc, v, r, tree){
- if ( acc[r.id] || v.isBoundary )
- return acc;
-
- acc[r.id] = r;
-
- ctx.beginPath();
- // ctx.globalAlpha = 0.1;
- ctx.rect(r.x1,r.y1, r.width,r.height);
- ctx.fill();
- // ctx.globalAlpha = 1;
- ctx.stroke();
- ctx.closePath();
-
- return acc;
- }, {});
-
- $(canvas).show();
- },
-
- removeOverlay : function removeOverlay(gridEl){
- $('.overlay', gridEl).hide();
+ return Y(astar.search(grid, startN, endN))
}
-
});
-config.updateOnChange(
- ['pathing.overlayPathmap', 'pathing.overlayAiPaths'],
- PathMap.fn);
'set remove removeAll clear'
.split(' ')
};
});
+config.updateOnChange(
+ ['pathing.gridSquare', 'pathing.gridSquareMid'],
+ PathMap.fn);
+
var Y = require('Y').Y
+, op = require('Y/op')
, math = require('ezl/math')
, Vec = math.Vec
, Line = math.Line
, Rect = math.Rect
, BoundingBox = require('ezl/loc').BoundingBox
, Thing = require('tanks/thing/thing').Thing
+
+, BOUND_SIZE_RATIO = 0.75
,
+
Trajectory =
exports['Trajectory'] =
Line.subclass('Trajectory', {
// init with raw numbers to do calculations
Line.init.call(this, x1,y1, x2,y2, tdist || this.tdist);
+ // Find appropriate edge in direction of line
var pm = this.pathmap
, ex = (x1 > x2 ? -REF_SIZE : REF_SIZE+pm.width )
, ey = (y1 > y2 ? -REF_SIZE : REF_SIZE+pm.height)
, edge = this.near(ex,ey)
;
- // Move goal point beyond far wall to avoid rotations
+ // Move goal point beyond far wall to avoid rotations post-reflection
x2 = this.x2 = edge.x;
y2 = this.y2 = edge.y;
this.p2 = new Vec(x2,y2);
// Determine how much time can pass before we risk teleporting
// We'll need to reset this whenever the bounding box changes size
resetBound : function resetBound(){
- var BOUND_SIZE_RATIO = 0.75
+ var p = BOUND_SIZE_RATIO
, abs = Math.abs
, w = this.owner.width, h = this.owner.height;
- this.tBound = Math.min( abs(w / this.pa) * BOUND_SIZE_RATIO,
- abs(h / this.pb) * BOUND_SIZE_RATIO );
+ this.tBound = Math.min(abs(w/this.pa) * p, abs(h/this.pb) * p);
return this;
},
+ clone : function clone(){
+ return new Trajectory(this.owner, this.x1,this.y1, this.x2,this.y2, this.tdist);
+ },
+
intersects : function intersects(x,y){
var o = x;
if (o instanceof Rect)
return o.intersects(this);
- return Line.prototype.intersects.call(this, x,y);
+ return Line.fn.intersects.call(this, x,y);
},
* @return -1 if a closer b, 1 if a further b, 0 if a same as b
*/
compare : function compare(a, b){
- if (a instanceof Thing) a = a.midpoint;
- if (b instanceof Thing) b = b.midpoint;
+ if (a instanceof Thing) a = a.loc;
+ if (b instanceof Thing) b = b.loc;
var abs = Math.abs
- // , cur = this.owner.midpoint
- // , t = this.iparametric(cur.x, cur.y)
, t = this.elapsed
, xa = this.calcX(a.y), ya = this.calcY(a.x)
- , ta = this.iparametric(xa,ya)
- , da = (ta.x + ta.y)/2 - t
- // , da = (ta.x - t.x + ta.y - t.y)/2
- // , dxa = ta.x - t.x, dya = ta.y - t.y
+ , ta = this.iparametric(xa,ya) - t
, xb = this.calcX(b.y), yb = this.calcY(b.x)
- , tb = this.iparametric(xb,yb)
- , db = (tb.x + tb.y)/2 - t
- // , db = (tb.x - t.x + tb.y - t.y)/2
- // , dxb = tb.x - t.x, dyb = tb.y - t.y
+ , tb = this.iparametric(xb,yb) - t
;
// If one has passed, return the other
- if ( da < 0 && db >= 0 )
+ if ( ta < 0 && tb >= 0 )
return 1;
- if ( db < 0 && da >= 0 )
+ if ( tb < 0 && ta >= 0 )
return -1;
- return Y.op.cmp(abs(da), abs(db));
+ return op.cmp(abs(ta), abs(tb));
},
closer : function closer(o1, o2){
},
comesWithin : function comesWithin(pt, w,h){
- if ( !this.owner.midpoint )
+ if ( !this.owner.loc )
return false;
- if (pt instanceof Thing) pt = pt.midpoint;
+ if (pt instanceof Thing) pt = pt.loc;
if ( w === undefined ){
w = 0; h = 0;
h = w.height; w = w.width;
}
- var cur = this.owner.midpoint
+ var cur = this.owner.loc
, fx = this.calcX(pt.y), fy = this.calcY(pt.x)
, t = this.iparametric(cur.x, cur.y)
, ft = this.iparametric(fx,fy)
, dw = Math.abs(fx - pt.x), dh = Math.abs(fy - pt.y)
;
- return ( t.x <= ft.x && t.y <= ft.y
- && ( dw <= w || dh <= h ) );
+ return ( t <= ft && (dw <= w || dh <= h) );
},
pathBlocked : function pathBlocked(obj, ignore){
},
- step : function step(dt){
- this.halt = false;
-
- var _dt = dt = (dt === undefined ? ELAPSED : dt)
- , _bb = bb = this.owner.boundingBox, bw = bb.width, bh = bb.height
- , to, t, r;
-
- do {
- t = Math.min(this.tBound, dt);
- dt -= t;
- this.elapsed += t;
-
- to = this.stepTo(t, bb);
- if (this.halt) break;
-
- bb = new BoundingBox(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;
-
- 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 {
- ng = math.reflect(this.p2, test.side);
- }
-
- this.reset(to.x,to.y, ng.x,ng.y);
- owner.render(this.game.level);
-
- // 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'));
- }
-
- return to;
- },
+ // step : function step(dt){
+ // this.halt = false;
+ //
+ // var _dt = dt = (dt === undefined ? ELAPSED : dt)
+ // , _bb = bb = this.owner.boundingBox, bw = bb.width, bh = bb.height
+ // , to, t, r;
+ //
+ // do {
+ // t = Math.min(this.tBound, dt);
+ // dt -= t;
+ // this.elapsed += t;
+ //
+ // to = this.stepTo(t, bb);
+ // if (this.halt) break;
+ //
+ // bb = new BoundingBox(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;
+ //
+ // 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 {
+ // ng = math.reflect(this.p2, test.side);
+ // }
+ //
+ // this.reset(to.x,to.y, ng.x,ng.y);
+ // owner.render(this.game.level);
+ //
+ // // 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'));
+ // }
+ //
+ // return to;
+ // },
toString : function toString(){
return 'T['+this.p1+', '+this.p2+', slope='+this.slope.toFixed(3)+']';
--- /dev/null
+var Y = require('Y').Y
+,
+
+Traversal =
+exports['Traversal'] =
+Y.subclass('Traversal', {
+ elapsed : 0,
+ halt : false,
+
+ isBlocked : false,
+
+
+
+ init : function initTraversal(thing, trajectory){
+ this.thing = thing;
+ this.pathmap = thing.pathmap;
+ this.trajectory = trajectory || thing.trajectory;
+ },
+
+ step : function step(dt){
+ this.halt = false;
+
+ var traj = this.trajectory
+ , thing = this.thing
+ , bb = thing.boundingBox.clone()
+ , to, t;
+
+ do {
+ t = Math.min(traj.tBound, dt);
+ dt -= t;
+ this.elapsed += t;
+
+ to = this.stepTo(t, bb);
+ if (this.halt) break;
+
+ bb = bb.relocate(to);
+
+ } while (dt > 0);
+
+ return to;
+ },
+
+ stepTo : function stepTo(t, bb){
+ var ng, traj = this.trajectory, og = traj.p2, thing = this.thing
+ , to = traj.parametric(this.elapsed), _to = to
+ , test = this.pathmap.moveBlocked(thing, this, to, bb)
+ ;
+
+ // Blocked! Reflect trajectory
+ if ( test ) {
+ to = test.to;
+ this.bounces++;
+
+ var blocker = test.blockers[0];
+ thing.fire('collide', blocker);
+ blocker.fire('collide', thing);
+
+ // Potentially set by responders to collision event
+ if (this.halt) return to;
+
+ 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 {
+ ng = math.reflect(this.p2, test.side);
+ }
+
+ this.reset(to.x,to.y, ng.x,ng.y);
+ thing.render(this.game.level);
+
+ // console.log([
+ // '['+TICKS+' ('+this.depth+')] '+thing+' 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'));
+ }
+
+ return to;
+ },
+
+
+})
-var Y = require('Y').Y
-, Rect = require('ezl/shape').Rect
-, Thing = require('tanks/thing/thing').Thing
+var Y = require('Y').Y
+, op = require('Y/op')
+, Rect = require('ezl/shape').Rect
+, Thing = require('tanks/thing/thing').Thing
,
},
// inactive
- createCooldowns : Y.op.nop,
+ createCooldowns : op.nop,
// indestructable
- dealDamage : Y.op.nop,
+ dealDamage : op.nop,
render : function render(parent){
// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
var Y = require('Y').Y
+, op = require('Y/op')
, math = require('ezl/math')
, shape = require('ezl/shape')
+, config = require('tanks/config').config
+, thing = require('tanks/thing/thing')
, Wall = require('tanks/map/wall').Wall
-, Thing = require('tanks/thing/thing').Thing
, Trajectory = require('tanks/map/trajectory').Trajectory
, Explosion = require('tanks/fx/explosion').Explosion
-, config = require('tanks/config').values
+
+, fillStats = thing.fillStats
+, Thing = thing.Thing
, Line = shape.Line
, Circle = shape.Circle
,
+
Bullet =
exports['Bullet'] =
Thing.subclass('Bullet', {
move : 2.0 // move speed (squares/sec)
},
- fillStats : function(){
- this.stats = Y({},
- Thing.fillStats(this.owner.stats),
- Thing.fillStats(this.stats) );
+ createStats : function createStats(){
+ this.stats = Y({}, fillStats(this.owner.stats), fillStats(this.stats) );
},
remove : function remove(){
return this;
},
- createCooldowns : Y.op.nop,
- updateCooldowns : Y.op.nop,
+ createCooldowns : op.nop,
+ updateCooldowns : op.nop,
setTarget : function setTarget(x,y){
var loc = this.loc
// We test twice because the process of calculating the trajectory
// could result in a collision, killing the bullet
if (!this.dead) var to = this.trajectory.step( ELAPSED );
- if (!this.dead) this.game.moveUnitTo(this, to.x, to.y);
+ if (!this.dead) this.game.moveThingTo(this, to.x, to.y);
return this;
},
exports['Bullet'] = require('tanks/thing/bullet').Bullet;
exports['Tank'] = require('tanks/thing/tank').Tank;
exports['PlayerTank'] = require('tanks/thing/player').PlayerTank;
+exports['Item'] = require('tanks/thing/item').Item;
// exports['CustomTank'] = require('tanks/thing/customtank').CustomTank;
--- /dev/null
+var Y = require('Y').Y
+, op = require('Y/op')
+, Thing = require('tanks/thing/thing').Thing
+, shape = require('ezl/shape')
+, Rect = shape.Rect
+
+, SIZE = REF_SIZE * 0.5
+,
+
+
+Item =
+exports['Item'] =
+Thing.subclass('Item', {
+ blocking : true,
+ active : false,
+
+ width : SIZE,
+ height : SIZE,
+
+ stats : { hp:1, move:0, power:0 },
+
+ // inactive
+ createCooldowns : op.nop,
+ updateCooldowns : op.nop,
+ act : op.nop,
+
+ // indestructable
+ dealDamage : op.nop,
+
+
+ init : function initItem(){
+ Thing.init.call(this, 0);
+
+ this.addEventListener('collide', this.onCollide.bind(this));
+ },
+
+ onCollide : function onCollide(evt){
+ console.log('collide!', this);
+ this.destroy();
+ },
+
+ render : function render(parent){
+ this.remove();
+
+ var loc = this.loc;
+ this.shape = new Rect(this.width, this.height)
+ .origin('50%', '50%')
+ .position(loc.x, loc.y)
+ .fill('#83BB32')
+ .stroke('#1C625B', 2.0)
+ .appendTo( parent );
+ this.shape.layer.attr('title', ''+loc);
+
+ return this;
+ }
+
+})
+;
// ;
//
// if ( !blockers.size() )
- // this.game.moveUnitTo(this, x,y);
+ // this.game.moveThingTo(this, x,y);
// },
var Y = require('Y').Y
+, op = require('Y/op')
, Thing = require('tanks/thing/thing').Thing
, Bullet = require('tanks/thing/bullet').Bullet
, Trajectory = require('tanks/map/trajectory').Trajectory
width : REF_SIZE*0.55,
height : REF_SIZE*0.55,
+
// Attributes
stats : {
hp : 1, // health
shootEnemy : 0.75 // shoot at enemy tank if in range
},
+ buffs : null,
+
nShots : 0,
currentMove : null,
forceCurrentMove : false,
Thing.init.call(this, align);
this.coolgauge = new CooldownGauge(this.cooldowns.attack, this.width+1,this.height+1);
this.onBulletDeath = this.onBulletDeath.bind(this);
- this.addEventListener('destroy', destroyPath);
};
- function destroyPath(){
- this.game.pathmap.destroyPath(this.id);
- }
-
this['onBulletDeath'] =
function onBulletDeath(evt){ this.nShots--; };
};
this['findNearEnemies'] =
- function findNearEnemies(ticks, needLineOfSight){
+ function findNearEnemies(ticks, needLineOfSight){ // TODO: Split off LOS version
return this.findNearLike(ticks, function nearEnemyFilter(agent){
- var am = agent.midpoint;
+ var aLoc = agent.loc;
return ( agent.align !== this.align
- && Y.is(Tank, agent)
+ && (agent instanceof Tank)
&& !(needLineOfSight
- && new Trajectory(this,this.midpoint,am).pathBlocked(agent))
+ && new Trajectory(this,this.loc,aLoc).pathBlocked(agent))
);
});
};
return null;
var manhattan = Vec.manhattan
- , bb = this.boundingBox, mid = this.midpoint ;
+ , cmp = op.cmp
+ , loc = this.loc ;
agents.sort(function(a,b){
- return Y.op.cmp(
- manhattan(a.midpoint,mid),
- manhattan(b.midpoint,mid) ); // FIXME: midpoint-to-midpoint is wrong -- should use pt on closest boundary-side of object
+ return cmp(
+ manhattan(a.midpoint,loc),
+ manhattan(b.midpoint,loc) );
});
return agents.attr(0);
this.nShots++;
p.addEventListener('destroy', this.onBulletDeath);
- this.game.addUnit(p).render(this.game.level);
+ this.game.addThing(p).render(this.game.level);
return p;
};
, blockers = this.game.pathmap.get(nbb.x1,nbb.y1, nbb.x2,nbb.y2).remove(this);
if ( !blockers.size() )
- this.game.moveUnitTo(this, x,y);
+ this.game.moveThingTo(this, x,y);
return this;
};
// console.log(this, 'moving toward', t);
var pm = this.game.pathmap
- , start = this.midpoint
+ , start = this.loc
- , path = this.lastPath = pm.path(start, end, this.id)
+ , path = this.lastPath = pm.path(start, end, this)
, to = this.currentMove = path.shift()
;
};
//#ensure "evt"
-
var Y = require('Y').Y
+, op = require('Y/op')
, evt = require('evt')
-
, Loc = require('ezl/loc/loc').Loc
, BoundingBox = require('ezl/loc/boundingbox').BoundingBox
, Cooldown = require('ezl/loop').Cooldown
+, config = require('tanks/config').config
+
+, THING_ID = 0
+,
-, config = require('tanks/config').values
+fillStats =
+exports['fillStats'] =
+function fillStats(stats){
+ return Y.reduce(stats, function(_stats, v, k){
+ k = Y(k);
+ var k_ = k.rtrim('_max')
+ , k_max = k+'_max'
+ ;
+ if ( k.endsWith('_max') ) {
+ if ( _stats[k_] === undefined )
+ _stats[k_] = v;
+
+ } else if ( _stats[k_max] === undefined )
+ _stats[k_max] = v;
+
+ return _stats;
+ }, Y.extend({}, stats));
+}
,
stats: {
hp : 1, // health
move : 1.0, // move speed (squares/sec)
- rotate : HALF_PI, // rotation speed (radians/sec)
+ rotate : HALF_PI, // rotation speed (radians/sec) [UNUSED]
power : 1, // attack power
speed : 0.5, // attack cool (sec)
shots : 5 // max projectiles in the air at once
align : 0, // 0 reserved for neutral units
dead : false,
+ pathing : null,
blocking : true, // Whether the agent obstructs pathing
active : true, // Whether the agent takes actions
width : REF_SIZE,
height : REF_SIZE,
+ // Accessors
+ set : op.set.methodize(),
+ attr : op.attr.methodize(),
+
+
init : function init(align){
- this.id = Thing.THING_ID++;
+ this.id = THING_ID++;
this.align = align || 0;
+ this.createBoundingBox();
+ this.createStats();
+ this.createCooldowns();
+ },
+
+
+ createBoundingBox : function createBoundingBox(){
this.boundingBox = new BoundingBox(0,0, this.width,this.height, this.originX,this.originY);
+ },
+
+ createStats : function createStats(){
+ this.stats = fillStats(this.stats);
+ },
+
+ createCooldowns : function createCooldowns(){
+ this.cooldowns = {
+ 'attack': new Cooldown(1000 * this.stats.speed)
+ };
+ this.ai = Y(this.ai).map(function(freq, k){
+ return new Cooldown(1000 * freq);
+ });
- this.fillStats();
- this.createCooldowns();
+ this._cooldowns = Y(this.cooldowns);
+ this._ai = Y(this.ai);
},
- set : Y.op.set.methodize(),
- attr : Y.op.attr.methodize(),
+ updateCooldowns : function updateCooldowns(elapsed, now){
+ this._cooldowns.invoke('tick', elapsed, now);
+ this._ai.invoke('tick', elapsed, now);
+ return this;
+ },
position : function position(x,y){
var bb = this.boundingBox.relocate(x,y)
, loc = this.loc = bb.absOrigin;
this.midpoint = bb.midpoint;
- if (this.shape) this.shape.position(loc.x,loc.y);
+ if (this.shape) this.shape.position(loc.x,loc.y); // XXX: eh?
return this;
},
return this;
},
- fillStats : function fillStats(){
- this.stats = Thing.fillStats(this.stats);
- },
-
- createCooldowns : function createCooldowns(){
- this.cooldowns = {
- 'attack': new Cooldown(1000 * this.stats.speed)
- };
- this.ai = Y(this.ai).map(function(freq, k){
- return new Cooldown(1000 * freq);
- });
-
- this._cooldowns = Y(this.cooldowns);
- this._ai = Y(this.ai);
- },
-
- updateCooldowns : function updateCooldowns(elapsed, now){
- this._cooldowns.invoke('tick', elapsed, now);
- this._ai.invoke('tick', elapsed, now);
- return this;
- },
-
dealDamage : function dealDamage(d, source){
this.stats.hp -= d;
-
if (this.stats.hp <= 0)
this.destroy();
-
return this;
},
-
/**
* Calculates the location of the bullet-spawning point on this Thing.
*/
* Sets up unit appearance for minimal updates. Called once at start,
* or when the world needs to be redrawn from scratch.
*/
- render : function render( parent ){
+ render : function render(){
return this;
},
});
-Y(Thing).extend({
- THING_ID : 0,
-
- fillStats : function fillStats(stats){
- var st = Y(stats)
- , stats = st.clone().end() ;
-
- st.forEach(function(v, k){
- k = Y(k);
- var 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;
- }
-});
config.updateOnChange('ui.showAttackCooldown', Thing.fn);
, cookies = require('Y/modules/y.cookies')
, scaffold = require('Y/modules/y.scaffold')
, EventLoop = require('ezl/loop/eventloop').EventLoop
-, config = require('tanks/config').values
+, config = require('tanks/config').config
, configUI, fields
;
});
configUI.append( jQuery('<div class="clearer"></div>') );
- config.addEventListener('set:game.timeDilation', function(evt){
- EventLoop.fn.dilation = evt.data.newval;
- });
};
+// Persist config via cookies
config.addEventListener('set', function(evt){
var d = evt.data;
if (d.newval !== d.defval)
cookies.remove(d.key);
});
+config.updateOnChange('game.timeDilation', EventLoop.fn);
+
+
// exports['init'] =
// function initConfigUi(){
// $('#config [name=pathmap]').attr('checked', config.get('pathing.overlayPathmap'));
var Y = require('Y').Y
, Rect = require('ezl/shape').Rect
-, config = require('tanks/config').values
+, config = require('tanks/config').config
,
Grid =
createGridTable : null,
createGridCanvas : null,
- // Defaults
- _cssClasses : 'ezl layer shape rect grid',
+ // Shape Config
+ _cssClasses : 'grid rect shape layer ezl',
strokeStyle : '#6E6E6E',
lineWidth : 0.5,
Rect.init.call(this, cols*side, rows*side);
},
- drawShape : function drawShape(ctx){
+ render : function render(ctx){
if (this.table) this.table.remove();
// if (this.canvas) this.canvas.remove();
var Y = require('Y').Y;
+
Y.extend(exports, require('tanks/ui/configui'));
+
+exports['PathMapUI'] = require('tanks/ui/pathmapui').PathMapUI;
exports['main'] = require('tanks/ui/main');
, Game = require('tanks/game').Game
, Tank = require('tanks/thing').Tank
, FpsSparkline = require('ezl/loop').FpsSparkline
-, config = require('tanks/config').values
+, config = require('tanks/config').config
, updateTimer = null
;
--- /dev/null
+var Y = require('Y').Y
+, Rect = require('ezl/shape').Rect
+, astar = require('ezl/util/astar')
+, PathMap = require('tanks/map/pathmap').PathMap
+, config = require('tanks/config').config
+,
+
+
+PathMapUI =
+exports['PathMapUI'] =
+Rect.subclass('PathMapUI', {
+ // Config
+ gridSquare : null,
+ overlayPathmap : null,
+ overlayAiPaths : null,
+
+ // Shape Config
+ _cssClasses : 'pathmap rect shape layer ezl',
+
+ fillStyle : 'rgba(255,255,255,0.1)',
+ strokeStyle : 'rgba(255,255,255,0.2)',
+ lineWidth : 1,
+
+
+
+ init : function initPathMapUI(game, pathmap){
+ this.game = game;
+ this.pathmap = pathmap;
+
+ Rect.init.call(this, pathmap.width-2, pathmap.height-2);
+
+ pathmap.gridPath = this.gridPathWithUI.bind(this);
+ this.cleanUpAgent = this.cleanUpAgent.bind(this);
+ this._drawPathStep = this._drawPathStep.bind(this);
+ },
+
+ render : function render(ctx){
+ if (this.overlayPathmap)
+ this.overlay(ctx);
+ // AI Paths automatically show up (and are cleaned up)
+ // as they are created (and destroyed).
+ this.dirty = true;
+ },
+
+ overlay : function overlay(ctx){
+ this.pathmap.reduce(function(acc, v, r, tree){
+ if ( acc[r.id] || v.isBoundary )
+ return acc;
+ acc[r.id] = r;
+ ctx.beginPath();
+ ctx.rect(r.x1,r.y1, r.width,r.height);
+ ctx.fill();
+ ctx.stroke();
+ ctx.closePath();
+ return acc;
+ }, {});
+ },
+
+
+ /**
+ * Wraps PathMap.gridPath() to draw AI pathes.
+ */
+ gridPathWithUI : function gridPathWithUI(start, end, agent){
+ var size = this.gridSquare, floor = Math.floor
+ , grid = this.pathmap.grid()
+
+ , startX = floor(start.x/size)
+ , startY = floor(start.y/size)
+ , startN = grid[startX][startY]
+
+ , endX = floor(end.x/size)
+ , endY = floor(end.y/size)
+ , endN = grid[endX][endY]
+
+ , path = Y(astar.search(grid, startN, endN))
+ ;
+
+ if (this.overlayAiPaths)
+ this.drawPath(agent, startN, path);
+
+ return path;
+ },
+
+ monitorAgent : function monitorAgent(agent){
+ if ( agent && !this.hasPath(agent.id) )
+ agent.addEventListener('destroy', this.cleanUpAgent);
+ },
+
+ cleanUpAgent : function cleanUpAgent(evt){ this.destroyPath(evt.target.id); },
+
+ getPath : function getPath(id){ return $('.path.agent_'+id, this.layer); },
+ hasPath : function hasPath(id){ return !!this.getPath(id).length; },
+ showPaths : function showPaths(){ $('.path', this.layer).show(); },
+ hidePaths : function hidePaths(){ $('.path', this.layer).hide(); },
+ destroyPath : function destroyPath(id){ this.getPath(id).remove(); },
+
+ drawPath : function drawPath(agent, start, path){
+ this.monitorAgent(agent);
+
+ var id = agent.id
+ , w = this.layerWidth, h = this.layerHeight
+ , canvas = this.getPath(id)
+ ;
+
+ if (!canvas.length) {
+ canvas = $('<canvas class="path agent_'+id+'" style="position:absolute;top:0;left:0;"/>').appendTo(this.layer);
+ canvas.width(w).height(h);
+ canvas[0].width = w;
+ canvas[0].height = h;
+ }
+
+ canvas.hide();
+
+ var ctx = canvas[0].getContext('2d');
+ ctx.lineWidth = 0;
+
+ // Clear the canvas
+ ctx.beginPath();
+ ctx.clearRect(0,0, w,h);
+ ctx.closePath();
+
+ // Draw the path
+ ctx.fillStyle = 'rgba(0,0,0,0.1)';
+ this._drawPathStep(ctx, start);
+ path.reduce(this._drawPathStep, ctx);
+
+ // Draw start (concentric ring)
+ ctx.fillStyle = 'rgba(0,255,0,0.05)';
+ this._drawPathStep(ctx, start, -1);
+
+ // Draw finish
+ ctx.fillStyle = 'rgba(0,0,255,0.05)';
+ this._drawPathStep(ctx, path.last());
+
+ canvas.show();
+ },
+
+ _drawPathStep : function drawPathStep(ctx, p, idx){
+ var size = this.gridSquare
+ , off = size*(idx === -1 ? 0.15 : 0.3)
+ , r = size/2 - off
+ , x = p.x*size + off + r
+ , y = p.y*size + off + r ;
+
+ ctx.beginPath();
+ ctx.arc(x,y, r, 0, Math.PI*2, false);
+ ctx.fill();
+ ctx.closePath();
+
+ return ctx;
+ }
+
+
+});
+
+
+config.updateOnChange(
+ ['pathing.gridSquare', 'pathing.overlayPathmap', 'pathing.overlayAiPaths'],
+ PathMapUI.fn);
+
<script src="build/ezl/util/binaryheap.js" type="text/javascript"></script>
<script src="build/ezl/util/astar.js" type="text/javascript"></script>
<script src="build/ezl/util/tree/quadtree.js" type="text/javascript"></script>
-<script src="build/Y/modules/y.config.js" type="text/javascript"></script>
<script src="build/Y/modules/y.scaffold.js" type="text/javascript"></script>
+<script src="build/Y/modules/y.config.js" type="text/javascript"></script>
<script src="build/Y/modules/y.cookies.js" type="text/javascript"></script>
<script src="build/tanks/config.js" type="text/javascript"></script>
<script src="build/tanks/ui/configui.js" type="text/javascript"></script>
<script src="build/tanks/thing/thing.js" type="text/javascript"></script>
<script src="build/tanks/map/trajectory.js" type="text/javascript"></script>
-<script src="build/tanks/ui/grid.js" type="text/javascript"></script>
<script src="build/tanks/map/wall.js" type="text/javascript"></script>
+<script src="build/tanks/thing/item.js" type="text/javascript"></script>
+<script src="build/tanks/ui/grid.js" type="text/javascript"></script>
<script src="build/tanks/fx/explosion.js" type="text/javascript"></script>
<script src="build/tanks/thing/bullet.js" type="text/javascript"></script>
<script src="build/tanks/thing/tank.js" type="text/javascript"></script>
<script src="build/tanks/map/pathmap.js" type="text/javascript"></script>
+<script src="build/tanks/ui/pathmapui.js" type="text/javascript"></script>
<script src="build/tanks/thing/player.js" type="text/javascript"></script>
<script src="build/tanks/map/level.js" type="text/javascript"></script>
<script src="build/tanks/thing.js" type="text/javascript"></script>