diff --git a/lib/ClassicTileDfn.js b/lib/ClassicTileDfn.js new file mode 100644 index 0000000..32d98d5 --- /dev/null +++ b/lib/ClassicTileDfn.js @@ -0,0 +1,125 @@ +/** + * Contains tile definition for classic (original game) tile sets + * + * Copyright (C) 2012 Mike Gerwitz + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +/** + * Definition for classic tile sets found in the original game + */ +ltjs.ClassicTileDfn = Class( 'ClassicTileDfn' ) + .implement( ltjs.TileDfn ) + .extend( +{ + /** + * Retrieve the tile definition + * + * The definition should be an array of arrays, with the first index + * representing the tile id and the second index representing whether or not + * a mask should be applied (0 or 1). A list of standard tile ids are + * provided at the top of this file. + * + * The tiles are expected to be parsed per row, beginning at the upper-left + * corner. Specifying the tiles in this manner is both concise and helps to + * eliminate human error and maintenance concerns that may arise from the + * manual specification of tile coordinates. + * + * @return {Array.>} tile definition + */ + 'public getTileDefinition': function() + { + return [ + [ 'dirt', 0 ], /* dirt */ + [ 'tup', 1 ], /* tank up */ + [ 'tright', 1 ], /* tank right */ + [ 'tdown', 1 ], /* tank down */ + [ 'tleft', 1 ], /* tank left */ + [ 'base', 0 ], /* base */ + [ 'basealt', 0 ], /* base alt */ + [ 'basealt2', 0 ], /* base alt 2 */ + [ 'water', 0 ], /* water */ + [ 'wateralt', 0 ], /* water alt */ + + [ 'wateralt2', 0 ], /* water alt 2 */ + [ 'atdownb', 1 ], /* anti-tank, blown up, down */ + [ 'block', 0 ], /* non-movable block */ + [ 'mblock', 0 ], /* movable block */ + [ 'brick', 0 ], /* brick */ + [ 'atup', 1 ], /* anti-tank up */ + [ 'atupalt', 1 ], /* anti-tank up alt */ + [ 'atupalt2', 1 ], /* anti-tank up alt 2 */ + [ 'mblockw', 0 ], /* movable block in water */ + [ 'mirrorul', 1 ], /* mirror up-left */ + + [ 'mirrorur', 1 ], /* mirror up-right */ + [ 'mirrordr', 1 ], /* mirror down-right */ + [ 'mirrordl', 1 ], /* mirror down-left */ + [ 'owup', 0 ], /* one-way up */ + [ 'owupalt', 0 ], /* one-way up alt */ + [ 'owupalt2', 0 ], /* one-way up alt 2 */ + [ 'owright', 0 ], /* one-way right */ + [ 'owrightalt', 0 ], /* one-way right alt */ + [ 'owrightalt2', 0 ], /* one-way right alt 2 */ + [ 'owdown', 0 ], /* one-way down */ + + [ 'owdownalt', 0 ], /* one-way down alt */ + [ 'owdownalt2', 0 ], /* one-way down alt 2 */ + [ 'owleft', 0 ], /* one-way left */ + [ 'owleftalt', 0 ], /* one-way left alt */ + [ 'owleftalt2', 0 ], /* one-way left alt 3 */ + [ 'atright', 1 ], /* anti-tank right */ + [ 'atrightalt', 1 ], /* anti-tank right alt */ + [ 'atrightalt2', 1 ], /* anti-tank right alt 2 */ + [ 'atdown', 1 ], /* anti-tank down */ + [ 'atdownalt', 1 ], /* anti-tank down alt */ + + [ 'atdownalt2', 1 ], /* anti-tank down alt 2 */ + [ 'atleft', 1 ], /* anti-tank left */ + [ 'atleftalt', 1 ], /* anti-tank left alt */ + [ 'atleftalt2', 1 ], /* anti-tank left alt 2 */ + [ 'cblock', 0 ], /* crystal block */ + [ 'cblockht', 0 ], /* crystal block hit by tank */ + [ 'rmirrorul', 0 ], /* roto-mirror up-left */ + [ 'rmirrorur', 0 ], /* roto-mirror up-right */ + [ 'rmirrordr', 0 ], /* roto-mirror down-right */ + [ 'rmirrordl', 0 ], /* roto-mirror down-left */ + + [ 'cblockhat', 0 ], /* crystal block hit by anti-tank */ + [ 'atrightb', 1 ], /* anti-tank, blown up, right */ + [ 'atleftb', 1 ], /* anti-tank, blown up, left */ + [ 'atupb', 1 ], /* anti-tank, blown up, up */ + [ 'tunnel', 1 ], /* wormhole/tunnel */ + [ 'ice', 0 ], /* ice */ + [ 'thinice', 0 ] /* thin ice */ + ]; + }, + + + /** + * Retrieve tile dimensions + * + * This method should return an array containing three values: the width and + * height of the individual tiles and the number of tiles per row. From this + * data, any individual tile can be extracted from the tile set. + * + * @return {Array.} tile width, tile height, tiles per row + */ + 'public getTileDimensions': function() + { + return [ 32, 32, 10 ]; + } +} ); diff --git a/lib/TileDfn.js b/lib/TileDfn.js new file mode 100644 index 0000000..9a2e53c --- /dev/null +++ b/lib/TileDfn.js @@ -0,0 +1,137 @@ +/** + * Contains tile definition interface + * + * Copyright (C) 2012 Mike Gerwitz + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * + * This interface provides a definition for the processing of tile sets. + * Specifically, it includes the width and height of each individual tile, the + * number of tiles per row, the ids to map each tile to and a flag indicating + * whether or not the tile should have a mask applied to it. + * + * The true benefit of this interface is that it abstracts the tile + * interpretation logic in such a way that *any* tile set is supported. That is, + * should someone decide to support entirely separate tile sets (perhaps to + * add additional features to the game, or even to simply restructure the tile + * positions of the current one), we have them covered. + * + * Below is a list of standard ids that must be mapped for classic game play. + * The *alt tiles are used in animation (the terminology comes from the original + * game sources). + * + * dirt - dirt + * tup - tank up + * tright - tank right + * tdown - tank down + * tleft - tank left + * base - base + * basealt - base alt + * basealt2 - base alt 2 + * water - water + * wateralt - water alt + * + * wateralt2 - water alt 2 + * atdownb - anti-tank down + * block - block + * mblock - movable block + * brick - brick + * atup - anti-tank up + * atupalt - anti-tank up alt + * atupalt2 - anti-tank up alt 2 + * mblockw - movable block in water + * mirrorul - mirror up-left + * + * mirrorur - mirror up-right + * mirrordr - mirror down-right + * mirrordl - mirror down-left + * owup - one-way up + * owupalt - one-way up alt + * owupalt2 - one-way up alt 2 + * owright - one-way right + * owrightalt - one-way right alt + * owrightalt2 - one-way right alt 2 + * owdown - one-way down + * + * owdownalt - one-way down alt + * owdownalt2 - one-way down alt 2 + * owleft - one-way left + * owleftalt - one-way left alt + * owleftalt2 - one-way left alt 3 + * atright - anti-tank right + * atrightalt - anti-tank right alt + * atrightalt2 - anti-tank right alt 2 + * atdown - anti-tank down + * atdownalt - anti-tank down alt + * + * atdownalt2 - anti-tank down alt 2 + * atleft - anti-tank left + * atleftalt - anti-tank left alt + * atleftalt2 - anti-tank left alt 2 + * cblock - crystal block + * cblockht - crystal block hit by tank + * rmirrorul - roto-mirror up-left + * rmirrorur - roto-mirror up-right + * rmirrordr - roto-mirror down-right + * rmirrordl - roto-mirror down-left + * + * cblockhat - crystal block hit by anti-tank + * atrightb - anti-tank, blown up, right + * atleftb - anti-tank, blown up, left + * atupb - anti-tank, blown up, up + * tunnel - wormhole/tunnel + * ice - ice + * thinice - thin ice + */ + + +/** + * Defines the contents of a tile set + * + * The tile set is expected to be an image of any width (W) and height (H) + * containing individual tiles (of width w and height h) such that W % w === 0 + * and H % h === 0. All tiles are expected to share the same dimensions. + */ +ltjs.TileDfn = Interface( 'TileDfn', +{ + /** + * Retrieve the tile definition + * + * The definition should be an array of arrays, with the first index + * representing the tile id and the second index representing whether or not + * a mask should be applied (0 or 1). A list of standard tile ids are + * provided at the top of this file. + * + * The tiles are expected to be parsed per row, beginning at the upper-left + * corner. Specifying the tiles in this manner is both concise and helps to + * eliminate human error and maintenance concerns that may arise from the + * manual specification of tile coordinates. + * + * @return {Array.>} tile definition + */ + 'public getTileDefinition': [], + + + /** + * Retrieve tile dimensions + * + * This method should return an array containing three values: the width and + * height of the individual tiles and the number of tiles per row. From this + * data, any individual tile can be extracted from the tile set. + * + * @return {Array.} tile width, tile height, tiles per row + */ + 'public getTileDimensions': [] +} ); diff --git a/lib/TileMasker.js b/lib/TileMasker.js index 1056518..c577ef5 100644 --- a/lib/TileMasker.js +++ b/lib/TileMasker.js @@ -68,102 +68,113 @@ */ ltjs.TileMasker = Class( 'TileMasker', { - 'private const _TDATA': [ - [ 'dirt', 0 ], /* dirt */ - [ 'tup', 1 ], /* tank up */ - [ 'tright', 1 ], /* tank right */ - [ 'tdown', 1 ], /* tank down */ - [ 'tleft', 1 ], /* tank left */ - [ 'base', 0 ], /* base */ - [ 'basealt', 0 ], /* base alt */ - [ 'basealt2', 0 ], /* base alt 2 */ - [ 'water', 0 ], /* water */ - [ 'wateralt', 0 ], /* water alt */ - - [ 'wateralt2', 0 ], /* water alt 2 */ - [ 'atdownb', 1 ], /* anti-tank, blown up, down */ - [ 'block', 0 ], /* non-movable block */ - [ 'mblock', 0 ], /* movable block */ - [ 'brick', 0 ], /* brick */ - [ 'atup', 1 ], /* anti-tank up */ - [ 'atupalt', 1 ], /* anti-tank up alt */ - [ 'atupalt2', 1 ], /* anti-tank up alt 2 */ - [ 'mblockw', 0 ], /* movable block in water */ - [ 'mirrorul', 1 ], /* mirror up-left */ - - [ 'mirrorur', 1 ], /* mirror up-right */ - [ 'mirrordr', 1 ], /* mirror down-right */ - [ 'mirrordl', 1 ], /* mirror down-left */ - [ 'owup', 0 ], /* one-way up */ - [ 'owupalt', 0 ], /* one-way up alt */ - [ 'owupalt2', 0 ], /* one-way up alt 2 */ - [ 'owright', 0 ], /* one-way right */ - [ 'owrightalt', 0 ], /* one-way right alt */ - [ 'owrightalt2', 0 ], /* one-way right alt 2 */ - [ 'owdown', 0 ], /* one-way down */ - - [ 'owdownalt', 0 ], /* one-way down alt */ - [ 'owdownalt2', 0 ], /* one-way down alt 2 */ - [ 'owleft', 0 ], /* one-way left */ - [ 'owleftalt', 0 ], /* one-way left alt */ - [ 'owleftalt2', 0 ], /* one-way left alt 2 */ - [ 'atright', 1 ], /* anti-tank right */ - [ 'atrightalt', 1 ], /* anti-tank right alt */ - [ 'atrightalt2', 1 ], /* anti-tank right alt 2 */ - [ 'atdown', 1 ], /* anti-tank down */ - [ 'atdownalt', 1 ], /* anti-tank down alt */ - - [ 'atdownalt2', 1 ], /* anti-tank down alt 2 */ - [ 'atleft', 1 ], /* anti-tank left */ - [ 'atleftalt', 1 ], /* anti-tank left alt */ - [ 'atleftalt2', 1 ], /* anti-tank left alt 2 */ - [ 'cblock', 0 ], /* crystal block */ - [ 'cblockht', 0 ], /* crystal block hit by tank */ - [ 'rmirrorul', 0 ], /* roto-mirror up-left */ - [ 'rmirrorur', 0 ], /* roto-mirror up-right */ - [ 'rmirrordr', 0 ], /* roto-mirror down-right */ - [ 'rmirrordl', 0 ], /* roto-mirror down-left */ - - [ 'cblockhat', 0 ], /* crystal block hit by anti-tank */ - [ 'atrightb', 1 ], /* anti-tank, blown up, right */ - [ 'atleftb', 1 ], /* anti-tank, blown up, left */ - [ 'atupb', 1 ], /* anti-tank, blown up, up */ - [ 'tunnel', 1 ], /* wormhole/tunnel */ - [ 'ice', 0 ], /* ice */ - [ 'thinice', 0 ] /* thin ice */ - ], - - 'private const _TSIZE': [ 32, 32, 320, 192 ], - /** * Canvas 2D context (used for masking and tile slicing) * @type {CanvasRenderingContext2d} */ 'private _context': null, + /** + * Tile definition to use for all operations + * @type {Array.>} + */ + 'private _tileDfn': null, /** - * Initialize loader with a 2D canvas context - * - * The context will be used for masking the game bitmap and slicing the - * tiles. + * Width of each individual tile + * @type {number} */ - __construct: function() + 'private _tileWidth': 0, + + /** + * Height of each individual tile + * @type {number} + */ + 'private _tileHeight': 0, + + /** + * Number of tiles per row + * @type {number} + */ + 'private _tilesPerRow': 0, + + /** + * Calculated width of tile set provided a tile definition + * @type {number} + */ + 'private _setWidth': 0, + + /** + * Calculated height of tile set provided a tile definition + * @type {number} + */ + 'private _setHeight': 0, + + + /** + * Initialize loader with a tile definition + * + * The tile definition defines how a tile set should be interpreted. This + * allows us to support *any* type of tile set -- not just those that are + * defined by the original game. + * + * @param {ltjs.TileDfn} tile_dfn tile definition object + */ + __construct: function( tile_dfn ) { + if ( !( Class.isA( ltjs.TileDfn, tile_dfn ) ) ) + { + throw TypeError( "Invalid tile definition provided." ); + } + + // pre-calculate our tile information + this._tileDfn = tile_dfn.getTileDefinition(); + this._calcSetDimensions( tile_dfn ); + // 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' ), - sizes = this.__self.$( '_TSIZE' ); + var context = document.createElement( 'canvas' ).getContext( '2d' ); // size the canvas so that it can fit the entire tileset - context.canvas.width = sizes[ 2 ]; - context.canvas.height = sizes[ 3 ]; + context.canvas.width = this._setWidth; + context.canvas.height = this._setHeight; this._context = context; }, + /** + * Calculate tile set dimensions from the given tile definition object + * + * These dimensions are cached, as these are frequently used and it is + * unwise to continuously invoke methods unnecessarily (who knows what the + * developer of the given tile definition did!). + * + * @param {ltjs.TileDfn} tile_dfn tile definition object + * + * @return {undefined} + */ + 'private _calcSetDimensions': function( tile_dfn ) + { + // these vars are for clarity + var sizes = tile_dfn.getTileDimensions(), + n = this._tileDfn.length; + + // store values so that we do not have to make additional calls to our + // TileDfn instance + this._tileWidth = sizes[ 0 ]; + this._tileHeight = sizes[ 1 ]; + this._tilesPerRow = sizes[ 2 ]; + + // calculate full width and height of tile set + this._setWidth = ( this._tileWidth * this._tilesPerRow ); + this._setHeight = ( + Math.ceil( n / this._tilesPerRow ) * this._tileHeight + ); + }, + + /** * Retrieve image data for each individual tile (pre-masked) * @@ -221,17 +232,15 @@ ltjs.TileMasker = Class( 'TileMasker', */ 'virtual protected getMaskedTileSet': function( data_mask, callback ) { - var tdata = this.__self.$( '_TDATA' ), + var tdata = this._tileDfn, tiles = {}, i = -1, len = tdata.length, - // get tile width and height in pixels, and the number of tiles in - // each row (xn) - sizes = this.__self.$( '_TSIZE' ), - tw = sizes[ 0 ], - th = sizes[ 1 ], - xn = ( sizes[ 2 ] / tw ); + // shorten the names + tw = this._tileWidth, + th = this._tileHeight, + xn = this._tilesPerRow; // create each tile (preserving order, thus no decrementing) while ( ++i < len ) @@ -248,7 +257,7 @@ ltjs.TileMasker = Class( 'TileMasker', // to the tile tiles[ name ] = ( mask === 1 ) ? this.getMaskedTileData( data_mask, x, y ) - : this.getTileData( x, y); + : this.getTileData( x, y ); } callback( tiles ); @@ -330,8 +339,9 @@ ltjs.TileMasker = Class( 'TileMasker', **/ 'protected getTileData': function( x, y ) { - var sizes = this.__self.$( '_TSIZE' ); - return this._context.getImageData( x, y, sizes[ 0 ], sizes[ 1 ] ); + return this._context.getImageData( + x, y, this._tileWidth, this._tileHeight + ); }, @@ -377,10 +387,12 @@ ltjs.TileMasker = Class( 'TileMasker', this._renderImage( bmp, function() { - var size = _self.__self.$( '_TSIZE' ), - data = _self._context.getImageData( 0, 0, size[ 2 ], size[ 3 ] ); + callback( + _self._context.getImageData( + 0, 0, _self._setWidth, _self._setHeight + ) + ); - callback( data ); } ); } } ); diff --git a/test/ltgloader-demo.html b/test/ltgloader-demo.html index ab64ec3..3ab7e6a 100644 --- a/test/ltgloader-demo.html +++ b/test/ltgloader-demo.html @@ -52,7 +52,8 @@ + +