boardgame.io
Version:
library for turn-based games
1,717 lines (1,615 loc) • 278 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var redux = require('redux');
var produce = _interopDefault(require('immer'));
var flatted = require('flatted');
var React = _interopDefault(require('react'));
var PropTypes = _interopDefault(require('prop-types'));
var io = _interopDefault(require('socket.io-client'));
function noop() { }
const identity = x => x;
function assign(tar, src) {
// @ts-ignore
for (const k in src)
tar[k] = src[k];
return tar;
}
function run(fn) {
return fn();
}
function blank_object() {
return Object.create(null);
}
function run_all(fns) {
fns.forEach(run);
}
function is_function(thing) {
return typeof thing === 'function';
}
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function subscribe(store, callback) {
const unsub = store.subscribe(callback);
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
}
function component_subscribe(component, store, callback) {
component.$$.on_destroy.push(subscribe(store, callback));
}
function create_slot(definition, ctx, fn) {
if (definition) {
const slot_ctx = get_slot_context(definition, ctx, fn);
return definition[0](slot_ctx);
}
}
function get_slot_context(definition, ctx, fn) {
return definition[1]
? assign({}, assign(ctx.$$scope.ctx, definition[1](fn ? fn(ctx) : {})))
: ctx.$$scope.ctx;
}
function get_slot_changes(definition, ctx, changed, fn) {
return definition[1]
? assign({}, assign(ctx.$$scope.changed || {}, definition[1](fn ? fn(changed) : {})))
: ctx.$$scope.changed || {};
}
function exclude_internal_props(props) {
const result = {};
for (const k in props)
if (k[0] !== '$')
result[k] = props[k];
return result;
}
const is_client = typeof window !== 'undefined';
let now = is_client
? () => window.performance.now()
: () => Date.now();
let raf = is_client ? cb => requestAnimationFrame(cb) : noop;
const tasks = new Set();
let running = false;
function run_tasks() {
tasks.forEach(task => {
if (!task[0](now())) {
tasks.delete(task);
task[1]();
}
});
running = tasks.size > 0;
if (running)
raf(run_tasks);
}
function loop(fn) {
let task;
if (!running) {
running = true;
raf(run_tasks);
}
return {
promise: new Promise(fulfil => {
tasks.add(task = [fn, fulfil]);
}),
abort() {
tasks.delete(task);
}
};
}
function append(target, node) {
target.appendChild(node);
}
function insert(target, node, anchor) {
target.insertBefore(node, anchor || null);
}
function detach(node) {
node.parentNode.removeChild(node);
}
function destroy_each(iterations, detaching) {
for (let i = 0; i < iterations.length; i += 1) {
if (iterations[i])
iterations[i].d(detaching);
}
}
function element(name) {
return document.createElement(name);
}
function svg_element(name) {
return document.createElementNS('http://www.w3.org/2000/svg', name);
}
function text(data) {
return document.createTextNode(data);
}
function space() {
return text(' ');
}
function empty() {
return text('');
}
function listen(node, event, handler, options) {
node.addEventListener(event, handler, options);
return () => node.removeEventListener(event, handler, options);
}
function stop_propagation(fn) {
return function (event) {
event.stopPropagation();
// @ts-ignore
return fn.call(this, event);
};
}
function attr(node, attribute, value) {
if (value == null)
node.removeAttribute(attribute);
else
node.setAttribute(attribute, value);
}
function to_number(value) {
return value === '' ? undefined : +value;
}
function children(element) {
return Array.from(element.childNodes);
}
function set_data(text, data) {
data = '' + data;
if (text.data !== data)
text.data = data;
}
function set_input_value(input, value) {
if (value != null || input.value) {
input.value = value;
}
}
function select_option(select, value) {
for (let i = 0; i < select.options.length; i += 1) {
const option = select.options[i];
if (option.__value === value) {
option.selected = true;
return;
}
}
}
function select_value(select) {
const selected_option = select.querySelector(':checked') || select.options[0];
return selected_option && selected_option.__value;
}
function toggle_class(element, name, toggle) {
element.classList[toggle ? 'add' : 'remove'](name);
}
function custom_event(type, detail) {
const e = document.createEvent('CustomEvent');
e.initCustomEvent(type, false, false, detail);
return e;
}
let stylesheet;
let active = 0;
let current_rules = {};
// https://github.com/darkskyapp/string-hash/blob/master/index.js
function hash(str) {
let hash = 5381;
let i = str.length;
while (i--)
hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return hash >>> 0;
}
function create_rule(node, a, b, duration, delay, ease, fn, uid = 0) {
const step = 16.666 / duration;
let keyframes = '{\n';
for (let p = 0; p <= 1; p += step) {
const t = a + (b - a) * ease(p);
keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`;
}
const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`;
const name = `__svelte_${hash(rule)}_${uid}`;
if (!current_rules[name]) {
if (!stylesheet) {
const style = element('style');
document.head.appendChild(style);
stylesheet = style.sheet;
}
current_rules[name] = true;
stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
}
const animation = node.style.animation || '';
node.style.animation = `${animation ? `${animation}, ` : ``}${name} ${duration}ms linear ${delay}ms 1 both`;
active += 1;
return name;
}
function delete_rule(node, name) {
node.style.animation = (node.style.animation || '')
.split(', ')
.filter(name
? anim => anim.indexOf(name) < 0 // remove specific animation
: anim => anim.indexOf('__svelte') === -1 // remove all Svelte animations
)
.join(', ');
if (name && !--active)
clear_rules();
}
function clear_rules() {
raf(() => {
if (active)
return;
let i = stylesheet.cssRules.length;
while (i--)
stylesheet.deleteRule(i);
current_rules = {};
});
}
let current_component;
function set_current_component(component) {
current_component = component;
}
function get_current_component() {
if (!current_component)
throw new Error(`Function called outside component initialization`);
return current_component;
}
function afterUpdate(fn) {
get_current_component().$$.after_update.push(fn);
}
function onDestroy(fn) {
get_current_component().$$.on_destroy.push(fn);
}
function createEventDispatcher() {
const component = current_component;
return (type, detail) => {
const callbacks = component.$$.callbacks[type];
if (callbacks) {
// TODO are there situations where events could be dispatched
// in a server (non-DOM) environment?
const event = custom_event(type, detail);
callbacks.slice().forEach(fn => {
fn.call(component, event);
});
}
};
}
function setContext(key, context) {
get_current_component().$$.context.set(key, context);
}
function getContext(key) {
return get_current_component().$$.context.get(key);
}
const dirty_components = [];
const binding_callbacks = [];
const render_callbacks = [];
const flush_callbacks = [];
const resolved_promise = Promise.resolve();
let update_scheduled = false;
function schedule_update() {
if (!update_scheduled) {
update_scheduled = true;
resolved_promise.then(flush);
}
}
function add_render_callback(fn) {
render_callbacks.push(fn);
}
function flush() {
const seen_callbacks = new Set();
do {
// first, call beforeUpdate functions
// and update components
while (dirty_components.length) {
const component = dirty_components.shift();
set_current_component(component);
update(component.$$);
}
while (binding_callbacks.length)
binding_callbacks.pop()();
// then, once components are updated, call
// afterUpdate functions. This may cause
// subsequent updates...
for (let i = 0; i < render_callbacks.length; i += 1) {
const callback = render_callbacks[i];
if (!seen_callbacks.has(callback)) {
callback();
// ...so guard against infinite loops
seen_callbacks.add(callback);
}
}
render_callbacks.length = 0;
} while (dirty_components.length);
while (flush_callbacks.length) {
flush_callbacks.pop()();
}
update_scheduled = false;
}
function update($$) {
if ($$.fragment) {
$$.update($$.dirty);
run_all($$.before_update);
$$.fragment.p($$.dirty, $$.ctx);
$$.dirty = null;
$$.after_update.forEach(add_render_callback);
}
}
let promise;
function wait() {
if (!promise) {
promise = Promise.resolve();
promise.then(() => {
promise = null;
});
}
return promise;
}
function dispatch(node, direction, kind) {
node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`));
}
const outroing = new Set();
let outros;
function group_outros() {
outros = {
r: 0,
c: [],
p: outros // parent group
};
}
function check_outros() {
if (!outros.r) {
run_all(outros.c);
}
outros = outros.p;
}
function transition_in(block, local) {
if (block && block.i) {
outroing.delete(block);
block.i(local);
}
}
function transition_out(block, local, detach, callback) {
if (block && block.o) {
if (outroing.has(block))
return;
outroing.add(block);
outros.c.push(() => {
outroing.delete(block);
if (callback) {
if (detach)
block.d(1);
callback();
}
});
block.o(local);
}
}
const null_transition = { duration: 0 };
function create_bidirectional_transition(node, fn, params, intro) {
let config = fn(node, params);
let t = intro ? 0 : 1;
let running_program = null;
let pending_program = null;
let animation_name = null;
function clear_animation() {
if (animation_name)
delete_rule(node, animation_name);
}
function init(program, duration) {
const d = program.b - t;
duration *= Math.abs(d);
return {
a: t,
b: program.b,
d,
duration,
start: program.start,
end: program.start + duration,
group: program.group
};
}
function go(b) {
const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition;
const program = {
start: now() + delay,
b
};
if (!b) {
// @ts-ignore todo: improve typings
program.group = outros;
outros.r += 1;
}
if (running_program) {
pending_program = program;
}
else {
// if this is an intro, and there's a delay, we need to do
// an initial tick and/or apply CSS animation immediately
if (css) {
clear_animation();
animation_name = create_rule(node, t, b, duration, delay, easing, css);
}
if (b)
tick(0, 1);
running_program = init(program, duration);
add_render_callback(() => dispatch(node, b, 'start'));
loop(now => {
if (pending_program && now > pending_program.start) {
running_program = init(pending_program, duration);
pending_program = null;
dispatch(node, running_program.b, 'start');
if (css) {
clear_animation();
animation_name = create_rule(node, t, running_program.b, running_program.duration, 0, easing, config.css);
}
}
if (running_program) {
if (now >= running_program.end) {
tick(t = running_program.b, 1 - t);
dispatch(node, running_program.b, 'end');
if (!pending_program) {
// we're done
if (running_program.b) {
// intro — we can tidy up immediately
clear_animation();
}
else {
// outro — needs to be coordinated
if (!--running_program.group.r)
run_all(running_program.group.c);
}
}
running_program = null;
}
else if (now >= running_program.start) {
const p = now - running_program.start;
t = running_program.a + running_program.d * easing(p / running_program.duration);
tick(t, 1 - t);
}
}
return !!(running_program || pending_program);
});
}
}
return {
run(b) {
if (is_function(config)) {
wait().then(() => {
// @ts-ignore
config = config();
go(b);
});
}
else {
go(b);
}
},
end() {
clear_animation();
running_program = pending_program = null;
}
};
}
const globals = (typeof window !== 'undefined' ? window : global);
function get_spread_update(levels, updates) {
const update = {};
const to_null_out = {};
const accounted_for = { $$scope: 1 };
let i = levels.length;
while (i--) {
const o = levels[i];
const n = updates[i];
if (n) {
for (const key in o) {
if (!(key in n))
to_null_out[key] = 1;
}
for (const key in n) {
if (!accounted_for[key]) {
update[key] = n[key];
accounted_for[key] = 1;
}
}
levels[i] = n;
}
else {
for (const key in o) {
accounted_for[key] = 1;
}
}
}
for (const key in to_null_out) {
if (!(key in update))
update[key] = undefined;
}
return update;
}
function get_spread_object(spread_props) {
return typeof spread_props === 'object' && spread_props !== null ? spread_props : {};
}
function mount_component(component, target, anchor) {
const { fragment, on_mount, on_destroy, after_update } = component.$$;
fragment.m(target, anchor);
// onMount happens before the initial afterUpdate
add_render_callback(() => {
const new_on_destroy = on_mount.map(run).filter(is_function);
if (on_destroy) {
on_destroy.push(...new_on_destroy);
}
else {
// Edge case - component was destroyed immediately,
// most likely as a result of a binding initialising
run_all(new_on_destroy);
}
component.$$.on_mount = [];
});
after_update.forEach(add_render_callback);
}
function destroy_component(component, detaching) {
if (component.$$.fragment) {
run_all(component.$$.on_destroy);
component.$$.fragment.d(detaching);
// TODO null out other refs, including component.$$ (but need to
// preserve final state?)
component.$$.on_destroy = component.$$.fragment = null;
component.$$.ctx = {};
}
}
function make_dirty(component, key) {
if (!component.$$.dirty) {
dirty_components.push(component);
schedule_update();
component.$$.dirty = blank_object();
}
component.$$.dirty[key] = true;
}
function init(component, options, instance, create_fragment, not_equal, prop_names) {
const parent_component = current_component;
set_current_component(component);
const props = options.props || {};
const $$ = component.$$ = {
fragment: null,
ctx: null,
// state
props: prop_names,
update: noop,
not_equal,
bound: blank_object(),
// lifecycle
on_mount: [],
on_destroy: [],
before_update: [],
after_update: [],
context: new Map(parent_component ? parent_component.$$.context : []),
// everything else
callbacks: blank_object(),
dirty: null
};
let ready = false;
$$.ctx = instance
? instance(component, props, (key, ret, value = ret) => {
if ($$.ctx && not_equal($$.ctx[key], $$.ctx[key] = value)) {
if ($$.bound[key])
$$.bound[key](value);
if (ready)
make_dirty(component, key);
}
return ret;
})
: props;
$$.update();
ready = true;
run_all($$.before_update);
$$.fragment = create_fragment($$.ctx);
if (options.target) {
if (options.hydrate) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$$.fragment.l(children(options.target));
}
else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$$.fragment.c();
}
if (options.intro)
transition_in(component.$$.fragment);
mount_component(component, options.target, options.anchor);
flush();
}
set_current_component(parent_component);
}
class SvelteComponent {
$destroy() {
destroy_component(this, 1);
this.$destroy = noop;
}
$on(type, callback) {
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
callbacks.push(callback);
return () => {
const index = callbacks.indexOf(callback);
if (index !== -1)
callbacks.splice(index, 1);
};
}
$set() {
// overridden by instance, if it has props
}
}
/*
* 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 move to be dispatched to the game move reducer.
*
* @param {string} type - The move 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 makeMove = (type, args, playerID, credentials) => ({
type: MAKE_MOVE,
payload: { type, args, playerID, credentials },
});
/**
* 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,
});
const sync = (info) => ({
type: SYNC,
state: info.state,
log: info.log,
initialState: info.initialState,
clientOnly: true,
});
/**
* Used to update the Redux store's state in response to
* an action coming from another player.
* @param {object} state - The state to restore.
* @param {Array} deltalog - A log delta.
*/
const update$1 = (state, deltalog) => ({
type: UPDATE,
state,
deltalog,
clientOnly: true,
});
/**
* Used to reset the game state.
* @param {object} state - The initial state.
*/
const reset = (state) => ({
type: RESET,
state,
clientOnly: true,
});
/**
* Used to undo the last move.
* @param {string} playerID - The ID of the player making this action.
* @param {string} credentials - (optional) The credentials for the player making this action.
*/
const undo = (playerID, credentials) => ({
type: UNDO,
payload: { type: null, args: null, playerID, credentials },
});
/**
* Used to redo the last undone move.
* @param {string} playerID - The ID of the player making this action.
* @param {string} credentials - (optional) The credentials for the player making this action.
*/
const redo = (playerID, credentials) => ({
type: REDO,
payload: { type: null, args: null, playerID, credentials },
});
/**
* Allows plugins to define their own actions and intercept them.
*/
const plugin = (type, args, playerID, credentials) => ({
type: PLUGIN,
payload: { type, args, playerID, credentials },
});
var ActionCreators = /*#__PURE__*/Object.freeze({
makeMove: makeMove,
gameEvent: gameEvent,
automaticGameEvent: automaticGameEvent,
sync: sync,
update: update$1,
reset: reset,
undo: undo,
redo: redo,
plugin: plugin
});
/**
* Moves can return this when they want to indicate
* that the combination of arguments is illegal and
* the move ought to be discarded.
*/
const INVALID_MOVE = 'INVALID_MOVE';
/*
* 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.
*/
const ImmerPlugin = {
name: 'plugin-immer',
fnWrap: (move) => (G, ctx, ...args) => {
let isInvalid = false;
const newG = produce(G, G => {
const result = move(G, ctx, ...args);
if (result === INVALID_MOVE) {
isInvalid = true;
return;
}
return result;
});
if (isInvalid)
return INVALID_MOVE;
return newG;
},
};
function _typeof(obj) {
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(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(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(source, true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(source).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _possibleConstructorReturn(self, call) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
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(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.
*/
const RandomPlugin = {
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 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.
*/
/**
* Events
*/
class Events {
constructor(flow, playerID) {
this.flow = flow;
this.playerID = playerID;
this.dispatch = [];
}
/**
* Attaches the Events API to ctx.
* @param {object} ctx - The ctx object to attach to.
*/
api(ctx) {
const events = {
_obj: this,
};
const { phase, turn } = ctx;
for (const key of this.flow.eventNames) {
events[key] = (...args) => {
this.dispatch.push({ key, args, phase, turn });
};
}
return events;
}
isUsed() {
return this.dispatch.length > 0;
}
/**
* Updates ctx with the triggered events.
* @param {object} state - The state object { G, ctx }.
*/
update(state) {
for (let i = 0; i < this.dispatch.length; i++) {
const 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;
}
const action = automaticGameEvent(item.key, item.args, this.playerID);
state = {
...state,
...this.flow.processEvent(state, action),
};
}
return state;
}
}
/*
* 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.
*/
const EventsPlugin = {
name: 'events',
noClient: ({ api }) => {
return api._obj.isUsed();
},
dangerouslyFlushRawState: ({ 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 = [ImmerPlugin, RandomPlugin, EventsPlugin];
/**
* 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(([name, { api }]) => {
ctx[name] = api;
});
return ctx;
};
/**
* Applies the provided plugins to the given move / flow function.
*
* @param {function} fn - The move function or trigger to apply the plugins to.
* @param {object} plugins - The list of plugins.
*/
const FnWrap = (fn, plugins) => {
const reducer = (acc, { fnWrap }) => fnWrap(acc);
return [...DEFAULT_PLUGINS, ...plugins]
.filter(plugin => plugin.fnWrap !== undefined)
.reduce(reducer, fn);
};
/**
* Allows the plugin to generate its initial state.
*/
const Setup = (state, opts) => {
[...DEFAULT_PLUGINS, ...opts.game.plugins]
.filter(plugin => plugin.setup !== undefined)
.forEach(plugin => {
const name = plugin.name;
const data = plugin.setup({
G: state.G,
ctx: state.ctx,
game: opts.game,
});
state = {
...state,
plugins: {
...state.plugins,
[name]: { data },
},
};
});
return state;
};
/**
* Invokes the plugin before a move or event.
* The API that the plugin generates is stored inside
* the `plugins` section of the state (which is subsequently
* merged into ctx).
*/
const Enhance = (state, opts) => {
[...DEFAULT_PLUGINS, ...opts.game.plugins]
.filter(plugin => plugin.api !== undefined)
.forEach(plugin => {
const name = plugin.name;
const pluginState = state.plugins[name] || { data: {} };
const api = plugin.api({
G: state.G,
ctx: state.ctx,
data: pluginState.data,
game: opts.game,
playerID: opts.playerID,
});
state = {
...state,
plugins: {
...state.plugins,
[name]: { ...pluginState, api },
},
};
});
return state;
};
/**
* Allows plugins to update their state after a move / event.
*/
const Flush = (state, opts) => {
// Note that we flush plugins in reverse order, to make sure that plugins
// that come before in the chain are still available.
[...DEFAULT_PLUGINS, ...opts.game.plugins].reverse().forEach(plugin => {
const name = plugin.name;
const pluginState = state.plugins[name] || { data: {} };
if (plugin.flush) {
const newData = plugin.flush({
G: state.G,
ctx: state.ctx,
game: opts.game,
api: pluginState.api,
data: pluginState.data,
});
state = {
...state,
plugins: {
...state.plugins,
[plugin.name]: { data: newData },
},
};
}
else if (plugin.dangerouslyFlushRawState) {
state = plugin.dangerouslyFlushRawState({
state,
game: opts.game,
api: pluginState.api,
data: pluginState.data,
});
// Remove everything other than data.
const data = state.plugins[name].data;
state = {
...state,
plugins: {
...state.plugins,
[plugin.name]: { data },
},
};
}
});
return state;
};
/**
* Allows plugins to indicate if they should not be materialized on the client.
* This will cause the client to discard the state update and wait for the
* master instead.
*/
const NoClient = (state, opts) => {
return [...DEFAULT_PLUGINS, ...opts.game.plugins]
.filter(plugin => plugin.noClient !== undefined)
.map(plugin => {
const name = plugin.name;
const pluginState = state.plugins[name];
if (pluginState) {
return plugin.noClient({
G: state.G,
ctx: state.ctx,
game: opts.game,
api: pluginState.api,
data: pluginState.data,
});
}
return false;
})
.some(value => value === true);
};
/*
* 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.
*/
const production = process.env.NODE_ENV === 'production';
const logfn = production ? () => { } : console.log;
const errorfn = console.error;
function info(msg) {
logfn(`INFO: ${msg}`);
}
function error(error) {
errorfn('ERROR:', error);
}
/*
* 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.
*/
/**
* Event to change the active players (and their stages) in the current turn.
*/
function SetActivePlayersEvent(state, _playerID, arg) {
return { ...state, ctx: SetActivePlayers(state.ctx, arg) };
}
function SetActivePlayers(ctx, arg) {
let { _prevActivePlayers } = ctx;
let activePlayers = {};
let _nextActivePlayers = null;
let _activePlayersMoveLimit = {};
if (Array.isArray(arg)) {
// support a simple array of player IDs as active players
let value = {};
arg.forEach(v => (value[v] = Stage.NULL));
activePlayers = value;
}
else {
// process active players argument object
if (arg.next) {
_nextActivePlayers = arg.next;
}
if (arg.revert) {
_prevActivePlayers = _prevActivePlayers.concat({
activePlayers: ctx.activePlayers,
_activePlayersMoveLimit: ctx._activePlayersMoveLimit,
_activePlayersNumMoves: ctx._activePlayersNumMoves,
});
}
else {
_prevActivePlayers = [];
}
if (arg.currentPlayer !== undefined) {
ApplyActivePlayerArgument(activePlayers, _activePlayersMoveLimit, ctx.currentPlayer, arg.currentPlayer);
}
if (arg.others !== undefined) {
for (let i = 0; i < ctx.playOrder.length; i++) {
const id = ctx.playOrder[i];
if (id !== ctx.currentPlayer) {
ApplyActivePlayerArgument(activePlayers, _activePlayersMoveLimit, id, arg.others);
}
}
}
if (arg.all !== undefined) {
for (let i = 0; i < ctx.playOrder.length; i++) {
const id = ctx.playOrder[i];
ApplyActivePlayerArgument(activePlayers, _activePlayersMoveLimit, id, arg.all);
}
}
if (arg.value) {
for (const id in arg.value) {
ApplyActivePlayerArgument(activePlayers, _activePlayersMoveLimit, id, arg.value[id]);
}
}
if (arg.moveLimit) {
for (const id in activePlayers) {
if (_activePlayersMoveLimit[id] === undefined) {
_activePlayersMoveLimit[id] = arg.moveLimit;
}
}
}
}
if (Object.keys(activePlayers).length == 0) {
activePlayers = null;
}
if (Object.keys(_activePlayersMoveLimit).length == 0) {
_activePlayersMoveLimit = null;
}
let _activePlayersNumMoves = {};
for (const id in activePlayers) {
_activePlayersNumMoves[id] = 0;
}
return {
...ctx,
activePlayers,
_activePlayersMoveLimit,
_activePlayersNumMoves,
_prevActivePlayers,
_nextActivePlayers,
};
}
/**
* Update activePlayers, setting it to previous, next or null values
* when it becomes empty.
* @param ctx
*/
function UpdateActivePlayersOnceEmpty(ctx) {
let { activePlayers, _activePlayersMoveLimit, _activePlayersNumMoves, _prevActivePlayers, } = ctx;
if (activePlayers && Object.keys(activePlayers).length == 0) {
if (ctx._nextActivePlayers) {
ctx = SetActivePlayers(ctx, ctx._nextActivePlayers);
({
activePlayers,
_activePlayersMoveLimit,
_activePlayersNumMoves,
_prevActivePlayers,
} = ctx);
}
else if (_prevActivePlayers.length > 0) {
const lastIndex = _prevActivePlayers.length - 1;
({
activePlayers,
_activePlayersMoveLimit,
_activePlayersNumMoves,
} = _prevActivePlayers[lastIndex]);
_prevActivePlayers = _prevActivePlayers.slice(0, lastIndex);
}
else {
activePlayers = null;
_activePlayersMoveLimit = null;
}
}
return {
...ctx,
activePlayers,
_activePlayersMoveLimit,
_activePlayersNumMoves,
_prevActivePlayers,
};
}
/**
* Apply an active player argument to the given player ID
* @param {Object} activePlayers
* @param {Object} _activePlayersMoveLimit
* @param {String} playerID The player to apply the parameter to
* @param {(String|Object)} arg An active player argument
*/
function ApplyActivePlayerArgument(activePlayers, _activePlayersMoveLimit, pla