text: 'use strict';
  
  function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
  
  var reducer = require('./reducer-ee2dd9d3.js');
  var base = require('./base-bdd9c13b.js');
  var master = require('./master-b738b477.js');
  var io = _interopDefault(require('socket.io-client'));
  
  /*
   * 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.
   */
  /**
   * InMemory data storage.
   */
  class InMemory extends base.Sync {
      /**
       * Creates a new InMemory storage.
       */
      constructor() {
          super();
          this.state = new Map();
          this.initial = new Map();
          this.metadata = new Map();
          this.log = new Map();
      }
      /**
       * Create a new game.
       */
      createGame(gameID, opts) {
          this.initial.set(gameID, opts.initialState);
          this.setState(gameID, opts.initialState);
          this.setMetadata(gameID, opts.metadata);
      }
      /**
       * Write the game metadata to the in-memory object.
       */
      setMetadata(gameID, metadata) {
          this.metadata.set(gameID, metadata);
      }
      /**
       * Write the game state to the in-memory object.
       */
      setState(gameID, state, deltalog) {
          if (deltalog && deltalog.length > 0) {
              const log = this.log.get(gameID) || [];
              this.log.set(gameID, log.concat(deltalog));
          }
          this.state.set(gameID, state);
      }
      /**
       * Fetches state for a particular gameID.
       */
      fetch(gameID, opts) {
          let result = {};
          if (opts.state) {
              result.state = this.state.get(gameID);
          }
          if (opts.metadata) {
              result.metadata = this.metadata.get(gameID);
          }
          if (opts.log) {
              result.log = this.log.get(gameID) || [];
          }
          if (opts.initialState) {
              result.initialState = this.initial.get(gameID);
          }
          return result;
      }
      /**
       * Remove the game state from the in-memory object.
       */
      wipe(gameID) {
          this.state.delete(gameID);
          this.metadata.delete(gameID);
      }
      /**
       * Return all keys.
       */
      listGames(opts) {
          if (opts && opts.gameName !== undefined) {
              let gameIDs = [];
              this.metadata.forEach((metadata, gameID) => {
                  if (metadata.gameName === opts.gameName) {
                      gameIDs.push(gameID);
                  }
              });
              return gameIDs;
          }
          return [...this.metadata.keys()];
      }
  }
  
  /*
   * 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.
   */
  var Transport = function Transport(_ref) {
    var store = _ref.store,
        gameName = _ref.gameName,
        playerID = _ref.playerID,
        gameID = _ref.gameID,
        numPlayers = _ref.numPlayers;
  
    reducer._classCallCheck(this, Transport);
  
    this.store = store;
    this.gameName = gameName || 'default';
    this.playerID = playerID || null;
    this.gameID = gameID || 'default';
    this.numPlayers = numPlayers || 2;
  };
  
  /**
   * Returns null if it is not a bot's turn.
   * Otherwise, returns a playerID of a bot that may play now.
   */
  
  function GetBotPlayer(state, bots) {
    if (state.ctx.gameover !== undefined) {
      return null;
    }
  
    if (state.ctx.stage) {
      for (var _i = 0, _Object$keys = Object.keys(bots); _i < _Object$keys.length; _i++) {
        var key = _Object$keys[_i];
  
        if (key in state.ctx.stage) {
          return key;
        }
      }
    } else if (state.ctx.currentPlayer in bots) {
      return state.ctx.currentPlayer;
    }
  
    return null;
  }
  /**
   * Creates a local version of the master that the client
   * can interact with.
   */
  
  function LocalMaster(_ref) {
    var game = _ref.game,
        bots = _ref.bots;
    var clientCallbacks = {};
    var initializedBots = {};
  
    if (game && game.ai && bots) {
      for (var playerID in bots) {
        var bot = bots[playerID];
        initializedBots[playerID] = new bot({
          game: game,
          enumerate: game.ai.enumerate,
          seed: game.seed
        });
      }
    }
  
    var send = function send(_ref2) {
      var type = _ref2.type,
          playerID = _ref2.playerID,
          args = _ref2.args;
      var callback = clientCallbacks[playerID];
  
      if (callback !== undefined) {
        callback.apply(null, [type].concat(reducer._toConsumableArray(args)));
      }
    };
  
    var sendAll = function sendAll(arg) {
      for (var _playerID in clientCallbacks) {
        var _arg = arg(_playerID),
            type = _arg.type,
            args = _arg.args;
  
        send({
          type: type,
          playerID: _playerID,
          args: args
        });
      }
    };
  
    var master$1 = new master.Master(game, new InMemory(), {
      send: send,
      sendAll: sendAll
    }, false);
  
    master$1.connect = function (gameID, playerID, callback) {
      clientCallbacks[playerID] = callback;
    };
  
    master$1.subscribe(function (_ref3) {
      var state = _ref3.state,
          gameID = _ref3.gameID;
  
      if (!bots) {
        return;
      }
  
      var botPlayer = GetBotPlayer(state, initializedBots);
  
      if (botPlayer !== null) {
        setTimeout(async function () {
          var botAction = await initializedBots[botPlayer].play(state, botPlayer);
          await master$1.onUpdate(botAction.action, state._stateID, gameID, botAction.action.payload.playerID);
        }, 100);
      }
    });
    return master$1;
  }
  /**
   * Local
   *
   * Transport interface that embeds a GameMaster within it
   * that you can connect multiple clients to.
   */
  
  var LocalTransport =
  /*#__PURE__*/
  function (_Transport) {
    reducer._inherits(LocalTransport, _Transport);
  
    /**
     * Creates a new Mutiplayer instance.
     * @param {object} socket - Override for unit tests.
     * @param {object} socketOpts - Options to pass to socket.io.
     * @param {string} gameID - The game ID to connect to.
     * @param {string} playerID - The player ID associated with this client.
     * @param {string} gameName - The game type (the `name` field in `Game`).
     * @param {string} numPlayers - The number of players.
     * @param {string} server - The game server in the form of 'hostname:port'. Defaults to the server serving the client if not provided.
     */
    function LocalTransport(_ref4) {
      var _this;
  
      var master = _ref4.master,
          game = _ref4.game,
          store = _ref4.store,
          gameID = _ref4.gameID,
          playerID = _ref4.playerID,
          gameName = _ref4.gameName,
          numPlayers = _ref4.numPlayers;
  
      reducer._classCallCheck(this, LocalTransport);
  
      _this = reducer._possibleConstructorReturn(this, reducer._getPrototypeOf(LocalTransport).call(this, {
        store: store,
        gameName: gameName,
        playerID: playerID,
        gameID: gameID,
        numPlayers: numPlayers
      }));
      _this.master = master;
      _this.game = game;
      _this.isConnected = true;
      return _this;
    }
    /**
     * Called when another player makes a move and the
     * master broadcasts the update to other clients (including
     * this one).
     */
  
  
    reducer._createClass(LocalTransport, [{
      key: "onUpdate",
      value: async function onUpdate(gameID, state, deltalog) {
        var currentState = this.store.getState();
  
        if (gameID == this.gameID && state._stateID >= currentState._stateID) {
          var action = reducer.update(state, deltalog);
          this.store.dispatch(action);
        }
      }
      /**
       * Called when the client first connects to the master
       * and requests the current game state.
       */
  
    }, {
      key: "onSync",
      value: function onSync(gameID, syncInfo) {
        if (gameID == this.gameID) {
          var action = reducer.sync(syncInfo);
          this.store.dispatch(action);
        }
      }
      /**
       * Called when an action that has to be relayed to the
       * game master is made.
       */
  
    }, {
      key: "onAction",
      value: function onAction(state, action) {
        this.master.onUpdate(action, state._stateID, this.gameID, this.playerID);
      }
      /**
       * Connect to the master.
       */
  
    }, {
      key: "connect",
      value: function connect() {
        var _this2 = this;
  
        this.master.connect(this.gameID, this.playerID, function (type) {
          for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
            args[_key - 1] = arguments[_key];
          }
  
          if (type == 'sync') {
            _this2.onSync.apply(_this2, args);
          }
  
          if (type == 'update') {
            _this2.onUpdate.apply(_this2, args);
          }
        });
        this.master.onSync(this.gameID, this.playerID, this.numPlayers);
      }
      /**
       * Disconnect from the master.
       */
  
    }, {
      key: "disconnect",
      value: function disconnect() {}
      /**
       * Subscribe to connection state changes.
       */
  
    }, {
      key: "subscribe",
      value: function subscribe() {}
    }, {
      key: "subscribeGameMetadata",
      value: function subscribeGameMetadata(_metadata) {} // eslint-disable-line no-unused-vars
  
      /**
       * Updates the game id.
       * @param {string} id - The new game id.
       */
  
    }, {
      key: "updateGameID",
      value: function updateGameID(id) {
        this.gameID = id;
        var action = reducer.reset(null);
        this.store.dispatch(action);
        this.connect();
      }
      /**
       * Updates the player associated with this client.
       * @param {string} id - The new player id.
       */
  
    }, {
      key: "updatePlayerID",
      value: function updatePlayerID(id) {
        this.playerID = id;
        var action = reducer.reset(null);
        this.store.dispatch(action);
        this.connect();
      }
    }]);
  
    return LocalTransport;
  }(Transport);
  var localMasters = new Map();
  function Local(opts) {
    return function (transportOpts) {
      var master;
  
      if (localMasters.has(transportOpts.gameKey) & !opts) {
        master = localMasters.get(transportOpts.gameKey);
      } else {
        master = new LocalMaster({
          game: transportOpts.game,
          bots: opts && opts.bots
        });
        localMasters.set(transportOpts.gameKey, master);
      }
  
      return new LocalTransport(reducer._objectSpread2({
        master: master
      }, transportOpts));
    };
  }
  
  /**
   * SocketIO
   *
   * Transport interface that interacts with the Master via socket.io.
   */
  
  var SocketIOTransport =
  /*#__PURE__*/
  function (_Transport) {
    reducer._inherits(SocketIOTransport, _Transport);
  
    /**
     * Creates a new Mutiplayer instance.
     * @param {object} socket - Override for unit tests.
     * @param {object} socketOpts - Options to pass to socket.io.
     * @param {string} gameID - The game ID to connect to.
     * @param {string} playerID - The player ID associated with this client.
     * @param {string} gameName - The game type (the `name` field in `Game`).
     * @param {string} numPlayers - The number of players.
     * @param {string} server - The game server in the form of 'hostname:port'. Defaults to the server serving the client if not provided.
     */
    function SocketIOTransport() {
      var _this;
  
      var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
          socket = _ref.socket,
          socketOpts = _ref.socketOpts,
          store = _ref.store,
          gameID = _ref.gameID,
          playerID = _ref.playerID,
          gameName = _ref.gameName,
          numPlayers = _ref.numPlayers,
          server = _ref.server;
  
      reducer._classCallCheck(this, SocketIOTransport);
  
      _this = reducer._possibleConstructorReturn(this, reducer._getPrototypeOf(SocketIOTransport).call(this, {
        store: store,
        gameName: gameName,
        playerID: playerID,
        gameID: gameID,
        numPlayers: numPlayers
      }));
      _this.server = server;
      _this.socket = socket;
      _this.socketOpts = socketOpts;
      _this.isConnected = false;
  
      _this.callback = function () {};
  
      _this.gameMetadataCallback = function () {};
  
      return _this;
    }
    /**
     * Called when an action that has to be relayed to the
     * game master is made.
     */
  
  
    reducer._createClass(SocketIOTransport, [{
      key: "onAction",
      value: function onAction(state, action) {
        this.socket.emit('update', action, state._stateID, this.gameID, this.playerID);
      }
      /**
       * Connect to the server.
       */
  
    }, {
      key: "connect",
      value: function connect() {
        var _this2 = this;
  
        if (!this.socket) {
          if (this.server) {
            var server = this.server;
  
            if (server.search(/^https?:\/\//) == -1) {
              server = 'http://' + this.server;
            }
  
            if (server.substr(-1) != '/') {
              // add trailing slash if not already present
              server = server + '/';
            }
  
            this.socket = io(server + this.gameName, this.socketOpts);
          } else {
            this.socket = io('/' + this.gameName, this.socketOpts);
          }
        } // Called when another player makes a move and the
        // master broadcasts the update to other clients (including
        // this one).
  
  
        this.socket.on('update', function (gameID, state, deltalog) {
          var currentState = _this2.store.getState();
  
          if (gameID == _this2.gameID && state._stateID >= currentState._stateID) {
            var action = reducer.update(state, deltalog);
  
            _this2.store.dispatch(action);
          }
        }); // Called when the client first connects to the master
        // and requests the current game state.
  
        this.socket.on('sync', function (gameID, syncInfo) {
          if (gameID == _this2.gameID) {
            var action = reducer.sync(syncInfo);
  
            _this2.gameMetadataCallback(syncInfo.filteredMetadata);
  
            _this2.store.dispatch(action);
          }
        }); // Initial sync to get game state.
  
        this.socket.emit('sync', this.gameID, this.playerID, this.numPlayers); // Keep track of connection status.
  
        this.socket.on('connect', function () {
          _this2.isConnected = true;
  
          _this2.callback();
        });
        this.socket.on('disconnect', function () {
          _this2.isConnected = false;
  
          _this2.callback();
        });
      }
      /**
       * Disconnect from the server.
       */
  
    }, {
      key: "disconnect",
      value: function disconnect() {
        this.socket.close();
        this.socket = null;
        this.isConnected = false;
        this.callback();
      }
      /**
       * Subscribe to connection state changes.
       */
  
    }, {
      key: "subscribe",
      value: function subscribe(fn) {
        this.callback = fn;
      }
    }, {
      key: "subscribeGameMetadata",
      value: function subscribeGameMetadata(fn) {
        this.gameMetadataCallback = fn;
      }
      /**
       * Updates the game id.
       * @param {string} id - The new game id.
       */
  
    }, {
      key: "updateGameID",
      value: function updateGameID(id) {
        this.gameID = id;
        var action = reducer.reset(null);
        this.store.dispatch(action);
  
        if (this.socket) {
          this.socket.emit('sync', this.gameID, this.playerID, this.numPlayers);
        }
      }
      /**
       * Updates the player associated with this client.
       * @param {string} id - The new player id.
       */
  
    }, {
      key: "updatePlayerID",
      value: function updatePlayerID(id) {
        this.playerID = id;
        var action = reducer.reset(null);
        this.store.dispatch(action);
  
        if (this.socket) {
          this.socket.emit('sync', this.gameID, this.playerID, this.numPlayers);
        }
      }
    }]);
  
    return SocketIOTransport;
  }(Transport);
  function SocketIO() {
    var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
        server = _ref2.server,
        socketOpts = _ref2.socketOpts;
  
    return function (transportOpts) {
      return new SocketIOTransport(reducer._objectSpread2({
        server: server,
        socketOpts: socketOpts
      }, transportOpts));
    };
  }
  
  exports.Local = Local;
  exports.SocketIO = SocketIO;
  