board-game-engine 0.0.2 → 0.0.3

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 (102) hide show
  1. package/board-game-engine.test.js +2 -41
  2. package/dist/board-game-engine.js +32556 -5074
  3. package/dist/board-game-engine.min.js +2 -1
  4. package/dist/board-game-engine.min.js.LICENSE.txt +14 -0
  5. package/package.json +6 -3
  6. package/patch-bgio.cjs +23 -0
  7. package/src/client/client.js +287 -0
  8. package/src/game-factory/bank/bank-slot.js +69 -0
  9. package/src/game-factory/bank/bank.js +114 -0
  10. package/src/game-factory/board.js +3 -0
  11. package/src/game-factory/condition/condition-factory.js +52 -0
  12. package/src/game-factory/condition/condition.js +39 -0
  13. package/src/game-factory/condition/contains-condition.js +21 -0
  14. package/src/game-factory/condition/contains-same-condition.js +27 -0
  15. package/src/game-factory/condition/evaluate-condition.js +18 -0
  16. package/src/game-factory/condition/every-condition.js +25 -0
  17. package/src/game-factory/condition/has-line-condition.js +14 -0
  18. package/src/game-factory/condition/in-line-condition.js +19 -0
  19. package/src/game-factory/condition/is-condition.js +23 -0
  20. package/src/game-factory/condition/is-full-condition.js +9 -0
  21. package/src/game-factory/condition/no-possible-moves-condition.js +14 -0
  22. package/src/game-factory/condition/not-condition.js +14 -0
  23. package/src/game-factory/condition/or-condition.js +15 -0
  24. package/src/game-factory/condition/position-condition.js +12 -0
  25. package/src/game-factory/condition/some-condition.js +24 -0
  26. package/src/game-factory/condition/would-condition.js +94 -0
  27. package/src/game-factory/entity.js +29 -0
  28. package/src/game-factory/expand-game-rules.js +276 -0
  29. package/src/game-factory/game-factory.js +239 -0
  30. package/src/game-factory/move/end-turn.js +7 -0
  31. package/src/game-factory/move/for-each.js +18 -0
  32. package/src/game-factory/move/index.js +7 -0
  33. package/src/game-factory/move/move-entity.js +16 -0
  34. package/src/game-factory/move/move-factory.js +89 -0
  35. package/src/game-factory/move/move.js +131 -0
  36. package/src/game-factory/move/pass-turn.js +10 -0
  37. package/src/game-factory/move/pass.js +7 -0
  38. package/src/game-factory/move/place-new.js +33 -0
  39. package/src/game-factory/move/remove-entity.js +7 -0
  40. package/src/game-factory/move/set-active-players.js +23 -0
  41. package/src/game-factory/move/set-state.js +13 -0
  42. package/src/game-factory/move/shuffle.js +7 -0
  43. package/src/game-factory/move/take-from.js +7 -0
  44. package/src/game-factory/space/space.js +30 -0
  45. package/src/game-factory/space-group/grid.js +43 -0
  46. package/src/game-factory/space-group/space-group.js +29 -0
  47. package/src/index.js +2 -0
  48. package/src/registry.js +17 -0
  49. package/src/utils/any-valid-moves.js +157 -0
  50. package/src/utils/check-conditions.js +28 -0
  51. package/src/utils/create-payload.js +16 -0
  52. package/src/utils/deserialize-bgio-arguments.js +8 -0
  53. package/src/utils/do-moves.js +18 -0
  54. package/src/utils/entity-matches.js +20 -0
  55. package/src/utils/find-met-condition.js +22 -0
  56. package/src/utils/get-current-moves.js +12 -0
  57. package/src/utils/get-scenario-results.js +23 -0
  58. package/src/utils/get-steps.js +28 -0
  59. package/src/utils/get.js +25 -0
  60. package/src/utils/grid-contains-sequence.js +226 -0
  61. package/src/utils/json-transformer.js +12 -0
  62. package/src/utils/prepare-payload.js +16 -0
  63. package/src/utils/resolve-entity.js +9 -0
  64. package/src/utils/resolve-expression.js +10 -0
  65. package/src/utils/resolve-properties.js +157 -0
  66. package/src/utils/simulate-move.js +25 -0
  67. package/webpack.config.js +4 -1
  68. package/src/action/action-factory.js +0 -13
  69. package/src/action/action.js +0 -34
  70. package/src/action/move-piece-action.js +0 -11
  71. package/src/action/select-piece-action.js +0 -23
  72. package/src/action/swap-action.js +0 -14
  73. package/src/board/board-factory.js +0 -12
  74. package/src/board/board-group.js +0 -9
  75. package/src/board/board.js +0 -11
  76. package/src/board/grid.js +0 -52
  77. package/src/board/stack.js +0 -16
  78. package/src/condition/action-type-matches-condition.js +0 -7
  79. package/src/condition/bingo-condition.js +0 -50
  80. package/src/condition/blackout-condition.js +0 -9
  81. package/src/condition/condition-factory.js +0 -31
  82. package/src/condition/condition.js +0 -9
  83. package/src/condition/contains-condition.js +0 -14
  84. package/src/condition/does-not-contain-condition.js +0 -15
  85. package/src/condition/is-valid-player-condition.js +0 -7
  86. package/src/condition/piece-matches-condition.js +0 -23
  87. package/src/condition/relative-move-condition.js +0 -16
  88. package/src/condition/some-condition.js +0 -7
  89. package/src/game/game.ts +0 -362
  90. package/src/index.ts +0 -1
  91. package/src/piece/piece-factory.js +0 -5
  92. package/src/piece/piece.ts +0 -25
  93. package/src/piece/pile.js +0 -70
  94. package/src/player/player.ts +0 -13
  95. package/src/registry.ts +0 -51
  96. package/src/round/round-factory.js +0 -7
  97. package/src/round/round.js +0 -41
  98. package/src/round/sequential-player-turn.js +0 -18
  99. package/src/space/space.ts +0 -22
  100. package/src/utils/find-value-path.js +0 -37
  101. package/src/utils/resolve-board.ts +0 -38
  102. package/src/utils/resolve-piece.ts +0 -43
