UNPKG

puzzlescript

Version:

Play PuzzleScript games in your terminal!

442 lines 15.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GameLegendTileOr = exports.GameLegendTileAnd = exports.GameLegendTileSimple = exports.GameLegendTile = exports.GameSpritePixels = exports.GameSpriteSingleColor = exports.GameSprite = void 0; const util_1 = require("../util"); const BaseForLines_1 = require("./BaseForLines"); // BitSet does not export a default so import does not work in webpack-built file const BitSet2 = require('bitset'); // tslint:disable-line:no-var-requires class GameSprite extends BaseForLines_1.BaseForLines { constructor(source, name, optionalLegendChar) { super(source); this.name = name; this._optionalLegendChar = optionalLegendChar; this.trickleCells = new Set(); this.trickleTiles = new Set(); this.trickleTilesWithModifier = new Set(); this.allSpritesBitSetIndex = -1; // will be changed once we have all the sprites this.collisionLayer = null; this.bitSet = null; } isOr() { return false; } equals(t) { return this === t; // sprites MUST be exact } getName() { return this.name; } isBackground() { return this.name.toLowerCase() === 'background'; } _getDescendantTiles() { return []; } getSprites() { // to match the signature of LegendTile return [this]; } hasCollisionLayer() { return !!this.collisionLayer; } hasSingleCollisionLayer() { // always true. This is only ever false for OR tiles return this.hasCollisionLayer(); } setCollisionLayer(collisionLayer) { this.collisionLayer = collisionLayer; } setCollisionLayerAndIndex(collisionLayer, bitSetIndex) { this.collisionLayer = collisionLayer; this.bitSet = new BitSet2(); this.bitSet.set(bitSetIndex); } getCollisionLayer() { if (!this.collisionLayer) { throw new Error(`ERROR: This sprite was not in a Collision Layer\n${this.toString()}`); } return this.collisionLayer; } clearCaches() { this.trickleCells.clear(); } hasCell(cell) { return this.trickleCells.has(cell); } matchesCell(cell) { return cell.getSpritesAsSet().has(this); // because of Webworkers, we cannot perform equality tests (unless the sprites match exactly what comes out of gamedata... hmm, maybe that's the way to do it?) } getSpritesThatMatch(cell) { if (cell.getSpritesAsSet().has(this)) { return new Set([this]); } else { return new Set(); } } subscribeToCellChanges(t) { this.trickleTilesWithModifier.add(t); } subscribeToCellChangesTile(tile) { this.trickleTiles.add(tile); } addCell(cell, wantsToMove) { this.addCells(this, [cell], wantsToMove); } removeCell(cell) { this.removeCells(this, [cell]); } updateCell(cell, wantsToMove) { if (process.env.NODE_ENV === 'development') { // check that the cell is already in the sprite cell set if (!this.has(cell)) { throw new Error(`BUG: Expected cell to already be in the sprite set`); } } // propagate up for (const t of this.trickleTiles) { t.updateCells(this, [cell], wantsToMove); } for (const t of this.trickleTilesWithModifier) { t.updateCells(this, [cell], wantsToMove); } } addCells(sprite, cells, wantsToMove) { for (const cell of cells) { if (this.trickleCells.has(cell)) { throw new Error(`BUG: should not be trying to add a cell that has already been matched (right?)`); } this.trickleCells.add(cell); } // propagate up for (const t of this.trickleTiles) { t.addCells(this, cells, wantsToMove); } for (const t of this.trickleTilesWithModifier) { t.addCells(this, this, cells, wantsToMove); } } updateCells(sprite, cells, wantsToMove) { throw new Error(`BUG: Unreachable code`); } removeCells(sprite, cells) { for (const cell of cells) { this.trickleCells.delete(cell); } // propagate up for (const t of this.trickleTiles) { t.removeCells(this, cells); } for (const t of this.trickleTilesWithModifier) { t.removeCells(this, this, cells); } } has(cell) { return this.trickleCells.has(cell); } hasNegationTileWithModifier() { for (const t of this.trickleTilesWithModifier) { if (t.isNo()) { return true; } } for (const tile of this.trickleTiles) { if (tile.hasNegationTileWithModifier()) { return true; } } return false; } getCellsThatMatch(cells) { if (this.trickleCells.size > 0) { return this.trickleCells; } else if (cells) { // The Tile might just be an empty object (because of webworkers) // So check all the cells return new Set([...cells].filter((cell) => this.matchesCell(cell))); } else { return new Set(); } } } exports.GameSprite = GameSprite; class GameSpriteSingleColor extends GameSprite { constructor(source, name, optionalLegendChar, colors) { super(source, name, optionalLegendChar); this.color = colors[0]; // Ignore if the user added multiple colors (like `transparent yellow`) } isTransparent() { return this.color.isTransparent(); } hasAlpha() { return this.color.hasAlpha(); } hasPixels() { return false; } getPixels(spriteHeight, spriteWidth) { // When there are no pixels then it means "color the whole thing in the same color" const rows = []; for (let row = 0; row < spriteHeight; row++) { rows.push([]); for (let col = 0; col < spriteWidth; col++) { rows[row].push(this.color); } } return rows; } } exports.GameSpriteSingleColor = GameSpriteSingleColor; class GameSpritePixels extends GameSprite { constructor(source, name, optionalLegendChar, pixels) { super(source, name, optionalLegendChar); this.pixels = pixels; // Store for a11y (so we do not report the sprite) and for faster rendering this._isTransparent = true; this._hasAlpha = false; for (const row of pixels) { for (const pixel of row) { if (!pixel.isTransparent()) { this._isTransparent = false; } if (pixel.hasAlpha()) { this._hasAlpha = true; } } } } isTransparent() { return this._isTransparent; } hasAlpha() { return this._hasAlpha; } getSprites() { // to match the signature of LegendTile return [this]; } hasPixels() { return true; } getPixels(spriteHeight, spriteWidth) { // Make a copy because others may edit it return this.pixels.map((row) => { return row.map((col) => col); }); } } exports.GameSpritePixels = GameSpritePixels; class GameLegendTile extends BaseForLines_1.BaseForLines { constructor(source, spriteNameOrLevelChar, tiles) { super(source); this.spriteNameOrLevelChar = spriteNameOrLevelChar; this.tiles = tiles; this.trickleCells = new Set(); this.trickleTilesWithModifier = new Set(); this.collisionLayer = null; this.spritesCache = null; } equals(t) { if (this.isOr() !== t.isOr()) { return false; } const difference = (0, util_1.setDifference)(new Set(this.getSprites()), t.getSprites()); return difference.size === 0; } isOr() { return false; } getName() { return this.spriteNameOrLevelChar; } _getDescendantTiles() { // recursively pull all the tiles out return this.tiles.concat((0, util_1._flatten)(this.tiles.map((tile) => tile._getDescendantTiles()))); } getSprites() { // Use a cache because all the collision layers have not been loaded in time if (!this.spritesCache) { // 2 levels of indirection should be safe // Sort by collisionLayer so that the most-important sprite is first this.spritesCache = (0, util_1._flatten)(this.tiles.map((tile) => { return tile.getSprites(); })).sort((a, b) => { return a.getCollisionLayer().id - b.getCollisionLayer().id; }).reverse(); } return this.spritesCache; } setCollisionLayer(collisionLayer) { this.collisionLayer = collisionLayer; } getCollisionLayer() { // OR tiles and AND tiles don't necessarily have a collisionLayer set so pull it from the sprite (this might not work) if (this.collisionLayer) { return this.collisionLayer; } // check that all sprites are in the same collisionlayer... if not, thene our understanding is flawed const firstCollisionLayer = this.getSprites()[0].getCollisionLayer(); for (const sprite of this.getSprites()) { if (sprite.getCollisionLayer() !== firstCollisionLayer) { throw new Error(`ooh, sprites in a tile have different collision layers... that's a problem\n${this.toString()}`); } } return firstCollisionLayer; } getCollisionLayers() { const layers = new Set(); for (const sprite of this.getSprites()) { layers.add(sprite.getCollisionLayer()); } return [...layers]; } getCellsThatMatch(cells) { const matches = new Set(); for (const sprite of this.getSprites()) { for (const cell of sprite.getCellsThatMatch(cells)) { matches.add(cell); } } return matches; } subscribeToCellChanges(t) { this.trickleTilesWithModifier.add(t); // subscribe this to be notified of all Sprite changes of Cells for (const sprite of this.getSprites()) { sprite.subscribeToCellChangesTile(this); } } hasNegationTileWithModifier() { for (const t of this.trickleTilesWithModifier) { if (t.isNo()) { return true; } } return false; } addCells(sprite, cells, wantsToMove) { for (const cell of cells) { if (!this.trickleCells.has(cell)) { if (this.matchesCell(cell)) { this.trickleCells.add(cell); for (const t of this.trickleTilesWithModifier) { t.addCells(this, sprite, [cell], wantsToMove); } } } } } updateCells(sprite, cells, wantsToMove) { // verify that all the cells are in trickleCells if (process.env.NODE_ENV === 'development') { for (const cell of cells) { if (!this.trickleCells.has(cell)) { throw new Error(`Cell was not already added before`); } } } for (const t of this.trickleTilesWithModifier) { t.updateCells(sprite, cells, wantsToMove); } } removeCells(sprite, cells) { for (const cell of cells) { if (this.matchesCell(cell)) { if (!this.trickleCells.has(cell)) { this.addCells(sprite, [cell], null); } else { // We need to propagate this is an OR tile // because removing one of the OR tiles // may (or may not) cause this cell to // no longer match this.updateCells(sprite, [cell], null); } } else { this.trickleCells.delete(cell); for (const t of this.trickleTilesWithModifier) { t.removeCells(this, sprite, [cell]); } } } } hasCell(cell) { return this.trickleCells.has(cell); } } exports.GameLegendTile = GameLegendTile; class GameLegendTileSimple extends GameLegendTile { constructor(source, spriteNameOrLevelChar, tile) { super(source, spriteNameOrLevelChar, [tile]); } matchesCell(cell) { // Update code coverage (Maybe only count the number of times it was true?) if (process.env.NODE_ENV === 'development') { this.__incrementCoverage(); } // Check that the cell contains all of the tiles (ANDED) // Since this is a Simple Tile it should only contain 1 tile so anding is the right way to go. for (const tile of this.tiles) { if (!tile.matchesCell(cell)) { return false; } } return true; } getSpritesThatMatch(cell) { return (0, util_1.setIntersection)(new Set(this.getSprites()), cell.getSpritesAsSet()); } hasSingleCollisionLayer() { return !!this.collisionLayer; } } exports.GameLegendTileSimple = GameLegendTileSimple; class GameLegendTileAnd extends GameLegendTile { matchesCell(cell) { throw new Error(`BUG: Unreachable code`); } getSpritesThatMatch(cell) { // return setIntersection(new Set(this.getSprites()), cell.getSpritesAsSet()) throw new Error(`BUG: This method should only be called for OR tiles`); } hasSingleCollisionLayer() { return !!this.collisionLayer; } } exports.GameLegendTileAnd = GameLegendTileAnd; class GameLegendTileOr extends GameLegendTile { isOr() { return true; } matchesCell(cell) { // Update code coverage (Maybe only count the number of times it was true?) if (process.env.NODE_ENV === 'development') { this.__incrementCoverage(); } // Check that the cell contains any of the tiles (OR) for (const tile of this.tiles) { if (tile.matchesCell(cell)) { return true; } } return false; } getSpritesThatMatch(cell) { return (0, util_1.setIntersection)(new Set(this.getSprites()), cell.getSpritesAsSet()); } hasSingleCollisionLayer() { const sprites = this.getSprites(); for (const sprite of sprites) { if (sprite.getCollisionLayer() !== sprites[0].getCollisionLayer()) { return false; } } return true; } } exports.GameLegendTileOr = GameLegendTileOr; //# sourceMappingURL=tile.js.map