@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,381 @@
1
+ import { getFilterPlayerView, redactLog } from './filter-player-view';
2
+ import * as ActionCreators from '../core/action-creators';
3
+ import { Master } from './master';
4
+ import { InMemory } from '../server/db/inmemory';
5
+ import { PlayerView } from '../core/player-view';
6
+ import { INVALID_MOVE } from '../core/constants';
7
+ import type { Game, SyncInfo } from '../types';
8
+
9
+ function TransportAPI(send = jest.fn(), sendAll = jest.fn()) {
10
+ return { send, sendAll };
11
+ }
12
+
13
+ function validateNotTransientState(state: any) {
14
+ expect(state).toEqual(
15
+ expect.not.objectContaining({ transients: expect.anything() })
16
+ );
17
+ }
18
+
19
+ describe('playerView - update', () => {
20
+ const send = jest.fn();
21
+ const sendAll = jest.fn();
22
+ const game: Game = {
23
+ playerView: ({ G, playerID }) => {
24
+ return { ...G, player: playerID };
25
+ },
26
+ };
27
+ const master = new Master(game, new InMemory(), TransportAPI(send, sendAll));
28
+
29
+ beforeAll(async () => {
30
+ await master.onSync('matchID', '0', undefined, 2);
31
+ });
32
+
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+ });
36
+
37
+ test('sync', async () => {
38
+ await master.onSync('matchID', '0', undefined, 2);
39
+ const payload = send.mock.calls[0][0];
40
+ const filterPlayerView = getFilterPlayerView(game);
41
+ expect(
42
+ (filterPlayerView('0', payload).args[1] as SyncInfo).state
43
+ ).toMatchObject({
44
+ G: { player: '0' },
45
+ });
46
+ });
47
+
48
+ test('update', async () => {
49
+ const action = ActionCreators.gameEvent('endTurn');
50
+ await master.onSync('matchID', '0', undefined, 2);
51
+ await master.onUpdate(action, 0, 'matchID', '0');
52
+ const payload = sendAll.mock.calls[sendAll.mock.calls.length - 1][0];
53
+ const filterPlayerView = getFilterPlayerView(game);
54
+
55
+ const transportData0 = filterPlayerView('0', payload);
56
+ const G_player0 = (transportData0.args[1] as any).G;
57
+ const transportData1 = filterPlayerView('1', payload);
58
+ const G_player1 = (transportData1.args[1] as any).G;
59
+
60
+ expect(G_player0.player).toBe('0');
61
+ expect(G_player1.player).toBe('1');
62
+ });
63
+ });
64
+
65
+ describe('playerView - patch', () => {
66
+ const send = jest.fn();
67
+ const sendAll = jest.fn();
68
+ const db = new InMemory();
69
+ const game: Game = {
70
+ seed: 0,
71
+ deltaState: true,
72
+ setup: () => {
73
+ return {
74
+ players: {
75
+ '0': {
76
+ cards: ['card3'],
77
+ },
78
+ '1': {
79
+ cards: [],
80
+ },
81
+ },
82
+ cards: ['card0', 'card1', 'card2'],
83
+ discardedCards: [],
84
+ };
85
+ },
86
+ playerView: PlayerView.STRIP_SECRETS,
87
+ turn: {
88
+ activePlayers: { currentPlayer: { stage: 'A' } },
89
+ stages: {
90
+ A: {
91
+ moves: {
92
+ Invalid: () => {
93
+ return INVALID_MOVE;
94
+ },
95
+ A: {
96
+ client: false,
97
+ move: ({ G, playerID }) => {
98
+ const card = G.players[playerID].cards.shift();
99
+ G.discardedCards.push(card);
100
+ },
101
+ },
102
+ B: {
103
+ client: false,
104
+ ignoreStaleStateID: true,
105
+ move: ({ G, playerID }) => {
106
+ const card = G.cards.pop();
107
+ G.players[playerID].cards.push(card);
108
+ },
109
+ },
110
+ },
111
+ },
112
+ },
113
+ },
114
+ };
115
+ const master = new Master(game, db, TransportAPI(send, sendAll));
116
+ const move = ActionCreators.makeMove('A', null, '0');
117
+
118
+ beforeAll(async () => {
119
+ master.subscribe(({ state }) => {
120
+ validateNotTransientState(state);
121
+ });
122
+ await master.onSync('matchID', '0', undefined, 2);
123
+ });
124
+
125
+ beforeEach(() => {
126
+ jest.clearAllMocks();
127
+ });
128
+
129
+ test('patch', async () => {
130
+ await master.onUpdate(move, 0, 'matchID', '0');
131
+ expect(sendAll).toBeCalled();
132
+
133
+ const payload = sendAll.mock.calls[sendAll.mock.calls.length - 1][0];
134
+ expect(payload.type).toBe('patch');
135
+
136
+ const filterPlayerView = getFilterPlayerView(game);
137
+ const value = filterPlayerView('0', payload);
138
+ expect(value.type).toBe('patch');
139
+ expect(value.args[0]).toBe('matchID');
140
+ expect(value.args[1]).toBe(0);
141
+ expect(value.args[2]).toBe(1);
142
+ expect(value.args[3]).toMatchObject([
143
+ { op: 'remove', path: '/G/players/0/cards/0' },
144
+ { op: 'add', path: '/G/discardedCards/-', value: 'card3' },
145
+ { op: 'replace', path: '/ctx/numMoves', value: 1 },
146
+ { op: 'replace', path: '/_stateID', value: 1 },
147
+ ]);
148
+ });
149
+ });
150
+
151
+ describe('redactLog', () => {
152
+ test('no-op with undefined log', () => {
153
+ const result = redactLog(undefined, '0');
154
+ expect(result).toBeUndefined();
155
+ });
156
+
157
+ test('no redactedMoves', () => {
158
+ const logEvents = [
159
+ {
160
+ _stateID: 0,
161
+ turn: 0,
162
+ phase: '',
163
+ action: ActionCreators.gameEvent('endTurn'),
164
+ },
165
+ ];
166
+ const result = redactLog(logEvents, '0');
167
+ expect(result).toMatchObject(logEvents);
168
+ });
169
+
170
+ test('redacted move is only shown with args to the player that made the move', () => {
171
+ const logEvents = [
172
+ {
173
+ _stateID: 0,
174
+ turn: 0,
175
+ phase: '',
176
+ action: ActionCreators.makeMove('clickCell', [1, 2, 3], '0'),
177
+ redact: true,
178
+ },
179
+ ];
180
+
181
+ // player that made the move
182
+ let result = redactLog(logEvents, '0');
183
+ expect(result).toMatchObject(logEvents);
184
+
185
+ // other player
186
+ result = redactLog(logEvents, '1');
187
+ expect(result).toMatchObject([
188
+ {
189
+ _stateID: 0,
190
+ turn: 0,
191
+ phase: '',
192
+ action: {
193
+ type: 'MAKE_MOVE',
194
+ payload: {
195
+ credentials: undefined,
196
+ playerID: '0',
197
+ type: 'clickCell',
198
+ },
199
+ },
200
+ },
201
+ ]);
202
+ });
203
+
204
+ test('not redacted move is shown to all', () => {
205
+ const logEvents = [
206
+ {
207
+ _stateID: 0,
208
+ turn: 0,
209
+ phase: '',
210
+ action: ActionCreators.makeMove('unclickCell', [1, 2, 3], '0'),
211
+ },
212
+ ];
213
+
214
+ // player that made the move
215
+ let result = redactLog(logEvents, '0');
216
+ expect(result).toMatchObject(logEvents);
217
+ // other player
218
+ result = redactLog(logEvents, '1');
219
+ expect(result).toMatchObject(logEvents);
220
+ });
221
+
222
+ test('can explicitly set showing args to true', () => {
223
+ const logEvents = [
224
+ {
225
+ _stateID: 0,
226
+ turn: 0,
227
+ phase: '',
228
+ action: ActionCreators.makeMove('unclickCell', [1, 2, 3], '0'),
229
+ },
230
+ ];
231
+
232
+ // player that made the move
233
+ let result = redactLog(logEvents, '0');
234
+ expect(result).toMatchObject(logEvents);
235
+ // other player
236
+ result = redactLog(logEvents, '1');
237
+ expect(result).toMatchObject(logEvents);
238
+ });
239
+
240
+ test('events are not redacted', () => {
241
+ const logEvents = [
242
+ {
243
+ _stateID: 0,
244
+ turn: 0,
245
+ phase: '',
246
+ action: ActionCreators.gameEvent('endTurn'),
247
+ },
248
+ ];
249
+
250
+ // player that made the move
251
+ let result = redactLog(logEvents, '0');
252
+ expect(result).toMatchObject(logEvents);
253
+ // other player
254
+ result = redactLog(logEvents, '1');
255
+ expect(result).toMatchObject(logEvents);
256
+ });
257
+
258
+ test('make sure filter player view redacts the log', async () => {
259
+ const game: Game = {
260
+ moves: {
261
+ A: ({ G }) => G,
262
+ B: {
263
+ move: ({ G }) => G,
264
+ redact: true,
265
+ },
266
+ },
267
+ };
268
+
269
+ const send = jest.fn();
270
+ const master = new Master(game, new InMemory(), TransportAPI(send));
271
+ const filterPlayerView = getFilterPlayerView(game);
272
+
273
+ const actionA = ActionCreators.makeMove('A', ['not redacted'], '0');
274
+ const actionB = ActionCreators.makeMove('B', ['redacted'], '0');
275
+
276
+ // test: ping-pong two moves, then sync and check the log
277
+ await master.onSync('matchID', '0', undefined, 2);
278
+ await master.onUpdate(actionA, 0, 'matchID', '0');
279
+ await master.onUpdate(actionB, 1, 'matchID', '0');
280
+ await master.onSync('matchID', '1', undefined, 2);
281
+
282
+ const payload = send.mock.calls[send.mock.calls.length - 1][0];
283
+ expect(
284
+ (filterPlayerView('1', payload).args[1] as SyncInfo).log
285
+ ).toMatchObject([
286
+ {
287
+ action: {
288
+ type: 'MAKE_MOVE',
289
+ payload: {
290
+ type: 'A',
291
+ args: ['not redacted'],
292
+ playerID: '0',
293
+ },
294
+ },
295
+ _stateID: 0,
296
+ },
297
+ {
298
+ action: {
299
+ type: 'MAKE_MOVE',
300
+ payload: {
301
+ type: 'B',
302
+ args: null,
303
+ playerID: '0',
304
+ },
305
+ },
306
+ _stateID: 1,
307
+ },
308
+ ]);
309
+ });
310
+ });
311
+
312
+ test('make move args to be secret depends on G via conditional redact', async () => {
313
+ const game: Game = {
314
+ setup: () => ({
315
+ isASecret: false,
316
+ }),
317
+ moves: {
318
+ A: {
319
+ move: ({ G }) => G,
320
+ redact: ({ G }) => G.isASecret,
321
+ },
322
+ B: ({ G }) => {
323
+ return { ...G, isASecret: true };
324
+ },
325
+ },
326
+ };
327
+
328
+ const send = jest.fn();
329
+ const master = new Master(game, new InMemory(), TransportAPI(send));
330
+ const filterPlayerView = getFilterPlayerView(game);
331
+
332
+ const actionA0 = ActionCreators.makeMove('A', ['not redacted'], '0');
333
+ const actionB = ActionCreators.makeMove('B', ['not redacted'], '0');
334
+ const actionA1 = ActionCreators.makeMove('A', ['redacted'], '0');
335
+
336
+ // test: ping-pong two moves, then sync and check the log
337
+ await master.onSync('matchID', '0', undefined, 2);
338
+ await master.onUpdate(actionA0, 0, 'matchID', '0');
339
+ await master.onUpdate(actionB, 1, 'matchID', '0');
340
+ await master.onUpdate(actionA1, 2, 'matchID', '0');
341
+ await master.onSync('matchID', '1', undefined, 2);
342
+
343
+ const payload = send.mock.calls[send.mock.calls.length - 1][0];
344
+ expect(
345
+ (filterPlayerView('1', payload).args[1] as SyncInfo).log
346
+ ).toMatchObject([
347
+ {
348
+ action: {
349
+ type: 'MAKE_MOVE',
350
+ payload: {
351
+ type: 'A',
352
+ args: ['not redacted'],
353
+ playerID: '0',
354
+ },
355
+ },
356
+ _stateID: 0,
357
+ },
358
+ {
359
+ action: {
360
+ type: 'MAKE_MOVE',
361
+ payload: {
362
+ type: 'B',
363
+ args: ['not redacted'],
364
+ playerID: '0',
365
+ },
366
+ },
367
+ _stateID: 1,
368
+ },
369
+ {
370
+ action: {
371
+ type: 'MAKE_MOVE',
372
+ payload: {
373
+ type: 'A',
374
+ args: null,
375
+ playerID: '0',
376
+ },
377
+ },
378
+ _stateID: 2,
379
+ },
380
+ ]);
381
+ });
@@ -0,0 +1,102 @@
1
+ import { PlayerView } from '../plugins/main';
2
+ import { createPatch } from 'rfc6902';
3
+ import type { Game, State, LogEntry, PlayerID } from '../types';
4
+ import type { TransportData, IntermediateTransportData } from './master';
5
+
6
+ const applyPlayerView = (
7
+ game: Game,
8
+ playerID: string | null,
9
+ state: State
10
+ ): State => ({
11
+ ...state,
12
+ G: game.playerView({ G: state.G, ctx: state.ctx, playerID }),
13
+ plugins: PlayerView(state, { playerID, game }),
14
+ deltalog: undefined,
15
+ _undo: [],
16
+ _redo: [],
17
+ });
18
+
19
+ /** Gets a function that filters the TransportData for a given player and game. */
20
+ export const getFilterPlayerView =
21
+ (game: Game) =>
22
+ (
23
+ playerID: string | null,
24
+ payload: IntermediateTransportData
25
+ ): TransportData => {
26
+ switch (payload.type) {
27
+ case 'patch': {
28
+ const [matchID, stateID, prevState, state] = payload.args;
29
+ const log = redactLog(state.deltalog, playerID);
30
+ const filteredState = applyPlayerView(game, playerID, state);
31
+ const newStateID = state._stateID;
32
+ const prevFilteredState = applyPlayerView(game, playerID, prevState);
33
+ const patch = createPatch(prevFilteredState, filteredState);
34
+ return {
35
+ type: 'patch',
36
+ args: [matchID, stateID, newStateID, patch, log],
37
+ };
38
+ }
39
+ case 'update': {
40
+ const [matchID, state] = payload.args;
41
+ const log = redactLog(state.deltalog, playerID);
42
+ const filteredState = applyPlayerView(game, playerID, state);
43
+ return {
44
+ type: 'update',
45
+ args: [matchID, filteredState, log],
46
+ };
47
+ }
48
+ case 'sync': {
49
+ const [matchID, syncInfo] = payload.args;
50
+ const filteredState = applyPlayerView(game, playerID, syncInfo.state);
51
+ const log = redactLog(syncInfo.log, playerID);
52
+ const newSyncInfo = {
53
+ ...syncInfo,
54
+ state: filteredState,
55
+ log,
56
+ };
57
+ return {
58
+ type: 'sync',
59
+ args: [matchID, newSyncInfo],
60
+ };
61
+ }
62
+ default: {
63
+ return payload;
64
+ }
65
+ }
66
+ };
67
+
68
+ /**
69
+ * Redact the log.
70
+ *
71
+ * @param {Array} log - The game log (or deltalog).
72
+ * @param {String} playerID - The playerID that this log is
73
+ * to be sent to.
74
+ */
75
+ export function redactLog(log: LogEntry[], playerID: PlayerID | null) {
76
+ if (log === undefined) {
77
+ return log;
78
+ }
79
+
80
+ return log.map((logEvent) => {
81
+ // filter for all other players and spectators.
82
+ if (playerID !== null && +playerID === +logEvent.action.payload.playerID) {
83
+ return logEvent;
84
+ }
85
+
86
+ if (logEvent.redact !== true) {
87
+ return logEvent;
88
+ }
89
+
90
+ const payload = {
91
+ ...logEvent.action.payload,
92
+ args: null,
93
+ };
94
+ const filteredEvent = {
95
+ ...logEvent,
96
+ action: { ...logEvent.action, payload },
97
+ };
98
+
99
+ const { redact, ...remaining } = filteredEvent;
100
+ return remaining;
101
+ });
102
+ }