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
package/src/game/game.ts
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
import cloneDeep from "lodash/cloneDeep.js";
|
|
2
|
-
import matches from "lodash/matches.js";
|
|
3
|
-
import merge from "lodash/merge.js";
|
|
4
|
-
import get from "lodash/get.js";
|
|
5
|
-
import boardFactory from "../board/board-factory.js";
|
|
6
|
-
import Player from "../player/player";
|
|
7
|
-
import roundFactory from "../round/round-factory.js";
|
|
8
|
-
import conditionFactory from "../condition/condition-factory.js";
|
|
9
|
-
import actionFactory from "../action/action-factory.js";
|
|
10
|
-
import Pile from "../piece/pile.js";
|
|
11
|
-
import findValuePath from "../utils/find-value-path.js";
|
|
12
|
-
import { serialize, deserialize } from "wackson";
|
|
13
|
-
import { registry } from "../registry.ts";
|
|
14
|
-
|
|
15
|
-
export interface GameRules {
|
|
16
|
-
round: any;
|
|
17
|
-
player: any;
|
|
18
|
-
pieces: any[];
|
|
19
|
-
sharedBoard: Record<string, any>;
|
|
20
|
-
personalBoard?: Record<string, any>;
|
|
21
|
-
initialPlacements?: any[];
|
|
22
|
-
winCondition: any;
|
|
23
|
-
drawCondition?: any;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface GameOptions {
|
|
27
|
-
playerCount: number;
|
|
28
|
-
[key: string]: any;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface Move {
|
|
32
|
-
playerId: number;
|
|
33
|
-
type?: string;
|
|
34
|
-
piece?: {
|
|
35
|
-
id?: string;
|
|
36
|
-
name?: string;
|
|
37
|
-
player?: {
|
|
38
|
-
id: number;
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
board?: string[];
|
|
42
|
-
[key: string]: any;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface GameState {
|
|
46
|
-
currentPhaseIndex: number;
|
|
47
|
-
currentRoundIndex: number;
|
|
48
|
-
context: Record<string, any>;
|
|
49
|
-
status: 'waiting' | 'active' | 'done';
|
|
50
|
-
winner: Player | null;
|
|
51
|
-
sharedBoard: Record<string, any>;
|
|
52
|
-
players: Player[];
|
|
53
|
-
personalBoards: Record<string, any>;
|
|
54
|
-
pieces: Pile[];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function initializeState(state: GameState, rules: GameRules): GameState {
|
|
58
|
-
expandRules(rules)
|
|
59
|
-
state.sharedBoard = Object.entries(rules.sharedBoard).reduce(
|
|
60
|
-
(acc, [id, board]) => ({
|
|
61
|
-
...acc,
|
|
62
|
-
[id]: boardFactory({ ...board, path: ["sharedBoard", id] }),
|
|
63
|
-
}),
|
|
64
|
-
{},
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
state.personalBoards = state.players.reduce(
|
|
68
|
-
(acc, player) => ({
|
|
69
|
-
...acc,
|
|
70
|
-
[player.id]: Object.entries(rules.personalBoard || []).reduce(
|
|
71
|
-
(acc, [id, board]) => ({
|
|
72
|
-
...acc,
|
|
73
|
-
[id]: boardFactory(
|
|
74
|
-
{ ...board, path: ["sharedBoard", id] },
|
|
75
|
-
{ player },
|
|
76
|
-
),
|
|
77
|
-
}),
|
|
78
|
-
{},
|
|
79
|
-
),
|
|
80
|
-
}),
|
|
81
|
-
{},
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
state.pieces = rules.pieces.reduce((acc, pieceRule) => {
|
|
85
|
-
if (pieceRule.perPlayer) {
|
|
86
|
-
return [
|
|
87
|
-
...acc,
|
|
88
|
-
...state.players.map((player) => new Pile(pieceRule, { player })),
|
|
89
|
-
];
|
|
90
|
-
} else {
|
|
91
|
-
return [...acc, new Pile(pieceRule)];
|
|
92
|
-
}
|
|
93
|
-
}, []);
|
|
94
|
-
|
|
95
|
-
// Apply initial placements
|
|
96
|
-
rules.initialPlacements?.forEach((placement) => {
|
|
97
|
-
if (placement.perPlayer) {
|
|
98
|
-
state.players.forEach((player) => {
|
|
99
|
-
doInitialPlacement(placement, player, {
|
|
100
|
-
sharedBoard: state.sharedBoard,
|
|
101
|
-
personalBoards: state.personalBoards,
|
|
102
|
-
pieces: state.pieces,
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
} else {
|
|
106
|
-
doInitialPlacement(placement, null, {
|
|
107
|
-
sharedBoard: state.sharedBoard,
|
|
108
|
-
personalBoards: state.personalBoards,
|
|
109
|
-
pieces: state.pieces,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const currentRoundRule = rules.round.phases
|
|
115
|
-
? rules.round.phases[0]
|
|
116
|
-
: rules.round;
|
|
117
|
-
state.currentRound = roundFactory(currentRoundRule, state);
|
|
118
|
-
|
|
119
|
-
state.status = 'active'
|
|
120
|
-
|
|
121
|
-
return state;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function checkWinner(state: GameState, rules: GameRules): Player | null {
|
|
125
|
-
return (
|
|
126
|
-
state.players.find((player) => {
|
|
127
|
-
const winCondition = {
|
|
128
|
-
...rules.winCondition,
|
|
129
|
-
piece: {
|
|
130
|
-
...rules.winCondition.piece,
|
|
131
|
-
player,
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
const condition = conditionFactory(winCondition, state);
|
|
135
|
-
return condition.isMet();
|
|
136
|
-
}) || null
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function checkDraw(state: GameState, rules: GameRules): boolean {
|
|
141
|
-
return !!(
|
|
142
|
-
rules.drawCondition && conditionFactory(rules.drawCondition, state).isMet()
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function expandActionPayload(move: Move, state: GameState, rules: GameRules) {
|
|
147
|
-
const player = state.players.find((p) => p.id === move.playerId);
|
|
148
|
-
if (!player && move.type !== 'join' && move.type !== 'start') {
|
|
149
|
-
throw new Error("Invalid player ID");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const pieceRule = rules.pieces.find((piece) => piece.name === move.piece?.name);
|
|
153
|
-
|
|
154
|
-
const expandedMove = cloneDeep(move)
|
|
155
|
-
if (pieceRule?.perPlayer && !expandedMove.player) {
|
|
156
|
-
expandedMove.piece.player = { id: player.id };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (!expandedMove.board) {
|
|
160
|
-
expandedMove.board = getBoardPathContaining(expandedMove.piece, state);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
expandedMove.board = normalizePath(expandedMove.board, { player });
|
|
164
|
-
|
|
165
|
-
return expandedMove;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function handlePlayerJoin(state, rules, move) {
|
|
169
|
-
if (state.players.length < rules.playerCountRange[1]) {
|
|
170
|
-
state.players.push(new Player(rules.player, state.players.length, move.playerId))
|
|
171
|
-
} else {
|
|
172
|
-
throw new Error('game is full!')
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function handleStartGame(state,rules) {
|
|
177
|
-
if (state.players.length >= rules.playerCountRange[0]) {
|
|
178
|
-
initializeState(state, rules)
|
|
179
|
-
} else {
|
|
180
|
-
throw new Error('not enough players')
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export function makeMove(
|
|
185
|
-
rules: GameRules,
|
|
186
|
-
_state?: GameState,
|
|
187
|
-
move?: Move,
|
|
188
|
-
): GameState {
|
|
189
|
-
if (!_state) {
|
|
190
|
-
return serialize({
|
|
191
|
-
context: {},
|
|
192
|
-
winner: null,
|
|
193
|
-
status: 'waiting',
|
|
194
|
-
players: [],
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const state = deserializeState(_state)
|
|
199
|
-
|
|
200
|
-
if (state.status === 'done') {
|
|
201
|
-
throw new Error("Game is over!");
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (move === undefined) {
|
|
205
|
-
return serialize(state);
|
|
206
|
-
} else if (move?.type === 'join') {
|
|
207
|
-
handlePlayerJoin(state, rules, move)
|
|
208
|
-
return serialize(state)
|
|
209
|
-
} else if (move.type === 'start') {
|
|
210
|
-
handleStartGame(state, rules)
|
|
211
|
-
return serialize(state)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// Expand the move payload with defaults and normalizations
|
|
216
|
-
const expandedMove = expandActionPayload(move, state, rules);
|
|
217
|
-
|
|
218
|
-
const round = state.currentRound
|
|
219
|
-
|
|
220
|
-
round.doAction(expandedMove);
|
|
221
|
-
// Check if round is over and advance if needed
|
|
222
|
-
if (round.isOver(state)) {
|
|
223
|
-
if (rules.round.phases) {
|
|
224
|
-
// Get current phase rule
|
|
225
|
-
const currentPhaseRule = rules.round.phases[state.currentRound.currentPhaseIndex];
|
|
226
|
-
|
|
227
|
-
// Create round for current phase
|
|
228
|
-
const phaseRound = roundFactory(currentPhaseRule, state);
|
|
229
|
-
|
|
230
|
-
// If this phase is over, move to next phase
|
|
231
|
-
if (phaseRound.isOver(state)) {
|
|
232
|
-
state.currentRound.currentPhaseIndex++;
|
|
233
|
-
|
|
234
|
-
// If we've completed all phases, start new round
|
|
235
|
-
if (state.currentRound.currentPhaseIndex >= rules.round.phases.length) {
|
|
236
|
-
state.currentRound.currentPhaseIndex = 0;
|
|
237
|
-
state.currentRound.currentRoundIndex++;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
// No phases, just increment round
|
|
242
|
-
state.currentRound.currentRoundIndex++;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Check win/draw conditions
|
|
247
|
-
const winner = checkWinner(state, rules);
|
|
248
|
-
const isDraw = checkDraw(state, rules);
|
|
249
|
-
|
|
250
|
-
if (winner || isDraw) {
|
|
251
|
-
state.status = 'done'
|
|
252
|
-
state.winner = winner
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return serialize(state)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function doInitialPlacement(
|
|
259
|
-
placement: {
|
|
260
|
-
action: any;
|
|
261
|
-
payload?: any;
|
|
262
|
-
count?: number;
|
|
263
|
-
rules: GameRules;
|
|
264
|
-
},
|
|
265
|
-
player: Player | null,
|
|
266
|
-
state: GameState,
|
|
267
|
-
) {
|
|
268
|
-
const actionRule = placement.action;
|
|
269
|
-
const actionPayload = placement.payload || {};
|
|
270
|
-
|
|
271
|
-
if (player) {
|
|
272
|
-
actionPayload.player = player;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
Array.from(new Array(placement.count || 1)).forEach(() => {
|
|
276
|
-
const action = actionFactory(actionRule, state);
|
|
277
|
-
action.do(expandActionPayload(actionPayload, state, rules));
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function getBoardPathContaining(
|
|
282
|
-
piece: any,
|
|
283
|
-
state: GameState,
|
|
284
|
-
options?: any,
|
|
285
|
-
): string[] {
|
|
286
|
-
return getPiecePaths(piece, state, options)[0];
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function normalizePath(
|
|
290
|
-
path: string[],
|
|
291
|
-
options: { player?: Player } = {},
|
|
292
|
-
): string[] {
|
|
293
|
-
return path[0] === "personalBoard" && options.player
|
|
294
|
-
? ["personalBoards", options.player.id.toString(), ...path.slice(1)]
|
|
295
|
-
: path;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function getPiecePaths(
|
|
299
|
-
matcher: any,
|
|
300
|
-
state: GameState,
|
|
301
|
-
options?: any,
|
|
302
|
-
): string[][] {
|
|
303
|
-
const placesPiecesCanBe = {
|
|
304
|
-
personalBoards: state.personalBoards,
|
|
305
|
-
sharedBoard: state.sharedBoard,
|
|
306
|
-
pieces: state.pieces,
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
return Array.from(findValuePath(placesPiecesCanBe, matches(matcher)))
|
|
310
|
-
.filter((a) => a[a.length - 1] !== "rule")
|
|
311
|
-
.sort((a) => (a[0] === "pieces" ? 1 : -1))
|
|
312
|
-
.map((path) => normalizePath(path, options));
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function expandOptions(options) {
|
|
316
|
-
const defaultOptions = {
|
|
317
|
-
playerCount: 2,
|
|
318
|
-
};
|
|
319
|
-
return merge({}, defaultOptions, options);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// mutates rules
|
|
323
|
-
function expandRules (rules) {
|
|
324
|
-
addPathToRules(rules)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// mutates rules
|
|
328
|
-
export function addPathToRules (rules): void {
|
|
329
|
-
const CHILD_KEYS = ['phases', 'rounds'];
|
|
330
|
-
|
|
331
|
-
function annotate(node, pathSegs) {
|
|
332
|
-
if (!node || typeof node !== 'object') return;
|
|
333
|
-
|
|
334
|
-
// lodash.get path string, e.g. "round.phases.0.rounds.2"
|
|
335
|
-
node.path = pathSegs.join('.');
|
|
336
|
-
|
|
337
|
-
// recurse into either "phases" or "rounds" arrays if present
|
|
338
|
-
for (const key of CHILD_KEYS) {
|
|
339
|
-
const kids = node[key];
|
|
340
|
-
if (Array.isArray(kids)) {
|
|
341
|
-
kids.forEach((child, idx) => {
|
|
342
|
-
annotate(child, pathSegs.concat(key, idx));
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Top-level: support either "round" (object) or "rounds" (array), or both.
|
|
349
|
-
if (rules.round && typeof rules.round === 'object') {
|
|
350
|
-
annotate(rules.round, ['round']);
|
|
351
|
-
}
|
|
352
|
-
if (Array.isArray(rules.rounds)) {
|
|
353
|
-
rules.rounds.forEach((r, i) => annotate(r, ['rounds', i]));
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function deserializeState (state) {
|
|
358
|
-
return deserialize(
|
|
359
|
-
state,
|
|
360
|
-
registry
|
|
361
|
-
)
|
|
362
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { makeMove } from "./game/game";
|
package/src/piece/piece.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { PieceRule, PieceRuleMatcher } from "../../types";
|
|
2
|
-
import type Player from "../player/player";
|
|
3
|
-
|
|
4
|
-
interface Options {
|
|
5
|
-
player?: Player;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default class Piece {
|
|
9
|
-
rule: PieceRule;
|
|
10
|
-
player: Player;
|
|
11
|
-
constructor(pieceRule: PieceRule, options: Options) {
|
|
12
|
-
this.rule = pieceRule;
|
|
13
|
-
this.id = `${Math.random()}`
|
|
14
|
-
if (options.player !== undefined) {
|
|
15
|
-
({ player: this.player } = options);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
doesRuleMatch(matcher: PieceRuleMatcher): boolean {
|
|
20
|
-
if (matcher.player !== undefined) {
|
|
21
|
-
return matcher.player === this.player;
|
|
22
|
-
}
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
}
|
package/src/piece/pile.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import pieceFactory from './piece-factory.js'
|
|
2
|
-
|
|
3
|
-
// all this extra complication is to support arbitrary (infinite) piles of pieces
|
|
4
|
-
class Pile {
|
|
5
|
-
constructor (pieceRule, options = {}) {
|
|
6
|
-
this.pieceRule = pieceRule
|
|
7
|
-
this.name = pieceRule.name
|
|
8
|
-
this.id = `${Math.random()}`
|
|
9
|
-
if (options.player) {
|
|
10
|
-
this.player = options.player
|
|
11
|
-
}
|
|
12
|
-
this.options = options
|
|
13
|
-
this.pool = (
|
|
14
|
-
pieceRule.variants ? Object.entries(pieceRule.variants) : []
|
|
15
|
-
).reduce((acc, [variantId, variant]) => {
|
|
16
|
-
const count = variant.count || 1
|
|
17
|
-
return [
|
|
18
|
-
...acc,
|
|
19
|
-
...Array.from(Array(count)).map((_) =>
|
|
20
|
-
pieceFactory(
|
|
21
|
-
{ ...{ ...pieceRule, variantId }, ...variant },
|
|
22
|
-
this.options
|
|
23
|
-
)
|
|
24
|
-
)
|
|
25
|
-
]
|
|
26
|
-
}, [])
|
|
27
|
-
|
|
28
|
-
if (pieceRule.shuffled) {
|
|
29
|
-
this.pool = this.pool
|
|
30
|
-
.map((value) => ({ value, sort: Math.random() }))
|
|
31
|
-
.sort((a, b) => a.sort - b.sort)
|
|
32
|
-
.map(({ value }) => value)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
this.count = this.pool.length || pieceRule.count
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
getOne () {
|
|
39
|
-
return this.getMultiple(1)[0]
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
getMultiple (count) {
|
|
43
|
-
const toReturn = []
|
|
44
|
-
if (this.count === undefined || this.count >= count) {
|
|
45
|
-
if (this.count) {
|
|
46
|
-
this.count -= count
|
|
47
|
-
}
|
|
48
|
-
const remainder = count - this.pool.length
|
|
49
|
-
toReturn.push(...this.pool.splice(0, count))
|
|
50
|
-
|
|
51
|
-
if (remainder > 0) {
|
|
52
|
-
toReturn.push(
|
|
53
|
-
...Array.from(new Array(remainder)).map(() =>
|
|
54
|
-
pieceFactory(this.pieceRule, this.options)
|
|
55
|
-
)
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return toReturn
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
put (piece) {
|
|
63
|
-
if (this.count !== undefined) {
|
|
64
|
-
this.count += 1
|
|
65
|
-
}
|
|
66
|
-
this.pool.push(piece)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export default Pile
|
package/src/player/player.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { PlayerRule } from "../../types";
|
|
2
|
-
|
|
3
|
-
class Player {
|
|
4
|
-
rule: PlayerRule;
|
|
5
|
-
index: number;
|
|
6
|
-
constructor(rule: PlayerRule, index: number, id: string) {
|
|
7
|
-
this.rule = rule;
|
|
8
|
-
this.index = index;
|
|
9
|
-
this.id = id || `${Math.random()}`
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default Player;
|
package/src/registry.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import Cls0 from "./action/action.js";
|
|
2
|
-
import Cls1 from "./action/move-piece-action.js";
|
|
3
|
-
import Cls2 from "./action/select-piece-action.js";
|
|
4
|
-
import Cls3 from "./action/swap-action.js";
|
|
5
|
-
import Cls4 from "./board/board-group.js";
|
|
6
|
-
import Cls5 from "./board/board.js";
|
|
7
|
-
import Cls6 from "./board/grid.js";
|
|
8
|
-
import Cls7 from "./board/stack.js";
|
|
9
|
-
import Cls8 from "./condition/action-type-matches-condition.js";
|
|
10
|
-
import Cls9 from "./condition/bingo-condition.js";
|
|
11
|
-
import Cls10 from "./condition/blackout-condition.js";
|
|
12
|
-
import Cls11 from "./condition/condition.js";
|
|
13
|
-
import Cls12 from "./condition/contains-condition.js";
|
|
14
|
-
import Cls13 from "./condition/does-not-contain-condition.js";
|
|
15
|
-
import Cls14 from "./condition/is-valid-player-condition.js";
|
|
16
|
-
import Cls15 from "./condition/piece-matches-condition.js";
|
|
17
|
-
import Cls16 from "./condition/relative-move-condition.js";
|
|
18
|
-
import Cls17 from "./condition/some-condition.js";
|
|
19
|
-
import Cls18 from "./piece/piece.ts";
|
|
20
|
-
import Cls19 from "./piece/pile.js";
|
|
21
|
-
import Cls20 from "./player/player.ts";
|
|
22
|
-
import Cls21 from "./round/round.js";
|
|
23
|
-
import Cls22 from "./round/sequential-player-turn.js";
|
|
24
|
-
import Cls24 from "./space/space.ts";
|
|
25
|
-
|
|
26
|
-
export const registry = {
|
|
27
|
-
"Action": Cls0,
|
|
28
|
-
"MovePieceAction": Cls1,
|
|
29
|
-
"SelectPieceAction": Cls2,
|
|
30
|
-
"SwapAction": Cls3,
|
|
31
|
-
"BoardGroup": Cls4,
|
|
32
|
-
"Board": Cls5,
|
|
33
|
-
"Grid": Cls6,
|
|
34
|
-
"Stack": Cls7,
|
|
35
|
-
"ActionTypeMatchesCondition": Cls8,
|
|
36
|
-
"BingoCondition": Cls9,
|
|
37
|
-
"BlackoutCondition": Cls10,
|
|
38
|
-
"Condition": Cls11,
|
|
39
|
-
"ContainsCondition": Cls12,
|
|
40
|
-
"DoesNotContainCondition": Cls13,
|
|
41
|
-
"IsValidPlayerCondition": Cls14,
|
|
42
|
-
"PieceMatchesCondition": Cls15,
|
|
43
|
-
"RelativeMoveCondition": Cls16,
|
|
44
|
-
"SomeCondition": Cls17,
|
|
45
|
-
"Piece": Cls18,
|
|
46
|
-
"Pile": Cls19,
|
|
47
|
-
"Player": Cls20,
|
|
48
|
-
"Round": Cls21,
|
|
49
|
-
"SequentialPlayerTurn": Cls22,
|
|
50
|
-
"Space": Cls24
|
|
51
|
-
};
|
package/src/round/round.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import actionFactory from '../action/action-factory.js'
|
|
2
|
-
|
|
3
|
-
const DEBUG = true
|
|
4
|
-
|
|
5
|
-
export default class Round {
|
|
6
|
-
constructor (rules, game) {
|
|
7
|
-
this.rules = rules
|
|
8
|
-
this.game = game
|
|
9
|
-
this.history = []
|
|
10
|
-
this.id = `${Math.random()}`
|
|
11
|
-
this.actions = rules.actions?.map((actionRule) =>
|
|
12
|
-
actionFactory(actionRule, this.game)
|
|
13
|
-
)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
getCorrectAction (actionPayload) {
|
|
17
|
-
for (const action of this.actions) {
|
|
18
|
-
try {
|
|
19
|
-
action.assertIsValid(actionPayload)
|
|
20
|
-
return action
|
|
21
|
-
} catch (e) {
|
|
22
|
-
if (DEBUG) {
|
|
23
|
-
throw e
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
throw new Error(`Invalid Action: ${JSON.stringify(actionPayload)}`)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
doAction (actionPayload) {
|
|
31
|
-
this.getCorrectAction(actionPayload).do(actionPayload, this.game)
|
|
32
|
-
this.afterDoAction()
|
|
33
|
-
this.history.push(actionPayload)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
isOver () {
|
|
37
|
-
return true
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
afterDoAction () {}
|
|
41
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import Round from "./round.js";
|
|
2
|
-
|
|
3
|
-
export default class SequentialPlayerTurn extends Round {
|
|
4
|
-
constructor(rules, game) {
|
|
5
|
-
super(rules, game);
|
|
6
|
-
this.currentPlayerIndex = 0;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
afterDoAction() {
|
|
10
|
-
this.currentPlayerIndex = (this.currentPlayerIndex + 1) % this.game.players.length;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
isOver() {
|
|
14
|
-
return this.game.players.every((player) =>
|
|
15
|
-
this.history.some((p) => p === player),
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
}
|
package/src/space/space.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type Piece from "../piece/piece";
|
|
2
|
-
|
|
3
|
-
type Coordinates = [number, number];
|
|
4
|
-
|
|
5
|
-
export default class Space {
|
|
6
|
-
coordinates: Coordinates;
|
|
7
|
-
pieces: Piece[];
|
|
8
|
-
|
|
9
|
-
constructor(coordinates: Coordinates, startingPieces: Piece[] = []) {
|
|
10
|
-
this.coordinates = coordinates;
|
|
11
|
-
this.pieces = startingPieces;
|
|
12
|
-
this.id = `${Math.random()}`
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
placePiece(piece: Piece): void {
|
|
16
|
-
this.pieces.push(piece);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
isEmpty(): boolean {
|
|
20
|
-
return this.pieces.length === 0;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// todo: return found object along w/ path
|
|
2
|
-
export default function findValuePath(
|
|
3
|
-
obj,
|
|
4
|
-
compare,
|
|
5
|
-
currentPath = [],
|
|
6
|
-
visited = new Set(),
|
|
7
|
-
results = new Set(),
|
|
8
|
-
) {
|
|
9
|
-
// Check for circular reference
|
|
10
|
-
if (visited.has(obj)) {
|
|
11
|
-
return results; // Circular reference detected, short-circuit
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
visited.add(obj); // Mark the current object as visited
|
|
15
|
-
|
|
16
|
-
if (compare(obj)) {
|
|
17
|
-
results.add(currentPath);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (typeof obj === "object" && obj !== null) {
|
|
21
|
-
for (const key in obj) {
|
|
22
|
-
const newPath = [...currentPath, key];
|
|
23
|
-
const result = findValuePath(
|
|
24
|
-
obj[key],
|
|
25
|
-
compare,
|
|
26
|
-
newPath,
|
|
27
|
-
visited,
|
|
28
|
-
results,
|
|
29
|
-
);
|
|
30
|
-
if (result.length) {
|
|
31
|
-
results.add(currentPath);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return results;
|
|
37
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import get from 'lodash/get.js'
|
|
2
|
-
|
|
3
|
-
export default function resolveBoard (board, gameState) {
|
|
4
|
-
// Case 1: if board is an array treat it as a path
|
|
5
|
-
if (Array.isArray(board)) {
|
|
6
|
-
return get(gameState, board)
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const visited = new WeakSet()
|
|
10
|
-
|
|
11
|
-
function search (node) {
|
|
12
|
-
if (!node || typeof node !== 'object') return null
|
|
13
|
-
|
|
14
|
-
// Prevent infinite loops by tracking visited objects
|
|
15
|
-
if (visited.has(node)) return null
|
|
16
|
-
visited.add(node)
|
|
17
|
-
|
|
18
|
-
if (node.id === board.id) return node
|
|
19
|
-
|
|
20
|
-
if (node.grid && Array.isArray(node.grid)) {
|
|
21
|
-
for (const row of node.grid) {
|
|
22
|
-
for (const cell of row) {
|
|
23
|
-
const found = search(cell)
|
|
24
|
-
if (found) return found
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
for (const value of Object.values(node)) {
|
|
30
|
-
const found = search(value)
|
|
31
|
-
if (found) return found
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return search(gameState)
|
|
38
|
-
}
|