test:
name: Da Test
desc: A test level.
- width: 500
- height: 500
+ levelSize: 500 500
bounds:
- [-50,-50, 600,50]
- [-50,0, 50,500]
three_test:
name: Three Test
desc: A more complex test level.
- width: 500
- height: 500
+ levelSize: 500 500
# bounds: [[-1,-1], [501,-1], [501,351], [351,351], [351,501], [-1,501]]
# bounds:
# top: [[-50,-50, 600,50]]
autotest:
name: Automated AI Test
desc: AI test level.
- width: 500
- height: 500
+ levelSize: 500 500
bounds:
- [-50,-50, 600,50]
- [-50,0, 50,500]
los_test:
name: Line-of-Sight Test
desc: LOS test level.
- width: 450
- height: 450
+ levelSize: 450 450
bounds:
- [-50,-50, 550,50]
- [-50,0, 50,450]
, proto = cls.fn // use prototype so super() calls pick up the right lists
, binds = proto.__bind__ // list of names to bind
, inits = proto.__inits__ // init functions
- , super = cls.__super__
, args = Y(arguments)
;
if ( inits && inits.length )
inits.invoke('apply', instance, args);
- else if (super && super.init )
- super.init.apply(instance, args);
+ else if ( cls.__bases__ ) {
+ var superInits = super(cls, '__inits__');
+ if ( superInits && superInits.length )
+ superInits.invoke('apply', instance, args);
+ }
instance.emit('init', instance, {
'instance' : instance,
}
+/**
+ * Looks up a key on the first prototype in the chain.
+ * @param {Class} cls Class on which to perform lookup.
+ * @param {String} key Key to lookup.
+ * @return {*} First non-undefined value found in the prototypes of the __bases__ chain. (nb. This does not include cls.)
+ */
+function super(cls, key){
+ if ( !key )
+ return;
+ if ( !(cls instanceof Class) && cls.__class__ )
+ return super(cls.__class__, key); // cls = cls.__class__;
+ return cls.__bases__ // don't check cls.fn[key] -- we want super only
+ .reduce(function(v, base){
+ return (v !== undefined ? v : base.prototype[key]);
+ }, undefined);
+}
+
+/**
+ * Aggregate the objects up the prototypes in the __bases__ chain, merging together their properties
+ * such that the deepest subclasses supersceede their parents.
+ * @param {Class} cls Class on which to perform lookup.
+ * @param {String} key Key to lookup.
+ * @return {Object} Combined values from objects at key found in the prototypes of the __bases__ chain. (nb. This does not include cls.)
+ */
function aggregate(cls, key){
if ( !(cls instanceof Class) && cls.__class__ )
cls = cls.__class__;
}, {});
}
+/**
+ * Looks up a key on the Classes in the __bases__ chain.
+ * @param {Class} cls Class on which to perform lookup.
+ * @param {String} key Key to lookup.
+ * @return {*} First non-undefined value found in the __bases__ chain. (nb. This does not include cls.)
+ */
+function superStatic(cls, key){
+ if ( !(cls instanceof Class) && cls.__class__ )
+ return superStatic(cls.__class__, key); // cls = cls.__class__;
+ return cls.__bases__ // don't check cls[key] -- we want super only
+ .reduce(function(v, base){
+ return (v !== undefined ? v : base[key]);
+ }, undefined);
+}
+
Mixin.on('subclass',
function onMixinSubclass(evt){
exports['lookupClass'] = lookupClass;
exports['mixin'] = mixin;
exports['aggregate'] = aggregate;
+exports['superStatic'] = superStatic;
Layer =
exports['Layer'] =
evt.subclass('Layer', {
- __mixins__ : [ Layerable ],
- __bind__ : [],
-
-
- init : function initLayer(props, attrs, html){
- // Layer.init.call(this, props, attrs, html);
- }
+ __mixins__ : [ Layerable ]
})
;
/** Position relative to document. */
offset : makeDelegate('offset'),
+ scrollLeft : makeDelegate('scrollLeft'),
+ scrollTop : makeDelegate('scrollTop'),
+ scrollWidth : function scrollWidth(v){ return this.layer.attr('scrollWidth', v); },
+ scrollHeight : function scrollHeight(v){ return this.layer.attr('scrollHeight', v); },
/// Transformations ///
/// Misc ///
toString : function(){
- var pos = ((this.layer.length && this.layer.parent()[0]) ? this.position() : {top:NaN, left:NaN});
- return this.className+'['+pos.left+','+pos.top+']( children='+this.children.size()+' )';
+ var pos = { 'top':NaN, 'left':NaN }
+ , kids = this.children ? this.children.length : 0
+ , el = this.layer
+ ;
+ // Check we're in the DOM, otherwise jQuery vomits
+ if ( el && el.attr('parentNode') )
+ pos = this.position();
+ return this.className+'['+pos.left+','+pos.top+']( children='+kids+' )';
}
});
, EventLoop = require('ezl/loop').EventLoop
, Layer = require('ezl/layer/layer').Layer
+, HtmlLayer = require('ezl/layer/html').HtmlLayer
, Circle = require('ezl/shape').Circle
, config = require('tanks/config').config
, Level = require('tanks/map/level').Level
, Grid = require('tanks/ui/grid').Grid
, PathMapUI = require('tanks/ui/pathmapui').PathMapUI
+, Viewport = require('tanks/ui/viewport').Viewport
, Backpack = require('tanks/ui/inventory/backpack').Backpack
, Thing = thing.Thing
this.loop = new EventLoop(FRAME_RATE, this);
this.root =
- new Layer({ hasCanvas:false }, { 'id':'game' })
+ new HtmlLayer({ '_layerId':'game' })
.prependTo( $('body') );
this.ui =
- new Layer({ hasCanvas:false }, { 'id':'ui' })
+ new HtmlLayer({ '_layerId':'ui' })
.appendTo( this.root );
this.viewport =
- new Layer({ hasCanvas:false }, { 'id':'viewport' })
+ new Viewport({ '_layerId':'viewport' })
.appendTo( this.root );
this.level =
Level.create(this.levelId, this, CAPACITY, REF_SIZE)
this.grid =
new Grid(this.level, REF_SIZE)
.prependTo( this.viewport );
- this.viewport
- .width(this.level.width)
- .height(this.level.height);
this.map = this.level.map;
this.map.ui = new PathMapUI(this, this.map);
this.on('tick', this.tick);
this.level.setup(tanks.data);
+ this.viewport.centerOn();
+
this.emit('ready', this);
},
, min = Y(Math.min).limit(2)
, max = Y(Math.max).limit(2)
+, toInt = Y(parseInt).limit(1)
,
init : function init(game, capacity, buffer_size){
this.game = game;
- this.map = new Map(0,0, this.width, this.height, capacity, buffer_size);
+ this.map = new Map(0,0, this.levelWidth, this.levelHeight, capacity, buffer_size);
- Rect.init.call(this, this.width,this.height);
+ Rect.init.call(this, this.levelWidth,this.levelHeight);
this.fill('transparent');
- // var shape = this.shape = new Rect(this.width,this.height).fill('transparent');
+ // var shape = this.shape = new Rect(this.levelWidth,this.levelHeight).fill('transparent');
// shape.layer.attr('class', this._layerClasses);
// this.bbox = shape.bbox;
},
});
-proxy({ target:Level, donor:Rect, context:'shape', chain:true,
- names:'append appendTo remove invoke clear'.split(' ') });
+// proxy({ target:Level, donor:Rect, context:'shape', chain:true,
+// names:'append appendTo remove invoke clear'.split(' ') });
Level.on('speciate',
function onSpeciate(evt){
var NewLevel = evt.data.species
, proto = NewLevel.fn
, bounds = Y(proto.bounds)
+ , size = proto.levelSize
;
- if (!proto.width) {
- proto.width =
+ if (size) {
+ size = size.split(' ').map(toInt);
+ proto.levelWidth = size[0];
+ proto.levelHeight = size[1];
+ }
+
+ if (!proto.levelWidth) {
+ proto.levelWidth =
bounds.right.pluck(0).reduce(max,0)
- bounds.left.pluck(0).reduce(min,Infinity);
}
- if (!proto.height) {
- proto.height =
+ if (!proto.levelHeight) {
+ proto.levelHeight =
bounds.bottom.pluck(1).reduce(max,0)
- bounds.top.pluck(1).reduce(min,Infinity);
}
init : function init(level, side){
this.level = level;
- this.cols = Math.round(level.width/side);
- this.rows = Math.round(level.height/side);
+ this.cols = Math.round(level.levelWidth/side);
+ this.rows = Math.round(level.levelHeight/side);
this.side = side;
Rect.init.call(this, this.cols*side, this.rows*side);
},
var Y = require('Y').Y;
-
-exports['PathMapUI'] = require('tanks/ui/pathmapui').PathMapUI;
-exports['main'] = require('tanks/ui/main');
-exports['config'] = require('tanks/ui/configui');
-exports['inventory'] = require('tanks/ui/inventory');
\ No newline at end of file
+Y.core.extend(exports, {
+ 'config' : require('tanks/ui/configui'),
+ 'main' : require('tanks/ui/main'),
+ 'inventory' : require('tanks/ui/inventory'),
+
+ 'Grid' : require('tanks/ui/grid').Grid,
+ 'Viewport' : require('tanks/ui/viewport').Viewport,
+ 'PathMapUI' : require('tanks/ui/pathmapui').PathMapUI
+});
var level = game.level
, b = this.buffer = map.buffer_size;
- Rect.init.call(this, level.width, level.height);
+ Rect.init.call(this, level.levelWidth, level.levelHeight);
// this.posBleed.x = this.posBleed.y =
// this.negBleed.x = this.negBleed.y = b;
--- /dev/null
+var Y = require('Y').Y
+, Vec = require('ezl/math/vec').Vec
+, HtmlLayer = require('ezl/layer/html').HtmlLayer
+
+, PAD_X = 1024, PAD_Y = 690
+, min = Y(Math.min).limit(2)
+, max = Y(Math.max).limit(2)
+,
+
+Viewport =
+exports['Viewport'] =
+HtmlLayer.subclass('Viewport', {
+ _layerId : 'viewport',
+
+ spacer : null, // inner layer to induce scrolling so we can arbitrarily center the elements
+
+
+
+ init : function initViewport(props, attrs, html){
+ HtmlLayer.init.call(this, props, attrs, html);
+ this.spacer = $('<div class="spacer ezl layer" />').appendTo(this.layer);
+ },
+
+ get contentWidth(){ return this.children.pluck('layerWidth').reduce(max,0); },
+ get contentHeight(){ return this.children.pluck('layerHeight').reduce(max,0); },
+
+ /**
+ * Sets the current scroll position of the Viewport. (Unlike the DOM, Viewport has 50% negative scroll-space top and left.)
+ * @param {Number} x Left offset in pixels.
+ * @param {Number} y Top offset in pixels.
+ * @return {this}
+ */
+ /**
+ * Gets the current scroll position of the Viewport.
+ * @return {Vec} Current scroll position (left, top).
+ */
+ scroll : function scroll(x,y){
+ var el = this.layer;
+ if (arguments.length === 0)
+ return new Vec(el.scrollLeft()-PAD_X, el.scrollTop()-PAD_Y);
+
+ if (x instanceof Array) { y=x[1]; x=x[0]; }
+
+ if (x !== undefined) el.scrollLeft( x + PAD_X );
+ if (y !== undefined) el.scrollTop( y + PAD_Y );
+
+ return this;
+ },
+
+
+ /**
+ * Scrolls the viewport to place (x,y) at the center.
+ * Note: Viewport can be centered on values ranging from -100% to +100% of width and height.
+ * @param {Number} [x=undefined] Desired center x-coordinate. If undefined, half contentWidth will be used.
+ * @param {Number} [y=undefined] Desired center y-coordinate. If undefined, half contentHeight will be used.
+ * @return {this}
+ */
+ centerOn : function centerOn(x,y){
+ if (x === undefined) x = this.contentWidth/2;
+ if (y === undefined) y = this.contentHeight/2;
+ var el = this.layer
+ , w2 = el.width()/2, h2 = el.height()/2;
+ return this.scroll(x-w2, y-h2);
+ },
+
+
+ /// Debug ///
+
+ createGrid : function createGrid(step, maxX, maxY){
+ step = step || 50;
+ maxX = maxX || _X*3;
+ maxY = maxY || _Y*3;
+
+ // $('.chrome').hide();
+ $('#pen, #mapblotter').remove();
+
+ var map = $('#viewport .map');
+ $('<div id="mapblotter" class="ezl layer blotter" />')
+ .css({
+ 'background-color': '#3F3F3F', 'z-index':-1,
+ 'width':map.width(), 'height':map.height()
+ })
+ .appendTo(this.layer);
+ var pen = $('<div id="pen" />')
+ .css({
+ 'position': 'absolute',
+ 'left': 0, 'top': 0, 'margin': 0,
+ 'z-index':-10
+ })
+ .appendTo(this.layer);
+
+ Y(0,maxX,step).forEach(function(x, xth){
+ Y(0,maxY,step).forEach(function(y, yth){
+ var isColored = (xth % 2) == (yth % 2);
+ $('<div class="sq">'+x+','+y+'</div>')
+ .css({
+ 'opacity': 0.05,
+ 'position': 'absolute', 'display':'table-cell',
+ 'left': x, 'top': y,
+ 'width': step, 'height': step,
+ 'margin': 0, 'padding': 0,
+ 'font' : '8px/'+step+'px Menlo', 'text-align' : 'center',
+ 'color' : (isColored ? '#000' : '#fff'),
+ 'background-color': (isColored ? '#fff' : 'transparent')
+ })
+ .appendTo(pen)
+ ;
+ });
+ });
+
+ return this;
+ }
+
+})
+;
\ No newline at end of file
-html, body { width:100%; height:100%; top:0; left:0; margin:0; padding:0; background-color:#3F3F3F; }
+html, body { width:100%; height:100%; top:0; left:0; margin:0; padding:0; background-color:#333; }
body { position:absolute; font-family:Geogrotesque,Helvetica; font-size:12pt; color:#fff; }
h1, h2, h3, h4, h5 { margin:0.75em 0; font-weight:normal; }
ul, ol, li { list-style: none ! important; margin:0; padding:0; }
#notes li { margin-left:1em; }
-#game { position:absolute; width:100%; height:100%; margin:0; padding:0; }
-#viewport { position:relative; margin:1em auto; cursor:crosshair; width:auto; /* width:500px; height:500px; overflow:hidden; top:50px; left:50px; */ }
- #viewport .layer.grid { outline:1px solid rgba(255,255,255,0.1); }
+/*** Game and Viewport ***/
+
+#game { position:relative; width:100%; height:100%; margin:0; padding:0; }
+#viewport { position:relative; width:100%; height:100%; max-width:1024px; max-height:690px;
+ overflow:hidden; cursor:crosshair; margin:0 auto; background-color:#3F3F3F; }
+ #viewport > .layer { margin:690px 1024px; }
+ #viewport > .spacer { position:absolute; z-index:-1; width:1024px; height:690px; /* width:100%; height:100%; */ }
+ #viewport .layer.grid { outline:1px solid rgba(255,255,255, 0.1); }
+ #viewport .ezl.layer.map { }
+ #viewport .ezl.layer.pathmap { z-index:50; }
+
+
.item-container { position:absolute; }
.item-container h3 { color:#fff; margin:0.25em; padding:0.5em 0; font-size:0.8em; }
.item-container .slot { float:left; position:relative; width:50px; height:50px; margin:0.25em; border:1px solid transparent; }
.item-container .slot.drophover { border:1px solid #FFF6AE; }
-#ui { position:absolute; bottom:0; right:0; left:auto; top:auto; width:auto; }
+#ui { position:absolute; bottom:0; right:0; left:auto; top:auto; width:auto; z-index:500; }
#ui > .bag { position:relative; float:right; margin:0 0 1em 1em; }
#ui .equipslot .slot { background-position:center; background-repeat:no-repeat; }
#ui .equipslot .slot.occupied { background-image:none; }
margin:0; padding:0; white-space:nowrap; overflow:hidden; }
.showGridCoords table.grid td:hover { color:rgba(255,255,255,0.1); }
-.ezl.layer.pathmap { z-index:50; }
-
/*
#debug { position:relative; top:1em; right:1em; z-index:5000; }
#debug .inner { position:absolute; top:0; right:0; padding:1em; }
-<div id="loading" class="bigblue">
+<div id="loading" class="bigblue chrome">
<div class="box hud">
<h1>The Littlest Battletank</h1>
</div>
</div>
-<div id="welcome" class="bigblue" style="display:none">
+<div id="welcome" class="bigblue chrome" style="display:none">
<div class="box hud">
<h1>The Littlest Battletank</h1>