/**
* Represents a classic map (level)
*
* 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 .
*
*
* Each map is concatenated in the file and consists of the following
* information:
*
* - Playfield data (game objects), 16x16 multidimensional char array
* - Level name, 31 characters
* - Hint, 256 characters
* - Author, 31 characters
* - 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).
*
* 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.
*/
/**
* Represents a classic map, as they exist in the original game.
*
* Classic maps are 16x16 tiles in size (for a total of 256 tiles).
*/
ltjs.ClassicMap = Class( 'ClassicMap' )
.implement( ltjs.Map )
.extend(
{
/**
* Size of each map in bytes
* @type {number}
*/
'private const _SIZE': 576,
/**
* Game object size
*
* The game objects are stores as a 16x16 multi-dimensional signed char
* array (in the C sources).
*
* @type {number}
*/
'private const _GOSIZE': 256,
/**
* Tunnel bitmask
* @type {number}
*/
'private const _TMASK': 0x40,
/**
* 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.
*
* Taken from ColorList in LTANK2.C in the original sources. Note that there
* is an endianness difference.
*
* @type {Array.}
*/
'private const _TCOLORS': [
'#ff0000', '#00ff00', '#0000ff', '#00ffff', // r g b c
'#ffff00', '#ff00ff', '#ffffff', '#808080' // y m w b
],
/**
* Map set data (binary string)
* @type {string}
*/
'private _data': null,
/**
* Map id (1-indexed)
* @type {string}
*/
'private _id': 0,
/**
* Initialize map with map data and the given id
*
* @param {ltjs.MapSet} set map set data
* @param {number} id 1-indexed map id
*/
__construct: function( data, id )
{
this._data = ''+( data );
this._id = +id;
},
/**
* 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).
*
* @return {Array.} array of game objects
*/
'public getObjects': function()
{
// calculate offset in LVL file
var offset = ( this.__self.$( '_SIZE' ) * ( this._id - 1 ) );
return this._getMapData( offset );
},
/**
* Retrieve the map data (game objects only) at the requested offset
*
* The object data at the requested position will be loaded and converted to
* integers (from a binary string).
*
* @param {number} offset offset of the requested map data
*
* @return {Array.} converted map data
*/
'private _getMapData': function( offset )
{
var tiles = this._data.substr( offset, this.__self.$( '_GOSIZE' ) )
.split( '' ),
i = tiles.length;
while ( i-- )
{
tiles[ i ] = tiles[ i ].charCodeAt( 0 );
}
return tiles;
},
/**
* Retrieve map dimensions
*
* @return {Array.} width and height in tiles
*/
'public getDimensions': function()
{
return [ 16, 16 ];
},
/**
* Retrieve map of object codes to their appropriate tiles
*
* @return {Array.}
*/
'public getObjectTileMap': 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
// would not be altered
return [
'dirt', 'tup', 'base', 'water', 'block', 'mblock', 'brick',
'atup', 'atright', 'atdown', 'atleft', 'mirrorul', 'mirrorur',
'mirrordr', 'mirrordl', 'owup', 'owright', 'owdown', 'owleft',
'cblock', 'rmirrorul', 'rmirrorur', 'rmirrordr', 'rmirrordl',
'ice', 'thinice'
];
},
/**
* 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.
*
* @param {number} oid tunnel object id
*
* @return {string} tunnel color
*/
'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 );
return this.__self.$( '_TCOLORS' )[ tunnel_id ] || 'black';
},
/**
* Determines if the given object is a tunnel
*
* @return {boolean} true if tunnel, otherwise false
*/
'public isObjectTunnel': function( oid )
{
return ( oid & this.__self.$( '_TMASK' ) );
},
/**
* Retrieve size of map in bytes
*
* @return {number} size of map in bytes
*/
'public static getMapSize': function()
{
return this.$( '_SIZE' );
}
} );