From 18e966e01132d2337b62d8881fd4b84b940c1669 Mon Sep 17 00:00:00 2001 From: Nicolo Davis Date: Wed, 14 Mar 2018 02:42:08 +0800 Subject: [PATCH 1/7] remove global state --- examples/modules/random/game.js | 8 +- src/core/flow.js | 3 +- src/core/random.js | 262 ++++++++++++++++---------------- src/core/random.test.js | 55 ++++--- src/core/reducer.js | 33 ++-- src/core/reducer.test.js | 7 +- 6 files changed, 195 insertions(+), 173 deletions(-) diff --git a/examples/modules/random/game.js b/examples/modules/random/game.js index 3154d8fcd..51fe74238 100644 --- a/examples/modules/random/game.js +++ b/examples/modules/random/game.js @@ -6,7 +6,7 @@ * https://opensource.org/licenses/MIT. */ -import { Game, Random } from 'boardgame.io/core'; +import { Game } from 'boardgame.io/core'; const RandomExample = Game({ name: 'shuffle', @@ -16,9 +16,9 @@ const RandomExample = Game({ }), moves: { - shuffle: G => ({ ...G, deck: Random.Shuffle(G.deck) }), - rollDie: (G, ctx, value) => ({ ...G, dice: Random.Die(value) }), - rollD6: G => ({ ...G, dice: Random.D6() }), + shuffle: (G, ctx) => ({ ...G, deck: ctx.random.Shuffle(G.deck) }), + rollDie: (G, ctx, value) => ({ ...G, dice: ctx.random.Die(value) }), + rollD6: (G, ctx) => ({ ...G, dice: ctx.random.D6() }), }, }); diff --git a/src/core/flow.js b/src/core/flow.js index bd3799bcc..bb660d8f7 100644 --- a/src/core/flow.js +++ b/src/core/flow.js @@ -278,7 +278,8 @@ export function FlowWithPhases({ const startTurn = function(state, config) { const ctx = { ...state.ctx }; const G = config.onTurnBegin(state.G, ctx); - const _undo = [{ G, ctx }]; + const { random, ...ctxWithoutAPI } = ctx; // eslint-disable-line no-unused-vars + const _undo = [{ G, ctx: ctxWithoutAPI }]; return { ...state, G, ctx, _undo, _redo: [] }; }; diff --git a/src/core/random.js b/src/core/random.js index 3afd6fca4..ceb3f20ea 100644 --- a/src/core/random.js +++ b/src/core/random.js @@ -8,75 +8,155 @@ import { alea } from './random.alea'; -class _PRNGState { - constructor() { - this.R = undefined; - } - get() { - return this.R; - } - set(R) { - this.R = R; - } -} - -// Singleton that contains the PRNG state. -export const PRNGState = new _PRNGState(); - /** - * Return a random number. + * Random * - * Rehydrates the PRNG from PRNGState if possible. + * 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. */ -export function random() { - const R = PRNGState.get(); - - let fn; - if (R === undefined || R.prngstate === undefined) { +export class Random { + constructor(ctx) { // 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. - const seed = (R && R.seed) || '0'; - - // No call to a random function has been made. - fn = new alea(seed, { state: true }); - } else { - fn = new alea('', { state: R.prngstate }); + this.state = ctx._random || { seed: '0' }; } - const number = fn(); + /** + * Attaches the PRNG state and strips the API. + */ + ctxWithoutAPI(ctx) { + const { random, ...rest } = ctx; // eslint-disable-line no-unused-vars + return { ...rest, _random: this.state }; + } - PRNGState.set({ - ...R, - prngstate: fn.state(), - }); + /** + * Attaches the PRNG state and API. + */ + ctxWithAPI(ctx) { + return { ...ctx, _random: this.state, random: this._api() }; + } - return number; -} + /** + * Generate a random number. + */ + _random() { + const R = this.state; -const SpotValue = { - D4: 4, - D6: 6, - D8: 8, - D10: 10, - D12: 12, - D20: 20, -}; - -// Generate functions for predefined dice values D4 - D20. -const predefined = {}; -for (const key in SpotValue) { - const spotvalue = SpotValue[key]; - predefined[key] = diceCount => { - if (diceCount === undefined) { - return Math.floor(random() * spotvalue) + 1; + let fn; + if (R.prngstate === undefined) { + // No call to a random function has been made. + fn = new alea(R.seed, { state: true }); } else { - return [...Array(diceCount).keys()].map( - () => Math.floor(random() * spotvalue) + 1 - ); + fn = new alea('', { state: R.prngstate }); + } + + const number = fn(); + + this.state = { + ...R, + prngstate: fn.state(), + }; + + return number; + } + + _api() { + const random = this._random.bind(this); + + const SpotValue = { + D4: 4, + D6: 6, + D8: 8, + D10: 10, + D12: 12, + D20: 20, + }; + + // Generate functions for predefined dice values D4 - D20. + const predefined = {}; + for (const key in SpotValue) { + const spotvalue = SpotValue[key]; + predefined[key] = diceCount => { + if (diceCount === undefined) { + return Math.floor(random() * spotvalue) + 1; + } else { + return [...Array(diceCount).keys()].map( + () => Math.floor(random() * spotvalue) + 1 + ); + } + }; } - }; + + return { + /** + * Similar to Die below, but with fixed spot values. + * Supports passing a diceCount + * if not defined, defaults to 1 and returns the value directly. + * if defined, returns an array containing the random dice values. + * + * D4: (diceCount) => value + * D6: (diceCount) => value + * D8: (diceCount) => value + * D10: (diceCount) => value + * D12: (diceCount) => value + * D20: (diceCount) => value + */ + ...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: (spotvalue, diceCount) => { + if (spotvalue === undefined) { + spotvalue = 6; + } + + if (diceCount === undefined) { + return Math.floor(random() * spotvalue) + 1; + } else { + return [...Array(diceCount).keys()].map( + () => Math.floor(random() * spotvalue) + 1 + ); + } + }, + + /** + * Generate a random number between 0 and 1. + */ + Number: () => { + return random(); + }, + + /** + * Shuffle an array. + * + * @param {Array} deck - The array to shuffle. Does not mutate + * the input, but returns the shuffled array. + */ + Shuffle: deck => { + let clone = deck.slice(0); + let srcIndex = deck.length; + let dstIndex = 0; + let shuffled = new Array(srcIndex); + + while (srcIndex) { + let randIndex = (srcIndex * random()) | 0; + shuffled[dstIndex++] = clone[randIndex]; + clone[randIndex] = clone[--srcIndex]; + } + + return shuffled; + }, + }; + } } /** @@ -88,75 +168,3 @@ for (const key in SpotValue) { export function GenSeed() { return (+new Date()).toString(36).slice(-10); } - -/** - * Random - * - * Public API. - */ -export const Random = { - /** - * Similar to Die below, but with fixed spot values. - * Supports passing a diceCount - * if not defined, defaults to 1 and returns the value directly. - * if defined, returns an array containing the random dice values. - * - * D4: (diceCount) => value - * D6: (diceCount) => value - * D8: (diceCount) => value - * D10: (diceCount) => value - * D12: (diceCount) => value - * D20: (diceCount) => value - */ - ...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: (spotvalue, diceCount) => { - if (spotvalue === undefined) { - spotvalue = 6; - } - - if (diceCount === undefined) { - return Math.floor(random() * spotvalue) + 1; - } else { - return [...Array(diceCount).keys()].map( - () => Math.floor(random() * spotvalue) + 1 - ); - } - }, - - /** - * Generate a random number between 0 and 1. - */ - Number: () => { - return random(); - }, - - /** - * Shuffle an array. - * - * @param {Array} deck - The array to shuffle. Does not mutate - * the input, but returns the shuffled array. - */ - Shuffle: deck => { - let clone = deck.slice(0); - let srcIndex = deck.length; - let dstIndex = 0; - let shuffled = new Array(srcIndex); - - while (srcIndex) { - let randIndex = (srcIndex * random()) | 0; - shuffled[dstIndex++] = clone[randIndex]; - clone[randIndex] = clone[--srcIndex]; - } - - return shuffled; - }, -}; diff --git a/src/core/random.test.js b/src/core/random.test.js index b1f642d06..030f298a0 100644 --- a/src/core/random.test.js +++ b/src/core/random.test.js @@ -6,24 +6,34 @@ * https://opensource.org/licenses/MIT. */ -import { random, Random, PRNGState } from './random'; +import { Random } from './random'; import Game from './game'; import { makeMove } from './action-creators'; import { createGameReducer } from './reducer'; +function Init(seed) { + const ctx = { _random: { seed } }; + return new Random(ctx); +} + +test('constructor', () => { + const r = new Random({}); + expect(r.state).toEqual({ seed: '0' }); +}); + test('random', () => { - PRNGState.set({ seed: 'hi there' }); + const r = Init('hi there'); // make sure that subsequent calls are different. - expect(random()).toBe(0.573445922927931); - expect(random()).toBe(0.4695413049776107); - expect(random()).toBe(0.5943194630090147); + expect(r._random()).toBe(0.573445922927931); + expect(r._random()).toBe(0.4695413049776107); + expect(r._random()).toBe(0.5943194630090147); }); test('predefined dice values', () => { - PRNGState.set({ seed: 0 }); + const r = Init(0); const rfns = [4, 6, 8, 10, 12, 20].map(v => { - return { fn: Random[`D${v}`], highest: v }; + return { fn: r._api()[`D${v}`], highest: v }; }); rfns.forEach(pair => { @@ -31,7 +41,7 @@ test('predefined dice values', () => { expect(result).toBeDefined(); expect(result).toBeGreaterThanOrEqual(1); expect(result).toBeLessThanOrEqual(pair.highest); - expect(PRNGState.get().prngstate).toBeDefined(); + expect(r.state.prngstate).toBeDefined(); const multiple = pair.fn(5); expect(multiple).toBeDefined(); @@ -44,59 +54,60 @@ test('predefined dice values', () => { }); test('Random.Die', () => { - PRNGState.set({ seed: 0 }); + const r = Init(0); + const _api = r._api(); { - const result = Random.Die(123); + const result = _api.Die(123); expect(result).toBeDefined(); expect(result).toBe(74); - expect(PRNGState.get().prngstate).toBeDefined(); + expect(r.state.prngstate).toBeDefined(); } { - const result = Random.Die(); + const result = _api.Die(); expect(result).toBeDefined(); expect(result).toBeLessThanOrEqual(6); - expect(PRNGState.get().prngstate).toBeDefined(); + expect(r.state.prngstate).toBeDefined(); } { - const multiple = Random.Die(6, 3); + const multiple = _api.Die(6, 3); expect(multiple).toBeDefined(); expect(multiple.length).toBe(3); multiple.forEach(m => { expect(m).toBeGreaterThanOrEqual(1); expect(m).toBeLessThanOrEqual(6); }); - expect(PRNGState.get().prngstate).toBeDefined(); + expect(r.state.prngstate).toBeDefined(); } }); test('Random.Number', () => { - PRNGState.set({ seed: 0 }); - const result = Random.Number(); + const r = Init(0); + const result = r._api().Number(); expect(result).toBeDefined(); expect(result).toBeGreaterThanOrEqual(0); expect(result).toBeLessThanOrEqual(1); - expect(PRNGState.get().prngstate).toBeDefined(); + expect(r.state.prngstate).toBeDefined(); }); test('Random.Shuffle', () => { - PRNGState.set({ seed: 0 }); + const r = Init(0); const initialTiles = ['A', 'B', 'C', 'D', 'E']; const tiles = [...initialTiles]; - const result = Random.Shuffle(tiles); + const result = r._api().Shuffle(tiles); expect(result.length).toEqual(initialTiles.length); expect(result).toEqual(expect.arrayContaining(initialTiles)); expect(result.sort()).toEqual(initialTiles); - expect(PRNGState.get().prngstate).toBeDefined(); + expect(r.state.prngstate).toBeDefined(); }); test('Random API is not executed optimisitically', () => { const game = Game({ seed: 0, moves: { - rollDie: G => ({ ...G, die: Random.D6() }), + rollDie: (G, ctx) => ({ ...G, die: ctx.random.D6() }), }, }); diff --git a/src/core/reducer.js b/src/core/reducer.js index 54b35e00b..400e74319 100644 --- a/src/core/reducer.js +++ b/src/core/reducer.js @@ -7,7 +7,7 @@ */ import * as Actions from './action-types'; -import { PRNGState } from './random'; +import { Random } from './random'; /** * createGameReducer @@ -22,16 +22,18 @@ export function createGameReducer({ game, numPlayers, multiplayer }) { numPlayers = 2; } - // Need to init PRNGState here, otherwise calls to - // Random inside setup() are using undefined. - PRNGState.set({ seed: game.seed }); + let ctx = game.flow.ctx(numPlayers); + ctx._random = { seed: game.seed }; + + const random = new Random(ctx); + const ctxWithAPI = random.ctxWithAPI(ctx); const initial = { // User managed state. - G: game.setup(numPlayers), + G: game.setup(ctxWithAPI), // Framework managed state. - ctx: game.flow.ctx(numPlayers), + ctx: ctx, // A list of actions performed so far. Used by the // GameLog to display a journal of moves. @@ -54,7 +56,7 @@ export function createGameReducer({ game, numPlayers, multiplayer }) { }; // Initialize PRNG seed. - initial.ctx._random = PRNGState.get(); + initial.ctx = random.ctxWithoutAPI(initial.ctx); const state = game.flow.init({ G: initial.G, ctx: initial.ctx }); @@ -84,11 +86,12 @@ export function createGameReducer({ game, numPlayers, multiplayer }) { } // Init PRNG state. - PRNGState.set(state.ctx._random); + const random = new Random(state.ctx); + state = { ...state, ctx: random.ctxWithAPI(state.ctx) }; // Update state. const newState = game.flow.processGameEvent(state, action.payload); // Update PRNG state. - const ctx = { ...newState.ctx, _random: PRNGState.get() }; + const ctx = random.ctxWithoutAPI(newState.ctx); return { ...newState, ctx, _stateID: state._stateID + 1 }; } @@ -100,12 +103,13 @@ export function createGameReducer({ game, numPlayers, multiplayer }) { } // Init PRNG state. - PRNGState.set(state.ctx._random); + const random = new Random(state.ctx); + const ctxWithAPI = random.ctxWithAPI(state.ctx); // Process the move. - let G = game.processMove(state.G, action.payload, state.ctx); + let G = game.processMove(state.G, action.payload, ctxWithAPI); // Update PRNG state. - const ctx = { ...state.ctx, _random: PRNGState.get() }; + const ctx = random.ctxWithoutAPI(state.ctx); // Undo changes to G if the move should not run on the client. if ( @@ -127,10 +131,9 @@ export function createGameReducer({ game, numPlayers, multiplayer }) { } // Allow the flow reducer to process any triggers that happen after moves. + state = { ...state, ctx: random.ctxWithAPI(state.ctx) }; state = game.flow.processMove(state, action); - - // Update PRNG state. - state = { ...state, ctx: { ...state.ctx, _random: PRNGState.get() } }; + state = { ...state, ctx: random.ctxWithoutAPI(state.ctx) }; return state; } diff --git a/src/core/reducer.test.js b/src/core/reducer.test.js index 99dedad99..fc9bfcd96 100644 --- a/src/core/reducer.test.js +++ b/src/core/reducer.test.js @@ -9,7 +9,6 @@ import Game from './game'; import { createGameReducer } from './reducer'; import { makeMove, gameEvent, restore } from './action-creators'; -import { Random } from './random'; const game = Game({ moves: { @@ -156,17 +155,17 @@ test('log', () => { test('using Random inside setup()', () => { const game1 = Game({ seed: 'seed1', - setup: () => ({ n: Random.D6() }), + setup: ctx => ({ n: ctx.random.D6() }), }); const game2 = Game({ seed: 'seed2', - setup: () => ({ n: Random.D6() }), + setup: ctx => ({ n: ctx.random.D6() }), }); const game3 = Game({ seed: 'seed2', - setup: () => ({ n: Random.D6() }), + setup: ctx => ({ n: ctx.random.D6() }), }); const reducer1 = createGameReducer({ game: game1 }); From e72153564dda9722ea67b4c7ea8b78c313a9f7c5 Mon Sep 17 00:00:00 2001 From: Nicolo Davis Date: Wed, 14 Mar 2018 02:44:26 +0800 Subject: [PATCH 2/7] update docs --- docs/api/Random.md | 24 ++++++++---------------- docs/random.md | 4 +--- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/docs/api/Random.md b/docs/api/Random.md index edaa46381..53677f763 100644 --- a/docs/api/Random.md +++ b/docs/api/Random.md @@ -2,7 +2,7 @@ API for code that requires randomness. See the guide [here](random.md). -## 1. Random.Die +## 1. Die ### Arguments @@ -16,32 +16,28 @@ The die roll value (or an array of values if `diceCount` is greater than `1`). #### Usage ```js -import { Random } from `boardgame.io/core'; - const game = Game({ moves: { move(G, ctx) { - const die = Random.Die(6); // die = 1-6 - const dice = Random.Die(6, 3); // dice = [1-6, 1-6, 1-6] + const die = ctx.random.Die(6); // die = 1-6 + const dice = ctx.random.Die(6, 3); // dice = [1-6, 1-6, 1-6] ... }, } }); ``` -## 2. Random.Number +## 2. Number Returns a random number between `0` and `1`. #### Usage ```js -import { Random } from `boardgame.io/core'; - const game = Game({ moves: { move(G, ctx) { - const n = Random.Number(); + const n = ctx.random.Number(); ... }, } @@ -61,15 +57,13 @@ The shuffled array. #### Usage ```js -import { Random } from `boardgame.io/core'; - const game = Game({ moves: { move(G, ctx) { - const deck = Random.Shuffle(G.deck); + const deck = ctx.random.Shuffle(G.deck); return { ...G, deck }; }, - } + }, }); ``` @@ -85,12 +79,10 @@ const game = Game({ ### Usage ```js -import { Random } from `boardgame.io/core'; - const game = Game({ moves: { move(G, ctx) { - const die = Random.D6(); + const die = ctx.random.D6(); ... }, } diff --git a/docs/random.md b/docs/random.md index da95a15b1..a42a78167 100644 --- a/docs/random.md +++ b/docs/random.md @@ -22,12 +22,10 @@ This poses interesting challenges regarding the implementation. ### Using Randomness in Games ```js -import { Random } from 'boardgame.io/core'; - Game({ moves: { rollDie(G, ctx) { - return { ...G, dice: Random.D6() }; + return { ...G, dice: ctx.random.D6() }; }, }, }); From a62236fed433fff4c5f370949042f9ef3c502569 Mon Sep 17 00:00:00 2001 From: Nicolo Davis Date: Wed, 14 Mar 2018 02:49:51 +0800 Subject: [PATCH 3/7] remove from package --- packages/core.js | 3 +-- packages/main.js | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core.js b/packages/core.js index 5fd5800f2..499128cc2 100644 --- a/packages/core.js +++ b/packages/core.js @@ -10,6 +10,5 @@ import Game from '../src/core/game.js'; import { Flow, FlowWithPhases } from '../src/core/flow.js'; import { TurnOrder, Pass } from '../src/core/turn-order.js'; import { PlayerView } from '../src/core/player-view.js'; -import { Random } from '../src/core/random'; -export { Game, Flow, FlowWithPhases, TurnOrder, Pass, PlayerView, Random }; +export { Game, Flow, FlowWithPhases, TurnOrder, Pass, PlayerView }; diff --git a/packages/main.js b/packages/main.js index 0864fac4f..7bb001203 100644 --- a/packages/main.js +++ b/packages/main.js @@ -15,7 +15,6 @@ import Token from '../src/ui/token.js'; import { Card } from '../src/ui/card.js'; import { Grid } from '../src/ui/grid.js'; import { HexGrid } from '../src/ui/hex.js'; -import { Random } from '../src/core/random'; export default { Client, @@ -29,5 +28,4 @@ export default { Token, Grid, HexGrid, - Random, }; From e26375e1fdb3b13d4b1d7900f352a52b576c0837 Mon Sep 17 00:00:00 2001 From: Nicolo Davis Date: Wed, 14 Mar 2018 02:54:07 +0800 Subject: [PATCH 4/7] update docs --- docs/api/Game.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/Game.md b/docs/api/Game.md index eab186cf1..c0e0bc785 100644 --- a/docs/api/Game.md +++ b/docs/api/Game.md @@ -52,7 +52,7 @@ game state and the moves. The moves are converted to a import { Game } from `boardgame.io/core'; const game = Game({ - setup: (numPlayers) => { + setup: (ctx) => { const G = {...}; return G; }, @@ -75,7 +75,7 @@ const game = Game({ import { Game } from 'boardgame.io/core'; const game = Game({ - setup: (numPlayers) => { + setup: (ctx) => { ... }, @@ -99,7 +99,7 @@ const game = Game({ import { Game } from 'boardgame.io/core'; const game = Game({ - setup: (numPlayers) => { + setup: (ctx) => { ... }, From f833e64deb831147ca4e34c6d625986e8c7e60dd Mon Sep 17 00:00:00 2001 From: Nicolo Davis Date: Wed, 14 Mar 2018 02:55:38 +0800 Subject: [PATCH 5/7] update docs --- docs/tutorial.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index a2dc0d827..ce8d01753 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -21,9 +21,9 @@ $ npm install --save boardgame.io We create the game by providing the initial value of the game state `G` (through the `setup` function), and the moves of the game. The `setup` function also accepts a -`numPlayers` parameter if you need to customize the initial -state based on the number of players, but we don't need that -for Tic-Tac-Toe. +`ctx` parameter if you need to customize the initial +state based on some field in `ctx` (the number of players, for example), +but we don't need that for Tic-Tac-Toe. In Tic-Tac-Toe, we have just one type of move that we shall name `clickCell`. The move function accepts From 28d2bba24cb70aa16f0c959ed63dba8b1bb467a6 Mon Sep 17 00:00:00 2001 From: Nicolo Davis Date: Thu, 15 Mar 2018 14:00:53 +0800 Subject: [PATCH 6/7] review comments --- src/core/flow.js | 7 ++++--- src/core/game.js | 4 ++-- src/core/random.js | 32 ++++++++++++++++++++------------ src/core/random.test.js | 20 ++++++++++++++++++++ src/core/reducer.js | 27 ++++++++++++++------------- 5 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/core/flow.js b/src/core/flow.js index bb660d8f7..0bc0bba97 100644 --- a/src/core/flow.js +++ b/src/core/flow.js @@ -7,6 +7,7 @@ */ import { TurnOrder } from './turn-order'; +import { Random } from './random'; /** * Helper to create a reducer that manages ctx (with the @@ -276,10 +277,10 @@ export function FlowWithPhases({ }; const startTurn = function(state, config) { - const ctx = { ...state.ctx }; + let ctx = { ...state.ctx }; const G = config.onTurnBegin(state.G, ctx); - const { random, ...ctxWithoutAPI } = ctx; // eslint-disable-line no-unused-vars - const _undo = [{ G, ctx: ctxWithoutAPI }]; + ctx = Random.detach(ctx); + const _undo = [{ G, ctx }]; return { ...state, G, ctx, _undo, _redo: [] }; }; diff --git a/src/core/game.js b/src/core/game.js index 9f2f9e32a..7bed01e60 100644 --- a/src/core/game.js +++ b/src/core/game.js @@ -7,7 +7,7 @@ */ import { FlowWithPhases } from './flow'; -import { GenSeed } from './random'; +import { Random } from './random'; /** * Game @@ -76,7 +76,7 @@ function Game({ name, setup, moves, playerView, flow, seed }) { if (setup === undefined) setup = () => ({}); if (moves === undefined) moves = {}; if (playerView === undefined) playerView = G => G; - if (seed === undefined) seed = GenSeed(); + if (seed === undefined) seed = Random.seed(); if (!flow || flow.processGameEvent === undefined) { flow = FlowWithPhases(flow || {}); diff --git a/src/core/random.js b/src/core/random.js index ceb3f20ea..ee2b8c9ab 100644 --- a/src/core/random.js +++ b/src/core/random.js @@ -25,18 +25,18 @@ export class Random { } /** - * Attaches the PRNG state and strips the API. + * Updates ctx with the PRNG state. + * Also removes the Random API. */ - ctxWithoutAPI(ctx) { - const { random, ...rest } = ctx; // eslint-disable-line no-unused-vars - return { ...rest, _random: this.state }; + update(ctx) { + return Random.detach({ ...ctx, _random: this.state }); } /** - * Attaches the PRNG state and API. + * Attaches the Random API to ctx. */ - ctxWithAPI(ctx) { - return { ...ctx, _random: this.state, random: this._api() }; + attach(ctx) { + return { ...ctx, random: this._api() }; } /** @@ -160,11 +160,19 @@ export class Random { } /** - * GenSeed + * Removes the attached Random api from ctx. * - * Generates a new seed that's used in case none is - * passed in. + * @param {object} ctx - The ctx object with the Random API attached. + * @returns {object} A plain ctx object without the Random API. */ -export function GenSeed() { +Random.detach = function(ctx) { + const { random, ...rest } = ctx; // eslint-disable-line no-unused-vars + return rest; +}; + +/** + * Generates a new seed from the current date / time. + */ +Random.seed = function() { return (+new Date()).toString(36).slice(-10); -} +}; diff --git a/src/core/random.test.js b/src/core/random.test.js index 030f298a0..2a6a23318 100644 --- a/src/core/random.test.js +++ b/src/core/random.test.js @@ -21,6 +21,26 @@ test('constructor', () => { expect(r.state).toEqual({ seed: '0' }); }); +test('attach / detach / update', () => { + const r = new Random({}); + const ctx = r.attach({}); + + expect(ctx._random).not.toBeDefined(); + expect(ctx.random).toBeDefined(); + + { + const t = Random.detach(ctx); + expect(t._random).not.toBeDefined(); + expect(t.random).not.toBeDefined(); + } + + { + const t = r.update(ctx); + expect(t._random).toBeDefined(); + expect(t.random).not.toBeDefined(); + } +}); + test('random', () => { const r = Init('hi there'); // make sure that subsequent calls are different. diff --git a/src/core/reducer.js b/src/core/reducer.js index 400e74319..dfeab30b9 100644 --- a/src/core/reducer.js +++ b/src/core/reducer.js @@ -26,7 +26,7 @@ export function createGameReducer({ game, numPlayers, multiplayer }) { ctx._random = { seed: game.seed }; const random = new Random(ctx); - const ctxWithAPI = random.ctxWithAPI(ctx); + const ctxWithAPI = random.attach(ctx); const initial = { // User managed state. @@ -55,8 +55,8 @@ export function createGameReducer({ game, numPlayers, multiplayer }) { _initial: {}, }; - // Initialize PRNG seed. - initial.ctx = random.ctxWithoutAPI(initial.ctx); + // Initialize PRNG state. + initial.ctx = random.update(initial.ctx); const state = game.flow.init({ G: initial.G, ctx: initial.ctx }); @@ -85,13 +85,14 @@ export function createGameReducer({ game, numPlayers, multiplayer }) { return state; } - // Init PRNG state. + // Initialize PRNG from ctx. const random = new Random(state.ctx); - state = { ...state, ctx: random.ctxWithAPI(state.ctx) }; + state = { ...state, ctx: random.attach(state.ctx) }; + // Update state. const newState = game.flow.processGameEvent(state, action.payload); - // Update PRNG state. - const ctx = random.ctxWithoutAPI(newState.ctx); + // Update ctx with PRNG state. + const ctx = random.update(newState.ctx); return { ...newState, ctx, _stateID: state._stateID + 1 }; } @@ -102,14 +103,14 @@ export function createGameReducer({ game, numPlayers, multiplayer }) { return state; } - // Init PRNG state. + // Initialize PRNG from ctx. const random = new Random(state.ctx); - const ctxWithAPI = random.ctxWithAPI(state.ctx); + const ctxWithAPI = random.attach(state.ctx); // Process the move. let G = game.processMove(state.G, action.payload, ctxWithAPI); - // Update PRNG state. - const ctx = random.ctxWithoutAPI(state.ctx); + // Update ctx with PRNG state. + const ctx = random.update(state.ctx); // Undo changes to G if the move should not run on the client. if ( @@ -131,9 +132,9 @@ export function createGameReducer({ game, numPlayers, multiplayer }) { } // Allow the flow reducer to process any triggers that happen after moves. - state = { ...state, ctx: random.ctxWithAPI(state.ctx) }; + state = { ...state, ctx: random.attach(state.ctx) }; state = game.flow.processMove(state, action); - state = { ...state, ctx: random.ctxWithoutAPI(state.ctx) }; + state = { ...state, ctx: random.update(state.ctx) }; return state; } From 143cfdcd784903acf3115c702869c0a4e0b41048 Mon Sep 17 00:00:00 2001 From: Nicolo Davis Date: Thu, 15 Mar 2018 14:03:07 +0800 Subject: [PATCH 7/7] update docs/react/boardgameio.min.js --- docs/react/boardgameio.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/react/boardgameio.min.js b/docs/react/boardgameio.min.js index 23db03be6..e199f041e 100644 --- a/docs/react/boardgameio.min.js +++ b/docs/react/boardgameio.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):e.BoardgameIO=t(e.React)}(this,function(e){"use strict";function t(e,t){if("undefined"==typeof document)return t;e=e||"";var n=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");return r.type="text/css",n.appendChild(r),r.styleSheet?r.styleSheet.cssText=e:r.appendChild(document.createTextNode(e)),t}function n(e){var t=ee.call(e,ne),n=e[ne];try{e[ne]=void 0;var r=!0}catch(e){}var o=te.call(e);return r&&(t?e[ne]=n:delete e[ne]),o}function r(e){return re.call(e)}function o(e){return null==e?void 0===e?ie:oe:se&&se in Object(e)?n(e):r(e)}function i(e){return null!=e&&"object"==typeof e}function s(e){if(!i(e)||o(e)!=ce)return!1;var t=ae(e);if(null===t)return!0;var n=he.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&pe.call(n)==fe}function a(e,t){return t={exports:{}},e(t,t.exports),t.exports}function c(e,t,n){function r(){f===h&&(f=h.slice())}function o(){return p}function i(e){if("function"!=typeof e)throw Error("Expected listener to be a function.");var t=!0;return r(),f.push(e),function(){if(t){t=!1,r();var n=f.indexOf(e);f.splice(n,1)}}}function a(e){if(!s(e))throw Error("Actions must be plain objects. Use custom middleware for async actions.");if(void 0===e.type)throw Error('Actions may not have an undefined "type" property. Have you misspelled a constant?');if(d)throw Error("Reducers may not dispatch actions.");try{d=!0,p=l(p,e)}finally{d=!1}for(var t=h=f,n=0;t.length>n;n++)(0,t[n])();return e}var u;if("function"==typeof t&&void 0===n&&(n=t,t=void 0),void 0!==n){if("function"!=typeof n)throw Error("Expected the enhancer to be a function.");return n(c)(e,t)}if("function"!=typeof e)throw Error("Expected the reducer to be a function.");var l=e,p=t,h=[],f=h,d=!1;return a({type:ge.INIT}),u={dispatch:a,subscribe:i,getState:o,replaceReducer:function(e){if("function"!=typeof e)throw Error("Expected the nextReducer to be a function.");l=e,a({type:ge.INIT})}},u[me]=function(){var e,t=i;return e={subscribe:function(e){function n(){e.next&&e.next(o())}if("object"!=typeof e)throw new TypeError("Expected the observer to be an object.");return n(),{unsubscribe:t(n)}}},e[me]=function(){return this},e},u}function u(){for(var e=arguments.length,t=Array(e),n=0;e>n;n++)t[n]=arguments[n];return 0===t.length?function(e){return e}:1===t.length?t[0]:t.reduce(function(e,t){return function(){return e(t.apply(void 0,arguments))}})}function l(){for(var e=arguments.length,t=Array(e),n=0;e>n;n++)t[n]=arguments[n];return function(e){return function(n,r,o){var i=e(n,r,o),s=i.dispatch,a=[],c={getState:i.getState,dispatch:function(e){return s(e)}};return a=t.map(function(e){return e(c)}),s=u.apply(void 0,a)(i.dispatch),ve({},i,{dispatch:s})}}}function p(e){if(100>=(e+="").length){var t=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(e);if(t){var n=parseFloat(t[1]);switch((t[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return n*Oe;case"days":case"day":case"d":return n*Te;case"hours":case"hour":case"hrs":case"hr":case"h":return n*Ie;case"minutes":case"minute":case"mins":case"min":case"m":return n*Se;case"seconds":case"second":case"secs":case"sec":case"s":return n*De;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n;default:return}}}}function h(e){return Te>e?Ie>e?Se>e?De>e?e+"ms":Math.round(e/De)+"s":Math.round(e/Se)+"m":Math.round(e/Ie)+"h":Math.round(e/Te)+"d"}function f(e){return d(e,Te,"day")||d(e,Ie,"hour")||d(e,Se,"minute")||d(e,De,"second")||e+" ms"}function d(e,t,n){if(e>=t)return 1.5*t>e?Math.floor(e/t)+" "+n:Math.ceil(e/t)+" "+n+"s"}function y(e){if(!e||"object"!=typeof e)return!1;if(Ge(e)){for(var t=0,n=e.length;n>t;t++)if(y(e[t]))return!0;return!1}if("function"==typeof de.Buffer&&de.Buffer.isBuffer&&de.Buffer.isBuffer(e)||"function"==typeof de.ArrayBuffer&&e instanceof ArrayBuffer||Fe&&e instanceof Blob||Xe&&e instanceof File)return!0;if(e.toJSON&&"function"==typeof e.toJSON&&1===arguments.length)return y(e.toJSON(),!0);for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)&&y(e[r]))return!0;return!1}function m(e,t){if(!e)return e;if(Ve(e)){var n={_placeholder:!0,num:t.length};return t.push(e),n}if(We(e)){for(var r=Array(e.length),o=0;e.length>o;o++)r[o]=m(e[o],t);return r}if("object"==typeof e&&!(e instanceof Date)){r={};for(var i in e)r[i]=m(e[i],t);return r}return e}function g(e,t){if(!e)return e;if(e&&e._placeholder)return t[e.num];if(We(e))for(var n=0;e.length>n;n++)e[n]=g(e[n],t);else if("object"==typeof e)for(var r in e)e[r]=g(e[r],t);return e}function v(){}function b(e){for(var t=0;e.length>t;t++){var n=e[t];if(n.buffer instanceof ArrayBuffer){var r=n.buffer;if(n.byteLength!==r.byteLength){var o=new Uint8Array(n.byteLength);o.set(new Uint8Array(r,n.byteOffset,n.byteLength)),r=o.buffer}e[t]=r}}}function k(e,t){t=t||{};var n=new ct;b(e);for(var r=0;e.length>r;r++)n.append(e[r]);return t.type?n.getBlob(t.type):n.getBlob()}function w(e,t){return b(e),new Blob(e,t||{})}function x(e){this.path=e.path,this.hostname=e.hostname,this.port=e.port,this.secure=e.secure,this.query=e.query,this.timestampParam=e.timestampParam,this.timestampRequests=e.timestampRequests,this.readyState="",this.agent=e.agent||!1,this.socket=e.socket,this.enablesXDR=e.enablesXDR,this.pfx=e.pfx,this.key=e.key,this.passphrase=e.passphrase,this.cert=e.cert,this.ca=e.ca,this.ciphers=e.ciphers,this.rejectUnauthorized=e.rejectUnauthorized,this.forceNode=e.forceNode,this.extraHeaders=e.extraHeaders,this.localAddress=e.localAddress}function E(e){var t="";do{t=vt[e%bt]+t,e=Math.floor(e/bt)}while(e>0);return t}function C(){var e=E(+new Date);return e!==yt?(wt=0,yt=e):e+"."+E(wt++)}function _(e){var t=e&&e.forceBase64;Dt&&!t||(this.supportsBinary=!1),dt.call(this,e)}function A(){}function P(e){if(Pt.call(this,e),this.requestTimeout=e.requestTimeout,this.extraHeaders=e.extraHeaders,de.location){var t="https:"===location.protocol,n=location.port;n||(n=t?443:80),this.xd=e.hostname!==de.location.hostname||n!==e.port,this.xs=e.secure!==t}}function D(e){this.method=e.method||"GET",this.uri=e.uri,this.xd=!!e.xd,this.xs=!!e.xs,this.async=!1!==e.async,this.data=void 0!==e.data?e.data:null,this.agent=e.agent,this.isBinary=e.isBinary,this.supportsBinary=e.supportsBinary,this.enablesXDR=e.enablesXDR,this.requestTimeout=e.requestTimeout,this.pfx=e.pfx,this.key=e.key,this.passphrase=e.passphrase,this.cert=e.cert,this.ca=e.ca,this.ciphers=e.ciphers,this.rejectUnauthorized=e.rejectUnauthorized,this.extraHeaders=e.extraHeaders,this.create()}function S(){for(var e in D.requests)D.requests.hasOwnProperty(e)&&D.requests[e].abort()}function I(){}function T(e){Pt.call(this,e),this.query=this.query||{},Ot||(de.___eio||(de.___eio=[]),Ot=de.___eio),this.index=Ot.length;var t=this;Ot.push(function(e){t.onData(e)}),this.query.j=this.index,de.document&&de.addEventListener&&de.addEventListener("beforeunload",function(){t.script&&(t.script.onerror=I)},!1)}function O(e){e&&e.forceBase64&&(this.supportsBinary=!1),this.perMessageDeflate=e.perMessageDeflate,this.usingBrowserWebSocket=zt&&!e.forceNode,this.protocols=e.protocols,this.usingBrowserWebSocket||(Gt=jt),dt.call(this,e)}function B(e,t){if(!(this instanceof B))return new B(e,t);t=t||{},e&&"object"==typeof e&&(t=e,e=null),e?(e=Pe(e),t.hostname=e.host,t.secure="https"===e.protocol||"wss"===e.protocol,t.port=e.port,e.query&&(t.query=e.query)):t.host&&(t.hostname=Pe(t.host).host),this.secure=null!=t.secure?t.secure:de.location&&"https:"===location.protocol,t.hostname&&!t.port&&(t.port=this.secure?"443":"80"),this.agent=t.agent||!1,this.hostname=t.hostname||(de.location?location.hostname:"localhost"),this.port=t.port||(de.location&&location.port?location.port:this.secure?443:80),this.query=t.query||{},"string"==typeof this.query&&(this.query=mt.decode(this.query)),this.upgrade=!1!==t.upgrade,this.path=(t.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!t.forceJSONP,this.jsonp=!1!==t.jsonp,this.forceBase64=!!t.forceBase64,this.enablesXDR=!!t.enablesXDR,this.timestampParam=t.timestampParam||"t",this.timestampRequests=t.timestampRequests,this.transports=t.transports||["polling","websocket"],this.transportOptions=t.transportOptions||{},this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=t.policyPort||843,this.rememberUpgrade=t.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=t.onlyBinaryUpgrades,this.perMessageDeflate=!1!==t.perMessageDeflate&&(t.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=t.pfx||null,this.key=t.key||null,this.passphrase=t.passphrase||null,this.cert=t.cert||null,this.ca=t.ca||null,this.ciphers=t.ciphers||null,this.rejectUnauthorized=void 0===t.rejectUnauthorized||t.rejectUnauthorized,this.forceNode=!!t.forceNode;var n="object"==typeof de&&de;n.global===n&&(t.extraHeaders&&Object.keys(t.extraHeaders).length>0&&(this.extraHeaders=t.extraHeaders),t.localAddress&&(this.localAddress=t.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,this.open()}function N(e){var t={};for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}function R(e){e=e||{},this.ms=e.min||100,this.max=e.max||1e4,this.factor=e.factor||2,this.jitter=e.jitter>0&&1>=e.jitter?e.jitter:0,this.attempts=0}function j(e,t){if(!(this instanceof j))return new j(e,t);e&&"object"==typeof e&&(t=e,e=void 0),(t=t||{}).path=t.path||"/socket.io",this.nsps={},this.subs=[],this.opts=t,this.reconnection(!1!==t.reconnection),this.reconnectionAttempts(t.reconnectionAttempts||1/0),this.reconnectionDelay(t.reconnectionDelay||1e3),this.reconnectionDelayMax(t.reconnectionDelayMax||5e3),this.randomizationFactor(t.randomizationFactor||.5),this.backoff=new rn({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()}),this.timeout(null==t.timeout?2e4:t.timeout),this.readyState="closed",this.uri=e,this.connecting=[],this.lastPing=null,this.encoding=!1,this.packetBuffer=[];var n=t.parser||et;this.encoder=new n.Encoder,this.decoder=new n.Decoder,this.autoConnect=!1!==t.autoConnect,this.autoConnect&&this.open()}function M(e){var t=this,n=L();t.next=function(){var e=2091639*t.s0+2.3283064365386963e-10*t.c;return t.s0=t.s1,t.s1=t.s2,t.s2=e-(t.c=0|e)},t.c=1,t.s0=n(" "),t.s1=n(" "),t.s2=n(" "),t.s0-=n(e),0>t.s0&&(t.s0+=1),t.s1-=n(e),0>t.s1&&(t.s1+=1),t.s2-=n(e),0>t.s2&&(t.s2+=1),n=null}function q(e,t){return t.c=e.c,t.s0=e.s0,t.s1=e.s1,t.s2=e.s2,t}function L(){var e=4022871197;return function(t){t=""+t;for(var n=0;t.length>n;n++){var r=.02519603282416938*(e+=t.charCodeAt(n));r-=e=r>>>0,e=(r*=e)>>>0,e+=4294967296*(r-=e)}return 2.3283064365386963e-10*(e>>>0)}}function U(e,t){var n=new M(e),r=t&&t.state,o=n.next;return o.quick=o,r&&("object"==(void 0===r?"undefined":un(r))&&q(r,n),o.state=function(){return q(n,{})}),o}function z(){var e=vn.get(),t=void 0,n=(t=void 0===e||void 0===e.prngstate?new U(e&&e.seed||"0",{state:!0}):new U("",{state:e.prngstate}))();return vn.set(hn({},e,{prngstate:t.state()})),n}function G(){return(+new Date).toString(36).slice(-10)}function H(e){var t=e.game,n=e.numPlayers,r=e.multiplayer;n||(n=2),vn.set({seed:t.seed});var o={G:t.setup(n),ctx:t.flow.ctx(n),log:[],_undo:[],_redo:[],_stateID:0,_initial:{}};o.ctx._random=vn.get();var i=t.flow.init({G:o.G,ctx:o.ctx});o.G=i.G,o.ctx=i.ctx,o._undo=i._undo;return o._initial=function(e){return JSON.parse(JSON.stringify(e))}(o),function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:o,n=arguments[1];switch(n.type){case ke:if(r)return e;vn.set(e.ctx._random);var i=t.flow.processGameEvent(e,n.payload),s=hn({},i.ctx,{_random:vn.get()});return hn({},i,{ctx:s,_stateID:e._stateID+1});case be:if(!t.flow.validator(e.G,e.ctx,n.payload))return e;vn.set(e.ctx._random);var a=t.processMove(e.G,n.payload,e.ctx),c=hn({},e.ctx,{_random:vn.get()});r&&!t.flow.optimisticUpdate(a,c,n.payload)&&(a=e.G);var u=[].concat(mn(e.log),[n]);return e=hn({},e,{G:a,ctx:c,log:u,_stateID:e._stateID+1}),r?e:(e=t.flow.processMove(e,n),e=hn({},e,{ctx:hn({},e.ctx,{_random:vn.get()})}));case we:return n.state;default:return e}}}function F(e,t,n){var r={},o=!0,i=!1,s=void 0;try{for(var a,c=function(){var e=a.value;r[e]=function(){for(var r=arguments.length,o=Array(r),i=0;r>i;i++)o[i]=arguments[i];t.dispatch(Ee(e,o,n))}},u=e[Symbol.iterator]();!(o=(a=u.next()).done);o=!0)c()}catch(e){i=!0,s=e}finally{try{!o&&u.return&&u.return()}finally{if(i)throw s}}return r}function X(e,t,n){var r={},o=!0,i=!1,s=void 0;try{for(var a,c=function(){var e=a.value;r[e]=function(){for(var r=arguments.length,o=Array(r),i=0;r>i;i++)o[i]=arguments[i];t.dispatch(xe(e,o,n))}},u=e[Symbol.iterator]();!(o=(a=u.next()).done);o=!0)c()}catch(e){i=!0,s=e}finally{try{!o&&u.return&&u.return()}finally{if(i)throw s}}return r}function K(e){return new En(e)}function J(e){return function(){return e}}function W(e){var t=e.ctx,n=e.events,r=e.init,o=e.validator,i=e.processMove,s=e.optimisticUpdate;t||(t=function(){return{}}),n||(n={}),r||(r=function(e){return e}),o||(o=function(){return!0}),i||(i=function(e){return e}),void 0===s&&(s=function(){return!0});var a=function(e,t){if(n.hasOwnProperty(t.type)){var r={playerID:t.playerID},o=[e].concat(t.args),i=e.log||[],s=[].concat(mn(i),[t]),a=n[t.type].apply(r,o);return hn({},a,{log:s})}return e};return{ctx:t,init:r,validator:function(e,t,n){return void 0===t.gameover&&o(e,t,n)},eventNames:Object.getOwnPropertyNames(n),processMove:function(e,t){return i(e,t,a)},processGameEvent:function(e,t){return a(e,t)},optimisticUpdate:s}}function V(e){function t(e,r,o){var i=e.G,s=e.ctx,a=y[s.phase];i=a.onPhaseEnd(i,s);var c=a.endGameIf(i,s);if(void 0!==c)return hn({},e,{G:i,ctx:hn({},s,{gameover:c})});if(r in y)s=hn({},s,{phase:r});else{var u=d.indexOf(s.phase);s=hn({},s,{phase:n[u=(u+1)%n.length].name})}if(e=E(hn({},e,{G:i,ctx:s}),y[s.phase]),o||(o=0),n.length-1>o){var l=(a=y[e.ctx.phase]).endPhaseIf(e.G,e.ctx);l&&(e=t(e,l,o+1))}return e}var n=e.phases,r=e.movesPerTurn,o=e.endTurnIf,i=e.endGameIf,s=e.onTurnBegin,a=e.onTurnEnd,c=e.onMove,u=e.turnOrder,l=e.endTurn,p=e.endPhase,h=e.undo,f=e.optimisticUpdate;void 0===p&&n&&(p=!0),void 0===l&&(l=!0),void 0===h&&(h=!1),void 0===f&&(f=function(){return!0}),n||(n=[{name:"default"}]),o||(o=function(){return!1}),i||(i=function(){}),s||(s=function(e){return e}),a||(a=function(e){return e}),c||(c=function(e){return e}),u||(u=jn.DEFAULT);var d=[],y={},m=!0,g=!1,v=void 0;try{for(var b,k=n[Symbol.iterator]();!(m=(b=k.next()).done);m=!0){var w=b.value;d.push(w.name),y[w.name]=w,void 0===w.endPhaseIf&&(w.endPhaseIf=function(){return!1}),void 0===w.onPhaseBegin&&(w.onPhaseBegin=function(e){return e}),void 0===w.onPhaseEnd&&(w.onPhaseEnd=function(e){return e}),void 0===w.movesPerTurn&&(w.movesPerTurn=r),void 0===w.endTurnIf&&(w.endTurnIf=o),void 0===w.endGameIf&&(w.endGameIf=i),void 0===w.onTurnBegin&&(w.onTurnBegin=s),void 0===w.onTurnEnd&&(w.onTurnEnd=a),void 0===w.onMove&&(w.onMove=c),void 0===w.turnOrder&&(w.turnOrder=u)}}catch(e){g=!0,v=e}finally{try{!m&&k.return&&k.return()}finally{if(g)throw v}}var x=function(e,t){var n=y[t.phase];return!(!n.movesPerTurn||n.movesPerTurn>t.currentPlayerMoves)||n.endTurnIf(e,t)},E=function(e,t){var n=hn({},e.ctx),r=t.onPhaseBegin(e.G,n);return n.currentPlayer=t.turnOrder.first(r,n),hn({},e,{G:r,ctx:n})},C=function(e,t){var n=hn({},e.ctx),r=t.onTurnBegin(e.G,n);return hn({},e,{G:r,ctx:n,_undo:[{G:r,ctx:n}],_redo:[]})},_=function(e,t){return e=E(e,t),e=C(e,t)},A={};return h&&(A.undo=function(e){var t=e._undo,n=e._redo;if(2>t.length)return e;var r=t[t.length-1],o=t[t.length-2];return hn({},e,{G:o.G,ctx:o.ctx,_undo:t.slice(0,t.length-1),_redo:[r].concat(mn(n))})},A.redo=function(e){var t=e._undo,n=e._redo;if(0==n.length)return e;var r=n[0];return hn({},e,{G:r.G,ctx:r.ctx,_undo:[].concat(mn(t),[r]),_redo:n.slice(1)})}),l&&(A.endTurn=function(e){var n=e.G,r=e.ctx,o=y[r.phase];if(o.movesPerTurn&&o.movesPerTurn>r.currentPlayerMoves)return e;n=o.onTurnEnd(n,r);var i=o.endGameIf(n,r);if(void 0!==i)return hn({},e,{G:n,ctx:hn({},r,{gameover:i})});var s=o.turnOrder.next(n,r);r=hn({},r,{currentPlayer:s,turn:r.turn+1,currentPlayerMoves:0});var a=o.endPhaseIf(n,r);return a?t(hn({},e,{G:n,ctx:r}),a):C(hn({},e,{G:n,ctx:r}),o)}),p&&(A.endPhase=t),W({ctx:function(e){return{numPlayers:e,turn:0,currentPlayer:"0",currentPlayerMoves:0,phase:n[0].name}},init:function(e){return _(e,n[0])},optimisticUpdate:function(e,t,n){return(void 0===t._random||void 0===t._random.prngstate)&&f(e,t,n)},events:A,validator:function(e,t,n){var r=y[t.phase];return!r.allowedMoves||new Set(r.allowedMoves).has(n.type)},processMove:function(e,t,n){var r=e.ctx.currentPlayerMoves+1;e=hn({},e,{ctx:hn({},e.ctx,{currentPlayerMoves:r})});var o=y[e.ctx.phase],i=o.onMove(e.G,e.ctx,t);e=hn({},e,{G:i});var s=o.endGameIf(e.G,e.ctx),a=x(e.G,e.ctx);if((a||void 0!==s)&&(e=n(e,{type:"endTurn",playerID:t.playerID})),void 0!==s)return hn({},e,{ctx:hn({},e.ctx,{gameover:s})});var c=o.endPhaseIf(e.G,e.ctx);if(c&&(e=n(e,{type:"endPhase",args:[c],playerID:t.playerID})),!a){var u=e._undo||[];e=hn({},e,{_undo:[].concat(mn(u),[{G:e.G,ctx:e.ctx}]),_redo:[]})}return e}})}e=e&&e.hasOwnProperty("default")?e.default:e;var Y="object"==typeof global&&global&&global.Object===Object&&global,$="object"==typeof self&&self&&self.Object===Object&&self,Z=(Y||$||Function("return this")()).Symbol,Q=Object.prototype,ee=Q.hasOwnProperty,te=Q.toString,ne=Z?Z.toStringTag:void 0,re=Object.prototype.toString,oe="[object Null]",ie="[object Undefined]",se=Z?Z.toStringTag:void 0,ae=function(e,t){return function(n){return e(t(n))}}(Object.getPrototypeOf,Object),ce="[object Object]",ue=Function.prototype,le=Object.prototype,pe=ue.toString,he=le.hasOwnProperty,fe=pe.call(Object),de="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},ye=a(function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t,n=e.Symbol;return"function"==typeof n?n.observable?t=n.observable:(t=n("observable"),n.observable=t):t="@@observable",t}}),me=a(function(e,t){Object.defineProperty(t,"__esModule",{value:!0});var n,r=function(e){return e&&e.__esModule?e:{default:e}}(ye);n="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==de?de:e;var o=(0,r.default)(n);t.default=o}),ge={INIT:"@@redux/INIT"},ve=Object.assign||function(e){for(var t=1;arguments.length>t;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},be="MAKE_MOVE",ke="GAME_EVENT",we="RESTORE",xe=function(e,t,n){return{type:be,payload:{type:e,args:t,playerID:n}}},Ee=function(e,t,n){return{type:ke,payload:{type:e,args:t,playerID:n}}},Ce=function(e){return{type:we,state:e}},_e=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,Ae=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],Pe=function(e){var t=e,n=e.indexOf("["),r=e.indexOf("]");-1!=n&&-1!=r&&(e=e.substring(0,n)+e.substring(n,r).replace(/:/g,";")+e.substring(r,e.length));for(var o=_e.exec(e||""),i={},s=14;s--;)i[Ae[s]]=o[s]||"";return-1!=n&&-1!=r&&(i.source=t,i.host=i.host.substring(1,i.host.length-1).replace(/;/g,":"),i.authority=i.authority.replace("[","").replace("]","").replace(/;/g,":"),i.ipv6uri=!0),i},De=1e3,Se=60*De,Ie=60*Se,Te=24*Ie,Oe=365.25*Te,Be=function(e,t){t=t||{};var n=typeof e;if("string"===n&&e.length>0)return p(e);if("number"===n&&!1===isNaN(e))return t.long?f(e):h(e);throw Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))},Ne=a(function(e,t){function n(e){var n,r=0;for(n in e)r=(r<<5)-r+e.charCodeAt(n),r|=0;return t.colors[Math.abs(r)%t.colors.length]}function r(e){function r(){if(r.enabled){var e=r,n=+new Date,i=n-(o||n);e.diff=i,e.prev=o,e.curr=n,o=n;for(var s=Array(arguments.length),a=0;s.length>a;a++)s[a]=arguments[a];s[0]=t.coerce(s[0]),"string"!=typeof s[0]&&s.unshift("%O");var c=0;s[0]=s[0].replace(/%([a-zA-Z%])/g,function(n,r){if("%%"===n)return n;c++;var o=t.formatters[r];if("function"==typeof o){var i=s[c];n=o.call(e,i),s.splice(c,1),c--}return n}),t.formatArgs.call(e,s),(r.log||t.log||console.log.bind(console)).apply(e,s)}}return r.namespace=e,r.enabled=t.enabled(e),r.useColors=t.useColors(),r.color=n(e),"function"==typeof t.init&&t.init(r),r}(t=e.exports=r.debug=r.default=r).coerce=function(e){return e instanceof Error?e.stack||e.message:e},t.disable=function(){t.enable("")},t.enable=function(e){t.save(e),t.names=[],t.skips=[];for(var n=("string"==typeof e?e:"").split(/[\s,]+/),r=n.length,o=0;r>o;o++)n[o]&&("-"===(e=n[o].replace(/\*/g,".*?"))[0]?t.skips.push(RegExp("^"+e.substr(1)+"$")):t.names.push(RegExp("^"+e+"$")))},t.enabled=function(e){var n,r;for(n=0,r=t.skips.length;r>n;n++)if(t.skips[n].test(e))return!1;for(n=0,r=t.names.length;r>n;n++)if(t.names[n].test(e))return!0;return!1},t.humanize=Be,t.names=[],t.skips=[],t.formatters={};var o}),Re=a(function(e,t){function n(){var e;try{e=t.storage.debug}catch(e){}return!e&&"undefined"!=typeof process&&"env"in process&&(e=process.env.DEBUG),e}(t=e.exports=Ne).log=function(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)},t.formatArgs=function(e){var n=this.useColors;if(e[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+e[0]+(n?"%c ":" ")+"+"+t.humanize(this.diff),n){var r="color: "+this.color;e.splice(1,0,r,"color: inherit");var o=0,i=0;e[0].replace(/%[a-zA-Z%]/g,function(e){"%%"!==e&&(o++,"%c"===e&&(i=o))}),e.splice(i,0,r)}},t.save=function(e){try{null==e?t.storage.removeItem("debug"):t.storage.debug=e}catch(e){}},t.load=n,t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type)||"undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)},t.storage="undefined"!=typeof chrome&&void 0!==chrome.storage?chrome.storage.local:function(){try{return window.localStorage}catch(e){}}(),t.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],t.formatters.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},t.enable(n())}),je=Re("socket.io-client:url"),Me=function(e,t){var n=e;t=t||de.location,null==e&&(e=t.protocol+"//"+t.host),"string"==typeof e&&("/"===e.charAt(0)&&(e="/"===e.charAt(1)?t.protocol+e:t.host+e),/^(https?|wss?):\/\//.test(e)||(je("protocol-less url %s",e),e=void 0!==t?t.protocol+"//"+e:"https://"+e),je("parse %s",e),n=Pe(e)),n.port||(/^(http|ws)$/.test(n.protocol)?n.port="80":/^(http|ws)s$/.test(n.protocol)&&(n.port="443")),n.path=n.path||"/";var r=-1!==n.host.indexOf(":")?"["+n.host+"]":n.host;return n.id=n.protocol+"://"+r+":"+n.port,n.href=n.protocol+"://"+r+(t&&t.port===n.port?"":":"+n.port),n},qe=a(function(e,t){function n(e){var n,r=0;for(n in e)r=(r<<5)-r+e.charCodeAt(n),r|=0;return t.colors[Math.abs(r)%t.colors.length]}function r(e){function r(){if(r.enabled){var e=r,n=+new Date,i=n-(o||n);e.diff=i,e.prev=o,e.curr=n,o=n;for(var s=Array(arguments.length),a=0;s.length>a;a++)s[a]=arguments[a];s[0]=t.coerce(s[0]),"string"!=typeof s[0]&&s.unshift("%O");var c=0;s[0]=s[0].replace(/%([a-zA-Z%])/g,function(n,r){if("%%"===n)return n;c++;var o=t.formatters[r];if("function"==typeof o){var i=s[c];n=o.call(e,i),s.splice(c,1),c--}return n}),t.formatArgs.call(e,s),(r.log||t.log||console.log.bind(console)).apply(e,s)}}return r.namespace=e,r.enabled=t.enabled(e),r.useColors=t.useColors(),r.color=n(e),"function"==typeof t.init&&t.init(r),r}(t=e.exports=r.debug=r.default=r).coerce=function(e){return e instanceof Error?e.stack||e.message:e},t.disable=function(){t.enable("")},t.enable=function(e){t.save(e),t.names=[],t.skips=[];for(var n=("string"==typeof e?e:"").split(/[\s,]+/),r=n.length,o=0;r>o;o++)n[o]&&("-"===(e=n[o].replace(/\*/g,".*?"))[0]?t.skips.push(RegExp("^"+e.substr(1)+"$")):t.names.push(RegExp("^"+e+"$")))},t.enabled=function(e){var n,r;for(n=0,r=t.skips.length;r>n;n++)if(t.skips[n].test(e))return!1;for(n=0,r=t.names.length;r>n;n++)if(t.names[n].test(e))return!0;return!1},t.humanize=Be,t.names=[],t.skips=[],t.formatters={};var o}),Le=a(function(e,t){function n(){var e;try{e=t.storage.debug}catch(e){}return!e&&"undefined"!=typeof process&&"env"in process&&(e=process.env.DEBUG),e}(t=e.exports=qe).log=function(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)},t.formatArgs=function(e){var n=this.useColors;if(e[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+e[0]+(n?"%c ":" ")+"+"+t.humanize(this.diff),n){var r="color: "+this.color;e.splice(1,0,r,"color: inherit");var o=0,i=0;e[0].replace(/%[a-zA-Z%]/g,function(e){"%%"!==e&&(o++,"%c"===e&&(i=o))}),e.splice(i,0,r)}},t.save=function(e){try{null==e?t.storage.removeItem("debug"):t.storage.debug=e}catch(e){}},t.load=n,t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type)||"undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)},t.storage="undefined"!=typeof chrome&&void 0!==chrome.storage?chrome.storage.local:function(){try{return window.localStorage}catch(e){}}(),t.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],t.formatters.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},t.enable(n())}),Ue=a(function(e){function t(e){if(e)return n(e)}function n(e){for(var n in t.prototype)e[n]=t.prototype[n];return e}e.exports=t,t.prototype.on=t.prototype.addEventListener=function(e,t){return this._callbacks=this._callbacks||{},(this._callbacks["$"+e]=this._callbacks["$"+e]||[]).push(t),this},t.prototype.once=function(e,t){function n(){this.off(e,n),t.apply(this,arguments)}return n.fn=t,this.on(e,n),this},t.prototype.off=t.prototype.removeListener=t.prototype.removeAllListeners=t.prototype.removeEventListener=function(e,t){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n=this._callbacks["$"+e];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+e],this;for(var r,o=0;n.length>o;o++)if((r=n[o])===t||r.fn===t){n.splice(o,1);break}return this},t.prototype.emit=function(e){this._callbacks=this._callbacks||{};var t=[].slice.call(arguments,1),n=this._callbacks["$"+e];if(n)for(var r=0,o=(n=n.slice(0)).length;o>r;++r)n[r].apply(this,t);return this},t.prototype.listeners=function(e){return this._callbacks=this._callbacks||{},this._callbacks["$"+e]||[]},t.prototype.hasListeners=function(e){return!!this.listeners(e).length}}),ze={}.toString,Ge=Array.isArray||function(e){return"[object Array]"==ze.call(e)},He=Object.prototype.toString,Fe="function"==typeof de.Blob||"[object BlobConstructor]"===He.call(de.Blob),Xe="function"==typeof de.File||"[object FileConstructor]"===He.call(de.File),Ke=y,Je={}.toString,We=Array.isArray||function(e){return"[object Array]"==Je.call(e)},Ve=function(e){return de.Buffer&&de.Buffer.isBuffer(e)||de.ArrayBuffer&&e instanceof ArrayBuffer},Ye=Object.prototype.toString,$e="function"==typeof de.Blob||"[object BlobConstructor]"===Ye.call(de.Blob),Ze="function"==typeof de.File||"[object FileConstructor]"===Ye.call(de.File),Qe={deconstructPacket:function(e){var t=[],n=e.data,r=e;return r.data=m(n,t),r.attachments=t.length,{packet:r,buffers:t}},reconstructPacket:function(e,t){return e.data=g(e.data,t),e.attachments=void 0,e},removeBlobs:function(e,t){function n(e,i,s){if(!e)return e;if($e&&e instanceof Blob||Ze&&e instanceof File){r++;var a=new FileReader;a.onload=function(){s?s[i]=this.result:o=this.result,--r||t(o)},a.readAsArrayBuffer(e)}else if(We(e))for(var c=0;e.length>c;c++)n(e[c],c,e);else if("object"==typeof e&&!Ve(e))for(var u in e)n(e[u],u,e)}var r=0,o=e;n(o),r||t(o)}},et=a(function(e,t){function n(){}function r(e){var n=""+e.type;return t.BINARY_EVENT!==e.type&&t.BINARY_ACK!==e.type||(n+=e.attachments+"-"),e.nsp&&"/"!==e.nsp&&(n+=e.nsp+","),null!=e.id&&(n+=e.id),null!=e.data&&(n+=JSON.stringify(e.data)),l("encoded %j as %s",e,n),n}function o(e,t){Qe.removeBlobs(e,function(e){var n=Qe.deconstructPacket(e),o=r(n.packet),i=n.buffers;i.unshift(o),t(i)})}function i(){this.reconstructor=null}function s(e){var n=0,r={type:+e.charAt(0)};if(null==t.types[r.type])return u();if(t.BINARY_EVENT===r.type||t.BINARY_ACK===r.type){for(var o="";"-"!==e.charAt(++n)&&(o+=e.charAt(n),n!=e.length););if(o!=+o||"-"!==e.charAt(n))throw Error("Illegal attachments");r.attachments=+o}if("/"===e.charAt(n+1))for(r.nsp="";++n&&","!==(s=e.charAt(n))&&(r.nsp+=s,n!==e.length););else r.nsp="/";var i=e.charAt(n+1);if(""!==i&&+i==i){for(r.id="";++n;){var s=e.charAt(n);if(null==s||+s!=s){--n;break}if(r.id+=e.charAt(n),n===e.length)break}r.id=+r.id}return e.charAt(++n)&&(r=a(r,e.substr(n))),l("decoded %s as %j",e,r),r}function a(e,t){try{e.data=JSON.parse(t)}catch(e){return u()}return e}function c(e){this.reconPack=e,this.buffers=[]}function u(){return{type:t.ERROR,data:"parser error"}}var l=Le("socket.io-parser");t.protocol=4,t.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],t.CONNECT=0,t.DISCONNECT=1,t.EVENT=2,t.ACK=3,t.ERROR=4,t.BINARY_EVENT=5,t.BINARY_ACK=6,t.Encoder=n,t.Decoder=i,n.prototype.encode=function(e,n){e.type!==t.EVENT&&e.type!==t.ACK||!Ke(e.data)||(e.type=e.type===t.EVENT?t.BINARY_EVENT:t.BINARY_ACK),l("encoding packet %j",e),t.BINARY_EVENT===e.type||t.BINARY_ACK===e.type?o(e,n):n([r(e)])},Ue(i.prototype),i.prototype.add=function(e){var n;if("string"==typeof e)n=s(e),t.BINARY_EVENT===n.type||t.BINARY_ACK===n.type?(this.reconstructor=new c(n),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",n)):this.emit("decoded",n);else{if(!Ve(e)&&!e.base64)throw Error("Unknown type: "+e);if(!this.reconstructor)throw Error("got binary data when not reconstructing a packet");(n=this.reconstructor.takeBinaryData(e))&&(this.reconstructor=null,this.emit("decoded",n))}},i.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()},c.prototype.takeBinaryData=function(e){if(this.buffers.push(e),this.buffers.length===this.reconPack.attachments){var t=Qe.reconstructPacket(this.reconPack,this.buffers);return this.finishedReconstruction(),t}return null},c.prototype.finishedReconstruction=function(){this.reconPack=null,this.buffers=[]}}),tt=a(function(e){try{e.exports="undefined"!=typeof XMLHttpRequest&&"withCredentials"in new XMLHttpRequest}catch(t){e.exports=!1}}),nt=function(e){var t=e.xdomain,n=e.xscheme,r=e.enablesXDR;try{if("undefined"!=typeof XMLHttpRequest&&(!t||tt))return new XMLHttpRequest}catch(e){}try{if("undefined"!=typeof XDomainRequest&&!n&&r)return new XDomainRequest}catch(e){}if(!t)try{return new(de[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(e){}},rt=Object.keys||function(e){var t=[],n=Object.prototype.hasOwnProperty;for(var r in e)n.call(e,r)&&t.push(r);return t},ot=function(e,t,n){var r=e.byteLength;if(t=t||0,n=n||r,e.slice)return e.slice(t,n);if(0>t&&(t+=r),0>n&&(n+=r),n>r&&(n=r),t>=r||t>=n||0===r)return new ArrayBuffer(0);for(var o=new Uint8Array(e),i=new Uint8Array(n-t),s=t,a=0;n>s;s++,a++)i[a]=o[s];return i.buffer},it=function(e,t,n){function r(e,i){if(0>=r.count)throw Error("after called too many times");--r.count,e?(o=!0,t(e),t=n):0!==r.count||o||t(null,i)}var o=!1;return n=n||v,r.count=e,0===e?t():r},st=a(function(e,t){!function(n){function r(e){for(var t,n,r=[],o=0,i=e.length;i>o;)55296>(t=e.charCodeAt(o++))||t>56319||o>=i?r.push(t):56320==(64512&(n=e.charCodeAt(o++)))?r.push(((1023&t)<<10)+(1023&n)+65536):(r.push(t),o--);return r}function o(e){for(var t,n=e.length,r=-1,o="";++r65535&&(o+=m((t-=65536)>>>10&1023|55296),t=56320|1023&t),o+=m(t);return o}function i(e,t){if(e>=55296&&57343>=e){if(t)throw Error("Lone surrogate U+"+e.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function s(e,t){return m(e>>t&63|128)}function a(e,t){if(0==(4294967168&e))return m(e);var n="";return 0==(4294965248&e)?n=m(e>>6&31|192):0==(4294901760&e)?(i(e,t)||(e=65533),n=m(e>>12&15|224),n+=s(e,6)):0==(4292870144&e)&&(n=m(e>>18&7|240),n+=s(e,12),n+=s(e,6)),n+=m(63&e|128)}function c(){if(y>=d)throw Error("Invalid byte index");var e=255&f[y];if(y++,128==(192&e))return 63&e;throw Error("Invalid continuation byte")}function u(e){var t,n,r,o,s;if(y>d)throw Error("Invalid byte index");if(y==d)return!1;if(t=255&f[y],y++,0==(128&t))return t;if(192==(224&t)){if(n=c(),128>(s=(31&t)<<6|n))throw Error("Invalid continuation byte");return s}if(224==(240&t)){if(n=c(),r=c(),2048>(s=(15&t)<<12|n<<6|r))throw Error("Invalid continuation byte");return i(s,e)?s:65533}if(240==(248&t)&&(n=c(),r=c(),o=c(),(s=(7&t)<<18|n<<12|r<<6|o)>=65536&&1114111>=s))return s;throw Error("Invalid UTF-8 detected")}var l=t,p=e&&e.exports==l&&e,h="object"==typeof de&&de;h.global!==h&&h.window!==h||(n=h);var f,d,y,m=String.fromCharCode,g={version:"2.1.2",encode:function(e,t){for(var n=!1!==(t=t||{}).strict,o=r(e),i=o.length,s=-1,c="";++sr;r++)n[e.charCodeAt(r)]=r;t.encode=function(t){var n,r=new Uint8Array(t),o=r.length,i="";for(n=0;o>n;n+=3)i+=e[r[n]>>2],i+=e[(3&r[n])<<4|r[n+1]>>4],i+=e[(15&r[n+1])<<2|r[n+2]>>6],i+=e[63&r[n+2]];return o%3==2?i=i.substring(0,i.length-1)+"=":o%3==1&&(i=i.substring(0,i.length-2)+"=="),i},t.decode=function(e){var t,r,o,i,s,a=.75*e.length,c=e.length,u=0;"="===e[e.length-1]&&(a--,"="===e[e.length-2]&&a--);var l=new ArrayBuffer(a),p=new Uint8Array(l);for(t=0;c>t;t+=4)r=n[e.charCodeAt(t)],o=n[e.charCodeAt(t+1)],i=n[e.charCodeAt(t+2)],s=n[e.charCodeAt(t+3)],p[u++]=r<<2|o>>4,p[u++]=(15&o)<<4|i>>2,p[u++]=(3&i)<<6|63&s;return l}}()}),ct=de.BlobBuilder||de.WebKitBlobBuilder||de.MSBlobBuilder||de.MozBlobBuilder,ut=function(){try{return 2===new Blob(["hi"]).size}catch(e){return!1}}(),lt=ut&&function(){try{return 2===new Blob([new Uint8Array([1,2])]).size}catch(e){return!1}}(),pt=ct&&ct.prototype.append&&ct.prototype.getBlob,ht=ut?lt?de.Blob:w:pt?k:void 0,ft=a(function(e,t){function n(e,n){return n("b"+t.packets[e.type]+e.data.data)}function r(e,n,r){if(!n)return t.encodeBase64Packet(e,r);var o=e.data,i=new Uint8Array(o),s=new Uint8Array(1+o.byteLength);s[0]=h[e.type];for(var a=0;i.length>a;a++)s[a+1]=i[a];return r(s.buffer)}function o(e,n,r){if(!n)return t.encodeBase64Packet(e,r);var o=new FileReader;return o.onload=function(){e.data=o.result,t.encodePacket(e,n,!0,r)},o.readAsArrayBuffer(e.data)}function i(e,n,r){if(!n)return t.encodeBase64Packet(e,r);if(p)return o(e,n,r);var i=new Uint8Array(1);return i[0]=h[e.type],r(new ht([i.buffer,e.data]))}function s(e){try{e=st.decode(e,{strict:!1})}catch(e){return!1}return e}function a(e,t,n){for(var r=Array(e.length),o=it(e.length,n),i=0;e.length>i;i++)!function(e,n,o){t(n,function(t,n){r[e]=n,o(t,r)})}(i,e[i],o)}var c;de&&de.ArrayBuffer&&(c=at);var u="undefined"!=typeof navigator&&/Android/i.test(navigator.userAgent),l="undefined"!=typeof navigator&&/PhantomJS/i.test(navigator.userAgent),p=u||l;t.protocol=3;var h=t.packets={open:0,close:1,ping:2,pong:3,message:4,upgrade:5,noop:6},f=rt(h),d={type:"error",data:"parser error"};t.encodePacket=function(e,t,o,s){"function"==typeof t&&(s=t,t=!1),"function"==typeof o&&(s=o,o=null);var a=void 0===e.data?void 0:e.data.buffer||e.data;if(de.ArrayBuffer&&a instanceof ArrayBuffer)return r(e,t,s);if(ht&&a instanceof de.Blob)return i(e,t,s);if(a&&a.base64)return n(e,s);var c=h[e.type];return void 0!==e.data&&(c+=o?st.encode(e.data+"",{strict:!1}):e.data+""),s(""+c)},t.encodeBase64Packet=function(e,n){var r="b"+t.packets[e.type];if(ht&&e.data instanceof de.Blob){var o=new FileReader;return o.onload=function(){var e=o.result.split(",")[1];n(r+e)},o.readAsDataURL(e.data)}var i;try{i=String.fromCharCode.apply(null,new Uint8Array(e.data))}catch(t){for(var s=new Uint8Array(e.data),a=Array(s.length),c=0;s.length>c;c++)a[c]=s[c];i=String.fromCharCode.apply(null,a)}return r+=de.btoa(i),n(r)},t.decodePacket=function(e,n,r){if(void 0===e)return d;if("string"==typeof e)return"b"===e.charAt(0)?t.decodeBase64Packet(e.substr(1),n):r&&!1===(e=s(e))?d:+(o=e.charAt(0))==o&&f[o]?e.length>1?{type:f[o],data:e.substring(1)}:{type:f[o]}:d;var o=new Uint8Array(e)[0],i=ot(e,1);return ht&&"blob"===n&&(i=new ht([i])),{type:f[o],data:i}},t.decodeBase64Packet=function(e,t){var n=f[e.charAt(0)];if(!c)return{type:n,data:{base64:!0,data:e.substr(1)}};var r=c.decode(e.substr(1));return"blob"===t&&ht&&(r=new ht([r])),{type:n,data:r}},t.encodePayload=function(e,n,r){function o(e){return e.length+":"+e}"function"==typeof n&&(r=n,n=null);var i=Ke(e);return n&&i?ht&&!p?t.encodePayloadAsBlob(e,r):t.encodePayloadAsArrayBuffer(e,r):e.length?void a(e,function(e,r){t.encodePacket(e,!!i&&n,!1,function(e){r(null,o(e))})},function(e,t){return r(t.join(""))}):r("0:")},t.decodePayload=function(e,n,r){if("string"!=typeof e)return t.decodePayloadAsBinary(e,n,r);"function"==typeof n&&(r=n,n=null);var o;if(""===e)return r(d,0,1);for(var i,s,a="",c=0,u=e.length;u>c;c++){var l=e.charAt(c);if(":"===l){if(""===a||a!=(i=+a))return r(d,0,1);if(s=e.substr(c+1,i),a!=s.length)return r(d,0,1);if(s.length){if(o=t.decodePacket(s,n,!1),d.type===o.type&&d.data===o.data)return r(d,0,1);if(!1===r(o,c+i,u))return}c+=i,a=""}else a+=l}return""!==a?r(d,0,1):void 0},t.encodePayloadAsArrayBuffer=function(e,n){if(!e.length)return n(new ArrayBuffer(0));a(e,function(e,n){t.encodePacket(e,!0,!0,function(e){return n(null,e)})},function(e,t){var r=t.reduce(function(e,t){var n;return n="string"==typeof t?t.length:t.byteLength,e+(""+n).length+n+2},0),o=new Uint8Array(r),i=0;return t.forEach(function(e){var t="string"==typeof e,n=e;if(t){for(var r=new Uint8Array(e.length),s=0;e.length>s;s++)r[s]=e.charCodeAt(s);n=r.buffer}o[i++]=t?0:1;for(var a=""+n.byteLength,s=0;a.length>s;s++)o[i++]=parseInt(a[s]);o[i++]=255;for(var r=new Uint8Array(n),s=0;r.length>s;s++)o[i++]=r[s]}),n(o.buffer)})},t.encodePayloadAsBlob=function(e,n){a(e,function(e,n){t.encodePacket(e,!0,!0,function(e){var t=new Uint8Array(1);if(t[0]=1,"string"==typeof e){for(var r=new Uint8Array(e.length),o=0;e.length>o;o++)r[o]=e.charCodeAt(o);e=r.buffer,t[0]=0}for(var i=""+(e instanceof ArrayBuffer?e.byteLength:e.size),s=new Uint8Array(i.length+1),o=0;i.length>o;o++)s[o]=parseInt(i[o]);if(s[i.length]=255,ht){var a=new ht([t.buffer,s.buffer,e]);n(null,a)}})},function(e,t){return n(new ht(t))})},t.decodePayloadAsBinary=function(e,n,r){"function"==typeof n&&(r=n,n=null);for(var o=e,i=[];o.byteLength>0;){for(var s=new Uint8Array(o),a=0===s[0],c="",u=1;255!==s[u];u++){if(c.length>310)return r(d,0,1);c+=s[u]}o=ot(o,2+c.length);var l=ot(o,0,c=parseInt(c));if(a)try{l=String.fromCharCode.apply(null,new Uint8Array(l))}catch(e){var p=new Uint8Array(l);l="";for(u=0;p.length>u;u++)l+=String.fromCharCode(p[u])}i.push(l),o=ot(o,c)}var h=i.length;i.forEach(function(e,o){r(t.decodePacket(e,n,!0),o,h)})}}),dt=x;Ue(x.prototype),x.prototype.onError=function(e,t){var n=Error(e);return n.type="TransportError",n.description=t,this.emit("error",n),this},x.prototype.open=function(){return"closed"!==this.readyState&&""!==this.readyState||(this.readyState="opening",this.doOpen()),this},x.prototype.close=function(){return"opening"!==this.readyState&&"open"!==this.readyState||(this.doClose(),this.onClose()),this},x.prototype.send=function(e){if("open"!==this.readyState)throw Error("Transport not open");this.write(e)},x.prototype.onOpen=function(){this.readyState="open",this.writable=!0,this.emit("open")},x.prototype.onData=function(e){var t=ft.decodePacket(e,this.socket.binaryType);this.onPacket(t)},x.prototype.onPacket=function(e){this.emit("packet",e)},x.prototype.onClose=function(){this.readyState="closed",this.emit("close")};for(var yt,mt={encode:function(e){var t="";for(var n in e)e.hasOwnProperty(n)&&(t.length&&(t+="&"),t+=encodeURIComponent(n)+"="+encodeURIComponent(e[n]));return t},decode:function(e){for(var t={},n=e.split("&"),r=0,o=n.length;o>r;r++){var i=n[r].split("=");t[decodeURIComponent(i[0])]=decodeURIComponent(i[1])}return t}},gt=function(e,t){var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e},vt="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),bt=64,kt={},wt=0,xt=0;bt>xt;xt++)kt[vt[xt]]=xt;C.encode=E,C.decode=function(e){var t=0;for(xt=0;e.length>xt;xt++)t=t*bt+kt[e.charAt(xt)];return t};var Et=C,Ct=a(function(e,t){function n(e){var n,r=0;for(n in e)r=(r<<5)-r+e.charCodeAt(n),r|=0;return t.colors[Math.abs(r)%t.colors.length]}function r(e){function r(){if(r.enabled){var e=r,n=+new Date,i=n-(o||n);e.diff=i,e.prev=o,e.curr=n,o=n;for(var s=Array(arguments.length),a=0;s.length>a;a++)s[a]=arguments[a];s[0]=t.coerce(s[0]),"string"!=typeof s[0]&&s.unshift("%O");var c=0;s[0]=s[0].replace(/%([a-zA-Z%])/g,function(n,r){if("%%"===n)return n;c++;var o=t.formatters[r];if("function"==typeof o){var i=s[c];n=o.call(e,i),s.splice(c,1),c--}return n}),t.formatArgs.call(e,s),(r.log||t.log||console.log.bind(console)).apply(e,s)}}return r.namespace=e,r.enabled=t.enabled(e),r.useColors=t.useColors(),r.color=n(e),"function"==typeof t.init&&t.init(r),r}(t=e.exports=r.debug=r.default=r).coerce=function(e){return e instanceof Error?e.stack||e.message:e},t.disable=function(){t.enable("")},t.enable=function(e){t.save(e),t.names=[],t.skips=[];for(var n=("string"==typeof e?e:"").split(/[\s,]+/),r=n.length,o=0;r>o;o++)n[o]&&("-"===(e=n[o].replace(/\*/g,".*?"))[0]?t.skips.push(RegExp("^"+e.substr(1)+"$")):t.names.push(RegExp("^"+e+"$")))},t.enabled=function(e){var n,r;for(n=0,r=t.skips.length;r>n;n++)if(t.skips[n].test(e))return!1;for(n=0,r=t.names.length;r>n;n++)if(t.names[n].test(e))return!0;return!1},t.humanize=Be,t.names=[],t.skips=[],t.formatters={};var o}),_t=a(function(e,t){function n(){var e;try{e=t.storage.debug}catch(e){}return!e&&"undefined"!=typeof process&&"env"in process&&(e=process.env.DEBUG),e}(t=e.exports=Ct).log=function(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)},t.formatArgs=function(e){var n=this.useColors;if(e[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+e[0]+(n?"%c ":" ")+"+"+t.humanize(this.diff),n){var r="color: "+this.color;e.splice(1,0,r,"color: inherit");var o=0,i=0;e[0].replace(/%[a-zA-Z%]/g,function(e){"%%"!==e&&(o++,"%c"===e&&(i=o))}),e.splice(i,0,r)}},t.save=function(e){try{null==e?t.storage.removeItem("debug"):t.storage.debug=e}catch(e){}},t.load=n,t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type)||"undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)},t.storage="undefined"!=typeof chrome&&void 0!==chrome.storage?chrome.storage.local:function(){try{return window.localStorage}catch(e){}}(),t.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],t.formatters.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},t.enable(n())}),At=_t("engine.io-client:polling"),Pt=_,Dt=null!=new nt({xdomain:!1}).responseType;gt(_,dt),_.prototype.name="polling",_.prototype.doOpen=function(){this.poll()},_.prototype.pause=function(e){function t(){At("paused"),n.readyState="paused",e()}var n=this;if(this.readyState="pausing",this.polling||!this.writable){var r=0;this.polling&&(At("we are currently polling - waiting to pause"),r++,this.once("pollComplete",function(){At("pre-pause polling complete"),--r||t()})),this.writable||(At("we are currently writing - waiting to pause"),r++,this.once("drain",function(){At("pre-pause writing complete"),--r||t()}))}else t()},_.prototype.poll=function(){At("polling"),this.polling=!0,this.doPoll(),this.emit("poll")},_.prototype.onData=function(e){var t=this;At("polling got data %s",e);ft.decodePayload(e,this.socket.binaryType,function(e,n,r){if("opening"===t.readyState&&t.onOpen(),"close"===e.type)return t.onClose(),!1;t.onPacket(e)}),"closed"!==this.readyState&&(this.polling=!1,this.emit("pollComplete"),"open"===this.readyState?this.poll():At('ignoring poll - transport state "%s"',this.readyState))},_.prototype.doClose=function(){function e(){At("writing close packet"),t.write([{type:"close"}])}var t=this;"open"===this.readyState?(At("transport open - closing"),e()):(At("transport not open - deferring close"),this.once("open",e))},_.prototype.write=function(e){var t=this;this.writable=!1;var n=function(){t.writable=!0,t.emit("drain")};ft.encodePayload(e,this.supportsBinary,function(e){t.doWrite(e,n)})},_.prototype.uri=function(){var e=this.query||{},t=this.secure?"https":"http",n="";return!1!==this.timestampRequests&&(e[this.timestampParam]=Et()),this.supportsBinary||e.sid||(e.b64=1),e=mt.encode(e),this.port&&("https"===t&&443!=+this.port||"http"===t&&80!=+this.port)&&(n=":"+this.port),e.length&&(e="?"+e),t+"://"+(-1!==this.hostname.indexOf(":")?"["+this.hostname+"]":this.hostname)+n+this.path+e};var St=_t("engine.io-client:polling-xhr"),It=P,Tt=D;gt(P,Pt),P.prototype.supportsBinary=!0,P.prototype.request=function(e){return e=e||{},e.uri=this.uri(),e.xd=this.xd,e.xs=this.xs,e.agent=this.agent||!1,e.supportsBinary=this.supportsBinary,e.enablesXDR=this.enablesXDR,e.pfx=this.pfx,e.key=this.key,e.passphrase=this.passphrase,e.cert=this.cert,e.ca=this.ca,e.ciphers=this.ciphers,e.rejectUnauthorized=this.rejectUnauthorized,e.requestTimeout=this.requestTimeout,e.extraHeaders=this.extraHeaders,new D(e)},P.prototype.doWrite=function(e,t){var n="string"!=typeof e&&void 0!==e,r=this.request({method:"POST",data:e,isBinary:n}),o=this;r.on("success",t),r.on("error",function(e){o.onError("xhr post error",e)}),this.sendXhr=r},P.prototype.doPoll=function(){St("xhr poll");var e=this.request(),t=this;e.on("data",function(e){t.onData(e)}),e.on("error",function(e){t.onError("xhr poll error",e)}),this.pollXhr=e},Ue(D.prototype),D.prototype.create=function(){var e={agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR};e.pfx=this.pfx,e.key=this.key,e.passphrase=this.passphrase,e.cert=this.cert,e.ca=this.ca,e.ciphers=this.ciphers,e.rejectUnauthorized=this.rejectUnauthorized;var t=this.xhr=new nt(e),n=this;try{St("xhr open %s: %s",this.method,this.uri),t.open(this.method,this.uri,this.async);try{if(this.extraHeaders){t.setDisableHeaderCheck&&t.setDisableHeaderCheck(!0);for(var r in this.extraHeaders)this.extraHeaders.hasOwnProperty(r)&&t.setRequestHeader(r,this.extraHeaders[r])}}catch(e){}if("POST"===this.method)try{this.isBinary?t.setRequestHeader("Content-type","application/octet-stream"):t.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(e){}try{t.setRequestHeader("Accept","*/*")}catch(e){}"withCredentials"in t&&(t.withCredentials=!0),this.requestTimeout&&(t.timeout=this.requestTimeout),this.hasXDR()?(t.onload=function(){n.onLoad()},t.onerror=function(){n.onError(t.responseText)}):t.onreadystatechange=function(){if(2===t.readyState){var e;try{e=t.getResponseHeader("Content-Type")}catch(e){}"application/octet-stream"===e&&(t.responseType="arraybuffer")}4===t.readyState&&(200===t.status||1223===t.status?n.onLoad():setTimeout(function(){n.onError(t.status)},0))},St("xhr data %s",this.data),t.send(this.data)}catch(e){return void setTimeout(function(){n.onError(e)},0)}de.document&&(this.index=D.requestsCount++,D.requests[this.index]=this)},D.prototype.onSuccess=function(){this.emit("success"),this.cleanup()},D.prototype.onData=function(e){this.emit("data",e),this.onSuccess()},D.prototype.onError=function(e){this.emit("error",e),this.cleanup(!0)},D.prototype.cleanup=function(e){if(void 0!==this.xhr&&null!==this.xhr){if(this.hasXDR()?this.xhr.onload=this.xhr.onerror=A:this.xhr.onreadystatechange=A,e)try{this.xhr.abort()}catch(e){}de.document&&delete D.requests[this.index],this.xhr=null}},D.prototype.onLoad=function(){var e;try{var t;try{t=this.xhr.getResponseHeader("Content-Type")}catch(e){}e="application/octet-stream"===t?this.xhr.response||this.xhr.responseText:this.xhr.responseText}catch(e){this.onError(e)}null!=e&&this.onData(e)},D.prototype.hasXDR=function(){return void 0!==de.XDomainRequest&&!this.xs&&this.enablesXDR},D.prototype.abort=function(){this.cleanup()},D.requestsCount=0,D.requests={},de.document&&(de.attachEvent?de.attachEvent("onunload",S):de.addEventListener&&de.addEventListener("beforeunload",S,!1)),It.Request=Tt;var Ot,Bt=T,Nt=/\n/g,Rt=/\\n/g;gt(T,Pt),T.prototype.supportsBinary=!1,T.prototype.doClose=function(){this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),this.form&&(this.form.parentNode.removeChild(this.form),this.form=null,this.iframe=null),Pt.prototype.doClose.call(this)},T.prototype.doPoll=function(){var e=this,t=document.createElement("script");this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),t.async=!0,t.src=this.uri(),t.onerror=function(t){e.onError("jsonp poll error",t)};var n=document.getElementsByTagName("script")[0];n?n.parentNode.insertBefore(t,n):(document.head||document.body).appendChild(t),this.script=t,"undefined"!=typeof navigator&&/gecko/i.test(navigator.userAgent)&&setTimeout(function(){var e=document.createElement("iframe");document.body.appendChild(e),document.body.removeChild(e)},100)},T.prototype.doWrite=function(e,t){function n(){r(),t()}function r(){if(o.iframe)try{o.form.removeChild(o.iframe)}catch(e){o.onError("jsonp polling iframe removal error",e)}try{var e='