UNPKG

boardgame.io

Version:
1,717 lines (1,615 loc) 278 kB
'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