@@ -0,0 +1,14 @@
1
+ /*! *****************************************************************************
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for any
5
+ purpose with or without fee is hereby granted.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
14
+ ***************************************************************************** */
package/package.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "board-game-engine",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "wip board game engine",
5
5
  "main": "dist/board-game-engine.js",
6
6
  "scripts": {
7
7
  "test": "jest --coverage --verbose false",
8
8
  "lint": "eslint .",
9
+ "postinstall": "node ./patch-bgio.cjs",
9
10
  "build": "webpack",
10
11
  "prepublish": "npm run build"
11
12
  },
12
13
  "author": "Matthew Broatch",
13
14
  "license": "ISC",
14
15
  "devDependencies": {
15
- "@babel/preset-env": "^7.16.7",
16
+ "@babel/preset-env": "^7.16.8",
16
17
  "@babel/preset-typescript": "^7.27.1",
17
18
  "babel-jest": "^27.4.4",
18
19
  "babel-loader": "",
@@ -23,10 +24,12 @@
23
24
  "eslint-plugin-import": "^2.3.0",
24
25
  "jest": "^27.4.3",
25
26
  "standard": "^16.0.4",
26
- "webpack": "^5.65.0",
27
+ "webpack": "^5.66.0",
27
28
  "webpack-cli": "^4.9.1"
28
29
  },
29
30
  "dependencies": {
31
+ "boardgame.io": "^0.50.2",
32
+ "expr-eval": "^2.0.2",
30
33
  "wackson": "^1.1.0"
31
34
  },
