@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,172 @@
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 PluginPlayer from './plugin-player';
10
+ import type { PlayerAPI } from './plugin-player';
11
+ import { Client } from '../client/client';
12
+ import type { Game } from '../types';
13
+
14
+ describe('default values', () => {
15
+ test('playerState is not passed', () => {
16
+ const plugin = PluginPlayer();
17
+ const game: Game<any, { player: PlayerAPI }> = {
18
+ plugins: [plugin],
19
+ };
20
+ const client = Client({ game });
21
+ expect(client.getState().plugins[plugin.name].data).toEqual({
22
+ players: { '0': {}, '1': {} },
23
+ });
24
+ });
25
+
26
+ test('playerState is passed', () => {
27
+ const plugin = PluginPlayer({ setup: () => ({ A: 1 }) });
28
+ const game: Game<any, { player: PlayerAPI }> = {
29
+ plugins: [plugin],
30
+ };
31
+ const client = Client({ game });
32
+ expect(client.getState().plugins[plugin.name].data).toEqual({
33
+ players: { '0': { A: 1 }, '1': { A: 1 } },
34
+ });
35
+ });
36
+ });
37
+
38
+ describe('2 player game', () => {
39
+ let client;
40
+
41
+ beforeAll(() => {
42
+ const game: Game<any, { player: PlayerAPI }> = {
43
+ moves: {
44
+ A: ({ player }) => {
45
+ player.set({ field: 'A1' });
46
+ player.opponent.set({ field: 'A2' });
47
+ },
48
+
49
+ B: ({ G, player }) => {
50
+ G.playerValue = player.get().field;
51
+ G.opponentValue = player.opponent.get().field;
52
+ },
53
+ },
54
+
55
+ plugins: [PluginPlayer()],
56
+ };
57
+
58
+ client = Client({ game });
59
+ });
60
+
61
+ test('player 0 turn', () => {
62
+ client.moves.A();
63
+ expect(client.getState().plugins[PluginPlayer().name].data).toEqual({
64
+ players: {
65
+ '0': { field: 'A1' },
66
+ '1': { field: 'A2' },
67
+ },
68
+ });
69
+ });
70
+
71
+ test('player 1 turn', () => {
72
+ client.events.endTurn();
73
+ client.moves.A();
74
+ expect(client.getState().plugins[PluginPlayer().name].data).toEqual({
75
+ players: {
76
+ '0': { field: 'A2' },
77
+ '1': { field: 'A1' },
78
+ },
79
+ });
80
+ });
81
+
82
+ test('player 1 makes move B', () => {
83
+ client.moves.B();
84
+ expect(client.getState().G).toEqual({
85
+ playerValue: 'A1',
86
+ opponentValue: 'A2',
87
+ });
88
+ });
89
+ });
90
+
91
+ describe('3 player game', () => {
92
+ let client;
93
+
94
+ beforeAll(() => {
95
+ const game: Game<any, { player: PlayerAPI }> = {
96
+ moves: {
97
+ A: ({ player }) => {
98
+ player.set({ field: 'A' });
99
+ },
100
+ },
101
+
102
+ plugins: [PluginPlayer()],
103
+ };
104
+
105
+ client = Client({ game, numPlayers: 3 });
106
+ });
107
+
108
+ test('G.opponent is not created', () => {
109
+ client.moves.A();
110
+ expect(client.getState().plugins[PluginPlayer().name].data).toEqual({
111
+ players: {
112
+ '0': { field: 'A' },
113
+ '1': {},
114
+ '2': {},
115
+ },
116
+ });
117
+ });
118
+ });
119
+
120
+ describe('game with phases', () => {
121
+ let client;
122
+
123
+ beforeAll(() => {
124
+ const game: Game<any, { player: PlayerAPI }> = {
125
+ plugins: [PluginPlayer({ setup: (id) => ({ id }) })],
126
+ phases: {
127
+ phase: {},
128
+ },
129
+ };
130
+
131
+ client = Client({ game });
132
+ });
133
+
134
+ test('includes playerSetup state', () => {
135
+ expect(client.getState().plugins[PluginPlayer().name].data).toEqual({
136
+ players: {
137
+ 0: {
138
+ id: '0',
139
+ },
140
+ 1: {
141
+ id: '1',
142
+ },
143
+ },
144
+ });
145
+ });
146
+ });
147
+
148
+ describe('with playerView', () => {
149
+ const plugin = PluginPlayer({
150
+ setup: (id) => ({ id }),
151
+ playerView: (players, playerID) => ({
152
+ [playerID]: players[playerID],
153
+ }),
154
+ });
155
+ const game = {
156
+ plugins: [plugin],
157
+ };
158
+
159
+ test('spectator doesn’t see player state', () => {
160
+ const spectator = Client({ game });
161
+ expect(spectator.getState().plugins[plugin.name].data).toEqual({
162
+ players: {},
163
+ });
164
+ });
165
+
166
+ test('player only sees own state', () => {
167
+ const client = Client({ game, playerID: '0' });
168
+ expect(client.getState().plugins[plugin.name].data).toEqual({
169
+ players: { '0': { id: '0' } },
170
+ });
171
+ });
172
+ });
@@ -0,0 +1,100 @@
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 { Plugin, PlayerID } from '../types';
10
+
11
+ interface PlayerData<PlayerState extends any = any> {
12
+ players: Record<PlayerID, PlayerState>;
13
+ }
14
+
15
+ export interface PlayerAPI<PlayerState extends any = any> {
16
+ state: Record<PlayerID, PlayerState>;
17
+ get(): PlayerState;
18
+ set(value: PlayerState): PlayerState;
19
+ opponent?: {
20
+ get(): PlayerState;
21
+ set(value: PlayerState): PlayerState;
22
+ };
23
+ }
24
+
25
+ interface PluginPlayerOpts<PlayerState extends any = any> {
26
+ setup?: (playerID: string) => PlayerState;
27
+ playerView?: (
28
+ players: Record<PlayerID, PlayerState>,
29
+ playerID?: string | null
30
+ ) => any;
31
+ }
32
+
33
+ export interface PlayerPlugin<PlayerState extends any = any> {
34
+ player: PlayerAPI<PlayerState>;
35
+ }
36
+
37
+ /**
38
+ * Plugin that maintains state for each player in G.players.
39
+ * During a turn, G.player will contain the object for the current player.
40
+ * In two player games, G.opponent will contain the object for the other player.
41
+ *
42
+ * @param {function} initPlayerState - Function of type (playerID) => playerState.
43
+ */
44
+ const PlayerPlugin = <PlayerState extends any = any>({
45
+ setup,
46
+ playerView,
47
+ }: PluginPlayerOpts<PlayerState> = {}): Plugin<
48
+ PlayerAPI<PlayerState>,
49
+ PlayerData<PlayerState>
50
+ > => ({
51
+ name: 'player',
52
+
53
+ flush: ({ api }) => {
54
+ return { players: api.state };
55
+ },
56
+
57
+ api: ({ ctx, data }): PlayerAPI => {
58
+ const state = data.players;
59
+
60
+ const get = () => {
61
+ return data.players[ctx.currentPlayer];
62
+ };
63
+
64
+ const set = (value) => {
65
+ return (state[ctx.currentPlayer] = value);
66
+ };
67
+
68
+ const result: PlayerAPI = { state, get, set };
69
+
70
+ if (ctx.numPlayers === 2) {
71
+ const other = ctx.currentPlayer === '0' ? '1' : '0';
72
+ const get = () => {
73
+ return data.players[other];
74
+ };
75
+ const set = (value) => {
76
+ return (state[other] = value);
77
+ };
78
+ result.opponent = { get, set };
79
+ }
80
+
81
+ return result;
82
+ },
83
+
84
+ setup: ({ ctx }) => {
85
+ const players: Record<PlayerID, any> = {};
86
+ for (let i = 0; i < ctx.numPlayers; i++) {
87
+ let playerState: any = {};
88
+ if (setup !== undefined) {
89
+ playerState = setup(i + '');
90
+ }
91
+ players[i + ''] = playerState;
92
+ }
93
+ return { players };
94
+ },
95
+
96
+ playerView: ({ data, playerID }) =>
97
+ playerView ? { players: playerView(data.players, playerID) } : data,
98
+ });
99
+
100
+ export default PlayerPlugin;
@@ -0,0 +1,40 @@
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 { Plugin } from '../types';
10
+ import type { RandomAPI, PrivateRandomAPI, RandomState } from './random/random';
11
+ import { Random } from './random/random';
12
+
13
+ const RandomPlugin: Plugin<RandomAPI & PrivateRandomAPI, RandomState> = {
14
+ name: 'random',
15
+
16
+ noClient: ({ api }) => {
17
+ return api._private.isUsed();
18
+ },
19
+
20
+ flush: ({ api }) => {
21
+ return api._private.getState();
22
+ },
23
+
24
+ api: ({ data }) => {
25
+ const random = new Random(data);
26
+ return random.api();
27
+ },
28
+
29
+ setup: ({ game }) => {
30
+ let { seed } = game;
31
+ if (seed === undefined) {
32
+ seed = Random.seed();
33
+ }
34
+ return { seed };
35
+ },
36
+
37
+ playerView: () => undefined,
38
+ };
39
+
40
+ export default RandomPlugin;
@@ -0,0 +1,40 @@
1
+ import { Client } from '../client/client';
2
+ import type { Game } from '../types';
3
+
4
+ describe('plugin-serializable', () => {
5
+ let client: ReturnType<typeof Client>;
6
+
7
+ beforeAll(() => {
8
+ const game: Game = {
9
+ moves: {
10
+ serializable: () => {
11
+ return { hello: 'world' };
12
+ },
13
+
14
+ nonSerializable: () => {
15
+ class Foo {
16
+ a: number;
17
+ constructor(a: number) {
18
+ this.a = a;
19
+ }
20
+ }
21
+ return { hello: new Foo(1) };
22
+ },
23
+ },
24
+ };
25
+
26
+ client = Client({ game });
27
+ });
28
+
29
+ test('does not throw for serializable move', () => {
30
+ expect(() => {
31
+ client.moves.serializable();
32
+ }).not.toThrow();
33
+ });
34
+
35
+ test('throws for non-serializable move', () => {
36
+ expect(() => {
37
+ client.moves.nonSerializable();
38
+ }).toThrow();
39
+ });
40
+ });
@@ -0,0 +1,55 @@
1
+ import type { Plugin } from '../types';
2
+ import isPlainObject from 'lodash.isplainobject';
3
+
4
+ /**
5
+ * Check if a value can be serialized (e.g. using `JSON.stringify`).
6
+ * Adapted from: https://stackoverflow.com/a/30712764/3829557
7
+ */
8
+ function isSerializable(value: any) {
9
+ // Primitives are OK.
10
+ if (
11
+ value === undefined ||
12
+ value === null ||
13
+ typeof value === 'boolean' ||
14
+ typeof value === 'number' ||
15
+ typeof value === 'string'
16
+ ) {
17
+ return true;
18
+ }
19
+
20
+ // A non-primitive value that is neither a POJO or an array cannot be serialized.
21
+ if (!isPlainObject(value) && !Array.isArray(value)) {
22
+ return false;
23
+ }
24
+
25
+ // Recurse entries if the value is an object or array.
26
+ for (const key in value) {
27
+ if (!isSerializable(value[key])) return false;
28
+ }
29
+
30
+ return true;
31
+ }
32
+
33
+ /**
34
+ * Plugin that checks whether state is serializable, in order to avoid
35
+ * network serialization bugs.
36
+ */
37
+ const SerializablePlugin: Plugin = {
38
+ name: 'plugin-serializable',
39
+
40
+ fnWrap:
41
+ (move) =>
42
+ (context, ...args) => {
43
+ const result = move(context, ...args);
44
+ // Check state in non-production environments.
45
+ if (process.env.NODE_ENV !== 'production' && !isSerializable(result)) {
46
+ throw new Error(
47
+ 'Move state is not JSON-serialiazable.\n' +
48
+ 'See https://boardgame.io/documentation/#/?id=state for more information.'
49
+ );
50
+ }
51
+ return result;
52
+ },
53
+ };
54
+
55
+ export default SerializablePlugin;
@@ -0,0 +1,109 @@
1
+ // Inlined version of Alea from https://github.com/davidbau/seedrandom.
2
+ // Converted to Typescript October 2020.
3
+
4
+ /*
5
+ * Copyright 2015 David Bau.
6
+ *
7
+ * Permission is hereby granted, free of charge,
8
+ * to any person obtaining a copy of this software
9
+ * and associated documentation files (the "Software"),
10
+ * to deal in the Software without restriction, including
11
+ * without limitation the rights to use, copy, modify, merge,
12
+ * publish, distribute, sublicense, and/or sell copies of the
13
+ * Software, and to permit persons to whom the Software is
14
+ * furnished to do so, subject to the following conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall
17
+ * be included in all copies or substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
+ * DEALINGS IN THE SOFTWARE.
27
+ */
28
+
29
+ export interface AleaState {
30
+ c: number;
31
+ s0: number;
32
+ s1: number;
33
+ s2: number;
34
+ }
35
+
36
+ class Alea {
37
+ c: number;
38
+ s0: number;
39
+ s1: number;
40
+ s2: number;
41
+
42
+ constructor(seed: string | number) {
43
+ const mash = Mash();
44
+
45
+ // Apply the seeding algorithm from Baagoe.
46
+ this.c = 1;
47
+ this.s0 = mash(' ');
48
+ this.s1 = mash(' ');
49
+ this.s2 = mash(' ');
50
+ this.s0 -= mash(seed);
51
+ if (this.s0 < 0) {
52
+ this.s0 += 1;
53
+ }
54
+ this.s1 -= mash(seed);
55
+ if (this.s1 < 0) {
56
+ this.s1 += 1;
57
+ }
58
+ this.s2 -= mash(seed);
59
+ if (this.s2 < 0) {
60
+ this.s2 += 1;
61
+ }
62
+ }
63
+
64
+ next() {
65
+ const t = 2091639 * this.s0 + this.c * 2.3283064365386963e-10; // 2^-32
66
+ this.s0 = this.s1;
67
+ this.s1 = this.s2;
68
+ return (this.s2 = t - (this.c = Math.trunc(t)));
69
+ }
70
+ }
71
+
72
+ function Mash() {
73
+ let n = 0xefc8249d;
74
+
75
+ const mash = function (data: string | number) {
76
+ const str = data.toString();
77
+ for (let i = 0; i < str.length; i++) {
78
+ n += str.charCodeAt(i);
79
+ let h = 0.02519603282416938 * n;
80
+ n = h >>> 0;
81
+ h -= n;
82
+ h *= n;
83
+ n = h >>> 0;
84
+ h -= n;
85
+ n += h * 0x100000000; // 2^32
86
+ }
87
+ return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
88
+ };
89
+
90
+ return mash;
91
+ }
92
+
93
+ function copy(f: AleaState, t: Partial<AleaState>) {
94
+ t.c = f.c;
95
+ t.s0 = f.s0;
96
+ t.s1 = f.s1;
97
+ t.s2 = f.s2;
98
+ return t as AleaState;
99
+ }
100
+
101
+ type PRNG = Alea['next'] & { state?: () => AleaState };
102
+
103
+ export function alea(seed: string | number, state?: AleaState): PRNG {
104
+ const xg = new Alea(seed);
105
+ const prng = xg.next.bind(xg) as PRNG;
106
+ if (state) copy(state, xg);
107
+ prng.state = () => copy(xg, {});
108
+ return prng;
109
+ }
@@ -0,0 +1,167 @@
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 { Random } from './random';
10
+ import { makeMove } from '../../core/action-creators';
11
+ import { CreateGameReducer } from '../../core/reducer';
12
+ import { InitializeGame } from '../../core/initialize';
13
+ import { Client } from '../../client/client';
14
+ import type { Game } from '../../types';
15
+ import { PlayerView } from '../main';
16
+
17
+ function Init(seed) {
18
+ return new Random({ seed });
19
+ }
20
+
21
+ test('random', () => {
22
+ const r = Init('hi there');
23
+ // make sure that subsequent calls are different.
24
+ expect(r._random()).toBe(0.573445922927931);
25
+ expect(r._random()).toBe(0.4695413049776107);
26
+ expect(r._random()).toBe(0.5943194630090147);
27
+ });
28
+
29
+ test('predefined dice values', () => {
30
+ const r = Init(0);
31
+
32
+ const rfns = [4, 6, 8, 10, 12, 20].map((v) => {
33
+ return { fn: r.api()[`D${v}`], highest: v };
34
+ });
35
+
36
+ rfns.forEach((pair) => {
37
+ const result = pair.fn();
38
+ expect(result).toBeDefined();
39
+ expect(result).toBeGreaterThanOrEqual(1);
40
+ expect(result).toBeLessThanOrEqual(pair.highest);
41
+ expect(r.state.prngstate).toBeDefined();
42
+
43
+ const multiple = pair.fn(5);
44
+ expect(multiple).toBeDefined();
45
+ expect(multiple).toHaveLength(5);
46
+ multiple.forEach((m) => {
47
+ expect(m).toBeGreaterThanOrEqual(1);
48
+ expect(m).toBeLessThanOrEqual(pair.highest);
49
+ });
50
+ });
51
+ });
52
+
53
+ test('Random.Die', () => {
54
+ const r = Init(0);
55
+ const api = r.api();
56
+
57
+ {
58
+ const result = api.Die(123);
59
+ expect(result).toBeDefined();
60
+ expect(result).toBe(74);
61
+ expect(r.state.prngstate).toBeDefined();
62
+ }
63
+
64
+ {
65
+ const result = api.Die();
66
+ expect(result).toBeDefined();
67
+ expect(result).toBeLessThanOrEqual(6);
68
+ expect(r.state.prngstate).toBeDefined();
69
+ }
70
+
71
+ {
72
+ const multiple = api.Die(6, 3);
73
+ expect(multiple).toBeDefined();
74
+ expect(multiple).toHaveLength(3);
75
+ multiple.forEach((m) => {
76
+ expect(m).toBeGreaterThanOrEqual(1);
77
+ expect(m).toBeLessThanOrEqual(6);
78
+ });
79
+ expect(r.state.prngstate).toBeDefined();
80
+ }
81
+ });
82
+
83
+ test('Random.Number', () => {
84
+ const r = Init(0);
85
+ const result = r.api().Number();
86
+ expect(result).toBeDefined();
87
+ expect(result).toBeGreaterThanOrEqual(0);
88
+ expect(result).toBeLessThanOrEqual(1);
89
+ expect(r.state.prngstate).toBeDefined();
90
+ });
91
+
92
+ test('Random.Shuffle', () => {
93
+ const r = Init(0);
94
+ const initialTiles = ['A', 'B', 'C', 'D', 'E'];
95
+ const tiles = [...initialTiles];
96
+ const result = r.api().Shuffle(tiles);
97
+ expect(result).toHaveLength(initialTiles.length);
98
+ expect(result).toEqual(expect.arrayContaining(initialTiles));
99
+ expect(result.sort()).toEqual(initialTiles);
100
+ expect(r.state.prngstate).toBeDefined();
101
+ });
102
+
103
+ test('Random API is not executed optimisitically', () => {
104
+ const game: Game = {
105
+ seed: 0,
106
+ moves: {
107
+ rollDie: ({ G, random }) => ({ ...G, die: random.D6() }),
108
+ },
109
+ };
110
+
111
+ {
112
+ const reducer = CreateGameReducer({ game });
113
+ let state = InitializeGame({ game });
114
+ expect(state.G.die).not.toBeDefined();
115
+ state = reducer(state, makeMove('rollDie'));
116
+ expect(state.G).toMatchObject({ die: 4 });
117
+ }
118
+
119
+ {
120
+ const reducer = CreateGameReducer({ game, isClient: true });
121
+ let state = InitializeGame({ game });
122
+ expect(state.G.die).not.toBeDefined();
123
+ state = reducer(state, makeMove('rollDie'));
124
+ expect(state.G.die).not.toBeDefined();
125
+ }
126
+ });
127
+
128
+ test('Random API works when its state is redacted by playerView', () => {
129
+ const game = {
130
+ seed: 0,
131
+ moves: {
132
+ rollDie: ({ G, random }) => ({ ...G, die: random.D6() }),
133
+ },
134
+ };
135
+
136
+ const opts = { game, isClient: true };
137
+ const reducer = CreateGameReducer(opts);
138
+ let state = InitializeGame({ game });
139
+ state.plugins = PlayerView(state, { ...opts, playerID: '0' });
140
+ expect(state.plugins.random.data).not.toBeDefined();
141
+ expect(state.G.die).not.toBeDefined();
142
+ state = reducer(state, makeMove('rollDie'));
143
+ expect(state.G.die).not.toBeDefined();
144
+ });
145
+
146
+ test('turn.onBegin has ctx APIs at the beginning of the game', () => {
147
+ let random = null;
148
+ let events = null;
149
+
150
+ const game: Game = {
151
+ turn: {
152
+ onBegin: (context) => {
153
+ random = context.random;
154
+ events = context.events;
155
+ },
156
+ },
157
+ };
158
+
159
+ InitializeGame({ game });
160
+ expect(random).not.toBe(null);
161
+ expect(events).not.toBe(null);
162
+ });
163
+
164
+ test('PRNG state is not sent to the client', () => {
165
+ const client = Client({ game: {} });
166
+ expect(client.getState().plugins.random.data).toBeUndefined();
167
+ });