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
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
check:
jshint src/ scripts/
@PATH="$(PATH):$(CURDIR)/node_modules/mocha/bin" \
mocha --recursive $(TESTARGS)

View File

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

View File

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

View File

@ -17,5 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* jshint browser:true */
// 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' ),
ClassicGameObjectFactory = require( './ClassicGameObjectFactory' ),
ClassicMap = require( './ClassicMap' ),
ClassicTileDfn = require( './ClassicTileDfn' ),
LtgLoader = require( './LtgLoader' ),
MapBounds = require( './MapBounds' ),
MapRender = require( './MapRender' ),
MapSet = require( './MapSet' ),
MapState = require( './MapState' ),
ClassicLevel = require( './level/ClassicLevel' ),
LevelBounds = require( './level/LevelBounds' ),
LevelRender = require( './level/LevelRender' ),
LevelSet = require( './level/LevelSet' ),
LevelState = require( './level/LevelState' ),
TileMasker = require( './TileMasker' );
/**
* Facade for the classic (original) game of LaserTank
*/
@ -56,10 +57,10 @@ module.exports = Class( 'ClassicGame' )
'private _tileSet': null,
/**
* Set of available maps
* @type {MapSet}
* Set of available levels
* @type {LevelSet}
*/
'private _mapSet': null,
'private _levelSet': null,
/**
* Event handlers
@ -68,8 +69,8 @@ module.exports = Class( 'ClassicGame' )
'private _callback': {},
/**
* Performs map rendering
* @type {MapRender}
* Performs level rendering
* @type {LevelRender}
*/
'private _render': null,
@ -83,26 +84,31 @@ module.exports = Class( 'ClassicGame' )
/**
* Initialize game with LTG and LVL data
*
* The LTG and LVL data can be changed at any time, but are required in the
* constructor because they are needed in order for the game to be
* The LTG and LVL data can be changed at any time, but are required in
* the constructor because they are needed in order for the game to be
* 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} 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._masker = TileMasker( ClassicTileDfn() );
this._masker = TileMasker( ClassicTileDfn(), document );
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.setMapData( lvl_data, function()
this.setLevelData( lvl_data, function()
{
_self._trigger( 'ready' );
} );
@ -113,32 +119,35 @@ module.exports = Class( 'ClassicGame' )
/**
* Render to the given 2d canvas context
*
* EVENT_TARGET will be monitored for keypresses.
*
* @param {CanvasRenderingContext2d} ctx 2d canvas context
* @param {*} event_target keyboard event target
*
* @return {ClassicGame} self
*/
'public renderTo': function( ctx )
'public renderTo': function( ctx, event_target )
{
// if there is a previous renderer, free its canvas before continuing
// (to both clean up and to free any locks, allowing for tile set and
// map changes)
// if there is a previous renderer, free its canvas before
// continuing (to both clean up and to free any locks, allowing for
// tile set and level changes)
if ( this._render )
{
this._render.clearCanvas();
}
var map = this._mapSet.getMapByNumber( 1 ),
map_state = MapState( map, this._gameObjFactory ),
bounds = MapBounds( map );
const level = this._levelSet.getLevelByNumber( 1 ),
level_state = LevelState( level, this._gameObjFactory ),
bounds = LevelBounds( level );
// render the first map (hardcoded for now)
this._render = MapRender( ctx, this._tileSet )
.render( map, map_state );
// render the first level (hardcoded for now)
this._render = LevelRender( ctx, this._tileSet )
.render( level, level_state );
// POC
window.onkeydown = function( event )
event_target.onkeydown = function( event )
{
var dir;
let dir;
switch ( event.keyCode )
{
@ -153,7 +162,8 @@ module.exports = Class( 'ClassicGame' )
return;
}
map_state.movePlayer( dir, bounds );
event.preventDefault();
level_state.movePlayer( dir, bounds );
};
return this;
@ -171,7 +181,7 @@ module.exports = Class( 'ClassicGame' )
'public setTileData': function( data, callback )
{
// get tile metadata
var _self = this,
const _self = this,
meta = this._ltgLoader.fromString( data );
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 {function()} callback function to call when complete
*
* @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 );
return this;

View File

@ -17,7 +17,7 @@
* 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' ),
GameObject = require( './gameobjs/GameObject' ),
Tank = require( './gameobjs/Tank' );

View File

@ -17,7 +17,7 @@
* 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' );

View File

@ -21,7 +21,7 @@
* 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
*
* @param {HtmlInputElement} element file element to monitor
* @param {FileReader} reader file reader
*/
__construct: function( element )
__construct: function( element, reader )
{
if ( element.type !== 'file' )
{
@ -58,6 +59,7 @@ module.exports = Class( 'FileLoader',
}
this._element = element;
this._reader = reader;
},
@ -100,23 +102,23 @@ module.exports = Class( 'FileLoader',
*/
'private _loadFile': function( event )
{
var _self = this,
const _self = this,
files = event.target.files;
if ( files.length === 0 ) return;
var reader = new FileReader();
reader.onload = function( revent )
this._reader.onload = function( revent )
{
_self._callback.call( this.__inst, null, revent.target.result );
};
reader.onerror = function( e )
this._reader.onerror = function( e )
{
_self._callback.call( this.__inst, e );
};
// 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/>.
*/
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 {function()} callback function to call when complete
*
* @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/>.
*/
var Interface = require( 'easejs' ).Interface;
const Interface = require( 'easejs' ).Interface;
module.exports = Interface( 'GameObjectFactory',

View File

@ -42,7 +42,10 @@
* 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 )
{
var mask_offset = this._getMaskOffsetFromData( ltg_data );
const mask_offset = this._getMaskOffsetFromData( ltg_data );
return {
name: this._getNameFromData( ltg_data ),
@ -112,7 +115,7 @@ module.exports = Class( 'LtgLoader',
sgmt = this.__self.$( sgmt );
}
var data = String.prototype.substr.apply( ltg_data, sgmt );
const data = String.prototype.substr.apply( ltg_data, sgmt );
return ( stripnull )
? 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
// function the same with them stripped, but let's avoid the confusion
// since we are supposed to be working with a 32-bit value)
var data = this._getDataSegment( ltg_data, '_POS_MOFF', false ),
i = data.length,
const data = this._getDataSegment( ltg_data, '_POS_MOFF', false );
let i = data.length,
offset = 0;
// 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 )
{
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 ltg_data.substr( bmp_offset, ( mask_offset - bmp_offset ) );

View File

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

View File

@ -63,7 +63,7 @@
* rendering.
*/
var Class = require( 'easejs' ).Class,
const Class = require( 'easejs' ).Class,
TileDfn = require( './TileDfn' );
/**
@ -77,6 +77,12 @@ module.exports = Class( 'TileMasker',
*/
'private _context': null,
/**
* DOM document
* @type {HTMLDocument}
*/
'private _document': null,
/**
* Tile definition to use for all operations
* @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
* 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 {HTMLDocument} document DOM document
*/
__construct: function( tile_dfn )
__construct: function( tile_dfn, document )
{
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
// memory to perform our operations (it will not be added to the DOM, so
// 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
context.canvas.width = this._setWidth;
context.canvas.height = this._setHeight;
this._context = context;
this._document = document;
},
@ -161,7 +172,7 @@ module.exports = Class( 'TileMasker',
'private _calcSetDimensions': function( tile_dfn )
{
// these vars are for clarity
var sizes = tile_dfn.getTileDimensions(),
const sizes = tile_dfn.getTileDimensions(),
n = this._tileDfn.length;
// 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 )
{
var _self = this;
const _self = this;
this._getImageData( bmp_mask, function( data_mask )
{
@ -248,9 +259,7 @@ module.exports = Class( 'TileMasker',
*/
'virtual protected getMaskedTileSet': function( data_mask, callback )
{
var tdata = this._tileDfn,
tiles = {},
i = -1,
const tdata = this._tileDfn,
len = tdata.length,
// shorten the names
@ -258,15 +267,18 @@ module.exports = Class( 'TileMasker',
th = this._tileHeight,
xn = this._tilesPerRow;
let tiles = {},
i = -1;
// create each tile (preserving order, thus no decrementing)
while ( ++i < len )
{
var id = tdata[ i ][ 0 ],
mask = tdata[ i ][ 1 ],
const id = tdata[ i ][ 0 ],
mask = tdata[ i ][ 1 ];
// calculate the X and Y position of this tile based on the tile
// and bitmap dimensions
x = ( ( i % xn ) * th ),
const x = ( ( i % xn ) * th ),
y = ( ( Math.floor( i / xn ) ) * tw );
// 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 )
{
var prev = set[ id ];
const prev = set[ id ];
set[ id ] = {
data: data,
@ -314,7 +326,7 @@ module.exports = Class( 'TileMasker',
// if there was a previous entry, set its 'next' entry to our new frame,
// 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 )
{
var raw = this.getTileData( x, y ),
const raw = this.getTileData( x, y ),
w = raw.width,
h = raw.height,
mw = data_mask.width,
yi = h;
mw = data_mask.width;
let yi = h;
// 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
// pixel
while ( yi-- )
{
xi = w;
let xi = w;
while ( xi-- )
{
@ -366,7 +379,7 @@ module.exports = Class( 'TileMasker',
// (remember that, although we are dealing with applying the
// mask to a single tile, the mask image contains all tiles, so
// 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 ];
// 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 )
{
var _self = this,
img = new Image();
const _self = this,
img = this._document.createElement( 'img' );
img.onload = function()
{
@ -437,7 +450,7 @@ module.exports = Class( 'TileMasker',
*/
'private _getImageData': function( bmp, callback )
{
var _self = this;
const _self = this;
this._renderImage( bmp, function()
{

View File

@ -17,7 +17,7 @@
* 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',
@ -44,6 +44,7 @@ module.exports = Class( 'GameObject',
},
/* jshint -W098 */
'virtual public move': function( dir, c, sc )
{
// move in the appropriate direction (action has been pre-configured
@ -52,4 +53,5 @@ module.exports = Class( 'GameObject',
return this;
}
/* jshint +W098 */
} );

View File

@ -17,7 +17,7 @@
* 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' );
@ -26,12 +26,12 @@ module.exports = Class( 'Tank' )
{
'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() )
{
sc( state );
return;
return this;
}
// let parent handle the movement

View File

@ -17,7 +17,7 @@
* 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' );

View File

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

View File

@ -17,7 +17,7 @@
* 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:
*
* - 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.
*/
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
* data associated with the requested map. This allows the data to be lazily
* sliced out of the map file.
* Levels simply act as basic wrappers around a set of maps, returning only the
* data associated with the requested level. This allows the data to be lazily
* sliced out of the level file.
*
* Note that this interface specifies a constructor definition; this allows it
* 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 {number} id 1-indexed map id
* @param {LevelSet} set level set data
* @param {number} id 1-indexed level 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
*/
@ -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>}
*/
'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
* 4, with 0 representing "kids" and 4 representing "deadly".
* The level difficulty should be returned as a 0-indexed value between
* 0 and 4, with 0 representing "kids" and 4 representing "deadly".
*
* @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/>.
*/
var Class = require( 'easejs' ).Class,
MapBounds = require( './MapBounds' );
const Class = require( 'easejs' ).Class,
LevelBounds = require( './LevelBounds' );
module.exports = Class( 'MapAction',
module.exports = Class( 'LevelAction',
{
// arranged by keycode
'const D__MIN': 0,
@ -43,9 +43,9 @@ module.exports = Class( 'MapAction',
__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' );
@ -57,7 +57,7 @@ module.exports = Class( 'MapAction',
'public move': function()
{
var method = [
const method = [
'getLeftPos',
'getUpperPos',
'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
*
@ -17,45 +17,46 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var Class = require( 'easejs' ).Class,
Map = require( './Map' );
const Class = require( 'easejs' ).Class,
Level = require( './Level' );
/**
* Calculates map bounding box
* Calculates level bounding box
*
* This simply encapsulates the process of determining whether a given position
* is against an edge of the map.
* This simply encapsulates the process of determining whether a given
* 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}
*/
'private _mw': 0,
/**
* Map height (number of tiles)
* Level height (number of tiles)
* @type {number}
*/
'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
var dimen = map.getDimensions();
// we are only interested in the dimensions of the level
const dimen = level.getDimensions();
this._mw = dimen[ 0 ];
this._mh = dimen[ 1 ];
},
@ -64,7 +65,7 @@ module.exports = Class( 'MapBounds',
/**
* 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.
*
* @param {number} pos original tile position
@ -82,8 +83,8 @@ module.exports = Class( 'MapBounds',
/**
* Retrieve the tile position below the given position
*
* If the given tile position is at the bottom of the map, then the given
* position will be returned.
* If the given tile position is at the bottom of the level, then the
* given position will be returned.
*
* @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
*
* If the given tile position is at the leftmost column of the map, then the
* given position will be returned.
* If the given tile position is at the leftmost column of the level,
* then the given position will be returned.
*
* @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
*
* If the given tile position is at the rightmost column of the map, then
* the given position will be returned.
* If the given tile position is at the rightmost column of the level,
* then the given position will be returned.
*
* @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
*
@ -144,14 +145,14 @@ module.exports = Class( 'MapBounds',
*/
'public isAtTop': function( pos )
{
// since tile positions are zero-indexed, we know that we're at the top
// if the map height divides the position
// since tile positions are zero-indexed, we know that we're at the
// top if the level height divides the position
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
*
@ -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
*
@ -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
*

View File

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

View File

@ -17,22 +17,20 @@
* 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
* file.
* Levels are stored in a fixed-width LVL file.
*/
var Class = require( 'easejs' ).Class;
const Class = require( 'easejs' ).Class;
/**
* Handles delegation of LVL data
*
* This acts as a Map factory, leaving all processing responsibilities to the
* Map instance. Consequently, any custom map format would be supported,
* provided that the appropriate Map is handling it.
* This acts as a Level factory, leaving all processing responsibilities to
* the Level instance. Consequently, any custom level format would be
* supported, provided that the appropriate Level is handling it.
*/
module.exports = Class( 'MapSet',
module.exports = Class( 'LevelSet',
{
/**
* Raw level set data (binary)
@ -41,61 +39,62 @@ module.exports = Class( 'MapSet',
'private _data': '',
/**
* Constructor used to create new map instances
* Constructor used to create new level instances
* @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}
*/
'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 {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._mapCtor = map_ctor;
this._levelCtor = level_ctor;
// 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
* invalid, or if the LVL data is invalid for the given Map constructor.
* The check will simply ensure that the map size (in bytes) divides into
* the total LVL size (in bytes) without any remainder.
* invalid, or if the LVL data is invalid for the given Level
* constructor. The check will simply ensure that the level size (in
* bytes) divides into the total LVL size (in bytes) without any
* remainder.
*
* This is by no means fool-proof, but it should catch most.
*
* @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
// file is corrupt, or we were given the wrong Map constructor
// if the result is not an integer, then it is either not an LVL,
// the file is corrupt, or we were given the wrong Level constructor
if ( n % 1 )
{
throw Error( 'Invalid or corrupt LVL data' );
}
// 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
*/
'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
*
@ -17,16 +17,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var Class = require( 'easejs' ).Class,
MapAction = require( './MapAction' ),
GameObjectFactory = require( './GameObjectFactory' ),
GameObject = require( './gameobjs/GameObject' );
const Class = require( 'easejs' ).Class,
LevelAction = require( './LevelAction' ),
GameObjectFactory = require( '../GameObjectFactory' ),
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
@ -47,7 +47,7 @@ module.exports = Class( 'MapState',
'private _player': null,
/**
* Game objects representing every object on the map
* Game objects representing every object on the level
* @type {GameObject}
*/
'private _objs': [],
@ -60,39 +60,41 @@ module.exports = Class( 'MapState',
/**
* Initializes map state with a given map and factory with which to create
* game objects
* Initializes level state with a given level and factory with which to
* create game objects
*
* Game objects influence map state rules, therefore fundamental game logic
* may be altered simply by passing in a custom GameObjectFactory instance.
* Game objects influence level state rules, therefore fundamental game
* 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
*/
__construct: function( map, obj_factory )
__construct: function( level, obj_factory )
{
if ( !( Class.isA( GameObjectFactory, obj_factory ) ) )
{
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._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
* map. Note that this should not be used to re-render the map after
* movements or state changes, as that would not be performant (especially
* since the map size could be arbitrarily large).
* This may be used to perform an initial rendering or re-draw of the
* entire level. Note that this should not be used to re-render the
* level after movements or state changes, as that would not be
* performant (especially since the level size could be arbitrarily
* large).
*/
'public flush': function()
{
var _self = this;
const _self = this;
// emit the change event for each game object
this._forEachObj( function( obj, pos )
@ -105,20 +107,20 @@ module.exports = Class( 'MapState',
/**
* Register an object change callback
*
* Registers a continuation to be called when a game object changes state
* (a state change may represent an object transforming into another, a
* movement, or anything else that requires re-rendering).
* Registers a continuation to be called when a game object changes
* state (a state change may represent an object transforming into
* another, a movement, or anything else that requires re-rendering).
*
* TODO: use event base
*
* The continuation will be passed the game object in its new state its tile
* position. If the game object is null, then it is to be assumed that the
* object no longer exists (e.g. has moved to another location) and should
* be cleared.
* The continuation will be passed the game object in its new state its
* tile position. If the game object is null, then it is to be assumed
* that the object no longer exists (e.g. has moved to another location)
* and should be cleared.
*
* @param {function(GameObject,number)} callback continuation
*
* @return {MapState} self
* @return {LevelState} self
*/
'public onChange': function( callback )
{
@ -130,11 +132,11 @@ module.exports = Class( 'MapState',
/**
* Emit a change event for the given object
*
* States that an object's state has changed; see the onChange() method for
* additional information. A change may not necessarily imply that 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
* cleared of an object.
* States that an object's state has changed; see the onChange() method
* for additional information. A change may not necessarily imply that
* 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 cleared of an object.
*
* @param {GameObject} obj the game object that has updated, or null
* @param {number} pos tile position of the game object
@ -143,8 +145,8 @@ module.exports = Class( 'MapState',
*/
'private _emitChange': function( obj, pos )
{
var i = -1,
l = this._stateCallbacks.length;
const l = this._stateCallbacks.length;
let i = -1;
while ( ++i < l )
{
@ -153,7 +155,7 @@ module.exports = Class( 'MapState',
if ( obj === null )
{
var _self = this;
const _self = this;
this._objs[ pos ].forEach( function( o )
{
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
* the map at its default state. Effectively, this creates the default map
* state.
* All necessary game objects will be created to represent all objects
* on the level at its default state. Effectively, this creates the
* default level state.
*
* @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}
*/
'private _initObjects': function( objs, objmap )
'private _initObjects': function( objs, objlevel )
{
var i = objs.length;
let i = objs.length;
while ( i-- )
{
var val = objs[ i ],
obj = this._createObj( objmap[ val ] );
const val = objs[ i ],
obj = this._createObj( objlevel[ val ] );
this._objs[ 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
* object
* Creates a game object from a given tile id and (optionally) a
* previous object
*
* A previous game object may be provided if state is to be transferred
* between objects---that is, the transformation of one game object into
* another may require a certain transfer of information. If no such game
* object is provided, then the game object will be created with its default
* state.
* another may require a certain transfer of information. If no such
* game object is provided, then the game object will be created with
* its default state.
*
* @param {string} tid tile id
* @param {GameObject?} from optional game object for state change data
@ -214,9 +216,10 @@ module.exports = Class( 'MapState',
*/
'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 )
{
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
* position.
@ -251,7 +254,8 @@ module.exports = Class( 'MapState',
/**
* 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 {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
* object
*
* If the given game object can be found at the given tile position, then it
* will be replaced with the new given game object. If it cannot be found,
* then it will be added at the given tile position without any replacement
* being made (effectively an append), unless the given replacement object
* is null.
* If the given game object can be found at the given tile position,
* then it will be replaced with the new given game object. If it cannot
* be found, then it will be added at the given tile position without
* any replacement being made (effectively an append), unless the given
* replacement object is null.
*
* If appending, the object will be placed in any open space (represented by
* a null); ``open'' space is created when an object is replaced with null,
* which has the effect of removing an object entirely (with no
* replacement).
* If appending, the object will be placed in any open space
* (represented by a null); ``open'' space is created when an object is
* replaced with null, which has the effect of removing an object
* entirely (with no replacement).
*
* The change will result in the notification of any observers that have
* registered continuations for game object state changes.
*
* It is an error to provide an unknown tile position or a replacement that
* is not a game object.
* It is an error to provide an unknown tile position or a replacement
* that is not a game object.
*
* @param {GameObject} cur game object to replace (or null)
* @param {GameObject} newobj replacement game object
@ -301,8 +305,7 @@ module.exports = Class( 'MapState',
*/
'private _replaceObj': function( cur, newobj, pos )
{
var o = this._objs[ pos ],
i = o.length;
const o = this._objs[ pos ];
// type checks
if ( !( Array.isArray( o ) ) )
@ -316,10 +319,9 @@ module.exports = Class( 'MapState',
throw TypeError( "Invalid GameObject or null provided: " + newobj );
}
var free = null;
let i = o.length,
free = null;
( function()
{
while ( i-- )
{
if ( o[ i ] === cur )
@ -338,7 +340,6 @@ module.exports = Class( 'MapState',
if ( newobj === null ) return;
else if ( free ) o[ i ] = newobj;
else o.push( newobj );
} )();
// notify observers of the change
this._emitChange( newobj, pos );
@ -369,8 +370,8 @@ module.exports = Class( 'MapState',
/**
* Move a game object from one tile position to another
*
* This has the direct effect of (a) removing the given game object from its
* original position and (b) adding it to its new position.
* This has the direct effect of (a) removing the given game object from
* 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
* specify tile positions that do not exist.
@ -395,8 +396,8 @@ module.exports = Class( 'MapState',
* Initializes player game object and tile position references
*
* These references exist purely for performance, preventing the need to
* scan for game objects that may represent the player. This also allows for
* any arbitrary game object to represent the "player".
* scan for game objects that may represent the player. This also allows
* for any arbitrary game object to represent the "player".
*
* @param {GameObject} obj game object representing the player
* @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
*
* The "state" of a game object is represented by the object's type. If the
* state is unchanged, then no action will be taken. Otherwise, the game
* object at the given tile position, if available, will be replaced with a
* new game object representing the given state. If a game object cannot be
* found at the given tile position, then it will be added.
* The "state" of a game object is represented by the object's type. If
* the state is unchanged, then no action will be taken. Otherwise, the
* game object at the given tile position, if available, will be
* replaced with a new game object representing the given state. If a
* game object cannot be found at the given tile position, then it will
* be added.
*
* @param {GameObject} cur current game object
* @param {string} state new object state
@ -435,7 +437,7 @@ module.exports = Class( 'MapState',
}
// replace game object with a new one
var newobj = this._createObj( state, cur );
const newobj = this._createObj( state, cur );
this._replaceObj( cur, newobj, pos );
return newobj;
@ -448,8 +450,9 @@ module.exports = Class( 'MapState',
* Produces a continuation that will perform a state change on the given
* game object. The desired state should be passed to the continuation.
*
* If an additional continuation c is provided, it will be invoked after the
* state change and may be used to process the resulting game object.
* If an additional continuation c is provided, it will be invoked after
* the state change and may be used to process the resulting game
* object.
*
* @param {GameObject} cur game object to alter
* @param {number} pos game object tile position
@ -460,11 +463,11 @@ module.exports = Class( 'MapState',
*/
'private _createStateCallback': function( cur, pos, c )
{
var _self = this;
const _self = this;
return function( state )
{
var newobj = _self._changeState( cur, state, pos );
const newobj = _self._changeState( cur, state, pos );
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
*
* Produces a continuation that will perform a tile position move on the
* given game object. The desired direction of movement should be passed to
* the continuation (see MapAction for direction codes).
* given game object. The desired direction of movement should be passed
* to the continuation (see LevelAction for direction codes).
*
* If an additional continuation c is provided, it will be invoked after the
* movement and may be used to process the new position.
* If an additional continuation c is provided, it will be invoked after
* the movement and may be used to process the new position.
*
* @param {GameObject} cur game object to alter
* @param {number} pos game object tile position
@ -493,7 +496,7 @@ module.exports = Class( 'MapState',
*/
'private _createMoveCallback': function( obj, pos, c )
{
var _self = this;
const _self = this;
return function( dest )
{
@ -511,23 +514,23 @@ module.exports = Class( 'MapState',
/**
* Move a player in the given direction
*
* The directions, as defined in MapAction, are: 0:left, 1:up, 2:right,
* 3:down.
* The directions, as defined in LevelAction, are: 0:left, 1:up,
* 2:right, 3:down.
*
* XXX: the bounds argument is temporary
*
* @param {number} direction direction code
* @param {MapBounds} bounds map boundaries
* @param {LevelBounds} bounds level boundaries
*
* @return {undefined}
*/
'public movePlayer': function( direction, bounds )
{
var _self = this,
const _self = this,
player = this._player;
// XXX: tightly coupled
var action = MapAction(
const action = LevelAction(
bounds,
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;
} );
}
);
action.direction = direction;
action.srcPos = this._playerPos;

View File

@ -17,7 +17,7 @@
* 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()
{
var _self = this,
id = this._bar.id,
const id = this._bar.id,
menus = this._bar.parentNode.querySelectorAll( '#'+id+' > li > a' ),
i = menus.length,
click = function( event )
{
event.target.parentNode.parentNode.className += ' focus';
return false;
};
let i = menus.length;
// on menu click, apply focus class (this allows the menu to be opened
// properly on click rather than a simple CSS hover menu)
while ( i-- )
@ -103,7 +102,7 @@ module.exports = Class( 'MenuBar',
*/
'private _hookMenuMouseOut': function()
{
var _self = this,
const _self = this,
bar = this._bar;
this._bar.addEventListener( 'mouseout', function( event )

View File

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

View File

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