board-game-engine 0.0.11 → 1.0.4
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/README.md +138 -1
- package/dist/board-game-engine.cjs +36 -36
- package/dist/board-game-engine.js +36 -36
- package/dist/board-game-engine.min.js +20 -20
- package/dist/board-game-engine.mjs +36 -36
- package/e2e/coverage.js +33 -0
- package/package.json +2 -1
- package/playwright-report/index.html +1 -1
- package/src/client/client.js +42 -38
package/README.md
CHANGED
|
@@ -1 +1,138 @@
|
|
|
1
|
-
|
|
1
|
+
# board-game-engine
|
|
2
|
+
|
|
3
|
+
Runs games written with the early-in-development JSON-based B.A.G.E.L. (Board-based Automated Game Engine Language). Built upon [boardgame.io](https://boardgame.io/)
|
|
4
|
+
|
|
5
|
+
Currently supports enough rules flexibility to describe:
|
|
6
|
+
- Tic Tac Toe
|
|
7
|
+
- Checkers
|
|
8
|
+
- Othello
|
|
9
|
+
- 4-in-a-Row But There is Gravity
|
|
10
|
+
- Crazy Eights
|
|
11
|
+
|
|
12
|
+
[**B.A.G.E.L. docs:**](https://boardgameengine.com/docs/index.html) — reference for moves, conditions, values, shorthand, and examples.
|
|
13
|
+
[**board-game-engine-react**](https://github.com/mnbroatch/board-game-engine-react) — react component wrapping this repo
|
|
14
|
+
[**boardgameengine.com**](https://boardgameengine.com) — Main page
|
|
15
|
+
- Create custom games or edit
|
|
16
|
+
- Create multiplayer lobbies for playing custom B.A.G.E.L. games, also includes client-side editor sandbox. Maintained by author of board-game-engine
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Benefits
|
|
21
|
+
|
|
22
|
+
The B.A.G.E.L. Domain-Specific Langage:
|
|
23
|
+
- declarative
|
|
24
|
+
- safe because there is no custom code to run
|
|
25
|
+
- this means it would be hard for, say, bad LLM output to embed anything too nasty inside "user"-defined games
|
|
26
|
+
- complete enough to produce multiplayer web prototypes
|
|
27
|
+
|
|
28
|
+
Using B.A.G.E.L. in conjunction with this engine also enables UX features:
|
|
29
|
+
- client-side staging for multi-step moves
|
|
30
|
+
- You can for instance select a piece, then select a destination to put it at, all before committing the move
|
|
31
|
+
- Undo steps before commit
|
|
32
|
+
- Highlight currently-playable targets during a move
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install board-game-engine
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Public API
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### `Client`
|
|
48
|
+
|
|
49
|
+
A client that runs a B.A.G.E.L.-defined game
|
|
50
|
+
|
|
51
|
+
**Constructor: `new Client(options)`**
|
|
52
|
+
|
|
53
|
+
| Option | Type | Description |
|
|
54
|
+
|--------|------|-------------|
|
|
55
|
+
| `gameRules` | string | JSON string of the B.A.G.E.L. game definition |
|
|
56
|
+
| `numPlayers` | number | Number of players in client-side game. |
|
|
57
|
+
| `onClientUpdate` | function | Callback after state updates (e.g. to re-render UI). |
|
|
58
|
+
| `debug` | object | boardgame.io debug panel config; e.g. `false`. |
|
|
59
|
+
|
|
60
|
+
**Multiplayer** — For connecting to a remote game, see [Multiplayer](#multiplayer) below. Options such as `server` and `matchID` are passed through to the boardgame.io client; see the [boardgame.io Client API](https://boardgame.io/documentation/#/api/Client) for details.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
**Methods**
|
|
64
|
+
|
|
65
|
+
- **`connect()`** — Connects to the game (local or server), starts the client, subscribes to updates. Returns `this`.
|
|
66
|
+
- **`getState()`** — Returns current state. See [getState() return value](#getstate-return-value) below.
|
|
67
|
+
- **`update()`** — Triggers `onClientUpdate` if set.
|
|
68
|
+
- **`doStep(target)`** — Applies one step of a multi-step move (e.g. “from” then “to”). For B.A.G.E.L. games only.
|
|
69
|
+
- **`undoStep()`** — Undoes the last step of the current move. B.A.G.E.L. games only.
|
|
70
|
+
- **`reset()`** — Clears the current move builder (targets/steps). B.A.G.E.L. games only.
|
|
71
|
+
|
|
72
|
+
**Properties**
|
|
73
|
+
|
|
74
|
+
- **`client`** — boardgame.io client instance
|
|
75
|
+
- **`moveBuilder`** — Mostly for internal use with multi-step moves but could inform UI
|
|
76
|
+
|
|
77
|
+
#### getState() return value
|
|
78
|
+
|
|
79
|
+
`Client` can run normal boardgame.io games, with limited features compared to B.A.G.E.L. games.
|
|
80
|
+
|
|
81
|
+
In non-B.A.G.E.L. games, `doStep(target)` is not used. instead, the boardgame.io client's moves object is returned from getState().
|
|
82
|
+
|
|
83
|
+
- `state` — current game state
|
|
84
|
+
- `gameover` — game-over result when the game has ended
|
|
85
|
+
- `allClickable` — B.A.G.E.L. games only. Clickable targets for the current step of the current move
|
|
86
|
+
- `moves` — non-B.A.G.E.L. games only. From boardgame.io client
|
|
87
|
+
- `currentMoves` — non-B.A.G.E.L. games only. `moves` object filtered to only contain current phase / stage moves. May break for complex turns.
|
|
88
|
+
|
|
89
|
+
**Example**
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
import { Client } from 'board-game-engine'
|
|
93
|
+
|
|
94
|
+
const client = new Client({
|
|
95
|
+
gameRules: JSON.stringify(myGameRules),
|
|
96
|
+
numPlayers: 2,
|
|
97
|
+
onClientUpdate: () => render(client.getState())
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
client.connect()
|
|
101
|
+
// Later, when user picks a target (e.g. a cell or piece):
|
|
102
|
+
client.doStep(target)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Multiplayer
|
|
106
|
+
|
|
107
|
+
To connect to a remote match instead of running client-only, pass `server`, `matchID`, `playerID`, and `credentials`. When `credentials` is absent, the client runs in client-only mode.
|
|
108
|
+
|
|
109
|
+
Note: boardgame.io does not allow adding games to the server instance after it is instantiated. boardgameengine.com hacks around that, but your server will need to have access to any games you intend to play when you boot it up.
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
| Option | Type | Description |
|
|
113
|
+
|--------|------|-------------|
|
|
114
|
+
| `server` | string | URL of gameserver running boardgame.io server instance |
|
|
115
|
+
| `matchID` | string | Match ID. |
|
|
116
|
+
| `gameName` | string | Game name |
|
|
117
|
+
| `playerID` | string | Player ID (first player is `'0'`, next is `'1'`, etc.) |
|
|
118
|
+
| `credentials` | string | Credentials for your server to interpret |
|
|
119
|
+
| `multiplayer` | object | boardgame.io multiplayer transport. Defaults to `SocketIO({ server, socketOpts: { transports: ['websocket', 'polling'] } })`, (SocketIO is exported from `boardgame.io/multiplayer` |
|
|
120
|
+
|
|
121
|
+
All options are required, (only one of `server` or `multiplayer`). For full details on the client options and multiplayer setup, see the [boardgame.io Client API](https://boardgame.io/documentation/#/api/Client).
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
### `gameFactory(gameRules, gameName)`
|
|
126
|
+
|
|
127
|
+
Builds a boardgame.io-compatible game from a B.A.G.E.L. game definition. Useful for preloading games in server code (server games must be preloaded in boardgame.io)
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## How it fits
|
|
132
|
+
|
|
133
|
+
1. You write a B.A.G.E.L. game definition (JSON).
|
|
134
|
+
2. **board-game-engine** turns it into a boardgame.io game
|
|
135
|
+
3. The game runs with boardgame.io (local or server).
|
|
136
|
+
4. `Client` wrapper adds client-side functionality like valid move highlighting
|
|
137
|
+
|
|
138
|
+
For the full language reference, examples, and getting started, see **[https://boardgameengine.com/docs/index.html](https://boardgameengine.com/docs/index.html)**.
|
|
@@ -27071,7 +27071,7 @@ function createPayload(bgioState, moveRule, targets, context) {
|
|
|
27071
27071
|
var Client2 = class {
|
|
27072
27072
|
constructor(options) {
|
|
27073
27073
|
this.options = options;
|
|
27074
|
-
this.game = options.boardgameIOGame || gameFactory(JSON.parse(options.gameRules)
|
|
27074
|
+
this.game = options.boardgameIOGame || gameFactory(JSON.parse(options.gameRules));
|
|
27075
27075
|
if (!options.boardgameIOGame) {
|
|
27076
27076
|
this.moveBuilder = { targets: [], stepIndex: 0, eliminatedMoves: [] };
|
|
27077
27077
|
this.optimisticWinner = null;
|
|
@@ -27085,18 +27085,19 @@ var Client2 = class {
|
|
|
27085
27085
|
collapseOnLoad: true,
|
|
27086
27086
|
impl: Debug
|
|
27087
27087
|
},
|
|
27088
|
-
|
|
27089
|
-
|
|
27090
|
-
|
|
27091
|
-
|
|
27088
|
+
matchID,
|
|
27089
|
+
playerID,
|
|
27090
|
+
credentials,
|
|
27091
|
+
multiplayer = SocketIO({ server, socketOpts: { transports: ["websocket", "polling"] } })
|
|
27092
27092
|
} = this.options;
|
|
27093
27093
|
try {
|
|
27094
|
-
const clientOptions =
|
|
27094
|
+
const clientOptions = !credentials ? { game: this.game, numPlayers, debug } : {
|
|
27095
27095
|
game: this.game,
|
|
27096
|
-
multiplayer
|
|
27097
|
-
matchID
|
|
27098
|
-
playerID
|
|
27099
|
-
credentials
|
|
27096
|
+
multiplayer,
|
|
27097
|
+
matchID,
|
|
27098
|
+
playerID,
|
|
27099
|
+
credentials,
|
|
27100
|
+
numPlayers,
|
|
27100
27101
|
debug
|
|
27101
27102
|
};
|
|
27102
27103
|
this.client = Client(clientOptions);
|
|
@@ -27112,46 +27113,45 @@ var Client2 = class {
|
|
|
27112
27113
|
this.options.onClientUpdate?.();
|
|
27113
27114
|
}
|
|
27114
27115
|
getState() {
|
|
27115
|
-
const
|
|
27116
|
-
if (!
|
|
27116
|
+
const bgioState = this.client?.getState();
|
|
27117
|
+
if (!bgioState) return {};
|
|
27118
|
+
const state = this.options.boardgameIOGame ? bgioState : {
|
|
27119
|
+
...bgioState,
|
|
27120
|
+
G: deserialize(JSON.stringify(bgioState.G), registry)
|
|
27121
|
+
};
|
|
27122
|
+
const gameover = this.optimisticWinner ?? state?.ctx?.gameover;
|
|
27123
|
+
const currentMoves = gameover ? [] : getCurrentMoves(state, this.client);
|
|
27117
27124
|
if (this.options.boardgameIOGame) {
|
|
27118
27125
|
return {
|
|
27119
|
-
state
|
|
27120
|
-
gameover
|
|
27121
|
-
moves: this.client.moves
|
|
27126
|
+
state,
|
|
27127
|
+
gameover,
|
|
27128
|
+
moves: this.client.moves,
|
|
27129
|
+
currentMoves
|
|
27122
27130
|
};
|
|
27123
27131
|
}
|
|
27124
|
-
const
|
|
27125
|
-
...clientState,
|
|
27126
|
-
G: deserialize(JSON.stringify(clientState.G), registry),
|
|
27127
|
-
originalG: clientState.G
|
|
27128
|
-
};
|
|
27129
|
-
const gameover = state?.ctx?.gameover;
|
|
27130
|
-
const moves = !gameover ? Object.entries(getCurrentMoves(state, this.client)).reduce((acc, [moveName, rawMove]) => {
|
|
27132
|
+
const _wrappedMoves = Object.entries(currentMoves).reduce((acc, [moveName, rawMove]) => {
|
|
27131
27133
|
const move = (payload) => {
|
|
27132
27134
|
this.client.moves[moveName](preparePayload(payload));
|
|
27133
27135
|
};
|
|
27134
27136
|
move.moveInstance = rawMove.moveInstance;
|
|
27135
27137
|
return { ...acc, [moveName]: move };
|
|
27136
|
-
}, {})
|
|
27137
|
-
const
|
|
27138
|
-
|
|
27139
|
-
const possibleMoveMeta = possibleMoves.possibleMoveMeta;
|
|
27140
|
-
return { state, gameover, moves, allClickable, possibleMoveMeta };
|
|
27138
|
+
}, {});
|
|
27139
|
+
const { allClickable, _possibleMoveMeta } = getPossibleMoves(state, _wrappedMoves, this.moveBuilder);
|
|
27140
|
+
return { state, gameover, allClickable, _wrappedMoves, _possibleMoveMeta };
|
|
27141
27141
|
}
|
|
27142
27142
|
doStep(_target) {
|
|
27143
27143
|
if (this.options.boardgameIOGame) return;
|
|
27144
|
-
const { state,
|
|
27144
|
+
const { state, _wrappedMoves, _possibleMoveMeta } = this.getState();
|
|
27145
27145
|
const target = _target.abstract ? _target : state.G.bank.locate(_target.entityId);
|
|
27146
|
-
const newEliminated = Object.entries(
|
|
27147
|
-
if (newEliminated.length === Object.keys(
|
|
27146
|
+
const newEliminated = Object.entries(_possibleMoveMeta).filter(([_2, meta]) => !hasTarget(meta.clickableForMove, target)).map(([name]) => name).concat(this.moveBuilder.eliminatedMoves);
|
|
27147
|
+
if (newEliminated.length === Object.keys(_wrappedMoves).length) {
|
|
27148
27148
|
console.error("invalid move with target:", target?.rule);
|
|
27149
27149
|
return;
|
|
27150
27150
|
}
|
|
27151
|
-
const remainingMoveEntries = Object.entries(
|
|
27152
|
-
if (isMoveCompleted(state,
|
|
27151
|
+
const remainingMoveEntries = Object.entries(_possibleMoveMeta).filter(([name]) => !newEliminated.includes(name));
|
|
27152
|
+
if (isMoveCompleted(state, _wrappedMoves, remainingMoveEntries, this.moveBuilder.stepIndex)) {
|
|
27153
27153
|
const [moveName] = remainingMoveEntries[0];
|
|
27154
|
-
const move =
|
|
27154
|
+
const move = _wrappedMoves[moveName];
|
|
27155
27155
|
const payload = createPayload(
|
|
27156
27156
|
state,
|
|
27157
27157
|
move.moveInstance.rule,
|
|
@@ -27194,7 +27194,7 @@ function hasTarget(clickableSet, target) {
|
|
|
27194
27194
|
}
|
|
27195
27195
|
function getPossibleMoves(bgioState, moves, moveBuilder) {
|
|
27196
27196
|
const { eliminatedMoves, stepIndex } = moveBuilder;
|
|
27197
|
-
const
|
|
27197
|
+
const _possibleMoveMeta = {};
|
|
27198
27198
|
const allClickable = /* @__PURE__ */ new Set();
|
|
27199
27199
|
Object.entries(moves).filter(([moveName]) => !eliminatedMoves.includes(moveName)).forEach(([moveName, move]) => {
|
|
27200
27200
|
const moveRule = resolveProperties(bgioState, { ...move.moveInstance.rule, moveName });
|
|
@@ -27212,10 +27212,10 @@ function getPossibleMoves(bgioState, moves, moveBuilder) {
|
|
|
27212
27212
|
const clickableForMove = new Set(
|
|
27213
27213
|
moveIsAllowed && moveSteps?.[stepIndex]?.getClickable(context) || []
|
|
27214
27214
|
);
|
|
27215
|
-
|
|
27215
|
+
_possibleMoveMeta[moveName] = { clickableForMove };
|
|
27216
27216
|
clickableForMove.forEach((entity) => allClickable.add(entity));
|
|
27217
27217
|
});
|
|
27218
|
-
return {
|
|
27218
|
+
return { _possibleMoveMeta, allClickable };
|
|
27219
27219
|
}
|
|
27220
27220
|
function isMoveCompleted(state, moves, remainingMoveEntries, stepIndex) {
|
|
27221
27221
|
return remainingMoveEntries.length === 1 && getSteps(state, moves[remainingMoveEntries[0][0]].moveInstance.rule).length === stepIndex + 1;
|
|
@@ -27071,7 +27071,7 @@ ${message}`);
|
|
|
27071
27071
|
var Client2 = class {
|
|
27072
27072
|
constructor(options) {
|
|
27073
27073
|
this.options = options;
|
|
27074
|
-
this.game = options.boardgameIOGame || gameFactory(JSON.parse(options.gameRules)
|
|
27074
|
+
this.game = options.boardgameIOGame || gameFactory(JSON.parse(options.gameRules));
|
|
27075
27075
|
if (!options.boardgameIOGame) {
|
|
27076
27076
|
this.moveBuilder = { targets: [], stepIndex: 0, eliminatedMoves: [] };
|
|
27077
27077
|
this.optimisticWinner = null;
|
|
@@ -27085,18 +27085,19 @@ ${message}`);
|
|
|
27085
27085
|
collapseOnLoad: true,
|
|
27086
27086
|
impl: Debug
|
|
27087
27087
|
},
|
|
27088
|
-
|
|
27089
|
-
|
|
27090
|
-
|
|
27091
|
-
|
|
27088
|
+
matchID,
|
|
27089
|
+
playerID,
|
|
27090
|
+
credentials,
|
|
27091
|
+
multiplayer = SocketIO({ server, socketOpts: { transports: ["websocket", "polling"] } })
|
|
27092
27092
|
} = this.options;
|
|
27093
27093
|
try {
|
|
27094
|
-
const clientOptions =
|
|
27094
|
+
const clientOptions = !credentials ? { game: this.game, numPlayers, debug } : {
|
|
27095
27095
|
game: this.game,
|
|
27096
|
-
multiplayer
|
|
27097
|
-
matchID
|
|
27098
|
-
playerID
|
|
27099
|
-
credentials
|
|
27096
|
+
multiplayer,
|
|
27097
|
+
matchID,
|
|
27098
|
+
playerID,
|
|
27099
|
+
credentials,
|
|
27100
|
+
numPlayers,
|
|
27100
27101
|
debug
|
|
27101
27102
|
};
|
|
27102
27103
|
this.client = Client(clientOptions);
|
|
@@ -27112,46 +27113,45 @@ ${message}`);
|
|
|
27112
27113
|
this.options.onClientUpdate?.();
|
|
27113
27114
|
}
|
|
27114
27115
|
getState() {
|
|
27115
|
-
const
|
|
27116
|
-
if (!
|
|
27116
|
+
const bgioState = this.client?.getState();
|
|
27117
|
+
if (!bgioState) return {};
|
|
27118
|
+
const state = this.options.boardgameIOGame ? bgioState : {
|
|
27119
|
+
...bgioState,
|
|
27120
|
+
G: deserialize(JSON.stringify(bgioState.G), registry)
|
|
27121
|
+
};
|
|
27122
|
+
const gameover = this.optimisticWinner ?? state?.ctx?.gameover;
|
|
27123
|
+
const currentMoves = gameover ? [] : getCurrentMoves(state, this.client);
|
|
27117
27124
|
if (this.options.boardgameIOGame) {
|
|
27118
27125
|
return {
|
|
27119
|
-
state
|
|
27120
|
-
gameover
|
|
27121
|
-
moves: this.client.moves
|
|
27126
|
+
state,
|
|
27127
|
+
gameover,
|
|
27128
|
+
moves: this.client.moves,
|
|
27129
|
+
currentMoves
|
|
27122
27130
|
};
|
|
27123
27131
|
}
|
|
27124
|
-
const
|
|
27125
|
-
...clientState,
|
|
27126
|
-
G: deserialize(JSON.stringify(clientState.G), registry),
|
|
27127
|
-
originalG: clientState.G
|
|
27128
|
-
};
|
|
27129
|
-
const gameover = state?.ctx?.gameover;
|
|
27130
|
-
const moves = !gameover ? Object.entries(getCurrentMoves(state, this.client)).reduce((acc, [moveName, rawMove]) => {
|
|
27132
|
+
const _wrappedMoves = Object.entries(currentMoves).reduce((acc, [moveName, rawMove]) => {
|
|
27131
27133
|
const move = (payload) => {
|
|
27132
27134
|
this.client.moves[moveName](preparePayload(payload));
|
|
27133
27135
|
};
|
|
27134
27136
|
move.moveInstance = rawMove.moveInstance;
|
|
27135
27137
|
return { ...acc, [moveName]: move };
|
|
27136
|
-
}, {})
|
|
27137
|
-
const
|
|
27138
|
-
|
|
27139
|
-
const possibleMoveMeta = possibleMoves.possibleMoveMeta;
|
|
27140
|
-
return { state, gameover, moves, allClickable, possibleMoveMeta };
|
|
27138
|
+
}, {});
|
|
27139
|
+
const { allClickable, _possibleMoveMeta } = getPossibleMoves(state, _wrappedMoves, this.moveBuilder);
|
|
27140
|
+
return { state, gameover, allClickable, _wrappedMoves, _possibleMoveMeta };
|
|
27141
27141
|
}
|
|
27142
27142
|
doStep(_target) {
|
|
27143
27143
|
if (this.options.boardgameIOGame) return;
|
|
27144
|
-
const { state,
|
|
27144
|
+
const { state, _wrappedMoves, _possibleMoveMeta } = this.getState();
|
|
27145
27145
|
const target = _target.abstract ? _target : state.G.bank.locate(_target.entityId);
|
|
27146
|
-
const newEliminated = Object.entries(
|
|
27147
|
-
if (newEliminated.length === Object.keys(
|
|
27146
|
+
const newEliminated = Object.entries(_possibleMoveMeta).filter(([_2, meta]) => !hasTarget(meta.clickableForMove, target)).map(([name]) => name).concat(this.moveBuilder.eliminatedMoves);
|
|
27147
|
+
if (newEliminated.length === Object.keys(_wrappedMoves).length) {
|
|
27148
27148
|
console.error("invalid move with target:", target?.rule);
|
|
27149
27149
|
return;
|
|
27150
27150
|
}
|
|
27151
|
-
const remainingMoveEntries = Object.entries(
|
|
27152
|
-
if (isMoveCompleted(state,
|
|
27151
|
+
const remainingMoveEntries = Object.entries(_possibleMoveMeta).filter(([name]) => !newEliminated.includes(name));
|
|
27152
|
+
if (isMoveCompleted(state, _wrappedMoves, remainingMoveEntries, this.moveBuilder.stepIndex)) {
|
|
27153
27153
|
const [moveName] = remainingMoveEntries[0];
|
|
27154
|
-
const move =
|
|
27154
|
+
const move = _wrappedMoves[moveName];
|
|
27155
27155
|
const payload = createPayload(
|
|
27156
27156
|
state,
|
|
27157
27157
|
move.moveInstance.rule,
|
|
@@ -27194,7 +27194,7 @@ ${message}`);
|
|
|
27194
27194
|
}
|
|
27195
27195
|
function getPossibleMoves(bgioState, moves, moveBuilder) {
|
|
27196
27196
|
const { eliminatedMoves, stepIndex } = moveBuilder;
|
|
27197
|
-
const
|
|
27197
|
+
const _possibleMoveMeta = {};
|
|
27198
27198
|
const allClickable = /* @__PURE__ */ new Set();
|
|
27199
27199
|
Object.entries(moves).filter(([moveName]) => !eliminatedMoves.includes(moveName)).forEach(([moveName, move]) => {
|
|
27200
27200
|
const moveRule = resolveProperties(bgioState, { ...move.moveInstance.rule, moveName });
|
|
@@ -27212,10 +27212,10 @@ ${message}`);
|
|
|
27212
27212
|
const clickableForMove = new Set(
|
|
27213
27213
|
moveIsAllowed && moveSteps?.[stepIndex]?.getClickable(context) || []
|
|
27214
27214
|
);
|
|
27215
|
-
|
|
27215
|
+
_possibleMoveMeta[moveName] = { clickableForMove };
|
|
27216
27216
|
clickableForMove.forEach((entity) => allClickable.add(entity));
|
|
27217
27217
|
});
|
|
27218
|
-
return {
|
|
27218
|
+
return { _possibleMoveMeta, allClickable };
|
|
27219
27219
|
}
|
|
27220
27220
|
function isMoveCompleted(state, moves, remainingMoveEntries, stepIndex) {
|
|
27221
27221
|
return remainingMoveEntries.length === 1 && getSteps(state, moves[remainingMoveEntries[0][0]].moveInstance.rule).length === stepIndex + 1;
|