board-game-engine 1.0.5 → 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 (254) 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/examples/checkers.json +2 -2
  135. package/examples/connect-four.json +1 -1
  136. package/examples/eights.json +15 -15
  137. package/package.json +9 -3
  138. package/playwright-report/index.html +1 -1
  139. package/scripts/build.mjs +2 -2
  140. package/src/client/client.ts +306 -0
  141. package/src/game-factory/bank/bank-slot.ts +81 -0
  142. package/src/game-factory/bank/bank.ts +125 -0
  143. package/src/game-factory/{board.js → board.ts} +1 -1
  144. package/src/game-factory/condition/condition-factory.ts +59 -0
  145. package/src/game-factory/condition/condition.ts +50 -0
  146. package/src/game-factory/condition/{contains-condition.js → contains-condition.ts} +5 -4
  147. package/src/game-factory/condition/{contains-same-condition.js → contains-same-condition.ts} +8 -5
  148. package/src/game-factory/condition/{evaluate-condition.js → evaluate-condition.ts} +4 -3
  149. package/src/game-factory/condition/every-condition.ts +27 -0
  150. package/src/game-factory/condition/has-line-condition.ts +15 -0
  151. package/src/game-factory/condition/in-line-condition.ts +25 -0
  152. package/src/game-factory/condition/is-condition.ts +24 -0
  153. package/src/game-factory/condition/is-full-condition.ts +10 -0
  154. package/src/game-factory/condition/{no-possible-moves-condition.js → no-possible-moves-condition.ts} +3 -2
  155. package/src/game-factory/condition/{not-condition.js → not-condition.ts} +3 -2
  156. package/src/game-factory/condition/{or-condition.js → or-condition.ts} +3 -2
  157. package/src/game-factory/condition/position-condition.ts +13 -0
  158. package/src/game-factory/condition/{some-condition.js → some-condition.ts} +5 -3
  159. package/src/game-factory/condition/would-condition.ts +104 -0
  160. package/src/game-factory/entity.ts +37 -0
  161. package/src/game-factory/expand-game-rules.ts +263 -0
  162. package/src/game-factory/game-factory.ts +263 -0
  163. package/src/game-factory/move/end-turn.ts +7 -0
  164. package/src/game-factory/move/for-each.ts +20 -0
  165. package/src/game-factory/move/move-entity.ts +18 -0
  166. package/src/game-factory/move/move-factory.ts +107 -0
  167. package/src/game-factory/move/move.ts +147 -0
  168. package/src/game-factory/move/pass-turn.ts +15 -0
  169. package/src/game-factory/move/pass.ts +7 -0
  170. package/src/game-factory/move/place-new.ts +42 -0
  171. package/src/game-factory/move/remove-entity.ts +11 -0
  172. package/src/game-factory/move/set-active-players.ts +26 -0
  173. package/src/game-factory/move/set-state.ts +14 -0
  174. package/src/game-factory/move/shuffle.ts +9 -0
  175. package/src/game-factory/move/take-from.ts +12 -0
  176. package/src/game-factory/space/space.ts +36 -0
  177. package/src/game-factory/space-group/grid.ts +48 -0
  178. package/src/game-factory/space-group/space-group.ts +44 -0
  179. package/src/index.ts +5 -0
  180. package/src/types/bagel-types.ts +449 -0
  181. package/src/types/boardgame-io-core.d.ts +7 -0
  182. package/src/types/index.ts +70 -0
  183. package/src/types/rule-with-conditions.ts +9 -0
  184. package/src/utils/{any-valid-moves.js → any-valid-moves.ts} +54 -49
  185. package/src/utils/bgio-resolve-types.ts +27 -0
  186. package/src/utils/check-conditions.ts +28 -0
  187. package/src/utils/create-payload.ts +19 -0
  188. package/src/utils/deserialize-bgio-arguments.ts +10 -0
  189. package/src/utils/do-moves.ts +22 -0
  190. package/src/utils/entity-matches.ts +30 -0
  191. package/src/utils/expr-eval.d.ts +6 -0
  192. package/src/utils/find-met-condition.ts +23 -0
  193. package/src/utils/get-current-moves.ts +39 -0
  194. package/src/utils/get-scenario-results.ts +30 -0
  195. package/src/utils/get-steps.ts +38 -0
  196. package/src/utils/get.ts +28 -0
  197. package/src/utils/{grid-contains-sequence.js → grid-contains-sequence.ts} +71 -33
  198. package/src/utils/json-transformer.ts +17 -0
  199. package/src/utils/prepare-payload.ts +20 -0
  200. package/src/utils/resolve-entity.ts +15 -0
  201. package/src/utils/resolve-expression.ts +16 -0
  202. package/src/utils/resolve-properties.ts +172 -0
  203. package/src/utils/simulate-move.ts +32 -0
  204. package/src/wackson.d.ts +4 -0
  205. package/tsconfig.build.json +14 -0
  206. package/tsconfig.json +21 -0
  207. package/src/client/client.js +0 -224
  208. package/src/game-factory/bank/bank-slot.js +0 -69
  209. package/src/game-factory/bank/bank.js +0 -114
  210. package/src/game-factory/condition/condition-factory.js +0 -52
  211. package/src/game-factory/condition/condition.js +0 -39
  212. package/src/game-factory/condition/every-condition.js +0 -25
  213. package/src/game-factory/condition/has-line-condition.js +0 -14
  214. package/src/game-factory/condition/in-line-condition.js +0 -19
  215. package/src/game-factory/condition/is-condition.js +0 -23
  216. package/src/game-factory/condition/is-full-condition.js +0 -9
  217. package/src/game-factory/condition/position-condition.js +0 -12
  218. package/src/game-factory/condition/would-condition.js +0 -94
  219. package/src/game-factory/entity.js +0 -29
  220. package/src/game-factory/expand-game-rules.js +0 -271
  221. package/src/game-factory/game-factory.js +0 -239
  222. package/src/game-factory/move/for-each.js +0 -18
  223. package/src/game-factory/move/move-entity.js +0 -16
  224. package/src/game-factory/move/move-factory.js +0 -89
  225. package/src/game-factory/move/move.js +0 -131
  226. package/src/game-factory/move/pass-turn.js +0 -10
  227. package/src/game-factory/move/place-new.js +0 -33
  228. package/src/game-factory/move/remove-entity.js +0 -7
  229. package/src/game-factory/move/set-active-players.js +0 -23
  230. package/src/game-factory/move/set-state.js +0 -11
  231. package/src/game-factory/move/shuffle.js +0 -7
  232. package/src/game-factory/move/take-from.js +0 -7
  233. package/src/game-factory/space/space.js +0 -30
  234. package/src/game-factory/space-group/grid.js +0 -43
  235. package/src/game-factory/space-group/space-group.js +0 -29
  236. package/src/index.js +0 -2
  237. package/src/utils/check-conditions.js +0 -28
  238. package/src/utils/create-payload.js +0 -16
  239. package/src/utils/deserialize-bgio-arguments.js +0 -8
  240. package/src/utils/do-moves.js +0 -18
  241. package/src/utils/entity-matches.js +0 -20
  242. package/src/utils/find-met-condition.js +0 -22
  243. package/src/utils/get-current-moves.js +0 -12
  244. package/src/utils/get-scenario-results.js +0 -23
  245. package/src/utils/get-steps.js +0 -29
  246. package/src/utils/get.js +0 -25
  247. package/src/utils/json-transformer.js +0 -12
  248. package/src/utils/prepare-payload.js +0 -16
  249. package/src/utils/resolve-entity.js +0 -9
  250. package/src/utils/resolve-expression.js +0 -10
  251. package/src/utils/resolve-properties.js +0 -149
  252. package/src/utils/simulate-move.js +0 -25
  253. /package/src/game-factory/move/{index.js → index.ts} +0 -0
  254. /package/src/{registry.js → registry.ts} +0 -0
