puzzlescript
Version:
Play PuzzleScript games in your terminal!
570 lines • 25.4 kB
JavaScript
"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