@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,1091 @@
1
+ import produce from 'immer';
2
+ import { R as RandomPlugin } from './plugin-random-087f861e.js';
3
+ import isPlainObject from 'lodash.isplainobject';
4
+
5
+ /*
6
+ * Copyright 2017 The boardgame.io Authors
7
+ *
8
+ * Use of this source code is governed by a MIT-style
9
+ * license that can be found in the LICENSE file or at
10
+ * https://opensource.org/licenses/MIT.
11
+ */
12
+ const MAKE_MOVE = 'MAKE_MOVE';
13
+ const GAME_EVENT = 'GAME_EVENT';
14
+ const REDO = 'REDO';
15
+ const RESET = 'RESET';
16
+ const SYNC = 'SYNC';
17
+ const UNDO = 'UNDO';
18
+ const UPDATE = 'UPDATE';
19
+ const PATCH = 'PATCH';
20
+ const PLUGIN = 'PLUGIN';
21
+ const STRIP_TRANSIENTS = 'STRIP_TRANSIENTS';
22
+
23
+ /*
24
+ * Copyright 2017 The boardgame.io Authors
25
+ *
26
+ * Use of this source code is governed by a MIT-style
27
+ * license that can be found in the LICENSE file or at
28
+ * https://opensource.org/licenses/MIT.
29
+ */
30
+ /**
31
+ * Generate a move to be dispatched to the game move reducer.
32
+ *
33
+ * @param {string} type - The move type.
34
+ * @param {Array} args - Additional arguments.
35
+ * @param {string} playerID - The ID of the player making this action.
36
+ * @param {string} credentials - (optional) The credentials for the player making this action.
37
+ */
38
+ const makeMove = (type, args, playerID, credentials) => ({
39
+ type: MAKE_MOVE,
40
+ payload: { type, args, playerID, credentials },
41
+ });
42
+ /**
43
+ * Generate a game event to be dispatched to the flow reducer.
44
+ *
45
+ * @param {string} type - The event type.
46
+ * @param {Array} args - Additional arguments.
47
+ * @param {string} playerID - The ID of the player making this action.
48
+ * @param {string} credentials - (optional) The credentials for the player making this action.
49
+ */
50
+ const gameEvent = (type, args, playerID, credentials) => ({
51
+ type: GAME_EVENT,
52
+ payload: { type, args, playerID, credentials },
53
+ });
54
+ /**
55
+ * Generate an automatic game event that is a side-effect of a move.
56
+ * @param {string} type - The event type.
57
+ * @param {Array} args - Additional arguments.
58
+ * @param {string} playerID - The ID of the player making this action.
59
+ * @param {string} credentials - (optional) The credentials for the player making this action.
60
+ */
61
+ const automaticGameEvent = (type, args, playerID, credentials) => ({
62
+ type: GAME_EVENT,
63
+ payload: { type, args, playerID, credentials },
64
+ automatic: true,
65
+ });
66
+ const sync = (info) => ({
67
+ type: SYNC,
68
+ state: info.state,
69
+ log: info.log,
70
+ initialState: info.initialState,
71
+ clientOnly: true,
72
+ });
73
+ /**
74
+ * Used to update the Redux store's state with patch in response to
75
+ * an action coming from another player.
76
+ * @param prevStateID previous stateID
77
+ * @param stateID stateID after this patch
78
+ * @param {Operation[]} patch - The patch to apply.
79
+ * @param {LogEntry[]} deltalog - A log delta.
80
+ */
81
+ const patch = (prevStateID, stateID, patch, deltalog) => ({
82
+ type: PATCH,
83
+ prevStateID,
84
+ stateID,
85
+ patch,
86
+ deltalog,
87
+ clientOnly: true,
88
+ });
89
+ /**
90
+ * Used to update the Redux store's state in response to
91
+ * an action coming from another player.
92
+ * @param {object} state - The state to restore.
93
+ * @param {Array} deltalog - A log delta.
94
+ */
95
+ const update = (state, deltalog) => ({
96
+ type: UPDATE,
97
+ state,
98
+ deltalog,
99
+ clientOnly: true,
100
+ });
101
+ /**
102
+ * Used to reset the game state.
103
+ * @param {object} state - The initial state.
104
+ */
105
+ const reset = (state) => ({
106
+ type: RESET,
107
+ state,
108
+ clientOnly: true,
109
+ });
110
+ /**
111
+ * Used to undo the last move.
112
+ * @param {string} playerID - The ID of the player making this action.
113
+ * @param {string} credentials - (optional) The credentials for the player making this action.
114
+ */
115
+ const undo = (playerID, credentials) => ({
116
+ type: UNDO,
117
+ payload: { type: null, args: null, playerID, credentials },
118
+ });
119
+ /**
120
+ * Used to redo the last undone move.
121
+ * @param {string} playerID - The ID of the player making this action.
122
+ * @param {string} credentials - (optional) The credentials for the player making this action.
123
+ */
124
+ const redo = (playerID, credentials) => ({
125
+ type: REDO,
126
+ payload: { type: null, args: null, playerID, credentials },
127
+ });
128
+ /**
129
+ * Allows plugins to define their own actions and intercept them.
130
+ */
131
+ const plugin = (type, args, playerID, credentials) => ({
132
+ type: PLUGIN,
133
+ payload: { type, args, playerID, credentials },
134
+ });
135
+ /**
136
+ * Private action used to strip transient metadata (e.g. errors) from the game
137
+ * state.
138
+ */
139
+ const stripTransients = () => ({
140
+ type: STRIP_TRANSIENTS,
141
+ });
142
+
143
+ var ActionCreators = /*#__PURE__*/Object.freeze({
144
+ __proto__: null,
145
+ makeMove: makeMove,
146
+ gameEvent: gameEvent,
147
+ automaticGameEvent: automaticGameEvent,
148
+ sync: sync,
149
+ patch: patch,
150
+ update: update,
151
+ reset: reset,
152
+ undo: undo,
153
+ redo: redo,
154
+ plugin: plugin,
155
+ stripTransients: stripTransients
156
+ });
157
+
158
+ /**
159
+ * Moves can return this when they want to indicate
160
+ * that the combination of arguments is illegal and
161
+ * the move ought to be discarded.
162
+ */
163
+ const INVALID_MOVE = 'INVALID_MOVE';
164
+
165
+ /*
166
+ * Copyright 2018 The boardgame.io Authors
167
+ *
168
+ * Use of this source code is governed by a MIT-style
169
+ * license that can be found in the LICENSE file or at
170
+ * https://opensource.org/licenses/MIT.
171
+ */
172
+ /**
173
+ * Plugin that allows using Immer to make immutable changes
174
+ * to G by just mutating it.
175
+ */
176
+ const ImmerPlugin = {
177
+ name: 'plugin-immer',
178
+ fnWrap: (move) => (context, ...args) => {
179
+ let isInvalid = false;
180
+ const newG = produce(context.G, (G) => {
181
+ const result = move({ ...context, G }, ...args);
182
+ if (result === INVALID_MOVE) {
183
+ isInvalid = true;
184
+ return;
185
+ }
186
+ return result;
187
+ });
188
+ if (isInvalid)
189
+ return INVALID_MOVE;
190
+ return newG;
191
+ },
192
+ };
193
+
194
+ var GameMethod;
195
+ (function (GameMethod) {
196
+ GameMethod["MOVE"] = "MOVE";
197
+ GameMethod["GAME_ON_END"] = "GAME_ON_END";
198
+ GameMethod["PHASE_ON_BEGIN"] = "PHASE_ON_BEGIN";
199
+ GameMethod["PHASE_ON_END"] = "PHASE_ON_END";
200
+ GameMethod["TURN_ON_BEGIN"] = "TURN_ON_BEGIN";
201
+ GameMethod["TURN_ON_MOVE"] = "TURN_ON_MOVE";
202
+ GameMethod["TURN_ON_END"] = "TURN_ON_END";
203
+ })(GameMethod || (GameMethod = {}));
204
+
205
+ /*
206
+ * Copyright 2018 The boardgame.io Authors
207
+ *
208
+ * Use of this source code is governed by a MIT-style
209
+ * license that can be found in the LICENSE file or at
210
+ * https://opensource.org/licenses/MIT.
211
+ */
212
+ var Errors;
213
+ (function (Errors) {
214
+ Errors["CalledOutsideHook"] = "Events must be called from moves or the `onBegin`, `onEnd`, and `onMove` hooks.\nThis error probably means you called an event from other game code, like an `endIf` trigger or one of the `turn.order` methods.";
215
+ Errors["EndTurnInOnEnd"] = "`endTurn` is disallowed in `onEnd` hooks \u2014 the turn is already ending.";
216
+ Errors["MaxTurnEndings"] = "Maximum number of turn endings exceeded for this update.\nThis likely means game code is triggering an infinite loop.";
217
+ Errors["PhaseEventInOnEnd"] = "`setPhase` & `endPhase` are disallowed in a phase\u2019s `onEnd` hook \u2014 the phase is already ending.\nIf you\u2019re trying to dynamically choose the next phase when a phase ends, use the phase\u2019s `next` trigger.";
218
+ Errors["StageEventInOnEnd"] = "`setStage`, `endStage` & `setActivePlayers` are disallowed in `onEnd` hooks.";
219
+ Errors["StageEventInPhaseBegin"] = "`setStage`, `endStage` & `setActivePlayers` are disallowed in a phase\u2019s `onBegin` hook.\nUse `setActivePlayers` in a `turn.onBegin` hook or declare stages with `turn.activePlayers` instead.";
220
+ Errors["StageEventInTurnBegin"] = "`setStage` & `endStage` are disallowed in `turn.onBegin`.\nUse `setActivePlayers` or declare stages with `turn.activePlayers` instead.";
221
+ })(Errors || (Errors = {}));
222
+ /**
223
+ * Events
224
+ */
225
+ class Events {
226
+ constructor(flow, ctx, playerID) {
227
+ this.flow = flow;
228
+ this.playerID = playerID;
229
+ this.dispatch = [];
230
+ this.initialTurn = ctx.turn;
231
+ this.updateTurnContext(ctx, undefined);
232
+ // This is an arbitrarily large upper threshold, which could be made
233
+ // configurable via a game option if the need arises.
234
+ this.maxEndedTurnsPerAction = ctx.numPlayers * 100;
235
+ }
236
+ api() {
237
+ const events = {
238
+ _private: this,
239
+ };
240
+ for (const type of this.flow.eventNames) {
241
+ events[type] = (...args) => {
242
+ this.dispatch.push({
243
+ type,
244
+ args,
245
+ phase: this.currentPhase,
246
+ turn: this.currentTurn,
247
+ calledFrom: this.currentMethod,
248
+ // Used to capture a stack trace in case it is needed later.
249
+ error: new Error('Events Plugin Error'),
250
+ });
251
+ };
252
+ }
253
+ return events;
254
+ }
255
+ isUsed() {
256
+ return this.dispatch.length > 0;
257
+ }
258
+ updateTurnContext(ctx, methodType) {
259
+ this.currentPhase = ctx.phase;
260
+ this.currentTurn = ctx.turn;
261
+ this.currentMethod = methodType;
262
+ }
263
+ unsetCurrentMethod() {
264
+ this.currentMethod = undefined;
265
+ }
266
+ /**
267
+ * Updates ctx with the triggered events.
268
+ * @param {object} state - The state object { G, ctx }.
269
+ */
270
+ update(state) {
271
+ const initialState = state;
272
+ const stateWithError = ({ stack }, message) => ({
273
+ ...initialState,
274
+ plugins: {
275
+ ...initialState.plugins,
276
+ events: {
277
+ ...initialState.plugins.events,
278
+ data: { error: message + '\n' + stack },
279
+ },
280
+ },
281
+ });
282
+ EventQueue: for (let i = 0; i < this.dispatch.length; i++) {
283
+ const event = this.dispatch[i];
284
+ const turnHasEnded = event.turn !== state.ctx.turn;
285
+ // This protects against potential infinite loops if specific events are called on hooks.
286
+ // The moment we exceed the defined threshold, we just bail out of all phases.
287
+ const endedTurns = this.currentTurn - this.initialTurn;
288
+ if (endedTurns >= this.maxEndedTurnsPerAction) {
289
+ return stateWithError(event.error, Errors.MaxTurnEndings);
290
+ }
291
+ if (event.calledFrom === undefined) {
292
+ return stateWithError(event.error, Errors.CalledOutsideHook);
293
+ }
294
+ // Stop processing events once the game has finished.
295
+ if (state.ctx.gameover)
296
+ break EventQueue;
297
+ switch (event.type) {
298
+ case 'endStage':
299
+ case 'setStage':
300
+ case 'setActivePlayers': {
301
+ switch (event.calledFrom) {
302
+ // Disallow all stage events in onEnd and phase.onBegin hooks.
303
+ case GameMethod.TURN_ON_END:
304
+ case GameMethod.PHASE_ON_END:
305
+ return stateWithError(event.error, Errors.StageEventInOnEnd);
306
+ case GameMethod.PHASE_ON_BEGIN:
307
+ return stateWithError(event.error, Errors.StageEventInPhaseBegin);
308
+ // Disallow setStage & endStage in turn.onBegin hooks.
309
+ case GameMethod.TURN_ON_BEGIN:
310
+ if (event.type === 'setActivePlayers')
311
+ break;
312
+ return stateWithError(event.error, Errors.StageEventInTurnBegin);
313
+ }
314
+ // If the turn already ended, don't try to process stage events.
315
+ if (turnHasEnded)
316
+ continue EventQueue;
317
+ break;
318
+ }
319
+ case 'endTurn': {
320
+ if (event.calledFrom === GameMethod.TURN_ON_END ||
321
+ event.calledFrom === GameMethod.PHASE_ON_END) {
322
+ return stateWithError(event.error, Errors.EndTurnInOnEnd);
323
+ }
324
+ // If the turn already ended some other way,
325
+ // don't try to end the turn again.
326
+ if (turnHasEnded)
327
+ continue EventQueue;
328
+ break;
329
+ }
330
+ case 'endPhase':
331
+ case 'setPhase': {
332
+ if (event.calledFrom === GameMethod.PHASE_ON_END) {
333
+ return stateWithError(event.error, Errors.PhaseEventInOnEnd);
334
+ }
335
+ // If the phase already ended some other way,
336
+ // don't try to end the phase again.
337
+ if (event.phase !== state.ctx.phase)
338
+ continue EventQueue;
339
+ break;
340
+ }
341
+ }
342
+ const action = automaticGameEvent(event.type, event.args, this.playerID);
343
+ state = this.flow.processEvent(state, action);
344
+ }
345
+ return state;
346
+ }
347
+ }
348
+
349
+ /*
350
+ * Copyright 2020 The boardgame.io Authors
351
+ *
352
+ * Use of this source code is governed by a MIT-style
353
+ * license that can be found in the LICENSE file or at
354
+ * https://opensource.org/licenses/MIT.
355
+ */
356
+ const EventsPlugin = {
357
+ name: 'events',
358
+ noClient: ({ api }) => api._private.isUsed(),
359
+ isInvalid: ({ data }) => data.error || false,
360
+ // Update the events plugin’s internal turn context each time a move
361
+ // or hook is called. This allows events called after turn or phase
362
+ // endings to dispatch the current turn and phase correctly.
363
+ fnWrap: (method, methodType) => (context, ...args) => {
364
+ const api = context.events;
365
+ if (api)
366
+ api._private.updateTurnContext(context.ctx, methodType);
367
+ const G = method(context, ...args);
368
+ if (api)
369
+ api._private.unsetCurrentMethod();
370
+ return G;
371
+ },
372
+ dangerouslyFlushRawState: ({ state, api }) => api._private.update(state),
373
+ api: ({ game, ctx, playerID }) => new Events(game.flow, ctx, playerID).api(),
374
+ };
375
+
376
+ /*
377
+ * Copyright 2018 The boardgame.io Authors
378
+ *
379
+ * Use of this source code is governed by a MIT-style
380
+ * license that can be found in the LICENSE file or at
381
+ * https://opensource.org/licenses/MIT.
382
+ */
383
+ /**
384
+ * Plugin that makes it possible to add metadata to log entries.
385
+ * During a move, you can set metadata using ctx.log.setMetadata and it will be
386
+ * available on the log entry for that move.
387
+ */
388
+ const LogPlugin = {
389
+ name: 'log',
390
+ flush: () => ({}),
391
+ api: ({ data }) => {
392
+ return {
393
+ setMetadata: (metadata) => {
394
+ data.metadata = metadata;
395
+ },
396
+ };
397
+ },
398
+ setup: () => ({}),
399
+ };
400
+
401
+ /**
402
+ * Check if a value can be serialized (e.g. using `JSON.stringify`).
403
+ * Adapted from: https://stackoverflow.com/a/30712764/3829557
404
+ */
405
+ function isSerializable(value) {
406
+ // Primitives are OK.
407
+ if (value === undefined ||
408
+ value === null ||
409
+ typeof value === 'boolean' ||
410
+ typeof value === 'number' ||
411
+ typeof value === 'string') {
412
+ return true;
413
+ }
414
+ // A non-primitive value that is neither a POJO or an array cannot be serialized.
415
+ if (!isPlainObject(value) && !Array.isArray(value)) {
416
+ return false;
417
+ }
418
+ // Recurse entries if the value is an object or array.
419
+ for (const key in value) {
420
+ if (!isSerializable(value[key]))
421
+ return false;
422
+ }
423
+ return true;
424
+ }
425
+ /**
426
+ * Plugin that checks whether state is serializable, in order to avoid
427
+ * network serialization bugs.
428
+ */
429
+ const SerializablePlugin = {
430
+ name: 'plugin-serializable',
431
+ fnWrap: (move) => (context, ...args) => {
432
+ const result = move(context, ...args);
433
+ // Check state in non-production environments.
434
+ if (process.env.NODE_ENV !== 'production' && !isSerializable(result)) {
435
+ throw new Error('Move state is not JSON-serialiazable.\n' +
436
+ 'See https://boardgame.io/documentation/#/?id=state for more information.');
437
+ }
438
+ return result;
439
+ },
440
+ };
441
+
442
+ /*
443
+ * Copyright 2018 The boardgame.io Authors
444
+ *
445
+ * Use of this source code is governed by a MIT-style
446
+ * license that can be found in the LICENSE file or at
447
+ * https://opensource.org/licenses/MIT.
448
+ */
449
+ const production = process.env.NODE_ENV === 'production';
450
+ const logfn = production ? () => { } : (...msg) => console.log(...msg);
451
+ const errorfn = (...msg) => console.error(...msg);
452
+ function info(msg) {
453
+ logfn(`INFO: ${msg}`);
454
+ }
455
+ function error(error) {
456
+ errorfn('ERROR:', error);
457
+ }
458
+
459
+ /*
460
+ * Copyright 2018 The boardgame.io Authors
461
+ *
462
+ * Use of this source code is governed by a MIT-style
463
+ * license that can be found in the LICENSE file or at
464
+ * https://opensource.org/licenses/MIT.
465
+ */
466
+ /**
467
+ * List of plugins that are always added.
468
+ */
469
+ const CORE_PLUGINS = [ImmerPlugin, RandomPlugin, LogPlugin, SerializablePlugin];
470
+ const DEFAULT_PLUGINS = [...CORE_PLUGINS, EventsPlugin];
471
+ /**
472
+ * Allow plugins to intercept actions and process them.
473
+ */
474
+ const ProcessAction = (state, action, opts) => {
475
+ // TODO(#723): Extend error handling to plugins.
476
+ opts.game.plugins
477
+ .filter((plugin) => plugin.action !== undefined)
478
+ .filter((plugin) => plugin.name === action.payload.type)
479
+ .forEach((plugin) => {
480
+ const name = plugin.name;
481
+ const pluginState = state.plugins[name] || { data: {} };
482
+ const data = plugin.action(pluginState.data, action.payload);
483
+ state = {
484
+ ...state,
485
+ plugins: {
486
+ ...state.plugins,
487
+ [name]: { ...pluginState, data },
488
+ },
489
+ };
490
+ });
491
+ return state;
492
+ };
493
+ /**
494
+ * The APIs created by various plugins are stored in the plugins
495
+ * section of the state object:
496
+ *
497
+ * {
498
+ * G: {},
499
+ * ctx: {},
500
+ * plugins: {
501
+ * plugin-a: {
502
+ * data: {}, // this is generated by the plugin at Setup / Flush.
503
+ * api: {}, // this is ephemeral and generated by Enhance.
504
+ * }
505
+ * }
506
+ * }
507
+ *
508
+ * This function retrieves plugin APIs and returns them as an object
509
+ * for consumption as used by move contexts.
510
+ */
511
+ const GetAPIs = ({ plugins }) => Object.entries(plugins || {}).reduce((apis, [name, { api }]) => {
512
+ apis[name] = api;
513
+ return apis;
514
+ }, {});
515
+ /**
516
+ * Applies the provided plugins to the given move / flow function.
517
+ *
518
+ * @param methodToWrap - The move function or hook to apply the plugins to.
519
+ * @param methodType - The type of the move or hook being wrapped.
520
+ * @param plugins - The list of plugins.
521
+ */
522
+ const FnWrap = (methodToWrap, methodType, plugins) => {
523
+ return [...CORE_PLUGINS, ...plugins, EventsPlugin]
524
+ .filter((plugin) => plugin.fnWrap !== undefined)
525
+ .reduce((method, { fnWrap }) => fnWrap(method, methodType), methodToWrap);
526
+ };
527
+ /**
528
+ * Allows the plugin to generate its initial state.
529
+ */
530
+ const Setup = (state, opts) => {
531
+ [...DEFAULT_PLUGINS, ...opts.game.plugins]
532
+ .filter((plugin) => plugin.setup !== undefined)
533
+ .forEach((plugin) => {
534
+ const name = plugin.name;
535
+ const data = plugin.setup({
536
+ G: state.G,
537
+ ctx: state.ctx,
538
+ game: opts.game,
539
+ });
540
+ state = {
541
+ ...state,
542
+ plugins: {
543
+ ...state.plugins,
544
+ [name]: { data },
545
+ },
546
+ };
547
+ });
548
+ return state;
549
+ };
550
+ /**
551
+ * Invokes the plugin before a move or event.
552
+ * The API that the plugin generates is stored inside
553
+ * the `plugins` section of the state (which is subsequently
554
+ * merged into ctx).
555
+ */
556
+ const Enhance = (state, opts) => {
557
+ [...DEFAULT_PLUGINS, ...opts.game.plugins]
558
+ .filter((plugin) => plugin.api !== undefined)
559
+ .forEach((plugin) => {
560
+ const name = plugin.name;
561
+ const pluginState = state.plugins[name] || { data: {} };
562
+ const api = plugin.api({
563
+ G: state.G,
564
+ ctx: state.ctx,
565
+ data: pluginState.data,
566
+ game: opts.game,
567
+ playerID: opts.playerID,
568
+ });
569
+ state = {
570
+ ...state,
571
+ plugins: {
572
+ ...state.plugins,
573
+ [name]: { ...pluginState, api },
574
+ },
575
+ };
576
+ });
577
+ return state;
578
+ };
579
+ /**
580
+ * Allows plugins to update their state after a move / event.
581
+ */
582
+ const Flush = (state, opts) => {
583
+ // We flush the events plugin first, then custom plugins and the core plugins.
584
+ // This means custom plugins cannot use the events API but will be available in event hooks.
585
+ // Note that plugins are flushed in reverse, to allow custom plugins calling each other.
586
+ [...CORE_PLUGINS, ...opts.game.plugins, EventsPlugin]
587
+ .reverse()
588
+ .forEach((plugin) => {
589
+ const name = plugin.name;
590
+ const pluginState = state.plugins[name] || { data: {} };
591
+ if (plugin.flush) {
592
+ const newData = plugin.flush({
593
+ G: state.G,
594
+ ctx: state.ctx,
595
+ game: opts.game,
596
+ api: pluginState.api,
597
+ data: pluginState.data,
598
+ });
599
+ state = {
600
+ ...state,
601
+ plugins: {
602
+ ...state.plugins,
603
+ [plugin.name]: { data: newData },
604
+ },
605
+ };
606
+ }
607
+ else if (plugin.dangerouslyFlushRawState) {
608
+ state = plugin.dangerouslyFlushRawState({
609
+ state,
610
+ game: opts.game,
611
+ api: pluginState.api,
612
+ data: pluginState.data,
613
+ });
614
+ // Remove everything other than data.
615
+ const data = state.plugins[name].data;
616
+ state = {
617
+ ...state,
618
+ plugins: {
619
+ ...state.plugins,
620
+ [plugin.name]: { data },
621
+ },
622
+ };
623
+ }
624
+ });
625
+ return state;
626
+ };
627
+ /**
628
+ * Allows plugins to indicate if they should not be materialized on the client.
629
+ * This will cause the client to discard the state update and wait for the
630
+ * master instead.
631
+ */
632
+ const NoClient = (state, opts) => {
633
+ return [...DEFAULT_PLUGINS, ...opts.game.plugins]
634
+ .filter((plugin) => plugin.noClient !== undefined)
635
+ .map((plugin) => {
636
+ const name = plugin.name;
637
+ const pluginState = state.plugins[name];
638
+ if (pluginState) {
639
+ return plugin.noClient({
640
+ G: state.G,
641
+ ctx: state.ctx,
642
+ game: opts.game,
643
+ api: pluginState.api,
644
+ data: pluginState.data,
645
+ });
646
+ }
647
+ return false;
648
+ })
649
+ .includes(true);
650
+ };
651
+ /**
652
+ * Allows plugins to indicate if the entire action should be thrown out
653
+ * as invalid. This will cancel the entire state update.
654
+ */
655
+ const IsInvalid = (state, opts) => {
656
+ const firstInvalidReturn = [...DEFAULT_PLUGINS, ...opts.game.plugins]
657
+ .filter((plugin) => plugin.isInvalid !== undefined)
658
+ .map((plugin) => {
659
+ const { name } = plugin;
660
+ const pluginState = state.plugins[name];
661
+ const message = plugin.isInvalid({
662
+ G: state.G,
663
+ ctx: state.ctx,
664
+ game: opts.game,
665
+ data: pluginState && pluginState.data,
666
+ });
667
+ return message ? { plugin: name, message } : false;
668
+ })
669
+ .find((value) => value);
670
+ return firstInvalidReturn || false;
671
+ };
672
+ /**
673
+ * Update plugin state after move/event & check if plugins consider the update to be valid.
674
+ * @returns Tuple of `[updatedState]` or `[originalState, invalidError]`.
675
+ */
676
+ const FlushAndValidate = (state, opts) => {
677
+ const updatedState = Flush(state, opts);
678
+ const isInvalid = IsInvalid(updatedState, opts);
679
+ if (!isInvalid)
680
+ return [updatedState];
681
+ const { plugin, message } = isInvalid;
682
+ error(`${plugin} plugin declared action invalid:\n${message}`);
683
+ return [state, isInvalid];
684
+ };
685
+ /**
686
+ * Allows plugins to customize their data for specific players.
687
+ * For example, a plugin may want to share no data with the client, or
688
+ * want to keep some player data secret from opponents.
689
+ */
690
+ const PlayerView = ({ G, ctx, plugins = {} }, { game, playerID }) => {
691
+ [...DEFAULT_PLUGINS, ...game.plugins].forEach(({ name, playerView }) => {
692
+ if (!playerView)
693
+ return;
694
+ const { data } = plugins[name] || { data: {} };
695
+ const newData = playerView({ G, ctx, game, data, playerID });
696
+ plugins = {
697
+ ...plugins,
698
+ [name]: { data: newData },
699
+ };
700
+ });
701
+ return plugins;
702
+ };
703
+
704
+ /**
705
+ * Adjust the given options to use the new minMoves/maxMoves if a legacy moveLimit was given
706
+ * @param options The options object to apply backwards compatibility to
707
+ * @param enforceMinMoves Use moveLimit to set both minMoves and maxMoves
708
+ */
709
+ function supportDeprecatedMoveLimit(options, enforceMinMoves = false) {
710
+ if (options.moveLimit) {
711
+ if (enforceMinMoves) {
712
+ options.minMoves = options.moveLimit;
713
+ }
714
+ options.maxMoves = options.moveLimit;
715
+ delete options.moveLimit;
716
+ }
717
+ }
718
+
719
+ /*
720
+ * Copyright 2017 The boardgame.io Authors
721
+ *
722
+ * Use of this source code is governed by a MIT-style
723
+ * license that can be found in the LICENSE file or at
724
+ * https://opensource.org/licenses/MIT.
725
+ */
726
+ function SetActivePlayers(ctx, arg) {
727
+ let activePlayers = {};
728
+ let _prevActivePlayers = [];
729
+ let _nextActivePlayers = null;
730
+ let _activePlayersMinMoves = {};
731
+ let _activePlayersMaxMoves = {};
732
+ if (Array.isArray(arg)) {
733
+ // support a simple array of player IDs as active players
734
+ const value = {};
735
+ arg.forEach((v) => (value[v] = Stage.NULL));
736
+ activePlayers = value;
737
+ }
738
+ else {
739
+ // process active players argument object
740
+ // stages previously did not enforce minMoves, this behaviour is kept intentionally
741
+ supportDeprecatedMoveLimit(arg);
742
+ if (arg.next) {
743
+ _nextActivePlayers = arg.next;
744
+ }
745
+ if (arg.revert) {
746
+ _prevActivePlayers = [
747
+ ...ctx._prevActivePlayers,
748
+ {
749
+ activePlayers: ctx.activePlayers,
750
+ _activePlayersMinMoves: ctx._activePlayersMinMoves,
751
+ _activePlayersMaxMoves: ctx._activePlayersMaxMoves,
752
+ _activePlayersNumMoves: ctx._activePlayersNumMoves,
753
+ },
754
+ ];
755
+ }
756
+ if (arg.currentPlayer !== undefined) {
757
+ ApplyActivePlayerArgument(activePlayers, _activePlayersMinMoves, _activePlayersMaxMoves, ctx.currentPlayer, arg.currentPlayer);
758
+ }
759
+ if (arg.others !== undefined) {
760
+ for (let i = 0; i < ctx.playOrder.length; i++) {
761
+ const id = ctx.playOrder[i];
762
+ if (id !== ctx.currentPlayer) {
763
+ ApplyActivePlayerArgument(activePlayers, _activePlayersMinMoves, _activePlayersMaxMoves, id, arg.others);
764
+ }
765
+ }
766
+ }
767
+ if (arg.all !== undefined) {
768
+ for (let i = 0; i < ctx.playOrder.length; i++) {
769
+ const id = ctx.playOrder[i];
770
+ ApplyActivePlayerArgument(activePlayers, _activePlayersMinMoves, _activePlayersMaxMoves, id, arg.all);
771
+ }
772
+ }
773
+ if (arg.value) {
774
+ for (const id in arg.value) {
775
+ ApplyActivePlayerArgument(activePlayers, _activePlayersMinMoves, _activePlayersMaxMoves, id, arg.value[id]);
776
+ }
777
+ }
778
+ if (arg.minMoves) {
779
+ for (const id in activePlayers) {
780
+ if (_activePlayersMinMoves[id] === undefined) {
781
+ _activePlayersMinMoves[id] = arg.minMoves;
782
+ }
783
+ }
784
+ }
785
+ if (arg.maxMoves) {
786
+ for (const id in activePlayers) {
787
+ if (_activePlayersMaxMoves[id] === undefined) {
788
+ _activePlayersMaxMoves[id] = arg.maxMoves;
789
+ }
790
+ }
791
+ }
792
+ }
793
+ if (Object.keys(activePlayers).length === 0) {
794
+ activePlayers = null;
795
+ }
796
+ if (Object.keys(_activePlayersMinMoves).length === 0) {
797
+ _activePlayersMinMoves = null;
798
+ }
799
+ if (Object.keys(_activePlayersMaxMoves).length === 0) {
800
+ _activePlayersMaxMoves = null;
801
+ }
802
+ const _activePlayersNumMoves = {};
803
+ for (const id in activePlayers) {
804
+ _activePlayersNumMoves[id] = 0;
805
+ }
806
+ return {
807
+ ...ctx,
808
+ activePlayers,
809
+ _activePlayersMinMoves,
810
+ _activePlayersMaxMoves,
811
+ _activePlayersNumMoves,
812
+ _prevActivePlayers,
813
+ _nextActivePlayers,
814
+ };
815
+ }
816
+ /**
817
+ * Update activePlayers, setting it to previous, next or null values
818
+ * when it becomes empty.
819
+ * @param ctx
820
+ */
821
+ function UpdateActivePlayersOnceEmpty(ctx) {
822
+ let { activePlayers, _activePlayersMinMoves, _activePlayersMaxMoves, _activePlayersNumMoves, _prevActivePlayers, _nextActivePlayers, } = ctx;
823
+ if (activePlayers && Object.keys(activePlayers).length === 0) {
824
+ if (_nextActivePlayers) {
825
+ ctx = SetActivePlayers(ctx, _nextActivePlayers);
826
+ ({
827
+ activePlayers,
828
+ _activePlayersMinMoves,
829
+ _activePlayersMaxMoves,
830
+ _activePlayersNumMoves,
831
+ _prevActivePlayers,
832
+ } = ctx);
833
+ }
834
+ else if (_prevActivePlayers.length > 0) {
835
+ const lastIndex = _prevActivePlayers.length - 1;
836
+ ({
837
+ activePlayers,
838
+ _activePlayersMinMoves,
839
+ _activePlayersMaxMoves,
840
+ _activePlayersNumMoves,
841
+ } = _prevActivePlayers[lastIndex]);
842
+ _prevActivePlayers = _prevActivePlayers.slice(0, lastIndex);
843
+ }
844
+ else {
845
+ activePlayers = null;
846
+ _activePlayersMinMoves = null;
847
+ _activePlayersMaxMoves = null;
848
+ }
849
+ }
850
+ return {
851
+ ...ctx,
852
+ activePlayers,
853
+ _activePlayersMinMoves,
854
+ _activePlayersMaxMoves,
855
+ _activePlayersNumMoves,
856
+ _prevActivePlayers,
857
+ };
858
+ }
859
+ /**
860
+ * Apply an active player argument to the given player ID
861
+ * @param {Object} activePlayers
862
+ * @param {Object} _activePlayersMinMoves
863
+ * @param {Object} _activePlayersMaxMoves
864
+ * @param {String} playerID The player to apply the parameter to
865
+ * @param {(String|Object)} arg An active player argument
866
+ */
867
+ function ApplyActivePlayerArgument(activePlayers, _activePlayersMinMoves, _activePlayersMaxMoves, playerID, arg) {
868
+ if (typeof arg !== 'object' || arg === Stage.NULL) {
869
+ arg = { stage: arg };
870
+ }
871
+ if (arg.stage !== undefined) {
872
+ // stages previously did not enforce minMoves, this behaviour is kept intentionally
873
+ supportDeprecatedMoveLimit(arg);
874
+ activePlayers[playerID] = arg.stage;
875
+ if (arg.minMoves)
876
+ _activePlayersMinMoves[playerID] = arg.minMoves;
877
+ if (arg.maxMoves)
878
+ _activePlayersMaxMoves[playerID] = arg.maxMoves;
879
+ }
880
+ }
881
+ /**
882
+ * Converts a playOrderPos index into its value in playOrder.
883
+ * @param {Array} playOrder - An array of player ID's.
884
+ * @param {number} playOrderPos - An index into the above.
885
+ */
886
+ function getCurrentPlayer(playOrder, playOrderPos) {
887
+ // convert to string in case playOrder is set to number[]
888
+ return playOrder[playOrderPos] + '';
889
+ }
890
+ /**
891
+ * Called at the start of a turn to initialize turn order state.
892
+ *
893
+ * TODO: This is called inside StartTurn, which is called from
894
+ * both UpdateTurn and StartPhase (so it's called at the beginning
895
+ * of a new phase as well as between turns). We should probably
896
+ * split it into two.
897
+ */
898
+ function InitTurnOrderState(state, turn) {
899
+ let { G, ctx } = state;
900
+ const { numPlayers } = ctx;
901
+ const pluginAPIs = GetAPIs(state);
902
+ const context = { ...pluginAPIs, G, ctx };
903
+ const order = turn.order;
904
+ let playOrder = [...Array.from({ length: numPlayers })].map((_, i) => i + '');
905
+ if (order.playOrder !== undefined) {
906
+ playOrder = order.playOrder(context);
907
+ }
908
+ const playOrderPos = order.first(context);
909
+ const posType = typeof playOrderPos;
910
+ if (posType !== 'number') {
911
+ error(`invalid value returned by turn.order.first — expected number got ${posType} “${playOrderPos}”.`);
912
+ }
913
+ const currentPlayer = getCurrentPlayer(playOrder, playOrderPos);
914
+ ctx = { ...ctx, currentPlayer, playOrderPos, playOrder };
915
+ ctx = SetActivePlayers(ctx, turn.activePlayers || {});
916
+ return ctx;
917
+ }
918
+ /**
919
+ * Called at the end of each turn to update the turn order state.
920
+ * @param {object} G - The game object G.
921
+ * @param {object} ctx - The game object ctx.
922
+ * @param {object} turn - A turn object for this phase.
923
+ * @param {string} endTurnArg - An optional argument to endTurn that
924
+ may specify the next player.
925
+ */
926
+ function UpdateTurnOrderState(state, currentPlayer, turn, endTurnArg) {
927
+ const order = turn.order;
928
+ let { G, ctx } = state;
929
+ let playOrderPos = ctx.playOrderPos;
930
+ let endPhase = false;
931
+ if (endTurnArg && endTurnArg !== true) {
932
+ if (typeof endTurnArg !== 'object') {
933
+ error(`invalid argument to endTurn: ${endTurnArg}`);
934
+ }
935
+ Object.keys(endTurnArg).forEach((arg) => {
936
+ switch (arg) {
937
+ case 'remove':
938
+ currentPlayer = getCurrentPlayer(ctx.playOrder, playOrderPos);
939
+ break;
940
+ case 'next':
941
+ playOrderPos = ctx.playOrder.indexOf(endTurnArg.next);
942
+ currentPlayer = endTurnArg.next;
943
+ break;
944
+ default:
945
+ error(`invalid argument to endTurn: ${arg}`);
946
+ }
947
+ });
948
+ }
949
+ else {
950
+ const pluginAPIs = GetAPIs(state);
951
+ const context = { ...pluginAPIs, G, ctx };
952
+ const t = order.next(context);
953
+ const type = typeof t;
954
+ if (t !== undefined && type !== 'number') {
955
+ error(`invalid value returned by turn.order.next — expected number or undefined got ${type} “${t}”.`);
956
+ }
957
+ if (t === undefined) {
958
+ endPhase = true;
959
+ }
960
+ else {
961
+ playOrderPos = t;
962
+ currentPlayer = getCurrentPlayer(ctx.playOrder, playOrderPos);
963
+ }
964
+ }
965
+ ctx = {
966
+ ...ctx,
967
+ playOrderPos,
968
+ currentPlayer,
969
+ };
970
+ return { endPhase, ctx };
971
+ }
972
+ /**
973
+ * Set of different turn orders possible in a phase.
974
+ * These are meant to be passed to the `turn` setting
975
+ * in the flow objects.
976
+ *
977
+ * Each object defines the first player when the phase / game
978
+ * begins, and also a function `next` to determine who the
979
+ * next player is when the turn ends.
980
+ *
981
+ * The phase ends if next() returns undefined.
982
+ */
983
+ const TurnOrder = {
984
+ /**
985
+ * DEFAULT
986
+ *
987
+ * The default round-robin turn order.
988
+ */
989
+ DEFAULT: {
990
+ first: ({ ctx }) => ctx.turn === 0
991
+ ? ctx.playOrderPos
992
+ : (ctx.playOrderPos + 1) % ctx.playOrder.length,
993
+ next: ({ ctx }) => (ctx.playOrderPos + 1) % ctx.playOrder.length,
994
+ },
995
+ /**
996
+ * RESET
997
+ *
998
+ * Similar to DEFAULT, but starts from 0 each time.
999
+ */
1000
+ RESET: {
1001
+ first: () => 0,
1002
+ next: ({ ctx }) => (ctx.playOrderPos + 1) % ctx.playOrder.length,
1003
+ },
1004
+ /**
1005
+ * CONTINUE
1006
+ *
1007
+ * Similar to DEFAULT, but starts with the player who ended the last phase.
1008
+ */
1009
+ CONTINUE: {
1010
+ first: ({ ctx }) => ctx.playOrderPos,
1011
+ next: ({ ctx }) => (ctx.playOrderPos + 1) % ctx.playOrder.length,
1012
+ },
1013
+ /**
1014
+ * ONCE
1015
+ *
1016
+ * Another round-robin turn order, but goes around just once.
1017
+ * The phase ends after all players have played.
1018
+ */
1019
+ ONCE: {
1020
+ first: () => 0,
1021
+ next: ({ ctx }) => {
1022
+ if (ctx.playOrderPos < ctx.playOrder.length - 1) {
1023
+ return ctx.playOrderPos + 1;
1024
+ }
1025
+ },
1026
+ },
1027
+ /**
1028
+ * CUSTOM
1029
+ *
1030
+ * Identical to DEFAULT, but also sets playOrder at the
1031
+ * beginning of the phase.
1032
+ *
1033
+ * @param {Array} playOrder - The play order.
1034
+ */
1035
+ CUSTOM: (playOrder) => ({
1036
+ playOrder: () => playOrder,
1037
+ first: () => 0,
1038
+ next: ({ ctx }) => (ctx.playOrderPos + 1) % ctx.playOrder.length,
1039
+ }),
1040
+ /**
1041
+ * CUSTOM_FROM
1042
+ *
1043
+ * Identical to DEFAULT, but also sets playOrder at the
1044
+ * beginning of the phase to a value specified by a field
1045
+ * in G.
1046
+ *
1047
+ * @param {string} playOrderField - Field in G.
1048
+ */
1049
+ CUSTOM_FROM: (playOrderField) => ({
1050
+ playOrder: ({ G }) => G[playOrderField],
1051
+ first: () => 0,
1052
+ next: ({ ctx }) => (ctx.playOrderPos + 1) % ctx.playOrder.length,
1053
+ }),
1054
+ };
1055
+ const Stage = {
1056
+ NULL: null,
1057
+ };
1058
+ const ActivePlayers = {
1059
+ /**
1060
+ * ALL
1061
+ *
1062
+ * The turn stays with one player, but any player can play (in any order)
1063
+ * until the phase ends.
1064
+ */
1065
+ ALL: { all: Stage.NULL },
1066
+ /**
1067
+ * ALL_ONCE
1068
+ *
1069
+ * The turn stays with one player, but any player can play (once, and in any order).
1070
+ * This is typically used in a phase where you want to elicit a response
1071
+ * from every player in the game.
1072
+ */
1073
+ ALL_ONCE: { all: Stage.NULL, minMoves: 1, maxMoves: 1 },
1074
+ /**
1075
+ * OTHERS
1076
+ *
1077
+ * The turn stays with one player, and every *other* player can play (in any order)
1078
+ * until the phase ends.
1079
+ */
1080
+ OTHERS: { others: Stage.NULL },
1081
+ /**
1082
+ * OTHERS_ONCE
1083
+ *
1084
+ * The turn stays with one player, and every *other* player can play (once, and in any order).
1085
+ * This is typically used in a phase where you want to elicit a response
1086
+ * from every *other* player in the game.
1087
+ */
1088
+ OTHERS_ONCE: { others: Stage.NULL, minMoves: 1, maxMoves: 1 },
1089
+ };
1090
+
1091
+ export { ActionCreators as A, makeMove as B, ActivePlayers as C, Enhance as E, FnWrap as F, GAME_EVENT as G, InitTurnOrderState as I, MAKE_MOVE as M, NoClient as N, PlayerView as P, RESET as R, SYNC as S, TurnOrder as T, UPDATE as U, redo as a, PATCH as b, REDO as c, UNDO as d, STRIP_TRANSIENTS as e, update as f, error as g, GameMethod as h, supportDeprecatedMoveLimit as i, GetAPIs as j, Stage as k, SetActivePlayers as l, info as m, UpdateTurnOrderState as n, UpdateActivePlayersOnceEmpty as o, patch as p, gameEvent as q, reset as r, sync as s, PLUGIN as t, undo as u, ProcessAction as v, INVALID_MOVE as w, FlushAndValidate as x, stripTransients as y, Setup as z };