puzzlescript
Version:
Play PuzzleScript games in your terminal!
442 lines • 15.1 kB
JavaScript
;
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