@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,209 @@
1
+ /*
2
+ * Copyright 2018 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 type { State, Ctx, PlayerID, Game, ActivePlayersArg } from '../../types';
10
+ import { automaticGameEvent } from '../../core/action-creators';
11
+ import { GameMethod } from '../../core/game-methods';
12
+
13
+ enum Errors {
14
+ CalledOutsideHook = 'Events must be called from moves or the `onBegin`, `onEnd`, and `onMove` hooks.\n' +
15
+ 'This error probably means you called an event from other game code, like an `endIf` trigger or one of the `turn.order` methods.',
16
+
17
+ EndTurnInOnEnd = '`endTurn` is disallowed in `onEnd` hooks — the turn is already ending.',
18
+
19
+ MaxTurnEndings = 'Maximum number of turn endings exceeded for this update.\n' +
20
+ 'This likely means game code is triggering an infinite loop.',
21
+
22
+ PhaseEventInOnEnd = '`setPhase` & `endPhase` are disallowed in a phase’s `onEnd` hook — the phase is already ending.\n' +
23
+ 'If you’re trying to dynamically choose the next phase when a phase ends, use the phase’s `next` trigger.',
24
+
25
+ StageEventInOnEnd = '`setStage`, `endStage` & `setActivePlayers` are disallowed in `onEnd` hooks.',
26
+
27
+ StageEventInPhaseBegin = '`setStage`, `endStage` & `setActivePlayers` are disallowed in a phase’s `onBegin` hook.\n' +
28
+ 'Use `setActivePlayers` in a `turn.onBegin` hook or declare stages with `turn.activePlayers` instead.',
29
+
30
+ StageEventInTurnBegin = '`setStage` & `endStage` are disallowed in `turn.onBegin`.\n' +
31
+ 'Use `setActivePlayers` or declare stages with `turn.activePlayers` instead.',
32
+ }
33
+
34
+ export interface EventsAPI {
35
+ endGame(gameover?: any): void;
36
+ endPhase(): void;
37
+ endStage(): void;
38
+ endTurn(arg?: { next: PlayerID }): void;
39
+ pass(arg?: { remove: true }): void;
40
+ setActivePlayers(arg: ActivePlayersArg): void;
41
+ setPhase(newPhase: string): void;
42
+ setStage(newStage: string): void;
43
+ }
44
+
45
+ export interface PrivateEventsAPI {
46
+ _private: {
47
+ isUsed(): boolean;
48
+ updateTurnContext(ctx: Ctx, methodType: GameMethod | undefined): void;
49
+ unsetCurrentMethod(): void;
50
+ update(state: State): State;
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Events
56
+ */
57
+ export class Events {
58
+ flow: Game['flow'];
59
+ playerID: PlayerID | undefined;
60
+ dispatch: Array<{
61
+ type: string;
62
+ args: any[];
63
+ phase: string;
64
+ turn: number;
65
+ calledFrom: GameMethod | undefined;
66
+ error: Error;
67
+ }>;
68
+ maxEndedTurnsPerAction: number;
69
+ initialTurn: number;
70
+ currentPhase: string;
71
+ currentTurn: number;
72
+ currentMethod?: GameMethod;
73
+
74
+ constructor(flow: Game['flow'], ctx: Ctx, playerID?: PlayerID) {
75
+ this.flow = flow;
76
+ this.playerID = playerID;
77
+ this.dispatch = [];
78
+ this.initialTurn = ctx.turn;
79
+ this.updateTurnContext(ctx, undefined);
80
+ // This is an arbitrarily large upper threshold, which could be made
81
+ // configurable via a game option if the need arises.
82
+ this.maxEndedTurnsPerAction = ctx.numPlayers * 100;
83
+ }
84
+
85
+ api() {
86
+ const events = {
87
+ _private: this,
88
+ } as unknown as EventsAPI & PrivateEventsAPI;
89
+ for (const type of this.flow.eventNames) {
90
+ events[type] = (...args: any[]) => {
91
+ this.dispatch.push({
92
+ type,
93
+ args,
94
+ phase: this.currentPhase,
95
+ turn: this.currentTurn,
96
+ calledFrom: this.currentMethod,
97
+ // Used to capture a stack trace in case it is needed later.
98
+ error: new Error('Events Plugin Error'),
99
+ });
100
+ };
101
+ }
102
+
103
+ return events;
104
+ }
105
+
106
+ isUsed() {
107
+ return this.dispatch.length > 0;
108
+ }
109
+
110
+ updateTurnContext(ctx: Ctx, methodType: GameMethod | undefined) {
111
+ this.currentPhase = ctx.phase;
112
+ this.currentTurn = ctx.turn;
113
+ this.currentMethod = methodType;
114
+ }
115
+
116
+ unsetCurrentMethod() {
117
+ this.currentMethod = undefined;
118
+ }
119
+
120
+ /**
121
+ * Updates ctx with the triggered events.
122
+ * @param {object} state - The state object { G, ctx }.
123
+ */
124
+ update(state: State): State {
125
+ const initialState = state;
126
+ const stateWithError = ({ stack }: Error, message: Errors) => ({
127
+ ...initialState,
128
+ plugins: {
129
+ ...initialState.plugins,
130
+ events: {
131
+ ...initialState.plugins.events,
132
+ data: { error: message + '\n' + stack },
133
+ },
134
+ },
135
+ });
136
+
137
+ EventQueue: for (let i = 0; i < this.dispatch.length; i++) {
138
+ const event = this.dispatch[i];
139
+ const turnHasEnded = event.turn !== state.ctx.turn;
140
+
141
+ // This protects against potential infinite loops if specific events are called on hooks.
142
+ // The moment we exceed the defined threshold, we just bail out of all phases.
143
+ const endedTurns = this.currentTurn - this.initialTurn;
144
+ if (endedTurns >= this.maxEndedTurnsPerAction) {
145
+ return stateWithError(event.error, Errors.MaxTurnEndings);
146
+ }
147
+
148
+ if (event.calledFrom === undefined) {
149
+ return stateWithError(event.error, Errors.CalledOutsideHook);
150
+ }
151
+
152
+ // Stop processing events once the game has finished.
153
+ if (state.ctx.gameover) break EventQueue;
154
+
155
+ switch (event.type) {
156
+ case 'endStage':
157
+ case 'setStage':
158
+ case 'setActivePlayers': {
159
+ switch (event.calledFrom) {
160
+ // Disallow all stage events in onEnd and phase.onBegin hooks.
161
+ case GameMethod.TURN_ON_END:
162
+ case GameMethod.PHASE_ON_END:
163
+ return stateWithError(event.error, Errors.StageEventInOnEnd);
164
+ case GameMethod.PHASE_ON_BEGIN:
165
+ return stateWithError(event.error, Errors.StageEventInPhaseBegin);
166
+ // Disallow setStage & endStage in turn.onBegin hooks.
167
+ case GameMethod.TURN_ON_BEGIN:
168
+ if (event.type === 'setActivePlayers') break;
169
+ return stateWithError(event.error, Errors.StageEventInTurnBegin);
170
+ }
171
+
172
+ // If the turn already ended, don't try to process stage events.
173
+ if (turnHasEnded) continue EventQueue;
174
+ break;
175
+ }
176
+
177
+ case 'endTurn': {
178
+ if (
179
+ event.calledFrom === GameMethod.TURN_ON_END ||
180
+ event.calledFrom === GameMethod.PHASE_ON_END
181
+ ) {
182
+ return stateWithError(event.error, Errors.EndTurnInOnEnd);
183
+ }
184
+
185
+ // If the turn already ended some other way,
186
+ // don't try to end the turn again.
187
+ if (turnHasEnded) continue EventQueue;
188
+ break;
189
+ }
190
+
191
+ case 'endPhase':
192
+ case 'setPhase': {
193
+ if (event.calledFrom === GameMethod.PHASE_ON_END) {
194
+ return stateWithError(event.error, Errors.PhaseEventInOnEnd);
195
+ }
196
+
197
+ // If the phase already ended some other way,
198
+ // don't try to end the phase again.
199
+ if (event.phase !== state.ctx.phase) continue EventQueue;
200
+ break;
201
+ }
202
+ }
203
+
204
+ const action = automaticGameEvent(event.type, event.args, this.playerID);
205
+ state = this.flow.processEvent(state, action);
206
+ }
207
+ return state;
208
+ }
209
+ }
@@ -0,0 +1,411 @@
1
+ /*
2
+ * Copyright 2019 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 { Client } from '../client/client';
10
+ import { Local } from '../client/transport/local';
11
+ import type { Game, Plugin } from '../types';
12
+ import { GameMethod } from '../../packages/core';
13
+
14
+ describe('basic', () => {
15
+ let client: ReturnType<typeof Client>;
16
+
17
+ beforeAll(() => {
18
+ interface TestPluginAPI {
19
+ get(): number;
20
+ increment(): number;
21
+ }
22
+
23
+ const TestPlugin = (init: {
24
+ n: number;
25
+ }): Plugin<TestPluginAPI, { n: number }> => ({
26
+ name: 'test',
27
+
28
+ setup: () => init,
29
+
30
+ api: ({ data }) => {
31
+ const state = { value: data.n };
32
+ const increment = () => state.value++;
33
+ const get = () => state.value;
34
+ return { increment, get };
35
+ },
36
+
37
+ flush: ({ api }) => ({ n: api.get() }),
38
+
39
+ fnWrap: (fn) => (context) => {
40
+ const G = fn(context);
41
+ return { ...G, wrap: true };
42
+ },
43
+ });
44
+
45
+ const game: Game<
46
+ { beginA: number; endA: number; onMove: number; onTurnEnd: number },
47
+ { test: TestPluginAPI }
48
+ > = {
49
+ moves: {
50
+ A: ({ G, test }) => {
51
+ G.beginA = test.get();
52
+ test.increment();
53
+ G.endA = test.get();
54
+ },
55
+ },
56
+
57
+ endIf: ({ test }) => {
58
+ if (test === undefined) {
59
+ throw new Error('API is not defined');
60
+ }
61
+ },
62
+
63
+ turn: {
64
+ onMove: ({ G, test }) => {
65
+ G.onMove = test.get();
66
+ test.increment();
67
+ },
68
+
69
+ onEnd: ({ G, test }) => {
70
+ G.onTurnEnd = test.get();
71
+ test.increment();
72
+ },
73
+ },
74
+
75
+ plugins: [TestPlugin({ n: 10 })],
76
+ };
77
+
78
+ client = Client({ game });
79
+ });
80
+
81
+ test('setup', () => {
82
+ expect(client.getState().plugins.test.data).toEqual({
83
+ n: 10,
84
+ });
85
+ });
86
+
87
+ test('make move', () => {
88
+ client.moves.A();
89
+ expect(client.getState().G).toEqual({
90
+ beginA: 10,
91
+ endA: 11,
92
+ onMove: 11,
93
+ wrap: true,
94
+ });
95
+ expect(client.getState().plugins.test.data).toEqual({ n: 12 });
96
+ });
97
+
98
+ test('make another move', () => {
99
+ client.moves.A();
100
+ expect(client.getState().G).toEqual({
101
+ beginA: 12,
102
+ endA: 13,
103
+ onMove: 13,
104
+ wrap: true,
105
+ });
106
+ expect(client.getState().plugins.test.data).toEqual({ n: 14 });
107
+ });
108
+
109
+ test('event', () => {
110
+ client.events.endTurn();
111
+ expect(client.getState().G).toMatchObject({ onTurnEnd: 14 });
112
+ expect(client.getState().plugins.test.data).toEqual({ n: 15 });
113
+ });
114
+
115
+ test('does not make it into undo state', () => {
116
+ client.moves.A();
117
+ client.undo();
118
+ expect(Object.keys(client.getState()._undo[0].ctx)).not.toContain('test');
119
+ });
120
+ });
121
+
122
+ describe('default values', () => {
123
+ const pluginData = { value: true };
124
+
125
+ const plugin = {
126
+ name: 'test',
127
+ flush: () => pluginData,
128
+ };
129
+
130
+ const anotherPlugin = {
131
+ name: 'test2',
132
+ noClient: () => false,
133
+ };
134
+
135
+ const game: Game = {
136
+ moves: { A: () => {} },
137
+ plugins: [plugin, anotherPlugin],
138
+ };
139
+
140
+ test('are used if no setup is present', () => {
141
+ const client = Client({ game, playerID: '0', multiplayer: Local() });
142
+ client.start();
143
+ client.moves.A();
144
+ expect(client.getState().plugins.test.data).toEqual(pluginData);
145
+ });
146
+ });
147
+
148
+ describe('isInvalid method', () => {
149
+ // Silence expected error logging and restore when finished.
150
+ const stderr = console.error;
151
+ beforeAll(() => (console.error = () => {}));
152
+ afterAll(() => (console.error = stderr));
153
+
154
+ test('basic plugin', () => {
155
+ const goodG = { good: 'nice' };
156
+ const game: Game = {
157
+ plugins: [
158
+ {
159
+ name: 'test',
160
+ isInvalid: ({ G }) => 'bad' in G && 'not ok',
161
+ },
162
+ ],
163
+ moves: {
164
+ good: () => goodG,
165
+ bad: () => ({ bad: 'not ok' }),
166
+ },
167
+ };
168
+
169
+ const client = Client({ game, playerID: '0' });
170
+ client.start();
171
+ client.moves.good();
172
+ expect(client.getState().G).toEqual(goodG);
173
+ client.moves.bad();
174
+ expect(client.getState().G).toEqual(goodG);
175
+ });
176
+
177
+ test('plugin with API and data', () => {
178
+ const game: Game<any, any> = {
179
+ plugins: [
180
+ {
181
+ name: 'test',
182
+ setup: () => ({}),
183
+ api: ({ data }) => ({
184
+ set: (key, val) => {
185
+ data[key] = val;
186
+ },
187
+ }),
188
+ isInvalid: ({ data }) => 'bad' in data && 'not ok',
189
+ },
190
+ ],
191
+ moves: {
192
+ good: ({ test }) => {
193
+ test.set('good', 'nice');
194
+ },
195
+ bad: ({ test }) => {
196
+ test.set('bad', 'not ok');
197
+ },
198
+ },
199
+ };
200
+
201
+ const client = Client({ game, playerID: '0' });
202
+ client.start();
203
+ expect(client.getState().ctx.numMoves).toBe(0);
204
+ client.moves.good();
205
+ expect(client.getState().ctx.numMoves).toBe(1);
206
+ client.moves.bad();
207
+ expect(client.getState().ctx.numMoves).toBe(1);
208
+ });
209
+ });
210
+
211
+ describe('actions', () => {
212
+ let client: ReturnType<typeof Client>;
213
+
214
+ beforeAll(() => {
215
+ const game: Game = {
216
+ plugins: [
217
+ {
218
+ name: 'test',
219
+
220
+ setup: () => ({
221
+ initial: true,
222
+ }),
223
+
224
+ action: (_, payload) => {
225
+ return payload.args[0];
226
+ },
227
+ },
228
+ {
229
+ name: 'nosetup',
230
+ action: () => ({ action: true }),
231
+ },
232
+ ],
233
+ };
234
+
235
+ client = Client({ game });
236
+ });
237
+
238
+ test('setup', () => {
239
+ expect(client.getState().plugins.test.data).toEqual({
240
+ initial: true,
241
+ });
242
+ expect(client.getState().plugins.nosetup).toBeUndefined();
243
+ });
244
+
245
+ test('take action', () => {
246
+ const payload = { payload: true };
247
+
248
+ client.plugins.test(payload);
249
+ client.plugins.nosetup(payload);
250
+
251
+ expect(client.getState().plugins.test.data).toEqual(payload);
252
+ expect(client.getState().plugins.nosetup.data).toEqual({ action: true });
253
+ });
254
+ });
255
+
256
+ describe('plugins are accessible in events triggered from moves', () => {
257
+ type TestPluginAPI = { get: () => boolean };
258
+ type PluginAPIs = { test: TestPluginAPI };
259
+ const plugins = [
260
+ {
261
+ name: 'test',
262
+
263
+ setup: () => ({
264
+ initial: true,
265
+ }),
266
+
267
+ flush: () => ({ initial: true }),
268
+
269
+ api: ({ data }): TestPluginAPI => {
270
+ return {
271
+ get: () => data.initial,
272
+ };
273
+ },
274
+ },
275
+ ];
276
+
277
+ test('turn/onBegin', () => {
278
+ const game: Game<any, PluginAPIs> = {
279
+ plugins,
280
+ moves: {
281
+ stop: ({ events }) => events.endTurn(),
282
+ },
283
+ turn: {
284
+ onBegin: ({ G, random, test }) => {
285
+ G.onBegin = random.Die(1);
286
+ G.test = test.get();
287
+ },
288
+ },
289
+ };
290
+
291
+ const client = Client({ game });
292
+ client.moves.stop();
293
+ expect(client.getState().G).toEqual({
294
+ onBegin: 1,
295
+ test: true,
296
+ });
297
+ });
298
+
299
+ test('turn/onEnd', () => {
300
+ const game: Game<any, PluginAPIs> = {
301
+ plugins,
302
+ moves: {
303
+ stop: ({ events }) => events.endTurn(),
304
+ },
305
+ turn: {
306
+ onEnd: ({ G, random, test }) => {
307
+ G.onEnd = random.Die(1);
308
+ G.test = test.get();
309
+ },
310
+ },
311
+ };
312
+
313
+ const client = Client({ game });
314
+ client.moves.stop();
315
+ expect(client.getState().G).toEqual({
316
+ onEnd: 1,
317
+ test: true,
318
+ });
319
+ });
320
+
321
+ test('phase/onBegin', () => {
322
+ const game: Game<any, PluginAPIs> = {
323
+ plugins,
324
+ moves: {
325
+ stop: ({ events }) => events.setPhase('second'),
326
+ },
327
+ phases: {
328
+ first: {
329
+ start: true,
330
+ },
331
+ second: {
332
+ onBegin: ({ G, random, test }) => {
333
+ G.onBegin = random.Die(1);
334
+ G.test = test.get();
335
+ },
336
+ },
337
+ },
338
+ };
339
+
340
+ const client = Client({ game });
341
+ client.moves.stop();
342
+ expect(client.getState().G).toEqual({
343
+ onBegin: 1,
344
+ test: true,
345
+ });
346
+ });
347
+
348
+ test('phase/onEnd', () => {
349
+ const game: Game<any, PluginAPIs> = {
350
+ plugins,
351
+ moves: {
352
+ stop: ({ events }) => events.endPhase(),
353
+ },
354
+ phases: {
355
+ first: {
356
+ start: true,
357
+ onEnd: ({ G, random, test }) => {
358
+ G.onEnd = random.Die(1);
359
+ G.test = test.get();
360
+ },
361
+ },
362
+ },
363
+ };
364
+
365
+ const client = Client({ game });
366
+ client.moves.stop();
367
+ expect(client.getState().G).toEqual({
368
+ onEnd: 1,
369
+ test: true,
370
+ });
371
+ });
372
+ });
373
+
374
+ describe('plugins can use events in fnWrap', () => {
375
+ const game: Game = {
376
+ plugins: [
377
+ {
378
+ name: 'test',
379
+ fnWrap:
380
+ (fn, type) =>
381
+ (context, ...args) => {
382
+ const G = fn(context, ...args);
383
+ if (G.endTurn && type === GameMethod.MOVE) {
384
+ context.events.endTurn();
385
+ }
386
+ if (G.endGame) {
387
+ context.events.endGame(G.endGame);
388
+ }
389
+ return G;
390
+ },
391
+ },
392
+ ],
393
+ moves: {
394
+ endGame: () => ({ endGame: true }),
395
+ endTurn: () => ({ endTurn: true }),
396
+ },
397
+ };
398
+
399
+ test('plugin can end turn', () => {
400
+ const client = Client({ game });
401
+ client.moves.endTurn();
402
+ expect(client.getState().ctx.turn).toBe(2);
403
+ expect(client.getState().ctx.currentPlayer).toBe('1');
404
+ });
405
+
406
+ test('plugin can end game', () => {
407
+ const client = Client({ game });
408
+ client.moves.endGame();
409
+ expect(client.getState().ctx.gameover).toBe(true);
410
+ });
411
+ });