1
0
Fork 0

jshint integration and error fixes

master
Mike Gerwitz 2015-12-23 23:59:29 -05:00
commit 3c2790791d
No known key found for this signature in database
GPG Key ID: F22BB8158EE30EAB
27 changed files with 648 additions and 539 deletions

20
.jshintrc 100644
View File

@ -0,0 +1,20 @@
{
"eqeqeq": true,
"esnext": true,
"forin": true,
"freeze": true,
"futurehostile": true,
"latedef": true,
"laxbreak": true,
"maxcomplexity": 100,
"maxdepth": 3,
"maxparams": 5,
"noarg": true,
"nocomma": true,
"node": true,
"nonbsp": true,
"nonew": true,
"undef": true,
"unused": true,
"varstmt": true
}

View File

@ -31,10 +31,16 @@ modindex: $(nsindex)
standalone: lasertank.js standalone: lasertank.js
lasertank.js: modindex lasertank.js: modindex
./node_modules/.bin/browserify -s lasertank --debug src/index.js > "$@" ./node_modules/.bin/browserify \
-t strictify \
-s lasertank \
--debug \
src/index.js \
> "$@"
test: check test: check
check: check:
jshint src/ scripts/
@PATH="$(PATH):$(CURDIR)/node_modules/mocha/bin" \ @PATH="$(PATH):$(CURDIR)/node_modules/mocha/bin" \
mocha --recursive $(TESTARGS) mocha --recursive $(TESTARGS)

View File

