@pompidup/kingdomino-engine 3.6.0 → 3.7.0

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.
@@ -164,6 +164,12 @@ var wrapWithDebug = (engine, options) => {
164
164
  logger,
165
165
  level
166
166
  ),
167
+ getValidDominoPicks: wrapMethod(
168
+ "getValidDominoPicks",
169
+ engine.getValidDominoPicks,
170
+ logger,
171
+ level
172
+ ),
167
173
  serialize: wrapMethod("serialize", engine.serialize, logger, level),
168
174
  deserialize: wrapMethod("deserialize", engine.deserialize, logger, level),
169
175
  getDynastyResults: wrapMethod(
@@ -224,6 +230,12 @@ var wrapWithDebug = (engine, options) => {
224
230
  logger,
225
231
  level
226
232
  ),
233
+ getValidDragonTargets: wrapMethod(
234
+ "getValidDragonTargets",
235
+ engine.getValidDragonTargets,
236
+ logger,
237
+ level
238
+ ),
227
239
  getValidCavemanPlacements: wrapMethod(
228
240
  "getValidCavemanPlacements",
229
241
  engine.getValidCavemanPlacements,
@@ -269,6 +281,23 @@ var wrapWithEvents = (engine, callbacks) => {
269
281
  callbacks.onGameEnd?.({ game: after });
270
282
  }
271
283
  };
284
+ const emitNeolithicPair = (before, after, lordId) => {
285
+ if (!callbacks.onNeolithicPairChosen) return;
286
+ if (before.currentDominoes.some((d) => d.reservedFor !== void 0)) return;
287
+ const playerId = before.lords.find((l) => l.id === lordId)?.playerId;
288
+ const reserved = after.currentDominoes.filter(
289
+ (d) => d.reservedFor !== void 0
290
+ );
291
+ if (!playerId || reserved.length === 0) return;
292
+ const numbersFor = (own) => reserved.filter((d) => d.reservedFor === playerId === own).map((d) => d.domino.number);
293
+ callbacks.onNeolithicPairChosen({
294
+ game: after,
295
+ lordId,
296
+ playerId,
297
+ dominoNumbers: numbersFor(true),
298
+ opponentDominoNumbers: numbersFor(false)
299
+ });
300
+ };
272
301
  const gameplay = (run, emit) => (cmd) => {
273
302
  const game = run(cmd);
274
303
  emit(game, cmd);
@@ -282,6 +311,7 @@ var wrapWithEvents = (engine, callbacks) => {
282
311
  calculateScore: (cmd) => engine.calculateScore(cmd),
283
312
  getValidPlacements: (cmd) => engine.getValidPlacements(cmd),
284
313
  canPlaceDomino: (cmd) => engine.canPlaceDomino(cmd),
314
+ getValidDominoPicks: (cmd) => engine.getValidDominoPicks(cmd),
285
315
  serialize: (cmd) => engine.serialize(cmd),
286
316
  deserialize: (cmd) => engine.deserialize(cmd),
287
317
  getResults: (cmd) => engine.getResults(cmd),
@@ -290,6 +320,7 @@ var wrapWithEvents = (engine, callbacks) => {
290
320
  getValidKnightPositions: (cmd) => engine.getValidKnightPositions(cmd),
291
321
  getValidBuildingPositions: (cmd) => engine.getValidBuildingPositions(cmd),
292
322
  getAffordableBuildings: (cmd) => engine.getAffordableBuildings(cmd),
323
+ getValidDragonTargets: (cmd) => engine.getValidDragonTargets(cmd),
293
324
  getValidCavemanPlacements: (cmd) => engine.getValidCavemanPlacements(cmd),
294
325
  getValidGiantPositions: (cmd) => engine.getValidGiantPositions(cmd),
295
326
  getValidGiantTargets: (cmd) => engine.getValidGiantTargets(cmd),
@@ -320,11 +351,14 @@ var wrapWithEvents = (engine, callbacks) => {
320
351
  // Gameplay methods — specific event, then derived turn / game-end events
321
352
  chooseDomino: gameplay(
322
353
  (cmd) => engine.chooseDomino(cmd),
323
- (game, cmd) => callbacks.onDominoPicked?.({
324
- game,
325
- lordId: cmd.lordId,
326
- dominoNumber: cmd.dominoPick
327
- })
354
+ (game, cmd) => {
355
+ callbacks.onDominoPicked?.({
356
+ game,
357
+ lordId: cmd.lordId,
358
+ dominoNumber: cmd.dominoPick
359
+ });
360
+ emitNeolithicPair(cmd.game, game, cmd.lordId);
361
+ }
328
362
  ),
329
363
  placeDomino: gameplay(
330
364
  (cmd) => engine.placeDomino(cmd),
@@ -3857,6 +3891,29 @@ var rules_default = {
3857
3891
  dominoesDiscardedPerTurn: 0
3858
3892
  }
3859
3893
  ],
3894
+ modes: {
3895
+ QueenDomino: [
3896
+ {
3897
+ playersLimit: 2,
3898
+ lordsPerPlayer: 2,
3899
+ maxDominoes: 48,
3900
+ dominoesPerTurn: 4,
3901
+ maxTurns: 12,
3902
+ maxKingdomSize: 7
3903
+ }
3904
+ ],
3905
+ "AgeOfGiants-QueenDomino": [
3906
+ {
3907
+ playersLimit: 2,
3908
+ lordsPerPlayer: 2,
3909
+ maxDominoes: 60,
3910
+ dominoesPerTurn: 5,
3911
+ maxTurns: 12,
3912
+ maxKingdomSize: 7,
3913
+ dominoesDiscardedPerTurn: 1
3914
+ }
3915
+ ]
3916
+ },
3860
3917
  extra: [
3861
3918
  {
3862
3919
  name: "The middle Kingdom",
@@ -3970,6 +4027,24 @@ var jsonRules = () => {
3970
4027
  };
3971
4028
  });
3972
4029
  }
4030
+ const modeRules = rules_default.modes ?? {};
4031
+ for (const [modeName, modeEntries] of Object.entries(modeRules)) {
4032
+ rules.modeBasic ??= {};
4033
+ const byPlayers = {};
4034
+ modeEntries.forEach((rule) => {
4035
+ byPlayers[rule.playersLimit] = {
4036
+ lords: rule.lordsPerPlayer,
4037
+ maxDominoes: rule.maxDominoes,
4038
+ dominoesPerTurn: rule.dominoesPerTurn,
4039
+ maxTurns: rule.maxTurns,
4040
+ maxKingdomSize: rule.maxKingdomSize,
4041
+ ...rule.dominoesDiscardedPerTurn !== void 0 && {
4042
+ dominoesDiscardedPerTurn: rule.dominoesDiscardedPerTurn
4043
+ }
4044
+ };
4045
+ });
4046
+ rules.modeBasic[modeName] = byPlayers;
4047
+ }
3973
4048
  extraRules.forEach((rule) => {
3974
4049
  rules.extraRules.push({
3975
4050
  name: rule.name,
@@ -4029,6 +4104,7 @@ var ErrorCode = {
4029
4104
  CANNOT_PICK: "CANNOT_PICK",
4030
4105
  CANNOT_PLACE: "CANNOT_PLACE",
4031
4106
  DOMINO_ALREADY_PICKED: "DOMINO_ALREADY_PICKED",
4107
+ DOMINO_RESERVED: "DOMINO_RESERVED",
4032
4108
  // History errors
4033
4109
  UNDO_UNAVAILABLE: "UNDO_UNAVAILABLE",
4034
4110
  REDO_UNAVAILABLE: "REDO_UNAVAILABLE",
@@ -4165,6 +4241,7 @@ var TranslationKey = {
4165
4241
  ERROR_CANNOT_PICK: "error.cannotPick",
4166
4242
  ERROR_CANNOT_PLACE: "error.cannotPlace",
4167
4243
  ERROR_DOMINO_ALREADY_PICKED: "error.dominoAlreadyPicked",
4244
+ ERROR_DOMINO_RESERVED: "error.dominoReserved",
4168
4245
  ERROR_INVALID_PLAYER_COUNT: "error.invalidPlayerCount",
4169
4246
  ERROR_INVALID_PLAYER_NAME: "error.invalidPlayerName",
4170
4247
  ERROR_EXTRA_RULE_NOT_AVAILABLE: "error.extraRuleNotAvailable",
@@ -4265,6 +4342,7 @@ var TranslationKey = {
4265
4342
  // hunter-gatherers reuse the CAVEMAN_* keys)
4266
4343
  SCORE_QUEEN: "score.queen",
4267
4344
  SCORE_BUILDING: "score.building",
4345
+ SCORE_WEALTH: "score.wealth",
4268
4346
  SCORE_TOTEM_MAMMOTH: "score.totemMammoth",
4269
4347
  SCORE_TOTEM_FISH: "score.totemFish",
4270
4348
  SCORE_TOTEM_MUSHROOM: "score.totemMushroom",
@@ -4299,6 +4377,7 @@ var defaultTranslations = {
4299
4377
  [TranslationKey.ERROR_CANNOT_PICK]: "Cannot pick a domino now",
4300
4378
  [TranslationKey.ERROR_CANNOT_PLACE]: "Cannot place a domino now",
4301
4379
  [TranslationKey.ERROR_DOMINO_ALREADY_PICKED]: "Domino has already been picked",
4380
+ [TranslationKey.ERROR_DOMINO_RESERVED]: "This domino is reserved for the other player (Neolithic: pairs 1 & 4 and 2 & 3)",
4302
4381
  [TranslationKey.ERROR_INVALID_PLAYER_COUNT]: "Invalid number of players (1-4 required)",
4303
4382
  [TranslationKey.ERROR_INVALID_PLAYER_NAME]: "Player name must be at least 3 characters",
4304
4383
  [TranslationKey.ERROR_EXTRA_RULE_NOT_AVAILABLE]: "Extra rule is not available for this game mode or number of players",
@@ -4397,6 +4476,7 @@ var defaultTranslations = {
4397
4476
  // Score breakdown labels
4398
4477
  [TranslationKey.SCORE_QUEEN]: "Queen",
4399
4478
  [TranslationKey.SCORE_BUILDING]: "Building: {{name}}",
4479
+ [TranslationKey.SCORE_WEALTH]: "Wealth",
4400
4480
  [TranslationKey.SCORE_TOTEM_MAMMOTH]: "Mammoth totem",
4401
4481
  [TranslationKey.SCORE_TOTEM_FISH]: "Fish totem",
4402
4482
  [TranslationKey.SCORE_TOTEM_MUSHROOM]: "Mushroom totem",
@@ -4432,6 +4512,7 @@ var frenchTranslations = {
4432
4512
  [TranslationKey.ERROR_CANNOT_PICK]: "Impossible de choisir un domino maintenant",
4433
4513
  [TranslationKey.ERROR_CANNOT_PLACE]: "Impossible de placer un domino maintenant",
4434
4514
  [TranslationKey.ERROR_DOMINO_ALREADY_PICKED]: "Ce domino a d\xE9j\xE0 \xE9t\xE9 choisi",
4515
+ [TranslationKey.ERROR_DOMINO_RESERVED]: "Ce domino est r\xE9serv\xE9 \xE0 l'autre joueur (N\xE9olithique : paires 1 et 4, 2 et 3)",
4435
4516
  [TranslationKey.ERROR_INVALID_PLAYER_COUNT]: "Nombre de joueurs invalide (1 \xE0 4 requis)",
4436
4517
  [TranslationKey.ERROR_INVALID_PLAYER_NAME]: "Le nom du joueur doit contenir au moins 3 caract\xE8res",
4437
4518
  [TranslationKey.ERROR_EXTRA_RULE_NOT_AVAILABLE]: "Cette r\xE8gle optionnelle n'est pas disponible pour ce mode ou ce nombre de joueurs",
@@ -4530,6 +4611,7 @@ var frenchTranslations = {
4530
4611
  // Score breakdown labels
4531
4612
  [TranslationKey.SCORE_QUEEN]: "Reine",
4532
4613
  [TranslationKey.SCORE_BUILDING]: "B\xE2timent : {{name}}",
4614
+ [TranslationKey.SCORE_WEALTH]: "Richesse",
4533
4615
  [TranslationKey.SCORE_TOTEM_MAMMOTH]: "Totem mammouth",
4534
4616
  [TranslationKey.SCORE_TOTEM_FISH]: "Totem poisson",
4535
4617
  [TranslationKey.SCORE_TOTEM_MUSHROOM]: "Totem champignon",
@@ -4641,6 +4723,7 @@ var errorCodeToTranslationKey = {
4641
4723
  [ErrorCode.CANNOT_PICK]: TranslationKey.ERROR_CANNOT_PICK,
4642
4724
  [ErrorCode.CANNOT_PLACE]: TranslationKey.ERROR_CANNOT_PLACE,
4643
4725
  [ErrorCode.DOMINO_ALREADY_PICKED]: TranslationKey.ERROR_DOMINO_ALREADY_PICKED,
4726
+ [ErrorCode.DOMINO_RESERVED]: TranslationKey.ERROR_DOMINO_RESERVED,
4644
4727
  [ErrorCode.UNDO_UNAVAILABLE]: TranslationKey.ERROR_UNDO_UNAVAILABLE,
4645
4728
  [ErrorCode.REDO_UNAVAILABLE]: TranslationKey.ERROR_REDO_UNAVAILABLE,
4646
4729
  [ErrorCode.CANNOT_PLACE_KNIGHT]: TranslationKey.ERROR_CANNOT_PLACE_KNIGHT,
@@ -5666,6 +5749,8 @@ var scoreBonusTypes = {
5666
5749
  queen: "queen",
5667
5750
  /** Queendomino: end-game scoring of one constructed building */
5668
5751
  building: "building",
5752
+ /** Queendomino: Wealth, 1 point per 3 unused coins */
5753
+ wealth: "wealth",
5669
5754
  /** Age of Giants: one quest tile */
5670
5755
  quest: "quest",
5671
5756
  /** Origins Totem: one totem held by the player */
@@ -5733,7 +5818,7 @@ var addExtraRulesUseCase = (deps) => (game, rules) => {
5733
5818
  }
5734
5819
  let updatedBasic = game.rules.basic;
5735
5820
  let updatedDominoes = game.dominoes;
5736
- const hasLargeKingdomRule = newExtraRules.some(isLargeKingdomExtraRule);
5821
+ const hasLargeKingdomRule = newExtraRules.some(isLargeKingdomExtraRule) && game.rules.basic.maxKingdomSize < 7;
5737
5822
  if (hasLargeKingdomRule) {
5738
5823
  updatedBasic = {
5739
5824
  ...updatedBasic,
@@ -5784,7 +5869,10 @@ var isFootprintDomino = (domino) => (domino.left.hasFootprint ?? false) || (domi
5784
5869
 
5785
5870
  // src/core/domain/entities/economy.ts
5786
5871
  var STARTING_COINS = 7;
5787
- var MAX_KNIGHTS = 3;
5872
+ var STARTING_KNIGHTS = 1;
5873
+ var DRAGON_COST = 1;
5874
+ var QUEEN_DISCOUNT = 1;
5875
+ var COINS_PER_WEALTH_POINT = 3;
5788
5876
  var addCoins = (player, amount) => ({
5789
5877
  ...player,
5790
5878
  coins: (player.coins ?? 0) + amount
@@ -5803,7 +5891,8 @@ var addBonusKnights = (player, amount) => ({
5803
5891
  ...player,
5804
5892
  bonusKnights: (player.bonusKnights ?? 0) + amount
5805
5893
  });
5806
- var getMaxKnights = (player) => MAX_KNIGHTS + (player.bonusKnights ?? 0);
5894
+ var getMaxKnights = (player) => STARTING_KNIGHTS + (player.bonusKnights ?? 0);
5895
+ var getWealthPoints = (player) => Math.floor(Math.max(0, player.coins ?? 0) / COINS_PER_WEALTH_POINT);
5807
5896
 
5808
5897
  // src/core/domain/entities/domino.ts
5809
5898
  var isGround = (ground) => {
@@ -6158,7 +6247,7 @@ var addPlayersUseCase = (deps) => (game, players) => {
6158
6247
  newPlayers.push(newPlayer.value);
6159
6248
  }
6160
6249
  const rules = ruleRepository.getAll();
6161
- const basicRules = isAoG && rules.aogBasic ? rules.aogBasic[newPlayers.length] : rules.basic[newPlayers.length];
6250
+ const basicRules = rules.modeBasic?.[game.mode.name]?.[newPlayers.length] ?? (isAoG && rules.aogBasic ? rules.aogBasic[newPlayers.length] : rules.basic[newPlayers.length]);
6162
6251
  if (!basicRules) {
6163
6252
  return err(ErrorCode.STEP_EXECUTION_FAILED);
6164
6253
  }
@@ -6417,7 +6506,15 @@ var calculateOriginsFireBonus = (kingdom, placedFireTokens) => {
6417
6506
  };
6418
6507
 
6419
6508
  // src/core/domain/entities/queen.ts
6420
- var determineQueenHolder = (players, currentHolderId) => {
6509
+ var determineQueenHolder = (players, currentHolderId, actingPlayerId) => {
6510
+ if (actingPlayerId !== void 0) {
6511
+ const acting = players.find((p) => p.id === actingPlayerId);
6512
+ if (!acting) return currentHolderId;
6513
+ const towers = getTowerCount(acting);
6514
+ const others = players.filter((p) => p.id !== actingPlayerId);
6515
+ const reachesMost = towers > 0 && others.every((p) => getTowerCount(p) <= towers);
6516
+ return reachesMost ? actingPlayerId : currentHolderId;
6517
+ }
6421
6518
  let maxTowers = 0;
6422
6519
  let leaderId = null;
6423
6520
  let isTied = false;
@@ -6929,6 +7026,12 @@ var queendominoBonuses = (game, player, provisional) => {
6929
7026
  provisional
6930
7027
  });
6931
7028
  }
7029
+ bonuses.push({
7030
+ type: "wealth",
7031
+ label: TranslationKey.SCORE_WEALTH,
7032
+ points: getWealthPoints(player),
7033
+ provisional
7034
+ });
6932
7035
  for (const placed of player.buildings ?? []) {
6933
7036
  if (!placed.building.endGameScoring) continue;
6934
7037
  bonuses.push({
@@ -7124,31 +7227,58 @@ var calculateScoreUseCase = (kingdom, giantPositions) => {
7124
7227
  };
7125
7228
 
7126
7229
  // src/core/domain/entities/building.ts
7127
- var BOARD_SLOTS = 4;
7230
+ var BUILDERS_BOARD_SLOT_COSTS = [5, 4, 3, 2, 1, 0];
7231
+ var BOARD_SLOTS = BUILDERS_BOARD_SLOT_COSTS.length;
7128
7232
  var createBuildersBoard = (tiles, shuffle) => {
7129
7233
  const shuffled = shuffle([...tiles]);
7130
7234
  const slots = shuffled.slice(0, BOARD_SLOTS);
7235
+ while (slots.length < BOARD_SLOTS) slots.push(null);
7131
7236
  const drawPile = shuffled.slice(BOARD_SLOTS);
7132
7237
  return {
7133
7238
  slots,
7134
- drawPile
7239
+ drawPile,
7240
+ slotCosts: [...BUILDERS_BOARD_SLOT_COSTS]
7135
7241
  };
7136
7242
  };
7137
7243
  var refillBuildersBoard = (board) => {
7138
- const newSlots = [...board.slots];
7139
7244
  const newDrawPile = [...board.drawPile];
7140
- for (let i = 0; i < newSlots.length; i++) {
7141
- if (newSlots[i] === null && newDrawPile.length > 0) {
7142
- newSlots[i] = newDrawPile.shift();
7245
+ if (!board.slotCosts) {
7246
+ const newSlots2 = [...board.slots];
7247
+ for (let i = 0; i < newSlots2.length; i++) {
7248
+ if (newSlots2[i] === null && newDrawPile.length > 0) {
7249
+ newSlots2[i] = newDrawPile.shift();
7250
+ }
7143
7251
  }
7252
+ return { slots: newSlots2, drawPile: newDrawPile };
7144
7253
  }
7145
- return { slots: newSlots, drawPile: newDrawPile };
7254
+ const remaining = board.slots.filter(
7255
+ (slot) => slot !== null
7256
+ );
7257
+ const empty = board.slots.length - remaining.length;
7258
+ const newSlots = [
7259
+ ...Array.from({ length: empty }, () => null),
7260
+ ...remaining
7261
+ ];
7262
+ for (let i = empty - 1; i >= 0 && newDrawPile.length > 0; i--) {
7263
+ newSlots[i] = newDrawPile.shift();
7264
+ }
7265
+ return { ...board, slots: newSlots, drawPile: newDrawPile };
7146
7266
  };
7147
7267
  var startNewQueenDominoRound = (state) => ({
7148
7268
  ...state,
7149
7269
  buildersBoard: refillBuildersBoard(state.buildersBoard),
7150
7270
  dragonUsedThisRound: false
7151
7271
  });
7272
+ var getSlotCost = (board, slotIndex) => {
7273
+ const building = board.slots[slotIndex];
7274
+ if (!building) return null;
7275
+ return board.slotCosts?.[slotIndex] ?? building.cost;
7276
+ };
7277
+ var getConstructionCost = (board, slotIndex, isQueenHost) => {
7278
+ const cost = getSlotCost(board, slotIndex);
7279
+ if (cost === null) return null;
7280
+ return Math.max(0, cost - (isQueenHost ? QUEEN_DISCOUNT : 0));
7281
+ };
7152
7282
  var findBuildingOnBoard = (board, buildingId) => {
7153
7283
  for (let i = 0; i < board.slots.length; i++) {
7154
7284
  const slot = board.slots[i];
@@ -7163,12 +7293,17 @@ var removeBuildingFromBoard = (board, slotIndex) => {
7163
7293
  newSlots[slotIndex] = null;
7164
7294
  return { ...board, slots: newSlots };
7165
7295
  };
7166
- var purchaseBuilding = (board, player, buildingId) => {
7296
+ var purchaseBuilding = (board, player, buildingId, queenHolderId = null) => {
7167
7297
  const found = findBuildingOnBoard(board, buildingId);
7168
7298
  if (!found) return null;
7169
7299
  const { building, slotIndex } = found;
7170
- if (!canAfford(player, building.cost)) return null;
7171
- let updatedPlayer = removeCoins(player, building.cost);
7300
+ const cost = getConstructionCost(
7301
+ board,
7302
+ slotIndex,
7303
+ queenHolderId !== null && queenHolderId === player.id
7304
+ );
7305
+ if (cost === null || !canAfford(player, cost)) return null;
7306
+ let updatedPlayer = removeCoins(player, cost);
7172
7307
  if (building.immediateBonus) {
7173
7308
  switch (building.immediateBonus.type) {
7174
7309
  case "coins":
@@ -7192,7 +7327,7 @@ var purchaseBuilding = (board, player, buildingId) => {
7192
7327
  updatedPlayer = addTowers(updatedPlayer, building.towers);
7193
7328
  }
7194
7329
  const updatedBoard = removeBuildingFromBoard(board, slotIndex);
7195
- return { updatedBoard, updatedPlayer, building };
7330
+ return { updatedBoard, updatedPlayer, building, cost };
7196
7331
  };
7197
7332
  var isValidConstructionPosition = (kingdom, placedBuildings, position) => {
7198
7333
  const tile = kingdom[position.y]?.[position.x];
@@ -7218,9 +7353,9 @@ var placeBuildingOnKingdom = (kingdom, building, position) => {
7218
7353
  var destroyWithDragon = (board, buildingId, player) => {
7219
7354
  const found = findBuildingOnBoard(board, buildingId);
7220
7355
  if (!found) return null;
7221
- const { building, slotIndex } = found;
7222
- if (!canAfford(player, building.cost)) return null;
7223
- const updatedPlayer = removeCoins(player, building.cost);
7356
+ const { slotIndex } = found;
7357
+ if (!canAfford(player, DRAGON_COST)) return null;
7358
+ const updatedPlayer = removeCoins(player, DRAGON_COST);
7224
7359
  const updatedBoard = removeBuildingFromBoard(board, slotIndex);
7225
7360
  return { updatedBoard, updatedPlayer };
7226
7361
  };
@@ -7339,6 +7474,30 @@ var nextOriginsAction = (currentAction, isTribe) => {
7339
7474
  return sequence[index + 1];
7340
7475
  };
7341
7476
 
7477
+ // src/core/domain/entities/neolithic.ts
7478
+ var NEOLITHIC_RULE_NAME = "Neolithic";
7479
+ var NEOLITHIC_PAIRS = [
7480
+ [1, 4],
7481
+ [2, 3]
7482
+ ];
7483
+ var NEOLITHIC_LINE_SIZE = 4;
7484
+ var isNeolithicGame = (game) => game.players.length === 2 && game.rules.extra.some((rule) => rule.name === NEOLITHIC_RULE_NAME);
7485
+ var neolithicPairOf = (position) => NEOLITHIC_PAIRS.find((pair) => pair.includes(position));
7486
+ var isNeolithicPairChoice = (game, line) => isNeolithicGame(game) && line.length === NEOLITHIC_LINE_SIZE && line.every((domino) => !domino.picked && domino.reservedFor === void 0);
7487
+ var canLordPickRevealed = (lord, domino) => !domino.picked && (domino.reservedFor === void 0 || domino.reservedFor === lord.playerId);
7488
+ var reserveNeolithicPairs = (game, line, lord, pickedNumber) => {
7489
+ if (!isNeolithicPairChoice(game, line)) return line;
7490
+ const picked = line.find((domino) => domino.domino.number === pickedNumber);
7491
+ const pair = picked && neolithicPairOf(picked.position);
7492
+ const opponent = game.players.find((player) => player.id !== lord.playerId);
7493
+ if (!pair || !opponent) return line;
7494
+ return line.map((domino) => ({
7495
+ ...domino,
7496
+ reservedFor: pair.includes(domino.position) ? lord.playerId : opponent.id
7497
+ }));
7498
+ };
7499
+ var pickableDominoNumbers = (line, lord) => line.filter((domino) => canLordPickRevealed(lord, domino)).map((domino) => domino.domino.number);
7500
+
7342
7501
  // src/core/useCases/chooseDomino.ts
7343
7502
  var chooseDominoUseCase = (game, lordId, dominoPick) => {
7344
7503
  const currentLord = game.lords.find(
@@ -7362,7 +7521,15 @@ var chooseDominoUseCase = (game, lordId, dominoPick) => {
7362
7521
  if (selectedDomino.picked) {
7363
7522
  return err(ErrorCode.DOMINO_ALREADY_PICKED);
7364
7523
  }
7365
- const updatedCurrentDominoes = game.currentDominoes.map(
7524
+ if (selectedDomino.reservedFor !== void 0 && selectedDomino.reservedFor !== currentLord.playerId) {
7525
+ return err(ErrorCode.DOMINO_RESERVED);
7526
+ }
7527
+ const updatedCurrentDominoes = reserveNeolithicPairs(
7528
+ game,
7529
+ game.currentDominoes,
7530
+ currentLord,
7531
+ dominoPick
7532
+ ).map(
7366
7533
  (domino) => domino.domino.number === dominoPick ? { ...domino, picked: true, lordId: currentLord.id } : domino
7367
7534
  );
7368
7535
  const isTribe = isTribeMode(game.mode.name);
@@ -7472,7 +7639,8 @@ var constructBuildingUseCase = (game, lordId, buildingId, position) => {
7472
7639
  const purchaseResult = purchaseBuilding(
7473
7640
  queendomino.buildersBoard,
7474
7641
  currentPlayer,
7475
- buildingId
7642
+ buildingId,
7643
+ queendomino.queenHolderId
7476
7644
  );
7477
7645
  if (!purchaseResult) {
7478
7646
  return err(ErrorCode.CANNOT_AFFORD_BUILDING);
@@ -7494,7 +7662,8 @@ var constructBuildingUseCase = (game, lordId, buildingId, position) => {
7494
7662
  );
7495
7663
  const newQueenHolder = determineQueenHolder(
7496
7664
  updatedPlayers,
7497
- queendomino.queenHolderId
7665
+ queendomino.queenHolderId,
7666
+ currentPlayer.id
7498
7667
  );
7499
7668
  const nextAction = nextQueenDominoAction(playerActions.constructBuilding);
7500
7669
  return ok({
@@ -8024,55 +8193,18 @@ var getResultUseCase = (game) => {
8024
8193
  };
8025
8194
 
8026
8195
  // src/core/domain/entities/knight.ts
8027
- var countConstructionSquaresInTerritory = (kingdom, position) => {
8028
- const rows = kingdom.length;
8029
- const cols = kingdom[0]?.length ?? 0;
8196
+ var countTerritorySquares = (kingdom, position) => {
8030
8197
  const startTile = kingdom[position.y]?.[position.x];
8031
8198
  if (!startTile || startTile.type === "empty" || startTile.type === "castle") {
8032
8199
  return 0;
8033
8200
  }
8034
- const terrainType = startTile.type;
8035
- const visited = kingdom.map((row) => row.map(() => false));
8036
- const queue = [[position.x, position.y]];
8037
- visited[position.y][position.x] = true;
8038
- let count = 0;
8039
- const directions = [
8040
- [-1, 0],
8041
- [1, 0],
8042
- [0, -1],
8043
- [0, 1]
8044
- ];
8045
- while (queue.length > 0) {
8046
- const [cx, cy] = queue.pop();
8047
- const tile = kingdom[cy][cx];
8048
- if (tile.type !== "empty" && tile.type !== "castle" && "hasConstructionSquare" in tile && tile.hasConstructionSquare) {
8049
- count++;
8050
- }
8051
- for (const [dx, dy] of directions) {
8052
- const nx = cx + dx;
8053
- const ny = cy + dy;
8054
- if (nx >= 0 && ny >= 0 && nx < cols && ny < rows && !visited[ny][nx] && kingdom[ny][nx].type === terrainType) {
8055
- visited[ny][nx] = true;
8056
- queue.push([nx, ny]);
8057
- }
8058
- }
8059
- }
8060
- return count;
8061
- };
8062
- var getTerritoryTerrain = (kingdom, position) => {
8063
- const tile = kingdom[position.y]?.[position.x];
8064
- if (!tile || tile.type === "empty" || tile.type === "castle") return null;
8065
- return tile.type;
8066
- };
8067
- var territoryHasKnight = (kingdom, position, knights) => {
8068
- const terrain = getTerritoryTerrain(kingdom, position);
8069
- if (!terrain) return false;
8070
8201
  const rows = kingdom.length;
8071
8202
  const cols = kingdom[0]?.length ?? 0;
8203
+ const terrain = startTile.type;
8072
8204
  const visited = kingdom.map((row) => row.map(() => false));
8073
8205
  const queue = [[position.x, position.y]];
8074
8206
  visited[position.y][position.x] = true;
8075
- const territoryPositions = /* @__PURE__ */ new Set();
8207
+ let count = 0;
8076
8208
  const directions = [
8077
8209
  [-1, 0],
8078
8210
  [1, 0],
@@ -8081,7 +8213,7 @@ var territoryHasKnight = (kingdom, position, knights) => {
8081
8213
  ];
8082
8214
  while (queue.length > 0) {
8083
8215
  const [cx, cy] = queue.pop();
8084
- territoryPositions.add(`${cx},${cy}`);
8216
+ count++;
8085
8217
  for (const [dx, dy] of directions) {
8086
8218
  const nx = cx + dx;
8087
8219
  const ny = cy + dy;
@@ -8091,10 +8223,9 @@ var territoryHasKnight = (kingdom, position, knights) => {
8091
8223
  }
8092
8224
  }
8093
8225
  }
8094
- return knights.some(
8095
- (k) => territoryPositions.has(`${k.position.x},${k.position.y}`)
8096
- );
8226
+ return count;
8097
8227
  };
8228
+ var calculateKnightTax = (kingdom, position) => countTerritorySquares(kingdom, position);
8098
8229
  var canPlaceKnight = (player, kingdom, position, allowedPositions) => {
8099
8230
  const knights = player.knights ?? [];
8100
8231
  if (knights.length >= getMaxKnights(player)) return false;
@@ -8103,13 +8234,17 @@ var canPlaceKnight = (player, kingdom, position, allowedPositions) => {
8103
8234
  }
8104
8235
  const tile = kingdom[position.y]?.[position.x];
8105
8236
  if (!tile || tile.type === "empty" || tile.type === "castle") return false;
8106
- if (territoryHasKnight(kingdom, position, knights)) return false;
8237
+ if (knights.some(
8238
+ (k) => k.position.x === position.x && k.position.y === position.y
8239
+ )) {
8240
+ return false;
8241
+ }
8107
8242
  return true;
8108
8243
  };
8109
8244
  var placeKnight = (player, kingdom, position) => {
8110
8245
  const knight = { playerId: player.id, position };
8111
8246
  const knights = [...player.knights ?? [], knight];
8112
- const taxAmount = countConstructionSquaresInTerritory(kingdom, position);
8247
+ const taxAmount = calculateKnightTax(kingdom, position);
8113
8248
  const updatedPlayer = addCoins({ ...player, knights }, taxAmount);
8114
8249
  return updatedPlayer;
8115
8250
  };
@@ -8445,7 +8580,15 @@ var placeKnightUseCase = (game, lordId, position) => {
8445
8580
  const updatedPlayers = game.players.map(
8446
8581
  (p) => p.id === currentPlayer.id ? updatedPlayer : p
8447
8582
  );
8448
- const nextAction = nextQueenDominoAction(playerActions.placeKnight);
8583
+ const canPlaceAnother = allowedPositions.some(
8584
+ (square) => canPlaceKnight(
8585
+ updatedPlayer,
8586
+ updatedPlayer.kingdom,
8587
+ square,
8588
+ allowedPositions
8589
+ )
8590
+ );
8591
+ const nextAction = canPlaceAnother ? playerActions.placeKnight : nextQueenDominoAction(playerActions.placeKnight);
8449
8592
  return ok({
8450
8593
  ...game,
8451
8594
  players: updatedPlayers,
@@ -8653,6 +8796,74 @@ var advanceToNextLordOrResult2 = (game) => {
8653
8796
  });
8654
8797
  };
8655
8798
 
8799
+ // src/core/useCases/useDragon.ts
8800
+ var resolveDragon = (game, lordId) => {
8801
+ const currentAction = game.nextAction;
8802
+ if (currentAction.nextLord !== lordId) {
8803
+ return err(ErrorCode.NOT_YOUR_TURN);
8804
+ }
8805
+ if (currentAction.nextAction !== playerActions.useDragon) {
8806
+ return err(ErrorCode.INVALID_OPTIONAL_ACTION);
8807
+ }
8808
+ if (!game.queendomino) {
8809
+ return err(ErrorCode.INVALID_OPTIONAL_ACTION);
8810
+ }
8811
+ if (!game.queendomino.dragonAvailable || game.queendomino.dragonUsedThisRound) {
8812
+ return err(ErrorCode.DRAGON_UNAVAILABLE);
8813
+ }
8814
+ const currentLord = game.lords.find((l) => l.id === lordId);
8815
+ if (!currentLord) return err(ErrorCode.LORD_NOT_FOUND);
8816
+ const currentPlayer = game.players.find((p) => p.id === currentLord.playerId);
8817
+ if (!currentPlayer) return err(ErrorCode.PLAYER_NOT_FOUND);
8818
+ if (game.queendomino.queenHolderId === currentPlayer.id) {
8819
+ return err(ErrorCode.QUEEN_HOLDER_CANNOT_USE_DRAGON);
8820
+ }
8821
+ return ok({ player: currentPlayer, queendomino: game.queendomino });
8822
+ };
8823
+ var useDragonUseCase = (game, lordId, buildingId) => {
8824
+ const context = resolveDragon(game, lordId);
8825
+ if (isErr(context)) {
8826
+ return context;
8827
+ }
8828
+ const { player: currentPlayer, queendomino } = context.value;
8829
+ const result = destroyWithDragon(
8830
+ queendomino.buildersBoard,
8831
+ buildingId,
8832
+ currentPlayer
8833
+ );
8834
+ if (!result) {
8835
+ return err(ErrorCode.BUILDING_NOT_FOUND);
8836
+ }
8837
+ const { updatedBoard, updatedPlayer } = result;
8838
+ const updatedPlayers = game.players.map(
8839
+ (p) => p.id === currentPlayer.id ? updatedPlayer : p
8840
+ );
8841
+ const maxTurns = game.rules.basic.maxTurns;
8842
+ const isLastTurn = game.turn === maxTurns;
8843
+ if (isLastTurn && allLordsHavePlayed(game.lords)) {
8844
+ return ok({
8845
+ ...game,
8846
+ players: updatedPlayers,
8847
+ queendomino: {
8848
+ ...queendomino,
8849
+ buildersBoard: updatedBoard,
8850
+ dragonUsedThisRound: true
8851
+ },
8852
+ nextAction: { type: "step", step: gameSteps.result }
8853
+ });
8854
+ }
8855
+ return ok({
8856
+ ...game,
8857
+ players: updatedPlayers,
8858
+ queendomino: {
8859
+ ...queendomino,
8860
+ buildersBoard: updatedBoard,
8861
+ dragonUsedThisRound: true
8862
+ },
8863
+ nextAction: nextLordWithAction(game.lords)
8864
+ });
8865
+ };
8866
+
8656
8867
  // src/core/useCases/getValidActionTargets.ts
8657
8868
  var turnFor = (game, action, lordId) => {
8658
8869
  if (!isGameWithNextAction(game) || game.nextAction.nextAction !== action) {
@@ -8703,13 +8914,24 @@ var getConstructionOptions = (game, lordId) => {
8703
8914
  )
8704
8915
  );
8705
8916
  const buildings = board.slots.filter(
8706
- (slot) => slot !== null && purchaseBuilding(board, player, slot.id) !== null
8917
+ (slot) => slot !== null && purchaseBuilding(board, player, slot.id, queendomino.queenHolderId) !== null
8707
8918
  );
8708
8919
  if (positions.length === 0 || buildings.length === 0) return noConstruction();
8709
8920
  return { positions, buildings };
8710
8921
  };
8711
8922
  var getValidBuildingPositionsUseCase = (game, lordId) => getConstructionOptions(game, lordId).positions;
8712
8923
  var getAffordableBuildingsUseCase = (game, lordId) => getConstructionOptions(game, lordId).buildings;
8924
+ var getValidDragonTargetsUseCase = (game, lordId) => {
8925
+ const turn = turnFor(game, playerActions.useDragon, lordId);
8926
+ if (!turn) return [];
8927
+ const context = resolveDragon(turn.game, turn.lordId);
8928
+ if (isErr(context)) return [];
8929
+ const { player, queendomino } = context.value;
8930
+ const board = queendomino.buildersBoard;
8931
+ return board.slots.filter(
8932
+ (slot) => slot !== null && destroyWithDragon(board, slot.id, player) !== null
8933
+ );
8934
+ };
8713
8935
  var noRecruitment = () => ({
8714
8936
  options: [],
8715
8937
  positions: []
@@ -8789,6 +9011,18 @@ var getValidGiantTargetsUseCase = (game, lordId) => {
8789
9011
  };
8790
9012
  var canSkipOptionalActionUseCase = (game, lordId) => isGameWithNextAction(game) && isOk(resolveSkip(game, lordId ?? game.nextAction.nextLord));
8791
9013
 
9014
+ // src/core/useCases/getValidDominoPicks.ts
9015
+ var getValidDominoPicksUseCase = (game, lordId) => {
9016
+ if (!isGameWithNextAction(game) || game.nextAction.nextAction !== playerActions.pickDomino) {
9017
+ return [];
9018
+ }
9019
+ const id = lordId ?? game.nextAction.nextLord;
9020
+ if (id !== game.nextAction.nextLord) return [];
9021
+ const lord = game.lords.find((l) => l.id === id);
9022
+ if (!lord || !canPick(lord)) return [];
9023
+ return pickableDominoNumbers(game.currentDominoes, lord);
9024
+ };
9025
+
8792
9026
  // src/core/useCases/placeDomino.ts
8793
9027
  var findVolcanoInDomino = (game, position, rotation) => {
8794
9028
  const lord = game.lords.find((l) => l.id === game.nextAction.nextLord);
@@ -9097,66 +9331,6 @@ var startGameUseCase = (deps) => (game) => {
9097
9331
  return ok(newState);
9098
9332
  };
9099
9333
 
9100
- // src/core/useCases/useDragon.ts
9101
- var useDragonUseCase = (game, lordId, buildingId) => {
9102
- const currentAction = game.nextAction;
9103
- if (currentAction.nextLord !== lordId) {
9104
- return err(ErrorCode.NOT_YOUR_TURN);
9105
- }
9106
- if (currentAction.nextAction !== playerActions.useDragon) {
9107
- return err(ErrorCode.INVALID_OPTIONAL_ACTION);
9108
- }
9109
- if (!game.queendomino) {
9110
- return err(ErrorCode.INVALID_OPTIONAL_ACTION);
9111
- }
9112
- if (!game.queendomino.dragonAvailable || game.queendomino.dragonUsedThisRound) {
9113
- return err(ErrorCode.DRAGON_UNAVAILABLE);
9114
- }
9115
- const currentLord = game.lords.find((l) => l.id === lordId);
9116
- if (!currentLord) return err(ErrorCode.LORD_NOT_FOUND);
9117
- const currentPlayer = game.players.find((p) => p.id === currentLord.playerId);
9118
- if (!currentPlayer) return err(ErrorCode.PLAYER_NOT_FOUND);
9119
- if (game.queendomino.queenHolderId === currentPlayer.id) {
9120
- return err(ErrorCode.QUEEN_HOLDER_CANNOT_USE_DRAGON);
9121
- }
9122
- const result = destroyWithDragon(
9123
- game.queendomino.buildersBoard,
9124
- buildingId,
9125
- currentPlayer
9126
- );
9127
- if (!result) {
9128
- return err(ErrorCode.BUILDING_NOT_FOUND);
9129
- }
9130
- const { updatedBoard, updatedPlayer } = result;
9131
- const updatedPlayers = game.players.map(
9132
- (p) => p.id === currentPlayer.id ? updatedPlayer : p
9133
- );
9134
- const maxTurns = game.rules.basic.maxTurns;
9135
- const isLastTurn = game.turn === maxTurns;
9136
- if (isLastTurn && allLordsHavePlayed(game.lords)) {
9137
- return ok({
9138
- ...game,
9139
- players: updatedPlayers,
9140
- queendomino: {
9141
- ...game.queendomino,
9142
- buildersBoard: updatedBoard,
9143
- dragonUsedThisRound: true
9144
- },
9145
- nextAction: { type: "step", step: gameSteps.result }
9146
- });
9147
- }
9148
- return ok({
9149
- ...game,
9150
- players: updatedPlayers,
9151
- queendomino: {
9152
- ...game.queendomino,
9153
- buildersBoard: updatedBoard,
9154
- dragonUsedThisRound: true
9155
- },
9156
- nextAction: nextLordWithAction(game.lords)
9157
- });
9158
- };
9159
-
9160
9334
  // src/config.ts
9161
9335
  var configureEngine = (config) => {
9162
9336
  const modeRepository = jsonModes_default();
@@ -9249,6 +9423,13 @@ var configureEngine = (config) => {
9249
9423
  translator,
9250
9424
  canPlaceDominoUseCase
9251
9425
  ),
9426
+ getValidDominoPicksHandler: getValidActionTargetsHandler(
9427
+ logger,
9428
+ translator,
9429
+ "getValidDominoPicks",
9430
+ getValidDominoPicksUseCase,
9431
+ (numbers) => `dominoes [${numbers.join(", ")}]`
9432
+ ),
9252
9433
  serializeGameHandler: serializeGameHandler(logger, translator),
9253
9434
  deserializeGameHandler: deserializeGameHandler(logger, translator),
9254
9435
  getDynastyResultHandler: getDynastyResultHandler(
@@ -9312,6 +9493,13 @@ var configureEngine = (config) => {
9312
9493
  getAffordableBuildingsUseCase,
9313
9494
  (buildings) => `${buildings.length} buildings`
9314
9495
  ),
9496
+ getValidDragonTargetsHandler: getValidActionTargetsHandler(
9497
+ logger,
9498
+ translator,
9499
+ "getValidDragonTargets",
9500
+ getValidDragonTargetsUseCase,
9501
+ (buildings) => `${buildings.length} buildings`
9502
+ ),
9315
9503
  getValidCavemanPlacementsHandler: getValidActionTargetsHandler(
9316
9504
  logger,
9317
9505
  translator,
@@ -9654,6 +9842,15 @@ var validateDominoes = (game) => {
9654
9842
  )
9655
9843
  );
9656
9844
  }
9845
+ if (revealed.reservedFor !== void 0 && (!isNonEmptyString(revealed.reservedFor) || Array.isArray(game.players) && !game.players.some((p) => p.id === revealed.reservedFor))) {
9846
+ issues.push(
9847
+ issue(
9848
+ `${path}.reservedFor`,
9849
+ "RESERVED_PLAYER_NOT_FOUND",
9850
+ `Domino is reserved for a non-existent player: ${String(revealed.reservedFor)}`
9851
+ )
9852
+ );
9853
+ }
9657
9854
  }
9658
9855
  }
9659
9856
  if (Array.isArray(game.lords)) {
@@ -9872,15 +10069,10 @@ var buildingContext = (game, lordId) => ({
9872
10069
  validPositions: getValidBuildingPositionsUseCase(game, lordId),
9873
10070
  affordableBuildings: getAffordableBuildingsUseCase(game, lordId)
9874
10071
  });
9875
- var dragonContext = (game, lordId) => {
9876
- const slots = game.queendomino?.buildersBoard.slots ?? [];
9877
- return {
9878
- ...baseContext(game, lordId),
9879
- targetBuildings: slots.filter(
9880
- (slot) => slot !== null && isOk(useDragonUseCase(game, lordId, slot.id))
9881
- )
9882
- };
9883
- };
10072
+ var dragonContext = (game, lordId) => ({
10073
+ ...baseContext(game, lordId),
10074
+ targetBuildings: getValidDragonTargetsUseCase(game, lordId)
10075
+ });
9884
10076
  var fireTokenContext = (game, lordId) => ({
9885
10077
  ...baseContext(game, lordId),
9886
10078
  validPositions: getValidFireTokenPositionsUseCase(game, lordId)
@@ -10495,7 +10687,10 @@ var playBotTurn = (engine, game, strategy) => {
10495
10687
  const action = game.nextAction.nextAction;
10496
10688
  const defaults = greedyStrategy;
10497
10689
  if (action === "pickDomino") {
10498
- const availableDominoes = game.currentDominoes.filter((d) => !d.picked);
10690
+ const { lord } = getLordAndPlayer(game, lordId);
10691
+ const availableDominoes = game.currentDominoes.filter(
10692
+ (d) => lord ? canLordPickRevealed(lord, d) : !d.picked
10693
+ );
10499
10694
  const dominoPick = strategy.chooseDomino({
10500
10695
  game,
10501
10696
  lordId,
@@ -10669,6 +10864,7 @@ var wrapWithActionLog = (engine) => {
10669
10864
  calculateScore: (cmd) => engine.calculateScore(cmd),
10670
10865
  getValidPlacements: (cmd) => engine.getValidPlacements(cmd),
10671
10866
  canPlaceDomino: (cmd) => engine.canPlaceDomino(cmd),
10867
+ getValidDominoPicks: (cmd) => engine.getValidDominoPicks(cmd),
10672
10868
  serialize: (cmd) => engine.serialize(cmd),
10673
10869
  deserialize: (cmd) => engine.deserialize(cmd),
10674
10870
  getResults: (cmd) => engine.getResults(cmd),
@@ -10833,6 +11029,7 @@ var wrapWithActionLog = (engine) => {
10833
11029
  getValidKnightPositions: (cmd) => engine.getValidKnightPositions(cmd),
10834
11030
  getValidBuildingPositions: (cmd) => engine.getValidBuildingPositions(cmd),
10835
11031
  getAffordableBuildings: (cmd) => engine.getAffordableBuildings(cmd),
11032
+ getValidDragonTargets: (cmd) => engine.getValidDragonTargets(cmd),
10836
11033
  getValidCavemanPlacements: (cmd) => engine.getValidCavemanPlacements(cmd),
10837
11034
  getValidGiantPositions: (cmd) => engine.getValidGiantPositions(cmd),
10838
11035
  getValidGiantTargets: (cmd) => engine.getValidGiantTargets(cmd),
@@ -11068,6 +11265,7 @@ var createGameEngine = (config) => {
11068
11265
  calculateScoreHandler: calculateScoreHandler2,
11069
11266
  getValidPlacementsHandler: getValidPlacementsHandler2,
11070
11267
  canPlaceDominoHandler: canPlaceDominoHandler2,
11268
+ getValidDominoPicksHandler,
11071
11269
  serializeGameHandler: serializeGameHandler2,
11072
11270
  deserializeGameHandler: deserializeGameHandler2,
11073
11271
  getDynastyResultHandler: getDynastyResultHandler2,
@@ -11083,6 +11281,7 @@ var createGameEngine = (config) => {
11083
11281
  getValidKnightPositionsHandler,
11084
11282
  getValidBuildingPositionsHandler,
11085
11283
  getAffordableBuildingsHandler,
11284
+ getValidDragonTargetsHandler,
11086
11285
  getValidCavemanPlacementsHandler,
11087
11286
  getValidGiantPositionsHandler,
11088
11287
  getValidGiantTargetsHandler,
@@ -11103,6 +11302,7 @@ var createGameEngine = (config) => {
11103
11302
  calculateScore: (command) => calculateScoreHandler2(command),
11104
11303
  getValidPlacements: (command) => getValidPlacementsHandler2(command),
11105
11304
  canPlaceDomino: (command) => canPlaceDominoHandler2(command),
11305
+ getValidDominoPicks: (command) => getValidDominoPicksHandler(command),
11106
11306
  serialize: (command) => serializeGameHandler2(command),
11107
11307
  deserialize: (command) => deserializeGameHandler2(command),
11108
11308
  getDynastyResults: (command) => getDynastyResultHandler2(command),
@@ -11118,6 +11318,7 @@ var createGameEngine = (config) => {
11118
11318
  getValidKnightPositions: (command) => getValidKnightPositionsHandler(command),
11119
11319
  getValidBuildingPositions: (command) => getValidBuildingPositionsHandler(command),
11120
11320
  getAffordableBuildings: (command) => getAffordableBuildingsHandler(command),
11321
+ getValidDragonTargets: (command) => getValidDragonTargetsHandler(command),
11121
11322
  getValidCavemanPlacements: (command) => getValidCavemanPlacementsHandler(command),
11122
11323
  getValidGiantPositions: (command) => getValidGiantPositionsHandler(command),
11123
11324
  getValidGiantTargets: (command) => getValidGiantTargetsHandler(command),