board-state
Version:
build turn-based board game state machines with hidden information
203 lines (170 loc) • 6.05 kB
JavaScript
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
import produce from "immer";
import { JSON_delta } from "./vendor-json-delta";
import deepcopy from "deepcopy";
var context = Symbol("context");
var Game =
/*#__PURE__*/
function () {
function Game() {
_classCallCheck(this, Game);
}
_createClass(Game, null, [{
key: "setupFilters",
value: function setupFilters(state) {
var filters = this.getFilters(state);
var f;
if (typeof filters == "function") {
f = {
default: filters
};
this[context].filterMode = "single";
} else {
f = filters;
this[context].filterMode = "multi";
}
this[context].filters = {};
for (var filterKey in f) {
this[context].filters[filterKey] = produce(f[filterKey]);
}
}
}, {
key: "filter",
value: function filter(state, filterKey) {
var filters = this.getFilters(state);
if (typeof filters == "function") {
return produce(state, filters);
} else {
return produce(state, filters[filterKey]);
}
}
}, {
key: "getFilters",
value: function getFilters() {
// default implementation if not overridden
return function () {};
}
}, {
key: "playAction",
value: function playAction(state, action) {
var _this = this;
if (this[context] !== undefined) {
throw new Error("Nested calls to playAction()/replayAction() are not supported");
}
var views = {},
newState,
oldContext;
try {
this[context] = {
mode: "play",
diffs: {}
};
this.setupFilters(state);
for (var filterKey in this[context].filters) {
views[filterKey] = this[context].filters[filterKey](state);
this[context].diffs[filterKey] = [];
}
newState = produce(state, function (draft) {
return _this.updateState(draft, action);
});
} finally {
oldContext = this[context];
this[context] = undefined;
}
if (process.env.NODE_ENV != "production") {
// development or test
for (var _filterKey in oldContext.filters) {
var replayResult = this.replayAction(views[_filterKey], action, oldContext.diffs[_filterKey]);
var filteredNewState = oldContext.filters[_filterKey](newState);
var diff = JSON_delta.diff(replayResult, filteredNewState);
if (diff.length != 0) {
var error = new Error("Result of replaying the action did not match the new state");
error.result = filteredNewState;
error.replay = replayResult;
error.diff = diff;
throw error;
}
}
}
if (oldContext.filterMode == "single") {
var newInfo = oldContext.diffs["default"];
return {
state: newState,
newInfo: newInfo
};
} else {
return {
state: newState,
newInfos: oldContext.diffs
};
}
}
}, {
key: "replayAction",
value: function replayAction(state, action, diffs) {
var _this2 = this;
if (this[context] !== undefined) {
throw new Error("Nested calls to playAction()/replayAction() are not supported");
}
var result;
try {
this[context] = {
mode: "replay",
diffs: diffs,
diffIndex: 0
};
result = produce(state, function (draft) {
return _this2.updateState(draft, action);
});
} finally {
this[context] = undefined;
}
return result;
}
}, {
key: "applyUpdate",
value: function applyUpdate(state, transform) {
switch (this[context].mode) {
case "play":
this._playApplyUpdate(state, transform);
break;
case "replay":
this._replayApplyUpdate(state, transform);
break;
}
}
}, {
key: "_playApplyUpdate",
value: function _playApplyUpdate(draft, transform) {
// Have to clone the previous state here as Immer will try to help out with
// structural sharing which breaks because this is really a mutable draft
var original = deepcopy(draft);
var views = {};
for (var filterKey in this[context].filters) {
views[filterKey] = produce(original, this[context].filters[filterKey]);
}
transform(draft);
var updatedViews = {};
for (var _filterKey2 in this[context].filters) {
updatedViews[_filterKey2] = produce(draft, this[context].filters[_filterKey2]); // We deepcopy the diff here to ensure that it contains references
// only to plain objects and not proxies
var diff = deepcopy(JSON_delta.diff(views[_filterKey2], updatedViews[_filterKey2]));
this[context].diffs[_filterKey2].push(diff);
}
}
}, {
key: "_replayApplyUpdate",
value: function _replayApplyUpdate(state, _transform) {
// We also have to clone the diff we are applying, because otherwise
// modifying the state later on can also modify the original diff!
var diff = deepcopy(this[context].diffs[this[context].diffIndex]);
JSON_delta.patch(state, diff);
this[context].diffIndex++;
}
}]);
return Game;
}();
export default Game;
//# sourceMappingURL=game.js.map