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