@@ -0,0 +1,306 @@
1
+ import { Client as BoardgameIOClient } from "@mnbroatch/boardgame.io/client";
2
+ import { Debug } from "@mnbroatch/boardgame.io/debug";
3
+ import { SocketIO } from "@mnbroatch/boardgame.io/multiplayer";
4
+ import { serialize, deserialize } from "wackson";
5
+ import gameFactory from "../game-factory/game-factory.js";
6
+ import type { BoardGameEngineGame, BgioArguments } from "../game-factory/game-factory.js";
7
+ import type { Condition, GameFactoryInput } from "../types/bagel-types.js";
8
+ import { registry } from "../registry.js";
9
+ import simulateMove from "../utils/simulate-move.js";
10
+ import getCurrentMoves from "../utils/get-current-moves.js";
11
+ import type { GetCurrentMovesClient, GetCurrentMovesState } from "../utils/get-current-moves.js";
12
+ import resolveProperties from "../utils/resolve-properties.js";
13
+ import type { BgioResolveState } from "../utils/bgio-resolve-types.js";
14
+ import checkConditions from "../utils/check-conditions.js";
15
+ import preparePayload from "../utils/prepare-payload.js";
16
+ import getSteps from "../utils/get-steps.js";
17
+ import createPayload from "../utils/create-payload.js";
18
+
19
+ export interface ClientOptions {
20
+ boardgameIOGame?: BoardGameEngineGame;
21
+ /** JSON string of a {@link GameFactoryInput} rule object */
22
+ gameRules?: string;
23
+ gameName?: string;
24
+ server?: string;
25
+ numPlayers?: number;
26
+ debug?: {
27
+ collapseOnLoad?: boolean;
28
+ impl?: typeof Debug;
29
+ };
30
+ matchID?: string;
31
+ playerID?: string | null;
32
+ credentials?: string;
33
+ multiplayer?: ReturnType<typeof SocketIO>;
34
+ onClientUpdate?: () => void;
35
+ }
36
+
37
+ interface MoveBuilder {
38
+ targets: unknown[];
39
+ stepIndex: number;
40
+ eliminatedMoves: string[];
41
+ }
42
+
43
+ export class Client {
44
+ options: ClientOptions;
45
+ game: BoardGameEngineGame;
46
+ client?: ReturnType<typeof BoardgameIOClient>;
47
+ moveBuilder?: MoveBuilder;
48
+ optimisticWinner?: unknown | null;
49
+
50
+ constructor (options: ClientOptions) {
51
+ this.options = options;
52
+ this.game = options.boardgameIOGame
53
+ || gameFactory(JSON.parse(options.gameRules as string) as GameFactoryInput, options.gameName ?? "");
54
+
55
+ if (!options.boardgameIOGame) {
56
+ this.moveBuilder = { targets: [], stepIndex: 0, eliminatedMoves: [] };
57
+ this.optimisticWinner = null;
58
+ }
59
+ }
60
+
61
+ connect () {
62
+ const {
63
+ server,
64
+ numPlayers,
65
+ debug = {
66
+ collapseOnLoad: true,
67
+ impl: Debug,
68
+ },
69
+ matchID,
70
+ playerID,
71
+ credentials,
72
+ multiplayer = SocketIO({ server, socketOpts: { transports: ["websocket", "polling"] } }),
73
+ } = this.options;
74
+
75
+ try {
76
+ const clientOptions = !credentials
77
+ ? { game: this.game, numPlayers, debug }
78
+ : {
79
+ game: this.game,
80
+ multiplayer,
81
+ matchID,
82
+ playerID: playerID ?? undefined,
83
+ credentials,
84
+ numPlayers,
85
+ debug,
86
+ };
87
+
88
+ this.client = BoardgameIOClient(clientOptions as Parameters<typeof BoardgameIOClient>[0]);
89
+ this.client.subscribe(() => this.update());
90
+ this.client.start();
91
+ return this;
92
+ } catch (error: unknown) {
93
+ const err = error as { message?: string; stack?: string };
94
+ console.error("Failed to join game:", err?.message ?? error);
95
+ if (err?.stack) console.error(err.stack);
96
+ }
97
+ }
98
+
99
+ update () {
100
+ this.options.onClientUpdate?.();
101
+ }
102
+
103
+ getState () {
104
+ const bgioState = this.client?.getState();
105
+ if (!bgioState) return {};
106
+
107
+ const state = this.options.boardgameIOGame
108
+ ? bgioState
109
+ : {
110
+ ...bgioState,
111
+ G: deserialize(JSON.stringify(bgioState.G), registry),
112
+ };
113
+
114
+ const gameover = this.optimisticWinner ?? state?.ctx?.gameover;
115
+
116
+ const currentMoves = gameover
117
+ ? []
118
+ : getCurrentMoves(state as GetCurrentMovesState, this.client as GetCurrentMovesClient);
119
+
120
+ if (this.options.boardgameIOGame) {
121
+ return {
122
+ state,
123
+ gameover,
124
+ moves: this.client!.moves,
125
+ currentMoves,
126
+ };
127
+ }
128
+
129
+ const _wrappedMoves = Object.entries(currentMoves)
130
+ .reduce<Record<string, unknown>>((acc, [moveName, rawMove]) => {
131
+ const move = (payload: unknown) => {
132
+ this.client!.moves[moveName](preparePayload(payload));
133
+ };
134
+ (move as { moveInstance?: unknown }).moveInstance = (rawMove as { moveInstance: unknown }).moveInstance;
135
+ return { ...acc, [moveName]: move };
136
+ }, {});
137
+
138
+ const { allClickable, _possibleMoveMeta } = getPossibleMoves(state, _wrappedMoves, this.moveBuilder!);
139
+
140
+ return { state, gameover, allClickable, _wrappedMoves, _possibleMoveMeta };
141
+ }
142
+
143
+ doStep (_target: unknown) {
144
+ if (this.options.boardgameIOGame) return;
145
+
146
+ const { state, _wrappedMoves, _possibleMoveMeta } = this.getState() as {
147
+ state: { G: { bank: { locate: (id: unknown) => unknown } } };
148
+ _wrappedMoves: Record<string, { (p: unknown): void; moveInstance: unknown }>;
149
+ _possibleMoveMeta: Record<string, { clickableForMove: Set<unknown> }>;
150
+ };
151
+
152
+ const target = (_target as { abstract?: boolean; entityId?: unknown; value?: unknown }).abstract
153
+ ? _target
154
+ : state.G.bank.locate((_target as { entityId: unknown }).entityId);
155
+
156
+ const newEliminated = Object.entries(_possibleMoveMeta)
157
+ .filter(([_, meta]) => !hasTarget(meta.clickableForMove, target))
158
+ .map(([name]) => name)
159
+ .concat(this.moveBuilder!.eliminatedMoves);
160
+
161
+ if (newEliminated.length === Object.keys(_wrappedMoves).length) {
162
+ console.error("invalid move with target:", (target as { rule?: unknown })?.rule);
163
+ return;
164
+ }
165
+
166
+ const remainingMoveEntries = Object.entries(_possibleMoveMeta)
167
+ .filter(([name]) => !newEliminated.includes(name));
168
+
169
+ if (isMoveCompleted(
170
+ state,
171
+ _wrappedMoves as Record<string, { moveInstance: { rule: unknown } }>,
172
+ remainingMoveEntries,
173
+ this.moveBuilder!.stepIndex
174
+ )) {
175
+ const [moveName] = remainingMoveEntries[0];
176
+ const move = _wrappedMoves[moveName];
177
+ const payload = createPayload(
178
+ state as unknown as BgioResolveState,
179
+ (move as { moveInstance: { rule: Parameters<typeof getSteps>[1] } }).moveInstance.rule,
180
+ [...this.moveBuilder!.targets, target],
181
+ { moveInstance: (move as { moveInstance: unknown }).moveInstance }
182
+ );
183
+
184
+ this.optimisticWinner = getWinnerAfterMove(state, this.game, (move as { moveInstance: unknown }).moveInstance, payload);
185
+ move(payload);
186
+ this.moveBuilder = { targets: [], stepIndex: 0, eliminatedMoves: [] };
187
+ } else {
188
+ this.moveBuilder = {
189
+ eliminatedMoves: newEliminated,
190
+ stepIndex: this.moveBuilder!.stepIndex + 1,
191
+ targets: [...this.moveBuilder!.targets, target],
192
+ };
193
+ }
194
+
195
+ this.update();
196
+ }
197
+
198
+ reset () {
199
+ if (this.options.boardgameIOGame) return;
200
+ this.moveBuilder = { targets: [], stepIndex: 0, eliminatedMoves: [] };
201
+ this.optimisticWinner = null;
202
+ this.update();
203
+ }
204
+
205
+ undoStep () {
206
+ if (this.options.boardgameIOGame) return;
207
+ if (this.moveBuilder!.targets.length) {
208
+ this.moveBuilder = {
209
+ targets: this.moveBuilder!.targets.slice(0, -1),
210
+ stepIndex: Math.max(0, this.moveBuilder!.stepIndex - 1),
211
+ eliminatedMoves: [],
212
+ };
213
+ }
214
+ this.update();
215
+ }
216
+ }
217
+
218
+ function hasTarget (clickableSet: Set<unknown>, target: unknown) {
219
+ if (!(target as { abstract?: boolean }).abstract) return clickableSet.has(target);
220
+ return [...clickableSet].some((item) => (item as { abstract?: boolean; value?: unknown }).abstract && (item as { value: unknown }).value === (target as { value: unknown }).value);
221
+ }
222
+
223
+ function getPossibleMoves (
224
+ bgioState: unknown,
225
+ moves: Record<string, unknown>,
226
+ moveBuilder: MoveBuilder
227
+ ) {
228
+ const { eliminatedMoves, stepIndex } = moveBuilder;
229
+ const _possibleMoveMeta: Record<string, { clickableForMove: Set<unknown> }> = {};
230
+ const allClickable = new Set<unknown>();
231
+
232
+ Object.entries(moves)
233
+ .filter(([moveName]) => !eliminatedMoves.includes(moveName))
234
+ .forEach(([moveName, move]) => {
235
+ const moveRule = resolveProperties(bgioState as BgioResolveState, {
236
+ ...(move as { moveInstance: { rule: Record<string, unknown> } }).moveInstance.rule,
237
+ moveName,
238
+ }) as { arguments?: Record<string, unknown> };
239
+
240
+ const context = {
241
+ moveInstance: (move as { moveInstance: unknown }).moveInstance,
242
+ moveArguments: moveRule.arguments,
243
+ };
244
+
245
+ const targets = moveBuilder.targets.map((t) =>
246
+ (t as { abstract?: boolean }).abstract ? t : (bgioState as { G: { bank: { locate: (id: unknown) => unknown } } }).G.bank.locate((t as { entityId: unknown }).entityId)
247
+ );
248
+
249
+ const payload = createPayload(
250
+ bgioState as BgioResolveState,
251
+ moveRule as Parameters<typeof getSteps>[1],
252
+ targets,
253
+ context
254
+ );
255
+
256
+ context.moveArguments = { ...context.moveArguments, ...payload.arguments };
257
+
258
+ const moveIsAllowed = checkConditions(
259
+ bgioState as BgioResolveState,
260
+ (moveRule as { conditions?: Condition[] }).conditions,
261
+ {},
262
+ context
263
+ ).conditionsAreMet;
264
+ const moveSteps = getSteps(
265
+ bgioState as BgioResolveState,
266
+ moveRule as Parameters<typeof getSteps>[1]
267
+ );
268
+
269
+ const clickableForMove = new Set(
270
+ (moveIsAllowed && moveSteps?.[stepIndex]?.getClickable(context)) || []
271
+ );
272
+
273
+ _possibleMoveMeta[moveName] = { clickableForMove };
274
+ clickableForMove.forEach((entity) => allClickable.add(entity));
275
+ });
276
+
277
+ return { _possibleMoveMeta, allClickable };
278
+ }
279
+
280
+ function isMoveCompleted (
281
+ state: unknown,
282
+ moves: Record<string, { moveInstance: { rule: unknown } }>,
283
+ remainingMoveEntries: [string, unknown][],
284
+ stepIndex: number
285
+ ) {
286
+ return remainingMoveEntries.length === 1 &&
287
+ getSteps(
288
+ state as BgioResolveState,
289
+ moves[remainingMoveEntries[0][0]].moveInstance.rule as Parameters<typeof getSteps>[1]
290
+ ).length === stepIndex + 1;
291
+ }
292
+
293
+ function getWinnerAfterMove (
294
+ state: unknown,
295
+ game: BoardGameEngineGame,
296
+ moveInstance: unknown,
297
+ movePayload: unknown
298
+ ) {
299
+ const simulatedG = simulateMove(
300
+ state as unknown as BgioResolveState,
301
+ preparePayload(movePayload) as { arguments: Record<string, number | { abstract?: boolean; entityId?: unknown }> },
302
+ { moveInstance: moveInstance as { doMove: (...args: unknown[]) => unknown } }
303
+ );
304
+ const endIf = game.endIf as ((ctx: BgioArguments) => unknown) | undefined;
305
+ return endIf?.({ ...state as object, G: JSON.parse(serialize(simulatedG)) } as BgioArguments);
306
+ }
@@ -0,0 +1,81 @@
1
+ import resolveProperties from "../../utils/resolve-properties.js";
2
+ import type { BgioResolveState } from "../../utils/bgio-resolve-types.js";
3
+
4
+ class BankSlot {
5
+ bank: { createEntity: (rule: Record<string, unknown>) => unknown };
6
+ rule: Record<string, unknown> & { count?: number | string; name?: string };
7
+ pool: unknown[];
8
+ remaining: number;
9
+
10
+ constructor (rule: Record<string, unknown> & { count?: number | string; name?: string }, bank: BankSlot["bank"]) {
11
+ this.bank = bank;
12
+ this.rule = rule;
13
+ this.pool = [];
14
+ this.remaining = +((rule.count as number | string) || 1);
15
+ }
16
+
17
+ getOne (bgioArguments: BgioResolveState, options: { state?: unknown }, context: Record<string, unknown>) {
18
+ return this.getMultiple(bgioArguments, 1, options, context)[0];
19
+ }
20
+
21
+ getMultiple (
22
+ bgioArguments: BgioResolveState,
23
+ count: number = Infinity,
24
+ options: { state?: unknown } = {},
25
+ context: Record<string, unknown> = {}
26
+ ) {
27
+ const toReturn: unknown[] = [];
28
+
29
+ if (this.remaining === Infinity && count === Infinity) {
30
+ throw new Error(`Cannot get infinite pieces from slot with infinite remaining: ${this.rule.name}`);
31
+ }
32
+
33
+ if (count !== Infinity && count > this.remaining) {
34
+ throw new Error(`Requested ${count} pieces but only ${this.remaining} available in slot: ${this.rule.name}`);
35
+ }
36
+
37
+ const actualCount = count === Infinity ? this.remaining : count;
38
+
39
+ if (this.remaining !== Infinity) {
40
+ this.remaining -= actualCount;
41
+ }
42
+
43
+ const fromPool = Math.min(actualCount, this.pool.length);
44
+ toReturn.push(...this.pool.splice(0, fromPool));
45
+
46
+ const remainder = actualCount - fromPool;
47
+ if (remainder > 0) {
48
+ toReturn.push(
49
+ ...Array.from(new Array(remainder)).map(() =>
50
+ this.bank.createEntity(this.rule)
51
+ )
52
+ );
53
+ }
54
+
55
+ if (options.state) {
56
+ const newState = resolveProperties(bgioArguments, options.state, context);
57
+ toReturn.forEach((entity) => {
58
+ (entity as { state: Record<string, unknown> }).state = {
59
+ ...(entity as { state: Record<string, unknown> }).state,
60
+ ...newState as Record<string, unknown>,
61
+ };
62
+ });
63
+ }
64
+
65
+ return toReturn;
66
+ }
67
+
68
+ returnToBank (entity: { rule: { state?: unknown }; state?: unknown }) {
69
+ if (entity.rule.state) {
70
+ entity.state = entity.rule.state;
71
+ } else {
72
+ delete entity.state;
73
+ }
74
+ if (this.remaining !== undefined) {
75
+ this.remaining += 1;
76
+ }
77
+ this.pool.push(entity);
78
+ }
79
+ }
80
+
81
+ export default BankSlot;
@@ -0,0 +1,125 @@
1
+ import find from "lodash/find.js";
2
+ import filter from "lodash/filter.js";
3
+ import checkConditions from "../../utils/check-conditions.js";
4
+ import type { RuleWithConditions } from "../../types/rule-with-conditions.js";
5
+ import { registry } from "../../registry.js";
6
+ import BankSlot from "./bank-slot.js";
7
+ import type { BgioResolveState } from "../../utils/bgio-resolve-types.js";
8
+
9
+ class Bank {
10
+ currentEntityId: number;
11
+ tracker: Record<number, unknown>;
12
+ slots: InstanceType<typeof BankSlot>[];
13
+
14
+ constructor (entityRules: Record<string, unknown>[]) {
15
+ this.currentEntityId = 0;
16
+ this.tracker = {};
17
+ this.slots = entityRules.map((rule) => new BankSlot(rule, this));
18
+ }
19
+
20
+ createEntity (definition: Record<string, unknown> = {}, options?: Record<string, unknown>) {
21
+ const Ctor = registry[(definition.entityType || "Entity") as keyof typeof registry] as new (
22
+ a: unknown,
23
+ b: Record<string, unknown>,
24
+ c: number
25
+ ) => unknown;
26
+ const entity = new Ctor(
27
+ {
28
+ bank: this,
29
+ fromBank: true,
30
+ ...options,
31
+ },
32
+ definition,
33
+ this.currentEntityId++
34
+ );
35
+ this.track(entity as { entityId: number });
36
+ return entity;
37
+ }
38
+
39
+ track (entity: { entityId: number }) {
40
+ this.tracker[entity.entityId] = entity;
41
+ }
42
+
43
+ locate (entityId: unknown) {
44
+ return this.tracker[entityId as number];
45
+ }
46
+
47
+ findAll (bgioArguments: BgioResolveState, rule: RuleWithConditions, context: Record<string, unknown>) {
48
+ if (!rule.conditions) {
49
+ throw new Error(`Cannot find entity with no conditions. Rule: ${JSON.stringify(rule)}`);
50
+ }
51
+ return filter(
52
+ Object.values(this.tracker),
53
+ (entity) => checkConditions(
54
+ bgioArguments,
55
+ rule.conditions,
56
+ { target: entity },
57
+ context
58
+ ).conditionsAreMet
59
+ );
60
+ }
61
+
62
+ findOne (bgioArguments: BgioResolveState, rule: RuleWithConditions, context: Record<string, unknown>) {
63
+ return this.findAll(bgioArguments, rule, context)[0];
64
+ }
65
+
66
+ find (bgioArguments: BgioResolveState, rule: RuleWithConditions & { matchMultiple?: boolean }, context: Record<string, unknown>) {
67
+ return rule.matchMultiple
68
+ ? this.findAll(bgioArguments, rule, context)
69
+ : this.findOne(bgioArguments, rule, context);
70
+ }
71
+
72
+ findParent (entity: unknown) {
73
+ return find(this.tracker, (ent) =>
74
+ (ent as { entities?: unknown[] }).entities?.includes(entity)
75
+ || (ent as { spaces?: unknown[] }).spaces?.includes(entity)
76
+ );
77
+ }
78
+
79
+ getOne (bgioArguments: BgioResolveState, rule: RuleWithConditions & { state?: unknown }, context: Record<string, unknown>) {
80
+ const slot = this.getSlot(bgioArguments, rule, context);
81
+ if (!slot) {
82
+ console.error(`No matching slot for ${JSON.stringify(rule)}`);
83
+ }
84
+ return slot!.getOne(bgioArguments, { state: rule.state }, context);
85
+ }
86
+
87
+ getMultiple (bgioArguments: BgioResolveState, rule: RuleWithConditions & { state?: unknown }, count: number, context: Record<string, unknown>) {
88
+ const slots = this.getSlots(bgioArguments, rule, context);
89
+ if (!slots.length) {
90
+ console.error(`No matching slots for ${JSON.stringify(rule)}`);
91
+ }
92
+ return slots.reduce<unknown[]>((acc, slot) => [
93
+ ...acc,
94
+ ...slot.getMultiple(bgioArguments, count, { state: rule.state }),
95
+ ], []);
96
+ }
97
+
98
+ getSlot (bgioArguments: BgioResolveState, rule: RuleWithConditions, context: Record<string, unknown>) {
99
+ return this.slots.find((slot) => checkConditions(
100
+ bgioArguments,
101
+ rule.conditions,
102
+ { target: slot },
103
+ context
104
+ ).conditionsAreMet
105
+ );
106
+ }
107
+
108
+ getSlots (bgioArguments: BgioResolveState, rule: RuleWithConditions, context: Record<string, unknown>) {
109
+ return this.slots.filter((slot) => checkConditions(
110
+ bgioArguments,
111
+ rule.conditions,
112
+ { target: slot },
113
+ context
114
+ ).conditionsAreMet
115
+ );
116
+ }
117
+
118
+ returnToBank (bgioArguments: BgioResolveState, entity: { entityId: number; rule: Record<string, unknown> }) {
119
+ (this.findParent(entity) as { remove: (e: unknown) => void }).remove(entity);
120
+ this.getSlot(bgioArguments, entity.rule as RuleWithConditions, {})!.returnToBank(entity);
121
+ delete this.tracker[entity.entityId];
122
+ }
123
+ }
124
+
125
+ export default Bank;
@@ -1,3 +1,3 @@
1
- import Space from './space/space.js'
1
+ import Space from "./space/space.js";
2
2
 
