@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.
- package/README.md +34 -17
- package/dist/index.d.ts +81 -4
- package/dist/index.js +346 -145
- package/dist/index.js.map +1 -1
- package/dist/index.universal.js +346 -145
- package/dist/wasm-bridge.js +325 -135
- package/package.json +1 -1
package/dist/index.universal.js
CHANGED
|
@@ -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) =>
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
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) =>
|
|
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
|
|
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
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7171
|
-
|
|
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 {
|
|
7222
|
-
if (!canAfford(player,
|
|
7223
|
-
const updatedPlayer = removeCoins(player,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 =
|
|
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
|
|
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
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
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
|
|
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),
|