boardgame.io
Version:
library for turn-based games
1,614 lines (1,399 loc) • 147 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = 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);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var defineProperty = function (obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
};
var NOTHING = typeof Symbol !== "undefined" ? Symbol("immer-nothing") : defineProperty({}, "immer-nothing", true);
var DRAFTABLE = typeof Symbol !== "undefined" ? Symbol("immer-draftable") : "__$immer_draftable";
var DRAFT_STATE = typeof Symbol !== "undefined" ? Symbol("immer-state") : "__$immer_state";
function isDraft(value) {
return !!value && !!value[DRAFT_STATE];
}
function isDraftable(value) {
if (!value || (typeof value === "undefined" ? "undefined" : _typeof(value)) !== "object") return false;
if (Array.isArray(value)) return true;
var proto = Object.getPrototypeOf(value);
if (!proto || proto === Object.prototype) return true;
return !!value[DRAFTABLE] || !!value.constructor[DRAFTABLE];
}
var assign = Object.assign || function assign(target, value) {
for (var key in value) {
if (has(value, key)) {
target[key] = value[key];
}
}
return target;
};
var ownKeys = typeof Reflect !== "undefined" && Reflect.ownKeys ? Reflect.ownKeys : typeof Object.getOwnPropertySymbols !== "undefined" ? function (obj) {
return Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj));
} : Object.getOwnPropertyNames;
function shallowCopy(base) {
var invokeGetters = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (Array.isArray(base)) return base.slice();
var clone = Object.create(Object.getPrototypeOf(base));
ownKeys(base).forEach(function (key) {
if (key === DRAFT_STATE) {
return; // Never copy over draft state.
}
var desc = Object.getOwnPropertyDescriptor(base, key);
if (desc.get) {
if (!invokeGetters) {
throw new Error("Immer drafts cannot have computed properties");
}
desc.value = desc.get.call(base);
}
if (desc.enumerable) {
clone[key] = desc.value;
} else {
Object.defineProperty(clone, key, {
value: desc.value,
writable: true,
configurable: true
});
}
});
return clone;
}
function each(value, cb) {
if (Array.isArray(value)) {
for (var i = 0; i < value.length; i++) {
cb(i, value[i], value);
}
} else {
ownKeys(value).forEach(function (key) {
return cb(key, value[key], value);
});
}
}
function isEnumerable(base, prop) {
return Object.getOwnPropertyDescriptor(base, prop).enumerable;
}
function has(thing, prop) {
return Object.prototype.hasOwnProperty.call(thing, prop);
}
function is(x, y) {
// From: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js
if (x === y) {
return x !== 0 || 1 / x === 1 / y;
} else {
return x !== x && y !== y;
}
}
// @ts-check
var descriptors = {};
// For nested produce calls:
var scopes = [];
var currentScope = function currentScope() {
return scopes[scopes.length - 1];
};
function willFinalize(result, baseDraft, needPatches) {
var scope = currentScope();
scope.forEach(function (state) {
return state.finalizing = true;
});
if (result === undefined || result === baseDraft) {
if (needPatches) markChangesRecursively(baseDraft);
// This is faster when we don't care about which attributes changed.
markChangesSweep(scope);
}
}
function createDraft(base, parent) {
var isArray = Array.isArray(base);
var draft = clonePotentialDraft(base);
each(draft, function (prop) {
proxyProperty(draft, prop, isArray || isEnumerable(base, prop));
});
// See "proxy.js" for property documentation.
var state = {
scope: parent ? parent.scope : currentScope(),
modified: false,
finalizing: false, // es5 only
finalized: false,
assigned: {},
parent: parent,
base: base,
draft: draft,
copy: null,
revoke: revoke,
revoked: false // es5 only
};
createHiddenProperty(draft, DRAFT_STATE, state);
state.scope.push(state);
return draft;
}
function revoke() {
this.revoked = true;
}
function source(state) {
return state.copy || state.base;
}
function _get(state, prop) {
assertUnrevoked(state);
var value = source(state)[prop];
// Drafts are only created for proxyable values that exist in the base state.
if (!state.finalizing && value === state.base[prop] && isDraftable(value)) {
prepareCopy(state);
return state.copy[prop] = createDraft(value, state);
}
return value;
}
function _set(state, prop, value) {
assertUnrevoked(state);
state.assigned[prop] = true;
if (!state.modified) {
if (is(source(state)[prop], value)) return;
markChanged(state);
prepareCopy(state);
}
state.copy[prop] = value;
}
function markChanged(state) {
if (!state.modified) {
state.modified = true;
if (state.parent) markChanged(state.parent);
}
}
function prepareCopy(state) {
if (!state.copy) state.copy = clonePotentialDraft(state.base);
}
function clonePotentialDraft(base) {
var state = base && base[DRAFT_STATE];
if (state) {
state.finalizing = true;
var draft = shallowCopy(state.draft, true);
state.finalizing = false;
return draft;
}
return shallowCopy(base);
}
function proxyProperty(draft, prop, enumerable) {
var desc = descriptors[prop];
if (desc) {
desc.enumerable = enumerable;
} else {
descriptors[prop] = desc = {
configurable: true,
enumerable: enumerable,
get: function get$$1() {
return _get(this[DRAFT_STATE], prop);
},
set: function set$$1(value) {
_set(this[DRAFT_STATE], prop, value);
}
};
}
Object.defineProperty(draft, prop, desc);
}
function assertUnrevoked(state) {
if (state.revoked === true) throw new Error("Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? " + JSON.stringify(source(state)));
}
// This looks expensive, but only proxies are visited, and only objects without known changes are scanned.
function markChangesSweep(scope) {
// The natural order of drafts in the `scope` array is based on when they
// were accessed. By processing drafts in reverse natural order, we have a
// better chance of processing leaf nodes first. When a leaf node is known to
// have changed, we can avoid any traversal of its ancestor nodes.
for (var i = scope.length - 1; i >= 0; i--) {
var state = scope[i];
if (state.modified === false) {
if (Array.isArray(state.base)) {
if (hasArrayChanges(state)) markChanged(state);
} else if (hasObjectChanges(state)) markChanged(state);
}
}
}
function markChangesRecursively(object) {
if (!object || (typeof object === "undefined" ? "undefined" : _typeof(object)) !== "object") return;
var state = object[DRAFT_STATE];
if (!state) return;
var base = state.base,
draft = state.draft,
assigned = state.assigned;
if (!Array.isArray(object)) {
// Look for added keys.
Object.keys(draft).forEach(function (key) {
// The `undefined` check is a fast path for pre-existing keys.
if (base[key] === undefined && !has(base, key)) {
assigned[key] = true;
markChanged(state);
} else if (!assigned[key]) {
// Only untouched properties trigger recursion.
markChangesRecursively(draft[key]);
}
});
// Look for removed keys.
Object.keys(base).forEach(function (key) {
// The `undefined` check is a fast path for pre-existing keys.
if (draft[key] === undefined && !has(draft, key)) {
assigned[key] = false;
markChanged(state);
}
});
} else if (hasArrayChanges(state)) {
markChanged(state);
assigned.length = true;
if (draft.length < base.length) {
for (var i = draft.length; i < base.length; i++) {
assigned[i] = false;
}
} else {
for (var _i = base.length; _i < draft.length; _i++) {
assigned[_i] = true;
}
}
for (var _i2 = 0; _i2 < draft.length; _i2++) {
// Only untouched indices trigger recursion.
if (assigned[_i2] === undefined) markChangesRecursively(draft[_i2]);
}
}
}
function hasObjectChanges(state) {
var base = state.base,
draft = state.draft;
// Search for added keys. Start at the back, because non-numeric keys
// are ordered by time of definition on the object.
var keys = Object.keys(draft);
for (var i = keys.length - 1; i >= 0; i--) {
// The `undefined` check is a fast path for pre-existing keys.
if (base[keys[i]] === undefined && !has(base, keys[i])) {
return true;
}
}
// Since no keys have been added, we can compare lengths to know if an
// object has been deleted.
return keys.length !== Object.keys(base).length;
}
function hasArrayChanges(state) {
var draft = state.draft;
if (draft.length !== state.base.length) return true;
// See #116
// If we first shorten the length, our array interceptors will be removed.
// If after that new items are added, result in the same original length,
// those last items will have no intercepting property.
// So if there is no own descriptor on the last position, we know that items were removed and added
// N.B.: splice, unshift, etc only shift values around, but not prop descriptors, so we only have to check
// the last one
var descriptor = Object.getOwnPropertyDescriptor(draft, draft.length - 1);
// descriptor can be null, but only for newly created sparse arrays, eg. new Array(10)
if (descriptor && !descriptor.get) return true;
// For all other cases, we don't have to compare, as they would have been picked up by the index setters
return false;
}
function createHiddenProperty(target, prop, value) {
Object.defineProperty(target, prop, {
value: value,
enumerable: false,
writable: true
});
}
var legacyProxy = Object.freeze({
scopes: scopes,
currentScope: currentScope,
willFinalize: willFinalize,
createDraft: createDraft
});
// @ts-check
// For nested produce calls:
var scopes$1 = [];
var currentScope$1 = function currentScope() {
return scopes$1[scopes$1.length - 1];
};
// Do nothing before being finalized.
function willFinalize$1() {}
function createDraft$1(base, parent) {
var state = {
// Track which produce call this is associated with.
scope: parent ? parent.scope : currentScope$1(),
// True for both shallow and deep changes.
modified: false,
// Used during finalization.
finalized: false,
// Track which properties have been assigned (true) or deleted (false).
assigned: {},
// The parent draft state.
parent: parent,
// The base state.
base: base,
// The base proxy.
draft: null,
// Any property proxies.
drafts: {},
// The base copy with any updated values.
copy: null,
// Called by the `produce` function.
revoke: null
};
var _ref = Array.isArray(base) ? Proxy.revocable([state], arrayTraps) : Proxy.revocable(state, objectTraps),
revoke = _ref.revoke,
proxy = _ref.proxy;
state.draft = proxy;
state.revoke = revoke;
state.scope.push(state);
return proxy;
}
var objectTraps = {
get: get$1,
has: function has$$1(target, prop) {
return prop in source$1(target);
},
ownKeys: function ownKeys$$1(target) {
return Reflect.ownKeys(source$1(target));
},
set: set$1,
deleteProperty: deleteProperty,
getOwnPropertyDescriptor: getOwnPropertyDescriptor,
defineProperty: function defineProperty() {
throw new Error("Object.defineProperty() cannot be used on an Immer draft"); // prettier-ignore
},
getPrototypeOf: function getPrototypeOf(target) {
return Object.getPrototypeOf(target.base);
},
setPrototypeOf: function setPrototypeOf() {
throw new Error("Object.setPrototypeOf() cannot be used on an Immer draft"); // prettier-ignore
}
};
var arrayTraps = {};
each(objectTraps, function (key, fn) {
arrayTraps[key] = function () {
arguments[0] = arguments[0][0];
return fn.apply(this, arguments);
};
});
arrayTraps.deleteProperty = function (state, prop) {
if (isNaN(parseInt(prop))) {
throw new Error("Immer only supports deleting array indices"); // prettier-ignore
}
return objectTraps.deleteProperty.call(this, state[0], prop);
};
arrayTraps.set = function (state, prop, value) {
if (prop !== "length" && isNaN(parseInt(prop))) {
throw new Error("Immer only supports setting array indices and the 'length' property"); // prettier-ignore
}
return objectTraps.set.call(this, state[0], prop, value);
};
function source$1(state) {
return state.copy || state.base;
}
function get$1(state, prop) {
if (prop === DRAFT_STATE) return state;
var drafts = state.drafts;
// Check for existing draft in unmodified state.
if (!state.modified && has(drafts, prop)) {
return drafts[prop];
}
var value = source$1(state)[prop];
if (state.finalized || !isDraftable(value)) return value;
// Check for existing draft in modified state.
if (state.modified) {
// Assigned values are never drafted. This catches any drafts we created, too.
if (value !== state.base[prop]) return value;
// Store drafts on the copy (when one exists).
drafts = state.copy;
}
return drafts[prop] = createDraft$1(value, state);
}
function set$1(state, prop, value) {
if (!state.modified) {
// Optimize based on value's truthiness. Truthy values are guaranteed to
// never be undefined, so we can avoid the `in` operator. Lastly, truthy
// values may be drafts, but falsy values are never drafts.
var isUnchanged = value ? is(state.base[prop], value) || value === state.drafts[prop] : is(state.base[prop], value) && prop in state.base;
if (isUnchanged) return true;
markChanged$1(state);
}
state.assigned[prop] = true;
state.copy[prop] = value;
return true;
}
function deleteProperty(state, prop) {
// The `undefined` check is a fast path for pre-existing keys.
if (state.base[prop] !== undefined || prop in state.base) {
state.assigned[prop] = false;
markChanged$1(state);
}
if (state.copy) delete state.copy[prop];
return true;
}
function getOwnPropertyDescriptor(state, prop) {
var owner = source$1(state);
var desc = Reflect.getOwnPropertyDescriptor(owner, prop);
if (desc) {
desc.writable = true;
desc.configurable = !Array.isArray(owner) || prop !== "length";
}
return desc;
}
function markChanged$1(state) {
if (!state.modified) {
state.modified = true;
state.copy = assign(shallowCopy(state.base), state.drafts);
state.drafts = null;
if (state.parent) markChanged$1(state.parent);
}
}
var modernProxy = Object.freeze({
scopes: scopes$1,
currentScope: currentScope$1,
willFinalize: willFinalize$1,
createDraft: createDraft$1
});
function generatePatches(state, basePath, patches, inversePatches) {
Array.isArray(state.base) ? generateArrayPatches(state, basePath, patches, inversePatches) : generateObjectPatches(state, basePath, patches, inversePatches);
}
function generateArrayPatches(state, basePath, patches, inversePatches) {
var base = state.base,
copy = state.copy,
assigned = state.assigned;
var minLength = Math.min(base.length, copy.length);
// Look for replaced indices.
for (var i = 0; i < minLength; i++) {
if (assigned[i] && base[i] !== copy[i]) {
var path = basePath.concat(i);
patches.push({ op: "replace", path: path, value: copy[i] });
inversePatches.push({ op: "replace", path: path, value: base[i] });
}
}
// Did the array expand?
if (minLength < copy.length) {
for (var _i = minLength; _i < copy.length; _i++) {
patches.push({
op: "add",
path: basePath.concat(_i),
value: copy[_i]
});
}
inversePatches.push({
op: "replace",
path: basePath.concat("length"),
value: base.length
});
}
// ...or did it shrink?
else if (minLength < base.length) {
patches.push({
op: "replace",
path: basePath.concat("length"),
value: copy.length
});
for (var _i2 = minLength; _i2 < base.length; _i2++) {
inversePatches.push({
op: "add",
path: basePath.concat(_i2),
value: base[_i2]
});
}
}
}
function generateObjectPatches(state, basePath, patches, inversePatches) {
var base = state.base,
copy = state.copy;
each(state.assigned, function (key, assignedValue) {
var origValue = base[key];
var value = copy[key];
var op = !assignedValue ? "remove" : key in base ? "replace" : "add";
if (origValue === value && op === "replace") return;
var path = basePath.concat(key);
patches.push(op === "remove" ? { op: op, path: path } : { op: op, path: path, value: value });
inversePatches.push(op === "add" ? { op: "remove", path: path } : op === "remove" ? { op: "add", path: path, value: origValue } : { op: "replace", path: path, value: origValue });
});
}
function applyPatches(draft, patches) {
for (var i = 0; i < patches.length; i++) {
var patch = patches[i];
var path = patch.path;
if (path.length === 0 && patch.op === "replace") {
draft = patch.value;
} else {
var base = draft;
for (var _i3 = 0; _i3 < path.length - 1; _i3++) {
base = base[path[_i3]];
if (!base || (typeof base === "undefined" ? "undefined" : _typeof(base)) !== "object") throw new Error("Cannot apply patch, path doesn't resolve: " + path.join("/")); // prettier-ignore
}
var key = path[path.length - 1];
switch (patch.op) {
case "replace":
case "add":
// TODO: add support is not extensive, it does not support insertion or `-` atm!
base[key] = patch.value;
break;
case "remove":
if (Array.isArray(base)) {
if (key !== base.length - 1) throw new Error("Only the last index of an array can be removed, index: " + key + ", length: " + base.length); // prettier-ignore
base.length -= 1;
} else {
delete base[key];
}
break;
default:
throw new Error("Unsupported patch operation: " + patch.op);
}
}
}
return draft;
}
function verifyMinified() {}
var configDefaults = {
useProxies: typeof Proxy !== "undefined" && typeof Reflect !== "undefined",
autoFreeze: typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : verifyMinified.name === "verifyMinified",
onAssign: null,
onDelete: null,
onCopy: null
};
var Immer = function () {
function Immer(config) {
classCallCheck(this, Immer);
assign(this, configDefaults, config);
this.setUseProxies(this.useProxies);
this.produce = this.produce.bind(this);
}
createClass(Immer, [{
key: "produce",
value: function produce(base, recipe, patchListener) {
var _this = this;
// curried invocation
if (typeof base === "function" && typeof recipe !== "function") {
var defaultBase = recipe;
recipe = base;
// prettier-ignore
return function () {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var base = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultBase;
return _this.produce(base, function (draft) {
var _recipe;
return (_recipe = recipe).call.apply(_recipe, [draft, draft].concat(args));
});
};
}
// prettier-ignore
{
if (typeof recipe !== "function") throw new Error("if first argument is not a function, the second argument to produce should be a function");
if (patchListener !== undefined && typeof patchListener !== "function") throw new Error("the third argument of a producer should not be set or a function");
}
var result = void 0;
// Only create proxies for plain objects/arrays.
if (!isDraftable(base)) {
result = recipe(base);
if (result === undefined) return base;
}
// The given value must be proxied.
else {
this.scopes.push([]);
var baseDraft = this.createDraft(base);
try {
result = recipe.call(baseDraft, baseDraft);
this.willFinalize(result, baseDraft, !!patchListener);
// Never generate patches when no listener exists.
var patches = patchListener && [],
inversePatches = patchListener && [];
// Finalize the modified draft...
if (result === undefined || result === baseDraft) {
result = this.finalize(baseDraft, [], patches, inversePatches);
}
// ...or use a replacement value.
else {
// Users must never modify the draft _and_ return something else.
if (baseDraft[DRAFT_STATE].modified) throw new Error("An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."); // prettier-ignore
// Finalize the replacement in case it contains (or is) a subset of the draft.
if (isDraftable(result)) result = this.finalize(result);
if (patchListener) {
patches.push({
op: "replace",
path: [],
value: result
});
inversePatches.push({
op: "replace",
path: [],
value: base
});
}
}
} finally {
this.currentScope().forEach(function (state) {
return state.revoke();
});
this.scopes.pop();
}
patchListener && patchListener(patches, inversePatches);
}
// Normalize the result.
return result === NOTHING ? undefined : result;
}
}, {
key: "setAutoFreeze",
value: function setAutoFreeze(value) {
this.autoFreeze = value;
}
}, {
key: "setUseProxies",
value: function setUseProxies(value) {
this.useProxies = value;
assign(this, value ? modernProxy : legacyProxy);
}
}, {
key: "applyPatches",
value: function applyPatches$$1(base, patches) {
// Mutate the base state when a draft is passed.
if (isDraft(base)) {
return applyPatches(base, patches);
}
// Otherwise, produce a copy of the base state.
return this.produce(base, function (draft) {
return applyPatches(draft, patches);
});
}
/**
* @internal
* Finalize a draft, returning either the unmodified base state or a modified
* copy of the base state.
*/
}, {
key: "finalize",
value: function finalize(draft, path, patches, inversePatches) {
var _this2 = this;
var state = draft[DRAFT_STATE];
if (!state) {
if (Object.isFrozen(draft)) return draft;
return this.finalizeTree(draft);
}
// Never finalize drafts owned by an outer scope.
if (state.scope !== this.currentScope()) {
return draft;
}
if (!state.modified) return state.base;
if (!state.finalized) {
state.finalized = true;
this.finalizeTree(state.draft, path, patches, inversePatches);
if (this.onDelete) {
// The `assigned` object is unreliable with ES5 drafts.
if (this.useProxies) {
var assigned = state.assigned;
for (var prop in assigned) {
if (!assigned[prop]) this.onDelete(state, prop);
}
} else {
var base = state.base,
copy = state.copy;
each(base, function (prop) {
if (!has(copy, prop)) _this2.onDelete(state, prop);
});
}
}
if (this.onCopy) this.onCopy(state);
// Nested producers must never auto-freeze their result,
// because it may contain drafts from parent producers.
if (this.autoFreeze && this.scopes.length === 1) {
Object.freeze(state.copy);
}
if (patches) generatePatches(state, path, patches, inversePatches);
}
return state.copy;
}
/**
* @internal
* Finalize all drafts in the given state tree.
*/
}, {
key: "finalizeTree",
value: function finalizeTree(root, path, patches, inversePatches) {
var _this3 = this;
var state = root[DRAFT_STATE];
if (state) {
if (!this.useProxies) {
state.finalizing = true;
state.copy = shallowCopy(state.draft, true);
state.finalizing = false;
}
root = state.copy;
}
var onAssign = this.onAssign;
var finalizeProperty = function finalizeProperty(prop, value, parent) {
if (value === parent) {
throw Error("Immer forbids circular references");
}
// The only possible draft (in the scope of a `finalizeTree` call) is the `root` object.
var inDraft = !!state && parent === root;
if (isDraft(value)) {
value =
// Patches are never generated for assigned properties.
patches && inDraft && !state.assigned[prop] ? _this3.finalize(value, path.concat(prop), patches, inversePatches) // prettier-ignore
: _this3.finalize(value);
// Preserve non-enumerable properties.
if (Array.isArray(parent) || isEnumerable(parent, prop)) {
parent[prop] = value;
} else {
Object.defineProperty(parent, prop, { value: value });
}
// Unchanged drafts are never passed to the `onAssign` hook.
if (inDraft && value === state.base[prop]) return;
}
// Unchanged draft properties are ignored.
else if (inDraft && is(value, state.base[prop])) {
return;
}
// Search new objects for unfinalized drafts. Frozen objects should never contain drafts.
else if (isDraftable(value) && !Object.isFrozen(value)) {
each(value, finalizeProperty);
}
if (inDraft && onAssign) {
onAssign(state, prop, value);
}
};
each(root, finalizeProperty);
return root;
}
}]);
return Immer;
}();
var immer = new Immer();
/**
* The `produce` function takes a value and a "recipe function" (whose
* return value often depends on the base state). The recipe function is
* free to mutate its first argument however it wants. All mutations are
* only ever applied to a __copy__ of the base state.
*
* Pass only a function to create a "curried producer" which relieves you
* from passing the recipe function every time.
*
* Only plain objects and arrays are made mutable. All other objects are
* considered uncopyable.
*
* Note: This function is __bound__ to its `Immer` instance.
*
* @param {any} base - the initial state
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
var produce = immer.produce;
/**
* Pass true to automatically freeze all copies created by Immer.
*
* By default, auto-freezing is disabled in production.
*/
var setAutoFreeze = immer.setAutoFreeze.bind(immer);
/**
* Pass true to use the ES2015 `Proxy` class when creating drafts, which is
* always faster than using ES5 proxies.
*
* By default, feature detection is used, so calling this is rarely necessary.
*/
var setUseProxies = immer.setUseProxies.bind(immer);
/**
* Apply an array of Immer patches to the first argument.
*
* This function is a producer, which means copy-on-write is in effect.
*/
var applyPatches$1 = immer.applyPatches.bind(immer);
/*
* Copyright 2018 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
/**
* Plugin that allows using Immer to make immutable changes
* to G by just mutating it.
*/
var PluginImmer = {
name: 'plugin-immer',
fnWrap: function fnWrap(move) {
return produce(move);
}
};
function _typeof$1(obj) {
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof$1 = function (obj) {
return typeof obj;
};
} else {
_typeof$1 = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof$1(obj);
}
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;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function ownKeys$1(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys$1(source, true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys$1(source).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
return arr2;
}
}
function _iterableToArray(iter) {
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance");
}
// Inlined version of Alea from https://github.com/davidbau/seedrandom.
/*
* Copyright 2015 David Bau.
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software
* and associated documentation files (the "Software"),
* to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall
* be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
function Alea(seed) {
var me = this,
mash = Mash();
me.next = function () {
var t = 2091639 * me.s0 + me.c * 2.3283064365386963e-10; // 2^-32
me.s0 = me.s1;
me.s1 = me.s2;
return me.s2 = t - (me.c = t | 0);
}; // Apply the seeding algorithm from Baagoe.
me.c = 1;
me.s0 = mash(' ');
me.s1 = mash(' ');
me.s2 = mash(' ');
me.s0 -= mash(seed);
if (me.s0 < 0) {
me.s0 += 1;
}
me.s1 -= mash(seed);
if (me.s1 < 0) {
me.s1 += 1;
}
me.s2 -= mash(seed);
if (me.s2 < 0) {
me.s2 += 1;
}
mash = null;
}
function copy(f, t) {
t.c = f.c;
t.s0 = f.s0;
t.s1 = f.s1;
t.s2 = f.s2;
return t;
}
function Mash() {
var n = 0xefc8249d;
var mash = function mash(data) {
data = data.toString();
for (var i = 0; i < data.length; i++) {
n += data.charCodeAt(i);
var h = 0.02519603282416938 * n;
n = h >>> 0;
h -= n;
h *= n;
n = h >>> 0;
h -= n;
n += h * 0x100000000; // 2^32
}
return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
};
return mash;
}
function alea(seed, opts) {
var xg = new Alea(seed),
state = opts && opts.state,
prng = xg.next;
prng.quick = prng;
if (state) {
if (_typeof$1(state) == 'object') copy(state, xg);
prng.state = function () {
return copy(xg, {});
};
}
return prng;
}
/**
* Random
*
* Calls that require a pseudorandom number generator.
* Uses a seed from ctx, and also persists the PRNG
* state in ctx so that moves can stay pure.
*/
var Random =
/*#__PURE__*/
function () {
/**
* constructor
* @param {object} ctx - The ctx object to initialize from.
*/
function Random(state) {
_classCallCheck(this, Random);
// If we are on the client, the seed is not present.
// Just use a temporary seed to execute the move without
// crashing it. The move state itself is discarded,
// so the actual value doesn't matter.
this.state = state;
this.used = false;
}
_createClass(Random, [{
key: "isUsed",
value: function isUsed() {
return this.used;
}
}, {
key: "getState",
value: function getState() {
return this.state;
}
/**
* Generate a random number.
*/
}, {
key: "_random",
value: function _random() {
this.used = true;
var R = this.state;
var fn;
if (R.prngstate === undefined) {
// No call to a random function has been made.
fn = new alea(R.seed, {
state: true
});
} else {
fn = new alea('', {
state: R.prngstate
});
}
var number = fn();
this.state = _objectSpread2({}, R, {
prngstate: fn.state()
});
return number;
}
}, {
key: "api",
value: function api() {
var random = this._random.bind(this);
var SpotValue = {
D4: 4,
D6: 6,
D8: 8,
D10: 10,
D12: 12,
D20: 20
}; // Generate functions for predefined dice values D4 - D20.
var predefined = {};
var _loop = function _loop(key) {
var spotvalue = SpotValue[key];
predefined[key] = function (diceCount) {
if (diceCount === undefined) {
return Math.floor(random() * spotvalue) + 1;
} else {
return _toConsumableArray(new Array(diceCount).keys()).map(function () {
return Math.floor(random() * spotvalue) + 1;
});
}
};
};
for (var key in SpotValue) {
_loop(key);
}
return _objectSpread2({}, predefined, {
/**
* Roll a die of specified spot value.
*
* @param {number} spotvalue - The die dimension (default: 6).
* @param {number} diceCount - number of dice to throw.
* if not defined, defaults to 1 and returns the value directly.
* if defined, returns an array containing the random dice values.
*/
Die: function Die(spotvalue, diceCount) {
if (spotvalue === undefined) {
spotvalue = 6;
}
if (diceCount === undefined) {
return Math.floor(random() * spotvalue) + 1;
} else {
return _toConsumableArray(new Array(diceCount).keys()).map(function () {
return Math.floor(random() * spotvalue) + 1;
});
}
},
/**
* Generate a random number between 0 and 1.
*/
Number: function Number() {
return random();
},
/**
* Shuffle an array.
*
* @param {Array} deck - The array to shuffle. Does not mutate
* the input, but returns the shuffled array.
*/
Shuffle: function Shuffle(deck) {
var clone = deck.slice(0);
var srcIndex = deck.length;
var dstIndex = 0;
var shuffled = new Array(srcIndex);
while (srcIndex) {
var randIndex = srcIndex * random() | 0;
shuffled[dstIndex++] = clone[randIndex];
clone[randIndex] = clone[--srcIndex];
}
return shuffled;
},
_obj: this
});
}
}]);
return Random;
}();
/**
* Generates a new seed from the current date / time.
*/
Random.seed = function () {
return (+new Date()).toString(36).slice(-10);
};
/*
* Copyright 2018 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
var PluginRandom = {
name: 'random',
noClient: ({ api }) => {
return api._obj.isUsed();
},
flush: ({ api }) => {
return api._obj.getState();
},
api: ({ data }) => {
const random = new Random(data);
return random.api();
},
setup: ({ game }) => {
let seed = game.seed;
if (seed === undefined) {
seed = Random.seed();
}
return { seed };
},
};
/*
* Copyright 2017 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
const MAKE_MOVE = 'MAKE_MOVE';
const GAME_EVENT = 'GAME_EVENT';
const REDO = 'REDO';
const RESET = 'RESET';
const SYNC = 'SYNC';
const UNDO = 'UNDO';
const UPDATE = 'UPDATE';
const PLUGIN = 'PLUGIN';
/*
* Copyright 2017 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
/**
* Generate a game event to be dispatched to the flow reducer.
*
* @param {string} type - The event type.
* @param {Array} args - Additional arguments.
* @param {string} playerID - The ID of the player making this action.
* @param {string} credentials - (optional) The credentials for the player making this action.
*/
const gameEvent = (type, args, playerID, credentials) => ({
type: GAME_EVENT,
payload: { type, args, playerID, credentials },
});
/**
* Generate an automatic game event that is a side-effect of a move.
* @param {string} type - The event type.
* @param {Array} args - Additional arguments.
* @param {string} playerID - The ID of the player making this action.
* @param {string} credentials - (optional) The credentials for the player making this action.
*/
const automaticGameEvent = (type, args, playerID, credentials) => ({
type: GAME_EVENT,
payload: { type, args, playerID, credentials },
automatic: true,
});
/**
* Events
*/
var Events =
/*#__PURE__*/
function () {
function Events(flow, playerID) {
_classCallCheck(this, Events);
this.flow = flow;
this.playerID = playerID;
this.dispatch = [];
}
/**
* Attaches the Events API to ctx.
* @param {object} ctx - The ctx object to attach to.
*/
_createClass(Events, [{
key: "api",
value: function api(ctx) {
var _this = this;
var events = {};
var phase = ctx.phase,
turn = ctx.turn;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
var _loop = function _loop() {
var key = _step.value;
events[key] = function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this.dispatch.push({
key: key,
args: args,
phase: phase,
turn: turn
});
};
};
for (var _iterator = this.flow.eventNames[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
_loop();
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator["return"] != null) {
_iterator["return"]();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
events._obj = this;
return events;
}
}, {
key: "isUsed",
value: function isUsed() {
return this.dispatch.length > 0;
}
/**
* Updates ctx with the triggered events.
* @param {object} state - The state object { G, ctx }.
*/
}, {
key: "update",
value: function update(state) {
for (var i = 0; i < this.dispatch.length; i++) {
var item = this.dispatch[i]; // If the turn already ended some other way,
// don't try to end the turn again.
if (item.key === 'endTurn' && item.turn !== state.ctx.turn) {
continue;
} // If the phase already ended some other way,
// don't try to end the phase again.
if ((item.key === 'endPhase' || item.key === 'setPhase') && item.phase !== state.ctx.phase) {
continue;
}
var action = automaticGameEvent(item.key, item.args, this.playerID);
state = _objectSpread2({}, state, {}, this.flow.processEvent(state, action));
}
return state;
}
}]);
return Events;
}();
/*
* Copyright 2020 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
var PluginEvents = {
name: 'events',
noClient: ({ api }) => {
return api._obj.isUsed();
},
flushRaw: ({ state, api }) => {
return api._obj.update(state);
},
api: ({ game, playerID, ctx }) => {
return new Events(game.flow, playerID).api(ctx);
},
};
/*
* Copyright 2018 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
/**
* List of plugins that are always added.
*/
const DEFAULT_PLUGINS = [PluginImmer, PluginRandom, PluginEvents];
/**
* Allow plugins to intercept actions and process them.
*/
const ProcessAction = (state, action, opts) => {
opts.game.plugins
.filter(plugin => plugin.action !== undefined)
.filter(plugin => plugin.name === action.payload.type)
.forEach(plugin => {
const name = plugin.name;
const pluginState = state.plugins[name] || { data: {} };
const data = plugin.action(pluginState.data, action.payload);
state = {
...state,
plugins: {
...state.plugins,
[name]: { ...pluginState, data },
},
};
});
return state;
};
/**
* The API's created by various plugins are stored in the plugins
* section of the state object:
*
* {
* G: {},
* ctx: {},
* plugins: {
* plugin-a: {
* data: {}, // this is generated by the plugin at Setup / Flush.
* api: {}, // this is ephemeral and generated by Enhance.
* }
* }
* }
*
* This function takes these API's and stuffs them back into
* ctx for consumption inside a move function or hook.
*/
const EnhanceCtx = (state) => {
let ctx = { ...state.ctx };
const plugins = state.plugins || {};
Object.entries(plugins).forEach((