Home Reference Source Repository

app/tiles/Tile.js

import Dungeon from '../dungeons/Dungeon.js';
import Creature from '../entities/creatures/Creature.js';

/**
 * A grid tile in the dungeon
 */
export default class Tile {
    /**
      * @param {number} x - The x-coordinate this Tile will have within the Dungeon
      * @param {number} y - The y-coordinate this Tile will have within the Dungeon
      */
    constructor(x, y) {
        if(!Number.isInteger(x) || !Number.isInteger(y)) {
            throw new Error('Must pass an x and y coordinate');
        }
        this._x = x;
        this._y = y;
        this._items = [];
    }

    /**
     * Sets the Creature occupying this Tile. A Tile can have a maximum of
     * one Creature
     * @param {Creature} creature - The Creature to place on this Tile
     */
    setCreature(creature) {
        if(!(creature instanceof Creature)) {
            throw new Error('First parameter must be a Creature');
        }
        this._creature = creature;
    }

    /**
     * Removes the Creature from this Tile, if any
     * @returns {Creature} - The Creature that was removed
     */
    removeCreature() {
        var creature = this._creature;
        this._creature = null;
        return creature;
    }

    /**
     * Gets the Creature on this Tile
     * @returns {Creature} - The Creature on this Tile
     */
    getCreature() {
        return this._creature;
    }

    /**
     * Gets the list of Items on this Tile
     * @returns {Array<Item>}
     */
    getItems() {
        return this._items.slice();
    }

    /**
     * Adds an Item to the pile of Items on the Tile.
     * There is no limit to the number of items on a Tile
     * @param {Item} item - The Item to add
     */
    addItem(item) {
        this._items.push(item);
    }

    /**
     * Removes an Item from the Tile
     * @param {Item|number} param - The Item to remove or the index of it's position
     */
    removeItem(param) {
        if(isNaN(param)) {
            var index = this._items.indexOf(param);
            if(index === -1) {
                throw new Error('Item not found', param);
            } else {
                return this._items.splice(index, 1)[0];
            }
        } else {
            return this._items.splice(param, 1)[0];
        }
    }

    /**
     * Get's the x coordinate of this tile
     * @returns {number}
     */
    getX() {
        return this._x;
    }

    /**
     * Get's the y coordinate of this tile
     * @returns {number}
     */
    getY() {
        return this._y;
    }

    /**
     * Sets an identifier telling which room the Tile belongs to.
     * This can be used to apply rules to all Tiles in the same room
     * @param {string} key - A string representing the room this Tile is in
     */
    setRoomKey(key) {
        this._roomKey = key;
    }

    /**
     * Gets the room key for this Tile, if any
     * @returns {string}
     */
    getRoomKey() {
        return this._roomKey;
    }

    /**
     * Tells whether this tile is solid (e.g. a wall). Most
     * Creatures cannot pass through solid Tiles.
     * @returns {boolean} - true if the Tile is considered solid; false otherwise
     */
    isSolid() {
        return false;
    }

    /**
     * Tells whether this tile is opaque (e.g. a wall). Most
     * Creatures cannot see through opaque Tiles.
     * @returns {boolean} - true if the Tile is considered opaque; false otherwise
     */
    isOpaque() {
        return false;
    }

    /**
     * Tells whether this tile is considered a floor (not a pit). Most
     * Creatures cannot walk over non-floor Tiles
     * @returns {boolean} - true if the Tile has a walkable floor; false otherwise
     */
    hasFloor() {
        return true;
    }

    /**
     * Gets the (up to) 4 neighbors in the cardinal directions from this Tile
     * @param {Dungeon} dungeon - The dungeon this tile is in
     * @returns {Array<Tile>}
     */
    getNeighbors4(dungeon) {
        var x = this._x;
        var y = this._y;
        return [
            dungeon.getTile(x    , y - 1),
            dungeon.getTile(x - 1, y),
            dungeon.getTile(x + 1, y),
            dungeon.getTile(x    , y + 1)
        ].filter(Boolean);
    }

    /**
     * Gets all Tiles immediately adjacent to this Tile (including diagonally)
     * @param {Dungeon} dungeon - The dungeon this tile is in
     * @returns {Array<Tile>}
     */
    getNeighbors8(dungeon) {
        var x = this._x;
        var y = this._y;
        return [
            dungeon.getTile(x - 1, y - 1),
            dungeon.getTile(x    , y - 1),
            dungeon.getTile(x + 1, y - 1),
            dungeon.getTile(x - 1, y),
            dungeon.getTile(x + 1, y),
            dungeon.getTile(x - 1, y + 1),
            dungeon.getTile(x    , y + 1),
            dungeon.getTile(x + 1, y + 1)
        ].filter(Boolean);
    }

    /**
     * Gets the Chebyshev distance between this Tile and another.
     * This is the number of steps a walking Creature will need to
     * take to travel between the two tiles, assuming no obstacles
     * @param {Tile} other - The to which distance will be measured
     * @returns {number} - A distance, as an integer
     */
    getDirectDistance(other) {
        if(!(other instanceof Tile)) {
            throw new Error('First parameter must be a Tile');
        }
        var dx = Math.abs(other.getX() - this.getX());
        var dy = Math.abs(other.getY() - this.getY());
        return Math.max(dx, dy);
    }

    /**
     * Gets the Euclidean distance between this Tile and another,
     * using the Pythagorean Theorem. This is used when determining
     * if something is within range of a ranged attack
     * @param {Tile} other - The to which distance will be measured
     * @returns {number} - A distance, as a double
     */
    getEuclideanDistance(other) {
        if(!(other instanceof Tile)) {
            throw new Error('First parameter must be a Tile');
        }
        var dx = Math.abs(other.getX() - this.getX());
        var dy = Math.abs(other.getY() - this.getY());
        return Math.sqrt(dx * dx + dy * dy);
    }

    /**
     * Gets a human-friendly name for this Tile
     * @returns {string}
     */
    getName() {
        return 'Floor Tile';
    }

    /**
     * Gets a debug representation of this Tile
     * @returns {string}
     */
    toString() {
        return this.constructor.name + '<' + this.getX() + ',' + this.getY() + '>';
    }
}