@ -20,7 +20,8 @@
"devDependencies": { "devDependencies": {
"chai": ">=1.9.1", "chai": ">=1.9.1",
"mocha": ">=1.18.2", "mocha": ">=1.18.2",
"browserify": "~12" "browserify": "~12",
"strictify": "~0.2"
}, },
"licenses": [ "licenses": [

View File

@ -17,21 +17,30 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
( function() ( function()
{ {
/* jshint browser:true */
/* global lasertank */
var load_ltg = lasertank.FileLoader( document.getElementById( 'ltg' ) ), const ltg_input = document.getElementById( 'ltg' ),
load_lvl = lasertank.FileLoader( document.getElementById( 'lvl' ) ), lvl_input = document.getElementById( 'lvl' ),
menu_bar = lasertank.ui.MenuBar( document.getElementById( 'menubar' ) ), load_ltg = lasertank.FileLoader( ltg_input, new window.FileReader() ),
load_lvl = lasertank.FileLoader( lvl_input, new window.FileReader() ),
ele_game = document.getElementById( 'game' ), ele_game = document.getElementById( 'game' ),
ctx = document.getElementById( 'render' ).getContext( '2d' ), ctx = document.getElementById( 'render' ).getContext( '2d' );
ltg_data = '', let ltg_data = '',
lvl_data = ''; lvl_data = '';
// XXX: relies on side-effects of ctor
lasertank.ui.MenuBar( document.getElementById( 'menubar' ) );
load_ltg.onLoad( function( e, data ) load_ltg.onLoad( function( e, data )
{ {
if ( e ) throw e; if ( e ) throw e;
@ -40,6 +49,7 @@ load_ltg.onLoad( function( e, data )
gamechk(); gamechk();
} ); } );
load_lvl.onLoad( function( e, data ) load_lvl.onLoad( function( e, data )
{ {
if ( e ) throw e; if ( e ) throw e;
@ -48,6 +58,7 @@ load_lvl.onLoad( function( e, data )
gamechk(); gamechk();
} ); } );
function gamechk() function gamechk()
{ {
if ( !( ltg_data && lvl_data ) ) return; if ( !( ltg_data && lvl_data ) ) return;
@ -55,13 +66,14 @@ function gamechk()
// temporary // temporary
if ( ele_game.className.search( 'opening' ) > -1 ) return; if ( ele_game.className.search( 'opening' ) > -1 ) return;
lasertank.ClassicGame( ltg_data, lvl_data ) lasertank.ClassicGame( document, ltg_data, lvl_data )
.on( 'ready', function() .on( 'ready', function()
{ {
this.renderTo( ctx ); this.renderTo( ctx, window );
} ); } );
} }
// temporary // temporary
document.getElementById( 'new' ).onclick = function() document.getElementById( 'new' ).onclick = function()
{ {

View File

@ -17,5 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/* jshint browser:true */
// alert the user on all uncaught errors // alert the user on all uncaught errors
window.onerror = alert; window.onerror = window.alert;

View File

@ -18,18 +18,19 @@
*/ */
var Class = require( 'easejs' ).Class, const Class = require( 'easejs' ).Class,
Game = require( './Game' ), Game = require( './Game' ),
ClassicGameObjectFactory = require( './ClassicGameObjectFactory' ), ClassicGameObjectFactory = require( './ClassicGameObjectFactory' ),
ClassicMap = require( './ClassicMap' ),
ClassicTileDfn = require( './ClassicTileDfn' ), ClassicTileDfn = require( './ClassicTileDfn' ),
LtgLoader = require( './LtgLoader' ), LtgLoader = require( './LtgLoader' ),
MapBounds = require( './MapBounds' ), ClassicLevel = require( './level/ClassicLevel' ),
MapRender = require( './MapRender' ), LevelBounds = require( './level/LevelBounds' ),
MapSet = require( './MapSet' ), LevelRender = require( './level/LevelRender' ),
MapState = require( './MapState' ), LevelSet = require( './level/LevelSet' ),
LevelState = require( './level/LevelState' ),
TileMasker = require( './TileMasker' ); TileMasker = require( './TileMasker' );
/** /**
* Facade for the classic (original) game of LaserTank * Facade for the classic (original) game of LaserTank
*/ */
@ -56,10 +57,10 @@ module.exports = Class( 'ClassicGame' )
'private _tileSet': null, 'private _tileSet': null,
/** /**
* Set of available maps * Set of available levels
* @type {MapSet} * @type {LevelSet}
*/ */
'private _mapSet': null, 'private _levelSet': null,
/** /**
* Event handlers * Event handlers
@ -68,8 +69,8 @@ module.exports = Class( 'ClassicGame' )
'private _callback': {}, 'private _callback': {},
/** /**
* Performs map rendering * Performs level rendering
* @type {MapRender} * @type {LevelRender}
*/ */
'private _render': null, 'private _render': null,
@ -83,26 +84,31 @@ module.exports = Class( 'ClassicGame' )
/** /**
* Initialize game with LTG and LVL data * Initialize game with LTG and LVL data
* *
* The LTG and LVL data can be changed at any time, but are required in the * The LTG and LVL data can be changed at any time, but are required in
* constructor because they are needed in order for the game to be * the constructor because they are needed in order for the game to be
* functional. * functional.
* *
* DOCUMENT is used internally for creating elements; the DOM will not
* be manipulated.
*
* @param {HTMLDocument} document DOM document
*
* @param {string} ltg_data binary string containing LTG file data * @param {string} ltg_data binary string containing LTG file data
* @param {string} lvl_data binary string containing LVL file data * @param {string} lvl_data binary string containing LVL file data
*/ */
__construct: function( ltg_data, lvl_data ) __construct: function( document, ltg_data, lvl_data )
{ {
var _self = this; const _self = this;
this._ltgLoader = LtgLoader(); this._ltgLoader = LtgLoader();
this._masker = TileMasker( ClassicTileDfn() ); this._masker = TileMasker( ClassicTileDfn(), document );
this._gameObjFactory = ClassicGameObjectFactory(); this._gameObjFactory = ClassicGameObjectFactory();
// load initial tile and map data from the LTG and LVL data // load initial tile and level data from the LTG and LVL data
this.setTileData( ltg_data, function() this.setTileData( ltg_data, function()
{ {
this.setMapData( lvl_data, function() this.setLevelData( lvl_data, function()
{ {
_self._trigger( 'ready' ); _self._trigger( 'ready' );
} ); } );
@ -113,32 +119,35 @@ module.exports = Class( 'ClassicGame' )
/** /**
* Render to the given 2d canvas context * Render to the given 2d canvas context
* *
* EVENT_TARGET will be monitored for keypresses.
*
* @param {CanvasRenderingContext2d} ctx 2d canvas context * @param {CanvasRenderingContext2d} ctx 2d canvas context
* @param {*} event_target keyboard event target
* *
* @return {ClassicGame} self * @return {ClassicGame} self
*/ */
'public renderTo': function( ctx ) 'public renderTo': function( ctx, event_target )
{ {
// if there is a previous renderer, free its canvas before continuing // if there is a previous renderer, free its canvas before
// (to both clean up and to free any locks, allowing for tile set and // continuing (to both clean up and to free any locks, allowing for
// map changes) // tile set and level changes)
if ( this._render ) if ( this._render )
{ {
this._render.clearCanvas(); this._render.clearCanvas();
} }
var map = this._mapSet.getMapByNumber( 1 ), const level = this._levelSet.getLevelByNumber( 1 ),
map_state = MapState( map, this._gameObjFactory ), level_state = LevelState( level, this._gameObjFactory ),
bounds = MapBounds( map ); bounds = LevelBounds( level );
// render the first map (hardcoded for now) // render the first level (hardcoded for now)
this._render = MapRender( ctx, this._tileSet ) this._render = LevelRender( ctx, this._tileSet )
.render( map, map_state ); .render( level, level_state );
// POC // POC
window.onkeydown = function( event ) event_target.onkeydown = function( event )
{ {
var dir; let dir;
switch ( event.keyCode ) switch ( event.keyCode )
{ {
@ -153,7 +162,8 @@ module.exports = Class( 'ClassicGame' )
return; return;
} }
map_state.movePlayer( dir, bounds ); event.preventDefault();
level_state.movePlayer( dir, bounds );
}; };
return this; return this;
@ -171,7 +181,7 @@ module.exports = Class( 'ClassicGame' )
'public setTileData': function( data, callback ) 'public setTileData': function( data, callback )
{ {
// get tile metadata // get tile metadata
var _self = this, const _self = this,
meta = this._ltgLoader.fromString( data ); meta = this._ltgLoader.fromString( data );
this._masker.getMaskedTiles( meta.tiles, meta.mask, function( tdata ) this._masker.getMaskedTiles( meta.tiles, meta.mask, function( tdata )
@ -185,16 +195,16 @@ module.exports = Class( 'ClassicGame' )
/** /**
* Set LVL data for maps * Set LVL data for levels
* *
* @param {string} data binary string containing LVL data * @param {string} data binary string containing LVL data
* @param {function()} callback function to call when complete * @param {function()} callback function to call when complete
* *
* @return {ClassicGame} self * @return {ClassicGame} self
*/ */
'public setMapData': function( data, callback ) 'public setLevelData': function( data, callback )
{ {
this._mapSet = MapSet( data, ClassicMap ); this._levelSet = LevelSet( data, ClassicLevel );
callback.call( this.__inst ); callback.call( this.__inst );
return this; return this;

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Class = require( 'easejs' ).Class, const Class = require( 'easejs' ).Class,
GameObjectFactory = require( './GameObjectFactory' ), GameObjectFactory = require( './GameObjectFactory' ),
GameObject = require( './gameobjs/GameObject' ), GameObject = require( './gameobjs/GameObject' ),
Tank = require( './gameobjs/Tank' ); Tank = require( './gameobjs/Tank' );

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Class = require( 'easejs' ).Class, const Class = require( 'easejs' ).Class,
TileDfn = require( './TileDfn' ); TileDfn = require( './TileDfn' );

View File

@ -21,7 +21,7 @@
* specs, as it depends on FileReader. * specs, as it depends on FileReader.
*/ */
var Class = require( 'easejs' ).Class; const Class = require( 'easejs' ).Class;
/** /**
@ -49,8 +49,9 @@ module.exports = Class( 'FileLoader',
* Initialize file loader, monitoring the given file element * Initialize file loader, monitoring the given file element
* *
* @param {HtmlInputElement} element file element to monitor * @param {HtmlInputElement} element file element to monitor
* @param {FileReader} reader file reader
*/ */
__construct: function( element ) __construct: function( element, reader )
{ {
if ( element.type !== 'file' ) if ( element.type !== 'file' )
{ {
@ -58,6 +59,7 @@ module.exports = Class( 'FileLoader',
} }
this._element = element; this._element = element;
this._reader = reader;
}, },
@ -100,23 +102,23 @@ module.exports = Class( 'FileLoader',
*/ */
'private _loadFile': function( event ) 'private _loadFile': function( event )
{ {
var _self = this, const _self = this,
files = event.target.files; files = event.target.files;
if ( files.length === 0 ) return; if ( files.length === 0 ) return;
var reader = new FileReader(); this._reader.onload = function( revent )
reader.onload = function( revent )
{ {
_self._callback.call( this.__inst, null, revent.target.result ); _self._callback.call( this.__inst, null, revent.target.result );
}; };
reader.onerror = function( e )
this._reader.onerror = function( e )
{ {
_self._callback.call( this.__inst, e ); _self._callback.call( this.__inst, e );
}; };
// load file // load file
reader.readAsBinaryString( files[ 0 ] ); this._reader.readAsBinaryString( files[ 0 ] );
} }
} ); } );

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Interface = require( 'easejs' ).Interface; const Interface = require( 'easejs' ).Interface;
/** /**
@ -50,14 +50,14 @@ module.exports = Interface( 'Game',
/** /**
* Set LVL data for maps * Set LVL data for levels
* *
* @param {string} data binary string containing LVL data * @param {string} data binary string containing LVL data
* @param {function()} callback function to call when complete * @param {function()} callback function to call when complete
* *
* @return {Game} self * @return {Game} self
*/ */
'public setMapData': [ 'data', 'callback' ], 'public setLevelData': [ 'data', 'callback' ],
/** /**

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Interface = require( 'easejs' ).Interface; const Interface = require( 'easejs' ).Interface;
module.exports = Interface( 'GameObjectFactory', module.exports = Interface( 'GameObjectFactory',

View File

@ -42,7 +42,10 @@
* identify the file as a bitmap image.) * identify the file as a bitmap image.)
*/ */
var Class = require( 'easejs' ).Class; /* XXX: remove me! */
/* globals btoa */
const Class = require( 'easejs' ).Class;
/** /**
@ -73,7 +76,7 @@ module.exports = Class( 'LtgLoader',
*/ */
'public fromString': function( ltg_data ) 'public fromString': function( ltg_data )
{ {
var mask_offset = this._getMaskOffsetFromData( ltg_data ); const mask_offset = this._getMaskOffsetFromData( ltg_data );
return { return {
name: this._getNameFromData( ltg_data ), name: this._getNameFromData( ltg_data ),
@ -112,7 +115,7 @@ module.exports = Class( 'LtgLoader',
sgmt = this.__self.$( sgmt ); sgmt = this.__self.$( sgmt );
} }
var data = String.prototype.substr.apply( ltg_data, sgmt ); const data = String.prototype.substr.apply( ltg_data, sgmt );
return ( stripnull ) return ( stripnull )
? data.split( '\x00' )[ 0 ] ? data.split( '\x00' )[ 0 ]
@ -187,8 +190,9 @@ module.exports = Class( 'LtgLoader',
// grab the data and don't bother stripping off the null bytes (it would // grab the data and don't bother stripping off the null bytes (it would
// function the same with them stripped, but let's avoid the confusion // function the same with them stripped, but let's avoid the confusion
// since we are supposed to be working with a 32-bit value) // since we are supposed to be working with a 32-bit value)
var data = this._getDataSegment( ltg_data, '_POS_MOFF', false ), const data = this._getDataSegment( ltg_data, '_POS_MOFF', false );
i = data.length,
let i = data.length,
offset = 0; offset = 0;
// convert the DWORD entry (little-endian format, 32-bit) into an // convert the DWORD entry (little-endian format, 32-bit) into an
@ -234,7 +238,7 @@ module.exports = Class( 'LtgLoader',
*/ */
'private _getGameBitmap': function( ltg_data, mask_offset ) 'private _getGameBitmap': function( ltg_data, mask_offset )
{ {
var bmp_offset = this.__self.$( '_OFFSET_HEADER_END' ); const bmp_offset = this.__self.$( '_OFFSET_HEADER_END' );
// return the bitmap data up until the mask offset // return the bitmap data up until the mask offset
return ltg_data.substr( bmp_offset, ( mask_offset - bmp_offset ) ); return ltg_data.substr( bmp_offset, ( mask_offset - bmp_offset ) );

View File

@ -75,7 +75,7 @@
* thinice - thin ice * thinice - thin ice
*/ */
var Interface = require( 'easejs' ).Interface; const Interface = require( 'easejs' ).Interface;
/** /**

View File

@ -63,7 +63,7 @@
* rendering. * rendering.
*/ */
var Class = require( 'easejs' ).Class, const Class = require( 'easejs' ).Class,
TileDfn = require( './TileDfn' ); TileDfn = require( './TileDfn' );
/** /**
@ -77,6 +77,12 @@ module.exports = Class( 'TileMasker',
*/ */
'private _context': null, 'private _context': null,
/**
* DOM document
* @type {HTMLDocument}
*/
'private _document': null,
/** /**
* Tile definition to use for all operations * Tile definition to use for all operations
* @type {Array.<Array.<string,number>>} * @type {Array.<Array.<string,number>>}
@ -121,9 +127,13 @@ module.exports = Class( 'TileMasker',
* allows us to support *any* type of tile set -- not just those that are * allows us to support *any* type of tile set -- not just those that are
* defined by the original game. * defined by the original game.
* *
* DOCUMENT is used internally for creating elements; the DOM will not
* be manipulated.
*
* @param {TileDfn} tile_dfn tile definition object * @param {TileDfn} tile_dfn tile definition object
* @param {HTMLDocument} document DOM document
*/ */
__construct: function( tile_dfn ) __construct: function( tile_dfn, document )
{ {
if ( !( Class.isA( TileDfn, tile_dfn ) ) ) if ( !( Class.isA( TileDfn, tile_dfn ) ) )
{ {
@ -137,13 +147,14 @@ module.exports = Class( 'TileMasker',
// rather than accepting a context, we will create our own canvas in // rather than accepting a context, we will create our own canvas in
// memory to perform our operations (it will not be added to the DOM, so // memory to perform our operations (it will not be added to the DOM, so
// these operations will not be visible to the user) // these operations will not be visible to the user)
var context = document.createElement( 'canvas' ).getContext( '2d' ); let context = document.createElement( 'canvas' ).getContext( '2d' );
// size the canvas so that it can fit the entire tileset // size the canvas so that it can fit the entire tileset
context.canvas.width = this._setWidth; context.canvas.width = this._setWidth;
context.canvas.height = this._setHeight; context.canvas.height = this._setHeight;
this._context = context; this._context = context;
this._document = document;
}, },
@ -161,7 +172,7 @@ module.exports = Class( 'TileMasker',
'private _calcSetDimensions': function( tile_dfn ) 'private _calcSetDimensions': function( tile_dfn )
{ {
// these vars are for clarity // these vars are for clarity
var sizes = tile_dfn.getTileDimensions(), const sizes = tile_dfn.getTileDimensions(),
n = this._tileDfn.length; n = this._tileDfn.length;
// store values so that we do not have to make additional calls to our // store values so that we do not have to make additional calls to our
@ -201,7 +212,7 @@ module.exports = Class( 'TileMasker',
*/ */
'public getMaskedTiles': function( bmp_game, bmp_mask, callback ) 'public getMaskedTiles': function( bmp_game, bmp_mask, callback )
{ {
var _self = this; const _self = this;
this._getImageData( bmp_mask, function( data_mask ) this._getImageData( bmp_mask, function( data_mask )
{ {
@ -248,9 +259,7 @@ module.exports = Class( 'TileMasker',
*/ */
'virtual protected getMaskedTileSet': function( data_mask, callback ) 'virtual protected getMaskedTileSet': function( data_mask, callback )
{ {
var tdata = this._tileDfn, const tdata = this._tileDfn,
tiles = {},
i = -1,
len = tdata.length, len = tdata.length,
// shorten the names // shorten the names
@ -258,15 +267,18 @@ module.exports = Class( 'TileMasker',
th = this._tileHeight, th = this._tileHeight,
xn = this._tilesPerRow; xn = this._tilesPerRow;
let tiles = {},
i = -1;
// create each tile (preserving order, thus no decrementing) // create each tile (preserving order, thus no decrementing)
while ( ++i < len ) while ( ++i < len )
{ {
var id = tdata[ i ][ 0 ], const id = tdata[ i ][ 0 ],
mask = tdata[ i ][ 1 ], mask = tdata[ i ][ 1 ];
// calculate the X and Y position of this tile based on the tile // calculate the X and Y position of this tile based on the tile
// and bitmap dimensions // and bitmap dimensions
x = ( ( i % xn ) * th ), const x = ( ( i % xn ) * th ),
y = ( ( Math.floor( i / xn ) ) * tw ); y = ( ( Math.floor( i / xn ) ) * tw );
// the third index indicates whether or not a mask should be applied // the third index indicates whether or not a mask should be applied
@ -297,7 +309,7 @@ module.exports = Class( 'TileMasker',
*/ */
'protected appendTileFrame': function( set, id, mask, data ) 'protected appendTileFrame': function( set, id, mask, data )
{ {
var prev = set[ id ]; const prev = set[ id ];
set[ id ] = { set[ id ] = {
data: data, data: data,
@ -314,7 +326,7 @@ module.exports = Class( 'TileMasker',
// if there was a previous entry, set its 'next' entry to our new frame, // if there was a previous entry, set its 'next' entry to our new frame,
// expanding the linked list // expanding the linked list
prev && ( prev.next = set[ id ] ) if ( prev ) prev.next = set[ id ];
}, },
@ -347,18 +359,19 @@ module.exports = Class( 'TileMasker',
*/ */
'virtual protected getMaskedTileData': function( data_mask, x, y ) 'virtual protected getMaskedTileData': function( data_mask, x, y )
{ {
var raw = this.getTileData( x, y ), const raw = this.getTileData( x, y ),
w = raw.width, w = raw.width,
h = raw.height, h = raw.height,
mw = data_mask.width, mw = data_mask.width;
yi = h;
let yi = h;
// apply the mask to the raw tile data (simple and easy-to-understand // apply the mask to the raw tile data (simple and easy-to-understand
// algorithm; we can refine it later if need be), looping through each // algorithm; we can refine it later if need be), looping through each
// pixel // pixel
while ( yi-- ) while ( yi-- )
{ {
xi = w; let xi = w;
while ( xi-- ) while ( xi-- )
{ {
@ -366,7 +379,7 @@ module.exports = Class( 'TileMasker',
// (remember that, although we are dealing with applying the // (remember that, although we are dealing with applying the
// mask to a single tile, the mask image contains all tiles, so // mask to a single tile, the mask image contains all tiles, so
// we must calculate its position accordingly) // we must calculate its position accordingly)
var mi = ( ( ( yi + y ) * ( mw * 4 ) ) + ( ( xi + x ) * 4 ) ), const mi = ( ( ( yi + y ) * ( mw * 4 ) ) + ( ( xi + x ) * 4 ) ),
mr = data_mask.data[ mi ]; mr = data_mask.data[ mi ];
// manipulate the alpha channel of our tile; if the R value for // manipulate the alpha channel of our tile; if the R value for
@ -414,8 +427,8 @@ module.exports = Class( 'TileMasker',
*/ */
'private _renderImage': function( bmp, callback ) 'private _renderImage': function( bmp, callback )
{ {
var _self = this, const _self = this,
img = new Image(); img = this._document.createElement( 'img' );
img.onload = function() img.onload = function()
{ {
@ -437,7 +450,7 @@ module.exports = Class( 'TileMasker',
*/ */
'private _getImageData': function( bmp, callback ) 'private _getImageData': function( bmp, callback )
{ {
var _self = this; const _self = this;
this._renderImage( bmp, function() this._renderImage( bmp, function()
{ {

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Class = require( 'easejs' ).Class; const Class = require( 'easejs' ).Class;
module.exports = Class( 'GameObject', module.exports = Class( 'GameObject',
@ -44,6 +44,7 @@ module.exports = Class( 'GameObject',
}, },
/* jshint -W098 */
'virtual public move': function( dir, c, sc ) 'virtual public move': function( dir, c, sc )
{ {
// move in the appropriate direction (action has been pre-configured // move in the appropriate direction (action has been pre-configured
@ -52,4 +53,5 @@ module.exports = Class( 'GameObject',
return this; return this;
} }
/* jshint +W098 */
} ); } );

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Class = require( 'easejs' ).Class, const Class = require( 'easejs' ).Class,
GameObject = require( './GameObject' ); GameObject = require( './GameObject' );
@ -26,12 +26,12 @@ module.exports = Class( 'Tank' )
{ {
'override public move': function( direction, c, sc ) 'override public move': function( direction, c, sc )
{ {
var state = [ 'tleft', 'tup', 'tright', 'tdown' ][ direction ]; const state = [ 'tleft', 'tup', 'tright', 'tdown' ][ direction ];
if ( state !== this.getTid() ) if ( state !== this.getTid() )
{ {
sc( state ); sc( state );
return; return this;
} }
// let parent handle the movement // let parent handle the movement

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Class = require( 'easejs' ).Class, const Class = require( 'easejs' ).Class,
Tank = require( '../Tank' ); Tank = require( '../Tank' );

View File

@ -1,5 +1,5 @@
/** /**
* Represents a classic map (level) * Represents a classic level
* *
* Copyright (C) 2012, 2015 Mike Gerwitz * Copyright (C) 2012, 2015 Mike Gerwitz
* *
@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
* *
* Each map is concatenated in the file and consists of the following * Each level is concatenated in the file and consists of the following
* information: * information:
* *
* - Playfield data (game objects), 16x16 multidimensional char array * - Playfield data (game objects), 16x16 multidimensional char array
@ -27,34 +27,34 @@
* - Difficulty, 16-bit integer, little endian * - Difficulty, 16-bit integer, little endian
* *
* One can easily calculate the position of the level in a file, given its * One can easily calculate the position of the level in a file, given its
* number, by multiplying by the size of the data structure (see TLEVEL, LTANK.H * number, by multiplying by the size of the data structure (see TLEVEL,
* in the original sources). * LTANK.H in the original sources).
* *
* It is worth mentioning how the playfield data is stored. Since the * It is worth mentioning how the playfield data is stored. Since the
* multidimensional array is defined as [x][y], the array is created as an * multidimensional array is defined as [x][y], the array is created as an
* "array of columns", meaning that the data is organized in columns instead of * "array of columns", meaning that the data is organized in columns instead
* rows. For example, when viewing the data in a HEX editor that displays 16 * of rows. For example, when viewing the data in a HEX editor that displays
* bytes per line (e.g. xxd), the map would appear to be mirrored rotated 90 * 16 bytes per line (e.g. xxd), the level would appear to be mirrored
* degrees counter-clockwise. To make it easier to visualize, one can create a * rotated 90 degrees counter-clockwise. To make it easier to visualize, one
* map with a number of tunnels in a pattern to take advantage of the ASCII * can create a level with a number of tunnels in a pattern to take
* display. * advantage of the ASCII display.
*/ */
var Class = require( 'easejs' ).Class, const Class = require( 'easejs' ).Class,
Map = require( './Map' ); Level = require( './Level' );
/** /**
* Represents a classic map, as they exist in the original game. * Represents a classic level, as they exist in the original game.
* *
* Classic maps are 16x16 tiles in size (for a total of 256 tiles). * Classic levels are 16x16 tiles in size (for a total of 256 tiles).
*/ */
module.exports = Class( 'ClassicMap' ) module.exports = Class( 'ClassicLevel' )
.implement( Map ) .implement( Level )
.extend( .extend(
{ {
/** /**
* Size of each map in bytes * Size of each level in bytes
* @type {number} * @type {number}
*/ */
'private const _SIZE': 576, 'private const _SIZE': 576,
@ -70,13 +70,13 @@ module.exports = Class( 'ClassicMap' )
'private const _GOSIZE': [ 0, 256 ], 'private const _GOSIZE': [ 0, 256 ],
/** /**
* Offset and length of map name * Offset and length of level name
* @type {Array.<number>} * @type {Array.<number>}
*/ */
'private const _NAMESIZE': [ 256, 31 ], 'private const _NAMESIZE': [ 256, 31 ],
/** /**
* Offset and length of map hint * Offset and length of level hint
* @type {Array.<number>} * @type {Array.<number>}
*/ */
'private const _HINTSIZE': [ 287, 256 ], 'private const _HINTSIZE': [ 287, 256 ],
@ -105,12 +105,13 @@ module.exports = Class( 'ClassicMap' )
/** /**
* Colors used to identify tunnels * Colors used to identify tunnels
* *
* These colors will be rendered in the background and will bleed through * These colors will be rendered in the background and will bleed
* the transparent portions of the tile. We use explicit HEX codes rather * through the transparent portions of the tile. We use explicit HEX
* than CSS color names because environments may vary the colors used. * codes rather than CSS color names because environments may vary the
* colors used.
* *
* Taken from ColorList in LTANK2.C in the original sources. Note that there * Taken from ColorList in LTANK2.C in the original sources. Note that
* is an endianness difference. * there is an endianness difference.
* *
* @type {Array.<string>} * @type {Array.<string>}
*/ */
@ -121,36 +122,36 @@ module.exports = Class( 'ClassicMap' )
/** /**
* Map set data (binary string) * Level set data (binary string)
* @type {string} * @type {string}
*/ */
'private _data': null, 'private _data': null,
/** /**
* Map id (1-indexed) * Level id (1-indexed)
* @type {string} * @type {string}
*/ */
'private _id': 0, 'private _id': 0,
/** /**
* Offset of beginning of map data in bytes * Offset of beginning of level data in bytes
* @type {number} * @type {number}
*/ */
'private _offset': 0, 'private _offset': 0,
/** /**
* Initialize map with map data and the given id * Initialize level with level data and the given id
* *
* @param {MapSet} set map set data * @param {LevelSet} set level set data
* @param {number} id 1-indexed map id * @param {number} id 1-indexed level id
*/ */
__construct: function( data, id ) __construct: function( data, id )
{ {
this._data = ''+( data ); this._data = ''+( data );
this._id = +id; this._id = +id;
// calculate map offset in LVL data // calculate level offset in LVL data
this._offset = ( this.__self.$( '_SIZE' ) * ( this._id - 1 ) ); this._offset = ( this.__self.$( '_SIZE' ) * ( this._id - 1 ) );
}, },
@ -158,20 +159,20 @@ module.exports = Class( 'ClassicMap' )
/** /**
* Retrieve game objects * Retrieve game objects
* *
* The game objects are returned in a manner consistent with the original * The game objects are returned in a manner consistent with the
* sources - in columns, not rows. The reason for this is that the original * original sources - in columns, not rows. The reason for this is that
* game uses a multi-dimensional array [x][y], which creates an array of * the original game uses a multi-dimensional array [x][y], which
* columns (TPLAYFIELD, LTANK.H). * creates an array of columns (TPLAYFIELD, LTANK.H).
* *
* The object data at the requested position will be loaded and converted to * The object data at the requested position will be loaded and
* integers (from a binary string). * converted to integers (from a binary string).
* *
* @return {Array.<number>} array of game objects * @return {Array.<number>} array of game objects
*/ */
'public getObjects': function() 'public getObjects': function()
{ {
var tiles = this._getDataSegment( '_GOSIZE', false ).split( '' ), const tiles = this._getDataSegment( '_GOSIZE', false ).split( '' );
i = tiles.length; let i = tiles.length;
while ( i-- ) while ( i-- )
{ {
@ -192,7 +193,7 @@ module.exports = Class( 'ClassicMap' )
{ {
stripnull = ( arguments.length < 2 ) ? true : !!stripnull; stripnull = ( arguments.length < 2 ) ? true : !!stripnull;
var s = this.__self.$( name ), const s = this.__self.$( name ),
data = this._data.substr( ( this._offset + s[ 0 ] ), s[ 1 ] ); data = this._data.substr( ( this._offset + s[ 0 ] ), s[ 1 ] );
return ( stripnull ) return ( stripnull )
@ -202,7 +203,7 @@ module.exports = Class( 'ClassicMap' )
/** /**
* Retrieve map dimensions * Retrieve level dimensions
* *
* @return {Array.<number>} width and height in tiles * @return {Array.<number>} width and height in tiles
*/ */
@ -213,11 +214,11 @@ module.exports = Class( 'ClassicMap' )
/** /**
* Retrieve map of object codes to their appropriate tiles * Retrieve level of object codes to their appropriate tiles
* *
* @return {Array.<string>} * @return {Array.<string>}
*/ */
'public getObjectTileMap': function() 'public getObjectTileLevel': function()
{ {
// we return these values here instead of returning, say, a constant, // we return these values here instead of returning, say, a constant,
// because we would have to clone it to ensure that our inner state // because we would have to clone it to ensure that our inner state
@ -235,9 +236,10 @@ module.exports = Class( 'ClassicMap' )
/** /**
* Retrieve tunnel color * Retrieve tunnel color
* *
* The color will be rendered in the background and will bleed through the * The color will be rendered in the background and will bleed through
* transparent portions of the tile. We use explicit HEX codes rather than * the transparent portions of the tile. We use explicit HEX codes
* CSS color names because environments may vary the colors used. * rather than CSS color names because environments may vary the colors
* used.
* *
* @param {number} oid tunnel object id * @param {number} oid tunnel object id
* *
@ -245,9 +247,9 @@ module.exports = Class( 'ClassicMap' )
*/ */
'public getTunnelColor': function( oid ) 'public getTunnelColor': function( oid )
{ {
// get the tunnel id by stripping off the tunnel bitmask and then grab // get the tunnel id by stripping off the tunnel bitmask and then
// the associated color // grab the associated color
var tunnel_id = ( ( +oid ^ this.__self.$( '_TMASK' ) ) / 2 ); const tunnel_id = ( ( +oid ^ this.__self.$( '_TMASK' ) ) / 2 );
return this.__self.$( '_TCOLORS' )[ tunnel_id ] || 'black'; return this.__self.$( '_TCOLORS' )[ tunnel_id ] || 'black';
}, },
@ -265,79 +267,81 @@ module.exports = Class( 'ClassicMap' )
/** /**
* Retrieve map name * Retrieve level name
* *
* @return {string} map name * @return {string} level name
*/ */
'public getMapName': function() 'public getLevelName': function()
{ {
return this._getDataSegment( '_NAMESIZE' ); return this._getDataSegment( '_NAMESIZE' );
}, },
/** /**
* Retrieve map author name * Retrieve level author name
* *
* @return {string} map author name * @return {string} level author name
*/ */
'public getMapAuthor': function() 'public getLevelAuthor': function()
{ {
return this._getDataSegment( '_AUTHORSIZE' ); return this._getDataSegment( '_AUTHORSIZE' );
}, },
/** /**
* Retrieve map hint * Retrieve level hint
* *
* @return {string} map hint * @return {string} level hint
*/ */
'public getMapHint': function() 'public getLevelHint': function()
{ {
return this._getDataSegment( '_HINTSIZE' ); return this._getDataSegment( '_HINTSIZE' );
}, },
/** /**
* Retrieve map difficulty * Retrieve level difficulty
* *
* The map difficulty will be returned as a 0-indexed value between 0 and 4, * The level difficulty will be returned as a 0-indexed value between 0
* with 0 representing "kids" and 4 representing "deadly". * and 4, with 0 representing "kids" and 4 representing "deadly".
* *
* The original game uses a bitmask for this value (thus the 16-bit * The original game uses a bitmask for this value (thus the 16-bit
* integer), which is really of no particular use. For simplicity's sake, we * integer), which is really of no particular use. For simplicity's
* will convert it. * sake, we will convert it.
* *
* @return {number} 0-indexed difficulty level * @return {number} 0-indexed difficulty level
*/ */
'public getMapDifficulty': function() 'public getLevelDifficulty': function()
{ {
var val = this._getDataSegment( '_DIFFSIZE', false ), const val = this._getDataSegment( '_DIFFSIZE', false );
i = val.length,
let i = val.length,
n = 0; n = 0;
// first, convert the value to an integer (from little-endian) // first, convert the value to an integer (from little-endian)
while ( i-- ) n += ( val.charCodeAt( i ) << ( 8 * i ) ); while ( i-- ) n += ( val.charCodeAt( i ) << ( 8 * i ) );
// Finally, convert to a simple 0-4 value to represent difficulty by // Finally, convert to a simple 0-4 value to represent difficulty by
// taking the binary logarithm of the value (lg(n); original game uses // taking the binary logarithm of the value (lg(n); original game
// bitmasks). For those who do not understand logarithms, the concept // uses bitmasks). For those who do not understand logarithms, the
// here is simple: if we are given a difficulty of "hard" (value of 8), // concept here is simple: if we are given a difficulty of "hard"
// that has a binary representation of: 1000. We are interested in the // (value of 8), that has a binary representation of: 1000. We are
// position of the 1-bit. Since each bit position is an exponent of two, // interested in the position of the 1-bit. Since each bit position
// we can reverse that calculation with a binary logarithm. So, log2(8), // is an exponent of two, we can reverse that calculation with a
// also written as lg(8), is equal to 3, since 2^3 = 8. Similarly, // binary logarithm. So, log2(8), also written as lg(8), is equal to
// "deadly" = 16 = 0001 0000 => lg(16) = 4, and "kids" = 1 = 0001 => // 3, since 2^3 = 8. Similarly, "deadly" = 16 = 0001 0000 => lg(16)
// lg(1) = 0. This gives us a 0-indexed difficulty value. // = 4, and "kids" = 1 = 0001 => lg(1) = 0. This gives us a
// 0-indexed difficulty value.
return ( Math.log( n ) / Math.log( 2 ) ); return ( Math.log( n ) / Math.log( 2 ) );
}, },
/** /**
* Retrieve size of map in bytes * Retrieve size of level in bytes
* *
* @return {number} size of map in bytes * @return {number} size of level in bytes
*/ */
'public static getMapSize': function() 'public static getLevelSize': function()
{ {
return this.$( '_SIZE' ); return this.$( '_SIZE' );
} }

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
* *
* The details on exactly how the map data is stored is left to specific * The details on exactly how the level data is stored is left to specific
* implementations. However, the following is common to each file format: * implementations. However, the following is common to each file format:
* *
* - All game objects for the playfield should be returned in columns rather * - All game objects for the playfield should be returned in columns rather
@ -29,26 +29,26 @@
* tunnel identified by index 0 is 0x40, index 1 is 0x42, and so on. * tunnel identified by index 0 is 0x40, index 1 is 0x42, and so on.
*/ */
var Interface = require( 'easejs' ).Interface; const Interface = require( 'easejs' ).Interface;
/** /**
* Represents a map (level) * Represents a game level
* *
* Maps simply act as basic wrappers around a set of maps, returning only the * Levels simply act as basic wrappers around a set of maps, returning only the
* data associated with the requested map. This allows the data to be lazily * data associated with the requested level. This allows the data to be lazily
* sliced out of the map file. * sliced out of the level file.
* *
* Note that this interface specifies a constructor definition; this allows it * Note that this interface specifies a constructor definition; this allows it
* to be used in place of a separate Factory class. * to be used in place of a separate Factory class.
*/ */
module.exports = Interface( 'Map', module.exports = Interface( 'Level',
{ {
/** /**
* Initialize map with map data and the given id * Initialize level with level data and the given id
* *
* @param {MapSet} set map set data * @param {LevelSet} set level set data
* @param {number} id 1-indexed map id * @param {number} id 1-indexed level id
*/ */
__construct: [ 'set', 'id' ], __construct: [ 'set', 'id' ],
@ -67,7 +67,7 @@ module.exports = Interface( 'Map',
/** /**
* Retrieve map dimensions * Retrieve level dimensions
* *
* @return {Array.<number>} width and height in tiles * @return {Array.<number>} width and height in tiles
*/ */
@ -75,11 +75,11 @@ module.exports = Interface( 'Map',
/** /**
* Retrieve map of object codes to their appropriate tiles * Retrieve level of object codes to their appropriate tiles
* *
* @return {Array.<string>} * @return {Array.<string>}
*/ */
'public getObjectTileMap': [], 'public getObjectTileLevel': [],
/** /**
@ -106,44 +106,44 @@ module.exports = Interface( 'Map',
/** /**
* Retrieve map name * Retrieve level name
* *
* @return {string} map name * @return {string} level name
*/ */
'public getMapName': [], 'public getLevelName': [],
/** /**
* Retrieve map author name * Retrieve level author name
* *
* @return {string} map author name * @return {string} level author name
*/ */
'public getMapAuthor': [], 'public getLevelAuthor': [],
/** /**
* Retrieve map hint * Retrieve level hint
* *
* @return {string} map hint * @return {string} level hint
*/ */
'public getMapHint': [], 'public getLevelHint': [],
/** /**
* Retrieve map difficulty * Retrieve level difficulty
* *
* The map difficulty should be returned as a 0-indexed value between 0 and * The level difficulty should be returned as a 0-indexed value between
* 4, with 0 representing "kids" and 4 representing "deadly". * 0 and 4, with 0 representing "kids" and 4 representing "deadly".
* *
* @return {number} 0-indexed difficulty level * @return {number} 0-indexed difficulty level
*/ */
'public getMapDifficulty': [], 'public getLevelDifficulty': [],
/** /**
* Retrieve size of map in bytes * Retrieve size of level in bytes
* *
* @return {number} size of map in bytes * @return {number} size of level in bytes
*/ */
'public static getMapSize': [] 'public static getLevelSize': []
} ); } );

View File

@ -17,11 +17,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Class = require( 'easejs' ).Class, const Class = require( 'easejs' ).Class,
MapBounds = require( './MapBounds' ); LevelBounds = require( './LevelBounds' );
module.exports = Class( 'MapAction', module.exports = Class( 'LevelAction',
{ {
// arranged by keycode // arranged by keycode
'const D__MIN': 0, 'const D__MIN': 0,
@ -43,9 +43,9 @@ module.exports = Class( 'MapAction',
__construct: function( bounds, move_callback ) __construct: function( bounds, move_callback )
{ {
if ( !( Class.isA( MapBounds, bounds ) ) ) if ( !( Class.isA( LevelBounds, bounds ) ) )
{ {
throw TypeError( 'Invalid MapBounds provided' ); throw TypeError( 'Invalid LevelBounds provided' );
} }
this._dir = this.__self.$( 'D_UP' ); this._dir = this.__self.$( 'D_UP' );
@ -57,7 +57,7 @@ module.exports = Class( 'MapAction',
'public move': function() 'public move': function()
{ {
var method = [ const method = [
'getLeftPos', 'getLeftPos',
'getUpperPos', 'getUpperPos',
'getRightPos', 'getRightPos',

View File

@ -1,5 +1,5 @@
/** /**
* Handles map boundaries for collision detection and movement * Handles level boundaries for collision detection and movement
* *
* Copyright (C) 2012, 2015 Mike Gerwitz * Copyright (C) 2012, 2015 Mike Gerwitz
* *
@ -17,45 +17,46 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Class = require( 'easejs' ).Class, const Class = require( 'easejs' ).Class,
Map = require( './Map' ); Level = require( './Level' );
/** /**
* Calculates map bounding box * Calculates level bounding box
* *
* This simply encapsulates the process of determining whether a given position * This simply encapsulates the process of determining whether a given
* is against an edge of the map. * position is against an edge of the level.
*/ */
module.exports = Class( 'MapBounds', module.exports = Class( 'LevelBounds',
{ {
/** /**
* Map width (number of tiles) * Level width (number of tiles)
* @type {number} * @type {number}
*/ */
'private _mw': 0, 'private _mw': 0,
/** /**
* Map height (number of tiles) * Level height (number of tiles)
* @type {number} * @type {number}
*/ */
'private _mh': 0, 'private _mh': 0,
/** /**
* Initialize bounding box for a given map * Initialize bounding box for a given level
* *
* @param {Map} map map for which bounds should be calculated * @param {Level} level level for which bounds should be calculated
*/ */
__construct: function( map ) __construct: function( level )
{ {
if ( !( Class.isA( Map, map ) ) ) if ( !( Class.isA( Level, level ) ) )
{ {
throw TypeError( 'Invalid Map provided' ); throw TypeError( 'Invalid Level provided' );
} }
// we are only interested in the dimensions of the map // we are only interested in the dimensions of the level
var dimen = map.getDimensions(); const dimen = level.getDimensions();
this._mw = dimen[ 0 ]; this._mw = dimen[ 0 ];
this._mh = dimen[ 1 ]; this._mh = dimen[ 1 ];
}, },
@ -64,7 +65,7 @@ module.exports = Class( 'MapBounds',
/** /**
* Retrieve the tile position above the given position * Retrieve the tile position above the given position
* *
* If the given tile position is at the top of the map, then the given * If the given tile position is at the top of the level, then the given
* position will be returned. * position will be returned.
* *
* @param {number} pos original tile position * @param {number} pos original tile position
@ -82,8 +83,8 @@ module.exports = Class( 'MapBounds',
/** /**
* Retrieve the tile position below the given position * Retrieve the tile position below the given position
* *
* If the given tile position is at the bottom of the map, then the given * If the given tile position is at the bottom of the level, then the
* position will be returned. * given position will be returned.
* *
* @param {number} pos original tile position * @param {number} pos original tile position
* *
@ -100,8 +101,8 @@ module.exports = Class( 'MapBounds',
/** /**
* Retrieve the tile position to the left of the given position * Retrieve the tile position to the left of the given position
* *
* If the given tile position is at the leftmost column of the map, then the * If the given tile position is at the leftmost column of the level,
* given position will be returned. * then the given position will be returned.
* *
* @param {number} pos original tile position * @param {number} pos original tile position
* *
@ -119,8 +120,8 @@ module.exports = Class( 'MapBounds',
/** /**
* Retrieve the tile position to the right of the given position * Retrieve the tile position to the right of the given position
* *
* If the given tile position is at the rightmost column of the map, then * If the given tile position is at the rightmost column of the level,
* the given position will be returned. * then the given position will be returned.
* *
* @param {number} pos original tile position * @param {number} pos original tile position
* *
@ -136,7 +137,7 @@ module.exports = Class( 'MapBounds',
/** /**
* Determines if the given position is in the topmost row of the map * Determines if the given position is in the topmost row of the level
* *
* @param {number} pos tile position * @param {number} pos tile position
* *
@ -144,14 +145,14 @@ module.exports = Class( 'MapBounds',
*/ */
'public isAtTop': function( pos ) 'public isAtTop': function( pos )
{ {
// since tile positions are zero-indexed, we know that we're at the top // since tile positions are zero-indexed, we know that we're at the
// if the map height divides the position // top if the level height divides the position
return ( pos % this._mh === 0 ); return ( pos % this._mh === 0 );
}, },
/** /**
* Determines if the given position is in the bottom row of the map * Determines if the given position is in the bottom row of the level
* *
* @param {number} pos tile position * @param {number} pos tile position
* *
@ -165,7 +166,8 @@ module.exports = Class( 'MapBounds',
/** /**
* Determines if the given position is in the leftmost column of the map * Determines if the given position is in the leftmost column of the
* level
* *
* @param {number} pos tile position * @param {number} pos tile position
* *
@ -179,7 +181,8 @@ module.exports = Class( 'MapBounds',
/** /**
* Determines if the given position is in the rightmost column of the map * Determines if the given position is in the rightmost column of the
* level
* *
* @param {number} pos tile position * @param {number} pos tile position
* *

View File

@ -1,5 +1,5 @@
/** /**
* Renders a given map * Renders a given level
* *
* Copyright (C) 2012, 2015 Mike Gerwitz * Copyright (C) 2012, 2015 Mike Gerwitz
* *
@ -17,20 +17,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Class = require( 'easejs' ).Class, const Class = require( 'easejs' ).Class,
MapState = require( './MapState' ); LevelState = require( './LevelState' );
/** /**
* Renders a map to a canvas * Renders a level to a canvas
*/ */
module.exports = Class( 'MapRender', module.exports = Class( 'LevelRender',
{ {
/** /**
* Property to hold lock bit on canvas element * Property to hold lock bit on canvas element
* @type {string} * @type {string}
*/ */
'private const _LOCK': '__$$MapRenderLock$$', 'private const _LOCK': '__$$LevelRenderLock$$',
/** /**
* Animation interval in milliseconds * Animation interval in milliseconds
@ -40,7 +40,7 @@ module.exports = Class( 'MapRender',
/** /**
* 2d context to which map should be drawn * 2d context to which level should be drawn
* @type {CanvasRenderingContext2d} * @type {CanvasRenderingContext2d}
*/ */
'private _ctx': null, 'private _ctx': null,
@ -67,8 +67,8 @@ module.exports = Class( 'MapRender',
/** /**
* Initialize renderer with a canvas context and a tile set * Initialize renderer with a canvas context and a tile set
* *
* An additional canvas of equal dimensions will be created and laid atop of * An additional canvas of equal dimensions will be created and laid
* the provided canvas to render masked game objects. * atop of the provided canvas to render masked game objects.
* *
* @param {CanvasRenderingContext2d} ctx canvas 2d context * @param {CanvasRenderingContext2d} ctx canvas 2d context
* @param {Object} tiles tile set to render * @param {Object} tiles tile set to render
@ -79,7 +79,7 @@ module.exports = Class( 'MapRender',
this._tiles = tiles; this._tiles = tiles;
// ensure that we are exclusively rendering to this canvas (no other // ensure that we are exclusively rendering to this canvas (no other
// MapRenders) // LevelRenders)
this._lockCanvas(); this._lockCanvas();
this._ctxObj = this._getObjCanvas(); this._ctxObj = this._getObjCanvas();
@ -87,22 +87,24 @@ module.exports = Class( 'MapRender',
/** /**
* Lock the canvas to prevent other MapRender instances from rendering to it * Lock the canvas to prevent other LevelRender instances from rendering
* to it
* *
* The purpose of this is to provide feedback to the user/developer in the * The purpose of this is to provide feedback to the user/developer in
* event that multiple MapRender instances are attempting to render to the * the event that multiple LevelRender instances are attempting to
* same canvas, which would certainly cause display issues and confusion. * render to the same canvas, which would certainly cause display issues
* and confusion.
* *
* @return {undefined} * @return {undefined}
*/ */
'private _lockCanvas': function() 'private _lockCanvas': function()
{ {
var o = this._ctx, const o = this._ctx,
l = this.__self.$( '_LOCK' ); l = this.__self.$( '_LOCK' );
// simple one-line check to both set the lock and fail if the lock is // simple one-line check to both set the lock and fail if the lock
// already set (implying that something else is already rendering to the // is already set (implying that something else is already rendering
// canvas) // to the canvas)
if ( ( o[ l ] ^= 1 ) !== 1 ) if ( ( o[ l ] ^= 1 ) !== 1 )
{ {
// reset the lock // reset the lock
@ -110,24 +112,24 @@ module.exports = Class( 'MapRender',
throw Error( throw Error(
'Could not set exclusive lock on canvas (in use by another ' + 'Could not set exclusive lock on canvas (in use by another ' +
'MapRender instance)' 'LevelRender instance)'
); );
} }
}, },
/** /**
* Remove exclusive lock on canvas to permit other MapRender instances to * Remove exclusive lock on canvas to permit other LevelRender instances
* render to it * to render to it
* *
* This will also destroy the overlay canvas that the masked objects were * This will also destroy the overlay canvas that the masked objects
* rendered to. The remaining canvas will not be cleared. * were rendered to. The remaining canvas will not be cleared.
* *
* @return {MapRender} self * @return {LevelRender} self
*/ */
'public freeCanvas': function() 'public freeCanvas': function()
{ {
var c = this._ctxObj.canvas; const c = this._ctxObj.canvas;
// clear any running animations // clear any running animations
this._clearAnimation(); this._clearAnimation();
@ -148,7 +150,8 @@ module.exports = Class( 'MapRender',
*/ */
'private _getObjCanvas': function() 'private _getObjCanvas': function()
{ {
var canvas = this._ctx.canvas, const canvas = this._ctx.canvas,
document = this._getDocument( canvas ),
canvas_obj = document.createElement( 'canvas' ); canvas_obj = document.createElement( 'canvas' );
// mimic the dimensions and positions of the original canvas // mimic the dimensions and positions of the original canvas
@ -166,25 +169,42 @@ module.exports = Class( 'MapRender',
/** /**
* Render the provided map * Get HTMLDocument node from element ELEMENT
* *
* @param {Map} map map to render * ELEMENT must be on a DOM. This allows us to always reference the
* proper document node without being coupled with the browser's
* window.document, which may not be what we're interested in.
* *
* @return {MapRender} self * @param {HTMLElement} element reference element
*
* @return {HTMLDocument} document node
*/ */
'public render': function( map, map_state ) 'private _getDocument': function( element )
{ {
if ( !( Class.isA( MapState, map_state ) ) ) return ( element.parentElement === null )
? element.parentNode
: this._getDocument( element.parentElement );
},
/**
* Render the provided level
*
* @param {Level} level level to render
*
* @return {LevelRender} self
*/
'public render': function( level, level_state )
{ {
throw TypeError( 'Invalid MapState provided' ); if ( !( Class.isA( LevelState, level_state ) ) )
{
throw TypeError( 'Invalid LevelState provided' );
} }
var objs = map.getObjects(), const objs = level.getObjects(),
size = map.getDimensions(), size = level.getDimensions(),
omap = map.getObjectTileMap(),
sizex = size[ 0 ], sizex = size[ 0 ],
sizey = size[ 1 ], sizey = size[ 1 ],
i = objs.length,
// tiles to animate // tiles to animate
anim = [], anim = [],
@ -196,10 +216,10 @@ module.exports = Class( 'MapRender',
this._clearCanvases(); this._clearCanvases();
var _self = this; const _self = this;
map_state.onChange( function( obj, pos ) level_state.onChange( function( obj, pos )
{ {
var oid = objs[ pos ], const oid = objs[ pos ],
tid = ( obj ) ? obj.getTid() : 'dirt', tid = ( obj ) ? obj.getTid() : 'dirt',
tile = ( _self._tiles[ tid ] || {} ).first, tile = ( _self._tiles[ tid ] || {} ).first,
@ -214,9 +234,9 @@ module.exports = Class( 'MapRender',
} }
// tunnels are handled a bit differently than other objects // tunnels are handled a bit differently than other objects
if ( map.isObjectTunnel( oid ) ) if ( level.isObjectTunnel( oid ) )
{ {
_self._renderTunnel( x, y, map.getTunnelColor( oid ) ); _self._renderTunnel( x, y, level.getTunnelColor( oid ) );
return; return;
} }
@ -229,10 +249,10 @@ module.exports = Class( 'MapRender',
_self._drawTile( tile, x, y ); _self._drawTile( tile, x, y );
} ); } );
map_state.flush(); level_state.flush();
// render each object (remember, we're dealing with columns, not rows; // render each object (remember, we're dealing with columns, not
// see Map.getObjects()) // rows; see Level.getObjects())
this._beginAnimation( anim ); this._beginAnimation( anim );
@ -241,12 +261,12 @@ module.exports = Class( 'MapRender',
/** /**
* Retrieve a vector representing the x and y position coordinates of a tile * Retrieve a vector representing the x and y position coordinates of a
* position * tile position
* *
* @param {number} pos tile position * @param {number} pos tile position
* @param {number} sizex number of horizontal tiles in map * @param {number} sizex number of horizontal tiles in level
* @param {number} sizey number of vertical tiles in map * @param {number} sizey number of vertical tiles in level
* @param {number} w tile width * @param {number} w tile width
* @param {number} h tile height * @param {number} h tile height
* *
@ -264,26 +284,27 @@ module.exports = Class( 'MapRender',
/** /**
* Clears overlay canvas * Clears overlay canvas
* *
* This should be used before first rendering a map to ensure that any * This should be used before first rendering a level to ensure that any
* artifacts from previous map renderings will be erased. * artifacts from previous level renderings will be erased.
* *
* We need only clear the overlay canvas, because the lower canvas will * We need only clear the overlay canvas, because the lower canvas will
* always be overwritten with tiles in every location. Because none of the * always be overwritten with tiles in every location. Because none of
* tiles written to the lower canvas are masked, nothing from the previous * the tiles written to the lower canvas are masked, nothing from the
* render would ever peek through (of course, putImageData() would overwrite * previous render would ever peek through (of course, putImageData()
* it even if that were the case). As such, clearing the lower canvas would * would overwrite it even if that were the case). As such, clearing the
* simply be a waste of time and only serve to degrade performance * lower canvas would simply be a waste of time and only serve to
* (especially if this is being used with maps larger than the classic * degrade performance (especially if this is being used with levels
* 16x16). * larger than the classic 16x16).
* *
* @return {undefined} * @return {undefined}
*/ */
'private _clearCanvases': function() 'private _clearCanvases': function()
{ {
var ctx = this._ctxObj, const ctx = this._ctxObj,
c = ctx.canvas; c = ctx.canvas;
// we need only clear the overlay (to which masked tiles are rendered) // we need only clear the overlay (to which masked tiles are
// rendered)
ctx.clearRect( 0, 0, c.width, c.height ); ctx.clearRect( 0, 0, c.width, c.height );
}, },
@ -297,7 +318,7 @@ module.exports = Class( 'MapRender',
*/ */
'private _canAnimate': function( tid ) 'private _canAnimate': function( tid )
{ {
var tdata = this._tiles[ tid ]; const tdata = this._tiles[ tid ];
return ( tdata.next !== tdata ); return ( tdata.next !== tdata );
}, },
@ -305,9 +326,10 @@ module.exports = Class( 'MapRender',
/** /**
* Draw the tile identified by the given id * Draw the tile identified by the given id
* *
* The tile will be drawn to the appropriate canvas depending on whether or * The tile will be drawn to the appropriate canvas depending on whether
* not it has been masked. If it does have a mask, it will be drawn to the * or not it has been masked. If it does have a mask, it will be drawn
* overlaying canvas and the dirt tile will be drawn underneath it. * to the overlaying canvas and the dirt tile will be drawn underneath
* it.
* *
* @param {string} tid tile id * @param {string} tid tile id
* @param {number} x left position * @param {number} x left position
@ -317,7 +339,7 @@ module.exports = Class( 'MapRender',
*/ */
'private _drawTile': function( tile, x, y ) 'private _drawTile': function( tile, x, y )
{ {
var ctx = ( tile.masked ) ? this._ctxObj : this._ctx; const ctx = ( tile.masked ) ? this._ctxObj : this._ctx;
ctx.putImageData( tile.data, x, y ); ctx.putImageData( tile.data, x, y );
@ -338,8 +360,8 @@ module.exports = Class( 'MapRender',
* Render the given tunnel * Render the given tunnel
* *
* The tunnel background color (which will peek through the mask) is * The tunnel background color (which will peek through the mask) is
* rendered to the base canvas, whereas the tunnel tile itself is rendered * rendered to the base canvas, whereas the tunnel tile itself is
* on the overlaying canvas. * rendered on the overlaying canvas.
* *
* @param {number} x left position * @param {number} x left position
* @param {number} y top position * @param {number} y top position
@ -349,7 +371,7 @@ module.exports = Class( 'MapRender',
*/ */
'private _renderTunnel': function( x, y, color ) 'private _renderTunnel': function( x, y, color )
{ {
var tdata = this._tiles.tunnel.data; const tdata = this._tiles.tunnel.data;
// fill tile with the appropriate background color for this tile // fill tile with the appropriate background color for this tile
this._ctx.fillStyle = color; this._ctx.fillStyle = color;
@ -363,8 +385,8 @@ module.exports = Class( 'MapRender',
/** /**
* Begin basic tile animation * Begin basic tile animation
* *
* At each animation interval, each tile will be advanced a single frame and * At each animation interval, each tile will be advanced a single frame
* rendered atop of the previous. * and rendered atop of the previous.
* *
* @param {Array.<Array.<Object,number,number>>} anim array of tiles to * @param {Array.<Array.<Object,number,number>>} anim array of tiles to
* animate; tdata,x,y * animate; tdata,x,y
@ -373,32 +395,32 @@ module.exports = Class( 'MapRender',
*/ */
'private _beginAnimation': function( anim ) 'private _beginAnimation': function( anim )
{ {
var _self = this; const _self = this;
// clear any existing rendering animations // clear any existing rendering animations
this._clearAnimation(); this._clearAnimation();
return this._animTimer = setInterval( function() return ( this._animTimer = setInterval( function()
{ {
var i = anim.length; let i = anim.length;
while ( i-- ) while ( i-- )
{ {
var cur = anim[ i ]; const cur = anim[ i ];
// draw next frame // draw next frame
cur[ 0 ] = cur[ 0 ].next; cur[ 0 ] = cur[ 0 ].next;
_self._drawTile.apply( _self, anim[ i ] ); _self._drawTile.apply( _self, anim[ i ] );
} }
}, this.__self.$( '_ANIM_INTERVAL' ) ); }, this.__self.$( '_ANIM_INTERVAL' ) ) );
}, },
/** /**
* Clear any running animation timers * Clear any running animation timers
* *
* It is important that this be done when a MapRender instance is done being * It is important that this be done when a LevelRender instance is done
* used, or it will remain in memory indefinitely! * being used, or it will remain in memory indefinitely!
* *
* @return {undefined} * @return {undefined}
*/ */

View File

@ -17,22 +17,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
* *
* Maps (the term "level" is used in the game) are stored in a fixed-width LVL * Levels are stored in a fixed-width LVL file.
* file.
*/ */
var Class = require( 'easejs' ).Class; const Class = require( 'easejs' ).Class;
/** /**
* Handles delegation of LVL data * Handles delegation of LVL data
* *
* This acts as a Map factory, leaving all processing responsibilities to the * This acts as a Level factory, leaving all processing responsibilities to
* Map instance. Consequently, any custom map format would be supported, * the Level instance. Consequently, any custom level format would be
* provided that the appropriate Map is handling it. * supported, provided that the appropriate Level is handling it.
*/ */
module.exports = Class( 'MapSet', module.exports = Class( 'LevelSet',
{ {
/** /**
* Raw level set data (binary) * Raw level set data (binary)
@ -41,61 +39,62 @@ module.exports = Class( 'MapSet',
'private _data': '', 'private _data': '',
/** /**
* Constructor used to create new map instances * Constructor used to create new level instances
* @type {Function} * @type {Function}
*/ */
'private _mapCtor': null, 'private _levelCtor': null,
/** /**
* Number of maps in the given LVL data * Number of levels in the given LVL data
* @type {number} * @type {number}
*/ */
'private _mapCount': 0, 'private _levelCount': 0,
/** /**
* Initialize map set with LVL data and a Map constructor * Initialize level set with LVL data and a Level constructor
* *
* The Map constructor is used in place of a separate Map factory. * The Level constructor is used in place of a separate Level factory.
* *
* @param {string} data binary LVL data * @param {string} data binary LVL data
* @param {Map} map_ctor Map constructor * @param {Level} level_ctor Level constructor
*/ */
__construct: function( data, map_ctor ) __construct: function( data, level_ctor )
{ {
this._data = ''+( data ); this._data = ''+( data );
this._mapCtor = map_ctor; this._levelCtor = level_ctor;
// perform a simple integrity check on the provided data // perform a simple integrity check on the provided data
this._mapDataCheck(); this._levelDataCheck();
}, },
/** /**
* Perform a simple map data integrity check * Perform a simple level data integrity check
* *
* This is intended to throw an error if we believe the LVL data to be * This is intended to throw an error if we believe the LVL data to be
* invalid, or if the LVL data is invalid for the given Map constructor. * invalid, or if the LVL data is invalid for the given Level
* The check will simply ensure that the map size (in bytes) divides into * constructor. The check will simply ensure that the level size (in
* the total LVL size (in bytes) without any remainder. * bytes) divides into the total LVL size (in bytes) without any
* remainder.
* *
* This is by no means fool-proof, but it should catch most. * This is by no means fool-proof, but it should catch most.
* *
* @return {undefined} * @return {undefined}
*/ */
'private _mapDataCheck': function() 'private _levelDataCheck': function()
{ {
var n = ( this._data.length / this._mapCtor.getMapSize() ); const n = ( this._data.length / this._levelCtor.getLevelSize() );
// if the result is not an integer, then it is either not an LVL, the // if the result is not an integer, then it is either not an LVL,
// file is corrupt, or we were given the wrong Map constructor // the file is corrupt, or we were given the wrong Level constructor
if ( n % 1 ) if ( n % 1 )
{ {
throw Error( 'Invalid or corrupt LVL data' ); throw Error( 'Invalid or corrupt LVL data' );
} }
// we already calculated it, so we may as well store it // we already calculated it, so we may as well store it
this._mapCount = n; this._levelCount = n;
}, },
@ -104,19 +103,19 @@ module.exports = Class( 'MapSet',
* *
* @param {number} id number of level to load, 1-indexed * @param {number} id number of level to load, 1-indexed
*/ */
'public getMapByNumber': function( id ) 'public getLevelByNumber': function( id )
{ {
return this._mapCtor( this._data, id ); return this._levelCtor( this._data, id );
}, },
/** /**
* Retrieve the number of maps in the LVL file * Retrieve the number of levels in the LVL file
* *
* @return {number} number of maps * @return {number} number of levels
*/ */
'public getMapCount': function() 'public getLevelCount': function()
{ {
return this._mapCount; return this._levelCount;
}, },
} ); } );

View File

@ -1,5 +1,5 @@
/** /**
* Represents the current state of a map * Represents the current state of a level
* *
* Copyright (C) 2012, 2015 Mike Gerwitz * Copyright (C) 2012, 2015 Mike Gerwitz
* *
@ -17,16 +17,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Class = require( 'easejs' ).Class, const Class = require( 'easejs' ).Class,
MapAction = require( './MapAction' ), LevelAction = require( './LevelAction' ),
GameObjectFactory = require( './GameObjectFactory' ), GameObjectFactory = require( '../GameObjectFactory' ),
GameObject = require( './gameobjs/GameObject' ); GameObject = require( '../gameobjs/GameObject' );
/** /**
* Represents the current state of a map * Represents the current state of a level
*/ */
module.exports = Class( 'MapState', module.exports = Class( 'LevelState',
{ {
/** /**
* Game object factory * Game object factory
@ -47,7 +47,7 @@ module.exports = Class( 'MapState',
'private _player': null, 'private _player': null,
/** /**
* Game objects representing every object on the map * Game objects representing every object on the level
* @type {GameObject} * @type {GameObject}
*/ */
'private _objs': [], 'private _objs': [],
@ -60,39 +60,41 @@ module.exports = Class( 'MapState',
/** /**
* Initializes map state with a given map and factory with which to create * Initializes level state with a given level and factory with which to
* game objects * create game objects
* *
* Game objects influence map state rules, therefore fundamental game logic * Game objects influence level state rules, therefore fundamental game
* may be altered simply by passing in a custom GameObjectFactory instance. * logic may be altered simply by passing in a custom GameObjectFactory
* instance.
* *
* @param {Map} map game map * @param {Level} level game level
* @param {GameObjectFactory} obj_factory game object factory * @param {GameObjectFactory} obj_factory game object factory
*/ */
__construct: function( map, obj_factory ) __construct: function( level, obj_factory )
{ {
if ( !( Class.isA( GameObjectFactory, obj_factory ) ) ) if ( !( Class.isA( GameObjectFactory, obj_factory ) ) )
{ {
throw TypeError( 'Invalid GameObjectFactory provided' ); throw TypeError( 'Invalid GameObjectFactory provided' );
} }
// initialize game objects based on the initial map state // initialize game objects based on the initial level state
this._objFactory = obj_factory; this._objFactory = obj_factory;
this._initObjects( map.getObjects(), map.getObjectTileMap() ); this._initObjects( level.getObjects(), level.getObjectTileLevel() );
}, },
/** /**
* Flush map state, triggering the change event for each game object * Flush level state, triggering the change event for each game object
* *
* This may be used to perform an initial rendering or re-draw of the entire * This may be used to perform an initial rendering or re-draw of the
* map. Note that this should not be used to re-render the map after * entire level. Note that this should not be used to re-render the
* movements or state changes, as that would not be performant (especially * level after movements or state changes, as that would not be
* since the map size could be arbitrarily large). * performant (especially since the level size could be arbitrarily
* large).
*/ */
'public flush': function() 'public flush': function()
{ {
var _self = this; const _self = this;
// emit the change event for each game object // emit the change event for each game object
this._forEachObj( function( obj, pos ) this._forEachObj( function( obj, pos )
@ -105,20 +107,20 @@ module.exports = Class( 'MapState',
/** /**
* Register an object change callback * Register an object change callback
* *
* Registers a continuation to be called when a game object changes state * Registers a continuation to be called when a game object changes
* (a state change may represent an object transforming into another, a * state (a state change may represent an object transforming into
* movement, or anything else that requires re-rendering). * another, a movement, or anything else that requires re-rendering).
* *
* TODO: use event base * TODO: use event base
* *
* The continuation will be passed the game object in its new state its tile * The continuation will be passed the game object in its new state its
* position. If the game object is null, then it is to be assumed that the * tile position. If the game object is null, then it is to be assumed
* object no longer exists (e.g. has moved to another location) and should * that the object no longer exists (e.g. has moved to another location)
* be cleared. * and should be cleared.
* *
* @param {function(GameObject,number)} callback continuation * @param {function(GameObject,number)} callback continuation
* *
* @return {MapState} self * @return {LevelState} self
*/ */
'public onChange': function( callback ) 'public onChange': function( callback )
{ {
@ -130,11 +132,11 @@ module.exports = Class( 'MapState',
/** /**
* Emit a change event for the given object * Emit a change event for the given object
* *
* States that an object's state has changed; see the onChange() method for * States that an object's state has changed; see the onChange() method
* additional information. A change may not necessarily imply that the * for additional information. A change may not necessarily imply that
* object itself has changed---the change may simply represent a new * the object itself has changed---the change may simply represent a new
* position or---in the case of a null object---that the position has been * position or---in the case of a null object---that the position has
* cleared of an object. * been cleared of an object.
* *
* @param {GameObject} obj the game object that has updated, or null * @param {GameObject} obj the game object that has updated, or null
* @param {number} pos tile position of the game object * @param {number} pos tile position of the game object
@ -143,8 +145,8 @@ module.exports = Class( 'MapState',
*/ */
'private _emitChange': function( obj, pos ) 'private _emitChange': function( obj, pos )
{ {
var i = -1, const l = this._stateCallbacks.length;
l = this._stateCallbacks.length; let i = -1;
while ( ++i < l ) while ( ++i < l )
{ {
@ -153,7 +155,7 @@ module.exports = Class( 'MapState',
if ( obj === null ) if ( obj === null )
{ {
var _self = this; const _self = this;
this._objs[ pos ].forEach( function( o ) this._objs[ pos ].forEach( function( o )
{ {
if ( o === null ) return; if ( o === null ) return;
@ -165,25 +167,25 @@ module.exports = Class( 'MapState',
/** /**
* Initialize game objects for the map's original (default) state * Initialize game objects for the level's original (default) state
* *
* All necessary game objects will be created to represent all objects on * All necessary game objects will be created to represent all objects
* the map at its default state. Effectively, this creates the default map * on the level at its default state. Effectively, this creates the
* state. * default level state.
* *
* @param {Array.<number>} objs game object data (object ids) * @param {Array.<number>} objs game object data (object ids)
* @param {Array.<string>} objmap map from object ids to their tile ids * @param {Array.<string>} objlevel level from object ids to their tile ids
* *
* @return {undefined} * @return {undefined}
*/ */
'private _initObjects': function( objs, objmap ) 'private _initObjects': function( objs, objlevel )
{ {
var i = objs.length; let i = objs.length;
while ( i-- ) while ( i-- )
{ {
var val = objs[ i ], const val = objs[ i ],
obj = this._createObj( objmap[ val ] ); obj = this._createObj( objlevel[ val ] );
this._objs[ i ] = []; this._objs[ i ] = [];
this._addObj( obj, i ); this._addObj( obj, i );
@ -198,14 +200,14 @@ module.exports = Class( 'MapState',
/** /**
* Creates a game object from a given tile id and (optionally) a previous * Creates a game object from a given tile id and (optionally) a
* object * previous object
* *
* A previous game object may be provided if state is to be transferred * A previous game object may be provided if state is to be transferred
* between objects---that is, the transformation of one game object into * between objects---that is, the transformation of one game object into
* another may require a certain transfer of information. If no such game * another may require a certain transfer of information. If no such
* object is provided, then the game object will be created with its default * game object is provided, then the game object will be created with
* state. * its default state.
* *
* @param {string} tid tile id * @param {string} tid tile id
* @param {GameObject?} from optional game object for state change data * @param {GameObject?} from optional game object for state change data
@ -214,9 +216,10 @@ module.exports = Class( 'MapState',
*/ */
'private _createObj': function( tid, from ) 'private _createObj': function( tid, from )
{ {
var obj = this._objFactory.createObject( tid ); const obj = this._objFactory.createObject( tid );
// if a previous object was provided, copy over its mutable attributes // if a previous object was provided, copy over its mutable
// attributes
if ( from ) if ( from )
{ {
from.cloneTo( obj ); from.cloneTo( obj );
@ -227,7 +230,7 @@ module.exports = Class( 'MapState',
/** /**
* Invokes a continuation for each game object on the map * Invokes a continuation for each game object on the level
* *
* The continuation will be called with the game object and its tile * The continuation will be called with the game object and its tile
* position. * position.
@ -251,7 +254,8 @@ module.exports = Class( 'MapState',
/** /**
* Add a game object at the given tile position * Add a game object at the given tile position
* *
* It is an error to provide an invalid tile position or non-game object. * It is an error to provide an invalid tile position or non-game
* object.
* *
* @param {GameObject} obj game object to add * @param {GameObject} obj game object to add
* @param {number} pos tile position * @param {number} pos tile position
@ -273,22 +277,22 @@ module.exports = Class( 'MapState',
* Replaces the given game object at the given tile position with a new game * Replaces the given game object at the given tile position with a new game
* object * object
* *
* If the given game object can be found at the given tile position, then it * If the given game object can be found at the given tile position,
* will be replaced with the new given game object. If it cannot be found, * then it will be replaced with the new given game object. If it cannot
* then it will be added at the given tile position without any replacement * be found, then it will be added at the given tile position without
* being made (effectively an append), unless the given replacement object * any replacement being made (effectively an append), unless the given
* is null. * replacement object is null.
* *
* If appending, the object will be placed in any open space (represented by * If appending, the object will be placed in any open space
* a null); ``open'' space is created when an object is replaced with null, * (represented by a null); ``open'' space is created when an object is
* which has the effect of removing an object entirely (with no * replaced with null, which has the effect of removing an object
* replacement). * entirely (with no replacement).
* *
* The change will result in the notification of any observers that have * The change will result in the notification of any observers that have
* registered continuations for game object state changes. * registered continuations for game object state changes.
* *
* It is an error to provide an unknown tile position or a replacement that * It is an error to provide an unknown tile position or a replacement
* is not a game object. * that is not a game object.
* *
* @param {GameObject} cur game object to replace (or null) * @param {GameObject} cur game object to replace (or null)
* @param {GameObject} newobj replacement game object * @param {GameObject} newobj replacement game object
@ -301,8 +305,7 @@ module.exports = Class( 'MapState',
*/ */
'private _replaceObj': function( cur, newobj, pos ) 'private _replaceObj': function( cur, newobj, pos )
{ {
var o = this._objs[ pos ], const o = this._objs[ pos ];
i = o.length;
// type checks // type checks
if ( !( Array.isArray( o ) ) ) if ( !( Array.isArray( o ) ) )
@ -316,10 +319,9 @@ module.exports = Class( 'MapState',
throw TypeError( "Invalid GameObject or null provided: " + newobj ); throw TypeError( "Invalid GameObject or null provided: " + newobj );
} }
var free = null; let i = o.length,
free = null;
( function()
{
while ( i-- ) while ( i-- )
{ {
if ( o[ i ] === cur ) if ( o[ i ] === cur )
@ -338,7 +340,6 @@ module.exports = Class( 'MapState',
if ( newobj === null ) return; if ( newobj === null ) return;
else if ( free ) o[ i ] = newobj; else if ( free ) o[ i ] = newobj;
else o.push( newobj ); else o.push( newobj );
} )();
// notify observers of the change // notify observers of the change
this._emitChange( newobj, pos ); this._emitChange( newobj, pos );
@ -369,8 +370,8 @@ module.exports = Class( 'MapState',
/** /**
* Move a game object from one tile position to another * Move a game object from one tile position to another
* *
* This has the direct effect of (a) removing the given game object from its * This has the direct effect of (a) removing the given game object from
* original position and (b) adding it to its new position. * its original position and (b) adding it to its new position.
* *
* It is an error to specify an object that is not a game object, or to * It is an error to specify an object that is not a game object, or to
* specify tile positions that do not exist. * specify tile positions that do not exist.
@ -395,8 +396,8 @@ module.exports = Class( 'MapState',
* Initializes player game object and tile position references * Initializes player game object and tile position references
* *
* These references exist purely for performance, preventing the need to * These references exist purely for performance, preventing the need to
* scan for game objects that may represent the player. This also allows for * scan for game objects that may represent the player. This also allows
* any arbitrary game object to represent the "player". * for any arbitrary game object to represent the "player".
* *
* @param {GameObject} obj game object representing the player * @param {GameObject} obj game object representing the player
* @param {number} pos player tile position * @param {number} pos player tile position
@ -411,11 +412,12 @@ module.exports = Class( 'MapState',
/** /**
* Changes the state of a game object at a given tile position * Changes the state of a game object at a given tile position
* *
* The "state" of a game object is represented by the object's type. If the * The "state" of a game object is represented by the object's type. If
* state is unchanged, then no action will be taken. Otherwise, the game * the state is unchanged, then no action will be taken. Otherwise, the
* object at the given tile position, if available, will be replaced with a * game object at the given tile position, if available, will be
* new game object representing the given state. If a game object cannot be * replaced with a new game object representing the given state. If a
* found at the given tile position, then it will be added. * game object cannot be found at the given tile position, then it will
* be added.
* *
* @param {GameObject} cur current game object * @param {GameObject} cur current game object
* @param {string} state new object state * @param {string} state new object state
@ -435,7 +437,7 @@ module.exports = Class( 'MapState',
} }
// replace game object with a new one // replace game object with a new one
var newobj = this._createObj( state, cur ); const newobj = this._createObj( state, cur );
this._replaceObj( cur, newobj, pos ); this._replaceObj( cur, newobj, pos );
return newobj; return newobj;
@ -448,8 +450,9 @@ module.exports = Class( 'MapState',
* Produces a continuation that will perform a state change on the given * Produces a continuation that will perform a state change on the given
* game object. The desired state should be passed to the continuation. * game object. The desired state should be passed to the continuation.
* *
* If an additional continuation c is provided, it will be invoked after the * If an additional continuation c is provided, it will be invoked after
* state change and may be used to process the resulting game object. * the state change and may be used to process the resulting game
* object.
* *
* @param {GameObject} cur game object to alter * @param {GameObject} cur game object to alter
* @param {number} pos game object tile position * @param {number} pos game object tile position
@ -460,11 +463,11 @@ module.exports = Class( 'MapState',
*/ */
'private _createStateCallback': function( cur, pos, c ) 'private _createStateCallback': function( cur, pos, c )
{ {
var _self = this; const _self = this;
return function( state ) return function( state )
{ {
var newobj = _self._changeState( cur, state, pos ); const newobj = _self._changeState( cur, state, pos );
if ( typeof c === 'function' ) if ( typeof c === 'function' )
{ {
@ -478,11 +481,11 @@ module.exports = Class( 'MapState',
* Creates a callback to move a game object to a new tile position * Creates a callback to move a game object to a new tile position
* *
* Produces a continuation that will perform a tile position move on the * Produces a continuation that will perform a tile position move on the
* given game object. The desired direction of movement should be passed to * given game object. The desired direction of movement should be passed
* the continuation (see MapAction for direction codes). * to the continuation (see LevelAction for direction codes).
* *
* If an additional continuation c is provided, it will be invoked after the * If an additional continuation c is provided, it will be invoked after
* movement and may be used to process the new position. * the movement and may be used to process the new position.
* *
* @param {GameObject} cur game object to alter * @param {GameObject} cur game object to alter
* @param {number} pos game object tile position * @param {number} pos game object tile position
@ -493,7 +496,7 @@ module.exports = Class( 'MapState',
*/ */
'private _createMoveCallback': function( obj, pos, c ) 'private _createMoveCallback': function( obj, pos, c )
{ {
var _self = this; const _self = this;
return function( dest ) return function( dest )
{ {
@ -511,23 +514,23 @@ module.exports = Class( 'MapState',
/** /**
* Move a player in the given direction * Move a player in the given direction
* *
* The directions, as defined in MapAction, are: 0:left, 1:up, 2:right, * The directions, as defined in LevelAction, are: 0:left, 1:up,
* 3:down. * 2:right, 3:down.
* *
* XXX: the bounds argument is temporary * XXX: the bounds argument is temporary
* *
* @param {number} direction direction code * @param {number} direction direction code
* @param {MapBounds} bounds map boundaries * @param {LevelBounds} bounds level boundaries
* *
* @return {undefined} * @return {undefined}
*/ */
'public movePlayer': function( direction, bounds ) 'public movePlayer': function( direction, bounds )
{ {
var _self = this, const _self = this,
player = this._player; player = this._player;
// XXX: tightly coupled // XXX: tightly coupled
var action = MapAction( const action = LevelAction(
bounds, bounds,
this._createMoveCallback( player, this._playerPos, function( pos ) this._createMoveCallback( player, this._playerPos, function( pos )
{ {
@ -535,10 +538,14 @@ module.exports = Class( 'MapState',
} ) } )
); );
var sc = this._createStateCallback( player, this._playerPos, function( o ) const sc = this._createStateCallback(
player,
this._playerPos,
function( o )
{ {
_self._player = o; _self._player = o;
} ); }
);
action.direction = direction; action.direction = direction;
action.srcPos = this._playerPos; action.srcPos = this._playerPos;

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var Class = require( 'easejs' ).Class; const Class = require( 'easejs' ).Class;
/** /**
@ -68,17 +68,16 @@ module.exports = Class( 'MenuBar',
*/ */
'private _initMenuActivation': function() 'private _initMenuActivation': function()
{ {
var _self = this, const id = this._bar.id,
id = this._bar.id,
menus = this._bar.parentNode.querySelectorAll( '#'+id+' > li > a' ), menus = this._bar.parentNode.querySelectorAll( '#'+id+' > li > a' ),
i = menus.length,
click = function( event ) click = function( event )
{ {
event.target.parentNode.parentNode.className += ' focus'; event.target.parentNode.parentNode.className += ' focus';
return false; return false;
}; };
let i = menus.length;
// on menu click, apply focus class (this allows the menu to be opened // on menu click, apply focus class (this allows the menu to be opened
// properly on click rather than a simple CSS hover menu) // properly on click rather than a simple CSS hover menu)
while ( i-- ) while ( i-- )
@ -103,7 +102,7 @@ module.exports = Class( 'MenuBar',
*/ */
'private _hookMenuMouseOut': function() 'private _hookMenuMouseOut': function()
{ {
var _self = this, const _self = this,
bar = this._bar; bar = this._bar;
this._bar.addEventListener( 'mouseout', function( event ) this._bar.addEventListener( 'mouseout', function( event )

View File

@ -19,7 +19,7 @@
* this program. If not, see <http://www.gnu.org/licenses/>. * this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var major = @MAJOR@, let major = @MAJOR@,
minor = @MINOR@, minor = @MINOR@,
rev = @REV@, rev = @REV@,
suffix = '@SUFFIX@', suffix = '@SUFFIX@',

View File

@ -73,12 +73,12 @@
Your browser does not support the canvas element. Your browser does not support the canvas element.
</canvas> </canvas>
<canvas id="canvas_map" width="512" height="512"></canvas> <canvas id="canvas_level" width="512" height="512"></canvas>
<script src="../lasertank.js"></script> <script src="../lasertank.js"></script>
<script> <script>
var ctx = document.getElementById( 'canvas' ).getContext( '2d' ), var ctx = document.getElementById( 'canvas' ).getContext( '2d' ),
ctxmap = document.getElementById( 'canvas_map' ).getContext( '2d' ), ctxlevel = document.getElementById( 'canvas_level' ).getContext( '2d' ),
ltg = document.getElementById( 'ltg' ), ltg = document.getElementById( 'ltg' ),
lvl = document.getElementById( 'lvl' ), lvl = document.getElementById( 'lvl' ),
@ -104,8 +104,11 @@
reader.onload = function( event ) reader.onload = function( event )
{ {
var loader = lasertank.LtgLoader(), var loader = lasertank.LtgLoader(),
masker = lasertank.TileMasker( lasertank.ClassicTileDfn() ),
meta = loader.fromString( event.target.result ), meta = loader.fromString( event.target.result ),
masker = lasertank.TileMasker(
lasertank.ClassicTileDfn(),
document
),
bmp_game = document.getElementById( 'bmp_game' ), bmp_game = document.getElementById( 'bmp_game' ),
bmp_mask = document.getElementById( 'bmp_mas' ); bmp_mask = document.getElementById( 'bmp_mas' );
@ -175,8 +178,8 @@
} }
}, 200 ); }, 200 );
// trigger map change (in case of page refresh or tile set change // trigger level change (in case of page refresh or tile set change
// after map is already loaded) // after level is already loaded)
lvlChange(); lvlChange();
} ); } );
@ -201,18 +204,18 @@
reader.onload = function( event ) reader.onload = function( event )
{ {
var map_set = lasertank.MapSet( var level_set = lasertank.level.LevelSet(
event.target.result, event.target.result,
lasertank.ClassicMap lasertank.level.ClassicLevel
); );
// clean up any previous render and create a new one to render the // clean up any previous render and create a new one to render the
// chosen level with our chosen tile set // chosen level with our chosen tile set
render && render.freeCanvas(); render && render.freeCanvas();
render = lasertank.MapRender( ctxmap, tile_set ); render = lasertank.level.LevelRender( ctxlevel, tile_set );
var lvlsel = document.getElementById( 'lvl_id' ), var lvlsel = document.getElementById( 'lvl_id' ),
count = map_set.getMapCount(); count = level_set.getLevelCount();
lvlsel.innerHTML = ''; lvlsel.innerHTML = '';
for ( var i = 1; i <= count; i++ ) for ( var i = 1; i <= count; i++ )
@ -228,23 +231,23 @@
function lvlchange() function lvlchange()
{ {
var map = map_set.getMapByNumber( lvlsel.selectedIndex + 1 ); var level = level_set.getLevelByNumber( lvlsel.selectedIndex + 1 );
render.render( render.render(
map, level,
lasertank.MapState( lasertank.level.LevelState(
map, level,
lasertank.ClassicGameObjectFactory() lasertank.ClassicGameObjectFactory()
) )
); );
document.getElementById( 'lvl_name' ).innerHTML = document.getElementById( 'lvl_name' ).innerHTML =
map.getMapName(); level.getLevelName();
document.getElementById( 'lvl_author' ).innerHTML = document.getElementById( 'lvl_author' ).innerHTML =
map.getMapAuthor(); level.getLevelAuthor();
document.getElementById( 'lvl_diff' ).innerHTML = document.getElementById( 'lvl_diff' ).innerHTML =
map.getMapDifficulty(); level.getLevelDifficulty();
document.getElementById( 'lvl_hint' ).innerHTML = document.getElementById( 'lvl_hint' ).innerHTML =
map.getMapHint(); level.getLevelHint();
}; };
lvlchange(); lvlchange();