@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.
Files changed (296) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +102 -0
  3. package/ai/package.json +7 -0
  4. package/client/package.json +7 -0
  5. package/core/package.json +7 -0
  6. package/debug/package.json +7 -0
  7. package/dist/boardgameio.es.js +14238 -0
  8. package/dist/boardgameio.js +14277 -0
  9. package/dist/boardgameio.min.js +16 -0
  10. package/dist/cjs/Debug-9d141c06.js +9586 -0
  11. package/dist/cjs/ai-e0e8a768.js +377 -0
  12. package/dist/cjs/ai.js +20 -0
  13. package/dist/cjs/client-76dec77b.js +258 -0
  14. package/dist/cjs/client-a22d7500.js +524 -0
  15. package/dist/cjs/client.js +26 -0
  16. package/dist/cjs/core.js +52 -0
  17. package/dist/cjs/debug.js +18 -0
  18. package/dist/cjs/filter-player-view-bb02e2f6.js +89 -0
  19. package/dist/cjs/initialize-267fcd69.js +61 -0
  20. package/dist/cjs/internal.js +25 -0
  21. package/dist/cjs/master-2904879d.js +320 -0
  22. package/dist/cjs/master.js +18 -0
  23. package/dist/cjs/multiplayer.js +23 -0
  24. package/dist/cjs/plugin-random-7425844d.js +229 -0
  25. package/dist/cjs/plugins.js +59 -0
  26. package/dist/cjs/react-native.js +182 -0
  27. package/dist/cjs/react.js +727 -0
  28. package/dist/cjs/reducer-16eec232.js +1203 -0
  29. package/dist/cjs/server.js +4087 -0
  30. package/dist/cjs/socketio-7a0837eb.js +478 -0
  31. package/dist/cjs/testing.js +30 -0
  32. package/dist/cjs/transport-b1874dfa.js +37 -0
  33. package/dist/cjs/turn-order-b2ff8740.js +1136 -0
  34. package/dist/cjs/util-fcfd8fb8.js +140 -0
  35. package/dist/esm/Debug-0141fe2d.js +9577 -0
  36. package/dist/esm/ai-5c06e761.js +371 -0
  37. package/dist/esm/ai.js +8 -0
  38. package/dist/esm/client-2e653027.js +522 -0
  39. package/dist/esm/client-5f57c3f2.js +255 -0
  40. package/dist/esm/client.js +16 -0
  41. package/dist/esm/core.js +40 -0
  42. package/dist/esm/debug.js +10 -0
  43. package/dist/esm/filter-player-view-2c6cc96f.js +87 -0
  44. package/dist/esm/initialize-11d626ca.js +59 -0
  45. package/dist/esm/internal.js +10 -0
  46. package/dist/esm/master-fa8f2e43.js +318 -0
  47. package/dist/esm/master.js +10 -0
  48. package/dist/esm/multiplayer.js +14 -0
  49. package/dist/esm/plugin-random-087f861e.js +226 -0
  50. package/dist/esm/plugins.js +55 -0
  51. package/dist/esm/react-native.js +173 -0
  52. package/dist/esm/react.js +716 -0
  53. package/dist/esm/reducer-c46da7e5.js +1198 -0
  54. package/dist/esm/socketio-c22ffa65.js +455 -0
  55. package/dist/esm/testing.js +26 -0
  56. package/dist/esm/transport-ce07b771.js +35 -0
  57. package/dist/esm/turn-order-376d315e.js +1091 -0
  58. package/dist/esm/util-b6147cef.js +135 -0
  59. package/dist/types/packages/ai.d.ts +5 -0
  60. package/dist/types/packages/client.d.ts +3 -0
  61. package/dist/types/packages/core.d.ts +5 -0
  62. package/dist/types/packages/debug.d.ts +2 -0
  63. package/dist/types/packages/internal.d.ts +8 -0
  64. package/dist/types/packages/master.d.ts +2 -0
  65. package/dist/types/packages/multiplayer.d.ts +3 -0
  66. package/dist/types/packages/plugins.d.ts +3 -0
  67. package/dist/types/packages/react-native.d.ts +2 -0
  68. package/dist/types/packages/react.d.ts +3 -0
  69. package/dist/types/packages/server.d.ts +6 -0
  70. package/dist/types/packages/testing.d.ts +1 -0
  71. package/dist/types/src/ai/ai.d.ts +53 -0
  72. package/dist/types/src/ai/ai.test.d.ts +1 -0
  73. package/dist/types/src/ai/bot.d.ts +40 -0
  74. package/dist/types/src/ai/mcts-bot.d.ts +60 -0
  75. package/dist/types/src/ai/random-bot.d.ts +27 -0
  76. package/dist/types/src/client/client.d.ts +104 -0
  77. package/dist/types/src/client/client.test.d.ts +1 -0
  78. package/dist/types/src/client/debug/tests/debug.test.d.ts +1 -0
  79. package/dist/types/src/client/manager.d.ts +61 -0
  80. package/dist/types/src/client/react.d.ts +75 -0
  81. package/dist/types/src/client/react.ssr.test.d.ts +4 -0
  82. package/dist/types/src/client/react.test.d.ts +1 -0
  83. package/dist/types/src/client/transport/dummy.d.ts +18 -0
  84. package/dist/types/src/client/transport/local.d.ts +59 -0
  85. package/dist/types/src/client/transport/local.test.d.ts +1 -0
  86. package/dist/types/src/client/transport/socketio.d.ts +45 -0
  87. package/dist/types/src/client/transport/socketio.test.d.ts +1 -0
  88. package/dist/types/src/client/transport/transport.d.ts +50 -0
  89. package/dist/types/src/client/transport/transport.test.d.ts +1 -0
  90. package/dist/types/src/core/action-creators.d.ts +144 -0
  91. package/dist/types/src/core/action-types.d.ts +10 -0
  92. package/dist/types/src/core/backwards-compatibility.d.ts +12 -0
  93. package/dist/types/src/core/constants.d.ts +6 -0
  94. package/dist/types/src/core/errors.d.ts +15 -0
  95. package/dist/types/src/core/flow.d.ts +28 -0
  96. package/dist/types/src/core/flow.test.d.ts +1 -0
  97. package/dist/types/src/core/game-methods.d.ts +9 -0
  98. package/dist/types/src/core/game.d.ts +26 -0
  99. package/dist/types/src/core/game.test.d.ts +1 -0
  100. package/dist/types/src/core/initialize.d.ts +9 -0
  101. package/dist/types/src/core/logger.d.ts +2 -0
  102. package/dist/types/src/core/player-view.d.ts +7 -0
  103. package/dist/types/src/core/player-view.test.d.ts +1 -0
  104. package/dist/types/src/core/reducer.d.ts +155 -0
  105. package/dist/types/src/core/reducer.test.d.ts +1 -0
  106. package/dist/types/src/core/turn-order.d.ts +179 -0
  107. package/dist/types/src/core/turn-order.test.d.ts +8 -0
  108. package/dist/types/src/lobby/client.d.ts +194 -0
  109. package/dist/types/src/lobby/client.test.d.ts +1 -0
  110. package/dist/types/src/lobby/connection.d.ts +44 -0
  111. package/dist/types/src/lobby/connection.test.d.ts +1 -0
  112. package/dist/types/src/lobby/create-match-form.d.ts +26 -0
  113. package/dist/types/src/lobby/login-form.d.ts +23 -0
  114. package/dist/types/src/lobby/match-instance.d.ts +31 -0
  115. package/dist/types/src/lobby/react.d.ts +113 -0
  116. package/dist/types/src/lobby/react.ssr.test.d.ts +4 -0
  117. package/dist/types/src/lobby/react.test.d.ts +1 -0
  118. package/dist/types/src/master/filter-player-view.d.ts +96 -0
  119. package/dist/types/src/master/filter-player-view.test.d.ts +1 -0
  120. package/dist/types/src/master/master.d.ts +94 -0
  121. package/dist/types/src/master/master.test.d.ts +1 -0
  122. package/dist/types/src/plugins/events/events.d.ts +54 -0
  123. package/dist/types/src/plugins/events/events.test.d.ts +1 -0
  124. package/dist/types/src/plugins/main.d.ts +75 -0
  125. package/dist/types/src/plugins/main.test.d.ts +1 -0
  126. package/dist/types/src/plugins/plugin-events.d.ts +5 -0
  127. package/dist/types/src/plugins/plugin-immer.d.ts +7 -0
  128. package/dist/types/src/plugins/plugin-immer.test.d.ts +1 -0
  129. package/dist/types/src/plugins/plugin-log.d.ts +14 -0
  130. package/dist/types/src/plugins/plugin-log.test.d.ts +1 -0
  131. package/dist/types/src/plugins/plugin-player.d.ts +29 -0
  132. package/dist/types/src/plugins/plugin-player.test.d.ts +1 -0
  133. package/dist/types/src/plugins/plugin-random.d.ts +4 -0
  134. package/dist/types/src/plugins/plugin-serializable.d.ts +7 -0
  135. package/dist/types/src/plugins/plugin-serializable.test.d.ts +1 -0
  136. package/dist/types/src/plugins/random/random.alea.d.ts +19 -0
  137. package/dist/types/src/plugins/random/random.d.ts +54 -0
  138. package/dist/types/src/plugins/random/random.test.d.ts +1 -0
  139. package/dist/types/src/server/api.d.ts +13 -0
  140. package/dist/types/src/server/api.test.d.ts +1 -0
  141. package/dist/types/src/server/auth.d.ts +38 -0
  142. package/dist/types/src/server/auth.test.d.ts +1 -0
  143. package/dist/types/src/server/cors.d.ts +4 -0
  144. package/dist/types/src/server/cors.test.d.ts +1 -0
  145. package/dist/types/src/server/db/base.d.ts +192 -0
  146. package/dist/types/src/server/db/flatfile.d.ts +44 -0
  147. package/dist/types/src/server/db/flatfile.test.d.ts +1 -0
  148. package/dist/types/src/server/db/index.d.ts +4 -0
  149. package/dist/types/src/server/db/index.test.d.ts +1 -0
  150. package/dist/types/src/server/db/inmemory.d.ts +43 -0
  151. package/dist/types/src/server/db/inmemory.test.d.ts +1 -0
  152. package/dist/types/src/server/db/localstorage.d.ts +7 -0
  153. package/dist/types/src/server/db/localstorage.test.d.ts +1 -0
  154. package/dist/types/src/server/index.d.ts +68 -0
  155. package/dist/types/src/server/index.test.d.ts +1 -0
  156. package/dist/types/src/server/transport/pubsub/generic-pub-sub.d.ts +6 -0
  157. package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.d.ts +7 -0
  158. package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.test.d.ts +1 -0
  159. package/dist/types/src/server/transport/socketio-simultaneous.test.d.ts +1 -0
  160. package/dist/types/src/server/transport/socketio.d.ts +65 -0
  161. package/dist/types/src/server/transport/socketio.test.d.ts +1 -0
  162. package/dist/types/src/server/util.d.ts +35 -0
  163. package/dist/types/src/testing/mock-random.d.ts +15 -0
  164. package/dist/types/src/testing/mock-random.test.d.ts +1 -0
  165. package/dist/types/src/types.d.ts +387 -0
  166. package/internal/package.json +7 -0
  167. package/master/package.json +7 -0
  168. package/multiplayer/package.json +7 -0
  169. package/package.json +211 -0
  170. package/plugins/package.json +7 -0
  171. package/react/package.json +7 -0
  172. package/react-native/package.json +7 -0
  173. package/server/package.json +6 -0
  174. package/src/ai/ai.test.ts +433 -0
  175. package/src/ai/ai.ts +84 -0
  176. package/src/ai/bot.ts +122 -0
  177. package/src/ai/mcts-bot.ts +331 -0
  178. package/src/ai/random-bot.ts +20 -0
  179. package/src/client/client.test.ts +993 -0
  180. package/src/client/client.ts +588 -0
  181. package/src/client/debug/Debug.svelte +239 -0
  182. package/src/client/debug/Menu.svelte +65 -0
  183. package/src/client/debug/ai/AI.svelte +215 -0
  184. package/src/client/debug/ai/Options.svelte +48 -0
  185. package/src/client/debug/info/Info.svelte +22 -0
  186. package/src/client/debug/info/Item.svelte +24 -0
  187. package/src/client/debug/log/Log.svelte +157 -0
  188. package/src/client/debug/log/LogEvent.svelte +149 -0
  189. package/src/client/debug/log/LogMetadata.svelte +7 -0
  190. package/src/client/debug/log/PhaseMarker.svelte +27 -0
  191. package/src/client/debug/log/TurnMarker.svelte +23 -0
  192. package/src/client/debug/main/ClientSwitcher.svelte +59 -0
  193. package/src/client/debug/main/Controls.svelte +58 -0
  194. package/src/client/debug/main/Hotkey.svelte +84 -0
  195. package/src/client/debug/main/InteractiveFunction.svelte +85 -0
  196. package/src/client/debug/main/Main.svelte +121 -0
  197. package/src/client/debug/main/Move.svelte +68 -0
  198. package/src/client/debug/main/PlayerInfo.svelte +70 -0
  199. package/src/client/debug/mcts/Action.svelte +22 -0
  200. package/src/client/debug/mcts/MCTS.svelte +78 -0
  201. package/src/client/debug/mcts/Table.svelte +98 -0
  202. package/src/client/debug/tests/JSONTree.mock.svelte +3 -0
  203. package/src/client/debug/tests/debug.test.ts +183 -0
  204. package/src/client/debug/utils/shortcuts.js +50 -0
  205. package/src/client/debug/utils/shortcuts.test.js +49 -0
  206. package/src/client/manager.ts +177 -0
  207. package/src/client/react-native.js +136 -0
  208. package/src/client/react-native.test.js +229 -0
  209. package/src/client/react.ssr.test.tsx +24 -0
  210. package/src/client/react.test.tsx +213 -0
  211. package/src/client/react.tsx +192 -0
  212. package/src/client/transport/dummy.ts +19 -0
  213. package/src/client/transport/local.test.ts +353 -0
  214. package/src/client/transport/local.ts +230 -0
  215. package/src/client/transport/socketio.test.ts +328 -0
  216. package/src/client/transport/socketio.ts +210 -0
  217. package/src/client/transport/transport.test.ts +27 -0
  218. package/src/client/transport/transport.ts +95 -0
  219. package/src/core/action-creators.ts +159 -0
  220. package/src/core/action-types.ts +18 -0
  221. package/src/core/backwards-compatibility.ts +23 -0
  222. package/src/core/constants.ts +6 -0
  223. package/src/core/errors.ts +35 -0
  224. package/src/core/flow.test.ts +2433 -0
  225. package/src/core/flow.ts +897 -0
  226. package/src/core/game-methods.ts +9 -0
  227. package/src/core/game.test.ts +286 -0
  228. package/src/core/game.ts +114 -0
  229. package/src/core/initialize.ts +77 -0
  230. package/src/core/logger.test.js +90 -0
  231. package/src/core/logger.ts +18 -0
  232. package/src/core/player-view.test.ts +50 -0
  233. package/src/core/player-view.ts +39 -0
  234. package/src/core/reducer.test.ts +991 -0
  235. package/src/core/reducer.ts +532 -0
  236. package/src/core/turn-order.test.ts +1123 -0
  237. package/src/core/turn-order.ts +473 -0
  238. package/src/lobby/client.test.ts +385 -0
  239. package/src/lobby/client.ts +358 -0
  240. package/src/lobby/connection.test.ts +207 -0
  241. package/src/lobby/connection.ts +162 -0
  242. package/src/lobby/create-match-form.tsx +122 -0
  243. package/src/lobby/login-form.tsx +75 -0
  244. package/src/lobby/match-instance.tsx +135 -0
  245. package/src/lobby/react.ssr.test.tsx +22 -0
  246. package/src/lobby/react.test.tsx +594 -0
  247. package/src/lobby/react.tsx +402 -0
  248. package/src/master/filter-player-view.test.ts +381 -0
  249. package/src/master/filter-player-view.ts +102 -0
  250. package/src/master/master.test.ts +1068 -0
  251. package/src/master/master.ts +492 -0
  252. package/src/plugins/events/events.test.ts +108 -0
  253. package/src/plugins/events/events.ts +209 -0
  254. package/src/plugins/main.test.ts +411 -0
  255. package/src/plugins/main.ts +314 -0
  256. package/src/plugins/plugin-events.ts +40 -0
  257. package/src/plugins/plugin-immer.test.ts +86 -0
  258. package/src/plugins/plugin-immer.ts +37 -0
  259. package/src/plugins/plugin-log.test.ts +37 -0
  260. package/src/plugins/plugin-log.ts +40 -0
  261. package/src/plugins/plugin-player.test.ts +172 -0
  262. package/src/plugins/plugin-player.ts +100 -0
  263. package/src/plugins/plugin-random.ts +40 -0
  264. package/src/plugins/plugin-serializable.test.ts +40 -0
  265. package/src/plugins/plugin-serializable.ts +55 -0
  266. package/src/plugins/random/random.alea.ts +109 -0
  267. package/src/plugins/random/random.test.ts +167 -0
  268. package/src/plugins/random/random.ts +198 -0
  269. package/src/server/api.test.ts +1699 -0
  270. package/src/server/api.ts +527 -0
  271. package/src/server/auth.test.ts +275 -0
  272. package/src/server/auth.ts +89 -0
  273. package/src/server/cors.test.ts +121 -0
  274. package/src/server/cors.ts +7 -0
  275. package/src/server/db/base.ts +296 -0
  276. package/src/server/db/flatfile.test.ts +221 -0
  277. package/src/server/db/flatfile.ts +228 -0
  278. package/src/server/db/index.test.ts +8 -0
  279. package/src/server/db/index.ts +12 -0
  280. package/src/server/db/inmemory.test.ts +143 -0
  281. package/src/server/db/inmemory.ts +143 -0
  282. package/src/server/db/localstorage.test.ts +73 -0
  283. package/src/server/db/localstorage.ts +44 -0
  284. package/src/server/index.test.ts +265 -0
  285. package/src/server/index.ts +175 -0
  286. package/src/server/transport/pubsub/generic-pub-sub.ts +11 -0
  287. package/src/server/transport/pubsub/in-memory-pub-sub.test.ts +47 -0
  288. package/src/server/transport/pubsub/in-memory-pub-sub.ts +28 -0
  289. package/src/server/transport/socketio-simultaneous.test.ts +603 -0
  290. package/src/server/transport/socketio.test.ts +303 -0
  291. package/src/server/transport/socketio.ts +279 -0
  292. package/src/server/util.ts +85 -0
  293. package/src/testing/mock-random.test.ts +45 -0
  294. package/src/testing/mock-random.ts +27 -0
  295. package/src/types.ts +511 -0
  296. 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
+ }