board-game-engine 2.0.0 → 2.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 (251) hide show
  1. package/.github/workflows/ci.yml +33 -0
  2. package/dist/board-game-engine.cjs +463 -349
  3. package/dist/board-game-engine.js +463 -349
  4. package/dist/board-game-engine.min.js +22 -22
  5. package/dist/board-game-engine.mjs +461 -348
  6. package/dist/client/client.d.ts +76 -0
  7. package/dist/client/client.d.ts.map +1 -0
  8. package/dist/game-factory/bank/bank-slot.d.ts +30 -0
  9. package/dist/game-factory/bank/bank-slot.d.ts.map +1 -0
  10. package/dist/game-factory/bank/bank.d.ts +34 -0
  11. package/dist/game-factory/bank/bank.d.ts.map +1 -0
  12. package/dist/game-factory/board.d.ts +4 -0
  13. package/dist/game-factory/board.d.ts.map +1 -0
  14. package/dist/game-factory/condition/condition-factory.d.ts +4 -0
  15. package/dist/game-factory/condition/condition-factory.d.ts.map +1 -0
  16. package/dist/game-factory/condition/condition.d.ts +13 -0
  17. package/dist/game-factory/condition/condition.d.ts.map +1 -0
  18. package/dist/game-factory/condition/contains-condition.d.ts +8 -0
  19. package/dist/game-factory/condition/contains-condition.d.ts.map +1 -0
  20. package/dist/game-factory/condition/contains-same-condition.d.ts +7 -0
  21. package/dist/game-factory/condition/contains-same-condition.d.ts.map +1 -0
  22. package/dist/game-factory/condition/evaluate-condition.d.ts +8 -0
  23. package/dist/game-factory/condition/evaluate-condition.d.ts.map +1 -0
  24. package/dist/game-factory/condition/every-condition.d.ts +12 -0
  25. package/dist/game-factory/condition/every-condition.d.ts.map +1 -0
  26. package/dist/game-factory/condition/has-line-condition.d.ts +8 -0
  27. package/dist/game-factory/condition/has-line-condition.d.ts.map +1 -0
  28. package/dist/game-factory/condition/in-line-condition.d.ts +8 -0
  29. package/dist/game-factory/condition/in-line-condition.d.ts.map +1 -0
  30. package/dist/game-factory/condition/is-condition.d.ts +8 -0
  31. package/dist/game-factory/condition/is-condition.d.ts.map +1 -0
  32. package/dist/game-factory/condition/is-full-condition.d.ts +7 -0
  33. package/dist/game-factory/condition/is-full-condition.d.ts.map +1 -0
  34. package/dist/game-factory/condition/no-possible-moves-condition.d.ts +7 -0
  35. package/dist/game-factory/condition/no-possible-moves-condition.d.ts.map +1 -0
  36. package/dist/game-factory/condition/not-condition.d.ts +7 -0
  37. package/dist/game-factory/condition/not-condition.d.ts.map +1 -0
  38. package/dist/game-factory/condition/or-condition.d.ts +7 -0
  39. package/dist/game-factory/condition/or-condition.d.ts.map +1 -0
  40. package/dist/game-factory/condition/position-condition.d.ts +7 -0
  41. package/dist/game-factory/condition/position-condition.d.ts.map +1 -0
  42. package/dist/game-factory/condition/some-condition.d.ts +8 -0
  43. package/dist/game-factory/condition/some-condition.d.ts.map +1 -0
  44. package/dist/game-factory/condition/would-condition.d.ts +8 -0
  45. package/dist/game-factory/condition/would-condition.d.ts.map +1 -0
  46. package/dist/game-factory/entity.d.ts +13 -0
  47. package/dist/game-factory/entity.d.ts.map +1 -0
  48. package/dist/game-factory/expand-game-rules.d.ts +3 -0
  49. package/dist/game-factory/expand-game-rules.d.ts.map +1 -0
  50. package/dist/game-factory/game-factory.d.ts +10 -0
  51. package/dist/game-factory/game-factory.d.ts.map +1 -0
  52. package/{src/game-factory/move/end-turn.js → dist/game-factory/move/end-turn.d.ts} +2 -4
  53. package/dist/game-factory/move/end-turn.d.ts.map +1 -0
  54. package/dist/game-factory/move/for-each.d.ts +5 -0
  55. package/dist/game-factory/move/for-each.d.ts.map +1 -0
  56. package/dist/game-factory/move/index.d.ts +6 -0
  57. package/dist/game-factory/move/index.d.ts.map +1 -0
  58. package/dist/game-factory/move/move-entity.d.ts +7 -0
  59. package/dist/game-factory/move/move-entity.d.ts.map +1 -0
  60. package/dist/game-factory/move/move-factory.d.ts +18 -0
  61. package/dist/game-factory/move/move-factory.d.ts.map +1 -0
  62. package/dist/game-factory/move/move.d.ts +54 -0
  63. package/dist/game-factory/move/move.d.ts.map +1 -0
  64. package/dist/game-factory/move/pass-turn.d.ts +5 -0
  65. package/dist/game-factory/move/pass-turn.d.ts.map +1 -0
  66. package/{src/game-factory/move/pass.js → dist/game-factory/move/pass.d.ts} +2 -4
  67. package/dist/game-factory/move/pass.d.ts.map +1 -0
  68. package/dist/game-factory/move/place-new.d.ts +5 -0
  69. package/dist/game-factory/move/place-new.d.ts.map +1 -0
  70. package/dist/game-factory/move/remove-entity.d.ts +5 -0
  71. package/dist/game-factory/move/remove-entity.d.ts.map +1 -0
  72. package/dist/game-factory/move/set-active-players.d.ts +5 -0
  73. package/dist/game-factory/move/set-active-players.d.ts.map +1 -0
  74. package/dist/game-factory/move/set-state.d.ts +5 -0
  75. package/dist/game-factory/move/set-state.d.ts.map +1 -0
  76. package/dist/game-factory/move/shuffle.d.ts +5 -0
  77. package/dist/game-factory/move/shuffle.d.ts.map +1 -0
  78. package/dist/game-factory/move/take-from.d.ts +11 -0
  79. package/dist/game-factory/move/take-from.d.ts.map +1 -0
  80. package/dist/game-factory/space/space.d.ts +10 -0
  81. package/dist/game-factory/space/space.d.ts.map +1 -0
  82. package/dist/game-factory/space-group/grid.d.ts +15 -0
  83. package/dist/game-factory/space-group/grid.d.ts.map +1 -0
  84. package/dist/game-factory/space-group/space-group.d.ts +20 -0
  85. package/dist/game-factory/space-group/space-group.d.ts.map +1 -0
  86. package/dist/index.d.ts +6 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/registry.d.ts +17 -0
  89. package/dist/registry.d.ts.map +1 -0
  90. package/dist/types/bagel-types.d.ts +339 -0
  91. package/dist/types/bagel-types.d.ts.map +1 -0
  92. package/dist/types/index.d.ts +3 -0
  93. package/dist/types/index.d.ts.map +1 -0
  94. package/dist/types/rule-with-conditions.d.ts +9 -0
  95. package/dist/types/rule-with-conditions.d.ts.map +1 -0
  96. package/dist/utils/any-valid-moves.d.ts +2 -0
  97. package/dist/utils/any-valid-moves.d.ts.map +1 -0
  98. package/dist/utils/bgio-resolve-types.d.ts +25 -0
  99. package/dist/utils/bgio-resolve-types.d.ts.map +1 -0
  100. package/dist/utils/check-conditions.d.ts +7 -0
  101. package/dist/utils/check-conditions.d.ts.map +1 -0
  102. package/dist/utils/create-payload.d.ts +5 -0
  103. package/dist/utils/create-payload.d.ts.map +1 -0
  104. package/dist/utils/deserialize-bgio-arguments.d.ts +3 -0
  105. package/dist/utils/deserialize-bgio-arguments.d.ts.map +1 -0
  106. package/dist/utils/do-moves.d.ts +8 -0
  107. package/dist/utils/do-moves.d.ts.map +1 -0
  108. package/dist/utils/entity-matches.d.ts +6 -0
  109. package/dist/utils/entity-matches.d.ts.map +1 -0
  110. package/dist/utils/find-met-condition.d.ts +6 -0
  111. package/dist/utils/find-met-condition.d.ts.map +1 -0
  112. package/dist/utils/get-current-moves.d.ts +24 -0
  113. package/dist/utils/get-current-moves.d.ts.map +1 -0
  114. package/dist/utils/get-scenario-results.d.ts +3 -0
  115. package/dist/utils/get-scenario-results.d.ts.map +1 -0
  116. package/dist/utils/get-steps.d.ts +13 -0
  117. package/dist/utils/get-steps.d.ts.map +1 -0
  118. package/dist/utils/get.d.ts +7 -0
  119. package/dist/utils/get.d.ts.map +1 -0
  120. package/dist/utils/grid-contains-sequence.d.ts +27 -0
  121. package/dist/utils/grid-contains-sequence.d.ts.map +1 -0
  122. package/dist/utils/json-transformer.d.ts +8 -0
  123. package/dist/utils/json-transformer.d.ts.map +1 -0
  124. package/dist/utils/prepare-payload.d.ts +2 -0
  125. package/dist/utils/prepare-payload.d.ts.map +1 -0
  126. package/dist/utils/resolve-entity.d.ts +3 -0
  127. package/dist/utils/resolve-entity.d.ts.map +1 -0
  128. package/dist/utils/resolve-expression.d.ts +6 -0
  129. package/dist/utils/resolve-expression.d.ts.map +1 -0
  130. package/dist/utils/resolve-properties.d.ts +4 -0
  131. package/dist/utils/resolve-properties.d.ts.map +1 -0
  132. package/dist/utils/simulate-move.d.ts +16 -0
  133. package/dist/utils/simulate-move.d.ts.map +1 -0
  134. package/package.json +9 -3
  135. package/playwright-report/index.html +1 -1
  136. package/scripts/build.mjs +2 -2
  137. package/src/client/client.ts +306 -0
  138. package/src/game-factory/bank/bank-slot.ts +81 -0
  139. package/src/game-factory/bank/bank.ts +125 -0
  140. package/src/game-factory/{board.js → board.ts} +1 -1
  141. package/src/game-factory/condition/condition-factory.ts +59 -0
  142. package/src/game-factory/condition/condition.ts +50 -0
  143. package/src/game-factory/condition/{contains-condition.js → contains-condition.ts} +5 -4
  144. package/src/game-factory/condition/{contains-same-condition.js → contains-same-condition.ts} +8 -5
  145. package/src/game-factory/condition/{evaluate-condition.js → evaluate-condition.ts} +4 -3
  146. package/src/game-factory/condition/every-condition.ts +27 -0
  147. package/src/game-factory/condition/has-line-condition.ts +15 -0
  148. package/src/game-factory/condition/in-line-condition.ts +25 -0
  149. package/src/game-factory/condition/is-condition.ts +24 -0
  150. package/src/game-factory/condition/is-full-condition.ts +10 -0
  151. package/src/game-factory/condition/{no-possible-moves-condition.js → no-possible-moves-condition.ts} +3 -2
  152. package/src/game-factory/condition/{not-condition.js → not-condition.ts} +3 -2
  153. package/src/game-factory/condition/{or-condition.js → or-condition.ts} +3 -2
  154. package/src/game-factory/condition/position-condition.ts +13 -0
  155. package/src/game-factory/condition/{some-condition.js → some-condition.ts} +5 -3
  156. package/src/game-factory/condition/would-condition.ts +104 -0
  157. package/src/game-factory/entity.ts +37 -0
  158. package/src/game-factory/expand-game-rules.ts +263 -0
  159. package/src/game-factory/game-factory.ts +263 -0
  160. package/src/game-factory/move/end-turn.ts +7 -0
  161. package/src/game-factory/move/for-each.ts +20 -0
  162. package/src/game-factory/move/move-entity.ts +18 -0
  163. package/src/game-factory/move/move-factory.ts +107 -0
  164. package/src/game-factory/move/move.ts +147 -0
  165. package/src/game-factory/move/pass-turn.ts +15 -0
  166. package/src/game-factory/move/pass.ts +7 -0
  167. package/src/game-factory/move/place-new.ts +42 -0
  168. package/src/game-factory/move/remove-entity.ts +11 -0
  169. package/src/game-factory/move/set-active-players.ts +26 -0
  170. package/src/game-factory/move/set-state.ts +14 -0
  171. package/src/game-factory/move/shuffle.ts +9 -0
  172. package/src/game-factory/move/take-from.ts +12 -0
  173. package/src/game-factory/space/space.ts +36 -0
  174. package/src/game-factory/space-group/grid.ts +48 -0
  175. package/src/game-factory/space-group/space-group.ts +44 -0
  176. package/src/index.ts +5 -0
  177. package/src/types/bagel-types.ts +449 -0
  178. package/src/types/boardgame-io-core.d.ts +7 -0
  179. package/src/types/index.ts +70 -0
  180. package/src/types/rule-with-conditions.ts +9 -0
  181. package/src/utils/{any-valid-moves.js → any-valid-moves.ts} +54 -49
  182. package/src/utils/bgio-resolve-types.ts +27 -0
  183. package/src/utils/check-conditions.ts +28 -0
  184. package/src/utils/create-payload.ts +19 -0
  185. package/src/utils/deserialize-bgio-arguments.ts +10 -0
  186. package/src/utils/do-moves.ts +22 -0
  187. package/src/utils/entity-matches.ts +30 -0
  188. package/src/utils/expr-eval.d.ts +6 -0
  189. package/src/utils/find-met-condition.ts +23 -0
  190. package/src/utils/get-current-moves.ts +39 -0
  191. package/src/utils/get-scenario-results.ts +30 -0
  192. package/src/utils/get-steps.ts +38 -0
  193. package/src/utils/get.ts +28 -0
  194. package/src/utils/{grid-contains-sequence.js → grid-contains-sequence.ts} +71 -33
  195. package/src/utils/json-transformer.ts +17 -0
  196. package/src/utils/prepare-payload.ts +20 -0
  197. package/src/utils/resolve-entity.ts +15 -0
  198. package/src/utils/resolve-expression.ts +16 -0
  199. package/src/utils/resolve-properties.ts +172 -0
  200. package/src/utils/simulate-move.ts +32 -0
  201. package/src/wackson.d.ts +4 -0
  202. package/tsconfig.build.json +14 -0
  203. package/tsconfig.json +21 -0
  204. package/src/client/client.js +0 -224
  205. package/src/game-factory/bank/bank-slot.js +0 -69
  206. package/src/game-factory/bank/bank.js +0 -114
  207. package/src/game-factory/condition/condition-factory.js +0 -52
  208. package/src/game-factory/condition/condition.js +0 -39
  209. package/src/game-factory/condition/every-condition.js +0 -25
  210. package/src/game-factory/condition/has-line-condition.js +0 -14
  211. package/src/game-factory/condition/in-line-condition.js +0 -19
  212. package/src/game-factory/condition/is-condition.js +0 -23
  213. package/src/game-factory/condition/is-full-condition.js +0 -9
  214. package/src/game-factory/condition/position-condition.js +0 -12
  215. package/src/game-factory/condition/would-condition.js +0 -94
  216. package/src/game-factory/entity.js +0 -29
  217. package/src/game-factory/expand-game-rules.js +0 -271
  218. package/src/game-factory/game-factory.js +0 -239
  219. package/src/game-factory/move/for-each.js +0 -18
  220. package/src/game-factory/move/move-entity.js +0 -16
  221. package/src/game-factory/move/move-factory.js +0 -89
  222. package/src/game-factory/move/move.js +0 -131
  223. package/src/game-factory/move/pass-turn.js +0 -10
  224. package/src/game-factory/move/place-new.js +0 -33
  225. package/src/game-factory/move/remove-entity.js +0 -7
  226. package/src/game-factory/move/set-active-players.js +0 -23
  227. package/src/game-factory/move/set-state.js +0 -11
  228. package/src/game-factory/move/shuffle.js +0 -7
  229. package/src/game-factory/move/take-from.js +0 -7
  230. package/src/game-factory/space/space.js +0 -30
  231. package/src/game-factory/space-group/grid.js +0 -43
  232. package/src/game-factory/space-group/space-group.js +0 -29
  233. package/src/index.js +0 -2
  234. package/src/utils/check-conditions.js +0 -28
  235. package/src/utils/create-payload.js +0 -16
  236. package/src/utils/deserialize-bgio-arguments.js +0 -8
  237. package/src/utils/do-moves.js +0 -18
  238. package/src/utils/entity-matches.js +0 -20
  239. package/src/utils/find-met-condition.js +0 -22
  240. package/src/utils/get-current-moves.js +0 -12
  241. package/src/utils/get-scenario-results.js +0 -23
  242. package/src/utils/get-steps.js +0 -29
  243. package/src/utils/get.js +0 -25
  244. package/src/utils/json-transformer.js +0 -12
  245. package/src/utils/prepare-payload.js +0 -16
  246. package/src/utils/resolve-entity.js +0 -9
  247. package/src/utils/resolve-expression.js +0 -10
  248. package/src/utils/resolve-properties.js +0 -149
  249. package/src/utils/simulate-move.js +0 -25
  250. /package/src/game-factory/move/{index.js → index.ts} +0 -0
  251. /package/src/{registry.js → registry.ts} +0 -0
