@mnbroatch/boardgame.io 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/ai/package.json +7 -0
- package/client/package.json +7 -0
- package/core/package.json +7 -0
- package/debug/package.json +7 -0
- package/dist/boardgameio.es.js +14238 -0
- package/dist/boardgameio.js +14277 -0
- package/dist/boardgameio.min.js +16 -0
- package/dist/cjs/Debug-9d141c06.js +9586 -0
- package/dist/cjs/ai-e0e8a768.js +377 -0
- package/dist/cjs/ai.js +20 -0
- package/dist/cjs/client-76dec77b.js +258 -0
- package/dist/cjs/client-a22d7500.js +524 -0
- package/dist/cjs/client.js +26 -0
- package/dist/cjs/core.js +52 -0
- package/dist/cjs/debug.js +18 -0
- package/dist/cjs/filter-player-view-bb02e2f6.js +89 -0
- package/dist/cjs/initialize-267fcd69.js +61 -0
- package/dist/cjs/internal.js +25 -0
- package/dist/cjs/master-2904879d.js +320 -0
- package/dist/cjs/master.js +18 -0
- package/dist/cjs/multiplayer.js +23 -0
- package/dist/cjs/plugin-random-7425844d.js +229 -0
- package/dist/cjs/plugins.js +59 -0
- package/dist/cjs/react-native.js +182 -0
- package/dist/cjs/react.js +727 -0
- package/dist/cjs/reducer-16eec232.js +1203 -0
- package/dist/cjs/server.js +4087 -0
- package/dist/cjs/socketio-7a0837eb.js +478 -0
- package/dist/cjs/testing.js +30 -0
- package/dist/cjs/transport-b1874dfa.js +37 -0
- package/dist/cjs/turn-order-b2ff8740.js +1136 -0
- package/dist/cjs/util-fcfd8fb8.js +140 -0
- package/dist/esm/Debug-0141fe2d.js +9577 -0
- package/dist/esm/ai-5c06e761.js +371 -0
- package/dist/esm/ai.js +8 -0
- package/dist/esm/client-2e653027.js +522 -0
- package/dist/esm/client-5f57c3f2.js +255 -0
- package/dist/esm/client.js +16 -0
- package/dist/esm/core.js +40 -0
- package/dist/esm/debug.js +10 -0
- package/dist/esm/filter-player-view-2c6cc96f.js +87 -0
- package/dist/esm/initialize-11d626ca.js +59 -0
- package/dist/esm/internal.js +10 -0
- package/dist/esm/master-fa8f2e43.js +318 -0
- package/dist/esm/master.js +10 -0
- package/dist/esm/multiplayer.js +14 -0
- package/dist/esm/plugin-random-087f861e.js +226 -0
- package/dist/esm/plugins.js +55 -0
- package/dist/esm/react-native.js +173 -0
- package/dist/esm/react.js +716 -0
- package/dist/esm/reducer-c46da7e5.js +1198 -0
- package/dist/esm/socketio-c22ffa65.js +455 -0
- package/dist/esm/testing.js +26 -0
- package/dist/esm/transport-ce07b771.js +35 -0
- package/dist/esm/turn-order-376d315e.js +1091 -0
- package/dist/esm/util-b6147cef.js +135 -0
- package/dist/types/packages/ai.d.ts +5 -0
- package/dist/types/packages/client.d.ts +3 -0
- package/dist/types/packages/core.d.ts +5 -0
- package/dist/types/packages/debug.d.ts +2 -0
- package/dist/types/packages/internal.d.ts +8 -0
- package/dist/types/packages/master.d.ts +2 -0
- package/dist/types/packages/multiplayer.d.ts +3 -0
- package/dist/types/packages/plugins.d.ts +3 -0
- package/dist/types/packages/react-native.d.ts +2 -0
- package/dist/types/packages/react.d.ts +3 -0
- package/dist/types/packages/server.d.ts +6 -0
- package/dist/types/packages/testing.d.ts +1 -0
- package/dist/types/src/ai/ai.d.ts +53 -0
- package/dist/types/src/ai/ai.test.d.ts +1 -0
- package/dist/types/src/ai/bot.d.ts +40 -0
- package/dist/types/src/ai/mcts-bot.d.ts +60 -0
- package/dist/types/src/ai/random-bot.d.ts +27 -0
- package/dist/types/src/client/client.d.ts +104 -0
- package/dist/types/src/client/client.test.d.ts +1 -0
- package/dist/types/src/client/debug/tests/debug.test.d.ts +1 -0
- package/dist/types/src/client/manager.d.ts +61 -0
- package/dist/types/src/client/react.d.ts +75 -0
- package/dist/types/src/client/react.ssr.test.d.ts +4 -0
- package/dist/types/src/client/react.test.d.ts +1 -0
- package/dist/types/src/client/transport/dummy.d.ts +18 -0
- package/dist/types/src/client/transport/local.d.ts +59 -0
- package/dist/types/src/client/transport/local.test.d.ts +1 -0
- package/dist/types/src/client/transport/socketio.d.ts +45 -0
- package/dist/types/src/client/transport/socketio.test.d.ts +1 -0
- package/dist/types/src/client/transport/transport.d.ts +50 -0
- package/dist/types/src/client/transport/transport.test.d.ts +1 -0
- package/dist/types/src/core/action-creators.d.ts +144 -0
- package/dist/types/src/core/action-types.d.ts +10 -0
- package/dist/types/src/core/backwards-compatibility.d.ts +12 -0
- package/dist/types/src/core/constants.d.ts +6 -0
- package/dist/types/src/core/errors.d.ts +15 -0
- package/dist/types/src/core/flow.d.ts +28 -0
- package/dist/types/src/core/flow.test.d.ts +1 -0
- package/dist/types/src/core/game-methods.d.ts +9 -0
- package/dist/types/src/core/game.d.ts +26 -0
- package/dist/types/src/core/game.test.d.ts +1 -0
- package/dist/types/src/core/initialize.d.ts +9 -0
- package/dist/types/src/core/logger.d.ts +2 -0
- package/dist/types/src/core/player-view.d.ts +7 -0
- package/dist/types/src/core/player-view.test.d.ts +1 -0
- package/dist/types/src/core/reducer.d.ts +155 -0
- package/dist/types/src/core/reducer.test.d.ts +1 -0
- package/dist/types/src/core/turn-order.d.ts +179 -0
- package/dist/types/src/core/turn-order.test.d.ts +8 -0
- package/dist/types/src/lobby/client.d.ts +194 -0
- package/dist/types/src/lobby/client.test.d.ts +1 -0
- package/dist/types/src/lobby/connection.d.ts +44 -0
- package/dist/types/src/lobby/connection.test.d.ts +1 -0
- package/dist/types/src/lobby/create-match-form.d.ts +26 -0
- package/dist/types/src/lobby/login-form.d.ts +23 -0
- package/dist/types/src/lobby/match-instance.d.ts +31 -0
- package/dist/types/src/lobby/react.d.ts +113 -0
- package/dist/types/src/lobby/react.ssr.test.d.ts +4 -0
- package/dist/types/src/lobby/react.test.d.ts +1 -0
- package/dist/types/src/master/filter-player-view.d.ts +96 -0
- package/dist/types/src/master/filter-player-view.test.d.ts +1 -0
- package/dist/types/src/master/master.d.ts +94 -0
- package/dist/types/src/master/master.test.d.ts +1 -0
- package/dist/types/src/plugins/events/events.d.ts +54 -0
- package/dist/types/src/plugins/events/events.test.d.ts +1 -0
- package/dist/types/src/plugins/main.d.ts +75 -0
- package/dist/types/src/plugins/main.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-events.d.ts +5 -0
- package/dist/types/src/plugins/plugin-immer.d.ts +7 -0
- package/dist/types/src/plugins/plugin-immer.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-log.d.ts +14 -0
- package/dist/types/src/plugins/plugin-log.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-player.d.ts +29 -0
- package/dist/types/src/plugins/plugin-player.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-random.d.ts +4 -0
- package/dist/types/src/plugins/plugin-serializable.d.ts +7 -0
- package/dist/types/src/plugins/plugin-serializable.test.d.ts +1 -0
- package/dist/types/src/plugins/random/random.alea.d.ts +19 -0
- package/dist/types/src/plugins/random/random.d.ts +54 -0
- package/dist/types/src/plugins/random/random.test.d.ts +1 -0
- package/dist/types/src/server/api.d.ts +13 -0
- package/dist/types/src/server/api.test.d.ts +1 -0
- package/dist/types/src/server/auth.d.ts +38 -0
- package/dist/types/src/server/auth.test.d.ts +1 -0
- package/dist/types/src/server/cors.d.ts +4 -0
- package/dist/types/src/server/cors.test.d.ts +1 -0
- package/dist/types/src/server/db/base.d.ts +192 -0
- package/dist/types/src/server/db/flatfile.d.ts +44 -0
- package/dist/types/src/server/db/flatfile.test.d.ts +1 -0
- package/dist/types/src/server/db/index.d.ts +4 -0
- package/dist/types/src/server/db/index.test.d.ts +1 -0
- package/dist/types/src/server/db/inmemory.d.ts +43 -0
- package/dist/types/src/server/db/inmemory.test.d.ts +1 -0
- package/dist/types/src/server/db/localstorage.d.ts +7 -0
- package/dist/types/src/server/db/localstorage.test.d.ts +1 -0
- package/dist/types/src/server/index.d.ts +68 -0
- package/dist/types/src/server/index.test.d.ts +1 -0
- package/dist/types/src/server/transport/pubsub/generic-pub-sub.d.ts +6 -0
- package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.d.ts +7 -0
- package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.test.d.ts +1 -0
- package/dist/types/src/server/transport/socketio-simultaneous.test.d.ts +1 -0
- package/dist/types/src/server/transport/socketio.d.ts +65 -0
- package/dist/types/src/server/transport/socketio.test.d.ts +1 -0
- package/dist/types/src/server/util.d.ts +35 -0
- package/dist/types/src/testing/mock-random.d.ts +15 -0
- package/dist/types/src/testing/mock-random.test.d.ts +1 -0
- package/dist/types/src/types.d.ts +387 -0
- package/internal/package.json +7 -0
- package/master/package.json +7 -0
- package/multiplayer/package.json +7 -0
- package/package.json +211 -0
- package/plugins/package.json +7 -0
- package/react/package.json +7 -0
- package/react-native/package.json +7 -0
- package/server/package.json +6 -0
- package/src/ai/ai.test.ts +433 -0
- package/src/ai/ai.ts +84 -0
- package/src/ai/bot.ts +122 -0
- package/src/ai/mcts-bot.ts +331 -0
- package/src/ai/random-bot.ts +20 -0
- package/src/client/client.test.ts +993 -0
- package/src/client/client.ts +588 -0
- package/src/client/debug/Debug.svelte +239 -0
- package/src/client/debug/Menu.svelte +65 -0
- package/src/client/debug/ai/AI.svelte +215 -0
- package/src/client/debug/ai/Options.svelte +48 -0
- package/src/client/debug/info/Info.svelte +22 -0
- package/src/client/debug/info/Item.svelte +24 -0
- package/src/client/debug/log/Log.svelte +157 -0
- package/src/client/debug/log/LogEvent.svelte +149 -0
- package/src/client/debug/log/LogMetadata.svelte +7 -0
- package/src/client/debug/log/PhaseMarker.svelte +27 -0
- package/src/client/debug/log/TurnMarker.svelte +23 -0
- package/src/client/debug/main/ClientSwitcher.svelte +59 -0
- package/src/client/debug/main/Controls.svelte +58 -0
- package/src/client/debug/main/Hotkey.svelte +84 -0
- package/src/client/debug/main/InteractiveFunction.svelte +85 -0
- package/src/client/debug/main/Main.svelte +121 -0
- package/src/client/debug/main/Move.svelte +68 -0
- package/src/client/debug/main/PlayerInfo.svelte +70 -0
- package/src/client/debug/mcts/Action.svelte +22 -0
- package/src/client/debug/mcts/MCTS.svelte +78 -0
- package/src/client/debug/mcts/Table.svelte +98 -0
- package/src/client/debug/tests/JSONTree.mock.svelte +3 -0
- package/src/client/debug/tests/debug.test.ts +183 -0
- package/src/client/debug/utils/shortcuts.js +50 -0
- package/src/client/debug/utils/shortcuts.test.js +49 -0
- package/src/client/manager.ts +177 -0
- package/src/client/react-native.js +136 -0
- package/src/client/react-native.test.js +229 -0
- package/src/client/react.ssr.test.tsx +24 -0
- package/src/client/react.test.tsx +213 -0
- package/src/client/react.tsx +192 -0
- package/src/client/transport/dummy.ts +19 -0
- package/src/client/transport/local.test.ts +353 -0
- package/src/client/transport/local.ts +230 -0
- package/src/client/transport/socketio.test.ts +328 -0
- package/src/client/transport/socketio.ts +210 -0
- package/src/client/transport/transport.test.ts +27 -0
- package/src/client/transport/transport.ts +95 -0
- package/src/core/action-creators.ts +159 -0
- package/src/core/action-types.ts +18 -0
- package/src/core/backwards-compatibility.ts +23 -0
- package/src/core/constants.ts +6 -0
- package/src/core/errors.ts +35 -0
- package/src/core/flow.test.ts +2433 -0
- package/src/core/flow.ts +897 -0
- package/src/core/game-methods.ts +9 -0
- package/src/core/game.test.ts +286 -0
- package/src/core/game.ts +114 -0
- package/src/core/initialize.ts +77 -0
- package/src/core/logger.test.js +90 -0
- package/src/core/logger.ts +18 -0
- package/src/core/player-view.test.ts +50 -0
- package/src/core/player-view.ts +39 -0
- package/src/core/reducer.test.ts +991 -0
- package/src/core/reducer.ts +532 -0
- package/src/core/turn-order.test.ts +1123 -0
- package/src/core/turn-order.ts +473 -0
- package/src/lobby/client.test.ts +385 -0
- package/src/lobby/client.ts +358 -0
- package/src/lobby/connection.test.ts +207 -0
- package/src/lobby/connection.ts +162 -0
- package/src/lobby/create-match-form.tsx +122 -0
- package/src/lobby/login-form.tsx +75 -0
- package/src/lobby/match-instance.tsx +135 -0
- package/src/lobby/react.ssr.test.tsx +22 -0
- package/src/lobby/react.test.tsx +594 -0
- package/src/lobby/react.tsx +402 -0
- package/src/master/filter-player-view.test.ts +381 -0
- package/src/master/filter-player-view.ts +102 -0
- package/src/master/master.test.ts +1068 -0
- package/src/master/master.ts +492 -0
- package/src/plugins/events/events.test.ts +108 -0
- package/src/plugins/events/events.ts +209 -0
- package/src/plugins/main.test.ts +411 -0
- package/src/plugins/main.ts +314 -0
- package/src/plugins/plugin-events.ts +40 -0
- package/src/plugins/plugin-immer.test.ts +86 -0
- package/src/plugins/plugin-immer.ts +37 -0
- package/src/plugins/plugin-log.test.ts +37 -0
- package/src/plugins/plugin-log.ts +40 -0
- package/src/plugins/plugin-player.test.ts +172 -0
- package/src/plugins/plugin-player.ts +100 -0
- package/src/plugins/plugin-random.ts +40 -0
- package/src/plugins/plugin-serializable.test.ts +40 -0
- package/src/plugins/plugin-serializable.ts +55 -0
- package/src/plugins/random/random.alea.ts +109 -0
- package/src/plugins/random/random.test.ts +167 -0
- package/src/plugins/random/random.ts +198 -0
- package/src/server/api.test.ts +1699 -0
- package/src/server/api.ts +527 -0
- package/src/server/auth.test.ts +275 -0
- package/src/server/auth.ts +89 -0
- package/src/server/cors.test.ts +121 -0
- package/src/server/cors.ts +7 -0
- package/src/server/db/base.ts +296 -0
- package/src/server/db/flatfile.test.ts +221 -0
- package/src/server/db/flatfile.ts +228 -0
- package/src/server/db/index.test.ts +8 -0
- package/src/server/db/index.ts +12 -0
- package/src/server/db/inmemory.test.ts +143 -0
- package/src/server/db/inmemory.ts +143 -0
- package/src/server/db/localstorage.test.ts +73 -0
- package/src/server/db/localstorage.ts +44 -0
- package/src/server/index.test.ts +265 -0
- package/src/server/index.ts +175 -0
- package/src/server/transport/pubsub/generic-pub-sub.ts +11 -0
- package/src/server/transport/pubsub/in-memory-pub-sub.test.ts +47 -0
- package/src/server/transport/pubsub/in-memory-pub-sub.ts +28 -0
- package/src/server/transport/socketio-simultaneous.test.ts +603 -0
- package/src/server/transport/socketio.test.ts +303 -0
- package/src/server/transport/socketio.ts +279 -0
- package/src/server/util.ts +85 -0
- package/src/testing/mock-random.test.ts +45 -0
- package/src/testing/mock-random.ts +27 -0
- package/src/types.ts +511 -0
- package/testing/package.json +7 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { P as ProcessGameConfig, C as CreateGameReducer, T as TransientHandlingMiddleware, I as IsLongFormMove } from './reducer-c46da7e5.js';
|
|
2
|
+
import { g as error, d as UNDO, c as REDO, M as MAKE_MOVE } from './turn-order-376d315e.js';
|
|
3
|
+
import { applyMiddleware, createStore } from 'redux';
|
|
4
|
+
import { i as isSynchronous, c as createMatch } from './util-b6147cef.js';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* Copyright 2018 The boardgame.io Authors
|
|
8
|
+
*
|
|
9
|
+
* Use of this source code is governed by a MIT-style
|
|
10
|
+
* license that can be found in the LICENSE file or at
|
|
11
|
+
* https://opensource.org/licenses/MIT.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Filter match data to get a player metadata object with credentials stripped.
|
|
15
|
+
*/
|
|
16
|
+
const filterMatchData = (matchData) => Object.values(matchData.players).map((player) => {
|
|
17
|
+
const { credentials, ...filteredData } = player;
|
|
18
|
+
return filteredData;
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Remove player credentials from action payload
|
|
22
|
+
*/
|
|
23
|
+
const stripCredentialsFromAction = (action) => {
|
|
24
|
+
const { credentials, ...payload } = action.payload;
|
|
25
|
+
return { ...action, payload };
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Master
|
|
29
|
+
*
|
|
30
|
+
* Class that runs the game and maintains the authoritative state.
|
|
31
|
+
* It uses the transportAPI to communicate with clients and the
|
|
32
|
+
* storageAPI to communicate with the database.
|
|
33
|
+
*/
|
|
34
|
+
class Master {
|
|
35
|
+
constructor(game, storageAPI, transportAPI, auth) {
|
|
36
|
+
this.game = ProcessGameConfig(game);
|
|
37
|
+
this.storageAPI = storageAPI;
|
|
38
|
+
this.transportAPI = transportAPI;
|
|
39
|
+
this.subscribeCallback = () => { };
|
|
40
|
+
this.auth = auth;
|
|
41
|
+
}
|
|
42
|
+
subscribe(fn) {
|
|
43
|
+
this.subscribeCallback = fn;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Called on each move / event made by the client.
|
|
47
|
+
* Computes the new value of the game state and returns it
|
|
48
|
+
* along with a deltalog.
|
|
49
|
+
*/
|
|
50
|
+
async onUpdate(credAction, stateID, matchID, playerID) {
|
|
51
|
+
if (!credAction || !credAction.payload) {
|
|
52
|
+
return { error: 'missing action or action payload' };
|
|
53
|
+
}
|
|
54
|
+
let metadata;
|
|
55
|
+
if (isSynchronous(this.storageAPI)) {
|
|
56
|
+
({ metadata } = this.storageAPI.fetch(matchID, { metadata: true }));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
({ metadata } = await this.storageAPI.fetch(matchID, { metadata: true }));
|
|
60
|
+
}
|
|
61
|
+
if (this.auth) {
|
|
62
|
+
const isAuthentic = await this.auth.authenticateCredentials({
|
|
63
|
+
playerID,
|
|
64
|
+
credentials: credAction.payload.credentials,
|
|
65
|
+
metadata,
|
|
66
|
+
});
|
|
67
|
+
if (!isAuthentic) {
|
|
68
|
+
return { error: 'unauthorized action' };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const action = stripCredentialsFromAction(credAction);
|
|
72
|
+
const key = matchID;
|
|
73
|
+
let state;
|
|
74
|
+
if (isSynchronous(this.storageAPI)) {
|
|
75
|
+
({ state } = this.storageAPI.fetch(key, { state: true }));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
({ state } = await this.storageAPI.fetch(key, { state: true }));
|
|
79
|
+
}
|
|
80
|
+
if (state === undefined) {
|
|
81
|
+
error(`game not found, matchID=[${key}]`);
|
|
82
|
+
return { error: 'game not found' };
|
|
83
|
+
}
|
|
84
|
+
if (state.ctx.gameover !== undefined) {
|
|
85
|
+
error(`game over - matchID=[${key}] - playerID=[${playerID}]` +
|
|
86
|
+
` - action[${action.payload.type}]`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const reducer = CreateGameReducer({
|
|
90
|
+
game: this.game,
|
|
91
|
+
});
|
|
92
|
+
const middleware = applyMiddleware(TransientHandlingMiddleware);
|
|
93
|
+
const store = createStore(reducer, state, middleware);
|
|
94
|
+
// Only allow UNDO / REDO if there is exactly one player
|
|
95
|
+
// that can make moves right now and the person doing the
|
|
96
|
+
// action is that player.
|
|
97
|
+
if (action.type == UNDO || action.type == REDO) {
|
|
98
|
+
const hasActivePlayers = state.ctx.activePlayers !== null;
|
|
99
|
+
const isCurrentPlayer = state.ctx.currentPlayer === playerID;
|
|
100
|
+
if (
|
|
101
|
+
// If activePlayers is empty, non-current players can’t undo.
|
|
102
|
+
(!hasActivePlayers && !isCurrentPlayer) ||
|
|
103
|
+
// If player is not active or multiple players are active, can’t undo.
|
|
104
|
+
(hasActivePlayers &&
|
|
105
|
+
(state.ctx.activePlayers[playerID] === undefined ||
|
|
106
|
+
Object.keys(state.ctx.activePlayers).length > 1))) {
|
|
107
|
+
error(`playerID=[${playerID}] cannot undo / redo right now`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Check whether the player is active.
|
|
112
|
+
if (!this.game.flow.isPlayerActive(state.G, state.ctx, playerID)) {
|
|
113
|
+
error(`player not active - playerID=[${playerID}]` +
|
|
114
|
+
` - action[${action.payload.type}]`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Get move for further checks
|
|
118
|
+
const move = action.type == MAKE_MOVE
|
|
119
|
+
? this.game.flow.getMove(state.ctx, action.payload.type, playerID)
|
|
120
|
+
: null;
|
|
121
|
+
// Check whether the player is allowed to make the move.
|
|
122
|
+
if (action.type == MAKE_MOVE && !move) {
|
|
123
|
+
error(`move not processed - canPlayerMakeMove=false - playerID=[${playerID}]` +
|
|
124
|
+
` - action[${action.payload.type}]`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Check if action's stateID is different than store's stateID
|
|
128
|
+
// and if move does not have ignoreStaleStateID truthy.
|
|
129
|
+
if (state._stateID !== stateID &&
|
|
130
|
+
!(move && IsLongFormMove(move) && move.ignoreStaleStateID)) {
|
|
131
|
+
error(`invalid stateID, was=[${stateID}], expected=[${state._stateID}]` +
|
|
132
|
+
` - playerID=[${playerID}] - action[${action.payload.type}]`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const prevState = store.getState();
|
|
136
|
+
// Update server's version of the store.
|
|
137
|
+
store.dispatch(action);
|
|
138
|
+
state = store.getState();
|
|
139
|
+
this.subscribeCallback({
|
|
140
|
+
state,
|
|
141
|
+
action,
|
|
142
|
+
matchID,
|
|
143
|
+
});
|
|
144
|
+
if (this.game.deltaState) {
|
|
145
|
+
this.transportAPI.sendAll({
|
|
146
|
+
type: 'patch',
|
|
147
|
+
args: [matchID, stateID, prevState, state],
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
this.transportAPI.sendAll({
|
|
152
|
+
type: 'update',
|
|
153
|
+
args: [matchID, state],
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const { deltalog, ...stateWithoutDeltalog } = state;
|
|
157
|
+
let newMetadata;
|
|
158
|
+
if (metadata &&
|
|
159
|
+
(metadata.gameover === undefined || metadata.gameover === null)) {
|
|
160
|
+
newMetadata = {
|
|
161
|
+
...metadata,
|
|
162
|
+
updatedAt: Date.now(),
|
|
163
|
+
};
|
|
164
|
+
if (state.ctx.gameover !== undefined) {
|
|
165
|
+
newMetadata.gameover = state.ctx.gameover;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (isSynchronous(this.storageAPI)) {
|
|
169
|
+
this.storageAPI.setState(key, stateWithoutDeltalog, deltalog);
|
|
170
|
+
if (newMetadata)
|
|
171
|
+
this.storageAPI.setMetadata(key, newMetadata);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const writes = [
|
|
175
|
+
this.storageAPI.setState(key, stateWithoutDeltalog, deltalog),
|
|
176
|
+
];
|
|
177
|
+
if (newMetadata) {
|
|
178
|
+
writes.push(this.storageAPI.setMetadata(key, newMetadata));
|
|
179
|
+
}
|
|
180
|
+
await Promise.all(writes);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Called when the client connects / reconnects.
|
|
185
|
+
* Returns the latest game state and the entire log.
|
|
186
|
+
*/
|
|
187
|
+
async onSync(matchID, playerID, credentials, numPlayers = 2) {
|
|
188
|
+
const key = matchID;
|
|
189
|
+
const fetchOpts = {
|
|
190
|
+
state: true,
|
|
191
|
+
metadata: true,
|
|
192
|
+
log: true,
|
|
193
|
+
initialState: true,
|
|
194
|
+
};
|
|
195
|
+
const fetchResult = isSynchronous(this.storageAPI)
|
|
196
|
+
? this.storageAPI.fetch(key, fetchOpts)
|
|
197
|
+
: await this.storageAPI.fetch(key, fetchOpts);
|
|
198
|
+
let { state, initialState, log, metadata } = fetchResult;
|
|
199
|
+
if (this.auth && playerID !== undefined && playerID !== null) {
|
|
200
|
+
const isAuthentic = await this.auth.authenticateCredentials({
|
|
201
|
+
playerID,
|
|
202
|
+
credentials,
|
|
203
|
+
metadata,
|
|
204
|
+
});
|
|
205
|
+
if (!isAuthentic) {
|
|
206
|
+
return { error: 'unauthorized' };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// If the game doesn't exist, then create one on demand.
|
|
210
|
+
// TODO: Move this out of the sync call.
|
|
211
|
+
if (state === undefined) {
|
|
212
|
+
const match = createMatch({
|
|
213
|
+
game: this.game,
|
|
214
|
+
unlisted: true,
|
|
215
|
+
numPlayers,
|
|
216
|
+
setupData: undefined,
|
|
217
|
+
});
|
|
218
|
+
if ('setupDataError' in match) {
|
|
219
|
+
return { error: 'game requires setupData' };
|
|
220
|
+
}
|
|
221
|
+
initialState = state = match.initialState;
|
|
222
|
+
metadata = match.metadata;
|
|
223
|
+
this.subscribeCallback({ state, matchID });
|
|
224
|
+
if (isSynchronous(this.storageAPI)) {
|
|
225
|
+
this.storageAPI.createMatch(key, { initialState, metadata });
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
await this.storageAPI.createMatch(key, { initialState, metadata });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const filteredMetadata = metadata ? filterMatchData(metadata) : undefined;
|
|
232
|
+
const syncInfo = {
|
|
233
|
+
state,
|
|
234
|
+
log,
|
|
235
|
+
filteredMetadata,
|
|
236
|
+
initialState,
|
|
237
|
+
};
|
|
238
|
+
this.transportAPI.send({
|
|
239
|
+
playerID,
|
|
240
|
+
type: 'sync',
|
|
241
|
+
args: [matchID, syncInfo],
|
|
242
|
+
});
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Called when a client connects or disconnects.
|
|
247
|
+
* Updates and sends out metadata to reflect the player’s connection status.
|
|
248
|
+
*/
|
|
249
|
+
async onConnectionChange(matchID, playerID, credentials, connected) {
|
|
250
|
+
const key = matchID;
|
|
251
|
+
// Ignore changes for clients without a playerID, e.g. spectators.
|
|
252
|
+
if (playerID === undefined || playerID === null) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
let metadata;
|
|
256
|
+
if (isSynchronous(this.storageAPI)) {
|
|
257
|
+
({ metadata } = this.storageAPI.fetch(key, { metadata: true }));
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
({ metadata } = await this.storageAPI.fetch(key, { metadata: true }));
|
|
261
|
+
}
|
|
262
|
+
if (metadata === undefined) {
|
|
263
|
+
error(`metadata not found for matchID=[${key}]`);
|
|
264
|
+
return { error: 'metadata not found' };
|
|
265
|
+
}
|
|
266
|
+
if (metadata.players[playerID] === undefined) {
|
|
267
|
+
error(`Player not in the match, matchID=[${key}] playerID=[${playerID}]`);
|
|
268
|
+
return { error: 'player not in the match' };
|
|
269
|
+
}
|
|
270
|
+
if (this.auth) {
|
|
271
|
+
const isAuthentic = await this.auth.authenticateCredentials({
|
|
272
|
+
playerID,
|
|
273
|
+
credentials,
|
|
274
|
+
metadata,
|
|
275
|
+
});
|
|
276
|
+
if (!isAuthentic) {
|
|
277
|
+
return { error: 'unauthorized' };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
metadata.players[playerID].isConnected = connected;
|
|
281
|
+
const filteredMetadata = filterMatchData(metadata);
|
|
282
|
+
this.transportAPI.sendAll({
|
|
283
|
+
type: 'matchData',
|
|
284
|
+
args: [matchID, filteredMetadata],
|
|
285
|
+
});
|
|
286
|
+
if (isSynchronous(this.storageAPI)) {
|
|
287
|
+
this.storageAPI.setMetadata(key, metadata);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
await this.storageAPI.setMetadata(key, metadata);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async onChatMessage(matchID, chatMessage, credentials) {
|
|
294
|
+
const key = matchID;
|
|
295
|
+
if (this.auth) {
|
|
296
|
+
const { metadata } = await this.storageAPI.fetch(key, {
|
|
297
|
+
metadata: true,
|
|
298
|
+
});
|
|
299
|
+
if (!(chatMessage && typeof chatMessage.sender === 'string')) {
|
|
300
|
+
return { error: 'unauthorized' };
|
|
301
|
+
}
|
|
302
|
+
const isAuthentic = await this.auth.authenticateCredentials({
|
|
303
|
+
playerID: chatMessage.sender,
|
|
304
|
+
credentials,
|
|
305
|
+
metadata,
|
|
306
|
+
});
|
|
307
|
+
if (!isAuthentic) {
|
|
308
|
+
return { error: 'unauthorized' };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
this.transportAPI.sendAll({
|
|
312
|
+
type: 'chat',
|
|
313
|
+
args: [matchID, chatMessage],
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export { Master as M };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { M as Master } from './master-fa8f2e43.js';
|
|
2
|
+
import './reducer-c46da7e5.js';
|
|
3
|
+
import './turn-order-376d315e.js';
|
|
4
|
+
import 'immer';
|
|
5
|
+
import './plugin-random-087f861e.js';
|
|
6
|
+
import 'lodash.isplainobject';
|
|
7
|
+
import 'rfc6902';
|
|
8
|
+
import 'redux';
|
|
9
|
+
import './util-b6147cef.js';
|
|
10
|
+
import './initialize-11d626ca.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { L as Local, S as SocketIO } from './socketio-c22ffa65.js';
|
|
2
|
+
import './util-b6147cef.js';
|
|
3
|
+
import './initialize-11d626ca.js';
|
|
4
|
+
import './reducer-c46da7e5.js';
|
|
5
|
+
import './turn-order-376d315e.js';
|
|
6
|
+
import 'immer';
|
|
7
|
+
import './plugin-random-087f861e.js';
|
|
8
|
+
import 'lodash.isplainobject';
|
|
9
|
+
import 'rfc6902';
|
|
10
|
+
import './master-fa8f2e43.js';
|
|
11
|
+
import 'redux';
|
|
12
|
+
import './transport-ce07b771.js';
|
|
13
|
+
import './filter-player-view-2c6cc96f.js';
|
|
14
|
+
import 'socket.io-client';
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Inlined version of Alea from https://github.com/davidbau/seedrandom.
|
|
2
|
+
// Converted to Typescript October 2020.
|
|
3
|
+
class Alea {
|
|
4
|
+
constructor(seed) {
|
|
5
|
+
const mash = Mash();
|
|
6
|
+
// Apply the seeding algorithm from Baagoe.
|
|
7
|
+
this.c = 1;
|
|
8
|
+
this.s0 = mash(' ');
|
|
9
|
+
this.s1 = mash(' ');
|
|
10
|
+
this.s2 = mash(' ');
|
|
11
|
+
this.s0 -= mash(seed);
|
|
12
|
+
if (this.s0 < 0) {
|
|
13
|
+
this.s0 += 1;
|
|
14
|
+
}
|
|
15
|
+
this.s1 -= mash(seed);
|
|
16
|
+
if (this.s1 < 0) {
|
|
17
|
+
this.s1 += 1;
|
|
18
|
+
}
|
|
19
|
+
this.s2 -= mash(seed);
|
|
20
|
+
if (this.s2 < 0) {
|
|
21
|
+
this.s2 += 1;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
next() {
|
|
25
|
+
const t = 2091639 * this.s0 + this.c * 2.3283064365386963e-10; // 2^-32
|
|
26
|
+
this.s0 = this.s1;
|
|
27
|
+
this.s1 = this.s2;
|
|
28
|
+
return (this.s2 = t - (this.c = Math.trunc(t)));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function Mash() {
|
|
32
|
+
let n = 0xefc8249d;
|
|
33
|
+
const mash = function (data) {
|
|
34
|
+
const str = data.toString();
|
|
35
|
+
for (let i = 0; i < str.length; i++) {
|
|
36
|
+
n += str.charCodeAt(i);
|
|
37
|
+
let h = 0.02519603282416938 * n;
|
|
38
|
+
n = h >>> 0;
|
|
39
|
+
h -= n;
|
|
40
|
+
h *= n;
|
|
41
|
+
n = h >>> 0;
|
|
42
|
+
h -= n;
|
|
43
|
+
n += h * 0x100000000; // 2^32
|
|
44
|
+
}
|
|
45
|
+
return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
|
|
46
|
+
};
|
|
47
|
+
return mash;
|
|
48
|
+
}
|
|
49
|
+
function copy(f, t) {
|
|
50
|
+
t.c = f.c;
|
|
51
|
+
t.s0 = f.s0;
|
|
52
|
+
t.s1 = f.s1;
|
|
53
|
+
t.s2 = f.s2;
|
|
54
|
+
return t;
|
|
55
|
+
}
|
|
56
|
+
function alea(seed, state) {
|
|
57
|
+
const xg = new Alea(seed);
|
|
58
|
+
const prng = xg.next.bind(xg);
|
|
59
|
+
if (state)
|
|
60
|
+
copy(state, xg);
|
|
61
|
+
prng.state = () => copy(xg, {});
|
|
62
|
+
return prng;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
* Copyright 2017 The boardgame.io Authors
|
|
67
|
+
*
|
|
68
|
+
* Use of this source code is governed by a MIT-style
|
|
69
|
+
* license that can be found in the LICENSE file or at
|
|
70
|
+
* https://opensource.org/licenses/MIT.
|
|
71
|
+
*/
|
|
72
|
+
/**
|
|
73
|
+
* Random
|
|
74
|
+
*
|
|
75
|
+
* Calls that require a pseudorandom number generator.
|
|
76
|
+
* Uses a seed from ctx, and also persists the PRNG
|
|
77
|
+
* state in ctx so that moves can stay pure.
|
|
78
|
+
*/
|
|
79
|
+
class Random {
|
|
80
|
+
/**
|
|
81
|
+
* constructor
|
|
82
|
+
* @param {object} ctx - The ctx object to initialize from.
|
|
83
|
+
*/
|
|
84
|
+
constructor(state) {
|
|
85
|
+
// If we are on the client, the seed is not present.
|
|
86
|
+
// Just use a temporary seed to execute the move without
|
|
87
|
+
// crashing it. The move state itself is discarded,
|
|
88
|
+
// so the actual value doesn't matter.
|
|
89
|
+
this.state = state || { seed: '0' };
|
|
90
|
+
this.used = false;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Generates a new seed from the current date / time.
|
|
94
|
+
*/
|
|
95
|
+
static seed() {
|
|
96
|
+
return Date.now().toString(36).slice(-10);
|
|
97
|
+
}
|
|
98
|
+
isUsed() {
|
|
99
|
+
return this.used;
|
|
100
|
+
}
|
|
101
|
+
getState() {
|
|
102
|
+
return this.state;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Generate a random number.
|
|
106
|
+
*/
|
|
107
|
+
_random() {
|
|
108
|
+
this.used = true;
|
|
109
|
+
const R = this.state;
|
|
110
|
+
const seed = R.prngstate ? '' : R.seed;
|
|
111
|
+
const rand = alea(seed, R.prngstate);
|
|
112
|
+
const number = rand();
|
|
113
|
+
this.state = {
|
|
114
|
+
...R,
|
|
115
|
+
prngstate: rand.state(),
|
|
116
|
+
};
|
|
117
|
+
return number;
|
|
118
|
+
}
|
|
119
|
+
api() {
|
|
120
|
+
const random = this._random.bind(this);
|
|
121
|
+
const SpotValue = {
|
|
122
|
+
D4: 4,
|
|
123
|
+
D6: 6,
|
|
124
|
+
D8: 8,
|
|
125
|
+
D10: 10,
|
|
126
|
+
D12: 12,
|
|
127
|
+
D20: 20,
|
|
128
|
+
};
|
|
129
|
+
// Generate functions for predefined dice values D4 - D20.
|
|
130
|
+
const predefined = {};
|
|
131
|
+
for (const key in SpotValue) {
|
|
132
|
+
const spotvalue = SpotValue[key];
|
|
133
|
+
predefined[key] = (diceCount) => {
|
|
134
|
+
return diceCount === undefined
|
|
135
|
+
? Math.floor(random() * spotvalue) + 1
|
|
136
|
+
: Array.from({ length: diceCount }).map(() => Math.floor(random() * spotvalue) + 1);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function Die(spotvalue = 6, diceCount) {
|
|
140
|
+
return diceCount === undefined
|
|
141
|
+
? Math.floor(random() * spotvalue) + 1
|
|
142
|
+
: Array.from({ length: diceCount }).map(() => Math.floor(random() * spotvalue) + 1);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
/**
|
|
146
|
+
* Similar to Die below, but with fixed spot values.
|
|
147
|
+
* Supports passing a diceCount
|
|
148
|
+
* if not defined, defaults to 1 and returns the value directly.
|
|
149
|
+
* if defined, returns an array containing the random dice values.
|
|
150
|
+
*
|
|
151
|
+
* D4: (diceCount) => value
|
|
152
|
+
* D6: (diceCount) => value
|
|
153
|
+
* D8: (diceCount) => value
|
|
154
|
+
* D10: (diceCount) => value
|
|
155
|
+
* D12: (diceCount) => value
|
|
156
|
+
* D20: (diceCount) => value
|
|
157
|
+
*/
|
|
158
|
+
...predefined,
|
|
159
|
+
/**
|
|
160
|
+
* Roll a die of specified spot value.
|
|
161
|
+
*
|
|
162
|
+
* @param {number} spotvalue - The die dimension (default: 6).
|
|
163
|
+
* @param {number} diceCount - number of dice to throw.
|
|
164
|
+
* if not defined, defaults to 1 and returns the value directly.
|
|
165
|
+
* if defined, returns an array containing the random dice values.
|
|
166
|
+
*/
|
|
167
|
+
Die,
|
|
168
|
+
/**
|
|
169
|
+
* Generate a random number between 0 and 1.
|
|
170
|
+
*/
|
|
171
|
+
Number: () => {
|
|
172
|
+
return random();
|
|
173
|
+
},
|
|
174
|
+
/**
|
|
175
|
+
* Shuffle an array.
|
|
176
|
+
*
|
|
177
|
+
* @param {Array} deck - The array to shuffle. Does not mutate
|
|
178
|
+
* the input, but returns the shuffled array.
|
|
179
|
+
*/
|
|
180
|
+
Shuffle: (deck) => {
|
|
181
|
+
const clone = [...deck];
|
|
182
|
+
let sourceIndex = deck.length;
|
|
183
|
+
let destinationIndex = 0;
|
|
184
|
+
const shuffled = Array.from({ length: sourceIndex });
|
|
185
|
+
while (sourceIndex) {
|
|
186
|
+
const randomIndex = Math.trunc(sourceIndex * random());
|
|
187
|
+
shuffled[destinationIndex++] = clone[randomIndex];
|
|
188
|
+
clone[randomIndex] = clone[--sourceIndex];
|
|
189
|
+
}
|
|
190
|
+
return shuffled;
|
|
191
|
+
},
|
|
192
|
+
_private: this,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/*
|
|
198
|
+
* Copyright 2018 The boardgame.io Authors
|
|
199
|
+
*
|
|
200
|
+
* Use of this source code is governed by a MIT-style
|
|
201
|
+
* license that can be found in the LICENSE file or at
|
|
202
|
+
* https://opensource.org/licenses/MIT.
|
|
203
|
+
*/
|
|
204
|
+
const RandomPlugin = {
|
|
205
|
+
name: 'random',
|
|
206
|
+
noClient: ({ api }) => {
|
|
207
|
+
return api._private.isUsed();
|
|
208
|
+
},
|
|
209
|
+
flush: ({ api }) => {
|
|
210
|
+
return api._private.getState();
|
|
211
|
+
},
|
|
212
|
+
api: ({ data }) => {
|
|
213
|
+
const random = new Random(data);
|
|
214
|
+
return random.api();
|
|
215
|
+
},
|
|
216
|
+
setup: ({ game }) => {
|
|
217
|
+
let { seed } = game;
|
|
218
|
+
if (seed === undefined) {
|
|
219
|
+
seed = Random.seed();
|
|
220
|
+
}
|
|
221
|
+
return { seed };
|
|
222
|
+
},
|
|
223
|
+
playerView: () => undefined,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export { RandomPlugin as R, alea as a };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2018 The boardgame.io Authors
|
|
3
|
+
*
|
|
4
|
+
* Use of this source code is governed by a MIT-style
|
|
5
|
+
* license that can be found in the LICENSE file or at
|
|
6
|
+
* https://opensource.org/licenses/MIT.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Plugin that maintains state for each player in G.players.
|
|
10
|
+
* During a turn, G.player will contain the object for the current player.
|
|
11
|
+
* In two player games, G.opponent will contain the object for the other player.
|
|
12
|
+
*
|
|
13
|
+
* @param {function} initPlayerState - Function of type (playerID) => playerState.
|
|
14
|
+
*/
|
|
15
|
+
const PlayerPlugin = ({ setup, playerView, } = {}) => ({
|
|
16
|
+
name: 'player',
|
|
17
|
+
flush: ({ api }) => {
|
|
18
|
+
return { players: api.state };
|
|
19
|
+
},
|
|
20
|
+
api: ({ ctx, data }) => {
|
|
21
|
+
const state = data.players;
|
|
22
|
+
const get = () => {
|
|
23
|
+
return data.players[ctx.currentPlayer];
|
|
24
|
+
};
|
|
25
|
+
const set = (value) => {
|
|
26
|
+
return (state[ctx.currentPlayer] = value);
|
|
27
|
+
};
|
|
28
|
+
const result = { state, get, set };
|
|
29
|
+
if (ctx.numPlayers === 2) {
|
|
30
|
+
const other = ctx.currentPlayer === '0' ? '1' : '0';
|
|
31
|
+
const get = () => {
|
|
32
|
+
return data.players[other];
|
|
33
|
+
};
|
|
34
|
+
const set = (value) => {
|
|
35
|
+
return (state[other] = value);
|
|
36
|
+
};
|
|
37
|
+
result.opponent = { get, set };
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
},
|
|
41
|
+
setup: ({ ctx }) => {
|
|
42
|
+
const players = {};
|
|
43
|
+
for (let i = 0; i < ctx.numPlayers; i++) {
|
|
44
|
+
let playerState = {};
|
|
45
|
+
if (setup !== undefined) {
|
|
46
|
+
playerState = setup(i + '');
|
|
47
|
+
}
|
|
48
|
+
players[i + ''] = playerState;
|
|
49
|
+
}
|
|
50
|
+
return { players };
|
|
51
|
+
},
|
|
52
|
+
playerView: ({ data, playerID }) => playerView ? { players: playerView(data.players, playerID) } : data,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export { PlayerPlugin as PluginPlayer };
|