UNPKG

puzzlescript

Version:

Play PuzzleScript games in your terminal!

570 lines 25.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); const collisionLayer_1 = require("../models/collisionLayer"); const colors_1 = require("../models/colors"); const game_1 = require("../models/game"); const metadata_1 = require("../models/metadata"); const rule_1 = require("../models/rule"); const tile_1 = require("../models/tile"); const winCondition_1 = require("../models/winCondition"); const ast = __importStar(require("./astTypes")); var TILE_TYPE; (function (TILE_TYPE) { TILE_TYPE["OR"] = "OR"; TILE_TYPE["AND"] = "AND"; TILE_TYPE["SIMPLE"] = "SIMPLE"; TILE_TYPE["SPRITE"] = "SPRITE"; })(TILE_TYPE || (TILE_TYPE = {})); function toRULE_DIRECTION(dir) { return dir; } class MapWithId { constructor(prefix) { this.counter = 0; this.prefix = prefix; this.idMap = new Map(); this.jsonMap = new Map(); } set(key, value, id) { if (!this.idMap.has(key)) { this.idMap.set(key, id || this.freshId()); } this.jsonMap.set(key, value); return this.getId(key); } get(key) { const value = this.jsonMap.get(key); if (!value) { debugger; throw new Error(`BUG: Element has not been added to the set`); // tslint:disable-line:no-debugger } return value; } getId(key) { const value = this.idMap.get(key); if (!value) { debugger; throw new Error(`BUG: Element has not been added to the set`); // tslint:disable-line:no-debugger } return value; } toJson() { const ret = {}; for (const [obj, id] of this.idMap) { const json = this.jsonMap.get(obj); if (!json) { debugger; throw new Error(`BUG: Could not find matching json representation for "${id}"`); // tslint:disable-line:no-debugger } ret[id] = json; } return ret; } freshId() { return `${this.prefix}-${this.counter++}`; } } class DefiniteMap { constructor() { this.map = new Map(); } get(key) { const v = this.map.get(key); if (!v) { throw new Error(`ERROR: JSON is missing key "${key}". Should have already been added`); } return v; } set(k, v) { this.map.set(k, v); } values() { return this.map.values(); } } class Serializer { constructor(game) { this.game = game; this.colorsMap = new Map(); this.spritesMap = new MapWithId('sprite'); this.soundMap = new MapWithId('sound'); this.collisionLayerMap = new MapWithId('collision'); this.conditionsMap = new MapWithId('bracket'); this.neighborsMap = new MapWithId('neighbor'); this.tileWithModifierMap = new MapWithId('twm'); this.tileMap = new MapWithId('tile'); this.ruleMap = new MapWithId('rule'); this.commandMap = new MapWithId('command'); if (this.game.metadata.backgroundColor) { const hex = this.game.metadata.backgroundColor.toHex(); this.colorsMap.set(hex, hex); } if (this.game.metadata.textColor) { const hex = this.game.metadata.textColor.toHex(); this.colorsMap.set(hex, hex); } // Load up the colors and sprites this.game.collisionLayers.forEach((item) => this.buildCollisionLayer(item)); this.game.sounds.forEach((item) => { this.buildSound(item); }); this.game.objects.forEach((sprite) => { this.buildSprite(sprite); }); this.orderedRules = this.game.rules.map((item) => this.recBuildRule(item)); this.game.legends.forEach((tile) => { this.buildTile(tile); }); this.levels = this.game.levels.map((item) => this.buildLevel(item)); this.winConditions = this.game.winConditions.map((item) => { if (item instanceof winCondition_1.WinConditionOn) { const ret = { _sourceOffset: item.__source.sourceOffset, type: ast.WIN_CONDITION_TYPE.ON, qualifier: item.qualifier, tile: this.buildTile(item.tile), onTile: this.buildTile(item.onTile) }; return ret; } else { const ret = { _sourceOffset: item.__source.sourceOffset, type: ast.WIN_CONDITION_TYPE.SIMPLE, qualifier: item.qualifier, tile: this.buildTile(item.tile) }; return ret; } }); } static fromJson(source, code) { // First, build up all of the lookup maps const colorMap = new DefiniteMap(); const spritesMap = new DefiniteMap(); const soundMap = new DefiniteMap(); const collisionLayerMap = new DefiniteMap(); const bracketMap = new DefiniteMap(); const neighborsMap = new DefiniteMap(); const tileWithModifierMap = new DefiniteMap(); const tileMap = new DefiniteMap(); const ruleMap = new DefiniteMap(); const commandMap = new DefiniteMap(); for (const [key, val] of Object.entries(source.colors)) { colorMap.set(key, new colors_1.HexColor({ code, sourceOffset: 0 }, val)); } const layers = new DefiniteMap(); for (const [key] of Object.entries(source.collisionLayers)) { layers.set(key, []); } const transparent = new colors_1.TransparentColor({ code, sourceOffset: 0 }); let spriteIndex = 0; for (const [key, val] of Object.entries(source.sprites)) { const { _sourceOffset: sourceOffset, name, pixels } = val; const sprite = new tile_1.GameSpritePixels({ code, sourceOffset }, name, null, pixels.map((row) => row.map((color) => { if (color) { return colorMap.get(color) || transparent; } else { return transparent; } }))); // assign an index to each GameSprite sprite.allSpritesBitSetIndex = spriteIndex; spriteIndex++; spritesMap.set(key, sprite); layers.get(val.collisionLayer).push(sprite); } for (const [key, val] of Object.entries(source.tiles)) { const { _sourceOffset: sourceOffset } = val; let tile; switch (val.type) { case 'OR': tile = new tile_1.GameLegendTileOr({ code, sourceOffset }, val.name, val.sprites.map((item) => spritesMap.get(item))); break; case 'AND': tile = new tile_1.GameLegendTileAnd({ code, sourceOffset }, val.name, val.sprites.map((item) => spritesMap.get(item))); break; case 'SIMPLE': tile = new tile_1.GameLegendTileSimple({ code, sourceOffset }, val.name, spritesMap.get(val.sprite)); break; case 'SPRITE': tile = new tile_1.GameLegendTileSimple({ code, sourceOffset }, val.name, spritesMap.get(val.sprite)); break; default: throw new Error(`ERROR: Unsupported tile type`); } tileMap.set(key, tile); // layers.get(val.collisionLayer).push(tile) } for (const [key, val] of Object.entries(source.sounds)) { switch (val.type) { case ast.SOUND_TYPE.SFX: case ast.SOUND_TYPE.WHEN: soundMap.set(key, Object.assign({}, val)); break; case ast.SOUND_TYPE.SPRITE_DIRECTION: case ast.SOUND_TYPE.SPRITE_EVENT: case ast.SOUND_TYPE.SPRITE_MOVE: soundMap.set(key, Object.assign(Object.assign({}, val), { sprite: tileMap.get(val.sprite) })); break; } } for (const [key, val] of Object.entries(source.commands)) { switch (val.type) { case ast.COMMAND_TYPE.MESSAGE: commandMap.set(key, val); break; case ast.COMMAND_TYPE.SFX: commandMap.set(key, Object.assign(Object.assign({}, val), { sound: soundMap.get(val.sound) })); break; case ast.COMMAND_TYPE.RESTART: case ast.COMMAND_TYPE.AGAIN: case ast.COMMAND_TYPE.CANCEL: case ast.COMMAND_TYPE.CHECKPOINT: case ast.COMMAND_TYPE.WIN: commandMap.set(key, val); break; default: throw new Error(`ERROR: Unsupported command type`); } } for (const [key, val] of Object.entries(source.collisionLayers)) { const { _sourceOffset: sourceOffset } = val; collisionLayerMap.set(key, new collisionLayer_1.CollisionLayer({ code, sourceOffset }, layers.get(key))); } for (const [key, val] of Object.entries(source.tilesWithModifiers)) { const { _sourceOffset: sourceOffset } = val; tileWithModifierMap.set(key, new rule_1.SimpleTileWithModifier({ code, sourceOffset }, val.isNegated, val.isRandom, val.direction, tileMap.get(val.tile), val.debugFlag)); } for (const [key, val] of Object.entries(source.neighbors)) { const { _sourceOffset: sourceOffset } = val; neighborsMap.set(key, new rule_1.SimpleNeighbor({ code, sourceOffset }, new Set(val.tileWithModifiers.map((item) => tileWithModifierMap.get(item))), val.debugFlag)); } for (const [key, val] of Object.entries(source.brackets)) { const { _sourceOffset: sourceOffset } = val; switch (val.type) { case ast.BRACKET_TYPE.SIMPLE: bracketMap.set(key, new rule_1.SimpleBracket({ code, sourceOffset }, val.direction, val.neighbors.map((item) => neighborsMap.get(item)), val.debugFlag)); break; case ast.BRACKET_TYPE.ELLIPSIS: bracketMap.set(key, new rule_1.SimpleEllipsisBracket({ code, sourceOffset }, val.direction, val.beforeNeighbors.map((item) => neighborsMap.get(item)), val.afterNeighbors.map((item) => neighborsMap.get(item)), val.debugFlag)); break; default: throw new Error(`ERROR: Unsupported bracket type`); } } for (const [key, val] of Object.entries(source.ruleDefinitions)) { const { _sourceOffset: sourceOffset } = val; switch (val.type) { case ast.RULE_TYPE.SIMPLE: ruleMap.set(key, new rule_1.SimpleRule({ code, sourceOffset }, val.conditions.map((item) => bracketMap.get(item)), val.actions.map((item) => bracketMap.get(item)), val.commands.map((item) => commandMap.get(item)), val.isLate, val.isRigid, val.debugFlag)); break; case ast.RULE_TYPE.GROUP: ruleMap.set(key, new rule_1.SimpleRuleGroup({ code, sourceOffset }, val.isRandom, val.rules.map((item) => ruleMap.get(item)))); break; case ast.RULE_TYPE.LOOP: ruleMap.set(key, new rule_1.SimpleRuleLoop({ code, sourceOffset }, false /*TODO: Figure out if loops need isRandom*/, val.rules.map((item) => ruleMap.get(item)))); break; default: throw new Error(`ERROR: Unsupported rule type`); } } const levels = source.levels.map((item) => { switch (item.type) { case ast.LEVEL_TYPE.MAP: return Object.assign(Object.assign({}, item), { cells: item.cells.map((row) => row.map((tile) => tileMap.get(tile))) }); case ast.LEVEL_TYPE.MESSAGE: return item; default: throw new Error(`ERROR: Invalid level type`); } }); const winConditions = source.winConditions.map((item) => { const { _sourceOffset: sourceOffset } = item; switch (item.type) { case ast.WIN_CONDITION_TYPE.SIMPLE: return new winCondition_1.WinConditionSimple({ code, sourceOffset }, item.qualifier, tileMap.get(item.tile)); case ast.WIN_CONDITION_TYPE.ON: return new winCondition_1.WinConditionOn({ code, sourceOffset }, item.qualifier, tileMap.get(item.tile), tileMap.get(item.onTile)); default: throw new Error(`ERROR: Unsupported Win Condition type`); } }); const metadata = new metadata_1.GameMetadata(); for (const [key, val] of Object.entries(source.metadata)) { if (val) { switch (key) { case 'backgroundColor': case 'textColor': metadata._setValue(key, colorMap.get(val)); break; case 'zoomScreen': case 'flickScreen': const { width, height } = val; metadata._setValue(key, new metadata_1.Dimension(width, height)); break; default: metadata._setValue(key, val); } } } return new game_1.GameData({ code, sourceOffset: 0 }, source.title, metadata, [...spritesMap.values()], [...tileMap.values()], [...soundMap.values()], [...collisionLayerMap.values()], source.rules.map((item) => ruleMap.get(item)), winConditions, levels); } buildCollisionLayer(item) { return this.collisionLayerMap.set(item, { _sourceOffset: item.__source.sourceOffset }); } metadataToJson() { return { author: this.game.metadata.author, homepage: this.game.metadata.homepage, youtube: this.game.metadata.youtube, zoomscreen: this.game.metadata.zoomscreen, flickscreen: this.game.metadata.flickscreen, colorPalette: this.game.metadata.colorPalette, backgroundColor: this.game.metadata.backgroundColor ? this.buildColor(this.game.metadata.backgroundColor) : null, textColor: this.game.metadata.textColor ? this.buildColor(this.game.metadata.textColor) : null, realtimeInterval: this.game.metadata.realtimeInterval, keyRepeatInterval: this.game.metadata.keyRepeatInterval, againInterval: this.game.metadata.againInterval, noAction: this.game.metadata.noAction, noUndo: this.game.metadata.noUndo, runRulesOnLevelStart: this.game.metadata.runRulesOnLevelStart, noRepeatAction: this.game.metadata.noRepeatAction, throttleMovement: this.game.metadata.throttleMovement, noRestart: this.game.metadata.noRestart, requirePlayerMovement: this.game.metadata.requirePlayerMovement, verboseLogging: this.game.metadata.verboseLogging }; } toJson() { const colors = {}; for (const [key, value] of this.colorsMap) { colors[key] = value; } return { version: 1, title: this.game.title, metadata: this.metadataToJson(), colors, sounds: this.soundMap.toJson(), collisionLayers: this.collisionLayerMap.toJson(), commands: this.commandMap.toJson(), sprites: this.spritesMap.toJson(), tiles: this.tileMap.toJson(), tilesWithModifiers: this.tileWithModifierMap.toJson(), neighbors: this.neighborsMap.toJson(), brackets: this.conditionsMap.toJson(), ruleDefinitions: this.ruleMap.toJson(), winConditions: this.winConditions, rules: this.orderedRules, levels: this.levels }; } buildLevel(level) { switch (level.type) { case ast.LEVEL_TYPE.MAP: return Object.assign(Object.assign({}, level), { cells: level.cells.map((row) => row.map((cell) => this.buildTile(cell))) }); case ast.LEVEL_TYPE.MESSAGE: return level; default: debugger; throw new Error(`BUG: Unsupported level subtype`); // tslint:disable-line:no-debugger } } recBuildRule(rule) { if (rule instanceof rule_1.SimpleRule) { return this.ruleMap.set(rule, { type: ast.RULE_TYPE.SIMPLE, directions: [], conditions: rule.conditionBrackets.map((item) => this.buildConditionBracket(item)), actions: rule.actionBrackets.map((item) => this.buildConditionBracket(item)), commands: rule.commands.map((item) => this.buildCommand(item)), isRandom: null, isLate: rule.isLate(), isRigid: rule.hasRigid(), _sourceOffset: rule.__source.sourceOffset, debugFlag: rule.debugFlag }); } else if (rule instanceof rule_1.SimpleRuleGroup) { return this.ruleMap.set(rule, { type: ast.RULE_TYPE.GROUP, isRandom: rule.isRandom, rules: rule.getChildRules().map((item) => this.recBuildRule(item)), _sourceOffset: rule.__source.sourceOffset, debugFlag: null // TODO: Unhardcode me }); } else if (rule instanceof rule_1.SimpleRuleLoop) { const x = { type: ast.RULE_TYPE.LOOP, // isRandom: rule.isRandom, rules: rule.getChildRules().map((item) => this.recBuildRule(item)), _sourceOffset: rule.__source.sourceOffset, debugFlag: null // TODO: unhardcode me }; return this.ruleMap.set(rule, x); } else { debugger; throw new Error(`BUG: Unsupported rule type`); // tslint:disable-line:no-debugger } } buildCommand(command) { switch (command.type) { case ast.COMMAND_TYPE.SFX: return this.commandMap.set(command, Object.assign(Object.assign({}, command), { sound: this.soundMap.getId(command.sound) })); case ast.COMMAND_TYPE.AGAIN: case ast.COMMAND_TYPE.CANCEL: case ast.COMMAND_TYPE.CHECKPOINT: case ast.COMMAND_TYPE.MESSAGE: case ast.COMMAND_TYPE.RESTART: case ast.COMMAND_TYPE.WIN: return this.commandMap.set(command, command); default: debugger; throw new Error(`BUG: Unsupoprted command type`); // tslint:disable-line:no-debugger } } buildConditionBracket(bracket) { if (bracket instanceof rule_1.SimpleEllipsisBracket) { const b = bracket; const before = b.beforeEllipsisBracket.getNeighbors().map((item) => this.buildNeighbor(item)); // this.buildConditionBracket(b.beforeEllipsisBracket) const after = b.afterEllipsisBracket.getNeighbors().map((item) => this.buildNeighbor(item)); // this.buildConditionBracket(b.afterEllipsisBracket) return this.conditionsMap.set(bracket, { type: ast.BRACKET_TYPE.ELLIPSIS, direction: toRULE_DIRECTION(b.direction), beforeNeighbors: before, afterNeighbors: after, _sourceOffset: bracket.__source.sourceOffset, debugFlag: b.debugFlag }); } else if (bracket instanceof rule_1.SimpleBracket) { return this.conditionsMap.set(bracket, { type: ast.BRACKET_TYPE.SIMPLE, direction: toRULE_DIRECTION(bracket.direction), neighbors: bracket.getNeighbors().map((item) => this.buildNeighbor(item)), _sourceOffset: bracket.__source.sourceOffset, debugFlag: bracket.debugFlag }); } else { debugger; throw new Error(`BUG: Unsupported bracket type`); // tslint:disable-line:no-debugger } } buildNeighbor(neighbor) { return this.neighborsMap.set(neighbor, { tileWithModifiers: [...neighbor._tilesWithModifier].map((item) => this.buildTileWithModifier(item)), _sourceOffset: neighbor.__source.sourceOffset, debugFlag: null // TODO: Pull it out of the neighbor }); } buildTileWithModifier(t) { return this.tileWithModifierMap.set(t, { direction: t._direction ? toRULE_DIRECTION(t._direction) : null, isNegated: t._isNegated, isRandom: t._isRandom, tile: this.buildTile(t._tile), _sourceOffset: t.__source.sourceOffset, debugFlag: t._debugFlag }); } buildTile(tile) { if (tile instanceof tile_1.GameLegendTileOr) { return this.tileMap.set(tile, { type: TILE_TYPE.OR, name: tile.getName(), sprites: tile.getSprites().map((item) => this.buildSprite(item)), collisionLayers: tile.getCollisionLayers().map((item) => this.buildCollisionLayer(item)), _sourceOffset: tile.__source.sourceOffset }); } else if (tile instanceof tile_1.GameLegendTileAnd) { return this.tileMap.set(tile, { type: TILE_TYPE.AND, name: tile.getName(), sprites: tile.getSprites().map((item) => this.buildSprite(item)), collisionLayers: tile.getCollisionLayers().map((item) => this.buildCollisionLayer(item)), _sourceOffset: tile.__source.sourceOffset }); } else if (tile instanceof tile_1.GameLegendTileSimple) { return this.tileMap.set(tile, { type: TILE_TYPE.SIMPLE, name: tile.getName(), sprite: this.buildSprite(tile.getSprites()[0]), collisionLayers: tile.getCollisionLayers().map((item) => this.buildCollisionLayer(item)), _sourceOffset: tile.__source.sourceOffset }); } else if (tile instanceof tile_1.GameSprite) { return this.tileMap.set(tile, { type: TILE_TYPE.SPRITE, name: tile.getName(), sprite: this.buildSprite(tile), collisionLayer: this.buildCollisionLayer(tile.getCollisionLayer()), _sourceOffset: tile.__source.sourceOffset }); } else { debugger; throw new Error(`BUG: Invalid tile type`); // tslint:disable-line:no-debugger } } buildSprite(sprite) { const { spriteHeight, spriteWidth } = this.game.getSpriteSize(); return this.spritesMap.set(sprite, { name: sprite.getName(), collisionLayer: this.collisionLayerMap.getId(sprite.getCollisionLayer()), pixels: sprite.getPixels(spriteHeight, spriteWidth).map((row) => row.map((pixel) => { if (pixel.isTransparent()) { return null; } else { return this.buildColor(pixel); } })), _sourceOffset: sprite.__source.sourceOffset }); } buildColor(color) { const hex = color.toHex(); this.colorsMap.set(hex, hex); return hex; } buildSound(sound) { switch (sound.type) { case ast.SOUND_TYPE.SFX: case ast.SOUND_TYPE.WHEN: return this.soundMap.set(sound, Object.assign({}, sound)); case ast.SOUND_TYPE.SPRITE_DIRECTION: case ast.SOUND_TYPE.SPRITE_EVENT: case ast.SOUND_TYPE.SPRITE_MOVE: return this.soundMap.set(sound, Object.assign(Object.assign({}, sound), { sprite: this.buildTile(sound.sprite) })); } } } exports.default = Serializer; //# sourceMappingURL=serializer.js.map