3
3
  export default class Board extends Space {}
@@ -0,0 +1,59 @@
1
+ import type { Condition as ConditionRule } from "../../types/bagel-types.js";
2
+ import type ConditionBase from "./condition.js";
3
+ import Is from "./is-condition.js";
4
+ import Not from "./not-condition.js";
5
+ import Or from "./or-condition.js";
6
+ import Some from "./some-condition.js";
7
+ import Every from "./every-condition.js";
8
+ import ContainsCondition from "./contains-condition.js";
9
+ import ContainsSameCondition from "./contains-same-condition.js";
10
+ import InLine from "./in-line-condition.js";
11
+ import HasLine from "./has-line-condition.js";
12
+ import IsFull from "./is-full-condition.js";
13
+ import Would from "./would-condition.js";
14
+ import NoPossibleMoves from "./no-possible-moves-condition.js";
15
+ import Evaluate from "./evaluate-condition.js";
16
+ import Position from "./position-condition.js";
17
+ // import BingoCondition from "./bingo-condition.js";
18
+ // import RelativeMoveCondition from "./relative-move-condition.js";
19
+
20
+ export default function conditionFactory (rule: ConditionRule): ConditionBase | undefined {
21
+ if (typeof rule !== "object" || rule === null || !("conditionType" in rule)) {
22
+ return undefined;
23
+ }
24
+ const r = rule as { conditionType: string; [k: string]: unknown };
25
+ if (r.conditionType === "Is") {
26
+ return new Is(r);
27
+ } else if (r.conditionType === "Not") {
28
+ return new Not(r);
29
+ } else if (r.conditionType === "Or") {
30
+ return new Or(r);
31
+ } else if (r.conditionType === "Some") {
32
+ return new Some(r);
33
+ } else if (r.conditionType === "Contains") {
34
+ return new ContainsCondition(r);
35
+ } else if (r.conditionType === "ContainsSame") {
36
+ return new ContainsSameCondition(r);
37
+ } else if (r.conditionType === "Every") {
38
+ return new Every(r);
39
+ } else if (r.conditionType === "InLine") {
40
+ return new InLine(r);
41
+ } else if (r.conditionType === "HasLine") {
42
+ return new HasLine(r);
43
+ } else if (r.conditionType === "IsFull") {
44
+ return new IsFull(r);
45
+ } else if (r.conditionType === "Would") {
46
+ return new Would(r);
47
+ } else if (r.conditionType === "NoPossibleMoves") {
48
+ return new NoPossibleMoves(r);
49
+ } else if (r.conditionType === "Evaluate") {
50
+ return new Evaluate(r);
51
+ } else if (r.conditionType === "Position") {
52
+ return new Position(r);
53
+ // } else if (rule.conditionType === "bingo") {
54
+ // return new BingoCondition(rule);
55
+ // } else if (rule.conditionType === "relativeMove") {
56
+ // return new RelativeMoveCondition(rule);
57
+ }
58
+ return undefined;
59
+ }
@@ -0,0 +1,50 @@
1
+ import resolveProperties from "../../utils/resolve-properties.js";
2
+ import type { BgioResolveState } from "../../utils/bgio-resolve-types.js";
3
+
4
+ export default abstract class Condition {
5
+ rule: unknown;
6
+
7
+ constructor (rule: unknown) {
8
+ this.rule = rule;
9
+ }
10
+
11
+ check (bgioArguments: unknown, payload: Record<string, unknown>, context: Record<string, unknown>) {
12
+ const conditionPayload = { ...payload };
13
+ const newContext = { ...context };
14
+
15
+ if (conditionPayload.target) {
16
+ newContext.originalTarget = conditionPayload.target;
17
+ }
18
+
19
+ const rule = resolveProperties(
20
+ bgioArguments as BgioResolveState,
21
+ this.rule,
22
+ newContext
23
+ );
24
+
25
+ if ((rule as { target?: unknown }).target !== undefined) {
26
+ conditionPayload.target = (rule as { target: unknown }).target;
27
+ }
28
+
29
+ if ((this.rule as { target?: unknown }).target !== undefined && !conditionPayload.target) {
30
+ return { conditionIsMet: false };
31
+ }
32
+
33
+ return this.checkCondition(bgioArguments, rule, conditionPayload, newContext);
34
+ }
35
+
36
+ abstract checkCondition (
37
+ bgioArguments: unknown,
38
+ rule: unknown,
39
+ conditionPayload: Record<string, unknown>,
40
+ newContext: Record<string, unknown>
41
+ ): { conditionIsMet: boolean; [k: string]: unknown };
42
+
43
+ isMet (...args: unknown[]) {
44
+ return (this.check as (a: unknown, b: Record<string, unknown>, c: Record<string, unknown>) => { conditionIsMet: boolean })(
45
+ args[0],
46
+ (args[1] as Record<string, unknown>) ?? {},
47
+ (args[2] as Record<string, unknown>) ?? {}
48
+ ).conditionIsMet;
49
+ }
50
+ }
@@ -1,17 +1,18 @@
1
1
  import _matches from "lodash/matches.js";
