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.
- package/board-game-engine.test.js +2 -41
- package/dist/board-game-engine.js +32556 -5074
- package/dist/board-game-engine.min.js +2 -1
- package/dist/board-game-engine.min.js.LICENSE.txt +14 -0
- package/package.json +6 -3
- package/patch-bgio.cjs +23 -0
- package/src/client/client.js +287 -0
- package/src/game-factory/bank/bank-slot.js +69 -0
- package/src/game-factory/bank/bank.js +114 -0
- package/src/game-factory/board.js +3 -0
- package/src/game-factory/condition/condition-factory.js +52 -0
- package/src/game-factory/condition/condition.js +39 -0
- package/src/game-factory/condition/contains-condition.js +21 -0
- package/src/game-factory/condition/contains-same-condition.js +27 -0
- package/src/game-factory/condition/evaluate-condition.js +18 -0
- package/src/game-factory/condition/every-condition.js +25 -0
- package/src/game-factory/condition/has-line-condition.js +14 -0
- package/src/game-factory/condition/in-line-condition.js +19 -0
- package/src/game-factory/condition/is-condition.js +23 -0
- package/src/game-factory/condition/is-full-condition.js +9 -0
- package/src/game-factory/condition/no-possible-moves-condition.js +14 -0
- package/src/game-factory/condition/not-condition.js +14 -0
- package/src/game-factory/condition/or-condition.js +15 -0
- package/src/game-factory/condition/position-condition.js +12 -0
- package/src/game-factory/condition/some-condition.js +24 -0
- package/src/game-factory/condition/would-condition.js +94 -0
- package/src/game-factory/entity.js +29 -0
- package/src/game-factory/expand-game-rules.js +276 -0
- package/src/game-factory/game-factory.js +239 -0
- package/src/game-factory/move/end-turn.js +7 -0
- package/src/game-factory/move/for-each.js +18 -0
- package/src/game-factory/move/index.js +7 -0
- package/src/game-factory/move/move-entity.js +16 -0
- package/src/game-factory/move/move-factory.js +89 -0
- package/src/game-factory/move/move.js +131 -0
- package/src/game-factory/move/pass-turn.js +10 -0
- package/src/game-factory/move/pass.js +7 -0
- package/src/game-factory/move/place-new.js +33 -0
- package/src/game-factory/move/remove-entity.js +7 -0
- package/src/game-factory/move/set-active-players.js +23 -0
- package/src/game-factory/move/set-state.js +13 -0
- package/src/game-factory/move/shuffle.js +7 -0
- package/src/game-factory/move/take-from.js +7 -0
- package/src/game-factory/space/space.js +30 -0
- package/src/game-factory/space-group/grid.js +43 -0
- package/src/game-factory/space-group/space-group.js +29 -0
- package/src/index.js +2 -0
- package/src/registry.js +17 -0
- package/src/utils/any-valid-moves.js +157 -0
- package/src/utils/check-conditions.js +28 -0
- package/src/utils/create-payload.js +16 -0
- package/src/utils/deserialize-bgio-arguments.js +8 -0
- package/src/utils/do-moves.js +18 -0
- package/src/utils/entity-matches.js +20 -0
- package/src/utils/find-met-condition.js +22 -0
- package/src/utils/get-current-moves.js +12 -0
- package/src/utils/get-scenario-results.js +23 -0
- package/src/utils/get-steps.js +28 -0
- package/src/utils/get.js +25 -0
- package/src/utils/grid-contains-sequence.js +226 -0
- package/src/utils/json-transformer.js +12 -0
- package/src/utils/prepare-payload.js +16 -0
- package/src/utils/resolve-entity.js +9 -0
- package/src/utils/resolve-expression.js +10 -0
- package/src/utils/resolve-properties.js +157 -0
- package/src/utils/simulate-move.js +25 -0
- package/webpack.config.js +4 -1
- package/src/action/action-factory.js +0 -13
- package/src/action/action.js +0 -34
- package/src/action/move-piece-action.js +0 -11
- package/src/action/select-piece-action.js +0 -23
- package/src/action/swap-action.js +0 -14
- package/src/board/board-factory.js +0 -12
- package/src/board/board-group.js +0 -9
- package/src/board/board.js +0 -11
- package/src/board/grid.js +0 -52
- package/src/board/stack.js +0 -16
- package/src/condition/action-type-matches-condition.js +0 -7
- package/src/condition/bingo-condition.js +0 -50
- package/src/condition/blackout-condition.js +0 -9
- package/src/condition/condition-factory.js +0 -31
- package/src/condition/condition.js +0 -9
- package/src/condition/contains-condition.js +0 -14
- package/src/condition/does-not-contain-condition.js +0 -15
- package/src/condition/is-valid-player-condition.js +0 -7
- package/src/condition/piece-matches-condition.js +0 -23
- package/src/condition/relative-move-condition.js +0 -16
- package/src/condition/some-condition.js +0 -7
- package/src/game/game.ts +0 -362
- package/src/index.ts +0 -1
- package/src/piece/piece-factory.js +0 -5
- package/src/piece/piece.ts +0 -25
- package/src/piece/pile.js +0 -70
- package/src/player/player.ts +0 -13
- package/src/registry.ts +0 -51
- package/src/round/round-factory.js +0 -7
- package/src/round/round.js +0 -41
- package/src/round/sequential-player-turn.js +0 -18
- package/src/space/space.ts +0 -22
- package/src/utils/find-value-path.js +0 -37
- package/src/utils/resolve-board.ts +0 -38
- 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,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
|
+
}
|