@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,1198 @@
1
+ import { g as error, h as GameMethod, T as TurnOrder, i as supportDeprecatedMoveLimit, F as FnWrap, j as GetAPIs, k as Stage, l as SetActivePlayers, m as info, I as InitTurnOrderState, n as UpdateTurnOrderState, o as UpdateActivePlayersOnceEmpty, q as gameEvent, b as PATCH, t as PLUGIN, v as ProcessAction, c as REDO, d as UNDO, S as SYNC, U as UPDATE, R as RESET, M as MAKE_MOVE, E as Enhance, w as INVALID_MOVE, N as NoClient, G as GAME_EVENT, e as STRIP_TRANSIENTS, x as FlushAndValidate, y as stripTransients } from './turn-order-376d315e.js';
2
+ import { applyPatch } from 'rfc6902';
3
+
4
+ /*
5
+ * Copyright 2017 The boardgame.io Authors
6
+ *
7
+ * Use of this source code is governed by a MIT-style
8
+ * license that can be found in the LICENSE file or at
9
+ * https://opensource.org/licenses/MIT.
10
+ */
11
+ /**
12
+ * Flow
13
+ *
14
+ * Creates a reducer that updates ctx (analogous to how moves update G).
15
+ */
16
+ function Flow({ moves, phases, endIf, onEnd, turn, events, plugins, }) {
17
+ // Attach defaults.
18
+ if (moves === undefined) {
19
+ moves = {};
20
+ }
21
+ if (events === undefined) {
22
+ events = {};
23
+ }
24
+ if (plugins === undefined) {
25
+ plugins = [];
26
+ }
27
+ if (phases === undefined) {
28
+ phases = {};
29
+ }
30
+ if (!endIf)
31
+ endIf = () => undefined;
32
+ if (!onEnd)
33
+ onEnd = ({ G }) => G;
34
+ if (!turn)
35
+ turn = {};
36
+ const phaseMap = { ...phases };
37
+ if ('' in phaseMap) {
38
+ error('cannot specify phase with empty name');
39
+ }
40
+ phaseMap[''] = {};
41
+ const moveMap = {};
42
+ const moveNames = new Set();
43
+ let startingPhase = null;
44
+ Object.keys(moves).forEach((name) => moveNames.add(name));
45
+ const HookWrapper = (hook, hookType) => {
46
+ const withPlugins = FnWrap(hook, hookType, plugins);
47
+ return (state) => {
48
+ const pluginAPIs = GetAPIs(state);
49
+ return withPlugins({
50
+ ...pluginAPIs,
51
+ G: state.G,
52
+ ctx: state.ctx,
53
+ playerID: state.playerID,
54
+ });
55
+ };
56
+ };
57
+ const TriggerWrapper = (trigger) => {
58
+ return (state) => {
59
+ const pluginAPIs = GetAPIs(state);
60
+ return trigger({
61
+ ...pluginAPIs,
62
+ G: state.G,
63
+ ctx: state.ctx,
64
+ });
65
+ };
66
+ };
67
+ const wrapped = {
68
+ onEnd: HookWrapper(onEnd, GameMethod.GAME_ON_END),
69
+ endIf: TriggerWrapper(endIf),
70
+ };
71
+ for (const phase in phaseMap) {
72
+ const phaseConfig = phaseMap[phase];
73
+ if (phaseConfig.start === true) {
74
+ startingPhase = phase;
75
+ }
76
+ if (phaseConfig.moves !== undefined) {
77
+ for (const move of Object.keys(phaseConfig.moves)) {
78
+ moveMap[phase + '.' + move] = phaseConfig.moves[move];
79
+ moveNames.add(move);
80
+ }
81
+ }
82
+ if (phaseConfig.endIf === undefined) {
83
+ phaseConfig.endIf = () => undefined;
84
+ }
85
+ if (phaseConfig.onBegin === undefined) {
86
+ phaseConfig.onBegin = ({ G }) => G;
87
+ }
88
+ if (phaseConfig.onEnd === undefined) {
89
+ phaseConfig.onEnd = ({ G }) => G;
90
+ }
91
+ if (phaseConfig.turn === undefined) {
92
+ phaseConfig.turn = turn;
93
+ }
94
+ if (phaseConfig.turn.order === undefined) {
95
+ phaseConfig.turn.order = TurnOrder.DEFAULT;
96
+ }
97
+ if (phaseConfig.turn.onBegin === undefined) {
98
+ phaseConfig.turn.onBegin = ({ G }) => G;
99
+ }
100
+ if (phaseConfig.turn.onEnd === undefined) {
101
+ phaseConfig.turn.onEnd = ({ G }) => G;
102
+ }
103
+ if (phaseConfig.turn.endIf === undefined) {
104
+ phaseConfig.turn.endIf = () => false;
105
+ }
106
+ if (phaseConfig.turn.onMove === undefined) {
107
+ phaseConfig.turn.onMove = ({ G }) => G;
108
+ }
109
+ if (phaseConfig.turn.stages === undefined) {
110
+ phaseConfig.turn.stages = {};
111
+ }
112
+ // turns previously treated moveLimit as both minMoves and maxMoves, this behaviour is kept intentionally
113
+ supportDeprecatedMoveLimit(phaseConfig.turn, true);
114
+ for (const stage in phaseConfig.turn.stages) {
115
+ const stageConfig = phaseConfig.turn.stages[stage];
116
+ const moves = stageConfig.moves || {};
117
+ for (const move of Object.keys(moves)) {
118
+ const key = phase + '.' + stage + '.' + move;
119
+ moveMap[key] = moves[move];
120
+ moveNames.add(move);
121
+ }
122
+ }
123
+ phaseConfig.wrapped = {
124
+ onBegin: HookWrapper(phaseConfig.onBegin, GameMethod.PHASE_ON_BEGIN),
125
+ onEnd: HookWrapper(phaseConfig.onEnd, GameMethod.PHASE_ON_END),
126
+ endIf: TriggerWrapper(phaseConfig.endIf),
127
+ };
128
+ phaseConfig.turn.wrapped = {
129
+ onMove: HookWrapper(phaseConfig.turn.onMove, GameMethod.TURN_ON_MOVE),
130
+ onBegin: HookWrapper(phaseConfig.turn.onBegin, GameMethod.TURN_ON_BEGIN),
131
+ onEnd: HookWrapper(phaseConfig.turn.onEnd, GameMethod.TURN_ON_END),
132
+ endIf: TriggerWrapper(phaseConfig.turn.endIf),
133
+ };
134
+ if (typeof phaseConfig.next !== 'function') {
135
+ const { next } = phaseConfig;
136
+ phaseConfig.next = () => next || null;
137
+ }
138
+ phaseConfig.wrapped.next = TriggerWrapper(phaseConfig.next);
139
+ }
140
+ function GetPhase(ctx) {
141
+ return ctx.phase ? phaseMap[ctx.phase] : phaseMap[''];
142
+ }
143
+ function OnMove(state) {
144
+ return state;
145
+ }
146
+ function Process(state, events) {
147
+ const phasesEnded = new Set();
148
+ const turnsEnded = new Set();
149
+ for (let i = 0; i < events.length; i++) {
150
+ const { fn, arg, ...rest } = events[i];
151
+ // Detect a loop of EndPhase calls.
152
+ // This could potentially even be an infinite loop
153
+ // if the endIf condition of each phase blindly
154
+ // returns true. The moment we detect a single
155
+ // loop, we just bail out of all phases.
156
+ if (fn === EndPhase) {
157
+ turnsEnded.clear();
158
+ const phase = state.ctx.phase;
159
+ if (phasesEnded.has(phase)) {
160
+ const ctx = { ...state.ctx, phase: null };
161
+ return { ...state, ctx };
162
+ }
163
+ phasesEnded.add(phase);
164
+ }
165
+ // Process event.
166
+ const next = [];
167
+ state = fn(state, {
168
+ ...rest,
169
+ arg,
170
+ next,
171
+ });
172
+ if (fn === EndGame) {
173
+ break;
174
+ }
175
+ // Check if we should end the game.
176
+ const shouldEndGame = ShouldEndGame(state);
177
+ if (shouldEndGame) {
178
+ events.push({
179
+ fn: EndGame,
180
+ arg: shouldEndGame,
181
+ turn: state.ctx.turn,
182
+ phase: state.ctx.phase,
183
+ automatic: true,
184
+ });
185
+ continue;
186
+ }
187
+ // Check if we should end the phase.
188
+ const shouldEndPhase = ShouldEndPhase(state);
189
+ if (shouldEndPhase) {
190
+ events.push({
191
+ fn: EndPhase,
192
+ arg: shouldEndPhase,
193
+ turn: state.ctx.turn,
194
+ phase: state.ctx.phase,
195
+ automatic: true,
196
+ });
197
+ continue;
198
+ }
199
+ // Check if we should end the turn.
200
+ if ([OnMove, UpdateStage, UpdateActivePlayers].includes(fn)) {
201
+ const shouldEndTurn = ShouldEndTurn(state);
202
+ if (shouldEndTurn) {
203
+ events.push({
204
+ fn: EndTurn,
205
+ arg: shouldEndTurn,
206
+ turn: state.ctx.turn,
207
+ phase: state.ctx.phase,
208
+ automatic: true,
209
+ });
210
+ continue;
211
+ }
212
+ }
213
+ events.push(...next);
214
+ }
215
+ return state;
216
+ }
217
+ ///////////
218
+ // Start //
219
+ ///////////
220
+ function StartGame(state, { next }) {
221
+ next.push({ fn: StartPhase });
222
+ return state;
223
+ }
224
+ function StartPhase(state, { next }) {
225
+ let { G, ctx } = state;
226
+ const phaseConfig = GetPhase(ctx);
227
+ // Run any phase setup code provided by the user.
228
+ G = phaseConfig.wrapped.onBegin(state);
229
+ next.push({ fn: StartTurn });
230
+ return { ...state, G, ctx };
231
+ }
232
+ function StartTurn(state, { currentPlayer }) {
233
+ let { ctx } = state;
234
+ const phaseConfig = GetPhase(ctx);
235
+ // Initialize the turn order state.
236
+ if (currentPlayer) {
237
+ ctx = { ...ctx, currentPlayer };
238
+ if (phaseConfig.turn.activePlayers) {
239
+ ctx = SetActivePlayers(ctx, phaseConfig.turn.activePlayers);
240
+ }
241
+ }
242
+ else {
243
+ // This is only called at the beginning of the phase
244
+ // when there is no currentPlayer yet.
245
+ ctx = InitTurnOrderState(state, phaseConfig.turn);
246
+ }
247
+ const turn = ctx.turn + 1;
248
+ ctx = { ...ctx, turn, numMoves: 0, _prevActivePlayers: [] };
249
+ const G = phaseConfig.turn.wrapped.onBegin({ ...state, ctx });
250
+ return { ...state, G, ctx, _undo: [], _redo: [] };
251
+ }
252
+ ////////////
253
+ // Update //
254
+ ////////////
255
+ function UpdatePhase(state, { arg, next, phase }) {
256
+ const phaseConfig = GetPhase({ phase });
257
+ let { ctx } = state;
258
+ if (arg && arg.next) {
259
+ if (arg.next in phaseMap) {
260
+ ctx = { ...ctx, phase: arg.next };
261
+ }
262
+ else {
263
+ error('invalid phase: ' + arg.next);
264
+ return state;
265
+ }
266
+ }
267
+ else {
268
+ ctx = { ...ctx, phase: phaseConfig.wrapped.next(state) || null };
269
+ }
270
+ state = { ...state, ctx };
271
+ // Start the new phase.
272
+ next.push({ fn: StartPhase });
273
+ return state;
274
+ }
275
+ function UpdateTurn(state, { arg, currentPlayer, next }) {
276
+ let { G, ctx } = state;
277
+ const phaseConfig = GetPhase(ctx);
278
+ // Update turn order state.
279
+ const { endPhase, ctx: newCtx } = UpdateTurnOrderState(state, currentPlayer, phaseConfig.turn, arg);
280
+ ctx = newCtx;
281
+ state = { ...state, G, ctx };
282
+ if (endPhase) {
283
+ next.push({ fn: EndPhase, turn: ctx.turn, phase: ctx.phase });
284
+ }
285
+ else {
286
+ next.push({ fn: StartTurn, currentPlayer: ctx.currentPlayer });
287
+ }
288
+ return state;
289
+ }
290
+ function UpdateStage(state, { arg, playerID }) {
291
+ if (typeof arg === 'string' || arg === Stage.NULL) {
292
+ arg = { stage: arg };
293
+ }
294
+ if (typeof arg !== 'object')
295
+ return state;
296
+ // `arg` should be of type `StageArg`, loose typing as `any` here for historic reasons
297
+ // stages previously did not enforce minMoves, this behaviour is kept intentionally
298
+ supportDeprecatedMoveLimit(arg);
299
+ let { ctx } = state;
300
+ let { activePlayers, _activePlayersMinMoves, _activePlayersMaxMoves, _activePlayersNumMoves, } = ctx;
301
+ // Checking if stage is valid, even Stage.NULL
302
+ if (arg.stage !== undefined) {
303
+ if (activePlayers === null) {
304
+ activePlayers = {};
305
+ }
306
+ activePlayers[playerID] = arg.stage;
307
+ _activePlayersNumMoves[playerID] = 0;
308
+ if (arg.minMoves) {
309
+ if (_activePlayersMinMoves === null) {
310
+ _activePlayersMinMoves = {};
311
+ }
312
+ _activePlayersMinMoves[playerID] = arg.minMoves;
313
+ }
314
+ if (arg.maxMoves) {
315
+ if (_activePlayersMaxMoves === null) {
316
+ _activePlayersMaxMoves = {};
317
+ }
318
+ _activePlayersMaxMoves[playerID] = arg.maxMoves;
319
+ }
320
+ }
321
+ ctx = {
322
+ ...ctx,
323
+ activePlayers,
324
+ _activePlayersMinMoves,
325
+ _activePlayersMaxMoves,
326
+ _activePlayersNumMoves,
327
+ };
328
+ return { ...state, ctx };
329
+ }
330
+ function UpdateActivePlayers(state, { arg }) {
331
+ return { ...state, ctx: SetActivePlayers(state.ctx, arg) };
332
+ }
333
+ ///////////////
334
+ // ShouldEnd //
335
+ ///////////////
336
+ function ShouldEndGame(state) {
337
+ return wrapped.endIf(state);
338
+ }
339
+ function ShouldEndPhase(state) {
340
+ const phaseConfig = GetPhase(state.ctx);
341
+ return phaseConfig.wrapped.endIf(state);
342
+ }
343
+ function ShouldEndTurn(state) {
344
+ const phaseConfig = GetPhase(state.ctx);
345
+ // End the turn if the required number of moves has been made.
346
+ const currentPlayerMoves = state.ctx.numMoves || 0;
347
+ if (phaseConfig.turn.maxMoves &&
348
+ currentPlayerMoves >= phaseConfig.turn.maxMoves) {
349
+ return true;
350
+ }
351
+ return phaseConfig.turn.wrapped.endIf(state);
352
+ }
353
+ /////////
354
+ // End //
355
+ /////////
356
+ function EndGame(state, { arg, phase }) {
357
+ state = EndPhase(state, { phase });
358
+ if (arg === undefined) {
359
+ arg = true;
360
+ }
361
+ state = { ...state, ctx: { ...state.ctx, gameover: arg } };
362
+ // Run game end hook.
363
+ const G = wrapped.onEnd(state);
364
+ return { ...state, G };
365
+ }
366
+ function EndPhase(state, { arg, next, turn: initialTurn, automatic }) {
367
+ // End the turn first.
368
+ state = EndTurn(state, { turn: initialTurn, force: true, automatic: true });
369
+ const { phase, turn } = state.ctx;
370
+ if (next) {
371
+ next.push({ fn: UpdatePhase, arg, phase });
372
+ }
373
+ // If we aren't in a phase, there is nothing else to do.
374
+ if (phase === null) {
375
+ return state;
376
+ }
377
+ // Run any cleanup code for the phase that is about to end.
378
+ const phaseConfig = GetPhase(state.ctx);
379
+ const G = phaseConfig.wrapped.onEnd(state);
380
+ // Reset the phase.
381
+ const ctx = { ...state.ctx, phase: null };
382
+ // Add log entry.
383
+ const action = gameEvent('endPhase', arg);
384
+ const { _stateID } = state;
385
+ const logEntry = { action, _stateID, turn, phase };
386
+ if (automatic)
387
+ logEntry.automatic = true;
388
+ const deltalog = [...(state.deltalog || []), logEntry];
389
+ return { ...state, G, ctx, deltalog };
390
+ }
391
+ function EndTurn(state, { arg, next, turn: initialTurn, force, automatic, playerID }) {
392
+ // This is not the turn that EndTurn was originally
393
+ // called for. The turn was probably ended some other way.
394
+ if (initialTurn !== state.ctx.turn) {
395
+ return state;
396
+ }
397
+ const { currentPlayer, numMoves, phase, turn } = state.ctx;
398
+ const phaseConfig = GetPhase(state.ctx);
399
+ // Prevent ending the turn if minMoves haven't been reached.
400
+ const currentPlayerMoves = numMoves || 0;
401
+ if (!force &&
402
+ phaseConfig.turn.minMoves &&
403
+ currentPlayerMoves < phaseConfig.turn.minMoves) {
404
+ info(`cannot end turn before making ${phaseConfig.turn.minMoves} moves`);
405
+ return state;
406
+ }
407
+ // Run turn-end triggers.
408
+ const G = phaseConfig.turn.wrapped.onEnd(state);
409
+ if (next) {
410
+ next.push({ fn: UpdateTurn, arg, currentPlayer });
411
+ }
412
+ // Reset activePlayers.
413
+ let ctx = { ...state.ctx, activePlayers: null };
414
+ // Remove player from playerOrder
415
+ if (arg && arg.remove) {
416
+ playerID = playerID || currentPlayer;
417
+ const playOrder = ctx.playOrder.filter((i) => i != playerID);
418
+ const playOrderPos = ctx.playOrderPos > playOrder.length - 1 ? 0 : ctx.playOrderPos;
419
+ ctx = { ...ctx, playOrder, playOrderPos };
420
+ if (playOrder.length === 0) {
421
+ next.push({ fn: EndPhase, turn, phase });
422
+ return state;
423
+ }
424
+ }
425
+ // Create log entry.
426
+ const action = gameEvent('endTurn', arg);
427
+ const { _stateID } = state;
428
+ const logEntry = { action, _stateID, turn, phase };
429
+ if (automatic)
430
+ logEntry.automatic = true;
431
+ const deltalog = [...(state.deltalog || []), logEntry];
432
+ return { ...state, G, ctx, deltalog, _undo: [], _redo: [] };
433
+ }
434
+ function EndStage(state, { arg, next, automatic, playerID }) {
435
+ playerID = playerID || state.ctx.currentPlayer;
436
+ let { ctx, _stateID } = state;
437
+ let { activePlayers, _activePlayersNumMoves, _activePlayersMinMoves, _activePlayersMaxMoves, phase, turn, } = ctx;
438
+ const playerInStage = activePlayers !== null && playerID in activePlayers;
439
+ const phaseConfig = GetPhase(ctx);
440
+ if (!arg && playerInStage) {
441
+ const stage = phaseConfig.turn.stages[activePlayers[playerID]];
442
+ if (stage && stage.next) {
443
+ arg = stage.next;
444
+ }
445
+ }
446
+ // Checking if arg is a valid stage, even Stage.NULL
447
+ if (next) {
448
+ next.push({ fn: UpdateStage, arg, playerID });
449
+ }
450
+ // If player isn’t in a stage, there is nothing else to do.
451
+ if (!playerInStage)
452
+ return state;
453
+ // Prevent ending the stage if minMoves haven't been reached.
454
+ const currentPlayerMoves = _activePlayersNumMoves[playerID] || 0;
455
+ if (_activePlayersMinMoves &&
456
+ _activePlayersMinMoves[playerID] &&
457
+ currentPlayerMoves < _activePlayersMinMoves[playerID]) {
458
+ info(`cannot end stage before making ${_activePlayersMinMoves[playerID]} moves`);
459
+ return state;
460
+ }
461
+ // Remove player from activePlayers.
462
+ activePlayers = { ...activePlayers };
463
+ delete activePlayers[playerID];
464
+ if (_activePlayersMinMoves) {
465
+ // Remove player from _activePlayersMinMoves.
466
+ _activePlayersMinMoves = { ..._activePlayersMinMoves };
467
+ delete _activePlayersMinMoves[playerID];
468
+ }
469
+ if (_activePlayersMaxMoves) {
470
+ // Remove player from _activePlayersMaxMoves.
471
+ _activePlayersMaxMoves = { ..._activePlayersMaxMoves };
472
+ delete _activePlayersMaxMoves[playerID];
473
+ }
474
+ ctx = UpdateActivePlayersOnceEmpty({
475
+ ...ctx,
476
+ activePlayers,
477
+ _activePlayersMinMoves,
478
+ _activePlayersMaxMoves,
479
+ });
480
+ // Create log entry.
481
+ const action = gameEvent('endStage', arg);
482
+ const logEntry = { action, _stateID, turn, phase };
483
+ if (automatic)
484
+ logEntry.automatic = true;
485
+ const deltalog = [...(state.deltalog || []), logEntry];
486
+ return { ...state, ctx, deltalog };
487
+ }
488
+ /**
489
+ * Retrieves the relevant move that can be played by playerID.
490
+ *
491
+ * If ctx.activePlayers is set (i.e. one or more players are in some stage),
492
+ * then it attempts to find the move inside the stages config for
493
+ * that turn. If the stage for a player is '', then the player is
494
+ * allowed to make a move (as determined by the phase config), but
495
+ * isn't restricted to a particular set as defined in the stage config.
496
+ *
497
+ * If not, it then looks for the move inside the phase.
498
+ *
499
+ * If it doesn't find the move there, it looks at the global move definition.
500
+ *
501
+ * @param {object} ctx
502
+ * @param {string} name
503
+ * @param {string} playerID
504
+ */
505
+ function GetMove(ctx, name, playerID) {
506
+ const phaseConfig = GetPhase(ctx);
507
+ const stages = phaseConfig.turn.stages;
508
+ const { activePlayers } = ctx;
509
+ if (activePlayers &&
510
+ activePlayers[playerID] !== undefined &&
511
+ activePlayers[playerID] !== Stage.NULL &&
512
+ stages[activePlayers[playerID]] !== undefined &&
513
+ stages[activePlayers[playerID]].moves !== undefined) {
514
+ // Check if moves are defined for the player's stage.
515
+ const stage = stages[activePlayers[playerID]];
516
+ const moves = stage.moves;
517
+ if (name in moves) {
518
+ return moves[name];
519
+ }
520
+ }
521
+ else if (phaseConfig.moves) {
522
+ // Check if moves are defined for the current phase.
523
+ if (name in phaseConfig.moves) {
524
+ return phaseConfig.moves[name];
525
+ }
526
+ }
527
+ else if (name in moves) {
528
+ // Check for the move globally.
529
+ return moves[name];
530
+ }
531
+ return null;
532
+ }
533
+ function ProcessMove(state, action) {
534
+ const { playerID, type } = action;
535
+ const { currentPlayer, activePlayers, _activePlayersMaxMoves } = state.ctx;
536
+ const move = GetMove(state.ctx, type, playerID);
537
+ const shouldCount = !move || typeof move === 'function' || move.noLimit !== true;
538
+ let { numMoves, _activePlayersNumMoves } = state.ctx;
539
+ if (shouldCount) {
540
+ if (playerID === currentPlayer)
541
+ numMoves++;
542
+ if (activePlayers)
543
+ _activePlayersNumMoves[playerID]++;
544
+ }
545
+ state = {
546
+ ...state,
547
+ ctx: {
548
+ ...state.ctx,
549
+ numMoves,
550
+ _activePlayersNumMoves,
551
+ },
552
+ };
553
+ if (_activePlayersMaxMoves &&
554
+ _activePlayersNumMoves[playerID] >= _activePlayersMaxMoves[playerID]) {
555
+ state = EndStage(state, { playerID, automatic: true });
556
+ }
557
+ const phaseConfig = GetPhase(state.ctx);
558
+ const G = phaseConfig.turn.wrapped.onMove({ ...state, playerID });
559
+ state = { ...state, G };
560
+ const events = [{ fn: OnMove }];
561
+ return Process(state, events);
562
+ }
563
+ function SetStageEvent(state, playerID, arg) {
564
+ return Process(state, [{ fn: EndStage, arg, playerID }]);
565
+ }
566
+ function EndStageEvent(state, playerID) {
567
+ return Process(state, [{ fn: EndStage, playerID }]);
568
+ }
569
+ function SetActivePlayersEvent(state, _playerID, arg) {
570
+ return Process(state, [{ fn: UpdateActivePlayers, arg }]);
571
+ }
572
+ function SetPhaseEvent(state, _playerID, newPhase) {
573
+ return Process(state, [
574
+ {
575
+ fn: EndPhase,
576
+ phase: state.ctx.phase,
577
+ turn: state.ctx.turn,
578
+ arg: { next: newPhase },
579
+ },
580
+ ]);
581
+ }
582
+ function EndPhaseEvent(state) {
583
+ return Process(state, [
584
+ { fn: EndPhase, phase: state.ctx.phase, turn: state.ctx.turn },
585
+ ]);
586
+ }
587
+ function EndTurnEvent(state, _playerID, arg) {
588
+ return Process(state, [
589
+ { fn: EndTurn, turn: state.ctx.turn, phase: state.ctx.phase, arg },
590
+ ]);
591
+ }
592
+ function PassEvent(state, _playerID, arg) {
593
+ return Process(state, [
594
+ {
595
+ fn: EndTurn,
596
+ turn: state.ctx.turn,
597
+ phase: state.ctx.phase,
598
+ force: true,
599
+ arg,
600
+ },
601
+ ]);
602
+ }
603
+ function EndGameEvent(state, _playerID, arg) {
604
+ return Process(state, [
605
+ { fn: EndGame, turn: state.ctx.turn, phase: state.ctx.phase, arg },
606
+ ]);
607
+ }
608
+ const eventHandlers = {
609
+ endStage: EndStageEvent,
610
+ setStage: SetStageEvent,
611
+ endTurn: EndTurnEvent,
612
+ pass: PassEvent,
613
+ endPhase: EndPhaseEvent,
614
+ setPhase: SetPhaseEvent,
615
+ endGame: EndGameEvent,
616
+ setActivePlayers: SetActivePlayersEvent,
617
+ };
618
+ const enabledEventNames = [];
619
+ if (events.endTurn !== false) {
620
+ enabledEventNames.push('endTurn');
621
+ }
622
+ if (events.pass !== false) {
623
+ enabledEventNames.push('pass');
624
+ }
625
+ if (events.endPhase !== false) {
626
+ enabledEventNames.push('endPhase');
627
+ }
628
+ if (events.setPhase !== false) {
629
+ enabledEventNames.push('setPhase');
630
+ }
631
+ if (events.endGame !== false) {
632
+ enabledEventNames.push('endGame');
633
+ }
634
+ if (events.setActivePlayers !== false) {
635
+ enabledEventNames.push('setActivePlayers');
636
+ }
637
+ if (events.endStage !== false) {
638
+ enabledEventNames.push('endStage');
639
+ }
640
+ if (events.setStage !== false) {
641
+ enabledEventNames.push('setStage');
642
+ }
643
+ function ProcessEvent(state, action) {
644
+ const { type, playerID, args } = action.payload;
645
+ if (typeof eventHandlers[type] !== 'function')
646
+ return state;
647
+ return eventHandlers[type](state, playerID, ...(Array.isArray(args) ? args : [args]));
648
+ }
649
+ function IsPlayerActive(_G, ctx, playerID) {
650
+ if (ctx.activePlayers) {
651
+ return playerID in ctx.activePlayers;
652
+ }
653
+ return ctx.currentPlayer === playerID;
654
+ }
655
+ return {
656
+ ctx: (numPlayers) => ({
657
+ numPlayers,
658
+ turn: 0,
659
+ currentPlayer: '0',
660
+ playOrder: [...Array.from({ length: numPlayers })].map((_, i) => i + ''),
661
+ playOrderPos: 0,
662
+ phase: startingPhase,
663
+ activePlayers: null,
664
+ }),
665
+ init: (state) => {
666
+ return Process(state, [{ fn: StartGame }]);
667
+ },
668
+ isPlayerActive: IsPlayerActive,
669
+ eventHandlers,
670
+ eventNames: Object.keys(eventHandlers),
671
+ enabledEventNames,
672
+ moveMap,
673
+ moveNames: [...moveNames.values()],
674
+ processMove: ProcessMove,
675
+ processEvent: ProcessEvent,
676
+ getMove: GetMove,
677
+ };
678
+ }
679
+
680
+ /*
681
+ * Copyright 2017 The boardgame.io Authors
682
+ *
683
+ * Use of this source code is governed by a MIT-style
684
+ * license that can be found in the LICENSE file or at
685
+ * https://opensource.org/licenses/MIT.
686
+ */
687
+ function IsProcessed(game) {
688
+ return game.processMove !== undefined;
689
+ }
690
+ /**
691
+ * Helper to generate the game move reducer. The returned
692
+ * reducer has the following signature:
693
+ *
694
+ * (G, action, ctx) => {}
695
+ *
696
+ * You can roll your own if you like, or use any Redux
697
+ * addon to generate such a reducer.
698
+ *
699
+ * The convention used in this framework is to
700
+ * have action.type contain the name of the move, and
701
+ * action.args contain any additional arguments as an
702
+ * Array.
703
+ */
704
+ function ProcessGameConfig(game) {
705
+ // The Game() function has already been called on this
706
+ // config object, so just pass it through.
707
+ if (IsProcessed(game)) {
708
+ return game;
709
+ }
710
+ if (game.name === undefined)
711
+ game.name = 'default';
712
+ if (game.deltaState === undefined)
713
+ game.deltaState = false;
714
+ if (game.disableUndo === undefined)
715
+ game.disableUndo = false;
716
+ if (game.setup === undefined)
717
+ game.setup = () => ({});
718
+ if (game.moves === undefined)
719
+ game.moves = {};
720
+ if (game.playerView === undefined)
721
+ game.playerView = ({ G }) => G;
722
+ if (game.plugins === undefined)
723
+ game.plugins = [];
724
+ game.plugins.forEach((plugin) => {
725
+ if (plugin.name === undefined) {
726
+ throw new Error('Plugin missing name attribute');
727
+ }
728
+ if (plugin.name.includes(' ')) {
729
+ throw new Error(plugin.name + ': Plugin name must not include spaces');
730
+ }
731
+ });
732
+ if (game.name.includes(' ')) {
733
+ throw new Error(game.name + ': Game name must not include spaces');
734
+ }
735
+ const flow = Flow(game);
736
+ return {
737
+ ...game,
738
+ flow,
739
+ moveNames: flow.moveNames,
740
+ pluginNames: game.plugins.map((p) => p.name),
741
+ processMove: (state, action) => {
742
+ let moveFn = flow.getMove(state.ctx, action.type, action.playerID);
743
+ if (IsLongFormMove(moveFn)) {
744
+ moveFn = moveFn.move;
745
+ }
746
+ if (moveFn instanceof Function) {
747
+ const fn = FnWrap(moveFn, GameMethod.MOVE, game.plugins);
748
+ let args = [];
749
+ if (action.args !== undefined) {
750
+ args = Array.isArray(action.args) ? action.args : [action.args];
751
+ }
752
+ const context = {
753
+ ...GetAPIs(state),
754
+ G: state.G,
755
+ ctx: state.ctx,
756
+ playerID: action.playerID,
757
+ };
758
+ return fn(context, ...args);
759
+ }
760
+ error(`invalid move object: ${action.type}`);
761
+ return state.G;
762
+ },
763
+ };
764
+ }
765
+ function IsLongFormMove(move) {
766
+ return move instanceof Object && move.move !== undefined;
767
+ }
768
+
769
+ /*
770
+ * Copyright 2017 The boardgame.io Authors
771
+ *
772
+ * Use of this source code is governed by a MIT-style
773
+ * license that can be found in the LICENSE file or at
774
+ * https://opensource.org/licenses/MIT.
775
+ */
776
+ var UpdateErrorType;
777
+ (function (UpdateErrorType) {
778
+ // The action’s credentials were missing or invalid
779
+ UpdateErrorType["UnauthorizedAction"] = "update/unauthorized_action";
780
+ // The action’s matchID was not found
781
+ UpdateErrorType["MatchNotFound"] = "update/match_not_found";
782
+ // Could not apply Patch operation (rfc6902).
783
+ UpdateErrorType["PatchFailed"] = "update/patch_failed";
784
+ })(UpdateErrorType || (UpdateErrorType = {}));
785
+ var ActionErrorType;
786
+ (function (ActionErrorType) {
787
+ // The action contained a stale state ID
788
+ ActionErrorType["StaleStateId"] = "action/stale_state_id";
789
+ // The requested move is unknown or not currently available
790
+ ActionErrorType["UnavailableMove"] = "action/unavailable_move";
791
+ // The move declared it was invalid (INVALID_MOVE constant)
792
+ ActionErrorType["InvalidMove"] = "action/invalid_move";
793
+ // The player making the action is not currently active
794
+ ActionErrorType["InactivePlayer"] = "action/inactive_player";
795
+ // The game has finished
796
+ ActionErrorType["GameOver"] = "action/gameover";
797
+ // The requested action is disabled (e.g. undo/redo, events)
798
+ ActionErrorType["ActionDisabled"] = "action/action_disabled";
799
+ // The requested action is not currently possible
800
+ ActionErrorType["ActionInvalid"] = "action/action_invalid";
801
+ // The requested action was declared invalid by a plugin
802
+ ActionErrorType["PluginActionInvalid"] = "action/plugin_invalid";
803
+ })(ActionErrorType || (ActionErrorType = {}));
804
+
805
+ /*
806
+ * Copyright 2017 The boardgame.io Authors
807
+ *
808
+ * Use of this source code is governed by a MIT-style
809
+ * license that can be found in the LICENSE file or at
810
+ * https://opensource.org/licenses/MIT.
811
+ */
812
+ /**
813
+ * Check if the payload for the passed action contains a playerID.
814
+ */
815
+ const actionHasPlayerID = (action) => action.payload.playerID !== null && action.payload.playerID !== undefined;
816
+ /**
817
+ * Returns true if a move can be undone.
818
+ */
819
+ const CanUndoMove = (G, ctx, move) => {
820
+ function HasUndoable(move) {
821
+ return move.undoable !== undefined;
822
+ }
823
+ function IsFunction(undoable) {
824
+ return undoable instanceof Function;
825
+ }
826
+ if (!HasUndoable(move)) {
827
+ return true;
828
+ }
829
+ if (IsFunction(move.undoable)) {
830
+ return move.undoable({ G, ctx });
831
+ }
832
+ return move.undoable;
833
+ };
834
+ /**
835
+ * Update the undo and redo stacks for a move or event.
836
+ */
837
+ function updateUndoRedoState(state, opts) {
838
+ if (opts.game.disableUndo)
839
+ return state;
840
+ const undoEntry = {
841
+ G: state.G,
842
+ ctx: state.ctx,
843
+ plugins: state.plugins,
844
+ playerID: opts.action.payload.playerID || state.ctx.currentPlayer,
845
+ };
846
+ if (opts.action.type === 'MAKE_MOVE') {
847
+ undoEntry.moveType = opts.action.payload.type;
848
+ }
849
+ return {
850
+ ...state,
851
+ _undo: [...state._undo, undoEntry],
852
+ // Always reset redo stack when making a move or event
853
+ _redo: [],
854
+ };
855
+ }
856
+ /**
857
+ * Process state, adding the initial deltalog for this action.
858
+ */
859
+ function initializeDeltalog(state, action, move) {
860
+ // Create a log entry for this action.
861
+ const logEntry = {
862
+ action,
863
+ _stateID: state._stateID,
864
+ turn: state.ctx.turn,
865
+ phase: state.ctx.phase,
866
+ };
867
+ const pluginLogMetadata = state.plugins.log.data.metadata;
868
+ if (pluginLogMetadata !== undefined) {
869
+ logEntry.metadata = pluginLogMetadata;
870
+ }
871
+ if (typeof move === 'object' && move.redact === true) {
872
+ logEntry.redact = true;
873
+ }
874
+ else if (typeof move === 'object' && move.redact instanceof Function) {
875
+ logEntry.redact = move.redact({ G: state.G, ctx: state.ctx });
876
+ }
877
+ return {
878
+ ...state,
879
+ deltalog: [logEntry],
880
+ };
881
+ }
882
+ /**
883
+ * Update plugin state after move/event & check if plugins consider the action to be valid.
884
+ * @param state Current version of state in the reducer.
885
+ * @param oldState State to revert to in case of error.
886
+ * @param pluginOpts Plugin configuration options.
887
+ * @returns Tuple of the new state updated after flushing plugins and the old
888
+ * state augmented with an error if a plugin declared the action invalid.
889
+ */
890
+ function flushAndValidatePlugins(state, oldState, pluginOpts) {
891
+ const [newState, isInvalid] = FlushAndValidate(state, pluginOpts);
892
+ if (!isInvalid)
893
+ return [newState];
894
+ return [
895
+ newState,
896
+ WithError(oldState, ActionErrorType.PluginActionInvalid, isInvalid),
897
+ ];
898
+ }
899
+ /**
900
+ * ExtractTransientsFromState
901
+ *
902
+ * Split out transients from the a TransientState
903
+ */
904
+ function ExtractTransients(transientState) {
905
+ if (!transientState) {
906
+ // We preserve null for the state for legacy callers, but the transient
907
+ // field should be undefined if not present to be consistent with the
908
+ // code path below.
909
+ return [null, undefined];
910
+ }
911
+ const { transients, ...state } = transientState;
912
+ return [state, transients];
913
+ }
914
+ /**
915
+ * WithError
916
+ *
917
+ * Augment a State instance with transient error information.
918
+ */
919
+ function WithError(state, errorType, payload) {
920
+ const error = {
921
+ type: errorType,
922
+ payload,
923
+ };
924
+ return {
925
+ ...state,
926
+ transients: {
927
+ error,
928
+ },
929
+ };
930
+ }
931
+ /**
932
+ * Middleware for processing TransientState associated with the reducer
933
+ * returned by CreateGameReducer.
934
+ * This should pretty much be used everywhere you want realistic state
935
+ * transitions and error handling.
936
+ */
937
+ const TransientHandlingMiddleware = (store) => (next) => (action) => {
938
+ const result = next(action);
939
+ switch (action.type) {
940
+ case STRIP_TRANSIENTS: {
941
+ return result;
942
+ }
943
+ default: {
944
+ const [, transients] = ExtractTransients(store.getState());
945
+ if (typeof transients !== 'undefined') {
946
+ store.dispatch(stripTransients());
947
+ // Dev Note: If parent middleware needs to correlate the spawned
948
+ // StripTransients action to the triggering action, instrument here.
949
+ //
950
+ // This is a bit tricky; for more details, see:
951
+ // https://github.com/boardgameio/boardgame.io/pull/940#discussion_r636200648
952
+ return {
953
+ ...result,
954
+ transients,
955
+ };
956
+ }
957
+ return result;
958
+ }
959
+ }
960
+ };
961
+ /**
962
+ * CreateGameReducer
963
+ *
964
+ * Creates the main game state reducer.
965
+ */
966
+ function CreateGameReducer({ game, isClient, }) {
967
+ game = ProcessGameConfig(game);
968
+ /**
969
+ * GameReducer
970
+ *
971
+ * Redux reducer that maintains the overall game state.
972
+ * @param {object} state - The state before the action.
973
+ * @param {object} action - A Redux action.
974
+ */
975
+ return (stateWithTransients = null, action) => {
976
+ let [state /*, transients */] = ExtractTransients(stateWithTransients);
977
+ switch (action.type) {
978
+ case STRIP_TRANSIENTS: {
979
+ // This action indicates that transient metadata in the state has been
980
+ // consumed and should now be stripped from the state..
981
+ return state;
982
+ }
983
+ case GAME_EVENT: {
984
+ state = { ...state, deltalog: [] };
985
+ // Process game events only on the server.
986
+ // These events like `endTurn` typically
987
+ // contain code that may rely on secret state
988
+ // and cannot be computed on the client.
989
+ if (isClient) {
990
+ return state;
991
+ }
992
+ // Disallow events once the game is over.
993
+ if (state.ctx.gameover !== undefined) {
994
+ error(`cannot call event after game end`);
995
+ return WithError(state, ActionErrorType.GameOver);
996
+ }
997
+ // Ignore the event if the player isn't active.
998
+ if (actionHasPlayerID(action) &&
999
+ !game.flow.isPlayerActive(state.G, state.ctx, action.payload.playerID)) {
1000
+ error(`disallowed event: ${action.payload.type}`);
1001
+ return WithError(state, ActionErrorType.InactivePlayer);
1002
+ }
1003
+ // Execute plugins.
1004
+ state = Enhance(state, {
1005
+ game,
1006
+ isClient: false,
1007
+ playerID: action.payload.playerID,
1008
+ });
1009
+ // Process event.
1010
+ let newState = game.flow.processEvent(state, action);
1011
+ // Execute plugins.
1012
+ let stateWithError;
1013
+ [newState, stateWithError] = flushAndValidatePlugins(newState, state, {
1014
+ game,
1015
+ isClient: false,
1016
+ });
1017
+ if (stateWithError)
1018
+ return stateWithError;
1019
+ // Update undo / redo state.
1020
+ newState = updateUndoRedoState(newState, { game, action });
1021
+ return { ...newState, _stateID: state._stateID + 1 };
1022
+ }
1023
+ case MAKE_MOVE: {
1024
+ const oldState = (state = { ...state, deltalog: [] });
1025
+ // Check whether the move is allowed at this time.
1026
+ const move = game.flow.getMove(state.ctx, action.payload.type, action.payload.playerID || state.ctx.currentPlayer);
1027
+ if (move === null) {
1028
+ error(`disallowed move: ${action.payload.type}`);
1029
+ return WithError(state, ActionErrorType.UnavailableMove);
1030
+ }
1031
+ // Don't run move on client if move says so.
1032
+ if (isClient && move.client === false) {
1033
+ return state;
1034
+ }
1035
+ // Disallow moves once the game is over.
1036
+ if (state.ctx.gameover !== undefined) {
1037
+ error(`cannot make move after game end`);
1038
+ return WithError(state, ActionErrorType.GameOver);
1039
+ }
1040
+ // Ignore the move if the player isn't active.
1041
+ if (actionHasPlayerID(action) &&
1042
+ !game.flow.isPlayerActive(state.G, state.ctx, action.payload.playerID)) {
1043
+ error(`disallowed move: ${action.payload.type}`);
1044
+ return WithError(state, ActionErrorType.InactivePlayer);
1045
+ }
1046
+ // Execute plugins.
1047
+ state = Enhance(state, {
1048
+ game,
1049
+ isClient,
1050
+ playerID: action.payload.playerID,
1051
+ });
1052
+ // Process the move.
1053
+ const G = game.processMove(state, action.payload);
1054
+ // The game declared the move as invalid.
1055
+ if (G === INVALID_MOVE) {
1056
+ error(`invalid move: ${action.payload.type} args: ${action.payload.args}`);
1057
+ // TODO(#723): Marshal a nice error payload with the processed move.
1058
+ return WithError(state, ActionErrorType.InvalidMove);
1059
+ }
1060
+ const newState = { ...state, G };
1061
+ // Some plugin indicated that it is not suitable to be
1062
+ // materialized on the client (and must wait for the server
1063
+ // response instead).
1064
+ if (isClient && NoClient(newState, { game })) {
1065
+ return state;
1066
+ }
1067
+ state = newState;
1068
+ // If we're on the client, just process the move
1069
+ // and no triggers in multiplayer mode.
1070
+ // These will be processed on the server, which
1071
+ // will send back a state update.
1072
+ if (isClient) {
1073
+ let stateWithError;
1074
+ [state, stateWithError] = flushAndValidatePlugins(state, oldState, {
1075
+ game,
1076
+ isClient: true,
1077
+ });
1078
+ if (stateWithError)
1079
+ return stateWithError;
1080
+ return {
1081
+ ...state,
1082
+ _stateID: state._stateID + 1,
1083
+ };
1084
+ }
1085
+ // On the server, construct the deltalog.
1086
+ state = initializeDeltalog(state, action, move);
1087
+ // Allow the flow reducer to process any triggers that happen after moves.
1088
+ state = game.flow.processMove(state, action.payload);
1089
+ let stateWithError;
1090
+ [state, stateWithError] = flushAndValidatePlugins(state, oldState, {
1091
+ game,
1092
+ });
1093
+ if (stateWithError)
1094
+ return stateWithError;
1095
+ // Update undo / redo state.
1096
+ state = updateUndoRedoState(state, { game, action });
1097
+ return {
1098
+ ...state,
1099
+ _stateID: state._stateID + 1,
1100
+ };
1101
+ }
1102
+ case RESET:
1103
+ case UPDATE:
1104
+ case SYNC: {
1105
+ return action.state;
1106
+ }
1107
+ case UNDO: {
1108
+ state = { ...state, deltalog: [] };
1109
+ if (game.disableUndo) {
1110
+ error('Undo is not enabled');
1111
+ return WithError(state, ActionErrorType.ActionDisabled);
1112
+ }
1113
+ const { G, ctx, _undo, _redo, _stateID } = state;
1114
+ if (_undo.length < 2) {
1115
+ error(`No moves to undo`);
1116
+ return WithError(state, ActionErrorType.ActionInvalid);
1117
+ }
1118
+ const last = _undo[_undo.length - 1];
1119
+ const restore = _undo[_undo.length - 2];
1120
+ // Only allow players to undo their own moves.
1121
+ if (actionHasPlayerID(action) &&
1122
+ action.payload.playerID !== last.playerID) {
1123
+ error(`Cannot undo other players' moves`);
1124
+ return WithError(state, ActionErrorType.ActionInvalid);
1125
+ }
1126
+ // If undoing a move, check it is undoable.
1127
+ if (last.moveType) {
1128
+ const lastMove = game.flow.getMove(restore.ctx, last.moveType, last.playerID);
1129
+ if (!CanUndoMove(G, ctx, lastMove)) {
1130
+ error(`Move cannot be undone`);
1131
+ return WithError(state, ActionErrorType.ActionInvalid);
1132
+ }
1133
+ }
1134
+ state = initializeDeltalog(state, action);
1135
+ return {
1136
+ ...state,
1137
+ G: restore.G,
1138
+ ctx: restore.ctx,
1139
+ plugins: restore.plugins,
1140
+ _stateID: _stateID + 1,
1141
+ _undo: _undo.slice(0, -1),
1142
+ _redo: [last, ..._redo],
1143
+ };
1144
+ }
1145
+ case REDO: {
1146
+ state = { ...state, deltalog: [] };
1147
+ if (game.disableUndo) {
1148
+ error('Redo is not enabled');
1149
+ return WithError(state, ActionErrorType.ActionDisabled);
1150
+ }
1151
+ const { _undo, _redo, _stateID } = state;
1152
+ if (_redo.length === 0) {
1153
+ error(`No moves to redo`);
1154
+ return WithError(state, ActionErrorType.ActionInvalid);
1155
+ }
1156
+ const first = _redo[0];
1157
+ // Only allow players to redo their own undos.
1158
+ if (actionHasPlayerID(action) &&
1159
+ action.payload.playerID !== first.playerID) {
1160
+ error(`Cannot redo other players' moves`);
1161
+ return WithError(state, ActionErrorType.ActionInvalid);
1162
+ }
1163
+ state = initializeDeltalog(state, action);
1164
+ return {
1165
+ ...state,
1166
+ G: first.G,
1167
+ ctx: first.ctx,
1168
+ plugins: first.plugins,
1169
+ _stateID: _stateID + 1,
1170
+ _undo: [..._undo, first],
1171
+ _redo: _redo.slice(1),
1172
+ };
1173
+ }
1174
+ case PLUGIN: {
1175
+ // TODO(#723): Expose error semantics to plugin processing.
1176
+ return ProcessAction(state, action, { game });
1177
+ }
1178
+ case PATCH: {
1179
+ const oldState = state;
1180
+ const newState = JSON.parse(JSON.stringify(oldState));
1181
+ const patchError = applyPatch(newState, action.patch);
1182
+ const hasError = patchError.some((entry) => entry !== null);
1183
+ if (hasError) {
1184
+ error(`Patch ${JSON.stringify(action.patch)} apply failed`);
1185
+ return WithError(oldState, UpdateErrorType.PatchFailed, patchError);
1186
+ }
1187
+ else {
1188
+ return newState;
1189
+ }
1190
+ }
1191
+ default: {
1192
+ return state;
1193
+ }
1194
+ }
1195
+ };
1196
+ }
1197
+
1198
+ export { CreateGameReducer as C, IsLongFormMove as I, ProcessGameConfig as P, TransientHandlingMiddleware as T };