@@ -0,0 +1,104 @@
1
+ import type { Condition as ConditionRule } from "../../types/bagel-types.js";
2
+ import Condition from "./condition.js";
3
+ import checkConditions from "../../utils/check-conditions.js";
4
+ import simulateMove from "../../utils/simulate-move.js";
5
+ import type { BgioResolveState } from "../../utils/bgio-resolve-types.js";
6
+
7
+ const argNameMap: Record<string, string[]> = {
8
+ PlaceNew: ["destination"],
9
+ RemoveEntity: ["entity"],
10
+ MoveEntity: ["entity", "destination"],
11
+ TakeFrom: ["source", "destination"],
12
+ SetState: ["entity", "state"],
13
+ };
14
+
15
+ export default class WouldCondition extends Condition {
16
+ checkCondition (
17
+ bgioArguments: unknown,
18
+ rule: unknown,
19
+ conditionPayload: Record<string, unknown>,
20
+ context: Record<string, unknown>
21
+ ) {
22
+ const target = conditionPayload.target;
23
+ const targets = (conditionPayload.targets as unknown[] | undefined) ?? [target];
24
+
25
+ const moveType = (context.moveInstance as { rule: { moveType: string } } | undefined)?.rule?.moveType;
26
+ const argNames = moveType ? argNameMap[moveType] : undefined;
27
+ const payload = {
28
+ arguments: targets.reduce<Record<string, unknown>>((acc, t, i) => {
29
+ const key = argNames?.[i] ?? `arg${i}`;
30
+ return { ...acc, [key]: t };
31
+ }, {}),
32
+ };
33
+
34
+ const simulatedG = simulateMove(
35
+ bgioArguments as BgioResolveState,
36
+ payload as Parameters<typeof simulateMove>[1],
37
+ context as Parameters<typeof simulateMove>[2]
38
+ ) as { bank: { locate: (id: unknown) => unknown } };
39
+
40
+ let simulatedConditionsPayload: Record<string, unknown> = {};
41
+ if (target) {
42
+ simulatedConditionsPayload = {
43
+ target: simulatedG.bank.locate((target as { entityId: unknown }).entityId),
44
+ };
45
+ } else if (targets) {
46
+ simulatedConditionsPayload = {
47
+ targets: targets.map((t) => simulatedG.bank.locate((t as { entityId: unknown }).entityId)),
48
+ };
49
+ }
50
+
51
+ const conditionResults = checkConditions(
52
+ { ...(bgioArguments as BgioResolveState), G: simulatedG } as BgioResolveState,
53
+ (rule as { conditions?: ConditionRule[] }).conditions,
54
+ simulatedConditionsPayload,
55
+ context
56
+ );
57
+
58
+ const conditionIsMet = conditionResults.conditionsAreMet;
59
+
60
+ const results = conditionIsMet
61
+ ? restoreReferences(
62
+ conditionResults.results,
63
+ (entityId: unknown) =>
64
+ (bgioArguments as { G: { bank: { locate: (id: unknown) => unknown } } }).G.bank.locate(entityId)
65
+ )
66
+ : conditionResults.results;
67
+
68
+ return {
69
+ results,
70
+ conditionIsMet,
71
+ };
72
+ }
73
+ }
74
+
75
+ function restoreReferences (
76
+ obj: unknown,
77
+ getOriginalEntity: (id: unknown) => unknown,
78
+ seen = new WeakSet<object>()
79
+ ): unknown {
80
+ if (typeof obj !== "object" || obj === null) {
81
+ return obj;
82
+ }
83
+
84
+ if (seen.has(obj)) {
85
+ return obj;
86
+ }
87
+ seen.add(obj);
88
+
89
+ if ((obj as { entityId?: unknown }).entityId !== undefined) {
90
+ return getOriginalEntity((obj as { entityId: unknown }).entityId);
91
+ }
92
+
93
+ if (Array.isArray(obj)) {
94
+ return obj.map((item) => restoreReferences(item, getOriginalEntity, seen));
95
+ }
96
+
97
+ const restored: Record<string, unknown> = {};
98
+ for (const key in obj) {
99
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
100
+ restored[key] = restoreReferences((obj as Record<string, unknown>)[key], getOriginalEntity, seen);
101
+ }
102
+ }
103
+ return restored;
104
+ }
@@ -0,0 +1,37 @@
1
+ export default class Entity {
2
+ rule: Record<string, unknown>;
3
+ entityId: number;
4
+ state: Record<string, unknown>;
5
+
6
+ constructor (
7
+ options: { fromBank?: boolean; initialStateGroups?: Record<string, string> } | undefined,
8
+ rule: Record<string, unknown>,
9
+ id: number
10
+ ) {
11
+ if (!options?.fromBank) {
12
+ throw new Error(`Do not create entities directly. Go through the Bank. rule: ${JSON.stringify(rule)}`);
13
+ }
14
+ this.rule = rule;
15
+ this.entityId = id;
16
+ this.state = {};
17
+ if (this.rule.stateGroups) {
18
+ Object.entries(this.rule.stateGroups as Record<string, Record<string, Record<string, unknown>>>)
19
+ .forEach(([stateGroupName, stateGroupValues]) => {
20
+ const stateGroupValueName = options?.initialStateGroups?.[stateGroupName]
21
+ ?? Object.keys(stateGroupValues)[0];
22
+ Object.assign(this.state, stateGroupValues[stateGroupValueName]);
23
+ });
24
+ }
25
+ if (this.rule.state) {
26
+ Object.assign(this.state, this.rule.state as Record<string, unknown>);
27
+ }
28
+ }
29
+
30
+ get attributes () {
31
+ return {
32
+ ...this.rule,
33
+ ...this,
34
+ ...this.state,
35
+ };
36
+ }
37
+ }
@@ -0,0 +1,263 @@
1
+ import find from "lodash/find.js";
2
+ import type { Entity, EntityMatcher, EntityAttributes, GameFactoryInput, ExpandedGameRules, MoveDefinition } from "../types/bagel-types.js";
3
+ import transformJSON from "../utils/json-transformer.js";
4
+
5
+ type TransformRule = {
6
+ test: (val: unknown) => boolean;
7
+ replace: (val: unknown) => unknown;
8
+ };
9
+
10
+ const invariantEntities = [
11
+ {
12
+ entityType: "Space",
13
+ count: "Infinity",
14
+ },
15
+ {
16
+ entityType: "Board",
17
+ name: "sharedBoard",
18
+ },
19
+ {
20
+ name: "playerMarker",
21
+ perPlayer: true,
22
+ count: "Infinity",
23
+ },
24
+ ] as Entity[];
25
+
26
+ function expandEntities (rules: { entities: Entity[] }) {
27
+ rules.entities = [
28
+ ...invariantEntities,
29
+ ...(rules.entities || []),
30
+ ];
31
+ }
32
+
33
+ function expandInitialPlacements (rules: Record<string, unknown>, entities: Entity[]) {
34
+ if (rules.sharedBoard) {
35
+ const sharedBoard = rules.sharedBoard as EntityMatcher<EntityAttributes<Entity>>[];
36
+ const sharedBoardPlacements = sharedBoard.map((matcher) => ({ entity: matcher, destination: { name: "sharedBoard" } }));
37
+ if (!rules.initialPlacements) rules.initialPlacements = [];
38
+ (rules.initialPlacements as unknown[]).unshift(...sharedBoardPlacements);
39
+ }
40
+
41
+ if (rules.personalBoard) {
42
+ entities.push({
43
+ entityType: "Board",
44
+ name: "personalBoard",
45
+ perPlayer: true,
46
+ });
47
+ const personalBoard = rules.personalBoard as EntityMatcher<EntityAttributes<Entity>>[];
48
+ const personalBoardPlacements = personalBoard.map((matcher) => ({
49
+ entity: matcher,
50
+ destination: {
51
+ name: "personalBoard",
52
+ },
53
+ }));
54
+ if (!rules.initialPlacements) rules.initialPlacements = [];
55
+ (rules.initialPlacements as unknown[]).unshift(...personalBoardPlacements);
56
+ }
57
+
58
+ if (rules.initialPlacements) {
59
+ const initialPlacementMoves = (rules.initialPlacements as Array<{ entity: Record<string, unknown>; destination: { index?: number; name?: string } }>).map((placement) => {
60
+ const { state, ...matcher } = placement.entity;
61
+ const entityDefinition = find(entities, matcher) as Entity | undefined;
62
+
63
+ if (placement.destination.name === "personalBoard") {
64
+ return {
65
+ moveType: "ForEach",
66
+ arguments: {
67
+ targets: {
68
+ type: "ctxPath",
69
+ path: ["playOrder"],
70
+ },
71
+ },
72
+ move: {
73
+ moveType: "PlaceNew",
74
+ entity: {
75
+ state,
76
+ conditions: [{
77
+ conditionType: "Is",
78
+ matcher: {
79
+ ...matcher,
80
+ ...(entityDefinition?.perPlayer
81
+ ? {
82
+ player: {
83
+ type: "contextPath",
84
+ path: ["loopTarget"],
85
+ },
86
+ }
87
+ : {}
88
+ ),
89
+ },
90
+ }],
91
+ },
92
+ arguments: {
93
+ destination: {
94
+ conditions: [{
95
+ conditionType: "Is",
96
+ matcher: {
97
+ ...placement.destination,
98
+ player: {
99
+ type: "contextPath",
100
+ path: ["loopTarget"],
101
+ },
102
+ },
103
+ }],
104
+ },
105
+ },
106
+ },
107
+ } as MoveDefinition;
108
+ } else {
109
+ return {
110
+ moveType: "PlaceNew",
111
+ entity: {
112
+ state,
113
+ conditions: [{
114
+ conditionType: "Is",
115
+ matcher,
116
+ }],
117
+ },
118
+ arguments: {
119
+ destination: {
120
+ conditions: [{
121
+ conditionType: "Is",
122
+ matcher: placement.destination,
123
+ }],
124
+ },
125
+ },
126
+ } as MoveDefinition;
127
+ }
128
+ });
129
+ if (!rules.initialMoves) rules.initialMoves = [];
130
+ (rules.initialMoves as MoveDefinition[]).unshift(...initialPlacementMoves);
131
+ delete rules.initialPlacements;
132
+ }
133
+ }
134
+
135
+ const keyMappings: [string, string][] = [];
136
+
137
+ const simpleReplacements: [string, unknown][] = [
138
+ [
139
+ "isCurrentPlayer",
140
+ {
141
+ conditionType: "Is",
142
+ matcher: {
143
+ player: {
144
+ type: "ctxPath",
145
+ path: ["currentPlayer"],
146
+ },
147
+ },
148
+ },
149
+ ],
150
+ [
151
+ "isEmpty",
152
+ {
153
+ conditionType: "Not",
154
+ conditions: [{ conditionType: "Contains" }],
155
+ },
156
+ ],
157
+ [
158
+ "ownerOfFirstResultEntity",
159
+ {
160
+ type: "contextPath",
161
+ path: ["results", 0, "matches", 0, 0, "entities", 0, "attributes", "player"],
162
+ },
163
+ ],
164
+ ];
165
+
166
+ const transformationRules: TransformRule[] = [
167
+ {
168
+ test: (val) => Boolean(val && typeof val === "object"),
169
+ replace: (val) => {
170
+ const obj = val as Record<string, unknown>;
171
+ keyMappings.forEach(([oldKey, newKey]) => {
172
+ if (Object.prototype.hasOwnProperty.call(obj, oldKey)) {
173
+ obj[newKey] = obj[oldKey];
174
+ delete obj[oldKey];
175
+ }
176
+ });
177
+ return val;
178
+ },
179
+ },
180
+ {
181
+ test: (val) => typeof val === "string",
182
+ replace: (val) => {
183
+ for (let i = 0, len = simpleReplacements.length; i < len; i++) {
184
+ if (val === simpleReplacements[i][0]) {
185
+ return simpleReplacements[i][1];
186
+ }
187
+ }
188
+ return val;
189
+ },
190
+ },
191
+ {
192
+ test: (val) => Boolean(val && typeof val === "object" && (val as { conditions?: unknown }).conditions),
193
+ replace: (val) => {
194
+ const v = val as { conditions: unknown };
195
+ if (!Array.isArray(v.conditions)) {
196
+ v.conditions = [v.conditions];
197
+ }
198
+ return val;
199
+ },
200
+ },
201
+ {
202
+ test: (val) => Boolean(val && typeof val === "object" && (val as { conditions?: unknown }).conditions),
203
+ replace: (val) => {
204
+ const v = val as { conditions: Array<Record<string, unknown>> };
205
+ for (let i = 0, len = v.conditions.length; i < len; i++) {
206
+ if (!v.conditions[i].conditionType) {
207
+ v.conditions[i] = {
208
+ conditionType: "Is",
209
+ matcher: v.conditions[i],
210
+ };
211
+ }
212
+ }
213
+ return val;
214
+ },
215
+ },
216
+ {
217
+ test: (val) => Boolean(val && typeof val === "object" && typeof (val as { target?: unknown }).target === "string"),
218
+ replace: (val) => {
219
+ const v = val as { target: string };
220
+ return {
221
+ ...v,
222
+ target: {
223
+ conditions: [{
224
+ conditionType: "Is",
225
+ matcher: {
226
+ name: v.target,
227
+ },
228
+ }],
229
+ },
230
+ };
231
+ },
232
+ },
233
+ ];
234
+
235
+ export default function expandGameRules (gameRules: GameFactoryInput): ExpandedGameRules {
236
+ const rules = transformJSON(gameRules, transformationRules) as GameFactoryInput;
237
+
238
+ if (!rules.sharedBoard) {
239
+ rules.sharedBoard = rules.entities as unknown as GameFactoryInput["sharedBoard"];
240
+ }
241
+
242
+ if (!rules.turn) {
243
+ rules.turn = {
244
+ minMoves: 1,
245
+ maxMoves: 1,
246
+ };
247
+ }
248
+
249
+ expandEntities(rules);
250
+ expandInitialPlacements(rules as unknown as Record<string, unknown>, rules.entities);
251
+
252
+ if (rules.phases) {
253
+ Object.entries(rules.phases).forEach((phaseRule) => {
254
+ expandInitialPlacements(phaseRule as unknown as Record<string, unknown>, rules.entities);
255
+ });
256
+ }
257
+
258
+ if (gameRules.numPlayers) {
259
+ gameRules.minPlayers = gameRules.maxPlayers = gameRules.numPlayers;
260
+ }
261
+
262
+ return rules as ExpandedGameRules;
263
+ }
@@ -0,0 +1,263 @@
1
+ import { serialize } from "wackson";
2
+ import type { GameFactoryInput, MoveDefinition, TurnConfig } from "../types/bagel-types.js";
3
+ import moveFactory from "./move/move-factory.js";
4
+ import Bank from "./bank/bank.js";
5
+ import expandGameRules from "./expand-game-rules.js";
6
+ import getScenarioResults from "../utils/get-scenario-results.js";
7
+ import doMoves from "../utils/do-moves.js";
8
+ import deserializeBgioArguments from "../utils/deserialize-bgio-arguments.js";
9
+ import type { BgioResolveState } from "../utils/bgio-resolve-types.js";
10
+
11
+ /** boardgame.io-style arguments (minimal typing; engine passes full objects). */
12
+ export type BgioArguments = BgioResolveState;
13
+
14
+ /** Object returned from `gameFactory` (boardgame.io game definition). */
15
+ export type BoardGameEngineGame = Record<string, unknown> & { name: string };
16
+
17
+ export default function gameFactory (
18
+ gameRules: GameFactoryInput,
19
+ gameName: string
20
+ ): BoardGameEngineGame {
21
+ const game: BoardGameEngineGame = { name: gameName };
22
+ const rules = expandGameRules(gameRules);
23
+
24
+ game.setup = (bgioArguments: BgioArguments) => {
25
+ const { ctx } = bgioArguments;
26
+ const initialState: Record<string, unknown> = {
27
+ _meta: {
28
+ passedPlayers: [],
29
+ previousPayloads: {},
30
+ },
31
+ };
32
+
33
+ const entityDefinitions = expandEntityDefinitions(rules.entities, ctx as { numPlayers: number }) as Record<string, unknown>[];
34
+ const bank = new Bank(entityDefinitions);
35
+ initialState.bank = bank;
36
+ initialState.sharedBoard = bank.getOne(
37
+ bgioArguments,
38
+ {
39
+ conditions: [{
40
+ conditionType: "Is",
41
+ matcher: { name: "sharedBoard" },
42
+ }],
43
+ },
44
+ {}
45
+ );
46
+
47
+ if (rules.personalBoard) {
48
+ initialState.personalBoards = (bgioArguments.ctx as { playOrder: string[] }).playOrder.map((playerID: string) =>
49
+ bank.getOne(
50
+ bgioArguments,
51
+ {
52
+ conditions: [{
53
+ conditionType: "Is",
54
+ matcher: {
55
+ name: "personalBoard",
56
+ player: playerID,
57
+ },
58
+ }],
59
+ },
60
+ {}
61
+ )
62
+ );
63
+ }
64
+
65
+ rules.initialMoves?.forEach((moveRule) => {
66
+ moveFactory(moveRule, game).moveInstance!.doMove(
67
+ { ...bgioArguments, G: initialState },
68
+ undefined,
69
+ {}
70
+ );
71
+ });
72
+ return JSON.parse(serialize(initialState));
73
+ };
74
+
75
+ if (rules.moves) {
76
+ game.moves = createMoves(rules.moves, game);
77
+ }
78
+
79
+ if (rules.turn) {
80
+ game.turn = createTurn(rules.turn, game);
81
+ }
82
+
83
+ if (rules.phases) {
84
+ game.phases = Object.entries(rules.phases).reduce<Record<string, unknown>>((acc, [name, phaseRule]) => ({
85
+ ...acc,
86
+ [name]: createPhase(phaseRule as Record<string, unknown>, game),
87
+ }), {});
88
+ }
89
+
90
+ if (rules.endIf) {
91
+ const endIfRules = rules.endIf;
92
+ game.endIf = (bgioArguments: BgioArguments) => {
93
+ const newBgioArguments = deserializeBgioArguments(bgioArguments);
94
+ return getScenarioResults(newBgioArguments, endIfRules);
95
+ };
96
+ }
97
+
98
+ if (!gameRules.DEBUG_DISABLE_SECRET_STATE) {
99
+ game.playerView = (bgioArguments: BgioArguments) => {
100
+ const { G, playerID } = deserializeBgioArguments(bgioArguments);
101
+ const tracker = (G as { bank: { tracker: Record<string, {
102
+ rule: { contentsHiddenFrom?: string; player?: string; hideLength?: boolean };
103
+ spaces?: unknown[];
104
+ entities?: unknown[];
105
+ }> } }).bank.tracker;
106
+ Object.values(tracker).forEach((entity) => {
107
+ if (
108
+ entity.rule.contentsHiddenFrom === "All" ||
109
+ (
110
+ entity.rule.contentsHiddenFrom === "Others" &&
111
+ (
112
+ playerID !== entity.rule.player ||
113
+ playerID === undefined
114
+ )
115
+ )
116
+ ) {
117
+ if (entity.spaces) {
118
+ entity.spaces = entity.rule.hideLength
119
+ ? []
120
+ : entity.spaces.map(() => (G as { bank: { createEntity: () => unknown } }).bank.createEntity());
121
+ }
122
+ if (entity.entities) {
123
+ entity.entities = entity.rule.hideLength
124
+ ? []
125
+ : entity.entities.map(() => (G as { bank: { createEntity: () => unknown } }).bank.createEntity());
126
+ }
127
+ }
128
+ });
129
+ return JSON.parse(serialize(G));
130
+ };
131
+ }
132
+
133
+ return game;
134
+ }
135
+
136
+ function expandEntityDefinitions (entities: unknown[], ctx: { numPlayers: number }) {
137
+ return entities.reduce<unknown[]>((acc, entity) => {
138
+ const entityCopy = { ...(entity as Record<string, unknown>) };
139
+
140
+ if (entityCopy.perPlayer) {
141
+ delete entityCopy.perPlayer;
142
+ if (entityCopy.variants) {
143
+ entityCopy.variants =
144
+ (new Array(ctx.numPlayers)).fill(undefined).reduce<unknown[]>((accu, _, i) => [
145
+ ...accu,
146
+ ...(entityCopy.variants as unknown[]).map((variant: unknown) => ({ ...(variant as object), player: `${i}` })),
147
+ ], []);
148
+ } else {
149
+ entityCopy.variants =
150
+ (new Array(ctx.numPlayers)).fill(undefined).map((_, i) => ({ player: `${i}` }));
151
+ }
152
+ }
153
+
154
+ if (entityCopy.variants) {
155
+ const variants = entityCopy.variants as unknown[];
156
+ delete entityCopy.variants;
157
+
158
+ return [
159
+ ...acc,
160
+ ...variants.map((variant) => ({
161
+ ...entityCopy,
162
+ ...(variant as object),
163
+ })),
164
+ ];
165
+ } else {
166
+ return [
167
+ ...acc,
168
+ entityCopy,
169
+ ];
170
+ }
171
+ }, []);
172
+ }
173
+
174
+ function createTurn (turnRule: TurnConfig | Record<string, unknown>, game: BoardGameEngineGame) {
175
+ const turn = { ...turnRule } as Record<string, unknown>;
176
+
177
+ turn.onBegin = (bgioArguments: BgioArguments) => {
178
+ const newBgioArguments = deserializeBgioArguments(bgioArguments);
179
+ const stageRule = (turnRule.stages as Record<string, { initialMoves?: MoveDefinition[] }> | undefined)?.[
180
+ (newBgioArguments.ctx as { activePlayers?: Record<string, string>; currentPlayer: string }).activePlayers?.[
181
+ (newBgioArguments.ctx as { currentPlayer: string }).currentPlayer
182
+ ] as string
183
+ ];
184
+
185
+ (newBgioArguments.G as { _meta: { passedPlayers: string[] } })._meta.passedPlayers = (newBgioArguments.G as { _meta: { passedPlayers: string[] } })._meta.passedPlayers
186
+ .filter((p) => p !== (newBgioArguments.ctx as { currentPlayer: string }).currentPlayer);
187
+
188
+ doMoves(newBgioArguments, turnRule.initialMoves as MoveDefinition[] | undefined, { game });
189
+ doMoves(newBgioArguments, stageRule?.initialMoves, { game });
190
+
191
+ return JSON.parse(serialize(newBgioArguments.G));
192
+ };
193
+
194
+ if (turnRule.stages) {
195
+ Object.entries(turnRule.stages as Record<string, { moves?: Record<string, MoveDefinition> }>).forEach(([stageName, stageRule]) => {
196
+ if (stageRule.moves) {
197
+ ((turn.stages as Record<string, { moves?: Record<string, unknown> }>)[stageName]).moves = createMoves(stageRule.moves, game);
198
+ }
199
+ });
200
+ }
201
+
202
+ const order = turnRule.order as {
203
+ playOrder?: string | ((args: { ctx: { playOrder: string[] }; G: { _meta: { isAfterFirstPhase?: boolean } } }) => string[]);
204
+ first?: () => number;
205
+ next?: (args: { ctx: { playOrderPos: number; numPlayers: number } }) => number;
206
+ } | undefined;
207
+ if (order?.playOrder === "RotateFirst") {
208
+ order.first = () => 0;
209
+ order.next = ({ ctx }) => (ctx.playOrderPos + 1) % ctx.numPlayers;
210
+ (turn.order as typeof order).playOrder = ({ ctx, G }) => {
211
+ return G._meta.isAfterFirstPhase
212
+ ? [...ctx.playOrder.slice(1), ctx.playOrder[0]]
213
+ : ctx.playOrder;
214
+ };
215
+ }
216
+
217
+ return turn;
218
+ }
219
+
220
+ function createPhase (phaseRule: Record<string, unknown>, game: BoardGameEngineGame) {
221
+ const phase = { ...phaseRule };
222
+ if (phaseRule.turn) {
223
+ phase.turn = createTurn(phaseRule.turn as Record<string, unknown>, game);
224
+ }
225
+ if (phaseRule.moves) {
226
+ phase.moves = createMoves(phaseRule.moves as Record<string, MoveDefinition>, game);
227
+ }
228
+
229
+ phase.onBegin = (bgioArguments: BgioArguments) => {
230
+ const newBgioArguments = deserializeBgioArguments(bgioArguments);
231
+ doMoves(newBgioArguments, phaseRule.initialMoves as MoveDefinition[] | undefined, { game });
232
+ (newBgioArguments.G as { _meta: Record<string, unknown> })._meta.currentPhaseHasBeenSetUp = true;
233
+ (newBgioArguments.G as { _meta: Record<string, unknown> })._meta.nextPhase = phaseRule.next;
234
+ return JSON.parse(serialize(newBgioArguments.G));
235
+ };
236
+
237
+ if (phaseRule.endIf) {
238
+ const phaseEndIf = phaseRule.endIf as unknown[];
239
+ phase.endIf = (bgioArguments: BgioArguments) => {
240
+ const newBgioArguments = deserializeBgioArguments(bgioArguments);
241
+ if ((newBgioArguments.G as { _meta: { currentPhaseHasBeenSetUp?: boolean } })._meta.currentPhaseHasBeenSetUp) {
242
+ const result = getScenarioResults(newBgioArguments, phaseEndIf);
243
+ if (result) {
244
+ return result;
245
+ }
246
+ }
247
+ };
248
+ }
249
+
250
+ phase.onEnd = ({ G }: { G: { _meta: Record<string, unknown> } }) => {
251
+ G._meta.currentPhaseHasBeenSetUp = false;
252
+ G._meta.isAfterFirstPhase = true;
253
+ };
254
+
255
+ return phase;
256
+ }
257
+
258
+ function createMoves (moves: Record<string, MoveDefinition>, game: BoardGameEngineGame) {
259
+ return Object.entries(moves).reduce<Record<string, unknown>>((acc, [name, moveDefinition]) => ({
260
+ ...acc,
261
+ [name]: moveFactory({ ...moveDefinition, name }, game),
262
+ }), {});
263
+ }
@@ -0,0 +1,7 @@
1
+ import Move from "./move.js";
2
+
3
+ export default class EndTurn extends Move {
4
+ do (bgioArguments: unknown) {
5
+ (bgioArguments as { events: { endTurn: () => void } }).events.endTurn();
6
+ }
7
+ }
@@ -0,0 +1,20 @@
1
+ import type { MoveDefinition } from "../../types/bagel-types.js";
2
+ import Move from "./move.js";
3
+ import { getMoveInstance } from "./move-factory.js";
4
+
5
+ export default class ForEach extends Move {
6
+ do (bgioArguments: unknown, rule: unknown, resolvedPayload: unknown, context: Record<string, unknown>) {
7
+ const { targets } = (resolvedPayload as { arguments: { targets: unknown[] } }).arguments;
8
+ targets.forEach((target: unknown) => {
9
+ const loopContext = {
10
+ ...context,
11
+ loopTarget: target
12
+ }
13
+ getMoveInstance((rule as { move: MoveDefinition }).move)!.doMove(
14
+ bgioArguments,
15
+ undefined,
16
+ loopContext
17
+ )
18
+ })
19
+ }
20
+ }