32
35
  "repository": {
package/patch-bgio.cjs ADDED
@@ -0,0 +1,23 @@
1
+ // scalpel to make guts.js work
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const serverFile = path.join(
6
+ __dirname,
7
+ 'node_modules',
8
+ 'boardgame.io',
9
+ 'dist/cjs/server.js'
10
+ );
11
+
12
+ fs.appendFileSync(
13
+ serverFile,
14
+ `
15
+ exports.configureRouter = configureRouter
16
+ exports.createServerRunConfig = createServerRunConfig
17
+ exports.configureApp = configureApp
18
+ exports.getPortFromServer = getPortFromServer
19
+ exports.getPortFromServer = getPortFromServer
20
+ exports.Master = Master
21
+ exports.TransportAPI = TransportAPI
22
+ `
23
+ );
@@ -0,0 +1,287 @@
1
+ import { Client as BoardgameIOClient } from 'boardgame.io/client'
2
+ import { Debug } from 'boardgame.io/debug';
3
+ import { SocketIO } from 'boardgame.io/multiplayer'
4
+ import { serialize, deserialize } from 'wackson';
5
+
6
+ import gameFactory from '../game-factory/game-factory.js'
7
+ import { registry } from '../registry.js';
8
+
9
+ import simulateMove from '../utils/simulate-move.js';
10
+ import getCurrentMoves from '../utils/get-current-moves.js';
11
+ import resolveProperties from '../utils/resolve-properties.js';
12
+ import checkConditions from '../utils/check-conditions.js';
13
+ import preparePayload from '../utils/prepare-payload.js';
14
+ import getSteps from '../utils/get-steps.js';
15
+ import createPayload from '../utils/create-payload.js';
16
+
17
+
18
+ export default class Client {
19
+ constructor (options) {
20
+ this.options = options
21
+ this.moveBuilder = {
22
+ targets: [],
23
+ stepIndex: 0,
24
+ eliminatedMoves: []
25
+ }
26
+ this.optimisticWinner = null
27
+ this.allClickable = new Set()
28
+ this.game = gameFactory(JSON.parse(options.gameRules), options.gameName)
29
+ }
30
+
31
+ connect () {
32
+ const {
33
+ server,
34
+ numPlayers,
35
+ onClientUpdate,
36
+ debug = {
37
+ collapseOnLoad: true,
38
+ impl: Debug,
39
+ },
40
+ gameId,
41
+ gameRules,
42
+ boardgamePlayerID,
43
+ clientToken,
44
+ singlePlayer = !clientToken,
45
+ } = this.options
46
+
47
+ try {
48
+ const clientOptions = singlePlayer
49
+ ? { game: this.game, numPlayers, debug }
50
+ : {
51
+ game: this.game,
52
+ multiplayer: singlePlayer ? undefined : SocketIO({
53
+ server,
54
+ socketOpts: {
55
+ transports: ['websocket', 'polling']
56
+ }
57
+ }),
58
+ matchID: gameId,
59
+ playerID: boardgamePlayerID,
60
+ credentials: clientToken,
61
+ debug,
62
+ }
63
+
64
+ this.client = BoardgameIOClient(clientOptions)
65
+
66
+ if (onClientUpdate) {
67
+ this.client.subscribe(onClientUpdate)
68
+ }
69
+
70
+ this.client.start()
71
+
72
+ return this
73
+ } catch (error) {
74
+ console.error('Failed to join game:', error)
75
+ }
76
+ }
77
+
78
+ getState () {
79
+ let state
80
+ let moves
81
+ let gameover
82
+ const clientState = this.client?.getState()
83
+ if (clientState) {
84
+ state = {
85
+ ...clientState,
86
+ G: deserialize(JSON.stringify(clientState.G), registry),
87
+ originalG: clientState.G,
88
+ }
89
+
90
+ gameover = state?.ctx?.gameover
91
+
92
+ // Fix: use arrow function so `this` refers to the Client instance
93
+ moves = !gameover
94
+ ? Object.entries(getCurrentMoves(state, this.client)).reduce((acc, [moveName, rawMove]) => {
95
+ const move = (payload) => {
96
+ this.client.moves[moveName](preparePayload(payload))
97
+ }
98
+ move.moveInstance = rawMove.moveInstance
99
+ return {
100
+ ...acc,
101
+ [moveName]: move
102
+ }
103
+ }, {})
104
+ : []
105
+ }
106
+
107
+ return {
108
+ state,
109
+ gameover,
110
+ moves
111
+ }
112
+ }
113
+
114
+ doStep (target, isSpectator) {
115
+ const { state, moves } = this.getState()
116
+ const { possibleMoveMeta, allClickable } = getPossibleMoves(
117
+ state,
118
+ moves,
119
+ this.moveBuilder,
120
+ isSpectator
121
+ )
122
+ this.allClickable = allClickable
123
+
124
+ // Filter out moves that don't accept this target
125
+ const newEliminated = Object.entries(possibleMoveMeta)
126
+ .filter(([_, meta]) => !meta.finishedOnLastStep && !meta.clickableForMove.has(target))
127
+ .map(([name]) => name)
128
+ .concat(this.moveBuilder.eliminatedMoves);
129
+
130
+ if (newEliminated.length === Object.keys(moves).length) {
131
+ console.error('invalid move with target:', target?.rule);
132
+ return;
133
+ }
134
+
135
+ this.moveBuilder = {
136
+ eliminatedMoves: newEliminated,
137
+ stepIndex: this.moveBuilder.stepIndex + 1,
138
+ targets: [...this.moveBuilder.targets, target]
139
+ }
140
+
141
+ // Fix: filter possibleMoveMeta to only include moves not in newEliminated,
142
+ // so findCompletedMove sees the post-elimination state rather than the stale pre-elimination state
143
+ const filteredMoveMeta = Object.fromEntries(
144
+ Object.entries(possibleMoveMeta).filter(([name]) => !newEliminated.includes(name))
145
+ );
146
+
147
+ const completed = findCompletedMove(state, filteredMoveMeta, this.moveBuilder, moves);
148
+
149
+ if (completed) {
150
+ // without this, an extra post-game turn would flash
151
+ this.optimisticWinner = getWinnerAfterMove(
152
+ state,
153
+ this.game,
154
+ completed.move.moveInstance,
155
+ completed.payload
156
+ );
157
+
158
+ completed.move(completed.payload);
159
+
160
+ this.moveBuilder = ({ targets: [], stepIndex: 0, eliminatedMoves: [] });
161
+ }
162
+
163
+ this.options.onClientUpdate?.()
164
+ }
165
+
166
+ reset () {
167
+ this.moveBuilder = ({ targets: [], stepIndex: 0, eliminatedMoves: [] });
168
+ this.optimisticWinner = null
169
+ this.options.onClientUpdate?.()
170
+ }
171
+
172
+ undoStep () {
173
+ if (this.moveBuilder.targets.length) {
174
+ this.moveBuilder = {
175
+ targets: this.moveBuilder.targets.slice(0, -1),
176
+ stepIndex: Math.max(0, this.moveBuilder.stepIndex - 1),
177
+ eliminatedMoves: []
178
+ }
179
+ }
180
+ this.options.onClientUpdate?.()
181
+ }
182
+ }
183
+
184
+ function getPossibleMoves(bgioState, moves, moveBuilder, isSpectator) {
185
+ if (isSpectator) {
186
+ return { possibleMoveMeta: {}, allClickable: new Set() };
187
+ }
188
+
189
+ const { eliminatedMoves, stepIndex } = moveBuilder;
190
+
191
+ const possibleMoveMeta = {};
192
+ const allClickable = new Set();
193
+
194
+ const availableMoves = Object.entries(moves)
195
+ .filter(([moveName]) => !eliminatedMoves.includes(moveName));
196
+
197
+ availableMoves.forEach(([moveName, move]) => {
198
+ const moveRule = resolveProperties(
199
+ bgioState,
200
+ { ...move.moveInstance.rule, moveName }
201
+ )
202
+
203
+ const context = {
204
+ moveInstance: move.moveInstance,
205
+ moveArguments: moveRule.arguments
206
+ }
207
+
208
+ const payload = createPayload(
209
+ bgioState,
210
+ moveRule,
211
+ moveBuilder.targets,
212
+ context
213
+ );
214
+
215
+ context.moveArguments = {
216
+ ...context.moveArguments,
217
+ ...payload.arguments,
218
+ }
219
+
220
+ const moveIsAllowed = checkConditions(
221
+ bgioState,
222
+ moveRule,
223
+ {},
224
+ context
225
+ ).conditionsAreMet;
226
+
227
+ const moveSteps = getSteps(
228
+ bgioState,
229
+ moveRule
230
+ );
231
+
232
+ const lastStep = moveSteps?.[stepIndex - 1];
233
+ const currentStep = moveSteps?.[stepIndex];
234
+ const finishedOnLastStep = moveSteps && !!lastStep && !currentStep;
235
+
236
+ const clickableForMove = new Set(
237
+ (moveIsAllowed && currentStep?.getClickable(context)) || []
238
+ );
239
+
240
+ possibleMoveMeta[moveName] = { finishedOnLastStep, clickableForMove };
241
+
242
+ clickableForMove.forEach((entity) => {
243
+ allClickable.add(entity);
244
+ });
245
+ });
246
+
247
+ return { possibleMoveMeta, allClickable };
248
+ }
249
+
250
+ // Fix: renamed first parameter from `bgioArguments` to `bgioState` to match its actual usage
251
+ function findCompletedMove(bgioState, possibleMoveMeta, moveBuilder, moves) {
252
+ const possibleMoveNames = Object.keys(possibleMoveMeta);
253
+
254
+ // Only one possible move left
255
+ if (possibleMoveNames.length !== 1) return null;
256
+
257
+ const moveName = possibleMoveNames[0];
258
+ const meta = possibleMoveMeta[moveName];
259
+
260
+ // And it's finished
261
+ if (!meta.finishedOnLastStep) return null;
262
+
263
+ const move = moves[moveName];
264
+ const moveRule = move.moveInstance.rule;
265
+
266
+ const payload = createPayload(
267
+ bgioState,
268
+ moveRule,
269
+ moveBuilder.targets,
270
+ { moveInstance: move.moveInstance }
271
+ );
272
+
273
+ return { moveName, move, payload };
274
+ }
275
+
276
+ function getWinnerAfterMove (state, game, moveInstance, movePayload) {
277
+ const simulatedG = simulateMove(
278
+ state,
279
+ preparePayload(movePayload),
280
+ { moveInstance }
281
+ )
282
+
283
+ return game.endIf?.({
284
+ ...state,
285
+ G: JSON.parse(serialize(simulatedG))
286
+ })
287
+ }
@@ -0,0 +1,69 @@
1
+ import resolveProperties from '../../utils/resolve-properties.js'
2
+
3
+ // lazily create entities as needed, and also function as an index of entities created
4
+ class BankSlot {
5
+ constructor (rule, bank) {
6
+ this.bank = bank
7
+ this.rule = rule
8
+ this.pool = []
9
+ this.remaining = +rule.count || 1
10
+ }
11
+
12
+ getOne (bgioArguments, options, context) {
13
+ return this.getMultiple(bgioArguments, 1, options, context)[0]
14
+ }
15
+
16
+ getMultiple (bgioArguments, count = Infinity, options = {}, context) {
17
+ const toReturn = []
18
+
19
+ if (this.remaining === Infinity && count === Infinity) {
20
+ throw new Error(`Cannot get infinite pieces from slot with infinite remaining: ${this.rule.name}`)
21
+ }
22
+
23
+ if (count !== Infinity && count > this.remaining) {
24
+ throw new Error(`Requested ${count} pieces but only ${this.remaining} available in slot: ${this.rule.name}`)
25
+ }
26
+
27
+ // Determine actual count to fetch
28
+ const actualCount = count === Infinity ? this.remaining : count
29
+
30
+ if (this.remaining !== Infinity) {
31
+ this.remaining -= actualCount
32
+ }
33
+
34
+ const fromPool = Math.min(actualCount, this.pool.length)
35
+ toReturn.push(...this.pool.splice(0, fromPool))
36
+
37
+ const remainder = actualCount - fromPool
38
+ if (remainder > 0) {
39
+ toReturn.push(
40
+ ...Array.from(new Array(remainder)).map(() =>
41
+ this.bank.createEntity(this.rule)
42
+ )
43
+ )
44
+ }
45
+
46
+ if (options.state) {
47
+ const newState = resolveProperties(bgioArguments, options.state, context)
48
+ toReturn.forEach(entity => {
49
+ entity.state = { ...entity.state, ...newState }
50
+ })
51
+ }
52
+
53
+ return toReturn
54
+ }
55
+
56
+ returnToBank (entity) {
57
+ if (entity.rule.state) {
58
+ entity.state = entity.rule.state
59
+ } else {
60
+ delete entity.state
61
+ }
62
+ if (this.remaining !== undefined) {
63
+ this.remaining += 1
64
+ }
65
+ this.pool.push(entity)
66
+ }
67
+ }
68
+
69
+ export default BankSlot
@@ -0,0 +1,114 @@
1
+ import find from 'lodash/find.js'
2
+ import filter from 'lodash/filter.js'
3
+ import checkConditions from '../../utils/check-conditions.js'
4
+ import { registry } from '../../registry.js'
5
+ import BankSlot from './bank-slot.js'
6
+
7
+ class Bank {
8
+ constructor (entityRules) {
9
+ this.currentEntityId = 0
10
+ this.tracker = {}
11
+ this.slots = entityRules.map(rule => new BankSlot(rule, this))
12
+ }
13
+
14
+ createEntity (definition = {}, options) {
15
+ const entity = new (registry[definition.type || 'Entity'])(
16
+ {
17
+ bank: this,
18
+ fromBank: true,
19
+ ...options
20
+ },
21
+ definition,
22
+ this.currentEntityId++
23
+ )
24
+ this.track(entity)
25
+ return entity
26
+ }
27
+
28
+ track (entity) {
29
+ this.tracker[entity.entityId] = entity
30
+ }
31
+
32
+ locate (entityId) {
33
+ return this.tracker[entityId]
34
+ }
35
+
36
+ findAll (bgioArguments, rule, context) {
37
+ if (!rule.conditions) {
38
+ throw new Error (`Cannot find entity with no conditions. Rule: ${JSON.stringify(rule)}`)
39
+ }
40
+ return filter(
41
+ Object.values(this.tracker),
42
+ entity => checkConditions(
43
+ bgioArguments,
44
+ rule,
45
+ { target: entity },
46
+ context
47
+ ).conditionsAreMet
48
+ )
49
+ }
50
+
51
+ findOne (bgioArguments, rule, context) {
52
+ return this.findAll(bgioArguments, rule, context)[0]
53
+ }
54
+
55
+ find (bgioArguments, rule, context) {
56
+ return rule.matchMultiple
57
+ ? this.findAll(bgioArguments, rule, context)
58
+ : this.findOne(bgioArguments, rule, context)
59
+ }
60
+
61
+ findParent (entity) {
62
+ return find(this.tracker, ent =>
63
+ ent.entities?.includes(entity)
64
+ || ent.spaces?.includes(entity)
65
+ )
66
+ }
67
+
68
+ getOne (bgioArguments, rule, context) {
69
+ const slot = this.getSlot(bgioArguments, rule, context)
70
+ if (!slot) {
71
+ console.error(`No matching slot for ${JSON.stringify(rule)}`)
72
+ }
73
+ return slot.getOne(bgioArguments, { state: rule.state }, context)
74
+ }
75
+
76
+ getMultiple (bgioArguments, rule, count, context) {
77
+ const slots = this.getSlots(bgioArguments, rule, context)
78
+ if (!slots.length) {
79
+ console.error(`No matching slots for ${JSON.stringify(rule)}`)
80
+ }
81
+ return slots.reduce((acc, slot) => [
82
+ ...acc,
83
+ ...slot.getMultiple(bgioArguments, count, { state: rule.state })
84
+ ], [])
85
+ }
86
+
87
+ getSlot (bgioArguments, rule, context) {
88
+ return this.slots.find(slot => checkConditions(
89
+ bgioArguments,
90
+ rule,
91
+ { target: slot },
92
+ context
93
+ ).conditionsAreMet
94
+ )
95
+ }
96
+
97
+ getSlots (bgioArguments, rule, context) {
98
+ return this.slots.filter(slot => checkConditions(
99
+ bgioArguments,
100
+ rule,
101
+ { target: slot },
102
+ context
103
+ ).conditionsAreMet
104
+ )
105
+ }
106
+
107
+ returnToBank (bgioArguments, entity) {
108
+ this.findParent(entity).remove(entity)
109
+ this.getSlot(bgioArguments, entity.rule).returnToBank(entity)
110
+ delete this.tracker[entity.entityId]
111
+ }
112
+ }
113
+
114
+ export default Bank
@@ -0,0 +1,3 @@
1
+ import Space from './space/space.js'
2
+
3
+ export default class Board extends Space {}
@@ -0,0 +1,52 @@
1
+ import Is from "./is-condition.js";
2
+ import Not from "./not-condition.js";
3
+ import Or from "./or-condition.js";
4
+ import Some from "./some-condition.js";
5
+ import Every from "./every-condition.js";
6
+ import ContainsCondition from "./contains-condition.js";
7
+ import ContainsSameCondition from "./contains-same-condition.js";
8
+ import InLine from "./in-line-condition.js";
9
+ import HasLine from "./has-line-condition.js";
10
+ import IsFull from "./is-full-condition.js";
11
+ import Would from "./would-condition.js";
12
+ import NoPossibleMoves from "./no-possible-moves-condition.js";
13
+ import Evaluate from "./evaluate-condition.js";
14
+ import Position from "./position-condition.js";
15
+ // import BingoCondition from "./bingo-condition.js";
16
+ // import RelativeMoveCondition from "./relative-move-condition.js";
17
+
18
+ export default function conditionFactory(rule) {
19
+ if (rule.conditionType === "Is") {
20
+ return new Is(rule);
21
+ } else if (rule.conditionType === "Not") {
22
+ return new Not(rule);
23
+ } else if (rule.conditionType === "Or") {
24
+ return new Or(rule);
25
+ } else if (rule.conditionType === "Some") {
26
+ return new Some(rule);
27
+ } else if (rule.conditionType === "Contains") {
28
+ return new ContainsCondition(rule);
29
+ } else if (rule.conditionType === "ContainsSame") {
30
+ return new ContainsSameCondition(rule);
31
+ } else if (rule.conditionType === "Every") {
32
+ return new Every(rule);
33
+ } else if (rule.conditionType === "InLine") {
34
+ return new InLine(rule);
35
+ } else if (rule.conditionType === "HasLine") {
36
+ return new HasLine(rule);
37
+ } else if (rule.conditionType === "IsFull") {
38
+ return new IsFull(rule);
39
+ } else if (rule.conditionType === "Would") {
40
+ return new Would(rule);
41
+ } else if (rule.conditionType === "NoPossibleMoves") {
42
+ return new NoPossibleMoves(rule);
43
+ } else if (rule.conditionType === "Evaluate") {
44
+ return new Evaluate(rule);
45
+ } else if (rule.conditionType === "Position") {
46
+ return new Position(rule);
47
+ // } else if (rule.conditionType === "bingo") {
48
+ // return new BingoCondition(rule);
49
+ // } else if (rule.conditionType === "relativeMove") {
50
+ // return new RelativeMoveCondition(rule);
51
+ }
52
+ }
@@ -0,0 +1,39 @@
1
+ import resolveProperties from "../../utils/resolve-properties.js";
2
+
3
+ export default class Condition {
4
+ constructor (rule) {
5
+ this.rule = rule
6
+ }
7
+
8
+ check (bgioArguments, payload, context) {
9
+ const conditionPayload = { ...payload }
10
+ const newContext = { ...context }
11
+
12
+ if (conditionPayload.target) {
13
+ newContext.originalTarget = conditionPayload.target
14
+ }
15
+
16
+ const rule = resolveProperties(
17
+ bgioArguments,
18
+ this.rule,
19
+ newContext
20
+ )
21
+
22
+ // We don't simply defer to payload target because of Parent and RelativePath
23
+ // target types, for instance, which retarget to another entity
24
+ if (rule.target !== undefined) {
25
+ conditionPayload.target = rule.target
26
+ }
27
+
28
+ // Nonexistent entities never fulfill conditions (including "Not" conditions!)
29
+ if (this.rule.target !== undefined && !conditionPayload.target) {
30
+ return { conditionIsMet: false }
31
+ }
32
+
33
+ return this.checkCondition(bgioArguments, rule, conditionPayload, newContext)
34
+ }
35
+
36
+ isMet(...args) {
37
+ return this.check(...args).conditionIsMet
38
+ }
39
+ }
@@ -0,0 +1,21 @@
1
+ import _matches from "lodash/matches.js";
2
+ import Condition from "../condition/condition.js";
3
+ import checkConditions from "../../utils/check-conditions.js";
4
+
5
+ export default class ContainsCondition extends Condition {
6
+ checkCondition(bgioArguments, rule, payload, context) {
7
+ const { target } = payload
8
+ if (!target) {
9
+ return { matches: [], conditionIsMet: false }
10
+ } else {
11
+ const candidates = target.entities ?? target.spaces
12
+ const matches = candidates?.filter(entity => checkConditions(
13
+ bgioArguments,
14
+ rule,
15
+ { target: entity },
16
+ context
17
+ ).conditionsAreMet) ?? []
18
+ return { matches, conditionIsMet: !!matches.length }
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,27 @@
1
+ import pick from "lodash/pick.js";
2
+ import conditionFactory from "./condition-factory.js";
3
+ import Condition from "./condition.js";
4
+
5
+ export default class ContainsSame extends Condition {
6
+ checkCondition (bgioArguments, rule, { targets }) {
7
+ if (targets.length === 1 && targets[0].entities?.length) {
8
+ return { conditionIsMet: true }
9
+ }
10
+
11
+ const [ first, ...restEntities ] = targets;
12
+ const conditionIsMet = first.entities.some(entity => {
13
+ const condition = conditionFactory({
14
+ conditionType: "Contains",
15
+ conditions: [{
16
+ conditionType: 'Is',
17
+ matcher: pick(entity.rule, rule.properties)
18
+ }]
19
+ })
20
+ return restEntities.every(ent => {
21
+ return condition.isMet(bgioArguments, { target: ent })
22
+ })
23
+ })
24
+
25
+ return { conditionIsMet }
26
+ }
27
+ }