@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,1123 @@
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 { Flow } from './flow';
10
+ import { Client } from '../client/client';
11
+ import {
12
+ UpdateTurnOrderState,
13
+ Stage,
14
+ TurnOrder,
15
+ ActivePlayers,
16
+ } from './turn-order';
17
+ import { makeMove, gameEvent } from './action-creators';
18
+ import { CreateGameReducer } from './reducer';
19
+ import { InitializeGame } from './initialize';
20
+ import { error } from '../core/logger';
21
+ import type { Game, State } from '../types';
22
+
23
+ jest.mock('../core/logger', () => ({
24
+ info: jest.fn(),
25
+ error: jest.fn(),
26
+ }));
27
+
28
+ // Let the Typescript compiler know about our custom matcher.
29
+ declare global {
30
+ namespace jest {
31
+ interface Matchers<R> {
32
+ toHaveUndefinedProperties(): R;
33
+ }
34
+ }
35
+ }
36
+
37
+ describe('turn orders', () => {
38
+ // Defines a matcher for testing that ctx has no undefined properties.
39
+ // Identifies which property is undefined.
40
+ expect.extend({
41
+ toHaveUndefinedProperties(ctx) {
42
+ const undefinedEntry = Object.entries(ctx).find((entry) => {
43
+ const [, value] = entry;
44
+ return value === undefined;
45
+ });
46
+ if (undefinedEntry === undefined) {
47
+ return {
48
+ message: () => `expected some properties of ctx to be undefined`,
49
+ pass: false,
50
+ };
51
+ } else {
52
+ const [k] = undefinedEntry;
53
+ return {
54
+ message: () => `expected ctx.${k} to be defined`,
55
+ pass: true,
56
+ };
57
+ }
58
+ },
59
+ });
60
+
61
+ test('DEFAULT', () => {
62
+ const flow = Flow({
63
+ phases: { A: { start: true, next: 'B' }, B: {} },
64
+ });
65
+
66
+ let state = { ctx: flow.ctx(2) } as State;
67
+ state = flow.init(state);
68
+ expect(state.ctx.currentPlayer).toBe('0');
69
+ expect(state.ctx).not.toHaveUndefinedProperties();
70
+
71
+ state = flow.processEvent(state, gameEvent('endTurn'));
72
+ expect(state.ctx.currentPlayer).toBe('1');
73
+ state = flow.processEvent(state, gameEvent('endTurn'));
74
+ expect(state.ctx.currentPlayer).toBe('0');
75
+ state = flow.processEvent(state, gameEvent('endTurn'));
76
+ expect(state.ctx.currentPlayer).toBe('1');
77
+ expect(state.ctx.phase).toBe('A');
78
+ state = flow.processEvent(state, gameEvent('endPhase'));
79
+ expect(state.ctx.currentPlayer).toBe('0');
80
+ expect(state.ctx.phase).toBe('B');
81
+ });
82
+
83
+ test('CONTINUE', () => {
84
+ const flow = Flow({
85
+ turn: { order: TurnOrder.CONTINUE },
86
+ phases: { A: { start: true, next: 'B' }, B: {} },
87
+ });
88
+
89
+ let state = { ctx: flow.ctx(2) } as State;
90
+ state = flow.init(state);
91
+ expect(state.ctx.currentPlayer).toBe('0');
92
+ expect(state.ctx).not.toHaveUndefinedProperties();
93
+
94
+ state = flow.processEvent(state, gameEvent('endTurn'));
95
+ expect(state.ctx.currentPlayer).toBe('1');
96
+ state = flow.processEvent(state, gameEvent('endTurn'));
97
+ expect(state.ctx.currentPlayer).toBe('0');
98
+ state = flow.processEvent(state, gameEvent('endTurn'));
99
+ expect(state.ctx.currentPlayer).toBe('1');
100
+ expect(state.ctx.phase).toBe('A');
101
+ state = flow.processEvent(state, gameEvent('endPhase'));
102
+ expect(state.ctx.currentPlayer).toBe('1');
103
+ expect(state.ctx.phase).toBe('B');
104
+ });
105
+
106
+ test('RESET', () => {
107
+ const flow = Flow({
108
+ turn: { order: TurnOrder.RESET },
109
+ phases: { A: { start: true, next: 'B' }, B: {} },
110
+ });
111
+
112
+ let state = { ctx: flow.ctx(2) } as State;
113
+ state = flow.init(state);
114
+ expect(state.ctx.currentPlayer).toBe('0');
115
+ expect(state.ctx).not.toHaveUndefinedProperties();
116
+
117
+ state = flow.processEvent(state, gameEvent('endTurn'));
118
+ expect(state.ctx.currentPlayer).toBe('1');
119
+ state = flow.processEvent(state, gameEvent('endTurn'));
120
+ expect(state.ctx.currentPlayer).toBe('0');
121
+ state = flow.processEvent(state, gameEvent('endTurn'));
122
+ expect(state.ctx.currentPlayer).toBe('1');
123
+ expect(state.ctx.phase).toBe('A');
124
+ state = flow.processEvent(state, gameEvent('endPhase'));
125
+ expect(state.ctx.currentPlayer).toBe('0');
126
+ expect(state.ctx.phase).toBe('B');
127
+ });
128
+
129
+ test('ONCE', () => {
130
+ const flow = Flow({
131
+ turn: { order: TurnOrder.ONCE },
132
+ phases: { A: { start: true, next: 'B' }, B: {} },
133
+ });
134
+
135
+ let state = { ctx: flow.ctx(2) } as State;
136
+ state = flow.init(state);
137
+ expect(state.ctx.currentPlayer).toBe('0');
138
+ expect(state.ctx).not.toHaveUndefinedProperties();
139
+
140
+ state = flow.processEvent(state, gameEvent('endTurn'));
141
+ expect(state.ctx.currentPlayer).toBe('1');
142
+ state = flow.processEvent(state, gameEvent('endTurn'));
143
+ expect(state.ctx.currentPlayer).toBe('0');
144
+ expect(state.ctx.phase).toBe('B');
145
+ });
146
+
147
+ test('ALL', () => {
148
+ const flow = Flow({
149
+ turn: { activePlayers: ActivePlayers.ALL },
150
+ });
151
+
152
+ let state = { ctx: flow.ctx(2) } as State;
153
+ state = flow.init(state);
154
+ expect(state.ctx.currentPlayer).toBe('0');
155
+ expect(state.ctx.activePlayers).toEqual({
156
+ '0': Stage.NULL,
157
+ '1': Stage.NULL,
158
+ });
159
+ expect(state.ctx).not.toHaveUndefinedProperties();
160
+
161
+ state = flow.processEvent(state, gameEvent('endTurn'));
162
+ expect(state.ctx.currentPlayer).toBe('1');
163
+ expect(state.ctx.activePlayers).toEqual({
164
+ '0': Stage.NULL,
165
+ '1': Stage.NULL,
166
+ });
167
+ });
168
+
169
+ test('ALL_ONCE', () => {
170
+ const flow = Flow({
171
+ phases: {
172
+ A: { start: true, turn: { activePlayers: ActivePlayers.ALL_ONCE } },
173
+ },
174
+ });
175
+
176
+ let state = { ctx: flow.ctx(2) } as State;
177
+ state = flow.init(state);
178
+
179
+ expect(state.ctx.phase).toBe('A');
180
+ expect(state.ctx.currentPlayer).toBe('0');
181
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '1']);
182
+ expect(state.ctx).not.toHaveUndefinedProperties();
183
+
184
+ state = flow.processEvent(state, gameEvent('endTurn'));
185
+
186
+ expect(state.ctx.phase).toBe('A');
187
+ expect(state.ctx.currentPlayer).toBe('1');
188
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '1']);
189
+
190
+ state = flow.processMove(state, makeMove('', null, '0').payload);
191
+
192
+ expect(state.ctx.phase).toBe('A');
193
+ expect(state.ctx.currentPlayer).toBe('1');
194
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['1']);
195
+
196
+ state = flow.processMove(state, makeMove('', null, '1').payload);
197
+
198
+ expect(state.ctx.currentPlayer).toBe('1');
199
+ expect(state.ctx.activePlayers).toBeNull();
200
+
201
+ state = flow.processMove(state, makeMove('', null, '1').payload);
202
+ });
203
+
204
+ test('OTHERS', () => {
205
+ const flow = Flow({
206
+ turn: { activePlayers: ActivePlayers.OTHERS },
207
+ });
208
+
209
+ let state = { ctx: flow.ctx(3) } as State;
210
+ state = flow.init(state);
211
+ expect(state.ctx.currentPlayer).toBe('0');
212
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['1', '2']);
213
+ expect(state.ctx).not.toHaveUndefinedProperties();
214
+
215
+ state = flow.processEvent(state, gameEvent('endTurn'));
216
+ expect(state.ctx.currentPlayer).toBe('1');
217
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '2']);
218
+ });
219
+
220
+ test('OTHERS_ONCE', () => {
221
+ const flow = Flow({
222
+ turn: { activePlayers: ActivePlayers.OTHERS_ONCE },
223
+ phases: { A: { start: true } },
224
+ });
225
+
226
+ let state = { ctx: flow.ctx(3) } as State;
227
+ state = flow.init(state);
228
+
229
+ expect(state.ctx.phase).toBe('A');
230
+ expect(state.ctx.currentPlayer).toBe('0');
231
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['1', '2']);
232
+ expect(state.ctx).not.toHaveUndefinedProperties();
233
+
234
+ state = flow.processEvent(state, gameEvent('endTurn'));
235
+
236
+ expect(state.ctx.phase).toBe('A');
237
+ expect(state.ctx.currentPlayer).toBe('1');
238
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '2']);
239
+
240
+ state = flow.processMove(state, makeMove('', null, '0').payload);
241
+
242
+ expect(state.ctx.phase).toBe('A');
243
+ expect(state.ctx.currentPlayer).toBe('1');
244
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['2']);
245
+
246
+ state = flow.processMove(state, makeMove('', null, '2').payload);
247
+
248
+ expect(state.ctx.currentPlayer).toBe('1');
249
+ expect(state.ctx.activePlayers).toBeNull();
250
+
251
+ state = flow.processMove(state, makeMove('', null, '1').payload);
252
+ });
253
+
254
+ test('CUSTOM', () => {
255
+ const flow = Flow({
256
+ turn: { order: TurnOrder.CUSTOM(['1', '0']) },
257
+ });
258
+
259
+ let state = { ctx: flow.ctx(2) } as State;
260
+ state = flow.init(state);
261
+
262
+ expect(state.ctx.currentPlayer).toBe('1');
263
+ expect(state.ctx).not.toHaveUndefinedProperties();
264
+
265
+ state = flow.processEvent(state, gameEvent('endTurn'));
266
+ expect(state.ctx.currentPlayer).toBe('0');
267
+ });
268
+
269
+ test('CUSTOM_FROM', () => {
270
+ const flow = Flow({
271
+ turn: { order: TurnOrder.CUSTOM_FROM('order') },
272
+ });
273
+
274
+ let state = { G: { order: ['2', '1', '0'] }, ctx: flow.ctx(3) } as State;
275
+ state = flow.init(state);
276
+
277
+ expect(state.ctx.currentPlayer).toBe('2');
278
+ expect(state.ctx).not.toHaveUndefinedProperties();
279
+
280
+ state = flow.processEvent(state, gameEvent('endTurn'));
281
+ expect(state.ctx.currentPlayer).toBe('1');
282
+ state = flow.processEvent(state, gameEvent('endTurn'));
283
+ expect(state.ctx.currentPlayer).toBe('0');
284
+ });
285
+
286
+ test('manual', () => {
287
+ const flow = Flow({
288
+ phases: {
289
+ A: {
290
+ start: true,
291
+ turn: {
292
+ order: {
293
+ first: () => 9,
294
+ next: () => 3,
295
+ },
296
+ },
297
+ },
298
+ },
299
+ });
300
+
301
+ let state = { ctx: flow.ctx(10) } as State;
302
+ state = flow.init(state);
303
+ expect(state.ctx.currentPlayer).toBe('9');
304
+ expect(state.ctx).not.toHaveUndefinedProperties();
305
+
306
+ state = flow.processEvent(state, gameEvent('endTurn'));
307
+ expect(state.ctx.currentPlayer).toBe('3');
308
+ });
309
+ });
310
+
311
+ test('override', () => {
312
+ const even = {
313
+ first: () => 0,
314
+ next: ({ ctx }) => (+ctx.currentPlayer + 2) % ctx.numPlayers,
315
+ };
316
+
317
+ const odd = {
318
+ first: () => 1,
319
+ next: ({ ctx }) => (+ctx.currentPlayer + 2) % ctx.numPlayers,
320
+ };
321
+
322
+ const flow = Flow({
323
+ turn: { order: even },
324
+ phases: { A: { start: true, next: 'B' }, B: { turn: { order: odd } } },
325
+ });
326
+
327
+ let state = { ctx: flow.ctx(10) } as State;
328
+ state = flow.init(state);
329
+
330
+ expect(state.ctx.currentPlayer).toBe('0');
331
+ state = flow.processEvent(state, gameEvent('endTurn'));
332
+ expect(state.ctx.currentPlayer).toBe('2');
333
+ state = flow.processEvent(state, gameEvent('endTurn'));
334
+ expect(state.ctx.currentPlayer).toBe('4');
335
+
336
+ state = flow.processEvent(state, gameEvent('endPhase'));
337
+
338
+ expect(state.ctx.currentPlayer).toBe('1');
339
+ state = flow.processEvent(state, gameEvent('endTurn'));
340
+ expect(state.ctx.currentPlayer).toBe('3');
341
+ state = flow.processEvent(state, gameEvent('endTurn'));
342
+ expect(state.ctx.currentPlayer).toBe('5');
343
+ });
344
+
345
+ test('playOrder', () => {
346
+ const game: Game = {};
347
+ const reducer = CreateGameReducer({ game });
348
+
349
+ let state = InitializeGame({ game, numPlayers: 3 });
350
+
351
+ state.ctx = {
352
+ ...state.ctx,
353
+ currentPlayer: '2',
354
+ playOrder: ['2', '0', '1'],
355
+ };
356
+
357
+ state = reducer(state, gameEvent('endTurn'));
358
+ expect(state.ctx.currentPlayer).toBe('0');
359
+ state = reducer(state, gameEvent('endTurn'));
360
+ expect(state.ctx.currentPlayer).toBe('1');
361
+ state = reducer(state, gameEvent('endTurn'));
362
+ expect(state.ctx.currentPlayer).toBe('2');
363
+ });
364
+
365
+ describe('setActivePlayers', () => {
366
+ const flow = Flow({});
367
+ const state = { ctx: flow.ctx(2) } as State;
368
+
369
+ test('basic', () => {
370
+ const newState = flow.processEvent(
371
+ state,
372
+ gameEvent('setActivePlayers', [{ value: { '1': Stage.NULL } }])
373
+ );
374
+ expect(newState.ctx.activePlayers).toMatchObject({ '1': Stage.NULL });
375
+ });
376
+
377
+ test('short form', () => {
378
+ const newState = flow.processEvent(
379
+ state,
380
+ gameEvent('setActivePlayers', [['1', '2']])
381
+ );
382
+ expect(newState.ctx.activePlayers).toMatchObject({
383
+ '1': Stage.NULL,
384
+ '2': Stage.NULL,
385
+ });
386
+ });
387
+
388
+ test('undefined stage leaves player inactive', () => {
389
+ const newState = flow.processEvent(
390
+ state,
391
+ gameEvent('setActivePlayers', [
392
+ {
393
+ value: {
394
+ '1': {
395
+ minMoves: 2,
396
+ maxMoves: 2,
397
+ },
398
+ },
399
+ },
400
+ ])
401
+ );
402
+ expect(newState.ctx.activePlayers).toBeNull();
403
+ });
404
+
405
+ test('all', () => {
406
+ const newState = flow.processEvent(
407
+ state,
408
+ gameEvent('setActivePlayers', [{ all: Stage.NULL }])
409
+ );
410
+ expect(newState.ctx.activePlayers).toMatchObject({
411
+ '0': Stage.NULL,
412
+ '1': Stage.NULL,
413
+ });
414
+ });
415
+
416
+ test('once', () => {
417
+ const game: Game = {
418
+ moves: {
419
+ B: ({ G, events }) => {
420
+ events.setActivePlayers({
421
+ value: { '0': Stage.NULL, '1': Stage.NULL },
422
+ minMoves: 1,
423
+ maxMoves: 1,
424
+ });
425
+ return G;
426
+ },
427
+ A: ({ G }) => G,
428
+ },
429
+ };
430
+
431
+ const reducer = CreateGameReducer({ game });
432
+
433
+ let state = InitializeGame({ game });
434
+ state = reducer(state, makeMove('B', null, '0'));
435
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '1']);
436
+ state = reducer(state, makeMove('A', null, '0'));
437
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['1']);
438
+ state = reducer(state, makeMove('A', null, '1'));
439
+ expect(state.ctx.activePlayers).toBeNull();
440
+ });
441
+
442
+ test('others', () => {
443
+ const game: Game = {
444
+ moves: {
445
+ B: ({ G, events }) => {
446
+ events.setActivePlayers({
447
+ minMoves: 1,
448
+ maxMoves: 1,
449
+ others: Stage.NULL,
450
+ });
451
+ return G;
452
+ },
453
+ A: ({ G }) => G,
454
+ },
455
+ };
456
+
457
+ const reducer = CreateGameReducer({ game });
458
+
459
+ let state = InitializeGame({ game, numPlayers: 3 });
460
+
461
+ // on move B, control switches from player 0 to players 1 and 2
462
+ state = reducer(state, makeMove('B', null, '0'));
463
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['1', '2']);
464
+
465
+ // player 1 makes move
466
+ state = reducer(state, makeMove('A', null, '1'));
467
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['2']);
468
+
469
+ // player 2 makes move
470
+ state = reducer(state, makeMove('A', null, '2'));
471
+ expect(state.ctx.activePlayers).toBeNull();
472
+ });
473
+
474
+ test('set stages to Stage.NULL', () => {
475
+ const game: Game = {
476
+ moves: {
477
+ A: ({ G }) => G,
478
+ B: ({ G, events }) => {
479
+ events.setActivePlayers({
480
+ minMoves: 1,
481
+ maxMoves: 1,
482
+ currentPlayer: 'start',
483
+ });
484
+ return G;
485
+ },
486
+ },
487
+ turn: {
488
+ activePlayers: {
489
+ currentPlayer: {
490
+ stage: 'start',
491
+ },
492
+ others: Stage.NULL,
493
+ },
494
+ stages: {
495
+ start: {
496
+ moves: {
497
+ S: ({ G, events }) => {
498
+ events.setStage(Stage.NULL);
499
+ return G;
500
+ },
501
+ },
502
+ },
503
+ },
504
+ },
505
+ };
506
+ const reducer = CreateGameReducer({ game });
507
+ let state = InitializeGame({ game, numPlayers: 3 });
508
+
509
+ expect(state.ctx.currentPlayer).toBe('0');
510
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '1', '2']);
511
+ expect(state.ctx.activePlayers['0']).toEqual('start');
512
+ expect(state.ctx.activePlayers['1']).toEqual(Stage.NULL);
513
+ expect(state.ctx.activePlayers['2']).toEqual(Stage.NULL);
514
+
515
+ state = reducer(state, makeMove('S', null, '0'));
516
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '1', '2']);
517
+ expect(state.ctx.activePlayers['0']).toEqual(Stage.NULL);
518
+
519
+ state = reducer(state, makeMove('B', null, '0'));
520
+ expect(Object.keys(state.ctx.activePlayers)).toEqual(['0']);
521
+ expect(state.ctx.activePlayers['0']).toEqual('start');
522
+ });
523
+
524
+ describe('reset behavior', () => {
525
+ test('start of turn', () => {
526
+ const game: Game = {
527
+ moves: {
528
+ A: () => {},
529
+ },
530
+
531
+ turn: {
532
+ activePlayers: { currentPlayer: 'stage', minMoves: 1, maxMoves: 1 },
533
+ },
534
+ };
535
+
536
+ const reducer = CreateGameReducer({ game });
537
+ let state = InitializeGame({ game });
538
+
539
+ expect(state.ctx).toMatchObject({
540
+ activePlayers: { '0': 'stage' },
541
+ _prevActivePlayers: [],
542
+ });
543
+
544
+ state = reducer(state, makeMove('A', null, '0'));
545
+
546
+ expect(state.ctx).toMatchObject({
547
+ activePlayers: null,
548
+ _prevActivePlayers: [],
549
+ });
550
+ });
551
+
552
+ describe('revert', () => {
553
+ test('resets to previous', () => {
554
+ const game: Game = {
555
+ moves: {
556
+ A: ({ events }) => {
557
+ events.setActivePlayers({
558
+ currentPlayer: 'stage2',
559
+ minMoves: 1,
560
+ maxMoves: 1,
561
+ revert: true,
562
+ });
563
+ },
564
+ B: () => {},
565
+ },
566
+
567
+ turn: {
568
+ activePlayers: { currentPlayer: 'stage1' },
569
+ },
570
+ };
571
+
572
+ const reducer = CreateGameReducer({ game });
573
+ let state = InitializeGame({ game });
574
+
575
+ expect(state.ctx).toMatchObject({
576
+ activePlayers: { '0': 'stage1' },
577
+ _prevActivePlayers: [],
578
+ });
579
+
580
+ state = reducer(state, makeMove('A', null, '0'));
581
+
582
+ expect(state.ctx).toMatchObject({
583
+ activePlayers: { '0': 'stage2' },
584
+ _prevActivePlayers: [
585
+ {
586
+ activePlayers: { '0': 'stage1' },
587
+ _activePlayersMinMoves: null,
588
+ _activePlayersMaxMoves: null,
589
+ _activePlayersNumMoves: { '0': 1 },
590
+ },
591
+ ],
592
+ });
593
+
594
+ state = reducer(state, makeMove('B', null, '0'));
595
+
596
+ expect(state.ctx).toMatchObject({
597
+ activePlayers: { '0': 'stage1' },
598
+ _prevActivePlayers: [],
599
+ });
600
+ });
601
+
602
+ test('restores move limits and counts', () => {
603
+ const game: Game = {
604
+ moves: {
605
+ A: ({ events }) => {
606
+ events.setActivePlayers({
607
+ currentPlayer: 'stage2',
608
+ minMoves: 1,
609
+ maxMoves: 1,
610
+ revert: true,
611
+ });
612
+ },
613
+ B: () => {},
614
+ },
615
+
616
+ turn: {
617
+ activePlayers: {
618
+ currentPlayer: 'stage1',
619
+ minMoves: 2,
620
+ maxMoves: 3,
621
+ },
622
+ },
623
+ };
624
+
625
+ const reducer = CreateGameReducer({ game });
626
+ let state = InitializeGame({ game });
627
+
628
+ expect(state.ctx).toMatchObject({
629
+ activePlayers: { '0': 'stage1' },
630
+ _prevActivePlayers: [],
631
+ _activePlayersMinMoves: { '0': 2 },
632
+ _activePlayersMaxMoves: { '0': 3 },
633
+ _activePlayersNumMoves: {
634
+ '0': 0,
635
+ },
636
+ });
637
+
638
+ state = reducer(state, makeMove('B', null, '0'));
639
+
640
+ expect(state.ctx).toMatchObject({
641
+ activePlayers: { '0': 'stage1' },
642
+ _prevActivePlayers: [],
643
+ _activePlayersMinMoves: { '0': 2 },
644
+ _activePlayersMaxMoves: { '0': 3 },
645
+ _activePlayersNumMoves: {
646
+ '0': 1,
647
+ },
648
+ });
649
+
650
+ state = reducer(state, makeMove('A', null, '0'));
651
+
652
+ expect(state.ctx).toMatchObject({
653
+ activePlayers: { '0': 'stage2' },
654
+ _prevActivePlayers: [
655
+ {
656
+ activePlayers: { '0': 'stage1' },
657
+ _activePlayersNumMoves: { '0': 2 },
658
+ _activePlayersMinMoves: { '0': 2 },
659
+ _activePlayersMaxMoves: { '0': 3 },
660
+ },
661
+ ],
662
+ _activePlayersMinMoves: { '0': 1 },
663
+ _activePlayersMaxMoves: { '0': 1 },
664
+ _activePlayersNumMoves: {
665
+ '0': 0,
666
+ },
667
+ });
668
+
669
+ state = reducer(state, makeMove('B', null, '0'));
670
+
671
+ expect(state.ctx).toMatchObject({
672
+ activePlayers: { '0': 'stage1' },
673
+ _prevActivePlayers: [],
674
+ _activePlayersMinMoves: { '0': 2 },
675
+ _activePlayersMaxMoves: { '0': 3 },
676
+ _activePlayersNumMoves: {
677
+ '0': 2,
678
+ },
679
+ });
680
+ });
681
+ });
682
+
683
+ test('set to next', () => {
684
+ const game: Game = {
685
+ moves: {
686
+ A: () => {},
687
+ },
688
+
689
+ turn: {
690
+ activePlayers: {
691
+ currentPlayer: 'stage1',
692
+ minMoves: 1,
693
+ maxMoves: 1,
694
+ next: {
695
+ currentPlayer: 'stage2',
696
+ minMoves: 1,
697
+ maxMoves: 1,
698
+ next: {
699
+ currentPlayer: 'stage3',
700
+ },
701
+ },
702
+ },
703
+ },
704
+ };
705
+
706
+ const reducer = CreateGameReducer({ game });
707
+ let state = InitializeGame({ game });
708
+
709
+ expect(state.ctx).toMatchObject({
710
+ activePlayers: { '0': 'stage1' },
711
+ _prevActivePlayers: [],
712
+ _nextActivePlayers: {
713
+ currentPlayer: 'stage2',
714
+ minMoves: 1,
715
+ maxMoves: 1,
716
+ next: {
717
+ currentPlayer: 'stage3',
718
+ },
719
+ },
720
+ });
721
+
722
+ state = reducer(state, makeMove('A', null, '0'));
723
+
724
+ expect(state.ctx).toMatchObject({
725
+ activePlayers: { '0': 'stage2' },
726
+ _prevActivePlayers: [],
727
+ _nextActivePlayers: {
728
+ currentPlayer: 'stage3',
729
+ },
730
+ });
731
+
732
+ state = reducer(state, makeMove('A', null, '0'));
733
+
734
+ expect(state.ctx).toMatchObject({
735
+ activePlayers: { '0': 'stage3' },
736
+ _prevActivePlayers: [],
737
+ _nextActivePlayers: null,
738
+ });
739
+ });
740
+ });
741
+
742
+ describe('move limits', () => {
743
+ test('shorthand syntax', () => {
744
+ const game: Game = {
745
+ turn: {
746
+ activePlayers: {
747
+ all: 'play',
748
+ minMoves: 1,
749
+ maxMoves: 3,
750
+ },
751
+ stages: {
752
+ play: { moves: { A: () => {} } },
753
+ },
754
+ },
755
+ };
756
+
757
+ const reducer = CreateGameReducer({ game });
758
+ let state = InitializeGame({ game, numPlayers: 3 });
759
+
760
+ expect(state.ctx._activePlayersMinMoves).toEqual({
761
+ '0': 1,
762
+ '1': 1,
763
+ '2': 1,
764
+ });
765
+
766
+ expect(state.ctx._activePlayersMaxMoves).toEqual({
767
+ '0': 3,
768
+ '1': 3,
769
+ '2': 3,
770
+ });
771
+
772
+ expect(state.ctx._activePlayersNumMoves).toEqual({
773
+ '0': 0,
774
+ '1': 0,
775
+ '2': 0,
776
+ });
777
+
778
+ state = reducer(state, makeMove('A', null, '0'));
779
+ state = reducer(state, makeMove('A', null, '1'));
780
+ state = reducer(state, makeMove('A', null, '1'));
781
+ state = reducer(state, makeMove('A', null, '2'));
782
+
783
+ expect(state.ctx._activePlayersNumMoves).toEqual({
784
+ '0': 1,
785
+ '1': 2,
786
+ '2': 1,
787
+ });
788
+
789
+ state = reducer(state, makeMove('A', null, '1'));
790
+
791
+ expect(state.ctx.activePlayers).toEqual({
792
+ '0': 'play',
793
+ '2': 'play',
794
+ });
795
+ });
796
+
797
+ test('long-form syntax', () => {
798
+ const game: Game = {
799
+ turn: {
800
+ activePlayers: {
801
+ currentPlayer: { stage: 'play', minMoves: 1, maxMoves: 2 },
802
+ others: { stage: 'play', maxMoves: 1 },
803
+ },
804
+ stages: {
805
+ play: { moves: { A: () => {} } },
806
+ },
807
+ },
808
+ };
809
+
810
+ const reducer = CreateGameReducer({ game });
811
+ let state = InitializeGame({ game, numPlayers: 3 });
812
+
813
+ expect(state.ctx._activePlayersMinMoves).toStrictEqual({ '0': 1 });
814
+
815
+ expect(state.ctx._activePlayersMaxMoves).toEqual({
816
+ '0': 2,
817
+ '1': 1,
818
+ '2': 1,
819
+ });
820
+
821
+ expect(state.ctx._activePlayersNumMoves).toEqual({
822
+ '0': 0,
823
+ '1': 0,
824
+ '2': 0,
825
+ });
826
+
827
+ state = reducer(state, makeMove('A', null, '0'));
828
+ state = reducer(state, makeMove('A', null, '1'));
829
+ state = reducer(state, makeMove('A', null, '2'));
830
+
831
+ expect(state.ctx._activePlayersNumMoves).toEqual({
832
+ '0': 1,
833
+ '1': 1,
834
+ '2': 1,
835
+ });
836
+
837
+ expect(state.ctx.activePlayers).toEqual({ '0': 'play' });
838
+
839
+ state = reducer(state, makeMove('A', null, '0'));
840
+
841
+ expect(state.ctx.activePlayers).toBeNull();
842
+ });
843
+
844
+ test('player-specific limit overrides move limit args', () => {
845
+ const game: Game = {
846
+ turn: {
847
+ activePlayers: {
848
+ all: { stage: 'play', minMoves: 2, maxMoves: 2 },
849
+ minMoves: 1,
850
+ maxMoves: 1,
851
+ },
852
+ },
853
+ };
854
+
855
+ const state = InitializeGame({ game, numPlayers: 2 });
856
+
857
+ expect(state.ctx._activePlayersMinMoves).toEqual({
858
+ '0': 2,
859
+ '1': 2,
860
+ });
861
+
862
+ expect(state.ctx._activePlayersMaxMoves).toEqual({
863
+ '0': 2,
864
+ '1': 2,
865
+ });
866
+ });
867
+
868
+ test('value syntax', () => {
869
+ const game: Game = {
870
+ turn: {
871
+ activePlayers: {
872
+ value: {
873
+ '0': { stage: 'play', maxMoves: 1 },
874
+ '1': { stage: 'play', minMoves: 1, maxMoves: 2 },
875
+ '2': { stage: 'play', minMoves: 2, maxMoves: 3 },
876
+ },
877
+ },
878
+ stages: {
879
+ play: { moves: { A: () => {} } },
880
+ },
881
+ },
882
+ };
883
+
884
+ const reducer = CreateGameReducer({ game });
885
+ let state = InitializeGame({ game, numPlayers: 3 });
886
+
887
+ expect(state.ctx._activePlayersMinMoves).toStrictEqual({
888
+ '1': 1,
889
+ '2': 2,
890
+ });
891
+
892
+ expect(state.ctx._activePlayersMaxMoves).toEqual({
893
+ '0': 1,
894
+ '1': 2,
895
+ '2': 3,
896
+ });
897
+
898
+ state = reducer(state, makeMove('A', null, '0'));
899
+ state = reducer(state, makeMove('A', null, '1'));
900
+ state = reducer(state, makeMove('A', null, '2'));
901
+
902
+ expect(state.ctx.activePlayers).toEqual({ '1': 'play', '2': 'play' });
903
+
904
+ state = reducer(state, makeMove('A', null, '1'));
905
+ state = reducer(state, makeMove('A', null, '2'));
906
+
907
+ expect(state.ctx.activePlayers).toEqual({ '2': 'play' });
908
+
909
+ state = reducer(state, makeMove('A', null, '2'));
910
+ expect(state.ctx.activePlayers).toBeNull();
911
+ });
912
+
913
+ test('move counts reset on turn end', () => {
914
+ const game: Game = {
915
+ turn: {
916
+ activePlayers: {
917
+ all: 'play',
918
+ },
919
+ stages: {
920
+ play: { moves: { A: () => {} } },
921
+ },
922
+ },
923
+ };
924
+
925
+ const reducer = CreateGameReducer({ game });
926
+ let state = InitializeGame({ game, numPlayers: 3 });
927
+
928
+ state = reducer(state, makeMove('A', null, '0'));
929
+ state = reducer(state, makeMove('A', null, '1'));
930
+
931
+ expect(state.ctx._activePlayersNumMoves).toEqual({
932
+ '0': 1,
933
+ '1': 1,
934
+ '2': 0,
935
+ });
936
+
937
+ state = reducer(state, gameEvent('endTurn'));
938
+
939
+ expect(state.ctx._activePlayersNumMoves).toEqual({
940
+ '0': 0,
941
+ '1': 0,
942
+ '2': 0,
943
+ });
944
+ });
945
+ });
946
+
947
+ describe('militia', () => {
948
+ let state;
949
+ let reducer;
950
+ beforeAll(() => {
951
+ const game: Game = {
952
+ moves: {
953
+ militia: ({ events }) => {
954
+ events.setActivePlayers({
955
+ others: 'discard',
956
+ minMoves: 1,
957
+ maxMoves: 1,
958
+ revert: true,
959
+ });
960
+ },
961
+ },
962
+
963
+ turn: {
964
+ stages: {
965
+ discard: {
966
+ moves: {
967
+ discard: ({ G }) => G,
968
+ },
969
+ },
970
+ },
971
+ },
972
+ };
973
+
974
+ reducer = CreateGameReducer({ game });
975
+ state = InitializeGame({ game, numPlayers: 3 });
976
+ });
977
+
978
+ beforeEach(() => {
979
+ (error as jest.Mock).mockClear();
980
+ });
981
+
982
+ test('sanity', () => {
983
+ expect(state.ctx.activePlayers).toEqual(null);
984
+ });
985
+
986
+ test('player 1 cannot play the militia card', () => {
987
+ state = reducer(state, makeMove('militia', undefined, '1'));
988
+ expect(error).toHaveBeenCalledWith('disallowed move: militia');
989
+ });
990
+
991
+ test('player 2 cannot play the militia card', () => {
992
+ state = reducer(state, makeMove('militia', undefined, '2'));
993
+ expect(error).toHaveBeenCalledWith('disallowed move: militia');
994
+ });
995
+
996
+ test('player 0 cannot discard', () => {
997
+ state = reducer(state, makeMove('discard', undefined, '0'));
998
+ expect(error).toHaveBeenCalledWith('disallowed move: discard');
999
+ });
1000
+
1001
+ test('player 1 cannot discard', () => {
1002
+ state = reducer(state, makeMove('discard', undefined, '1'));
1003
+ expect(error).toHaveBeenCalledWith('disallowed move: discard');
1004
+ });
1005
+
1006
+ test('player 2 cannot discard', () => {
1007
+ state = reducer(state, makeMove('discard', undefined, '2'));
1008
+ expect(error).toHaveBeenCalledWith('disallowed move: discard');
1009
+ });
1010
+
1011
+ test('player 0 plays militia', () => {
1012
+ state = reducer(state, makeMove('militia', undefined, '0'));
1013
+ expect(state.ctx.activePlayers).toEqual({
1014
+ '1': 'discard',
1015
+ '2': 'discard',
1016
+ });
1017
+ });
1018
+
1019
+ test('player 0 cannot play militia again', () => {
1020
+ state = reducer(state, makeMove('militia', undefined, '0'));
1021
+ expect(error).toHaveBeenCalledWith('disallowed move: militia');
1022
+ });
1023
+
1024
+ test('player 0 still cannot discard', () => {
1025
+ state = reducer(state, makeMove('discard', undefined, '0'));
1026
+ expect(error).toHaveBeenCalledWith('disallowed move: discard');
1027
+ });
1028
+
1029
+ test('everyone else discards', () => {
1030
+ state = reducer(state, makeMove('discard', undefined, '1'));
1031
+ expect(state.ctx.activePlayers).toEqual({ '2': 'discard' });
1032
+ state = reducer(state, makeMove('discard', undefined, '2'));
1033
+ });
1034
+
1035
+ test('activePlayers is restored to previous state', () => {
1036
+ expect(state.ctx.activePlayers).toEqual(null);
1037
+ });
1038
+ });
1039
+ });
1040
+
1041
+ describe('UpdateTurnOrderState', () => {
1042
+ const G = {};
1043
+ const ctx = {
1044
+ currentPlayer: '0',
1045
+ playOrder: ['0', '1', '2'],
1046
+ playOrderPos: 0,
1047
+ };
1048
+ const turn = { order: TurnOrder.DEFAULT };
1049
+
1050
+ test('without next player', () => {
1051
+ const { ctx: t } = UpdateTurnOrderState(
1052
+ { G, ctx } as State,
1053
+ ctx.currentPlayer,
1054
+ turn
1055
+ );
1056
+ expect(t).toMatchObject({ currentPlayer: '1' });
1057
+ });
1058
+
1059
+ test('with next player', () => {
1060
+ const { ctx: t } = UpdateTurnOrderState(
1061
+ { G, ctx } as State,
1062
+ ctx.currentPlayer,
1063
+ turn,
1064
+ {
1065
+ next: '2',
1066
+ }
1067
+ );
1068
+ expect(t).toMatchObject({ currentPlayer: '2' });
1069
+ });
1070
+
1071
+ test('errors if turn.order.next doesn’t return a number', () => {
1072
+ UpdateTurnOrderState({ G, ctx } as State, ctx.currentPlayer, {
1073
+ order: {
1074
+ first: () => 0,
1075
+ next: () => '2' as unknown as number,
1076
+ },
1077
+ });
1078
+ expect(error).toHaveBeenCalledWith(
1079
+ `invalid value returned by turn.order.next — expected number or undefined got string “2”.`
1080
+ );
1081
+ });
1082
+ });
1083
+
1084
+ describe('Random API is available', () => {
1085
+ let first;
1086
+ let next;
1087
+
1088
+ const turn = {
1089
+ order: {
1090
+ first: ({ random }) => {
1091
+ if (random !== undefined) {
1092
+ first = true;
1093
+ }
1094
+ return 0;
1095
+ },
1096
+
1097
+ next: ({ random }) => {
1098
+ if (random !== undefined) {
1099
+ next = true;
1100
+ }
1101
+ return 0;
1102
+ },
1103
+ },
1104
+ };
1105
+
1106
+ const game: Game = { turn };
1107
+
1108
+ beforeEach(() => {
1109
+ first = next = false;
1110
+ });
1111
+
1112
+ test('init', () => {
1113
+ Client({ game });
1114
+ expect(first).toBe(true);
1115
+ });
1116
+
1117
+ test('end turn', () => {
1118
+ const client = Client({ game });
1119
+ expect(next).toBe(false);
1120
+ client.events.endTurn();
1121
+ expect(next).toBe(true);
1122
+ });
1123
+ });