2
+ import type { Condition as ConditionRule } from "../../types/bagel-types.js";
2
3
  import Condition from "../condition/condition.js";
3
4
  import checkConditions from "../../utils/check-conditions.js";
4
5
 
5
6
  export default class ContainsCondition extends Condition {
6
- checkCondition(bgioArguments, rule, payload, context) {
7
- const { target } = payload
7
+ checkCondition (bgioArguments: unknown, rule: unknown, payload: Record<string, unknown>, context: Record<string, unknown>) {
8
+ const target = payload.target as { entities?: unknown[]; spaces?: unknown[] } | undefined;
8
9
  if (!target) {
9
10
  return { matches: [], conditionIsMet: false }
10
11
  } else {
11
12
  const candidates = target.entities ?? target.spaces
12
- const matches = candidates?.filter(entity => checkConditions(
13
+ const matches = candidates?.filter((entity: unknown) => checkConditions(
13
14
  bgioArguments,
14
- rule,
15
+ (rule as { conditions?: ConditionRule[] }).conditions,
15
16
  { target: entity },
16
17
  context
17
18
  ).conditionsAreMet) ?? []
@@ -3,21 +3,24 @@ import conditionFactory from "./condition-factory.js";
3
3
  import Condition from "./condition.js";
4
4
 
5
5
  export default class ContainsSame extends Condition {
6
- checkCondition (bgioArguments, rule, { targets }) {
6
+ checkCondition (bgioArguments: unknown, rule: unknown, conditionPayload: Record<string, unknown>, _newContext: Record<string, unknown>) {
7
+ const { targets } = conditionPayload as { targets: { entities?: unknown[]; rule?: unknown }[] };
7
8
  if (targets.length === 1 && targets[0].entities?.length) {
8
9
  return { conditionIsMet: true }
9
10
  }
10
11
 
11
12
  const [ first, ...restEntities ] = targets;
12
- const conditionIsMet = first.entities.some(entity => {
13
+ const conditionIsMet = (first.entities ?? []).some((entity: unknown) => {
14
+ const e = entity as { rule?: unknown };
13
15
  const condition = conditionFactory({
14
16
  conditionType: "Contains",
15
17
  conditions: [{
16
18
  conditionType: 'Is',
17
- matcher: pick(entity.rule, rule.properties)
19
+ matcher: pick(e.rule as object, (rule as { properties: unknown }).properties as never)
18
20
  }]
19
- })
20
- return restEntities.every(ent => {
21
+ });
22
+ if (!condition) return false;
23
+ return restEntities.every((ent: unknown) => {
21
24
  return condition.isMet(bgioArguments, { target: ent })
22
25
  })
23
26
  })