diff --git a/frontend/ascii_tile_map.js b/frontend/ascii_tile_map.js index 0fa0a34..7946bac 100755 --- a/frontend/ascii_tile_map.js +++ b/frontend/ascii_tile_map.js @@ -140,7 +140,7 @@ export class TileMap { return this.tiles[y][x]; } - behavesLikeWall(x, y) { + isWallLike(x, y) { x |= 0; y |= 0; @@ -161,7 +161,7 @@ export class TileMap { * @param {number} y * @returns {boolean} */ - behavesLikeFloor(x, y) { + isFloorLike(x, y) { x |= 0; y |= 0; diff --git a/frontend/ascii_tile_types.js b/frontend/ascii_tile_types.js index 5c0b5ea..ef831e0 100755 --- a/frontend/ascii_tile_types.js +++ b/frontend/ascii_tile_types.js @@ -4,12 +4,14 @@ import { TileOptions } from "../utils/tileOptionsParser.js"; import { Orientation } from "./ascii_types.js"; /** @typedef {string} TileTypeId - a string with a length of 1 */ +/** @typedef {string} MinimapGrapheme - a string containing a single grapheme */ +/** @typedef {string|number} ResourceId - id of a resource (such as a texture or another tile) used by a tile */ /** * Array of __internal__ characters used to identify tile types. - * These are __not__ necessarily the characters used to display + * These are __not necessarily__ the characters used to display * the tile on the minimap - but they are used when serializing - * the maps into a semi-human-readable text-format. + * the maps into a human-readable text-format. * * @enum {TileTypeId} */ @@ -27,36 +29,35 @@ export const TileChars = Object.freeze({ * must have their actual value supplied by the creator * of the Tile object. * - * For instance, if a Tile has a textureId = PropertyPlaceholder.ID, then - * the creature of that Tile MUST supply the textureId before the tile can - * be used. Such values SHOULD be provided in the constructor, but CAN be - * provided later, as long as they are provided before the tile is used - * in the actual game. - * + * For instance, if a Tile has a textureId = REQUIRED_ID, then + * the creator of that Tile MUST supply the textureId before the tile can + * be used in the game Such values SHOULD be provided in the constructor, + * but CAN be provided later, as long as they are provided before the + * tile is used in the actual game. */ /** Properties with this value must be valid ID values. */ const REQUIRED_ID = Symbol("REQUIRED_ID"); const REQUIRED_ORIENTATION = Symbol("REQUIRED_ORIENTATION"); -function mustBeId(value) { +function sanitizeId(value) { if ((value | 0) === value) { return value; } if (typeof value !== "string") { - throw new Error("Value id not a valid id", { value }); + throw new Error("Value is not a valid id", { value }); } value = value.trim(); if (value === "") { - throw new Error("Value id not a valid id", { value }); + throw new Error("Value is not a valid id", { value }); } return value; } -function mustBeOrientation(value) { +function sanitizeOrientation(value) { const result = Orientation.normalize(value); if (result === undefined) { @@ -122,13 +123,13 @@ export const TileTypes = { }; export class Tile { - /** @readonly {string?|number?} Unique (but optional) instance if of this tile */ + /** @readonly {ResourceId?} Unique (but optional) instance id of this tile - only needed when this tile is referenced by other tiles */ id; /** @type {TileTypeId} Char that defines this tile */ typeId; - /** @type {TileTypeId} Icon char of tile */ + /** @type {MinimapGrapheme} Icon char of tile */ minimapChar; /** @type {string} Color of the icon of tile */ @@ -146,18 +147,15 @@ export class Tile { /** @type {boolean} Is this a portal exit and/or entry */ isPortal; - /** @type {string|number} Where is the player transported if they enter the portal */ + /** @type {ResourceId} id of the type where players will be transported - many portals may share same target */ portalTargetId; - /** @type {number|string} id of texture to use */ + /** @type {ResourceId} id of texture to use - does not need to be unique */ textureId; - /** @type {number|string} type of encounter located on this tile. May or may not be unique*/ + /** @type {ResourceId} id of encounter located on this tile. May or may not be unique */ encounterId; - /** @type {number|string} type of trap located on this tile. May or may not be unique*/ - trapType; - /** @type {Orientation} */ orientation; @@ -170,15 +168,71 @@ export class Tile { /** @type {boolean} Has the secret properties of this tile been revealed? */ revealed; - /** @type {TileTypeId} Icon char of tile after tile's secrets have been revealed */ + /** @type {MinimapGrapheme} Icon char of tile after tile's secrets have been revealed */ revealedMinimapChar; /** @type {string} Color of the icon char of tile after tile's secrets have been revealed */ revealedMinimapColor; - /** @type {number|string} id of texture to use after the secrets of this tile has been revealed */ + /** @type {ResourceId} id of texture to use after the secrets of this tile has been revealed */ revealedTextureId; + /** @returns {Tile} */ + static createWall() { + return this.fromChar(TileChars.WALL); + } + + /** @returns {Tile} */ + static createEncounterStartPoint(encounterId) { + return this.fromChar(TileChars.ENCOUNTER_START_POINT, { encounterId }); + } + + /** @returns {Tile} */ + static createFloor() { + return this.fromChar(TileChars.FLOOR); + } + + /** @returns {Tile} */ + static createPlayerStart(orientation) { + return this.fromChar(TileChars.PLAYER_START_POINT, { orientation }); + } + + /** + * Given a map symbol, + * @param {TileTypeId} typeId + * @param {TileOptions|Record} options + * @returns {Tile} + */ + static fromChar(typeId, options) { + const prototype = TileTypes[typeId]; + + if (!prototype) { + console.log("unknown type id", { typeId }); + throw new Error(`Unknown typeId >>>${typeId}<<<`); + } + + // + // Normalize options into a TileOptions object, + // + if (!(options instanceof TileOptions)) { + options = TileOptions.fromObject(typeId, options ?? {}); + } + + let optionPos = 0; + const properties = {}; + + for (let [key, val] of Object.entries(prototype)) { + // + if (typeof val === "symbol" && val.description.startsWith("REQUIRED_")) { + properties[key] = options.getValue(name, optionPos++); + } else { + properties[key] = shallowCopy(val); + } + } + + return new Tile(typeId, properties); + } + /** * @param {TileTypeId} typeId * @param {Tile?} properties @@ -250,80 +304,33 @@ export class Tile { // // Sanitize and normalize // - this.id ??= mustBeId(this.id); - this.textureId ??= mustBeId(this.textureId); - this.portalTargetId ??= mustBeId(this.portalTargetId); - this.orientation ??= mustBeOrientation(this.orientation); + if (this.id !== undefined) { + this.id = sanitizeId(this.id); + } + if (this.textureId !== undefined) { + this.textureId = sanitizeId(this.textureId); + } + if (this.portalId !== undefined) { + this.portalTargetId = sanitizeId(this.portalTargetId); + } + if (this.orientation !== undefined) { + this.orientation = sanitizeOrientation(this.orientation); + } mustBeSingleGrapheme(this.typeId); mustBeSingleGrapheme(this.minimapChar); mustBeSingleGrapheme(this.revealedMinimapChar); } - /** @returns {Tile} */ - static createWall() { - return this.fromChar(TileChars.WALL); - } - - /** @returns {Tile} */ - static createEncounterStartPoint(encounterId) { - return this.fromChar(TileChars.ENCOUNTER_START_POINT, { encounterId }); - } - - /** @returns {Tile} */ - static createFloor() { - return this.fromChar(TileChars.FLOOR); - } - - /** @returns {Tile} */ - static createPlayerStart(orientation) { - return this.fromChar(TileChars.PLAYER_START_POINT, { orientation }); - } - - /** - * Given a map symbol, - * @param {TileTypeId} typeId - * @param {TileOptions|Record} options - * @returns {Tile} - */ - static fromChar(typeId, options) { - const prototype = TileTypes[typeId]; - - if (!prototype) { - console.log("unknown type id", { typeId }); - throw new Error(`Unknown typeId >>>${typeId}<<<`); - } - - if (options === undefined) { - options = TileOptions.fromObject(typeId, TileTypes[typeId]); - } - - // - // Normalize options into a TileOptions object, - // - if (!(options instanceof TileOptions)) { - options = TileOptions.fromObject(typeId, options); - } - - let optionPos = 0; - const properties = {}; - const getOption = (name) => options.getValue(name, optionPos++); - for (let [key, val] of Object.entries(prototype)) { - properties[key] = val; - // - const fetchOption = typeof val === "symbol" && val.description.startsWith("REQUIRED_"); - - properties[key] = fetchOption ? getOption(key) : shallowCopy(val); - } - - return new Tile(typeId, properties); - } - clone() { return new Tile(this.typeId, { ...this }); } isWallLike() { + if (this.typeId === TileChars.WALL) { + return true; + } + if (this.is === TileChars.WALL) { return true; } @@ -335,6 +342,10 @@ export class Tile { return this.looksLikeWall && !this.isTraversable; } + isWall() { + return this.typeId === TileChars.WALL; + } + isFloorlike() { if (this.typeId === TileChars.FLOOR) { return true; diff --git a/frontend/dungeon_studio.js b/frontend/dungeon_studio.js index 873601a..f7a8630 100755 --- a/frontend/dungeon_studio.js +++ b/frontend/dungeon_studio.js @@ -115,20 +115,17 @@ class DungeonGenerator { let lastNonWallX = undefined; // x-index of the LAST (eastmost) non-wall tile that we encountered on this row for (let x = 0; x < this.width; x++) { - const isWall = this.map.get(x, y).looksLikeWall; - - if (isWall) { + // + if (this.map.get(x, y).isWall()) { continue; } - if (firstNonWallX === undefined) { - firstNonWallX = x; - } + firstNonWallX ??= x; lastNonWallX = x; } - const onlyWalls = lastNonWallX === undefined; - if (onlyWalls) { + // Did this row contain only walls? + if (firstNonWallX === undefined) { continue; } @@ -226,7 +223,7 @@ class DungeonGenerator { continue; } - if (this.map.behavesLikeFloor(x, y)) { + if (this.map.get(x, y).isFloor()) { walkabilityCache.push([x, y]); } } @@ -244,7 +241,7 @@ class DungeonGenerator { for (let [x, y] of walkabilityCache) { // - const walkable = (offsetX, offsetY) => this.map.behavesLikeFloor(x + offsetX, y + offsetY); + const walkable = (offsetX, offsetY) => this.map.isFloorLike(x + offsetX, y + offsetY); const surroundingFloorCount = 0 + @@ -281,7 +278,7 @@ class DungeonGenerator { continue; } - if (this.map.behavesLikeFloor(x, y)) { + if (this.map.isFloorLike(x, y)) { walkabilityCache.push([x, y]); } } @@ -290,7 +287,7 @@ class DungeonGenerator { const idx = this.random(0, walkabilityCache.length - 1); const [x, y] = walkabilityCache[idx]; - const walkable = (offsetX, offsetY) => this.map.behavesLikeFloor(x + offsetX, y + offsetY); + const walkable = (offsetX, offsetY) => this.map.isFloorLike(x + offsetX, y + offsetY); // // When spawning in, which direction should the player be oriented?