@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,897 @@
1
+ /*
2
+ * Copyright 2017 The boardgame.io Authors
3
+ *
4
+ * Use of this source code is governed by a MIT-style
5
+ * license that can be found in the LICENSE file or at
6
+ * https://opensource.org/licenses/MIT.
7
+ */
8
+
9
+ import {
10
+ SetActivePlayers,
11
+ UpdateActivePlayersOnceEmpty,
12
+ InitTurnOrderState,
13
+ UpdateTurnOrderState,
14
+ Stage,
15
+ TurnOrder,
16
+ } from './turn-order';
17
+ import { gameEvent } from './action-creators';
18
+ import * as plugin from '../plugins/main';
19
+ import * as logging from './logger';
20
+ import type {
21
+ ActionPayload,
22
+ ActionShape,
23
+ ActivePlayersArg,
24
+ State,
25
+ Ctx,
26
+ FnContext,
27
+ LogEntry,
28
+ Game,
29
+ PhaseConfig,
30
+ PlayerID,
31
+ Move,
32
+ } from '../types';
33
+ import { GameMethod } from './game-methods';
34
+ import { supportDeprecatedMoveLimit } from './backwards-compatibility';
35
+
36
+ /**
37
+ * Flow
38
+ *
39
+ * Creates a reducer that updates ctx (analogous to how moves update G).
40
+ */
41
+ export function Flow({
42
+ moves,
43
+ phases,
44
+ endIf,
45
+ onEnd,
46
+ turn,
47
+ events,
48
+ plugins,
49
+ }: Game) {
50
+ // Attach defaults.
51
+ if (moves === undefined) {
52
+ moves = {};
53
+ }
54
+ if (events === undefined) {
55
+ events = {};
56
+ }
57
+ if (plugins === undefined) {
58
+ plugins = [];
59
+ }
60
+ if (phases === undefined) {
61
+ phases = {};
62
+ }
63
+
64
+ if (!endIf) endIf = () => undefined;
65
+ if (!onEnd) onEnd = ({ G }) => G;
66
+ if (!turn) turn = {};
67
+
68
+ const phaseMap = { ...phases };
69
+
70
+ if ('' in phaseMap) {
71
+ logging.error('cannot specify phase with empty name');
72
+ }
73
+
74
+ phaseMap[''] = {};
75
+
76
+ const moveMap = {};
77
+ const moveNames = new Set();
78
+ let startingPhase = null;
79
+
80
+ Object.keys(moves).forEach((name) => moveNames.add(name));
81
+
82
+ const HookWrapper = (
83
+ hook: (context: FnContext) => any,
84
+ hookType: GameMethod
85
+ ) => {
86
+ const withPlugins = plugin.FnWrap(hook, hookType, plugins);
87
+ return (state: State & { playerID?: PlayerID }) => {
88
+ const pluginAPIs = plugin.GetAPIs(state);
89
+ return withPlugins({
90
+ ...pluginAPIs,
91
+ G: state.G,
92
+ ctx: state.ctx,
93
+ playerID: state.playerID,
94
+ });
95
+ };
96
+ };
97
+
98
+ const TriggerWrapper = (trigger: (context: FnContext) => any) => {
99
+ return (state: State) => {
100
+ const pluginAPIs = plugin.GetAPIs(state);
101
+ return trigger({
102
+ ...pluginAPIs,
103
+ G: state.G,
104
+ ctx: state.ctx,
105
+ });
106
+ };
107
+ };
108
+
109
+ const wrapped = {
110
+ onEnd: HookWrapper(onEnd, GameMethod.GAME_ON_END),
111
+ endIf: TriggerWrapper(endIf),
112
+ };
113
+
114
+ for (const phase in phaseMap) {
115
+ const phaseConfig = phaseMap[phase];
116
+
117
+ if (phaseConfig.start === true) {
118
+ startingPhase = phase;
119
+ }
120
+
121
+ if (phaseConfig.moves !== undefined) {
122
+ for (const move of Object.keys(phaseConfig.moves)) {
123
+ moveMap[phase + '.' + move] = phaseConfig.moves[move];
124
+ moveNames.add(move);
125
+ }
126
+ }
127
+
128
+ if (phaseConfig.endIf === undefined) {
129
+ phaseConfig.endIf = () => undefined;
130
+ }
131
+ if (phaseConfig.onBegin === undefined) {
132
+ phaseConfig.onBegin = ({ G }) => G;
133
+ }
134
+ if (phaseConfig.onEnd === undefined) {
135
+ phaseConfig.onEnd = ({ G }) => G;
136
+ }
137
+ if (phaseConfig.turn === undefined) {
138
+ phaseConfig.turn = turn;
139
+ }
140
+ if (phaseConfig.turn.order === undefined) {
141
+ phaseConfig.turn.order = TurnOrder.DEFAULT;
142
+ }
143
+ if (phaseConfig.turn.onBegin === undefined) {
144
+ phaseConfig.turn.onBegin = ({ G }) => G;
145
+ }
146
+ if (phaseConfig.turn.onEnd === undefined) {
147
+ phaseConfig.turn.onEnd = ({ G }) => G;
148
+ }
149
+ if (phaseConfig.turn.endIf === undefined) {
150
+ phaseConfig.turn.endIf = () => false;
151
+ }
152
+ if (phaseConfig.turn.onMove === undefined) {
153
+ phaseConfig.turn.onMove = ({ G }) => G;
154
+ }
155
+ if (phaseConfig.turn.stages === undefined) {
156
+ phaseConfig.turn.stages = {};
157
+ }
158
+
159
+ // turns previously treated moveLimit as both minMoves and maxMoves, this behaviour is kept intentionally
160
+ supportDeprecatedMoveLimit(phaseConfig.turn, true);
161
+
162
+ for (const stage in phaseConfig.turn.stages) {
163
+ const stageConfig = phaseConfig.turn.stages[stage];
164
+ const moves = stageConfig.moves || {};
165
+ for (const move of Object.keys(moves)) {
166
+ const key = phase + '.' + stage + '.' + move;
167
+ moveMap[key] = moves[move];
168
+ moveNames.add(move);
169
+ }
170
+ }
171
+
172
+ phaseConfig.wrapped = {
173
+ onBegin: HookWrapper(phaseConfig.onBegin, GameMethod.PHASE_ON_BEGIN),
174
+ onEnd: HookWrapper(phaseConfig.onEnd, GameMethod.PHASE_ON_END),
175
+ endIf: TriggerWrapper(phaseConfig.endIf),
176
+ };
177
+
178
+ phaseConfig.turn.wrapped = {
179
+ onMove: HookWrapper(phaseConfig.turn.onMove, GameMethod.TURN_ON_MOVE),
180
+ onBegin: HookWrapper(phaseConfig.turn.onBegin, GameMethod.TURN_ON_BEGIN),
181
+ onEnd: HookWrapper(phaseConfig.turn.onEnd, GameMethod.TURN_ON_END),
182
+ endIf: TriggerWrapper(phaseConfig.turn.endIf),
183
+ };
184
+
185
+ if (typeof phaseConfig.next !== 'function') {
186
+ const { next } = phaseConfig;
187
+ phaseConfig.next = () => next || null;
188
+ }
189
+ phaseConfig.wrapped.next = TriggerWrapper(phaseConfig.next);
190
+ }
191
+
192
+ function GetPhase(ctx: { phase: string }): PhaseConfig {
193
+ return ctx.phase ? phaseMap[ctx.phase] : phaseMap[''];
194
+ }
195
+
196
+ function OnMove(state: State) {
197
+ return state;
198
+ }
199
+
200
+ function Process(
201
+ state: State,
202
+ events: {
203
+ fn: (state: State, opts: any) => State;
204
+ arg?: any;
205
+ turn?: Ctx['turn'];
206
+ phase?: Ctx['phase'];
207
+ automatic?: boolean;
208
+ playerID?: PlayerID;
209
+ force?: boolean;
210
+ }[]
211
+ ): State {
212
+ const phasesEnded = new Set();
213
+ const turnsEnded = new Set();
214
+
215
+ for (let i = 0; i < events.length; i++) {
216
+ const { fn, arg, ...rest } = events[i];
217
+
218
+ // Detect a loop of EndPhase calls.
219
+ // This could potentially even be an infinite loop
220
+ // if the endIf condition of each phase blindly
221
+ // returns true. The moment we detect a single
222
+ // loop, we just bail out of all phases.
223
+ if (fn === EndPhase) {
224
+ turnsEnded.clear();
225
+ const phase = state.ctx.phase;
226
+ if (phasesEnded.has(phase)) {
227
+ const ctx = { ...state.ctx, phase: null };
228
+ return { ...state, ctx };
229
+ }
230
+ phasesEnded.add(phase);
231
+ }
232
+
233
+ // Process event.
234
+ const next = [];
235
+ state = fn(state, {
236
+ ...rest,
237
+ arg,
238
+ next,
239
+ });
240
+
241
+ if (fn === EndGame) {
242
+ break;
243
+ }
244
+
245
+ // Check if we should end the game.
246
+ const shouldEndGame = ShouldEndGame(state);
247
+ if (shouldEndGame) {
248
+ events.push({
249
+ fn: EndGame,
250
+ arg: shouldEndGame,
251
+ turn: state.ctx.turn,
252
+ phase: state.ctx.phase,
253
+ automatic: true,
254
+ });
255
+ continue;
256
+ }
257
+
258
+ // Check if we should end the phase.
259
+ const shouldEndPhase = ShouldEndPhase(state);
260
+ if (shouldEndPhase) {
261
+ events.push({
262
+ fn: EndPhase,
263
+ arg: shouldEndPhase,
264
+ turn: state.ctx.turn,
265
+ phase: state.ctx.phase,
266
+ automatic: true,
267
+ });
268
+ continue;
269
+ }
270
+
271
+ // Check if we should end the turn.
272
+ if ([OnMove, UpdateStage, UpdateActivePlayers].includes(fn)) {
273
+ const shouldEndTurn = ShouldEndTurn(state);
274
+ if (shouldEndTurn) {
275
+ events.push({
276
+ fn: EndTurn,
277
+ arg: shouldEndTurn,
278
+ turn: state.ctx.turn,
279
+ phase: state.ctx.phase,
280
+ automatic: true,
281
+ });
282
+ continue;
283
+ }
284
+ }
285
+
286
+ events.push(...next);
287
+ }
288
+
289
+ return state;
290
+ }
291
+
292
+ ///////////
293
+ // Start //
294
+ ///////////
295
+
296
+ function StartGame(state: State, { next }): State {
297
+ next.push({ fn: StartPhase });
298
+ return state;
299
+ }
300
+
301
+ function StartPhase(state: State, { next }): State {
302
+ let { G, ctx } = state;
303
+ const phaseConfig = GetPhase(ctx);
304
+
305
+ // Run any phase setup code provided by the user.
306
+ G = phaseConfig.wrapped.onBegin(state);
307
+
308
+ next.push({ fn: StartTurn });
309
+
310
+ return { ...state, G, ctx };
311
+ }
312
+
313
+ function StartTurn(state: State, { currentPlayer }): State {
314
+ let { ctx } = state;
315
+ const phaseConfig = GetPhase(ctx);
316
+
317
+ // Initialize the turn order state.
318
+ if (currentPlayer) {
319
+ ctx = { ...ctx, currentPlayer };
320
+ if (phaseConfig.turn.activePlayers) {
321
+ ctx = SetActivePlayers(ctx, phaseConfig.turn.activePlayers);
322
+ }
323
+ } else {
324
+ // This is only called at the beginning of the phase
325
+ // when there is no currentPlayer yet.
326
+ ctx = InitTurnOrderState(state, phaseConfig.turn);
327
+ }
328
+
329
+ const turn = ctx.turn + 1;
330
+ ctx = { ...ctx, turn, numMoves: 0, _prevActivePlayers: [] };
331
+
332
+ const G = phaseConfig.turn.wrapped.onBegin({ ...state, ctx });
333
+
334
+ return { ...state, G, ctx, _undo: [], _redo: [] } as State;
335
+ }
336
+
337
+ ////////////
338
+ // Update //
339
+ ////////////
340
+
341
+ function UpdatePhase(state: State, { arg, next, phase }): State {
342
+ const phaseConfig = GetPhase({ phase });
343
+ let { ctx } = state;
344
+
345
+ if (arg && arg.next) {
346
+ if (arg.next in phaseMap) {
347
+ ctx = { ...ctx, phase: arg.next };
348
+ } else {
349
+ logging.error('invalid phase: ' + arg.next);
350
+ return state;
351
+ }
352
+ } else {
353
+ ctx = { ...ctx, phase: phaseConfig.wrapped.next(state) || null };
354
+ }
355
+
356
+ state = { ...state, ctx };
357
+
358
+ // Start the new phase.
359
+ next.push({ fn: StartPhase });
360
+
361
+ return state;
362
+ }
363
+
364
+ function UpdateTurn(state: State, { arg, currentPlayer, next }): State {
365
+ let { G, ctx } = state;
366
+ const phaseConfig = GetPhase(ctx);
367
+
368
+ // Update turn order state.
369
+ const { endPhase, ctx: newCtx } = UpdateTurnOrderState(
370
+ state,
371
+ currentPlayer,
372
+ phaseConfig.turn,
373
+ arg
374
+ );
375
+ ctx = newCtx;
376
+
377
+ state = { ...state, G, ctx };
378
+
379
+ if (endPhase) {
380
+ next.push({ fn: EndPhase, turn: ctx.turn, phase: ctx.phase });
381
+ } else {
382
+ next.push({ fn: StartTurn, currentPlayer: ctx.currentPlayer });
383
+ }
384
+
385
+ return state;
386
+ }
387
+
388
+ function UpdateStage(state: State, { arg, playerID }): State {
389
+ if (typeof arg === 'string' || arg === Stage.NULL) {
390
+ arg = { stage: arg };
391
+ }
392
+ if (typeof arg !== 'object') return state;
393
+
394
+ // `arg` should be of type `StageArg`, loose typing as `any` here for historic reasons
395
+ // stages previously did not enforce minMoves, this behaviour is kept intentionally
396
+ supportDeprecatedMoveLimit(arg);
397
+
398
+ let { ctx } = state;
399
+ let {
400
+ activePlayers,
401
+ _activePlayersMinMoves,
402
+ _activePlayersMaxMoves,
403
+ _activePlayersNumMoves,
404
+ } = ctx;
405
+
406
+ // Checking if stage is valid, even Stage.NULL
407
+ if (arg.stage !== undefined) {
408
+ if (activePlayers === null) {
409
+ activePlayers = {};
410
+ }
411
+ activePlayers[playerID] = arg.stage;
412
+ _activePlayersNumMoves[playerID] = 0;
413
+
414
+ if (arg.minMoves) {
415
+ if (_activePlayersMinMoves === null) {
416
+ _activePlayersMinMoves = {};
417
+ }
418
+ _activePlayersMinMoves[playerID] = arg.minMoves;
419
+ }
420
+
421
+ if (arg.maxMoves) {
422
+ if (_activePlayersMaxMoves === null) {
423
+ _activePlayersMaxMoves = {};
424
+ }
425
+ _activePlayersMaxMoves[playerID] = arg.maxMoves;
426
+ }
427
+ }
428
+
429
+ ctx = {
430
+ ...ctx,
431
+ activePlayers,
432
+ _activePlayersMinMoves,
433
+ _activePlayersMaxMoves,
434
+ _activePlayersNumMoves,
435
+ };
436
+
437
+ return { ...state, ctx };
438
+ }
439
+
440
+ function UpdateActivePlayers(state: State, { arg }): State {
441
+ return { ...state, ctx: SetActivePlayers(state.ctx, arg) };
442
+ }
443
+
444
+ ///////////////
445
+ // ShouldEnd //
446
+ ///////////////
447
+
448
+ function ShouldEndGame(state: State): boolean {
449
+ return wrapped.endIf(state);
450
+ }
451
+
452
+ function ShouldEndPhase(state: State): boolean | void | { next: string } {
453
+ const phaseConfig = GetPhase(state.ctx);
454
+ return phaseConfig.wrapped.endIf(state);
455
+ }
456
+
457
+ function ShouldEndTurn(state: State): boolean | void | { next: PlayerID } {
458
+ const phaseConfig = GetPhase(state.ctx);
459
+
460
+ // End the turn if the required number of moves has been made.
461
+ const currentPlayerMoves = state.ctx.numMoves || 0;
462
+ if (
463
+ phaseConfig.turn.maxMoves &&
464
+ currentPlayerMoves >= phaseConfig.turn.maxMoves
465
+ ) {
466
+ return true;
467
+ }
468
+
469
+ return phaseConfig.turn.wrapped.endIf(state);
470
+ }
471
+
472
+ /////////
473
+ // End //
474
+ /////////
475
+
476
+ function EndGame(state: State, { arg, phase }): State {
477
+ state = EndPhase(state, { phase });
478
+
479
+ if (arg === undefined) {
480
+ arg = true;
481
+ }
482
+
483
+ state = { ...state, ctx: { ...state.ctx, gameover: arg } };
484
+
485
+ // Run game end hook.
486
+ const G = wrapped.onEnd(state);
487
+
488
+ return { ...state, G };
489
+ }
490
+
491
+ function EndPhase(
492
+ state: State,
493
+ { arg, next, turn: initialTurn, automatic }: any
494
+ ): State {
495
+ // End the turn first.
496
+ state = EndTurn(state, { turn: initialTurn, force: true, automatic: true });
497
+
498
+ const { phase, turn } = state.ctx;
499
+
500
+ if (next) {
501
+ next.push({ fn: UpdatePhase, arg, phase });
502
+ }
503
+
504
+ // If we aren't in a phase, there is nothing else to do.
505
+ if (phase === null) {
506
+ return state;
507
+ }
508
+
509
+ // Run any cleanup code for the phase that is about to end.
510
+ const phaseConfig = GetPhase(state.ctx);
511
+ const G = phaseConfig.wrapped.onEnd(state);
512
+
513
+ // Reset the phase.
514
+ const ctx = { ...state.ctx, phase: null };
515
+
516
+ // Add log entry.
517
+ const action = gameEvent('endPhase', arg);
518
+ const { _stateID } = state;
519
+ const logEntry: LogEntry = { action, _stateID, turn, phase };
520
+ if (automatic) logEntry.automatic = true;
521
+
522
+ const deltalog = [...(state.deltalog || []), logEntry];
523
+
524
+ return { ...state, G, ctx, deltalog };
525
+ }
526
+
527
+ function EndTurn(
528
+ state: State,
529
+ { arg, next, turn: initialTurn, force, automatic, playerID }: any
530
+ ): State {
531
+ // This is not the turn that EndTurn was originally
532
+ // called for. The turn was probably ended some other way.
533
+ if (initialTurn !== state.ctx.turn) {
534
+ return state;
535
+ }
536
+
537
+ const { currentPlayer, numMoves, phase, turn } = state.ctx;
538
+ const phaseConfig = GetPhase(state.ctx);
539
+
540
+ // Prevent ending the turn if minMoves haven't been reached.
541
+ const currentPlayerMoves = numMoves || 0;
542
+ if (
543
+ !force &&
544
+ phaseConfig.turn.minMoves &&
545
+ currentPlayerMoves < phaseConfig.turn.minMoves
546
+ ) {
547
+ logging.info(
548
+ `cannot end turn before making ${phaseConfig.turn.minMoves} moves`
549
+ );
550
+ return state;
551
+ }
552
+
553
+ // Run turn-end triggers.
554
+ const G = phaseConfig.turn.wrapped.onEnd(state);
555
+
556
+ if (next) {
557
+ next.push({ fn: UpdateTurn, arg, currentPlayer });
558
+ }
559
+
560
+ // Reset activePlayers.
561
+ let ctx = { ...state.ctx, activePlayers: null };
562
+
563
+ // Remove player from playerOrder
564
+ if (arg && arg.remove) {
565
+ playerID = playerID || currentPlayer;
566
+
567
+ const playOrder = ctx.playOrder.filter((i) => i != playerID);
568
+
569
+ const playOrderPos =
570
+ ctx.playOrderPos > playOrder.length - 1 ? 0 : ctx.playOrderPos;
571
+
572
+ ctx = { ...ctx, playOrder, playOrderPos };
573
+
574
+ if (playOrder.length === 0) {
575
+ next.push({ fn: EndPhase, turn, phase });
576
+ return state;
577
+ }
578
+ }
579
+
580
+ // Create log entry.
581
+ const action = gameEvent('endTurn', arg);
582
+ const { _stateID } = state;
583
+ const logEntry: LogEntry = { action, _stateID, turn, phase };
584
+ if (automatic) logEntry.automatic = true;
585
+
586
+ const deltalog = [...(state.deltalog || []), logEntry];
587
+
588
+ return { ...state, G, ctx, deltalog, _undo: [], _redo: [] };
589
+ }
590
+
591
+ function EndStage(
592
+ state: State,
593
+ { arg, next, automatic, playerID }: any
594
+ ): State {
595
+ playerID = playerID || state.ctx.currentPlayer;
596
+
597
+ let { ctx, _stateID } = state;
598
+ let {
599
+ activePlayers,
600
+ _activePlayersNumMoves,
601
+ _activePlayersMinMoves,
602
+ _activePlayersMaxMoves,
603
+ phase,
604
+ turn,
605
+ } = ctx;
606
+
607
+ const playerInStage = activePlayers !== null && playerID in activePlayers;
608
+
609
+ const phaseConfig = GetPhase(ctx);
610
+
611
+ if (!arg && playerInStage) {
612
+ const stage = phaseConfig.turn.stages[activePlayers[playerID]];
613
+ if (stage && stage.next) {
614
+ arg = stage.next;
615
+ }
616
+ }
617
+
618
+ // Checking if arg is a valid stage, even Stage.NULL
619
+ if (next) {
620
+ next.push({ fn: UpdateStage, arg, playerID });
621
+ }
622
+
623
+ // If player isn’t in a stage, there is nothing else to do.
624
+ if (!playerInStage) return state;
625
+
626
+ // Prevent ending the stage if minMoves haven't been reached.
627
+ const currentPlayerMoves = _activePlayersNumMoves[playerID] || 0;
628
+ if (
629
+ _activePlayersMinMoves &&
630
+ _activePlayersMinMoves[playerID] &&
631
+ currentPlayerMoves < _activePlayersMinMoves[playerID]
632
+ ) {
633
+ logging.info(
634
+ `cannot end stage before making ${_activePlayersMinMoves[playerID]} moves`
635
+ );
636
+ return state;
637
+ }
638
+
639
+ // Remove player from activePlayers.
640
+ activePlayers = { ...activePlayers };
641
+ delete activePlayers[playerID];
642
+
643
+ if (_activePlayersMinMoves) {
644
+ // Remove player from _activePlayersMinMoves.
645
+ _activePlayersMinMoves = { ..._activePlayersMinMoves };
646
+ delete _activePlayersMinMoves[playerID];
647
+ }
648
+
649
+ if (_activePlayersMaxMoves) {
650
+ // Remove player from _activePlayersMaxMoves.
651
+ _activePlayersMaxMoves = { ..._activePlayersMaxMoves };
652
+ delete _activePlayersMaxMoves[playerID];
653
+ }
654
+
655
+ ctx = UpdateActivePlayersOnceEmpty({
656
+ ...ctx,
657
+ activePlayers,
658
+ _activePlayersMinMoves,
659
+ _activePlayersMaxMoves,
660
+ });
661
+
662
+ // Create log entry.
663
+ const action = gameEvent('endStage', arg);
664
+ const logEntry: LogEntry = { action, _stateID, turn, phase };
665
+ if (automatic) logEntry.automatic = true;
666
+
667
+ const deltalog = [...(state.deltalog || []), logEntry];
668
+
669
+ return { ...state, ctx, deltalog };
670
+ }
671
+
672
+ /**
673
+ * Retrieves the relevant move that can be played by playerID.
674
+ *
675
+ * If ctx.activePlayers is set (i.e. one or more players are in some stage),
676
+ * then it attempts to find the move inside the stages config for
677
+ * that turn. If the stage for a player is '', then the player is
678
+ * allowed to make a move (as determined by the phase config), but
679
+ * isn't restricted to a particular set as defined in the stage config.
680
+ *
681
+ * If not, it then looks for the move inside the phase.
682
+ *
683
+ * If it doesn't find the move there, it looks at the global move definition.
684
+ *
685
+ * @param {object} ctx
686
+ * @param {string} name
687
+ * @param {string} playerID
688
+ */
689
+ function GetMove(ctx: Ctx, name: string, playerID: PlayerID): null | Move {
690
+ const phaseConfig = GetPhase(ctx);
691
+ const stages = phaseConfig.turn.stages;
692
+ const { activePlayers } = ctx;
693
+
694
+ if (
695
+ activePlayers &&
696
+ activePlayers[playerID] !== undefined &&
697
+ activePlayers[playerID] !== Stage.NULL &&
698
+ stages[activePlayers[playerID]] !== undefined &&
699
+ stages[activePlayers[playerID]].moves !== undefined
700
+ ) {
701
+ // Check if moves are defined for the player's stage.
702
+ const stage = stages[activePlayers[playerID]];
703
+ const moves = stage.moves;
704
+ if (name in moves) {
705
+ return moves[name];
706
+ }
707
+ } else if (phaseConfig.moves) {
708
+ // Check if moves are defined for the current phase.
709
+ if (name in phaseConfig.moves) {
710
+ return phaseConfig.moves[name];
711
+ }
712
+ } else if (name in moves) {
713
+ // Check for the move globally.
714
+ return moves[name];
715
+ }
716
+
717
+ return null;
718
+ }
719
+
720
+ function ProcessMove(state: State, action: ActionPayload.MakeMove): State {
721
+ const { playerID, type } = action;
722
+ const { currentPlayer, activePlayers, _activePlayersMaxMoves } = state.ctx;
723
+ const move = GetMove(state.ctx, type, playerID);
724
+ const shouldCount =
725
+ !move || typeof move === 'function' || move.noLimit !== true;
726
+
727
+ let { numMoves, _activePlayersNumMoves } = state.ctx;
728
+ if (shouldCount) {
729
+ if (playerID === currentPlayer) numMoves++;
730
+ if (activePlayers) _activePlayersNumMoves[playerID]++;
731
+ }
732
+
733
+ state = {
734
+ ...state,
735
+ ctx: {
736
+ ...state.ctx,
737
+ numMoves,
738
+ _activePlayersNumMoves,
739
+ },
740
+ };
741
+
742
+ if (
743
+ _activePlayersMaxMoves &&
744
+ _activePlayersNumMoves[playerID] >= _activePlayersMaxMoves[playerID]
745
+ ) {
746
+ state = EndStage(state, { playerID, automatic: true });
747
+ }
748
+
749
+ const phaseConfig = GetPhase(state.ctx);
750
+ const G = phaseConfig.turn.wrapped.onMove({ ...state, playerID });
751
+ state = { ...state, G };
752
+
753
+ const events = [{ fn: OnMove }];
754
+
755
+ return Process(state, events);
756
+ }
757
+
758
+ function SetStageEvent(state: State, playerID: PlayerID, arg: any): State {
759
+ return Process(state, [{ fn: EndStage, arg, playerID }]);
760
+ }
761
+
762
+ function EndStageEvent(state: State, playerID: PlayerID): State {
763
+ return Process(state, [{ fn: EndStage, playerID }]);
764
+ }
765
+
766
+ function SetActivePlayersEvent(
767
+ state: State,
768
+ _playerID: PlayerID,
769
+ arg: ActivePlayersArg
770
+ ): State {
771
+ return Process(state, [{ fn: UpdateActivePlayers, arg }]);
772
+ }
773
+
774
+ function SetPhaseEvent(
775
+ state: State,
776
+ _playerID: PlayerID,
777
+ newPhase: string
778
+ ): State {
779
+ return Process(state, [
780
+ {
781
+ fn: EndPhase,
782
+ phase: state.ctx.phase,
783
+ turn: state.ctx.turn,
784
+ arg: { next: newPhase },
785
+ },
786
+ ]);
787
+ }
788
+
789
+ function EndPhaseEvent(state: State): State {
790
+ return Process(state, [
791
+ { fn: EndPhase, phase: state.ctx.phase, turn: state.ctx.turn },
792
+ ]);
793
+ }
794
+
795
+ function EndTurnEvent(state: State, _playerID: PlayerID, arg: any): State {
796
+ return Process(state, [
797
+ { fn: EndTurn, turn: state.ctx.turn, phase: state.ctx.phase, arg },
798
+ ]);
799
+ }
800
+
801
+ function PassEvent(state: State, _playerID: PlayerID, arg: any): State {
802
+ return Process(state, [
803
+ {
804
+ fn: EndTurn,
805
+ turn: state.ctx.turn,
806
+ phase: state.ctx.phase,
807
+ force: true,
808
+ arg,
809
+ },
810
+ ]);
811
+ }
812
+
813
+ function EndGameEvent(state: State, _playerID: PlayerID, arg: any): State {
814
+ return Process(state, [
815
+ { fn: EndGame, turn: state.ctx.turn, phase: state.ctx.phase, arg },
816
+ ]);
817
+ }
818
+
819
+ const eventHandlers = {
820
+ endStage: EndStageEvent,
821
+ setStage: SetStageEvent,
822
+ endTurn: EndTurnEvent,
823
+ pass: PassEvent,
824
+ endPhase: EndPhaseEvent,
825
+ setPhase: SetPhaseEvent,
826
+ endGame: EndGameEvent,
827
+ setActivePlayers: SetActivePlayersEvent,
828
+ };
829
+
830
+ const enabledEventNames = [];
831
+
832
+ if (events.endTurn !== false) {
833
+ enabledEventNames.push('endTurn');
834
+ }
835
+ if (events.pass !== false) {
836
+ enabledEventNames.push('pass');
837
+ }
838
+ if (events.endPhase !== false) {
839
+ enabledEventNames.push('endPhase');
840
+ }
841
+ if (events.setPhase !== false) {
842
+ enabledEventNames.push('setPhase');
843
+ }
844
+ if (events.endGame !== false) {
845
+ enabledEventNames.push('endGame');
846
+ }
847
+ if (events.setActivePlayers !== false) {
848
+ enabledEventNames.push('setActivePlayers');
849
+ }
850
+ if (events.endStage !== false) {
851
+ enabledEventNames.push('endStage');
852
+ }
853
+ if (events.setStage !== false) {
854
+ enabledEventNames.push('setStage');
855
+ }
856
+
857
+ function ProcessEvent(state: State, action: ActionShape.GameEvent): State {
858
+ const { type, playerID, args } = action.payload;
859
+ if (typeof eventHandlers[type] !== 'function') return state;
860
+ return eventHandlers[type](
861
+ state,
862
+ playerID,
863
+ ...(Array.isArray(args) ? args : [args])
864
+ );
865
+ }
866
+
867
+ function IsPlayerActive(_G: any, ctx: Ctx, playerID: PlayerID): boolean {
868
+ if (ctx.activePlayers) {
869
+ return playerID in ctx.activePlayers;
870
+ }
871
+ return ctx.currentPlayer === playerID;
872
+ }
873
+
874
+ return {
875
+ ctx: (numPlayers: number): Ctx => ({
876
+ numPlayers,
877
+ turn: 0,
878
+ currentPlayer: '0',
879
+ playOrder: [...Array.from({ length: numPlayers })].map((_, i) => i + ''),
880
+ playOrderPos: 0,
881
+ phase: startingPhase,
882
+ activePlayers: null,
883
+ }),
884
+ init: (state: State): State => {
885
+ return Process(state, [{ fn: StartGame }]);
886
+ },
887
+ isPlayerActive: IsPlayerActive,
888
+ eventHandlers,
889
+ eventNames: Object.keys(eventHandlers),
890
+ enabledEventNames,
891
+ moveMap,
892
+ moveNames: [...moveNames.values()],
893
+ processMove: ProcessMove,
894
+ processEvent: ProcessEvent,
895
+ getMove: GetMove,
896
+ };
897
+ }