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,18 @@
1
+ import _matches from "lodash/matches.js";
2
+ import Condition from "../condition/condition.js";
3
+ import resolveExpression from "../../utils/resolve-expression.js";
4
+
5
+ export default class Evaluate extends Condition {
6
+ checkCondition(bgioArguments, rule, payload, context) {
7
+ const newContext = { ...context }
8
+ if (payload?.target) {
9
+ newContext.target = payload.target
10
+ }
11
+ const result = resolveExpression(
12
+ bgioArguments,
13
+ rule,
14
+ newContext
15
+ )
16
+ return { result, conditionIsMet: !!result }
17
+ }
18
+ }
@@ -0,0 +1,25 @@
1
+ import Condition from "./condition.js";
2
+ import checkConditions from "../../utils/check-conditions.js";
3
+
4
+ export default class EveryCondition extends Condition {
5
+ checkCondition(bgioArguments, rule, { target: targets }, context) {
6
+ const results = targets.map((target) => {
7
+ const loopContext = {
8
+ ...context,
9
+ loopTarget: target
10
+ }
11
+
12
+ return checkConditions(
13
+ bgioArguments,
14
+ rule,
15
+ undefined,
16
+ loopContext
17
+ )
18
+ })
19
+
20
+ return {
21
+ conditionIsMet: results.every(r => r.conditionsAreMet),
22
+ results
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,14 @@
1
+ import Condition from "./condition.js";
2
+ import gridContainsSequence from "../../utils/grid-contains-sequence.js";
3
+
4
+ export default class HasLineCondition extends Condition {
5
+ checkCondition(bgioArguments, rule, payload, context) {
6
+ const { matches } = gridContainsSequence(
7
+ bgioArguments,
8
+ payload.target,
9
+ rule.sequence,
10
+ context
11
+ );
12
+ return { matches, conditionIsMet: !!matches.length };
13
+ }
14
+ }
@@ -0,0 +1,19 @@
1
+ import Condition from "./condition.js";
2
+ import gridContainsSequence from "../../utils/grid-contains-sequence.js";
3
+
4
+
5
+ export default class InLineCondition extends Condition {
6
+ checkCondition(bgioArguments, rule, payload, context) {
7
+ const { G } = bgioArguments;
8
+ const { target } = payload;
9
+ const parent = G.bank.findParent(payload.target);
10
+
11
+ const { matches: allMatches } = gridContainsSequence(bgioArguments, parent, rule.sequence, context);
12
+
13
+ const matches = allMatches.filter(sequence =>
14
+ sequence.some(space => space === target)
15
+ );
16
+
17
+ return { matches, conditionIsMet: !!matches.length };
18
+ }
19
+ }
@@ -0,0 +1,23 @@
1
+ import Condition from "./condition.js";
2
+ import entityMatches from '../../utils/entity-matches.js'
3
+
4
+ export default class Is extends Condition {
5
+ checkCondition(bgioArguments, rule, { target }, context) {
6
+ if (this.rule.entity && target !== rule.entity) {
7
+ return {
8
+ target,
9
+ conditionIsMet: false,
10
+ }
11
+ }
12
+
13
+ return {
14
+ target,
15
+ conditionIsMet: entityMatches(
16
+ bgioArguments,
17
+ rule.matcher,
18
+ target,
19
+ context
20
+ )
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,9 @@
1
+ import Condition from "./condition.js";
2
+
3
+ export default class IsFull extends Condition {
4
+ checkCondition(bgioArguments, rule, payload, context) {
5
+ return {
6
+ conditionIsMet: payload.target.spaces.every(space => space.entities.length)
7
+ };
8
+ }
9
+ }
@@ -0,0 +1,14 @@
1
+ import Condition from "./condition.js";
2
+ import areThereValidMoves from "../../utils/any-valid-moves.js";
3
+ import getCurrentMoves from "../../utils/get-current-moves.js";
4
+
5
+ export default class NoPossibleMoves extends Condition {
6
+ checkCondition(bgioArguments, _, __, context) {
7
+ return {
8
+ conditionIsMet: !areThereValidMoves(
9
+ bgioArguments,
10
+ getCurrentMoves(bgioArguments, context),
11
+ )
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ import Condition from "./condition.js";
2
+ import checkConditions from "../../utils/check-conditions.js";
3
+
4
+ export default class NotCondition extends Condition {
5
+ checkCondition(bgioArguments, rule, payload, context) {
6
+ const result = checkConditions(
7
+ bgioArguments,
8
+ rule,
9
+ payload,
10
+ context
11
+ )
12
+ return { conditionIsMet: !result.conditionsAreMet }
13
+ }
14
+ }
@@ -0,0 +1,15 @@
1
+ import Condition from "./condition.js";
2
+ import findMetCondition from "../../utils/find-met-condition.js";
3
+
4
+ export default class Or extends Condition {
5
+ checkCondition(bgioArguments, rule, payload, context) {
6
+ const result = findMetCondition(
7
+ bgioArguments,
8
+ rule,
9
+ payload,
10
+ context
11
+ )
12
+ console.log('result', result)
13
+ return { conditionIsMet: !!result }
14
+ }
15
+ }
@@ -0,0 +1,12 @@
1
+ import Condition from "./condition.js";
2
+
3
+ export default class Position extends Condition {
4
+ checkCondition(bgioArguments, rule, { target }) {
5
+ const parent = bgioArguments.G.bank.findParent(target)
6
+ let conditionIsMet
7
+ if (rule.position === 'First') {
8
+ conditionIsMet = parent.entities.indexOf(target) === 0
9
+ }
10
+ return { conditionIsMet }
11
+ }
12
+ }
@@ -0,0 +1,24 @@
1
+ import Condition from "./condition.js";
2
+ import checkConditions from "../../utils/check-conditions.js";
3
+
4
+ export default class SomeCondition extends Condition {
5
+ checkCondition(bgioArguments, rule, { target: targets }, context) {
6
+ const result = targets.find((target) => {
7
+ const loopContext = {
8
+ ...context,
9
+ loopTarget: target
10
+ }
11
+ return checkConditions(
12
+ bgioArguments,
13
+ rule,
14
+ undefined,
15
+ loopContext
16
+ ).conditionsAreMet
17
+ })
18
+
19
+ return {
20
+ conditionIsMet: !!result,
21
+ result
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,94 @@
1
+ import Condition from "./condition.js";
2
+ import checkConditions from "../../utils/check-conditions.js";
3
+ import simulateMove from "../../utils/simulate-move.js";
4
+
5
+ // relying on target order is not perfect;
6
+ // I think we'll want to switch to named arguments
7
+ const argNameMap = {
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(bgioArguments, rule, { target, targets = [target] }, context) {
17
+ const payload = {
18
+ arguments: targets.reduce((acc, target, i) => ({
19
+ ...acc,
20
+ [argNameMap[context.moveInstance.rule.type][i]]: target
21
+ }), {})
22
+ }
23
+
24
+ const simulatedG = simulateMove(
25
+ bgioArguments,
26
+ payload,
27
+ context
28
+ )
29
+
30
+ let simulatedConditionsPayload = {}
31
+ if (target) {
32
+ simulatedConditionsPayload = {
33
+ target: simulatedG.bank.locate(target.entityId)
34
+ }
35
+ } else if (targets) {
36
+ simulatedConditionsPayload = {
37
+ targets: targets.map(t => simulatedG.bank.locate(t.entityId))
38
+ }
39
+ }
40
+
41
+ const conditionResults = checkConditions(
42
+ {
43
+ ...bgioArguments,
44
+ G: simulatedG
45
+ },
46
+ rule,
47
+ simulatedConditionsPayload,
48
+ context
49
+ )
50
+
51
+ const conditionIsMet = conditionResults.conditionsAreMet
52
+
53
+ // optimization: don't bother restoring on failure
54
+ const results = conditionIsMet
55
+ ? restoreReferences(
56
+ conditionResults.results,
57
+ entityId => bgioArguments.G.bank.locate(entityId)
58
+ )
59
+ : conditionResults.results
60
+
61
+ return {
62
+ results,
63
+ conditionIsMet
64
+ }
65
+ }
66
+ }
67
+
68
+ // references to simulated object are useless
69
+ function restoreReferences(obj, getOriginalEntity, seen = new WeakSet()) {
70
+ if (typeof obj !== 'object' || obj === null) {
71
+ return obj;
72
+ }
73
+
74
+ if (seen.has(obj)) {
75
+ return obj;
76
+ }
77
+ seen.add(obj);
78
+
79
+ if (obj.entityId !== undefined) {
80
+ return getOriginalEntity(obj.entityId);
81
+ }
82
+
83
+ if (Array.isArray(obj)) {
84
+ return obj.map(item => restoreReferences(item, getOriginalEntity, seen));
85
+ } else {
86
+ const restored = {};
87
+ for (const key in obj) {
88
+ if (obj.hasOwnProperty(key)) {
89
+ restored[key] = restoreReferences(obj[key], getOriginalEntity, seen);
90
+ }
91
+ }
92
+ return restored;
93
+ }
94
+ }
@@ -0,0 +1,29 @@
1
+ export default class Entity {
2
+ constructor (options, rule, id) {
3
+ if (!options?.fromBank) {
4
+ throw new Error(`Do not create entities directly. Go through the Bank. rule: ${JSON.stringify(rule)}`)
5
+ }
6
+ this.rule = rule
7
+ this.entityId = id
8
+ this.state = {}
9
+ if (this.rule.stateGroups) {
10
+ Object.entries(this.rule.stateGroups)
11
+ .forEach(([stateGroupName, stateGroupValues]) => {
12
+ const stateGroupValueName = options?.initialStateGroups?.[stateGroupName]
13
+ ?? Object.keys(stateGroupValues)[0]
14
+ Object.assign(this.state, stateGroupValues[stateGroupValueName])
15
+ })
16
+ }
17
+ if (this.rule.state) {
18
+ Object.assign(this.state, this.rule.state)
19
+ }
20
+ }
21
+
22
+ get attributes () {
23
+ return {
24
+ ...this.rule,
25
+ ...this,
26
+ ...this.state
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,276 @@
1
+ import cloneDeep from "lodash/cloneDeep.js";
2
+ import find from "lodash/find.js";
3
+ import transformJSON from "../utils/json-transformer.js";
4
+
5
+ // for later when we implement deep replacement
6
+ // { type: 'IsEmpty' } = { type: 'not', conditions: [{ type: 'Contains' }],
7
+ // { matcher: {...blah} } => conditions.push({type: "Is", matcher: {...blah} })
8
+
9
+ // put somewhere
10
+ // const invariantConditionMappings = [
11
+ // {
12
+ // rule: {
13
+ // type: 'BankHasEnough',
14
+ // entity: rule.entity
15
+ // }
16
+ // },
17
+ // ]
18
+
19
+
20
+ // Things we always want, don't need to configure, and
21
+ // want to treat as first-class citizens
22
+ const invariantEntities = [
23
+ {
24
+ type: "Space",
25
+ count: "Infinity",
26
+ },
27
+ {
28
+ type: "Board",
29
+ name: 'sharedBoard'
30
+ },
31
+ {
32
+ name: "playerMarker",
33
+ perPlayer: true,
34
+ count: "Infinity"
35
+ }
36
+ ]
37
+
38
+ function expandEntities (rules) {
39
+ rules.entities = [
40
+ ...invariantEntities,
41
+ ...(rules.entities || [])
42
+ ]
43
+ }
44
+
45
+ function expandInitialPlacements (rules, entities) {
46
+ if (rules.sharedBoard) {
47
+ const sharedBoardPlacements = rules.sharedBoard.map(matcher => ({ entity: matcher, destination: { name: 'sharedBoard' } }))
48
+ if (!rules.initialPlacements) rules.initialPlacements = []
49
+ rules.initialPlacements.unshift(...sharedBoardPlacements)
50
+ }
51
+
52
+ if (rules.personalBoard) {
53
+ entities.push({
54
+ type: "Board",
55
+ name: 'personalBoard',
56
+ perPlayer: true
57
+ })
58
+ const personalBoardPlacements = rules.personalBoard.map(matcher => ({
59
+ entity: matcher,
60
+ destination: {
61
+ name: 'personalBoard'
62
+ }
63
+ }))
64
+ if (!rules.initialPlacements) rules.initialPlacements = []
65
+ rules.initialPlacements.unshift(...personalBoardPlacements)
66
+ }
67
+
68
+ if (rules.initialPlacements) {
69
+ const initialPlacementMoves = rules.initialPlacements.map(placement => {
70
+
71
+ // probably going to need to separate this even in the shorthand. maybe
72
+ // combine, then search entity rule and extract state variables instead?
73
+ const { state, ...matcher } = placement.entity
74
+ const entityDefinition = find(entities, matcher)
75
+
76
+ if (placement.destination.name === 'personalBoard') {
77
+ return {
78
+ type: 'ForEach',
79
+ arguments: {
80
+ targets: {
81
+ type: 'ctxPath',
82
+ path: ['playOrder']
83
+ }
84
+ },
85
+ move: {
86
+ type: 'PlaceNew',
87
+ entity: {
88
+ state,
89
+ conditions: [{
90
+ conditionType: 'Is',
91
+ matcher: {
92
+ ...matcher,
93
+ ...(entityDefinition.perPlayer
94
+ ? {
95
+ player: {
96
+ type: 'contextPath',
97
+ path: ['loopTarget']
98
+ }
99
+ }
100
+ : {}
101
+ )
102
+ },
103
+ }]
104
+ },
105
+ arguments: {
106
+ destination: {
107
+ conditions: [{
108
+ conditionType: 'Is',
109
+ matcher: {
110
+ ...placement.destination,
111
+ player: {
112
+ type: 'contextPath',
113
+ path: ['loopTarget']
114
+ }
115
+ }
116
+ }]
117
+ },
118
+ }
119
+ }
120
+ }
121
+ } else {
122
+ return {
123
+ type: 'PlaceNew',
124
+ entity: {
125
+ state,
126
+ conditions: [{
127
+ conditionType: 'Is',
128
+ matcher,
129
+ }]
130
+ },
131
+ arguments: {
132
+ destination: {
133
+ conditions: [{
134
+ conditionType: 'Is',
135
+ matcher: placement.destination
136
+ }]
137
+ },
138
+ }
139
+ }
140
+ }
141
+ })
142
+ if (!rules.initialMoves) rules.initialMoves = []
143
+ rules.initialMoves.unshift(...initialPlacementMoves)
144
+ delete rules.initialPlacements
145
+ }
146
+ }
147
+
148
+ const keyMappings = [
149
+ ['thatMatches', 'conditions'],
150
+ ['entityType', 'type'],
151
+ ['moveType', 'type'],
152
+ ['endConditions', 'endIf'],
153
+ ]
154
+
155
+ const simpleReplacements = [
156
+ [
157
+ 'isCurrentPlayer',
158
+ {
159
+ conditionType: 'Is',
160
+ matcher: {
161
+ player: {
162
+ type: 'ctxPath',
163
+ path: ['currentPlayer']
164
+ }
165
+ }
166
+ }
167
+ ],
168
+ [
169
+ 'isEmpty',
170
+ {
171
+ conditionType: 'Not',
172
+ conditions: [{conditionType: 'Contains'}]
173
+ }
174
+ ],
175
+ [
176
+ 'ownerOfFirstResultEntity', // might have to more tightly couple this to HasLine condition
177
+ {
178
+ "type": "contextPath",
179
+ "path": ["results", 0, "matches", 0, 0, "entities", 0, "attributes", "player"]
180
+ }
181
+ ]
182
+ ]
183
+
184
+ const transformationRules = [
185
+ {
186
+ test: val => val && typeof val === 'object',
187
+ replace: (val) => {
188
+ keyMappings.forEach(([oldKey, newKey]) => {
189
+ if (val.hasOwnProperty(oldKey)) {
190
+ val[newKey] = val[oldKey]
191
+ delete val[oldKey]
192
+ }
193
+ })
194
+ return val
195
+ }
196
+ },
197
+ {
198
+ test: val => typeof val === 'string',
199
+ replace: (val) => {
200
+ for (let i = 0, len = simpleReplacements.length; i < len; i++) {
201
+ if (val === simpleReplacements[i][0]) {
202
+ return simpleReplacements[i][1]
203
+ }
204
+ }
205
+ return val
206
+ }
207
+ },
208
+ {
209
+ test: val => val?.conditions,
210
+ replace: (val) => {
211
+ if (!Array.isArray(val.conditions)) {
212
+ val.conditions = [val.conditions]
213
+ }
214
+ return val
215
+ }
216
+ },
217
+ {
218
+ test: val => val?.conditions,
219
+ replace: (val) => {
220
+ // make "Is" the default condition
221
+ for (let i = 0, len = val.conditions.length; i < len; i++) {
222
+ if (!val.conditions[i].conditionType) {
223
+ val.conditions[i] = {
224
+ conditionType: 'Is',
225
+ matcher: val.conditions[i]
226
+ }
227
+ }
228
+ }
229
+ return val
230
+ }
231
+ },
232
+ {
233
+ test: val => typeof val?.target === 'string',
234
+ replace: val => ({
235
+ ...val,
236
+ target: {
237
+ conditions: [{
238
+ conditionType: 'Is',
239
+ matcher: {
240
+ name: val.target
241
+ }
242
+ }]
243
+ }
244
+ })
245
+ }
246
+ ]
247
+
248
+ export default function expandGameRules (gameRules) {
249
+ const rules = transformJSON(gameRules, transformationRules)
250
+
251
+ if (!rules.sharedBoard) {
252
+ rules.sharedBoard = rules.entities
253
+ }
254
+
255
+ if (!rules.turn) {
256
+ rules.turn = {
257
+ minMoves: 1,
258
+ maxMoves: 1
259
+ }
260
+ }
261
+
262
+ expandEntities(rules)
263
+ expandInitialPlacements(rules, rules.entities)
264
+
265
+ if (rules.phases) {
266
+ Object.entries(rules.phases).forEach((phaseRule) => {
267
+ expandInitialPlacements(phaseRule, rules.entities)
268
+ })
269
+ }
270
+
271
+ if (gameRules.numPlayers) {
272
+ gameRules.minPlayers = gameRules.maxPlayers = gameRules.numPlayers
273
+ }
274
+
275
+ return rules
276
+ }