@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,588 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2017 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
|
+
import { nanoid } from 'nanoid/non-secure';
|
|
10
|
+
import 'svelte';
|
|
11
|
+
import type { Dispatch, StoreEnhancer } from 'redux';
|
|
12
|
+
import { createStore, compose, applyMiddleware } from 'redux';
|
|
13
|
+
import * as Actions from '../core/action-types';
|
|
14
|
+
import * as ActionCreators from '../core/action-creators';
|
|
15
|
+
import { ProcessGameConfig } from '../core/game';
|
|
16
|
+
import type Debug from './debug/Debug.svelte';
|
|
17
|
+
import {
|
|
18
|
+
CreateGameReducer,
|
|
19
|
+
TransientHandlingMiddleware,
|
|
20
|
+
} from '../core/reducer';
|
|
21
|
+
import { InitializeGame } from '../core/initialize';
|
|
22
|
+
import { PlayerView } from '../plugins/main';
|
|
23
|
+
import type { Transport, TransportOpts } from './transport/transport';
|
|
24
|
+
import { DummyTransport } from './transport/dummy';
|
|
25
|
+
import { ClientManager } from './manager';
|
|
26
|
+
import type { TransportData } from '../master/master';
|
|
27
|
+
import type {
|
|
28
|
+
ActivePlayersArg,
|
|
29
|
+
ActionShape,
|
|
30
|
+
CredentialedActionShape,
|
|
31
|
+
FilteredMetadata,
|
|
32
|
+
Game,
|
|
33
|
+
LogEntry,
|
|
34
|
+
PlayerID,
|
|
35
|
+
Reducer,
|
|
36
|
+
State,
|
|
37
|
+
Store,
|
|
38
|
+
ChatMessage,
|
|
39
|
+
} from '../types';
|
|
40
|
+
|
|
41
|
+
type ClientAction =
|
|
42
|
+
| ActionShape.Reset
|
|
43
|
+
| ActionShape.Sync
|
|
44
|
+
| ActionShape.Update
|
|
45
|
+
| ActionShape.Patch;
|
|
46
|
+
type Action =
|
|
47
|
+
| CredentialedActionShape.Any
|
|
48
|
+
| ActionShape.StripTransients
|
|
49
|
+
| ClientAction;
|
|
50
|
+
|
|
51
|
+
export interface DebugOpt {
|
|
52
|
+
target?: HTMLElement;
|
|
53
|
+
impl?: typeof Debug;
|
|
54
|
+
collapseOnLoad?: boolean;
|
|
55
|
+
hideToggleButton?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Global client manager instance that all clients register with.
|
|
60
|
+
*/
|
|
61
|
+
const GlobalClientManager = new ClientManager();
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Standardise the passed playerID, using currentPlayer if appropriate.
|
|
65
|
+
*/
|
|
66
|
+
function assumedPlayerID(
|
|
67
|
+
playerID: PlayerID | null | undefined,
|
|
68
|
+
store: Store,
|
|
69
|
+
multiplayer?: unknown
|
|
70
|
+
): PlayerID {
|
|
71
|
+
// In singleplayer mode, if the client does not have a playerID
|
|
72
|
+
// associated with it, we attach the currentPlayer as playerID.
|
|
73
|
+
if (!multiplayer && (playerID === null || playerID === undefined)) {
|
|
74
|
+
const state = store.getState();
|
|
75
|
+
playerID = state.ctx.currentPlayer;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return playerID;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* createDispatchers
|
|
83
|
+
*
|
|
84
|
+
* Create action dispatcher wrappers with bound playerID and credentials
|
|
85
|
+
*/
|
|
86
|
+
function createDispatchers(
|
|
87
|
+
storeActionType: 'makeMove' | 'gameEvent' | 'plugin',
|
|
88
|
+
innerActionNames: string[],
|
|
89
|
+
store: Store,
|
|
90
|
+
playerID: PlayerID,
|
|
91
|
+
credentials: string,
|
|
92
|
+
multiplayer?: unknown
|
|
93
|
+
) {
|
|
94
|
+
const dispatchers: Record<string, (...args: any[]) => void> = {};
|
|
95
|
+
for (const name of innerActionNames) {
|
|
96
|
+
dispatchers[name] = (...args) => {
|
|
97
|
+
const action = ActionCreators[storeActionType](
|
|
98
|
+
name,
|
|
99
|
+
args,
|
|
100
|
+
assumedPlayerID(playerID, store, multiplayer),
|
|
101
|
+
credentials
|
|
102
|
+
);
|
|
103
|
+
store.dispatch(action);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return dispatchers;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Creates a set of dispatchers to make moves.
|
|
110
|
+
export const createMoveDispatchers = createDispatchers.bind(null, 'makeMove');
|
|
111
|
+
// Creates a set of dispatchers to dispatch game flow events.
|
|
112
|
+
export const createEventDispatchers = createDispatchers.bind(null, 'gameEvent');
|
|
113
|
+
// Creates a set of dispatchers to dispatch actions to plugins.
|
|
114
|
+
export const createPluginDispatchers = createDispatchers.bind(null, 'plugin');
|
|
115
|
+
|
|
116
|
+
export interface ClientOpts<
|
|
117
|
+
G extends any = any,
|
|
118
|
+
PluginAPIs extends Record<string, unknown> = Record<string, unknown>
|
|
119
|
+
> {
|
|
120
|
+
game: Game<G, PluginAPIs>;
|
|
121
|
+
debug?: DebugOpt | boolean;
|
|
122
|
+
numPlayers?: number;
|
|
123
|
+
multiplayer?: (opts: TransportOpts) => Transport;
|
|
124
|
+
matchID?: string;
|
|
125
|
+
playerID?: PlayerID;
|
|
126
|
+
credentials?: string;
|
|
127
|
+
enhancer?: StoreEnhancer;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type ClientState<G extends any = any> =
|
|
131
|
+
| null
|
|
132
|
+
| (State<G> & {
|
|
133
|
+
isActive: boolean;
|
|
134
|
+
isConnected: boolean;
|
|
135
|
+
log: LogEntry[];
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Implementation of Client (see below).
|
|
140
|
+
*/
|
|
141
|
+
export class _ClientImpl<
|
|
142
|
+
G extends any = any,
|
|
143
|
+
PluginAPIs extends Record<string, unknown> = Record<string, unknown>
|
|
144
|
+
> {
|
|
145
|
+
private gameStateOverride?: any;
|
|
146
|
+
private initialState: State<G>;
|
|
147
|
+
readonly multiplayer: (opts: TransportOpts) => Transport;
|
|
148
|
+
private reducer: Reducer;
|
|
149
|
+
private _running: boolean;
|
|
150
|
+
private subscribers: Record<string, (state: State<G> | null) => void>;
|
|
151
|
+
private transport: Transport;
|
|
152
|
+
private manager: ClientManager;
|
|
153
|
+
readonly debugOpt?: DebugOpt | boolean;
|
|
154
|
+
readonly game: ReturnType<typeof ProcessGameConfig>;
|
|
155
|
+
readonly store: Store;
|
|
156
|
+
log: State['deltalog'];
|
|
157
|
+
matchID: string;
|
|
158
|
+
playerID: PlayerID | null;
|
|
159
|
+
credentials: string;
|
|
160
|
+
matchData?: FilteredMetadata;
|
|
161
|
+
moves: Record<string, (...args: any[]) => void>;
|
|
162
|
+
events: {
|
|
163
|
+
endGame?: (gameover?: any) => void;
|
|
164
|
+
endPhase?: () => void;
|
|
165
|
+
endTurn?: (arg?: { next: PlayerID }) => void;
|
|
166
|
+
setPhase?: (newPhase: string) => void;
|
|
167
|
+
endStage?: () => void;
|
|
168
|
+
setStage?: (newStage: string) => void;
|
|
169
|
+
setActivePlayers?: (arg: ActivePlayersArg) => void;
|
|
170
|
+
};
|
|
171
|
+
plugins: Record<string, (...args: any[]) => void>;
|
|
172
|
+
reset: () => void;
|
|
173
|
+
undo: () => void;
|
|
174
|
+
redo: () => void;
|
|
175
|
+
sendChatMessage: (message: any) => void;
|
|
176
|
+
chatMessages: ChatMessage[];
|
|
177
|
+
|
|
178
|
+
constructor({
|
|
179
|
+
game,
|
|
180
|
+
debug,
|
|
181
|
+
numPlayers,
|
|
182
|
+
multiplayer,
|
|
183
|
+
matchID: matchID,
|
|
184
|
+
playerID,
|
|
185
|
+
credentials,
|
|
186
|
+
enhancer,
|
|
187
|
+
}: ClientOpts<G, PluginAPIs>) {
|
|
188
|
+
this.game = ProcessGameConfig(game);
|
|
189
|
+
this.playerID = playerID;
|
|
190
|
+
this.matchID = matchID || 'default';
|
|
191
|
+
this.credentials = credentials;
|
|
192
|
+
this.multiplayer = multiplayer;
|
|
193
|
+
this.debugOpt = debug;
|
|
194
|
+
this.manager = GlobalClientManager;
|
|
195
|
+
this.gameStateOverride = null;
|
|
196
|
+
this.subscribers = {};
|
|
197
|
+
this._running = false;
|
|
198
|
+
|
|
199
|
+
this.reducer = CreateGameReducer({
|
|
200
|
+
game: this.game,
|
|
201
|
+
isClient: multiplayer !== undefined,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
this.initialState = null;
|
|
205
|
+
if (!multiplayer) {
|
|
206
|
+
this.initialState = InitializeGame({ game: this.game, numPlayers });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
this.reset = () => {
|
|
210
|
+
this.store.dispatch(ActionCreators.reset(this.initialState));
|
|
211
|
+
};
|
|
212
|
+
this.undo = () => {
|
|
213
|
+
const undo = ActionCreators.undo(
|
|
214
|
+
assumedPlayerID(this.playerID, this.store, this.multiplayer),
|
|
215
|
+
this.credentials
|
|
216
|
+
);
|
|
217
|
+
this.store.dispatch(undo);
|
|
218
|
+
};
|
|
219
|
+
this.redo = () => {
|
|
220
|
+
const redo = ActionCreators.redo(
|
|
221
|
+
assumedPlayerID(this.playerID, this.store, this.multiplayer),
|
|
222
|
+
this.credentials
|
|
223
|
+
);
|
|
224
|
+
this.store.dispatch(redo);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
this.log = [];
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Middleware that manages the log object.
|
|
231
|
+
* Reducers generate deltalogs, which are log events
|
|
232
|
+
* that are the result of application of a single action.
|
|
233
|
+
* The master may also send back a deltalog or the entire
|
|
234
|
+
* log depending on the type of request.
|
|
235
|
+
* The middleware below takes care of all these cases while
|
|
236
|
+
* managing the log object.
|
|
237
|
+
*/
|
|
238
|
+
const LogMiddleware =
|
|
239
|
+
(store: Store) => (next: Dispatch<Action>) => (action: Action) => {
|
|
240
|
+
const result = next(action);
|
|
241
|
+
const state = store.getState();
|
|
242
|
+
|
|
243
|
+
switch (action.type) {
|
|
244
|
+
case Actions.MAKE_MOVE:
|
|
245
|
+
case Actions.GAME_EVENT:
|
|
246
|
+
case Actions.UNDO:
|
|
247
|
+
case Actions.REDO: {
|
|
248
|
+
const deltalog = state.deltalog;
|
|
249
|
+
this.log = [...this.log, ...deltalog];
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
case Actions.RESET: {
|
|
254
|
+
this.log = [];
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
case Actions.PATCH:
|
|
258
|
+
case Actions.UPDATE: {
|
|
259
|
+
let id = -1;
|
|
260
|
+
if (this.log.length > 0) {
|
|
261
|
+
id = this.log[this.log.length - 1]._stateID;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let deltalog = action.deltalog || [];
|
|
265
|
+
|
|
266
|
+
// Filter out actions that are already present
|
|
267
|
+
// in the current log. This may occur when the
|
|
268
|
+
// client adds an entry to the log followed by
|
|
269
|
+
// the update from the master here.
|
|
270
|
+
deltalog = deltalog.filter((l) => l._stateID > id);
|
|
271
|
+
|
|
272
|
+
this.log = [...this.log, ...deltalog];
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
case Actions.SYNC: {
|
|
277
|
+
this.initialState = action.initialState;
|
|
278
|
+
this.log = action.log || [];
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return result;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Middleware that intercepts actions and sends them to the master,
|
|
288
|
+
* which keeps the authoritative version of the state.
|
|
289
|
+
*/
|
|
290
|
+
const TransportMiddleware =
|
|
291
|
+
(store: Store) => (next: Dispatch<Action>) => (action: Action) => {
|
|
292
|
+
const baseState = store.getState();
|
|
293
|
+
const result = next(action);
|
|
294
|
+
|
|
295
|
+
if (
|
|
296
|
+
!('clientOnly' in action) &&
|
|
297
|
+
action.type !== Actions.STRIP_TRANSIENTS
|
|
298
|
+
) {
|
|
299
|
+
this.transport.sendAction(baseState, action);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return result;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Middleware that intercepts actions and invokes the subscription callback.
|
|
307
|
+
*/
|
|
308
|
+
const SubscriptionMiddleware =
|
|
309
|
+
() => (next: Dispatch<Action>) => (action: Action) => {
|
|
310
|
+
const result = next(action);
|
|
311
|
+
this.notifySubscribers();
|
|
312
|
+
return result;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const middleware = applyMiddleware(
|
|
316
|
+
TransientHandlingMiddleware,
|
|
317
|
+
SubscriptionMiddleware,
|
|
318
|
+
TransportMiddleware,
|
|
319
|
+
LogMiddleware
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
enhancer =
|
|
323
|
+
enhancer !== undefined ? compose(middleware, enhancer) : middleware;
|
|
324
|
+
|
|
325
|
+
this.store = createStore(this.reducer, this.initialState, enhancer);
|
|
326
|
+
|
|
327
|
+
if (!multiplayer) multiplayer = DummyTransport;
|
|
328
|
+
this.transport = multiplayer({
|
|
329
|
+
transportDataCallback: (data) => this.receiveTransportData(data),
|
|
330
|
+
gameKey: game,
|
|
331
|
+
game: this.game,
|
|
332
|
+
matchID,
|
|
333
|
+
playerID,
|
|
334
|
+
credentials,
|
|
335
|
+
gameName: this.game.name,
|
|
336
|
+
numPlayers,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
this.createDispatchers();
|
|
340
|
+
|
|
341
|
+
this.chatMessages = [];
|
|
342
|
+
this.sendChatMessage = (payload) => {
|
|
343
|
+
this.transport.sendChatMessage(this.matchID, {
|
|
344
|
+
id: nanoid(7),
|
|
345
|
+
sender: this.playerID,
|
|
346
|
+
payload: payload,
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Handle incoming match data from a multiplayer transport. */
|
|
352
|
+
private receiveMatchData(matchData: FilteredMetadata): void {
|
|
353
|
+
this.matchData = matchData;
|
|
354
|
+
this.notifySubscribers();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Handle an incoming chat message from a multiplayer transport. */
|
|
358
|
+
private receiveChatMessage(message: ChatMessage): void {
|
|
359
|
+
this.chatMessages = [...this.chatMessages, message];
|
|
360
|
+
this.notifySubscribers();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Handle all incoming updates from a multiplayer transport. */
|
|
364
|
+
private receiveTransportData(data: TransportData): void {
|
|
365
|
+
const [matchID] = data.args;
|
|
366
|
+
if (matchID !== this.matchID) return;
|
|
367
|
+
switch (data.type) {
|
|
368
|
+
case 'sync': {
|
|
369
|
+
const [, syncInfo] = data.args;
|
|
370
|
+
const action = ActionCreators.sync(syncInfo);
|
|
371
|
+
this.receiveMatchData(syncInfo.filteredMetadata);
|
|
372
|
+
this.store.dispatch(action);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
case 'update': {
|
|
376
|
+
const [, state, deltalog] = data.args;
|
|
377
|
+
const currentState = this.store.getState();
|
|
378
|
+
if (state._stateID >= currentState._stateID) {
|
|
379
|
+
const action = ActionCreators.update(state, deltalog);
|
|
380
|
+
this.store.dispatch(action);
|
|
381
|
+
}
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case 'patch': {
|
|
385
|
+
const [, prevStateID, stateID, patch, deltalog] = data.args;
|
|
386
|
+
const currentStateID = this.store.getState()._stateID;
|
|
387
|
+
if (prevStateID !== currentStateID) break;
|
|
388
|
+
const action = ActionCreators.patch(
|
|
389
|
+
prevStateID,
|
|
390
|
+
stateID,
|
|
391
|
+
patch,
|
|
392
|
+
deltalog
|
|
393
|
+
);
|
|
394
|
+
this.store.dispatch(action);
|
|
395
|
+
// Emit sync if patch apply failed.
|
|
396
|
+
if (this.store.getState()._stateID === currentStateID) {
|
|
397
|
+
this.transport.requestSync();
|
|
398
|
+
}
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
case 'matchData': {
|
|
402
|
+
const [, matchData] = data.args;
|
|
403
|
+
this.receiveMatchData(matchData);
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
case 'chat': {
|
|
407
|
+
const [, chatMessage] = data.args;
|
|
408
|
+
this.receiveChatMessage(chatMessage);
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private notifySubscribers() {
|
|
415
|
+
Object.values(this.subscribers).forEach((fn) => fn(this.getState()));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
overrideGameState(state: any) {
|
|
419
|
+
this.gameStateOverride = state;
|
|
420
|
+
this.notifySubscribers();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
start() {
|
|
424
|
+
this.transport.connect();
|
|
425
|
+
this._running = true;
|
|
426
|
+
this.manager.register(this);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
stop() {
|
|
430
|
+
this.transport.disconnect();
|
|
431
|
+
this._running = false;
|
|
432
|
+
this.manager.unregister(this);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
subscribe(fn: (state: ClientState<G>) => void) {
|
|
436
|
+
const id = Object.keys(this.subscribers).length;
|
|
437
|
+
this.subscribers[id] = fn;
|
|
438
|
+
this.transport.subscribeToConnectionStatus(() => this.notifySubscribers());
|
|
439
|
+
|
|
440
|
+
if (this._running || !this.multiplayer) {
|
|
441
|
+
fn(this.getState());
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Return a handle that allows the caller to unsubscribe.
|
|
445
|
+
return () => {
|
|
446
|
+
delete this.subscribers[id];
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
getInitialState() {
|
|
451
|
+
return this.initialState;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
getState(): ClientState<G> {
|
|
455
|
+
let state = this.store.getState();
|
|
456
|
+
|
|
457
|
+
if (this.gameStateOverride !== null) {
|
|
458
|
+
state = this.gameStateOverride;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// This is the state before a sync with the game master.
|
|
462
|
+
if (state === null) {
|
|
463
|
+
return state as null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// isActive.
|
|
467
|
+
|
|
468
|
+
let isActive = true;
|
|
469
|
+
|
|
470
|
+
const isPlayerActive = this.game.flow.isPlayerActive(
|
|
471
|
+
state.G,
|
|
472
|
+
state.ctx,
|
|
473
|
+
this.playerID
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
if (this.multiplayer && !isPlayerActive) {
|
|
477
|
+
isActive = false;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (
|
|
481
|
+
!this.multiplayer &&
|
|
482
|
+
this.playerID !== null &&
|
|
483
|
+
this.playerID !== undefined &&
|
|
484
|
+
!isPlayerActive
|
|
485
|
+
) {
|
|
486
|
+
isActive = false;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (state.ctx.gameover !== undefined) {
|
|
490
|
+
isActive = false;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Secrets are normally stripped on the server,
|
|
494
|
+
// but we also strip them here so that game developers
|
|
495
|
+
// can see their effects while prototyping.
|
|
496
|
+
// Do not strip again if this is a multiplayer game
|
|
497
|
+
// since the server has already stripped secret info. (issue #818)
|
|
498
|
+
if (!this.multiplayer) {
|
|
499
|
+
state = {
|
|
500
|
+
...state,
|
|
501
|
+
G: this.game.playerView({
|
|
502
|
+
G: state.G,
|
|
503
|
+
ctx: state.ctx,
|
|
504
|
+
playerID: this.playerID,
|
|
505
|
+
}),
|
|
506
|
+
plugins: PlayerView(state, this),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Combine into return value.
|
|
511
|
+
return {
|
|
512
|
+
...state,
|
|
513
|
+
log: this.log,
|
|
514
|
+
isActive,
|
|
515
|
+
isConnected: this.transport.isConnected,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private createDispatchers() {
|
|
520
|
+
this.moves = createMoveDispatchers(
|
|
521
|
+
this.game.moveNames,
|
|
522
|
+
this.store,
|
|
523
|
+
this.playerID,
|
|
524
|
+
this.credentials,
|
|
525
|
+
this.multiplayer
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
this.events = createEventDispatchers(
|
|
529
|
+
this.game.flow.enabledEventNames,
|
|
530
|
+
this.store,
|
|
531
|
+
this.playerID,
|
|
532
|
+
this.credentials,
|
|
533
|
+
this.multiplayer
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
this.plugins = createPluginDispatchers(
|
|
537
|
+
this.game.pluginNames,
|
|
538
|
+
this.store,
|
|
539
|
+
this.playerID,
|
|
540
|
+
this.credentials,
|
|
541
|
+
this.multiplayer
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
updatePlayerID(playerID: PlayerID | null) {
|
|
546
|
+
this.playerID = playerID;
|
|
547
|
+
this.createDispatchers();
|
|
548
|
+
this.transport.updatePlayerID(playerID);
|
|
549
|
+
this.notifySubscribers();
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
updateMatchID(matchID: string) {
|
|
553
|
+
this.matchID = matchID;
|
|
554
|
+
this.createDispatchers();
|
|
555
|
+
this.transport.updateMatchID(matchID);
|
|
556
|
+
this.notifySubscribers();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
updateCredentials(credentials: string) {
|
|
560
|
+
this.credentials = credentials;
|
|
561
|
+
this.createDispatchers();
|
|
562
|
+
this.transport.updateCredentials(credentials);
|
|
563
|
+
this.notifySubscribers();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Client
|
|
569
|
+
*
|
|
570
|
+
* boardgame.io JS client.
|
|
571
|
+
*
|
|
572
|
+
* @param {...object} game - The return value of `Game`.
|
|
573
|
+
* @param {...object} numPlayers - The number of players.
|
|
574
|
+
* @param {...object} multiplayer - Set to a falsy value or a transportFactory, e.g., SocketIO()
|
|
575
|
+
* @param {...object} matchID - The matchID that you want to connect to.
|
|
576
|
+
* @param {...object} playerID - The playerID associated with this client.
|
|
577
|
+
* @param {...string} credentials - The authentication credentials associated with this client.
|
|
578
|
+
*
|
|
579
|
+
* Returns:
|
|
580
|
+
* A JS object that provides an API to interact with the
|
|
581
|
+
* game by dispatching moves and events.
|
|
582
|
+
*/
|
|
583
|
+
export function Client<
|
|
584
|
+
G extends any = any,
|
|
585
|
+
PluginAPIs extends Record<string, unknown> = Record<string, unknown>
|
|
586
|
+
>(opts: ClientOpts<G, PluginAPIs>) {
|
|
587
|
+
return new _ClientImpl<G, PluginAPIs>(opts);
|
|
588
|
+
}
|