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,239 @@
|
|
|
1
|
+
import { serialize } from "wackson";
|
|
2
|
+
import moveFactory from "./move/move-factory.js";
|
|
3
|
+
import Bank from "./bank/bank.js";
|
|
4
|
+
import expandGameRules from "./expand-game-rules.js";
|
|
5
|
+
import getScenarioResults from '../utils/get-scenario-results.js'
|
|
6
|
+
import doMoves from '../utils/do-moves.js'
|
|
7
|
+
import deserializeBgioArguments from '../utils/deserialize-bgio-arguments.js'
|
|
8
|
+
|
|
9
|
+
export default function gameFactory (gameRules, gameName) {
|
|
10
|
+
const game = { name: gameName }
|
|
11
|
+
const rules = expandGameRules(gameRules)
|
|
12
|
+
|
|
13
|
+
game.setup = (bgioArguments) => {
|
|
14
|
+
const { ctx } = bgioArguments
|
|
15
|
+
const initialState = {
|
|
16
|
+
_meta: {
|
|
17
|
+
passedPlayers: [],
|
|
18
|
+
previousPayloads: {},
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const entityDefinitions = expandEntityDefinitions(rules.entities, ctx)
|
|
23
|
+
initialState.bank = new Bank(entityDefinitions)
|
|
24
|
+
initialState.sharedBoard = initialState.bank.getOne(
|
|
25
|
+
bgioArguments,
|
|
26
|
+
{
|
|
27
|
+
conditions: [{
|
|
28
|
+
conditionType: 'Is',
|
|
29
|
+
matcher: { name: "sharedBoard" }
|
|
30
|
+
}]
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if (rules.personalBoard) {
|
|
35
|
+
initialState.personalBoards = bgioArguments.ctx.playOrder.map((playerID) =>
|
|
36
|
+
initialState.bank.getOne(
|
|
37
|
+
bgioArguments,
|
|
38
|
+
{
|
|
39
|
+
conditions: [{
|
|
40
|
+
conditionType: 'Is',
|
|
41
|
+
matcher: {
|
|
42
|
+
name: "personalBoard",
|
|
43
|
+
player: playerID,
|
|
44
|
+
}
|
|
45
|
+
}]
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
rules.initialMoves?.forEach((moveRule) => {
|
|
52
|
+
moveFactory(moveRule, game).moveInstance.doMove(
|
|
53
|
+
{ ...bgioArguments, G: initialState }
|
|
54
|
+
);
|
|
55
|
+
})
|
|
56
|
+
return JSON.parse(serialize(initialState));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (rules.moves) {
|
|
60
|
+
game.moves = createMoves(rules.moves, game)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (rules.turn) {
|
|
64
|
+
game.turn = createTurn(rules.turn, game)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (rules.phases) {
|
|
68
|
+
game.phases = Object.entries(rules.phases).reduce((acc, [name, phaseRule]) => ({
|
|
69
|
+
...acc,
|
|
70
|
+
[name]: createPhase(phaseRule, game)
|
|
71
|
+
}), {})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (rules.endIf) {
|
|
75
|
+
game.endIf = (bgioArguments) => {
|
|
76
|
+
const newBgioArguments = deserializeBgioArguments(bgioArguments)
|
|
77
|
+
return getScenarioResults(newBgioArguments, rules.endIf)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!gameRules.DEBUG_DISABLE_SECRET_STATE) {
|
|
82
|
+
game.playerView = (bgioArguments) => {
|
|
83
|
+
const { G, playerID } = deserializeBgioArguments(bgioArguments)
|
|
84
|
+
Object.values(G.bank.tracker).forEach(((entity) => {
|
|
85
|
+
if (
|
|
86
|
+
entity.rule.contentsHiddenFrom === 'All'
|
|
87
|
+
|| (
|
|
88
|
+
entity.rule.contentsHiddenFrom === 'Others'
|
|
89
|
+
&& (
|
|
90
|
+
playerID !== entity.rule.player
|
|
91
|
+
|| playerID == undefined
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
) {
|
|
95
|
+
// may want to hide entities inside spaces instead?
|
|
96
|
+
if (entity.spaces) {
|
|
97
|
+
entity.spaces = entity.rule.hideLength
|
|
98
|
+
? []
|
|
99
|
+
: entity.spaces.map(() => G.bank.createEntity())
|
|
100
|
+
}
|
|
101
|
+
if (entity.entities) {
|
|
102
|
+
entity.entities = entity.rule.hideLength
|
|
103
|
+
? []
|
|
104
|
+
: entity.entities.map(() => G.bank.createEntity())
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}))
|
|
108
|
+
return JSON.parse(serialize(G))
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return game
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// create a new entity for each variant
|
|
116
|
+
function expandEntityDefinitions (entities, ctx) {
|
|
117
|
+
return entities.reduce((acc, entity) => {
|
|
118
|
+
const entityCopy = { ...entity }
|
|
119
|
+
|
|
120
|
+
// perPlayer flag multiplies number of variants
|
|
121
|
+
if (entityCopy.perPlayer) {
|
|
122
|
+
delete entityCopy.perPlayer
|
|
123
|
+
if (entityCopy.variants) {
|
|
124
|
+
entityCopy.variants =
|
|
125
|
+
(new Array(ctx.numPlayers)).fill().reduce((accu, _, i) => [
|
|
126
|
+
...accu,
|
|
127
|
+
...entityCopy.variants.map(variant => ({ ...variant, player: `${i}` }))
|
|
128
|
+
], [])
|
|
129
|
+
} else {
|
|
130
|
+
entityCopy.variants =
|
|
131
|
+
(new Array(ctx.numPlayers)).fill().map((_, i) => ({ player: `${i}` }))
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// variants becomes new entitites
|
|
136
|
+
if (entityCopy.variants) {
|
|
137
|
+
const variants = entityCopy.variants
|
|
138
|
+
delete entityCopy.variants
|
|
139
|
+
|
|
140
|
+
return [
|
|
141
|
+
...acc,
|
|
142
|
+
...variants.map(variant => ({
|
|
143
|
+
...entityCopy,
|
|
144
|
+
...variant,
|
|
145
|
+
}))
|
|
146
|
+
]
|
|
147
|
+
} else {
|
|
148
|
+
return [
|
|
149
|
+
...acc,
|
|
150
|
+
entityCopy
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
}, [])
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function createTurn (turnRule, game) {
|
|
157
|
+
const turn = { ...turnRule }
|
|
158
|
+
|
|
159
|
+
turn.onBegin = (bgioArguments) => {
|
|
160
|
+
const newBgioArguments = deserializeBgioArguments(bgioArguments)
|
|
161
|
+
const stageRule = turnRule.stages?.[
|
|
162
|
+
newBgioArguments.ctx.activePlayers?.[newBgioArguments.ctx.currentPlayer]
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
// not 100% sure about this logic / timing, but it seems to work so far in terms
|
|
166
|
+
// of letting a player pass and still take a turn later if a move becomes available
|
|
167
|
+
newBgioArguments.G._meta.passedPlayers = newBgioArguments.G._meta.passedPlayers
|
|
168
|
+
.filter(p => p !== newBgioArguments.ctx.currentPlayer)
|
|
169
|
+
|
|
170
|
+
doMoves(newBgioArguments, turnRule.initialMoves, { game })
|
|
171
|
+
doMoves(newBgioArguments, stageRule?.initialMoves, { game })
|
|
172
|
+
|
|
173
|
+
return JSON.parse(serialize(newBgioArguments.G));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (turnRule.stages) {
|
|
177
|
+
Object.entries(turnRule.stages).forEach(([stageName, stageRule]) => {
|
|
178
|
+
if (stageRule.moves) {
|
|
179
|
+
turn.stages[stageName].moves = createMoves(stageRule.moves, game)
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (turnRule.order?.playOrder === 'RotateFirst') {
|
|
185
|
+
turnRule.order.first = () => 0
|
|
186
|
+
turnRule.order.next = ({ ctx }) => (ctx.playOrderPos + 1) % ctx.numPlayers
|
|
187
|
+
turn.order.playOrder = ({ ctx, G }) => {
|
|
188
|
+
return G._meta.isAfterFirstPhase
|
|
189
|
+
? [...ctx.playOrder.slice(1), ctx.playOrder[0]]
|
|
190
|
+
: ctx.playOrder
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return turn
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function createPhase (phaseRule, game) {
|
|
198
|
+
const phase = {...phaseRule}
|
|
199
|
+
if (phaseRule.turn) {
|
|
200
|
+
phase.turn = createTurn(phaseRule.turn, game)
|
|
201
|
+
}
|
|
202
|
+
if (phaseRule.moves) {
|
|
203
|
+
phase.moves = createMoves(phaseRule.moves, game)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
phase.onBegin = (bgioArguments) => {
|
|
207
|
+
const newBgioArguments = deserializeBgioArguments(bgioArguments)
|
|
208
|
+
doMoves(newBgioArguments, phaseRule.initialMoves, { game })
|
|
209
|
+
newBgioArguments.G._meta.currentPhaseHasBeenSetUp = true
|
|
210
|
+
newBgioArguments.G._meta.nextPhase = phaseRule.next
|
|
211
|
+
return JSON.parse(serialize(newBgioArguments.G));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (phaseRule.endIf) {
|
|
215
|
+
phase.endIf = (bgioArguments) => {
|
|
216
|
+
const newBgioArguments = deserializeBgioArguments(bgioArguments)
|
|
217
|
+
if (newBgioArguments.G._meta.currentPhaseHasBeenSetUp) {
|
|
218
|
+
const result = getScenarioResults(newBgioArguments, phaseRule.endIf)
|
|
219
|
+
if (result) {
|
|
220
|
+
return result
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
phase.onEnd = ({ G }) => {
|
|
227
|
+
G._meta.currentPhaseHasBeenSetUp = false
|
|
228
|
+
G._meta.isAfterFirstPhase = true
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return phase
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function createMoves (moves, game) {
|
|
235
|
+
return Object.entries(moves).reduce((acc, [name, moveDefinition]) => ({
|
|
236
|
+
...acc,
|
|
237
|
+
[name]: moveFactory({ ...moveDefinition, name }, game)
|
|
238
|
+
}), {})
|
|
239
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Move from "./move.js";
|
|
2
|
+
import { getMoveInstance } from "./move-factory.js";
|
|
3
|
+
|
|
4
|
+
export default class ForEach extends Move {
|
|
5
|
+
do(bgioArguments, rule, { arguments: { targets } }, context) {
|
|
6
|
+
targets.forEach((target) => {
|
|
7
|
+
const loopContext = {
|
|
8
|
+
...context,
|
|
9
|
+
loopTarget: target
|
|
10
|
+
}
|
|
11
|
+
getMoveInstance(rule.move).doMove(
|
|
12
|
+
bgioArguments,
|
|
13
|
+
undefined,
|
|
14
|
+
loopContext
|
|
15
|
+
)
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Move from "./move.js";
|
|
2
|
+
|
|
3
|
+
export default class MoveEntity extends Move {
|
|
4
|
+
do(bgioArguments, rule, { arguments: { entity, destination } }) {
|
|
5
|
+
// todo: move all such things to always be multiple
|
|
6
|
+
if (Array.isArray(entity)) {
|
|
7
|
+
entity.forEach((e) => {
|
|
8
|
+
bgioArguments.G.bank.findParent(e)?.remove(e)
|
|
9
|
+
destination.placeEntity(e, rule.position)
|
|
10
|
+
})
|
|
11
|
+
} else {
|
|
12
|
+
bgioArguments.G.bank.findParent(entity)?.remove(entity)
|
|
13
|
+
destination.placeEntity(entity, rule.position)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { serialize, deserialize } from 'wackson'
|
|
2
|
+
import { INVALID_MOVE } from 'boardgame.io/dist/cjs/core.js';
|
|
3
|
+
import { registry } from '../../registry.js'
|
|
4
|
+
import deserializeBgioArguments from '../../utils/deserialize-bgio-arguments.js'
|
|
5
|
+
import MoveEntity from "./move-entity.js";
|
|
6
|
+
import RemoveEntity from "./remove-entity.js";
|
|
7
|
+
import PlaceNew from "./place-new.js";
|
|
8
|
+
import TakeFrom from "./take-from.js";
|
|
9
|
+
import SetState from "./set-state.js";
|
|
10
|
+
import SetActivePlayers from "./set-active-players.js";
|
|
11
|
+
import EndTurn from "./end-turn.js";
|
|
12
|
+
import PassTurn from "./pass-turn.js";
|
|
13
|
+
import ForEach from "./for-each.js";
|
|
14
|
+
import Pass from "./pass.js";
|
|
15
|
+
import Shuffle from "./shuffle.js";
|
|
16
|
+
|
|
17
|
+
export default function moveFactory(moveRule, game) {
|
|
18
|
+
const moveInstance = getMoveInstance(moveRule)
|
|
19
|
+
|
|
20
|
+
// accepts serialized G and payload, returns serialized
|
|
21
|
+
const compatibleMove = function (
|
|
22
|
+
bgioArguments,
|
|
23
|
+
serializablePayload
|
|
24
|
+
) {
|
|
25
|
+
const newBgioArguments = deserializeBgioArguments(bgioArguments)
|
|
26
|
+
const { G } = newBgioArguments
|
|
27
|
+
const payload = revivePayload(serializablePayload, G)
|
|
28
|
+
const context = { moveInstance, game }
|
|
29
|
+
const moveConditionResults = moveInstance.doMove(newBgioArguments, payload, context)
|
|
30
|
+
|
|
31
|
+
context.moveConditionResults = [moveConditionResults]
|
|
32
|
+
|
|
33
|
+
if (moveConditionResults !== INVALID_MOVE && moveRule.then) {
|
|
34
|
+
for (let automaticMoveRule of moveRule.then) {
|
|
35
|
+
const result = getMoveInstance(automaticMoveRule).doMove(
|
|
36
|
+
newBgioArguments,
|
|
37
|
+
{},
|
|
38
|
+
{...context} // spread here so prevArguments doesn't change for sibling
|
|
39
|
+
)
|
|
40
|
+
context.moveConditionResults.push(result)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return JSON.parse(serialize(G))
|
|
45
|
+
}
|
|
46
|
+
compatibleMove.moveInstance = moveInstance
|
|
47
|
+
return compatibleMove
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function revivePayload (serializablePayload, G) {
|
|
51
|
+
if (serializablePayload) {
|
|
52
|
+
const payload = deserialize(JSON.stringify(serializablePayload), registry)
|
|
53
|
+
payload.arguments =
|
|
54
|
+
Object.entries(payload.arguments).reduce((acc, [key, argOrEntityId]) => ({
|
|
55
|
+
...acc,
|
|
56
|
+
[key]: typeof argOrEntityId === 'number' ? G.bank.locate(argOrEntityId) : argOrEntityId
|
|
57
|
+
}), {})
|
|
58
|
+
return payload
|
|
59
|
+
} else {
|
|
60
|
+
return serializablePayload
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getMoveInstance (moveRule) {
|
|
65
|
+
switch (moveRule.type) {
|
|
66
|
+
case 'MoveEntity':
|
|
67
|
+
return new MoveEntity(moveRule);
|
|
68
|
+
case 'PlaceNew':
|
|
69
|
+
return new PlaceNew(moveRule);
|
|
70
|
+
case 'RemoveEntity':
|
|
71
|
+
return new RemoveEntity(moveRule);
|
|
72
|
+
case 'TakeFrom':
|
|
73
|
+
return new TakeFrom(moveRule);
|
|
74
|
+
case 'SetState':
|
|
75
|
+
return new SetState(moveRule);
|
|
76
|
+
case 'ForEach':
|
|
77
|
+
return new ForEach(moveRule);
|
|
78
|
+
case 'Pass':
|
|
79
|
+
return new Pass(moveRule);
|
|
80
|
+
case 'Shuffle':
|
|
81
|
+
return new Shuffle(moveRule);
|
|
82
|
+
case 'SetActivePlayers':
|
|
83
|
+
return new SetActivePlayers(moveRule);
|
|
84
|
+
case 'EndTurn':
|
|
85
|
+
return new EndTurn(moveRule);
|
|
86
|
+
case 'PassTurn':
|
|
87
|
+
return new PassTurn(moveRule);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { INVALID_MOVE } from 'boardgame.io/dist/cjs/core.js';
|
|
2
|
+
import checkConditions from "../../utils/check-conditions.js";
|
|
3
|
+
import resolveProperties from "../../utils/resolve-properties.js";
|
|
4
|
+
|
|
5
|
+
export default class Move {
|
|
6
|
+
constructor (rule) {
|
|
7
|
+
this.rule = this.transformRule(rule)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
checkValidity (bgioArguments, payload, context) {
|
|
11
|
+
const argRuleEntries = Object.entries(this.rule.arguments ?? {})
|
|
12
|
+
|
|
13
|
+
if (
|
|
14
|
+
!argRuleEntries.every(([argName]) => {
|
|
15
|
+
const arg = payload.arguments[argName]
|
|
16
|
+
return arg !== undefined && (!Array.isArray(arg) || arg.length)
|
|
17
|
+
})
|
|
18
|
+
) {
|
|
19
|
+
// not the best return value but we don't want to do expensive checks
|
|
20
|
+
// when we know the operation is doomed. At least for now.
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const argumentResults = {}
|
|
25
|
+
|
|
26
|
+
for (let i = 0, len = argRuleEntries.length; i < len; i++) {
|
|
27
|
+
const [argName, argRule] = argRuleEntries[i]
|
|
28
|
+
const payloadArg = payload.arguments[argName]
|
|
29
|
+
const args = Array.isArray(payloadArg)
|
|
30
|
+
? payloadArg
|
|
31
|
+
: [payloadArg]
|
|
32
|
+
|
|
33
|
+
const argResults = []
|
|
34
|
+
for (let j = 0, len = args.length; j < len; j++) {
|
|
35
|
+
const arg = args[j]
|
|
36
|
+
const result = checkConditions(
|
|
37
|
+
bgioArguments,
|
|
38
|
+
argRule,
|
|
39
|
+
{ target: arg },
|
|
40
|
+
{ ...context, moveArguments: payload.arguments }
|
|
41
|
+
)
|
|
42
|
+
argResults.push(result)
|
|
43
|
+
if (!result.conditionsAreMet) {
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const argConditionsAreMet = argResults.at(-1).conditionsAreMet
|
|
49
|
+
argumentResults[argName] = {
|
|
50
|
+
results: argResults,
|
|
51
|
+
conditionsAreMet: argConditionsAreMet
|
|
52
|
+
}
|
|
53
|
+
if (!argConditionsAreMet) {
|
|
54
|
+
break
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const moveResults = checkConditions(
|
|
59
|
+
bgioArguments,
|
|
60
|
+
{ conditions: this.rule.conditions },
|
|
61
|
+
undefined,
|
|
62
|
+
{ ...context, moveArguments: payload.arguments }
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
argumentResults,
|
|
67
|
+
moveResults,
|
|
68
|
+
conditionsAreMet: moveResults.conditionsAreMet
|
|
69
|
+
&& Object.values(argumentResults).every(a => a.conditionsAreMet)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
isValid (bgioArguments, payload, context) {
|
|
74
|
+
const conditionResults = this.checkValidity(
|
|
75
|
+
bgioArguments,
|
|
76
|
+
payload,
|
|
77
|
+
context
|
|
78
|
+
)
|
|
79
|
+
return conditionResults.conditionsAreMet
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
doMove (bgioArguments, payload, context, { skipCheck = false } = {}) {
|
|
83
|
+
const rule = resolveProperties(
|
|
84
|
+
bgioArguments,
|
|
85
|
+
this.rule,
|
|
86
|
+
context
|
|
87
|
+
)
|
|
88
|
+
const resolvedPayload = {
|
|
89
|
+
...payload,
|
|
90
|
+
arguments: Object.entries(rule.arguments ?? {})
|
|
91
|
+
.reduce((acc, [argName, arg]) => {
|
|
92
|
+
return {
|
|
93
|
+
...acc,
|
|
94
|
+
[argName]: payload?.arguments?.[argName] ?? arg
|
|
95
|
+
};
|
|
96
|
+
}, {})
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// does not store automatic moves, do we need that?
|
|
100
|
+
if (rule.name) {
|
|
101
|
+
bgioArguments.G._meta.previousPayloads[rule.name] = resolvedPayload
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let conditionResults
|
|
105
|
+
if (!skipCheck) {
|
|
106
|
+
conditionResults = this.checkValidity(bgioArguments, resolvedPayload, context)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!skipCheck && !conditionResults.conditionsAreMet) {
|
|
110
|
+
return INVALID_MOVE
|
|
111
|
+
} else {
|
|
112
|
+
this.do(bgioArguments, rule, resolvedPayload, context)
|
|
113
|
+
if (context) {
|
|
114
|
+
context.previousArguments = resolvedPayload.arguments
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { conditionResults }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
transformRule (rule) {
|
|
122
|
+
const args = rule.arguments
|
|
123
|
+
for (let key in args) {
|
|
124
|
+
const arg = args[key]
|
|
125
|
+
if (!arg.playerChoice) {
|
|
126
|
+
arg.resolveAsEntity = true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return rule
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Move from "./move.js";
|
|
2
|
+
|
|
3
|
+
export default class PassTurn extends Move {
|
|
4
|
+
do(bgioArguments) {
|
|
5
|
+
if (bgioArguments.G._meta.passedPlayers.length < bgioArguments.ctx.numPlayers) {
|
|
6
|
+
bgioArguments.G._meta.passedPlayers.push(bgioArguments.ctx.currentPlayer)
|
|
7
|
+
bgioArguments.events.pass()
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Move from "./move.js";
|
|
2
|
+
|
|
3
|
+
export default class PlaceNew extends Move {
|
|
4
|
+
do(bgioArguments, rule, { arguments: { destination } }, context) {
|
|
5
|
+
const entities = rule.matchMultiple
|
|
6
|
+
? bgioArguments.G.bank.getMultiple(
|
|
7
|
+
bgioArguments,
|
|
8
|
+
{
|
|
9
|
+
...rule.entity,
|
|
10
|
+
conditions: [
|
|
11
|
+
...(rule.entity?.conditions || []),
|
|
12
|
+
...(rule.conditions || []),
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
rule.count,
|
|
16
|
+
context
|
|
17
|
+
)
|
|
18
|
+
: [bgioArguments.G.bank.getOne(
|
|
19
|
+
bgioArguments,
|
|
20
|
+
{
|
|
21
|
+
...rule.entity,
|
|
22
|
+
conditions: [
|
|
23
|
+
...(rule.entity?.conditions || []),
|
|
24
|
+
...(rule.conditions || []),
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
context
|
|
28
|
+
)]
|
|
29
|
+
entities.forEach((entity) => {
|
|
30
|
+
destination.placeEntity(entity, rule.position)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Move from "./move.js";
|
|
2
|
+
import doMoves from '../../utils/do-moves.js'
|
|
3
|
+
|
|
4
|
+
export default class SetActivePlayers extends Move {
|
|
5
|
+
do(bgioArguments, rule, _, context) {
|
|
6
|
+
bgioArguments.events.setActivePlayers(rule.options)
|
|
7
|
+
|
|
8
|
+
// this is going to need to be expanded to handle more complex things
|
|
9
|
+
// than "move current player to new stage"
|
|
10
|
+
const phaseName = bgioArguments.ctx.phase
|
|
11
|
+
const stageName = rule.options.currentPlayer?.stage
|
|
12
|
+
const phaseOrRoot = context.game.phases?.[phaseName] ?? context.game
|
|
13
|
+
const stage = phaseOrRoot?.turn?.stages?.[stageName]
|
|
14
|
+
doMoves(
|
|
15
|
+
bgioArguments,
|
|
16
|
+
stage?.initialMoves,
|
|
17
|
+
{
|
|
18
|
+
...context,
|
|
19
|
+
stageName,
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Move from "./move.js";
|
|
2
|
+
|
|
3
|
+
// todo: invariant conditions like "is one of the allowed values"
|
|
4
|
+
export default class SetState extends Move {
|
|
5
|
+
do(_, __, { arguments: { entity, state } }) {
|
|
6
|
+
console.log('entity', entity)
|
|
7
|
+
console.log('state', state)
|
|
8
|
+
entity.state = {
|
|
9
|
+
...entity.state,
|
|
10
|
+
[state.property]: state.value,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Entity from "../entity.js";
|
|
2
|
+
|
|
3
|
+
export default class Space extends Entity {
|
|
4
|
+
constructor (...args) {
|
|
5
|
+
super(...args)
|
|
6
|
+
this.entities = []
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
placeEntity (entity, position = 'Last') {
|
|
10
|
+
if (position === 'Last') {
|
|
11
|
+
this.entities.push(entity);
|
|
12
|
+
} else if (position === 'First') {
|
|
13
|
+
this.entities.unshift(entity);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
remove (entity) {
|
|
18
|
+
this.entities.splice(this.entities.indexOf(entity), 1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
takeOne (position = 'First') {
|
|
22
|
+
if (position === 'First') {
|
|
23
|
+
return this.entities.splice(0, 1)[0];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
isEmpty() {
|
|
28
|
+
return this.entities.length === 0;
|
|
29
|
+
}
|
|
30
|
+
}
|