@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,532 @@
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 * as Actions from './action-types';
10
+ import * as plugins from '../plugins/main';
11
+ import { ProcessGameConfig } from './game';
12
+ import { error } from './logger';
13
+ import { INVALID_MOVE } from './constants';
14
+ import type { Dispatch } from 'redux';
15
+ import type {
16
+ ActionShape,
17
+ Ctx,
18
+ ErrorType,
19
+ Game,
20
+ LogEntry,
21
+ LongFormMove,
22
+ Move,
23
+ State,
24
+ Store,
25
+ TransientMetadata,
26
+ TransientState,
27
+ Undo,
28
+ } from '../types';
29
+ import { stripTransients } from './action-creators';
30
+ import { ActionErrorType, UpdateErrorType } from './errors';
31
+ import { applyPatch } from 'rfc6902';
32
+
33
+ /**
34
+ * Check if the payload for the passed action contains a playerID.
35
+ */
36
+ const actionHasPlayerID = (
37
+ action:
38
+ | ActionShape.MakeMove
39
+ | ActionShape.GameEvent
40
+ | ActionShape.Undo
41
+ | ActionShape.Redo
42
+ ) => action.payload.playerID !== null && action.payload.playerID !== undefined;
43
+
44
+ /**
45
+ * Returns true if a move can be undone.
46
+ */
47
+ const CanUndoMove = (G: any, ctx: Ctx, move: Move): boolean => {
48
+ function HasUndoable(move: Move): move is LongFormMove {
49
+ return (move as LongFormMove).undoable !== undefined;
50
+ }
51
+
52
+ function IsFunction(
53
+ undoable: boolean | ((...args: any[]) => any)
54
+ ): undoable is (...args: any[]) => any {
55
+ return undoable instanceof Function;
56
+ }
57
+
58
+ if (!HasUndoable(move)) {
59
+ return true;
60
+ }
61
+
62
+ if (IsFunction(move.undoable)) {
63
+ return move.undoable({ G, ctx });
64
+ }
65
+
66
+ return move.undoable;
67
+ };
68
+
69
+ /**
70
+ * Update the undo and redo stacks for a move or event.
71
+ */
72
+ function updateUndoRedoState(
73
+ state: State,
74
+ opts: {
75
+ game: Game;
76
+ action: ActionShape.GameEvent | ActionShape.MakeMove;
77
+ }
78
+ ): State {
79
+ if (opts.game.disableUndo) return state;
80
+
81
+ const undoEntry: Undo = {
82
+ G: state.G,
83
+ ctx: state.ctx,
84
+ plugins: state.plugins,
85
+ playerID: opts.action.payload.playerID || state.ctx.currentPlayer,
86
+ };
87
+
88
+ if (opts.action.type === 'MAKE_MOVE') {
89
+ undoEntry.moveType = opts.action.payload.type;
90
+ }
91
+
92
+ return {
93
+ ...state,
94
+ _undo: [...state._undo, undoEntry],
95
+ // Always reset redo stack when making a move or event
96
+ _redo: [],
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Process state, adding the initial deltalog for this action.
102
+ */
103
+ function initializeDeltalog(
104
+ state: State,
105
+ action: ActionShape.MakeMove | ActionShape.Undo | ActionShape.Redo,
106
+ move?: Move
107
+ ): TransientState {
108
+ // Create a log entry for this action.
109
+ const logEntry: LogEntry = {
110
+ action,
111
+ _stateID: state._stateID,
112
+ turn: state.ctx.turn,
113
+ phase: state.ctx.phase,
114
+ };
115
+
116
+ const pluginLogMetadata = state.plugins.log.data.metadata;
117
+ if (pluginLogMetadata !== undefined) {
118
+ logEntry.metadata = pluginLogMetadata;
119
+ }
120
+
121
+ if (typeof move === 'object' && move.redact === true) {
122
+ logEntry.redact = true;
123
+ } else if (typeof move === 'object' && move.redact instanceof Function) {
124
+ logEntry.redact = move.redact({ G: state.G, ctx: state.ctx });
125
+ }
126
+
127
+ return {
128
+ ...state,
129
+ deltalog: [logEntry],
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Update plugin state after move/event & check if plugins consider the action to be valid.
135
+ * @param state Current version of state in the reducer.
136
+ * @param oldState State to revert to in case of error.
137
+ * @param pluginOpts Plugin configuration options.
138
+ * @returns Tuple of the new state updated after flushing plugins and the old
139
+ * state augmented with an error if a plugin declared the action invalid.
140
+ */
141
+ function flushAndValidatePlugins(
142
+ state: State,
143
+ oldState: State,
144
+ pluginOpts: { game: Game; isClient?: boolean }
145
+ ): [State, TransientState?] {
146
+ const [newState, isInvalid] = plugins.FlushAndValidate(state, pluginOpts);
147
+ if (!isInvalid) return [newState];
148
+ return [
149
+ newState,
150
+ WithError(oldState, ActionErrorType.PluginActionInvalid, isInvalid),
151
+ ];
152
+ }
153
+
154
+ /**
155
+ * ExtractTransientsFromState
156
+ *
157
+ * Split out transients from the a TransientState
158
+ */
159
+ function ExtractTransients(
160
+ transientState: TransientState | null
161
+ ): [State | null, TransientMetadata | undefined] {
162
+ if (!transientState) {
163
+ // We preserve null for the state for legacy callers, but the transient
164
+ // field should be undefined if not present to be consistent with the
165
+ // code path below.
166
+ return [null, undefined];
167
+ }
168
+ const { transients, ...state } = transientState;
169
+ return [state as State, transients as TransientMetadata];
170
+ }
171
+
172
+ /**
173
+ * WithError
174
+ *
175
+ * Augment a State instance with transient error information.
176
+ */
177
+ function WithError<PT extends any = any>(
178
+ state: State,
179
+ errorType: ErrorType,
180
+ payload?: PT
181
+ ): TransientState {
182
+ const error = {
183
+ type: errorType,
184
+ payload,
185
+ };
186
+ return {
187
+ ...state,
188
+ transients: {
189
+ error,
190
+ },
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Middleware for processing TransientState associated with the reducer
196
+ * returned by CreateGameReducer.
197
+ * This should pretty much be used everywhere you want realistic state
198
+ * transitions and error handling.
199
+ */
200
+ export const TransientHandlingMiddleware =
201
+ (store: Store) =>
202
+ (next: Dispatch<ActionShape.Any>) =>
203
+ (action: ActionShape.Any) => {
204
+ const result = next(action);
205
+ switch (action.type) {
206
+ case Actions.STRIP_TRANSIENTS: {
207
+ return result;
208
+ }
209
+ default: {
210
+ const [, transients] = ExtractTransients(store.getState());
211
+ if (typeof transients !== 'undefined') {
212
+ store.dispatch(stripTransients());
213
+ // Dev Note: If parent middleware needs to correlate the spawned
214
+ // StripTransients action to the triggering action, instrument here.
215
+ //
216
+ // This is a bit tricky; for more details, see:
217
+ // https://github.com/boardgameio/boardgame.io/pull/940#discussion_r636200648
218
+ return {
219
+ ...result,
220
+ transients,
221
+ };
222
+ }
223
+ return result;
224
+ }
225
+ }
226
+ };
227
+
228
+ /**
229
+ * CreateGameReducer
230
+ *
231
+ * Creates the main game state reducer.
232
+ */
233
+ export function CreateGameReducer({
234
+ game,
235
+ isClient,
236
+ }: {
237
+ game: Game;
238
+ isClient?: boolean;
239
+ }) {
240
+ game = ProcessGameConfig(game);
241
+
242
+ /**
243
+ * GameReducer
244
+ *
245
+ * Redux reducer that maintains the overall game state.
246
+ * @param {object} state - The state before the action.
247
+ * @param {object} action - A Redux action.
248
+ */
249
+ return (
250
+ stateWithTransients: TransientState | null = null,
251
+ action: ActionShape.Any
252
+ ): TransientState => {
253
+ let [state /*, transients */] = ExtractTransients(stateWithTransients);
254
+ switch (action.type) {
255
+ case Actions.STRIP_TRANSIENTS: {
256
+ // This action indicates that transient metadata in the state has been
257
+ // consumed and should now be stripped from the state..
258
+ return state;
259
+ }
260
+
261
+ case Actions.GAME_EVENT: {
262
+ state = { ...state, deltalog: [] };
263
+
264
+ // Process game events only on the server.
265
+ // These events like `endTurn` typically
266
+ // contain code that may rely on secret state
267
+ // and cannot be computed on the client.
268
+ if (isClient) {
269
+ return state;
270
+ }
271
+
272
+ // Disallow events once the game is over.
273
+ if (state.ctx.gameover !== undefined) {
274
+ error(`cannot call event after game end`);
275
+ return WithError(state, ActionErrorType.GameOver);
276
+ }
277
+
278
+ // Ignore the event if the player isn't active.
279
+ if (
280
+ actionHasPlayerID(action) &&
281
+ !game.flow.isPlayerActive(state.G, state.ctx, action.payload.playerID)
282
+ ) {
283
+ error(`disallowed event: ${action.payload.type}`);
284
+ return WithError(state, ActionErrorType.InactivePlayer);
285
+ }
286
+
287
+ // Execute plugins.
288
+ state = plugins.Enhance(state, {
289
+ game,
290
+ isClient: false,
291
+ playerID: action.payload.playerID,
292
+ });
293
+
294
+ // Process event.
295
+ let newState = game.flow.processEvent(state, action);
296
+
297
+ // Execute plugins.
298
+ let stateWithError: TransientState | undefined;
299
+ [newState, stateWithError] = flushAndValidatePlugins(newState, state, {
300
+ game,
301
+ isClient: false,
302
+ });
303
+ if (stateWithError) return stateWithError;
304
+
305
+ // Update undo / redo state.
306
+ newState = updateUndoRedoState(newState, { game, action });
307
+
308
+ return { ...newState, _stateID: state._stateID + 1 };
309
+ }
310
+
311
+ case Actions.MAKE_MOVE: {
312
+ const oldState = (state = { ...state, deltalog: [] });
313
+
314
+ // Check whether the move is allowed at this time.
315
+ const move: Move = game.flow.getMove(
316
+ state.ctx,
317
+ action.payload.type,
318
+ action.payload.playerID || state.ctx.currentPlayer
319
+ );
320
+ if (move === null) {
321
+ error(`disallowed move: ${action.payload.type}`);
322
+ return WithError(state, ActionErrorType.UnavailableMove);
323
+ }
324
+
325
+ // Don't run move on client if move says so.
326
+ if (isClient && (move as LongFormMove).client === false) {
327
+ return state;
328
+ }
329
+
330
+ // Disallow moves once the game is over.
331
+ if (state.ctx.gameover !== undefined) {
332
+ error(`cannot make move after game end`);
333
+ return WithError(state, ActionErrorType.GameOver);
334
+ }
335
+
336
+ // Ignore the move if the player isn't active.
337
+ if (
338
+ actionHasPlayerID(action) &&
339
+ !game.flow.isPlayerActive(state.G, state.ctx, action.payload.playerID)
340
+ ) {
341
+ error(`disallowed move: ${action.payload.type}`);
342
+ return WithError(state, ActionErrorType.InactivePlayer);
343
+ }
344
+
345
+ // Execute plugins.
346
+ state = plugins.Enhance(state, {
347
+ game,
348
+ isClient,
349
+ playerID: action.payload.playerID,
350
+ });
351
+
352
+ // Process the move.
353
+ const G = game.processMove(state, action.payload);
354
+
355
+ // The game declared the move as invalid.
356
+ if (G === INVALID_MOVE) {
357
+ error(
358
+ `invalid move: ${action.payload.type} args: ${action.payload.args}`
359
+ );
360
+ // TODO(#723): Marshal a nice error payload with the processed move.
361
+ return WithError(state, ActionErrorType.InvalidMove);
362
+ }
363
+
364
+ const newState = { ...state, G };
365
+
366
+ // Some plugin indicated that it is not suitable to be
367
+ // materialized on the client (and must wait for the server
368
+ // response instead).
369
+ if (isClient && plugins.NoClient(newState, { game })) {
370
+ return state;
371
+ }
372
+
373
+ state = newState;
374
+
375
+ // If we're on the client, just process the move
376
+ // and no triggers in multiplayer mode.
377
+ // These will be processed on the server, which
378
+ // will send back a state update.
379
+ if (isClient) {
380
+ let stateWithError: TransientState | undefined;
381
+ [state, stateWithError] = flushAndValidatePlugins(state, oldState, {
382
+ game,
383
+ isClient: true,
384
+ });
385
+ if (stateWithError) return stateWithError;
386
+ return {
387
+ ...state,
388
+ _stateID: state._stateID + 1,
389
+ };
390
+ }
391
+
392
+ // On the server, construct the deltalog.
393
+ state = initializeDeltalog(state, action, move);
394
+
395
+ // Allow the flow reducer to process any triggers that happen after moves.
396
+ state = game.flow.processMove(state, action.payload);
397
+ let stateWithError: TransientState | undefined;
398
+ [state, stateWithError] = flushAndValidatePlugins(state, oldState, {
399
+ game,
400
+ });
401
+ if (stateWithError) return stateWithError;
402
+
403
+ // Update undo / redo state.
404
+ state = updateUndoRedoState(state, { game, action });
405
+
406
+ return {
407
+ ...state,
408
+ _stateID: state._stateID + 1,
409
+ };
410
+ }
411
+
412
+ case Actions.RESET:
413
+ case Actions.UPDATE:
414
+ case Actions.SYNC: {
415
+ return action.state;
416
+ }
417
+
418
+ case Actions.UNDO: {
419
+ state = { ...state, deltalog: [] };
420
+
421
+ if (game.disableUndo) {
422
+ error('Undo is not enabled');
423
+ return WithError(state, ActionErrorType.ActionDisabled);
424
+ }
425
+
426
+ const { G, ctx, _undo, _redo, _stateID } = state;
427
+
428
+ if (_undo.length < 2) {
429
+ error(`No moves to undo`);
430
+ return WithError(state, ActionErrorType.ActionInvalid);
431
+ }
432
+
433
+ const last = _undo[_undo.length - 1];
434
+ const restore = _undo[_undo.length - 2];
435
+
436
+ // Only allow players to undo their own moves.
437
+ if (
438
+ actionHasPlayerID(action) &&
439
+ action.payload.playerID !== last.playerID
440
+ ) {
441
+ error(`Cannot undo other players' moves`);
442
+ return WithError(state, ActionErrorType.ActionInvalid);
443
+ }
444
+
445
+ // If undoing a move, check it is undoable.
446
+ if (last.moveType) {
447
+ const lastMove: Move = game.flow.getMove(
448
+ restore.ctx,
449
+ last.moveType,
450
+ last.playerID
451
+ );
452
+ if (!CanUndoMove(G, ctx, lastMove)) {
453
+ error(`Move cannot be undone`);
454
+ return WithError(state, ActionErrorType.ActionInvalid);
455
+ }
456
+ }
457
+
458
+ state = initializeDeltalog(state, action);
459
+
460
+ return {
461
+ ...state,
462
+ G: restore.G,
463
+ ctx: restore.ctx,
464
+ plugins: restore.plugins,
465
+ _stateID: _stateID + 1,
466
+ _undo: _undo.slice(0, -1),
467
+ _redo: [last, ..._redo],
468
+ };
469
+ }
470
+
471
+ case Actions.REDO: {
472
+ state = { ...state, deltalog: [] };
473
+
474
+ if (game.disableUndo) {
475
+ error('Redo is not enabled');
476
+ return WithError(state, ActionErrorType.ActionDisabled);
477
+ }
478
+
479
+ const { _undo, _redo, _stateID } = state;
480
+
481
+ if (_redo.length === 0) {
482
+ error(`No moves to redo`);
483
+ return WithError(state, ActionErrorType.ActionInvalid);
484
+ }
485
+
486
+ const first = _redo[0];
487
+
488
+ // Only allow players to redo their own undos.
489
+ if (
490
+ actionHasPlayerID(action) &&
491
+ action.payload.playerID !== first.playerID
492
+ ) {
493
+ error(`Cannot redo other players' moves`);
494
+ return WithError(state, ActionErrorType.ActionInvalid);
495
+ }
496
+
497
+ state = initializeDeltalog(state, action);
498
+
499
+ return {
500
+ ...state,
501
+ G: first.G,
502
+ ctx: first.ctx,
503
+ plugins: first.plugins,
504
+ _stateID: _stateID + 1,
505
+ _undo: [..._undo, first],
506
+ _redo: _redo.slice(1),
507
+ };
508
+ }
509
+
510
+ case Actions.PLUGIN: {
511
+ // TODO(#723): Expose error semantics to plugin processing.
512
+ return plugins.ProcessAction(state, action, { game });
513
+ }
514
+
515
+ case Actions.PATCH: {
516
+ const oldState = state;
517
+ const newState = JSON.parse(JSON.stringify(oldState));
518
+ const patchError = applyPatch(newState, action.patch);
519
+ const hasError = patchError.some((entry) => entry !== null);
520
+ if (hasError) {
521
+ error(`Patch ${JSON.stringify(action.patch)} apply failed`);
522
+ return WithError(oldState, UpdateErrorType.PatchFailed, patchError);
523
+ } else {
524
+ return newState;
525
+ }
526
+ }
527
+ default: {
528
+ return state;
529
+ }
530
+ }
531
+ };
532
+ }