puzzlescript
Version:
Play PuzzleScript games in your terminal!
327 lines • 12.7 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmptyGameEngineHandler = exports.shouldTick = exports.MESSAGE_TYPE = exports.pollingPromise = exports.spritesThatInteractWithPlayer = exports.DEBUG_FLAG = exports.getRandomSeed = exports.clearRandomValuesForTesting = exports.setRandomValuesForTesting = exports.resetRandomSeed = exports.nextRandom = exports.setDifference = exports.setIntersection = exports.setAddAll = exports.setEquals = exports.opposite = exports._debounce = exports._flatten = exports.RULE_DIRECTION_RELATIVE = exports.INPUT_BUTTON = exports.RULE_DIRECTION = void 0;
var RULE_DIRECTION;
(function (RULE_DIRECTION) {
RULE_DIRECTION["UP"] = "UP";
RULE_DIRECTION["DOWN"] = "DOWN";
RULE_DIRECTION["LEFT"] = "LEFT";
RULE_DIRECTION["RIGHT"] = "RIGHT";
RULE_DIRECTION["ACTION"] = "ACTION";
RULE_DIRECTION["STATIONARY"] = "STATIONARY";
RULE_DIRECTION["RANDOMDIR"] = "RANDOMDIR";
})(RULE_DIRECTION = exports.RULE_DIRECTION || (exports.RULE_DIRECTION = {}));
var INPUT_BUTTON;
(function (INPUT_BUTTON) {
INPUT_BUTTON["UP"] = "UP";
INPUT_BUTTON["DOWN"] = "DOWN";
INPUT_BUTTON["LEFT"] = "LEFT";
INPUT_BUTTON["RIGHT"] = "RIGHT";
INPUT_BUTTON["ACTION"] = "ACTION";
INPUT_BUTTON["UNDO"] = "UNDO";
INPUT_BUTTON["RESTART"] = "RESTART";
})(INPUT_BUTTON = exports.INPUT_BUTTON || (exports.INPUT_BUTTON = {}));
var RULE_DIRECTION_RELATIVE;
(function (RULE_DIRECTION_RELATIVE) {
RULE_DIRECTION_RELATIVE["RELATIVE_LEFT"] = "<";
RULE_DIRECTION_RELATIVE["RELATIVE_RIGHT"] = ">";
RULE_DIRECTION_RELATIVE["RELATIVE_UP"] = "^";
RULE_DIRECTION_RELATIVE["RELATIVE_DOWN"] = "V";
})(RULE_DIRECTION_RELATIVE = exports.RULE_DIRECTION_RELATIVE || (exports.RULE_DIRECTION_RELATIVE = {}));
// From https://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays-in-javascript/39000004#39000004
function _flatten(arrays) {
// return [].concat.apply([], arrays) as T[]
const ret = [];
arrays.forEach((ary) => {
ary.forEach((item) => {
ret.push(item);
});
});
return ret;
}
exports._flatten = _flatten;
// export function filterNulls<T>(items: Array<Optional<T>>) {
// const ret: T[] = []
// items.forEach((x) => {
// if (x) { ret.push(x) }
// })
// return ret
// }
// export function _zip<T1, T2>(array1: T1[], array2: T2[]) {
// if (array1.length < array2.length) {
// throw new Error(`BUG: Zip array length mismatch ${array1.length} != ${array2.length}`)
// }
// return array1.map((v1, index) => {
// return [v1, array2[index]]
// })
// }
// export function _extend(dest: any, ...rest: any[]) {
// for (const obj of rest) {
// for (const key of Object.keys(obj)) {
// dest[key] = obj[key]
// }
// }
// return dest
// }
function _debounce(callback) {
let timeout; // NodeJS.Timer
return () => {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
callback();
}, 10);
};
}
exports._debounce = _debounce;
function opposite(dir) {
switch (dir) {
case RULE_DIRECTION.UP:
return RULE_DIRECTION.DOWN;
case RULE_DIRECTION.DOWN:
return RULE_DIRECTION.UP;
case RULE_DIRECTION.LEFT:
return RULE_DIRECTION.RIGHT;
case RULE_DIRECTION.RIGHT:
return RULE_DIRECTION.LEFT;
default:
throw new Error(`BUG: Invalid direction: "${dir}"`);
}
}
exports.opposite = opposite;
function setEquals(set1, set2) {
if (set1.size !== set2.size)
return false;
for (const elem of set2) {
if (!set1.has(elem))
return false;
}
return true;
}
exports.setEquals = setEquals;
function setAddAll(setA, iterable) {
const newSet = new Set(setA);
for (const elem of iterable) {
newSet.add(elem);
}
return newSet;
}
exports.setAddAll = setAddAll;
function setIntersection(setA, setB) {
const intersection = new Set();
for (const elem of setB) {
if (setA.has(elem)) {
intersection.add(elem);
}
}
return intersection;
}
exports.setIntersection = setIntersection;
function setDifference(setA, setB) {
const difference = new Set(setA);
for (const elem of setB) {
difference.delete(elem);
}
return difference;
}
exports.setDifference = setDifference;
// From https://stackoverflow.com/a/19303725
let seed = 1;
let randomValuesForTesting = null;
function nextRandom(maxNonInclusive) {
if (randomValuesForTesting) {
if (randomValuesForTesting.length <= seed - 1) {
throw new Error(`BUG: the list of random values for testing was too short.
See calls to setRandomValuesForTesting([...]).
The list was [${randomValuesForTesting}]. Index being requested is ${seed - 1}`);
}
const ret = randomValuesForTesting[seed - 1];
seed++;
// console.log(`Sending "random" value of "${ret}"`);
return ret;
}
const x = Math.sin(seed++) * 10000;
return Math.round((x - Math.floor(x)) * (maxNonInclusive - 1));
// return Math.round(Math.random() * (maxNonInclusive - 1))
}
exports.nextRandom = nextRandom;
function resetRandomSeed() {
seed = 1;
}
exports.resetRandomSeed = resetRandomSeed;
function setRandomValuesForTesting(values) {
randomValuesForTesting = values;
resetRandomSeed();
}
exports.setRandomValuesForTesting = setRandomValuesForTesting;
function clearRandomValuesForTesting() {
randomValuesForTesting = null;
resetRandomSeed();
}
exports.clearRandomValuesForTesting = clearRandomValuesForTesting;
function getRandomSeed() {
return seed;
}
exports.getRandomSeed = getRandomSeed;
/**
* A `DEBUGGER` flag in the game source that causes the evaluation to pause.
* It works like the
* [debugger](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger)
* keyword in JavaScript.
*
* **Note:** the game needs to run in debug mode (`node --inspect-brk path/to/puzzlescript.js` or `npm run play-debug`)
* for this flag to have any effect.
*
* This string can be added to:
*
* - A Rule. Example: `DEBUGGER [ > player | cat ] -> [ > player | > cat ]`
* - A bracket when the condition is updated: `[ > player | cat ] DEBUGGER -> [ > player | > cat ]`
* - A bracket when it is evaluated: `[ > player | cat ] -> [ > player | > cat ] DEBUGGER`
* - A neighbor when the condition is updated: `[ > player DEBUGGER | cat ] -> [ > player | > cat ]`
* - A neighbor when it is evaluated: `[ > player | cat ] -> [ > player | > cat DEBUGGER ]`
* - A tile when the condition is updated: `[ > player | DEBUGGER cat ] -> [ > player | > cat ]`
* - A tile when it is matched: `[ > player | cat ] -> [ > player | DEBUGGER > cat ]`
*/
var DEBUG_FLAG;
(function (DEBUG_FLAG) {
DEBUG_FLAG["BREAKPOINT"] = "DEBUGGER";
/**
* Pause when a Cell causes an entry to be removed from the set of matches for this rule/bracket/neighbor/tile
*/
DEBUG_FLAG["BREAKPOINT_REMOVE"] = "DEBUGGER_REMOVE";
})(DEBUG_FLAG = exports.DEBUG_FLAG || (exports.DEBUG_FLAG = {}));
function spritesThatInteractWithPlayer(game) {
const playerSprites = game.getPlayer().getSprites();
const interactsWithPlayer = new Set(playerSprites);
// Add all the sprites in the same collision layer as the Player
for (const playerSprite of interactsWithPlayer) {
const collisionLayer = playerSprite.getCollisionLayer();
for (const sprite of game.objects) {
if (sprite.getCollisionLayer() === collisionLayer) {
interactsWithPlayer.add(sprite);
}
}
}
// Add all the winCondition sprites
for (const win of game.winConditions) {
for (const tile of win.a11yGetTiles()) {
for (const sprite of tile.getSprites()) {
interactsWithPlayer.add(sprite);
}
}
}
// Add all the other sprites that interact with the player
for (const rule of game.rules) {
for (const sprites of rule.a11yGetConditionSprites()) {
if (setIntersection(sprites, interactsWithPlayer).size > 0) {
for (const sprite of sprites) {
interactsWithPlayer.add(sprite);
}
}
}
}
// remove the background sprite (even though it transitively interacts)
const background = game.getMagicBackgroundSprite();
if (background) {
interactsWithPlayer.delete(background);
}
// remove transparent sprites once the dependecies are found
return new Set([...interactsWithPlayer].filter((s) => !s.isTransparent()));
}
exports.spritesThatInteractWithPlayer = spritesThatInteractWithPlayer;
// Webworker message interfaces
// Polls until a condition is true
function pollingPromise(ms, fn) {
return new Promise((resolve) => {
const timer = setInterval(() => {
const value = fn();
if (value) {
clearInterval(timer);
resolve(value);
}
}, ms);
});
}
exports.pollingPromise = pollingPromise;
var MESSAGE_TYPE;
(function (MESSAGE_TYPE) {
MESSAGE_TYPE["PAUSE"] = "PAUSE";
MESSAGE_TYPE["RESUME"] = "RESUME";
MESSAGE_TYPE["TICK"] = "TICK";
MESSAGE_TYPE["PRESS"] = "PRESS";
MESSAGE_TYPE["CLOSE"] = "CLOSE";
// Event handler events
MESSAGE_TYPE["ON_GAME_CHANGE"] = "ON_GAME_CHANGE";
MESSAGE_TYPE["ON_PRESS"] = "ON_PRESS";
MESSAGE_TYPE["ON_MESSAGE"] = "ON_MESSAGE";
MESSAGE_TYPE["ON_MESSAGE_DONE"] = "ON_MESSAGE_DONE";
MESSAGE_TYPE["ON_LEVEL_LOAD"] = "ON_LEVEL_LOAD";
MESSAGE_TYPE["ON_LEVEL_CHANGE"] = "ON_LEVEL_CHANGE";
MESSAGE_TYPE["ON_WIN"] = "ON_WIN";
MESSAGE_TYPE["ON_SOUND"] = "ON_SOUND";
MESSAGE_TYPE["ON_TICK"] = "ON_TICK";
MESSAGE_TYPE["ON_PAUSE"] = "ON_PAUSE";
MESSAGE_TYPE["ON_RESUME"] = "ON_RESUME";
})(MESSAGE_TYPE = exports.MESSAGE_TYPE || (exports.MESSAGE_TYPE = {}));
const shouldTick = (metadata, lastTick) => {
const now = Date.now();
let minTime = Math.min(metadata.realtimeInterval || 1000, metadata.keyRepeatInterval || 1000, metadata.againInterval || 1000);
if (minTime > 100 || Number.isNaN(minTime)) {
minTime = .01;
}
return (now - lastTick) >= (minTime * 1000);
};
exports.shouldTick = shouldTick;
class EmptyGameEngineHandler {
constructor(subHandlers) {
this.subHandlers = subHandlers || [];
}
onGameChange(gameData) { for (const h of this.subHandlers) {
h.onGameChange && h.onGameChange(gameData);
} }
onPress(dir) { for (const h of this.subHandlers) {
h.onPress && h.onPress(dir);
} }
onMessage(msg) {
return __awaiter(this, void 0, void 0, function* () { for (const h of this.subHandlers) {
h.onMessage && (yield h.onMessage(msg));
} });
}
onLevelLoad(level, newLevelSize) { for (const h of this.subHandlers) {
h.onLevelLoad && h.onLevelLoad(level, newLevelSize);
} }
onLevelChange(level, cells, message) { for (const h of this.subHandlers) {
h.onLevelChange && h.onLevelChange(level, cells, message);
} }
onWin() { for (const h of this.subHandlers) {
h.onWin && h.onWin();
} }
onSound(sound) {
return __awaiter(this, void 0, void 0, function* () { for (const h of this.subHandlers) {
h.onSound && h.onSound(sound);
} });
}
onTick(changedCells, checkpoint, hasAgain, a11yMessages) {
for (const h of this.subHandlers) {
h.onTick && h.onTick(changedCells, checkpoint, hasAgain, a11yMessages);
}
}
onPause() { for (const h of this.subHandlers) {
h.onPause && h.onPause();
} }
onResume() { for (const h of this.subHandlers) {
h.onResume && h.onResume();
} }
}
exports.EmptyGameEngineHandler = EmptyGameEngineHandler;
//# sourceMappingURL=util.js.map