Source: block.js

import { Coordinate, Coordinate2D, Coordinate3D } from "./coordinate.js";

/**
 * Represents a Block in a level.
 * @classdesc Block in LevelZ World
 */
export class Block {

    /**
     * The name of this block.
     * @type {string}
     */
    name;

    /**
     * The properties of this block.
     * @type {Map<string, any>}
     */
    properties = new Map();

    /**
     * Constructs a new Block.
     * @param {string} name Name of the block
     * @param {Map<string, any>|Object.<string, any>} [properties] Properties of the block
     * @example
     * new Block('grass', new Map([['snowy', false]]))
     * new Block('grass', {'snowy': false})
     */
    constructor(name, properties) {
        this.name = name
        if (properties) {
            if (properties instanceof Map) 
                properties.forEach((value, key) => { this.properties.set(key, value) })
            else for (const [key, value] of Object.entries(properties)) 
                this.properties.set(key, value)
        }
    }

    /**
     * Returns the string representation of this block.
     * @returns {string} The string representation of this block.
     * @example
     * grass<snowy=false>
     */
    toString() {
        let str = `${this.name}`
        if (this.properties.size == 0) return str;

        str += '<'
        for (const [key, value] of this.properties) {
            str += `${key}=${value}, `
        }
        str = str.slice(0, -2)

        return str + '>'
    }

}

/**
 * Utility Object for representing a Level Block and its Coordinate.
 * @classdesc Coordinate and Block in LevelZ World
 */
export class LevelObject {

    /**
     * The Block of this LevelObject.
     * @type {Block}
     */
    block;

    /**
     * The Coordinate of this LevelObject.
     * @type {Coordinate}
     */
    coordinate;

    /**
     * Constructs a new LevelObject.
     * @constructor
     * @param {Block|string} block The Block
     * @param {Coordinate|number[]} coordinate The Coordinate
     * @example
     * new LevelObject(new Block('block'), new Coordinate2D(1, 2))
     * @example
     * new LevelObject('block', [1, 2])
     */
    constructor(block, coordinate) {
        if (coordinate instanceof Array) {
            if (coordinate.length == 2) 
                coordinate = new Coordinate2D(coordinate[0], coordinate[1])
            else if (coordinate.length == 3) 
                coordinate = new Coordinate3D(coordinate[0], coordinate[1], coordinate[2])
        }

        if (typeof(block) === 'string')
            block = new Block(block)

        if (!(block instanceof Block) || !(coordinate instanceof Coordinate))
            throw new SyntaxError('Invalid input')

        this.block = block
        this.coordinate = coordinate
    }

    /**
     * Gets the name of the block.
     * @returns {string} The name of the block.
     */
    get name() {
        return this.block.name
    }

    /**
     * Gets the properties of the block.
     * @returns {Map<string, any>} The properties of the block.
     */
    get properties() {
        return this.block.properties
    }

    /**
     * Returns the string representation of this LevelObject.
     * @returns {string} The string representation of this LevelObject.
     * @example
     * block<property=value>: [x, y]
     */
    toString() {
        return `${this.block.toString()}: ${this.coordinate.toString()}`
    }
}