@pompidup/kingdomino-engine 3.2.0 → 3.4.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/dist/index.d.ts +200 -11
- package/dist/index.js +1514 -151
- package/dist/index.js.map +1 -1
- package/dist/index.universal.js +5169 -0
- package/dist/wasm-bridge.js +4287 -0
- package/package.json +3 -2
|
@@ -0,0 +1,4287 @@
|
|
|
1
|
+
// src/core/domain/types/game.ts
|
|
2
|
+
var gameSteps = {
|
|
3
|
+
/** Waiting for players to be added */
|
|
4
|
+
addPlayers: "addPlayers",
|
|
5
|
+
/** Optional rules selection phase */
|
|
6
|
+
options: "options",
|
|
7
|
+
/** Ready to start the game */
|
|
8
|
+
start: "start",
|
|
9
|
+
/** Game has ended, results available */
|
|
10
|
+
result: "result"
|
|
11
|
+
};
|
|
12
|
+
var isGameWithNextAction = (game) => {
|
|
13
|
+
return game.nextAction.type === "action";
|
|
14
|
+
};
|
|
15
|
+
var isGameWithNextStep = (game) => {
|
|
16
|
+
return game.nextAction.type === "step";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/core/useCases/gameDebug.ts
|
|
20
|
+
var summarizeGameState = (game) => {
|
|
21
|
+
const nextAction = isGameWithNextAction(game) ? `action:${game.nextAction.nextAction}` : `step:${game.nextAction.step}`;
|
|
22
|
+
return {
|
|
23
|
+
gameId: game.id,
|
|
24
|
+
turn: game.turn,
|
|
25
|
+
nextAction,
|
|
26
|
+
playersCount: game.players.length,
|
|
27
|
+
dominoesRemaining: game.dominoes.length,
|
|
28
|
+
currentDominoesCount: game.currentDominoes.length,
|
|
29
|
+
lordsCount: game.lords.length
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
var defaultDebugLogger = {
|
|
33
|
+
log: (entry) => {
|
|
34
|
+
const phase = entry.phase.toUpperCase().padEnd(5);
|
|
35
|
+
const base = `[KDomino:Debug] ${entry.timestamp} ${phase} ${entry.method}`;
|
|
36
|
+
if (entry.phase === "error") {
|
|
37
|
+
console.error(`${base} | error: ${entry.error}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (entry.summary) {
|
|
41
|
+
const s = entry.summary;
|
|
42
|
+
console.log(
|
|
43
|
+
`${base} | turn=${s.turn} next=${s.nextAction} players=${s.playersCount} dominoes=${s.dominoesRemaining}`
|
|
44
|
+
);
|
|
45
|
+
} else {
|
|
46
|
+
console.log(base);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var isGameState = (value) => {
|
|
51
|
+
return typeof value === "object" && value !== null && "id" in value && "nextAction" in value && "players" in value && "dominoes" in value;
|
|
52
|
+
};
|
|
53
|
+
var hasGameInCommand = (cmd) => {
|
|
54
|
+
return typeof cmd === "object" && cmd !== null && "game" in cmd && isGameState(cmd.game);
|
|
55
|
+
};
|
|
56
|
+
var extractParams = (cmd) => {
|
|
57
|
+
if (typeof cmd !== "object" || cmd === null) return void 0;
|
|
58
|
+
const record = cmd;
|
|
59
|
+
const params = {};
|
|
60
|
+
if ("mode" in record) params.mode = record.mode;
|
|
61
|
+
if ("lordId" in record) params.lordId = record.lordId;
|
|
62
|
+
if ("dominoPick" in record) params.dominoPick = record.dominoPick;
|
|
63
|
+
if ("position" in record) params.position = record.position;
|
|
64
|
+
if ("rotation" in record) params.rotation = record.rotation;
|
|
65
|
+
if ("players" in record) params.players = record.players;
|
|
66
|
+
if ("extraRules" in record) params.extraRules = record.extraRules;
|
|
67
|
+
return Object.keys(params).length > 0 ? params : void 0;
|
|
68
|
+
};
|
|
69
|
+
var wrapMethod = (method, fn, logger, level) => {
|
|
70
|
+
return (cmd) => {
|
|
71
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
72
|
+
const beforeSummary = level !== "minimal" && hasGameInCommand(cmd) ? summarizeGameState(cmd.game) : null;
|
|
73
|
+
const beforeEntry = {
|
|
74
|
+
timestamp,
|
|
75
|
+
method,
|
|
76
|
+
phase: "before",
|
|
77
|
+
summary: beforeSummary,
|
|
78
|
+
params: level !== "minimal" ? extractParams(cmd) : void 0
|
|
79
|
+
};
|
|
80
|
+
logger.log(beforeEntry);
|
|
81
|
+
const start = performance.now();
|
|
82
|
+
let result;
|
|
83
|
+
try {
|
|
84
|
+
result = fn(cmd);
|
|
85
|
+
} catch (err3) {
|
|
86
|
+
const durationMs2 = Math.round((performance.now() - start) * 100) / 100;
|
|
87
|
+
const errorEntry = {
|
|
88
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
89
|
+
method,
|
|
90
|
+
phase: "error",
|
|
91
|
+
summary: beforeSummary,
|
|
92
|
+
error: err3 instanceof Error ? err3.message : String(err3),
|
|
93
|
+
durationMs: durationMs2
|
|
94
|
+
};
|
|
95
|
+
logger.log(errorEntry);
|
|
96
|
+
throw err3;
|
|
97
|
+
}
|
|
98
|
+
const durationMs = Math.round((performance.now() - start) * 100) / 100;
|
|
99
|
+
const afterSummary = level !== "minimal" && isGameState(result) ? summarizeGameState(result) : null;
|
|
100
|
+
const afterEntry = {
|
|
101
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
102
|
+
method,
|
|
103
|
+
phase: "after",
|
|
104
|
+
summary: afterSummary,
|
|
105
|
+
durationMs,
|
|
106
|
+
fullState: level === "verbose" && isGameState(result) ? result : void 0
|
|
107
|
+
};
|
|
108
|
+
logger.log(afterEntry);
|
|
109
|
+
return result;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
var wrapWithDebug = (engine2, options) => {
|
|
113
|
+
const logger = options?.logger ?? defaultDebugLogger;
|
|
114
|
+
const level = options?.level ?? "standard";
|
|
115
|
+
return {
|
|
116
|
+
getModes: wrapMethod("getModes", engine2.getModes, logger, level),
|
|
117
|
+
getExtraRules: wrapMethod(
|
|
118
|
+
"getExtraRules",
|
|
119
|
+
engine2.getExtraRules,
|
|
120
|
+
logger,
|
|
121
|
+
level
|
|
122
|
+
),
|
|
123
|
+
createGame: wrapMethod("createGame", engine2.createGame, logger, level),
|
|
124
|
+
addPlayers: wrapMethod("addPlayers", engine2.addPlayers, logger, level),
|
|
125
|
+
addExtraRules: wrapMethod(
|
|
126
|
+
"addExtraRules",
|
|
127
|
+
engine2.addExtraRules,
|
|
128
|
+
logger,
|
|
129
|
+
level
|
|
130
|
+
),
|
|
131
|
+
startGame: wrapMethod("startGame", engine2.startGame, logger, level),
|
|
132
|
+
chooseDomino: wrapMethod(
|
|
133
|
+
"chooseDomino",
|
|
134
|
+
engine2.chooseDomino,
|
|
135
|
+
logger,
|
|
136
|
+
level
|
|
137
|
+
),
|
|
138
|
+
placeDomino: wrapMethod("placeDomino", engine2.placeDomino, logger, level),
|
|
139
|
+
discardDomino: wrapMethod(
|
|
140
|
+
"discardDomino",
|
|
141
|
+
engine2.discardDomino,
|
|
142
|
+
logger,
|
|
143
|
+
level
|
|
144
|
+
),
|
|
145
|
+
getResults: wrapMethod("getResults", engine2.getResults, logger, level),
|
|
146
|
+
calculateScore: wrapMethod(
|
|
147
|
+
"calculateScore",
|
|
148
|
+
engine2.calculateScore,
|
|
149
|
+
logger,
|
|
150
|
+
level
|
|
151
|
+
),
|
|
152
|
+
getValidPlacements: wrapMethod(
|
|
153
|
+
"getValidPlacements",
|
|
154
|
+
engine2.getValidPlacements,
|
|
155
|
+
logger,
|
|
156
|
+
level
|
|
157
|
+
),
|
|
158
|
+
canPlaceDomino: wrapMethod(
|
|
159
|
+
"canPlaceDomino",
|
|
160
|
+
engine2.canPlaceDomino,
|
|
161
|
+
logger,
|
|
162
|
+
level
|
|
163
|
+
),
|
|
164
|
+
serialize: wrapMethod("serialize", engine2.serialize, logger, level),
|
|
165
|
+
deserialize: wrapMethod("deserialize", engine2.deserialize, logger, level),
|
|
166
|
+
getDynastyResults: wrapMethod(
|
|
167
|
+
"getDynastyResults",
|
|
168
|
+
engine2.getDynastyResults,
|
|
169
|
+
logger,
|
|
170
|
+
level
|
|
171
|
+
),
|
|
172
|
+
placeKnight: wrapMethod(
|
|
173
|
+
"placeKnight",
|
|
174
|
+
engine2.placeKnight,
|
|
175
|
+
logger,
|
|
176
|
+
level
|
|
177
|
+
),
|
|
178
|
+
constructBuilding: wrapMethod(
|
|
179
|
+
"constructBuilding",
|
|
180
|
+
engine2.constructBuilding,
|
|
181
|
+
logger,
|
|
182
|
+
level
|
|
183
|
+
),
|
|
184
|
+
useDragon: wrapMethod("useDragon", engine2.useDragon, logger, level),
|
|
185
|
+
skipOptionalAction: wrapMethod(
|
|
186
|
+
"skipOptionalAction",
|
|
187
|
+
engine2.skipOptionalAction,
|
|
188
|
+
logger,
|
|
189
|
+
level
|
|
190
|
+
)
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// src/core/useCases/gameEvents.ts
|
|
195
|
+
var isGameEnded = (game) => isGameWithNextStep(game) && game.nextAction.step === "result";
|
|
196
|
+
var wrapWithEvents = (engine2, callbacks) => ({
|
|
197
|
+
// Read-only methods — pass through
|
|
198
|
+
getModes: (cmd) => engine2.getModes(cmd),
|
|
199
|
+
getExtraRules: (cmd) => engine2.getExtraRules(cmd),
|
|
200
|
+
calculateScore: (cmd) => engine2.calculateScore(cmd),
|
|
201
|
+
getValidPlacements: (cmd) => engine2.getValidPlacements(cmd),
|
|
202
|
+
canPlaceDomino: (cmd) => engine2.canPlaceDomino(cmd),
|
|
203
|
+
serialize: (cmd) => engine2.serialize(cmd),
|
|
204
|
+
deserialize: (cmd) => engine2.deserialize(cmd),
|
|
205
|
+
getResults: (cmd) => engine2.getResults(cmd),
|
|
206
|
+
getDynastyResults: (cmd) => engine2.getDynastyResults(cmd),
|
|
207
|
+
// State-changing methods — emit events after execution
|
|
208
|
+
createGame: (cmd) => {
|
|
209
|
+
const game = engine2.createGame(cmd);
|
|
210
|
+
callbacks.onGameCreated?.({ game });
|
|
211
|
+
return game;
|
|
212
|
+
},
|
|
213
|
+
addPlayers: (cmd) => {
|
|
214
|
+
const game = engine2.addPlayers(cmd);
|
|
215
|
+
callbacks.onPlayersAdded?.({ game });
|
|
216
|
+
return game;
|
|
217
|
+
},
|
|
218
|
+
addExtraRules: (cmd) => engine2.addExtraRules(cmd),
|
|
219
|
+
startGame: (cmd) => {
|
|
220
|
+
const game = engine2.startGame(cmd);
|
|
221
|
+
callbacks.onGameStarted?.({ game });
|
|
222
|
+
callbacks.onTurnStart?.({ game, turn: game.turn });
|
|
223
|
+
return game;
|
|
224
|
+
},
|
|
225
|
+
chooseDomino: (cmd) => {
|
|
226
|
+
const turnBefore = cmd.game.turn;
|
|
227
|
+
const game = engine2.chooseDomino(cmd);
|
|
228
|
+
callbacks.onDominoPicked?.({
|
|
229
|
+
game,
|
|
230
|
+
lordId: cmd.lordId,
|
|
231
|
+
dominoNumber: cmd.dominoPick
|
|
232
|
+
});
|
|
233
|
+
if (game.turn !== turnBefore) {
|
|
234
|
+
callbacks.onTurnEnd?.({ game, turn: turnBefore });
|
|
235
|
+
callbacks.onTurnStart?.({ game, turn: game.turn });
|
|
236
|
+
}
|
|
237
|
+
return game;
|
|
238
|
+
},
|
|
239
|
+
placeDomino: (cmd) => {
|
|
240
|
+
const game = engine2.placeDomino(cmd);
|
|
241
|
+
callbacks.onDominoPlaced?.({ game, lordId: cmd.lordId });
|
|
242
|
+
if (isGameEnded(game)) {
|
|
243
|
+
callbacks.onGameEnd?.({ game });
|
|
244
|
+
}
|
|
245
|
+
return game;
|
|
246
|
+
},
|
|
247
|
+
discardDomino: (cmd) => {
|
|
248
|
+
const game = engine2.discardDomino(cmd);
|
|
249
|
+
callbacks.onDominoDiscarded?.({ game, lordId: cmd.lordId });
|
|
250
|
+
if (isGameEnded(game)) {
|
|
251
|
+
callbacks.onGameEnd?.({ game });
|
|
252
|
+
}
|
|
253
|
+
return game;
|
|
254
|
+
},
|
|
255
|
+
// Queendomino methods — pass through (no specific events yet)
|
|
256
|
+
placeKnight: (cmd) => engine2.placeKnight(cmd),
|
|
257
|
+
constructBuilding: (cmd) => engine2.constructBuilding(cmd),
|
|
258
|
+
useDragon: (cmd) => engine2.useDragon(cmd),
|
|
259
|
+
skipOptionalAction: (cmd) => engine2.skipOptionalAction(cmd)
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// src/adapterServerside/consoleLogger.ts
|
|
263
|
+
var consoleLogger = (isLoggingEnabled) => {
|
|
264
|
+
const formatMessage = (level, message) => {
|
|
265
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
266
|
+
return `${timestamp} [KDomino_Engine] ${level}: ${message}`;
|
|
267
|
+
};
|
|
268
|
+
return {
|
|
269
|
+
info: (message) => {
|
|
270
|
+
if (isLoggingEnabled) {
|
|
271
|
+
console.log(formatMessage("info", message));
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
error: (message) => {
|
|
275
|
+
if (isLoggingEnabled) {
|
|
276
|
+
console.error(formatMessage("error", message));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
var silentLogger = {
|
|
282
|
+
info: () => {
|
|
283
|
+
},
|
|
284
|
+
error: () => {
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// src/datasources/buildings.json
|
|
289
|
+
var buildings_default = [
|
|
290
|
+
{ id: 1, name: "The Bakery", cost: 1, immediateBonus: { type: "coins", amount: 2 }, crowns: 0, towers: 0 },
|
|
291
|
+
{ id: 2, name: "The Bakery", cost: 1, immediateBonus: { type: "coins", amount: 2 }, crowns: 0, towers: 0 },
|
|
292
|
+
{ id: 3, name: "The Sawmill", cost: 2, crowns: 0, towers: 0, endGameScoring: { type: "perTerrain", terrain: "forest", points: 1 } },
|
|
293
|
+
{ id: 4, name: "The Fishmonger's", cost: 2, immediateBonus: { type: "coins", amount: 1 }, crowns: 0, towers: 0, endGameScoring: { type: "perTerrain", terrain: "sea", points: 1 } },
|
|
294
|
+
{ id: 5, name: "The Fishmonger's", cost: 2, immediateBonus: { type: "coins", amount: 1 }, crowns: 0, towers: 0, endGameScoring: { type: "perTerrain", terrain: "sea", points: 1 } },
|
|
295
|
+
{ id: 6, name: "The Weaving Shop", cost: 3, crowns: 1, towers: 0, endGameScoring: { type: "perTerrain", terrain: "wheat", points: 1 } },
|
|
296
|
+
{ id: 7, name: "The Magic School", cost: 4, crowns: 1, towers: 0, endGameScoring: { type: "perCrown", points: 1 } },
|
|
297
|
+
{ id: 8, name: "The Fountain", cost: 3, crowns: 0, towers: 1, endGameScoring: { type: "perTower", points: 2 } },
|
|
298
|
+
{ id: 9, name: "The Church", cost: 4, crowns: 1, towers: 0, endGameScoring: { type: "perBuilding", points: 2 } },
|
|
299
|
+
{ id: 10, name: "The Bath", cost: 2, immediateBonus: { type: "coins", amount: 1 }, crowns: 0, towers: 0, endGameScoring: { type: "flat", points: 4 } },
|
|
300
|
+
{ id: 11, name: "The Guard Tower", cost: 1, crowns: 0, towers: 1, endGameScoring: { type: "flat", points: 2 } },
|
|
301
|
+
{ id: 12, name: "The Guard Tower", cost: 1, crowns: 0, towers: 1, endGameScoring: { type: "flat", points: 2 } },
|
|
302
|
+
{ id: 13, name: "The Big Tower", cost: 3, crowns: 0, towers: 2, endGameScoring: { type: "flat", points: 4 } },
|
|
303
|
+
{ id: 14, name: "The Guardhouse", cost: 2, crowns: 0, towers: 1, endGameScoring: { type: "perBuilding", points: 1 } },
|
|
304
|
+
{ id: 15, name: "The Fortified Castle", cost: 5, crowns: 2, towers: 1, endGameScoring: { type: "flat", points: 6 } },
|
|
305
|
+
{ id: 16, name: "The Travelling Castle", cost: 3, crowns: 1, towers: 1, endGameScoring: { type: "perTower", points: 1 } },
|
|
306
|
+
{ id: 17, name: "The Travelling Group", cost: 2, immediateBonus: { type: "coins", amount: 1 }, crowns: 0, towers: 0, endGameScoring: { type: "perBuilding", points: 1 } },
|
|
307
|
+
{ id: 18, name: "The Harbour", cost: 3, crowns: 0, towers: 0, endGameScoring: { type: "perTerrain", terrain: "sea", points: 2 } }
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
// src/adapterServerside/jsonBuildings.ts
|
|
311
|
+
var jsonBuildings = () => {
|
|
312
|
+
const buildings = buildings_default.map((b) => {
|
|
313
|
+
const tile = {
|
|
314
|
+
id: b.id,
|
|
315
|
+
name: b.name,
|
|
316
|
+
cost: b.cost,
|
|
317
|
+
crowns: b.crowns,
|
|
318
|
+
towers: b.towers
|
|
319
|
+
};
|
|
320
|
+
if (b.immediateBonus) {
|
|
321
|
+
tile.immediateBonus = {
|
|
322
|
+
type: b.immediateBonus.type,
|
|
323
|
+
amount: b.immediateBonus.amount
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
if ("endGameScoring" in b && b.endGameScoring) {
|
|
327
|
+
tile.endGameScoring = {
|
|
328
|
+
type: b.endGameScoring.type,
|
|
329
|
+
points: b.endGameScoring.points,
|
|
330
|
+
..."terrain" in b.endGameScoring && {
|
|
331
|
+
terrain: b.endGameScoring.terrain
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
return tile;
|
|
336
|
+
});
|
|
337
|
+
return {
|
|
338
|
+
getAll: () => buildings
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
var jsonBuildings_default = jsonBuildings;
|
|
342
|
+
|
|
343
|
+
// src/datasources/dominoes.json
|
|
344
|
+
var dominoes_default = {
|
|
345
|
+
Classic: [
|
|
346
|
+
{
|
|
347
|
+
left: {
|
|
348
|
+
type: "wheat",
|
|
349
|
+
crowns: 0
|
|
350
|
+
},
|
|
351
|
+
right: {
|
|
352
|
+
type: "wheat",
|
|
353
|
+
crowns: 0
|
|
354
|
+
},
|
|
355
|
+
number: 1
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
left: {
|
|
359
|
+
type: "wheat",
|
|
360
|
+
crowns: 0
|
|
361
|
+
},
|
|
362
|
+
right: {
|
|
363
|
+
type: "wheat",
|
|
364
|
+
crowns: 0
|
|
365
|
+
},
|
|
366
|
+
number: 2
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
left: {
|
|
370
|
+
type: "forest",
|
|
371
|
+
crowns: 0
|
|
372
|
+
},
|
|
373
|
+
right: {
|
|
374
|
+
type: "forest",
|
|
375
|
+
crowns: 0
|
|
376
|
+
},
|
|
377
|
+
number: 3
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
left: {
|
|
381
|
+
type: "forest",
|
|
382
|
+
crowns: 0
|
|
383
|
+
},
|
|
384
|
+
right: {
|
|
385
|
+
type: "forest",
|
|
386
|
+
crowns: 0
|
|
387
|
+
},
|
|
388
|
+
number: 4
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
left: {
|
|
392
|
+
type: "forest",
|
|
393
|
+
crowns: 0
|
|
394
|
+
},
|
|
395
|
+
right: {
|
|
396
|
+
type: "forest",
|
|
397
|
+
crowns: 0
|
|
398
|
+
},
|
|
399
|
+
number: 5
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
left: {
|
|
403
|
+
type: "forest",
|
|
404
|
+
crowns: 0
|
|
405
|
+
},
|
|
406
|
+
right: {
|
|
407
|
+
type: "forest",
|
|
408
|
+
crowns: 0
|
|
409
|
+
},
|
|
410
|
+
number: 6
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
left: {
|
|
414
|
+
type: "sea",
|
|
415
|
+
crowns: 0
|
|
416
|
+
},
|
|
417
|
+
right: {
|
|
418
|
+
type: "sea",
|
|
419
|
+
crowns: 0
|
|
420
|
+
},
|
|
421
|
+
number: 7
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
left: {
|
|
425
|
+
type: "sea",
|
|
426
|
+
crowns: 0
|
|
427
|
+
},
|
|
428
|
+
right: {
|
|
429
|
+
type: "sea",
|
|
430
|
+
crowns: 0
|
|
431
|
+
},
|
|
432
|
+
number: 8
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
left: {
|
|
436
|
+
type: "sea",
|
|
437
|
+
crowns: 0
|
|
438
|
+
},
|
|
439
|
+
right: {
|
|
440
|
+
type: "sea",
|
|
441
|
+
crowns: 0
|
|
442
|
+
},
|
|
443
|
+
number: 9
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
left: {
|
|
447
|
+
type: "plain",
|
|
448
|
+
crowns: 0
|
|
449
|
+
},
|
|
450
|
+
right: {
|
|
451
|
+
type: "plain",
|
|
452
|
+
crowns: 0
|
|
453
|
+
},
|
|
454
|
+
number: 10
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
left: {
|
|
458
|
+
type: "plain",
|
|
459
|
+
crowns: 0
|
|
460
|
+
},
|
|
461
|
+
right: {
|
|
462
|
+
type: "plain",
|
|
463
|
+
crowns: 0
|
|
464
|
+
},
|
|
465
|
+
number: 11
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
left: {
|
|
469
|
+
type: "swamp",
|
|
470
|
+
crowns: 0
|
|
471
|
+
},
|
|
472
|
+
right: {
|
|
473
|
+
type: "swamp",
|
|
474
|
+
crowns: 0
|
|
475
|
+
},
|
|
476
|
+
number: 12
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
left: {
|
|
480
|
+
type: "wheat",
|
|
481
|
+
crowns: 0
|
|
482
|
+
},
|
|
483
|
+
right: {
|
|
484
|
+
type: "forest",
|
|
485
|
+
crowns: 0
|
|
486
|
+
},
|
|
487
|
+
number: 13
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
left: {
|
|
491
|
+
type: "wheat",
|
|
492
|
+
crowns: 0
|
|
493
|
+
},
|
|
494
|
+
right: {
|
|
495
|
+
type: "sea",
|
|
496
|
+
crowns: 0
|
|
497
|
+
},
|
|
498
|
+
number: 14
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
left: {
|
|
502
|
+
type: "wheat",
|
|
503
|
+
crowns: 0
|
|
504
|
+
},
|
|
505
|
+
right: {
|
|
506
|
+
type: "plain",
|
|
507
|
+
crowns: 0
|
|
508
|
+
},
|
|
509
|
+
number: 15
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
left: {
|
|
513
|
+
type: "wheat",
|
|
514
|
+
crowns: 0
|
|
515
|
+
},
|
|
516
|
+
right: {
|
|
517
|
+
type: "swamp",
|
|
518
|
+
crowns: 0
|
|
519
|
+
},
|
|
520
|
+
number: 16
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
left: {
|
|
524
|
+
type: "forest",
|
|
525
|
+
crowns: 0
|
|
526
|
+
},
|
|
527
|
+
right: {
|
|
528
|
+
type: "sea",
|
|
529
|
+
crowns: 0
|
|
530
|
+
},
|
|
531
|
+
number: 17
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
left: {
|
|
535
|
+
type: "forest",
|
|
536
|
+
crowns: 0
|
|
537
|
+
},
|
|
538
|
+
right: {
|
|
539
|
+
type: "plain",
|
|
540
|
+
crowns: 0
|
|
541
|
+
},
|
|
542
|
+
number: 18
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
left: {
|
|
546
|
+
type: "wheat",
|
|
547
|
+
crowns: 1
|
|
548
|
+
},
|
|
549
|
+
right: {
|
|
550
|
+
type: "forest",
|
|
551
|
+
crowns: 0
|
|
552
|
+
},
|
|
553
|
+
number: 19
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
left: {
|
|
557
|
+
type: "wheat",
|
|
558
|
+
crowns: 1
|
|
559
|
+
},
|
|
560
|
+
right: {
|
|
561
|
+
type: "sea",
|
|
562
|
+
crowns: 0
|
|
563
|
+
},
|
|
564
|
+
number: 20
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
left: {
|
|
568
|
+
type: "wheat",
|
|
569
|
+
crowns: 1
|
|
570
|
+
},
|
|
571
|
+
right: {
|
|
572
|
+
type: "plain",
|
|
573
|
+
crowns: 0
|
|
574
|
+
},
|
|
575
|
+
number: 21
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
left: {
|
|
579
|
+
type: "wheat",
|
|
580
|
+
crowns: 1
|
|
581
|
+
},
|
|
582
|
+
right: {
|
|
583
|
+
type: "swamp",
|
|
584
|
+
crowns: 0
|
|
585
|
+
},
|
|
586
|
+
number: 22
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
left: {
|
|
590
|
+
type: "wheat",
|
|
591
|
+
crowns: 1
|
|
592
|
+
},
|
|
593
|
+
right: {
|
|
594
|
+
type: "mine",
|
|
595
|
+
crowns: 0
|
|
596
|
+
},
|
|
597
|
+
number: 23
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
left: {
|
|
601
|
+
type: "forest",
|
|
602
|
+
crowns: 1
|
|
603
|
+
},
|
|
604
|
+
right: {
|
|
605
|
+
type: "wheat",
|
|
606
|
+
crowns: 0
|
|
607
|
+
},
|
|
608
|
+
number: 24
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
left: {
|
|
612
|
+
type: "forest",
|
|
613
|
+
crowns: 1
|
|
614
|
+
},
|
|
615
|
+
right: {
|
|
616
|
+
type: "wheat",
|
|
617
|
+
crowns: 0
|
|
618
|
+
},
|
|
619
|
+
number: 25
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
left: {
|
|
623
|
+
type: "forest",
|
|
624
|
+
crowns: 1
|
|
625
|
+
},
|
|
626
|
+
right: {
|
|
627
|
+
type: "wheat",
|
|
628
|
+
crowns: 0
|
|
629
|
+
},
|
|
630
|
+
number: 26
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
left: {
|
|
634
|
+
type: "forest",
|
|
635
|
+
crowns: 1
|
|
636
|
+
},
|
|
637
|
+
right: {
|
|
638
|
+
type: "wheat",
|
|
639
|
+
crowns: 0
|
|
640
|
+
},
|
|
641
|
+
number: 27
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
left: {
|
|
645
|
+
type: "forest",
|
|
646
|
+
crowns: 1
|
|
647
|
+
},
|
|
648
|
+
right: {
|
|
649
|
+
type: "sea",
|
|
650
|
+
crowns: 0
|
|
651
|
+
},
|
|
652
|
+
number: 28
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
left: {
|
|
656
|
+
type: "forest",
|
|
657
|
+
crowns: 1
|
|
658
|
+
},
|
|
659
|
+
right: {
|
|
660
|
+
type: "plain",
|
|
661
|
+
crowns: 0
|
|
662
|
+
},
|
|
663
|
+
number: 29
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
left: {
|
|
667
|
+
type: "sea",
|
|
668
|
+
crowns: 1
|
|
669
|
+
},
|
|
670
|
+
right: {
|
|
671
|
+
type: "wheat",
|
|
672
|
+
crowns: 0
|
|
673
|
+
},
|
|
674
|
+
number: 30
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
left: {
|
|
678
|
+
type: "sea",
|
|
679
|
+
crowns: 1
|
|
680
|
+
},
|
|
681
|
+
right: {
|
|
682
|
+
type: "wheat",
|
|
683
|
+
crowns: 0
|
|
684
|
+
},
|
|
685
|
+
number: 31
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
left: {
|
|
689
|
+
type: "sea",
|
|
690
|
+
crowns: 1
|
|
691
|
+
},
|
|
692
|
+
right: {
|
|
693
|
+
type: "forest",
|
|
694
|
+
crowns: 0
|
|
695
|
+
},
|
|
696
|
+
number: 32
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
left: {
|
|
700
|
+
type: "sea",
|
|
701
|
+
crowns: 1
|
|
702
|
+
},
|
|
703
|
+
right: {
|
|
704
|
+
type: "forest",
|
|
705
|
+
crowns: 0
|
|
706
|
+
},
|
|
707
|
+
number: 33
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
left: {
|
|
711
|
+
type: "sea",
|
|
712
|
+
crowns: 1
|
|
713
|
+
},
|
|
714
|
+
right: {
|
|
715
|
+
type: "forest",
|
|
716
|
+
crowns: 0
|
|
717
|
+
},
|
|
718
|
+
number: 34
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
left: {
|
|
722
|
+
type: "sea",
|
|
723
|
+
crowns: 1
|
|
724
|
+
},
|
|
725
|
+
right: {
|
|
726
|
+
type: "forest",
|
|
727
|
+
crowns: 0
|
|
728
|
+
},
|
|
729
|
+
number: 35
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
left: {
|
|
733
|
+
type: "wheat",
|
|
734
|
+
crowns: 0
|
|
735
|
+
},
|
|
736
|
+
right: {
|
|
737
|
+
type: "plain",
|
|
738
|
+
crowns: 1
|
|
739
|
+
},
|
|
740
|
+
number: 36
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
left: {
|
|
744
|
+
type: "sea",
|
|
745
|
+
crowns: 0
|
|
746
|
+
},
|
|
747
|
+
right: {
|
|
748
|
+
type: "plain",
|
|
749
|
+
crowns: 1
|
|
750
|
+
},
|
|
751
|
+
number: 37
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
left: {
|
|
755
|
+
type: "wheat",
|
|
756
|
+
crowns: 0
|
|
757
|
+
},
|
|
758
|
+
right: {
|
|
759
|
+
type: "swamp",
|
|
760
|
+
crowns: 1
|
|
761
|
+
},
|
|
762
|
+
number: 38
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
left: {
|
|
766
|
+
type: "plain",
|
|
767
|
+
crowns: 0
|
|
768
|
+
},
|
|
769
|
+
right: {
|
|
770
|
+
type: "swamp",
|
|
771
|
+
crowns: 0
|
|
772
|
+
},
|
|
773
|
+
number: 39
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
left: {
|
|
777
|
+
type: "mine",
|
|
778
|
+
crowns: 1
|
|
779
|
+
},
|
|
780
|
+
right: {
|
|
781
|
+
type: "wheat",
|
|
782
|
+
crowns: 0
|
|
783
|
+
},
|
|
784
|
+
number: 40
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
left: {
|
|
788
|
+
type: "wheat",
|
|
789
|
+
crowns: 0
|
|
790
|
+
},
|
|
791
|
+
right: {
|
|
792
|
+
type: "plain",
|
|
793
|
+
crowns: 2
|
|
794
|
+
},
|
|
795
|
+
number: 41
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
left: {
|
|
799
|
+
type: "sea",
|
|
800
|
+
crowns: 0
|
|
801
|
+
},
|
|
802
|
+
right: {
|
|
803
|
+
type: "plain",
|
|
804
|
+
crowns: 2
|
|
805
|
+
},
|
|
806
|
+
number: 42
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
left: {
|
|
810
|
+
type: "wheat",
|
|
811
|
+
crowns: 0
|
|
812
|
+
},
|
|
813
|
+
right: {
|
|
814
|
+
type: "swamp",
|
|
815
|
+
crowns: 2
|
|
816
|
+
},
|
|
817
|
+
number: 43
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
left: {
|
|
821
|
+
type: "plain",
|
|
822
|
+
crowns: 0
|
|
823
|
+
},
|
|
824
|
+
right: {
|
|
825
|
+
type: "swamp",
|
|
826
|
+
crowns: 2
|
|
827
|
+
},
|
|
828
|
+
number: 44
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
left: {
|
|
832
|
+
type: "mine",
|
|
833
|
+
crowns: 2
|
|
834
|
+
},
|
|
835
|
+
right: {
|
|
836
|
+
type: "wheat",
|
|
837
|
+
crowns: 0
|
|
838
|
+
},
|
|
839
|
+
number: 45
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
left: {
|
|
843
|
+
type: "swamp",
|
|
844
|
+
crowns: 0
|
|
845
|
+
},
|
|
846
|
+
right: {
|
|
847
|
+
type: "mine",
|
|
848
|
+
crowns: 2
|
|
849
|
+
},
|
|
850
|
+
number: 46
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
left: {
|
|
854
|
+
type: "swamp",
|
|
855
|
+
crowns: 0
|
|
856
|
+
},
|
|
857
|
+
right: {
|
|
858
|
+
type: "mine",
|
|
859
|
+
crowns: 2
|
|
860
|
+
},
|
|
861
|
+
number: 47
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
left: {
|
|
865
|
+
type: "wheat",
|
|
866
|
+
crowns: 0
|
|
867
|
+
},
|
|
868
|
+
right: {
|
|
869
|
+
type: "mine",
|
|
870
|
+
crowns: 3
|
|
871
|
+
},
|
|
872
|
+
number: 48
|
|
873
|
+
}
|
|
874
|
+
],
|
|
875
|
+
QueenDomino: [
|
|
876
|
+
{
|
|
877
|
+
left: {
|
|
878
|
+
type: "wheat",
|
|
879
|
+
crowns: 0
|
|
880
|
+
},
|
|
881
|
+
right: {
|
|
882
|
+
type: "wheat",
|
|
883
|
+
crowns: 0
|
|
884
|
+
},
|
|
885
|
+
number: 1
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
left: {
|
|
889
|
+
type: "wheat",
|
|
890
|
+
crowns: 0
|
|
891
|
+
},
|
|
892
|
+
right: {
|
|
893
|
+
type: "wheat",
|
|
894
|
+
crowns: 0
|
|
895
|
+
},
|
|
896
|
+
number: 2
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
left: {
|
|
900
|
+
type: "forest",
|
|
901
|
+
crowns: 0
|
|
902
|
+
},
|
|
903
|
+
right: {
|
|
904
|
+
type: "forest",
|
|
905
|
+
crowns: 0
|
|
906
|
+
},
|
|
907
|
+
number: 3
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
left: {
|
|
911
|
+
type: "forest",
|
|
912
|
+
crowns: 0
|
|
913
|
+
},
|
|
914
|
+
right: {
|
|
915
|
+
type: "forest",
|
|
916
|
+
crowns: 0
|
|
917
|
+
},
|
|
918
|
+
number: 4
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
left: {
|
|
922
|
+
type: "sea",
|
|
923
|
+
crowns: 0
|
|
924
|
+
},
|
|
925
|
+
right: {
|
|
926
|
+
type: "sea",
|
|
927
|
+
crowns: 0
|
|
928
|
+
},
|
|
929
|
+
number: 5
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
left: {
|
|
933
|
+
type: "sea",
|
|
934
|
+
crowns: 0
|
|
935
|
+
},
|
|
936
|
+
right: {
|
|
937
|
+
type: "sea",
|
|
938
|
+
crowns: 0
|
|
939
|
+
},
|
|
940
|
+
number: 6
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
left: {
|
|
944
|
+
type: "plain",
|
|
945
|
+
crowns: 0
|
|
946
|
+
},
|
|
947
|
+
right: {
|
|
948
|
+
type: "plain",
|
|
949
|
+
crowns: 0
|
|
950
|
+
},
|
|
951
|
+
number: 7
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
left: {
|
|
955
|
+
type: "plain",
|
|
956
|
+
crowns: 0
|
|
957
|
+
},
|
|
958
|
+
right: {
|
|
959
|
+
type: "plain",
|
|
960
|
+
crowns: 0
|
|
961
|
+
},
|
|
962
|
+
number: 8
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
left: {
|
|
966
|
+
type: "swamp",
|
|
967
|
+
crowns: 0
|
|
968
|
+
},
|
|
969
|
+
right: {
|
|
970
|
+
type: "swamp",
|
|
971
|
+
crowns: 0
|
|
972
|
+
},
|
|
973
|
+
number: 9
|
|
974
|
+
},
|
|
975
|
+
{
|
|
976
|
+
left: {
|
|
977
|
+
type: "swamp",
|
|
978
|
+
crowns: 0
|
|
979
|
+
},
|
|
980
|
+
right: {
|
|
981
|
+
type: "swamp",
|
|
982
|
+
crowns: 0
|
|
983
|
+
},
|
|
984
|
+
number: 10
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
left: {
|
|
988
|
+
type: "mine",
|
|
989
|
+
crowns: 0
|
|
990
|
+
},
|
|
991
|
+
right: {
|
|
992
|
+
type: "mine",
|
|
993
|
+
crowns: 0
|
|
994
|
+
},
|
|
995
|
+
number: 11
|
|
996
|
+
},
|
|
997
|
+
{
|
|
998
|
+
left: {
|
|
999
|
+
type: "wheat",
|
|
1000
|
+
crowns: 0
|
|
1001
|
+
},
|
|
1002
|
+
right: {
|
|
1003
|
+
type: "forest",
|
|
1004
|
+
crowns: 0
|
|
1005
|
+
},
|
|
1006
|
+
number: 12
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
left: {
|
|
1010
|
+
type: "wheat",
|
|
1011
|
+
crowns: 0,
|
|
1012
|
+
hasConstructionSquare: true
|
|
1013
|
+
},
|
|
1014
|
+
right: {
|
|
1015
|
+
type: "sea",
|
|
1016
|
+
crowns: 0
|
|
1017
|
+
},
|
|
1018
|
+
number: 13
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
left: {
|
|
1022
|
+
type: "wheat",
|
|
1023
|
+
crowns: 0
|
|
1024
|
+
},
|
|
1025
|
+
right: {
|
|
1026
|
+
type: "plain",
|
|
1027
|
+
crowns: 0,
|
|
1028
|
+
hasConstructionSquare: true
|
|
1029
|
+
},
|
|
1030
|
+
number: 14
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
left: {
|
|
1034
|
+
type: "forest",
|
|
1035
|
+
crowns: 0,
|
|
1036
|
+
hasConstructionSquare: true
|
|
1037
|
+
},
|
|
1038
|
+
right: {
|
|
1039
|
+
type: "sea",
|
|
1040
|
+
crowns: 0
|
|
1041
|
+
},
|
|
1042
|
+
number: 15
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
left: {
|
|
1046
|
+
type: "forest",
|
|
1047
|
+
crowns: 0
|
|
1048
|
+
},
|
|
1049
|
+
right: {
|
|
1050
|
+
type: "plain",
|
|
1051
|
+
crowns: 0
|
|
1052
|
+
},
|
|
1053
|
+
number: 16
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
left: {
|
|
1057
|
+
type: "sea",
|
|
1058
|
+
crowns: 0
|
|
1059
|
+
},
|
|
1060
|
+
right: {
|
|
1061
|
+
type: "plain",
|
|
1062
|
+
crowns: 0,
|
|
1063
|
+
hasConstructionSquare: true
|
|
1064
|
+
},
|
|
1065
|
+
number: 17
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
left: {
|
|
1069
|
+
type: "wheat",
|
|
1070
|
+
crowns: 0
|
|
1071
|
+
},
|
|
1072
|
+
right: {
|
|
1073
|
+
type: "swamp",
|
|
1074
|
+
crowns: 0
|
|
1075
|
+
},
|
|
1076
|
+
number: 18
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
left: {
|
|
1080
|
+
type: "wheat",
|
|
1081
|
+
crowns: 1,
|
|
1082
|
+
hasConstructionSquare: true
|
|
1083
|
+
},
|
|
1084
|
+
right: {
|
|
1085
|
+
type: "forest",
|
|
1086
|
+
crowns: 0
|
|
1087
|
+
},
|
|
1088
|
+
number: 19
|
|
1089
|
+
},
|
|
1090
|
+
{
|
|
1091
|
+
left: {
|
|
1092
|
+
type: "wheat",
|
|
1093
|
+
crowns: 1
|
|
1094
|
+
},
|
|
1095
|
+
right: {
|
|
1096
|
+
type: "sea",
|
|
1097
|
+
crowns: 0
|
|
1098
|
+
},
|
|
1099
|
+
number: 20
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
left: {
|
|
1103
|
+
type: "wheat",
|
|
1104
|
+
crowns: 1
|
|
1105
|
+
},
|
|
1106
|
+
right: {
|
|
1107
|
+
type: "plain",
|
|
1108
|
+
crowns: 0,
|
|
1109
|
+
hasConstructionSquare: true
|
|
1110
|
+
},
|
|
1111
|
+
number: 21
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
left: {
|
|
1115
|
+
type: "forest",
|
|
1116
|
+
crowns: 1
|
|
1117
|
+
},
|
|
1118
|
+
right: {
|
|
1119
|
+
type: "wheat",
|
|
1120
|
+
crowns: 0,
|
|
1121
|
+
hasConstructionSquare: true
|
|
1122
|
+
},
|
|
1123
|
+
number: 22
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
left: {
|
|
1127
|
+
type: "forest",
|
|
1128
|
+
crowns: 1
|
|
1129
|
+
},
|
|
1130
|
+
right: {
|
|
1131
|
+
type: "sea",
|
|
1132
|
+
crowns: 0
|
|
1133
|
+
},
|
|
1134
|
+
number: 23
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
left: {
|
|
1138
|
+
type: "forest",
|
|
1139
|
+
crowns: 1,
|
|
1140
|
+
hasConstructionSquare: true
|
|
1141
|
+
},
|
|
1142
|
+
right: {
|
|
1143
|
+
type: "plain",
|
|
1144
|
+
crowns: 0
|
|
1145
|
+
},
|
|
1146
|
+
number: 24
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
left: {
|
|
1150
|
+
type: "sea",
|
|
1151
|
+
crowns: 1
|
|
1152
|
+
},
|
|
1153
|
+
right: {
|
|
1154
|
+
type: "wheat",
|
|
1155
|
+
crowns: 0
|
|
1156
|
+
},
|
|
1157
|
+
number: 25
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
left: {
|
|
1161
|
+
type: "sea",
|
|
1162
|
+
crowns: 1
|
|
1163
|
+
},
|
|
1164
|
+
right: {
|
|
1165
|
+
type: "forest",
|
|
1166
|
+
crowns: 0,
|
|
1167
|
+
hasConstructionSquare: true
|
|
1168
|
+
},
|
|
1169
|
+
number: 26
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
left: {
|
|
1173
|
+
type: "plain",
|
|
1174
|
+
crowns: 1
|
|
1175
|
+
},
|
|
1176
|
+
right: {
|
|
1177
|
+
type: "wheat",
|
|
1178
|
+
crowns: 0
|
|
1179
|
+
},
|
|
1180
|
+
number: 27
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
left: {
|
|
1184
|
+
type: "plain",
|
|
1185
|
+
crowns: 1,
|
|
1186
|
+
hasConstructionSquare: true
|
|
1187
|
+
},
|
|
1188
|
+
right: {
|
|
1189
|
+
type: "forest",
|
|
1190
|
+
crowns: 0
|
|
1191
|
+
},
|
|
1192
|
+
number: 28
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
left: {
|
|
1196
|
+
type: "wheat",
|
|
1197
|
+
crowns: 1
|
|
1198
|
+
},
|
|
1199
|
+
right: {
|
|
1200
|
+
type: "mine",
|
|
1201
|
+
crowns: 0
|
|
1202
|
+
},
|
|
1203
|
+
number: 29
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
left: {
|
|
1207
|
+
type: "sea",
|
|
1208
|
+
crowns: 1
|
|
1209
|
+
},
|
|
1210
|
+
right: {
|
|
1211
|
+
type: "plain",
|
|
1212
|
+
crowns: 0,
|
|
1213
|
+
hasConstructionSquare: true
|
|
1214
|
+
},
|
|
1215
|
+
number: 30
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
left: {
|
|
1219
|
+
type: "wheat",
|
|
1220
|
+
crowns: 1,
|
|
1221
|
+
hasConstructionSquare: true
|
|
1222
|
+
},
|
|
1223
|
+
right: {
|
|
1224
|
+
type: "swamp",
|
|
1225
|
+
crowns: 1
|
|
1226
|
+
},
|
|
1227
|
+
number: 31
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
left: {
|
|
1231
|
+
type: "forest",
|
|
1232
|
+
crowns: 1
|
|
1233
|
+
},
|
|
1234
|
+
right: {
|
|
1235
|
+
type: "sea",
|
|
1236
|
+
crowns: 1,
|
|
1237
|
+
hasConstructionSquare: true
|
|
1238
|
+
},
|
|
1239
|
+
number: 32
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
left: {
|
|
1243
|
+
type: "sea",
|
|
1244
|
+
crowns: 1
|
|
1245
|
+
},
|
|
1246
|
+
right: {
|
|
1247
|
+
type: "plain",
|
|
1248
|
+
crowns: 1
|
|
1249
|
+
},
|
|
1250
|
+
number: 33
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
left: {
|
|
1254
|
+
type: "plain",
|
|
1255
|
+
crowns: 1,
|
|
1256
|
+
hasConstructionSquare: true
|
|
1257
|
+
},
|
|
1258
|
+
right: {
|
|
1259
|
+
type: "swamp",
|
|
1260
|
+
crowns: 1
|
|
1261
|
+
},
|
|
1262
|
+
number: 34
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
left: {
|
|
1266
|
+
type: "forest",
|
|
1267
|
+
crowns: 1,
|
|
1268
|
+
hasConstructionSquare: true
|
|
1269
|
+
},
|
|
1270
|
+
right: {
|
|
1271
|
+
type: "mine",
|
|
1272
|
+
crowns: 1
|
|
1273
|
+
},
|
|
1274
|
+
number: 35
|
|
1275
|
+
},
|
|
1276
|
+
{
|
|
1277
|
+
left: {
|
|
1278
|
+
type: "wheat",
|
|
1279
|
+
crowns: 2,
|
|
1280
|
+
hasConstructionSquare: true
|
|
1281
|
+
},
|
|
1282
|
+
right: {
|
|
1283
|
+
type: "forest",
|
|
1284
|
+
crowns: 0
|
|
1285
|
+
},
|
|
1286
|
+
number: 36
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
left: {
|
|
1290
|
+
type: "sea",
|
|
1291
|
+
crowns: 2
|
|
1292
|
+
},
|
|
1293
|
+
right: {
|
|
1294
|
+
type: "wheat",
|
|
1295
|
+
crowns: 0,
|
|
1296
|
+
hasConstructionSquare: true
|
|
1297
|
+
},
|
|
1298
|
+
number: 37
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
left: {
|
|
1302
|
+
type: "plain",
|
|
1303
|
+
crowns: 2
|
|
1304
|
+
},
|
|
1305
|
+
right: {
|
|
1306
|
+
type: "forest",
|
|
1307
|
+
crowns: 0
|
|
1308
|
+
},
|
|
1309
|
+
number: 38
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
left: {
|
|
1313
|
+
type: "swamp",
|
|
1314
|
+
crowns: 2
|
|
1315
|
+
},
|
|
1316
|
+
right: {
|
|
1317
|
+
type: "wheat",
|
|
1318
|
+
crowns: 0
|
|
1319
|
+
},
|
|
1320
|
+
number: 39
|
|
1321
|
+
},
|
|
1322
|
+
{
|
|
1323
|
+
left: {
|
|
1324
|
+
type: "mine",
|
|
1325
|
+
crowns: 2,
|
|
1326
|
+
hasConstructionSquare: true
|
|
1327
|
+
},
|
|
1328
|
+
right: {
|
|
1329
|
+
type: "wheat",
|
|
1330
|
+
crowns: 0
|
|
1331
|
+
},
|
|
1332
|
+
number: 40
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
left: {
|
|
1336
|
+
type: "wheat",
|
|
1337
|
+
crowns: 2
|
|
1338
|
+
},
|
|
1339
|
+
right: {
|
|
1340
|
+
type: "plain",
|
|
1341
|
+
crowns: 2
|
|
1342
|
+
},
|
|
1343
|
+
number: 41
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
left: {
|
|
1347
|
+
type: "sea",
|
|
1348
|
+
crowns: 2
|
|
1349
|
+
},
|
|
1350
|
+
right: {
|
|
1351
|
+
type: "forest",
|
|
1352
|
+
crowns: 1,
|
|
1353
|
+
hasConstructionSquare: true
|
|
1354
|
+
},
|
|
1355
|
+
number: 42
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
left: {
|
|
1359
|
+
type: "swamp",
|
|
1360
|
+
crowns: 2
|
|
1361
|
+
},
|
|
1362
|
+
right: {
|
|
1363
|
+
type: "plain",
|
|
1364
|
+
crowns: 1
|
|
1365
|
+
},
|
|
1366
|
+
number: 43
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
left: {
|
|
1370
|
+
type: "mine",
|
|
1371
|
+
crowns: 2
|
|
1372
|
+
},
|
|
1373
|
+
right: {
|
|
1374
|
+
type: "swamp",
|
|
1375
|
+
crowns: 1
|
|
1376
|
+
},
|
|
1377
|
+
number: 44
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
left: {
|
|
1381
|
+
type: "wheat",
|
|
1382
|
+
crowns: 2
|
|
1383
|
+
},
|
|
1384
|
+
right: {
|
|
1385
|
+
type: "mine",
|
|
1386
|
+
crowns: 2
|
|
1387
|
+
},
|
|
1388
|
+
number: 45
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
left: {
|
|
1392
|
+
type: "forest",
|
|
1393
|
+
crowns: 2
|
|
1394
|
+
},
|
|
1395
|
+
right: {
|
|
1396
|
+
type: "mine",
|
|
1397
|
+
crowns: 2,
|
|
1398
|
+
hasConstructionSquare: true
|
|
1399
|
+
},
|
|
1400
|
+
number: 46
|
|
1401
|
+
},
|
|
1402
|
+
{
|
|
1403
|
+
left: {
|
|
1404
|
+
type: "swamp",
|
|
1405
|
+
crowns: 2
|
|
1406
|
+
},
|
|
1407
|
+
right: {
|
|
1408
|
+
type: "mine",
|
|
1409
|
+
crowns: 3
|
|
1410
|
+
},
|
|
1411
|
+
number: 47
|
|
1412
|
+
},
|
|
1413
|
+
{
|
|
1414
|
+
left: {
|
|
1415
|
+
type: "plain",
|
|
1416
|
+
crowns: 2
|
|
1417
|
+
},
|
|
1418
|
+
right: {
|
|
1419
|
+
type: "mine",
|
|
1420
|
+
crowns: 3
|
|
1421
|
+
},
|
|
1422
|
+
number: 48
|
|
1423
|
+
}
|
|
1424
|
+
]
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
// src/adapterServerside/jsonDominoes.ts
|
|
1428
|
+
var mapTile = (raw) => {
|
|
1429
|
+
const tile = {
|
|
1430
|
+
type: raw.type,
|
|
1431
|
+
crowns: raw.crowns
|
|
1432
|
+
};
|
|
1433
|
+
if (raw.hasConstructionSquare) {
|
|
1434
|
+
tile.hasConstructionSquare = true;
|
|
1435
|
+
}
|
|
1436
|
+
return tile;
|
|
1437
|
+
};
|
|
1438
|
+
var jsonDominoes = () => {
|
|
1439
|
+
const dominoes = {};
|
|
1440
|
+
for (const [key, value] of Object.entries(dominoes_default)) {
|
|
1441
|
+
const currentDominoes = [];
|
|
1442
|
+
value.forEach((domino) => {
|
|
1443
|
+
currentDominoes.push({
|
|
1444
|
+
left: mapTile(domino.left),
|
|
1445
|
+
right: mapTile(domino.right),
|
|
1446
|
+
number: domino.number
|
|
1447
|
+
});
|
|
1448
|
+
});
|
|
1449
|
+
dominoes[key] = currentDominoes;
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
getForMode: (mode) => {
|
|
1453
|
+
return dominoes[mode.name] || [];
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
};
|
|
1457
|
+
var jsonDominoes_default = jsonDominoes;
|
|
1458
|
+
|
|
1459
|
+
// src/datasources/modes.json
|
|
1460
|
+
var modes_default = [
|
|
1461
|
+
{
|
|
1462
|
+
name: "Classic",
|
|
1463
|
+
description: "Original KingDomino rules"
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
name: "QueenDomino",
|
|
1467
|
+
description: "Queendomino expansion with buildings, knights, coins, the Queen and the Dragon"
|
|
1468
|
+
}
|
|
1469
|
+
];
|
|
1470
|
+
|
|
1471
|
+
// src/adapterServerside/jsonModes.ts
|
|
1472
|
+
var jsonModes = () => {
|
|
1473
|
+
const modes = [];
|
|
1474
|
+
modes_default.forEach((mode) => {
|
|
1475
|
+
modes.push({
|
|
1476
|
+
name: mode.name,
|
|
1477
|
+
description: mode.description
|
|
1478
|
+
});
|
|
1479
|
+
});
|
|
1480
|
+
return {
|
|
1481
|
+
getAvailables: () => {
|
|
1482
|
+
return modes;
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
};
|
|
1486
|
+
var jsonModes_default = jsonModes;
|
|
1487
|
+
|
|
1488
|
+
// src/datasources/rules.json
|
|
1489
|
+
var rules_default = {
|
|
1490
|
+
commun: [
|
|
1491
|
+
{
|
|
1492
|
+
playersLimit: 1,
|
|
1493
|
+
lordsPerPlayer: 1,
|
|
1494
|
+
maxDominoes: 48,
|
|
1495
|
+
dominoesPerTurn: 4,
|
|
1496
|
+
maxTurns: 12,
|
|
1497
|
+
maxKingdomSize: 5
|
|
1498
|
+
},
|
|
1499
|
+
{
|
|
1500
|
+
playersLimit: 2,
|
|
1501
|
+
lordsPerPlayer: 2,
|
|
1502
|
+
maxDominoes: 24,
|
|
1503
|
+
dominoesPerTurn: 4,
|
|
1504
|
+
maxTurns: 6,
|
|
1505
|
+
maxKingdomSize: 5
|
|
1506
|
+
},
|
|
1507
|
+
{
|
|
1508
|
+
playersLimit: 3,
|
|
1509
|
+
lordsPerPlayer: 1,
|
|
1510
|
+
maxDominoes: 48,
|
|
1511
|
+
dominoesPerTurn: 4,
|
|
1512
|
+
maxTurns: 12,
|
|
1513
|
+
maxKingdomSize: 5
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
playersLimit: 4,
|
|
1517
|
+
lordsPerPlayer: 1,
|
|
1518
|
+
maxDominoes: 48,
|
|
1519
|
+
dominoesPerTurn: 4,
|
|
1520
|
+
maxTurns: 12,
|
|
1521
|
+
maxKingdomSize: 5
|
|
1522
|
+
}
|
|
1523
|
+
],
|
|
1524
|
+
extra: [
|
|
1525
|
+
{
|
|
1526
|
+
name: "The middle Kingdom",
|
|
1527
|
+
description: "Gain 10 additional points if your castle is in the middle of the kingdom.",
|
|
1528
|
+
mode: [
|
|
1529
|
+
{ name: "Classic", description: "King Domino classic mode" },
|
|
1530
|
+
{ name: "QueenDomino", description: "Queendomino expansion with buildings, knights, coins, the Queen and the Dragon" }
|
|
1531
|
+
]
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
name: "Harmony",
|
|
1535
|
+
description: "Gain 5 additional points if your kingdom is complete (no discarded dominoes).",
|
|
1536
|
+
mode: [
|
|
1537
|
+
{ name: "Classic", description: "King Domino classic mode" },
|
|
1538
|
+
{ name: "QueenDomino", description: "Queendomino expansion with buildings, knights, coins, the Queen and the Dragon" }
|
|
1539
|
+
]
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
name: "The Mighty Duel",
|
|
1543
|
+
description: "Use all 48 dominoes and build a 7x7 kingdom. For 2 players only.",
|
|
1544
|
+
mode: [
|
|
1545
|
+
{ name: "Classic", description: "King Domino classic mode" },
|
|
1546
|
+
{ name: "QueenDomino", description: "Queendomino expansion with buildings, knights, coins, the Queen and the Dragon" }
|
|
1547
|
+
],
|
|
1548
|
+
playersLimit: 2
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
name: "Dynasty",
|
|
1552
|
+
description: "Play 3 games in a row. The player with the highest total points wins.",
|
|
1553
|
+
mode: [
|
|
1554
|
+
{ name: "Classic", description: "King Domino classic mode" },
|
|
1555
|
+
{ name: "QueenDomino", description: "Queendomino expansion with buildings, knights, coins, the Queen and the Dragon" }
|
|
1556
|
+
]
|
|
1557
|
+
}
|
|
1558
|
+
]
|
|
1559
|
+
};
|
|
1560
|
+
|
|
1561
|
+
// src/adapterServerside/jsonRules.ts
|
|
1562
|
+
var jsonRules = () => {
|
|
1563
|
+
const rules = {
|
|
1564
|
+
basic: {},
|
|
1565
|
+
extraRules: []
|
|
1566
|
+
};
|
|
1567
|
+
const commonRules = rules_default.commun;
|
|
1568
|
+
const extraRules = rules_default.extra;
|
|
1569
|
+
commonRules.forEach((rule) => {
|
|
1570
|
+
rules.basic[rule.playersLimit] = {
|
|
1571
|
+
lords: rule.lordsPerPlayer,
|
|
1572
|
+
maxDominoes: rule.maxDominoes,
|
|
1573
|
+
dominoesPerTurn: rule.dominoesPerTurn,
|
|
1574
|
+
maxTurns: rule.maxTurns,
|
|
1575
|
+
maxKingdomSize: rule.maxKingdomSize
|
|
1576
|
+
};
|
|
1577
|
+
});
|
|
1578
|
+
extraRules.forEach((rule) => {
|
|
1579
|
+
rules.extraRules.push({
|
|
1580
|
+
name: rule.name,
|
|
1581
|
+
description: rule.description,
|
|
1582
|
+
mode: rule.mode,
|
|
1583
|
+
..."playersLimit" in rule && { playersLimit: rule.playersLimit }
|
|
1584
|
+
});
|
|
1585
|
+
});
|
|
1586
|
+
return {
|
|
1587
|
+
getAll: () => {
|
|
1588
|
+
return rules;
|
|
1589
|
+
},
|
|
1590
|
+
getAllExtra: () => {
|
|
1591
|
+
return rules.extraRules;
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
};
|
|
1595
|
+
var jsonRules_default = jsonRules;
|
|
1596
|
+
|
|
1597
|
+
// src/adapterServerside/shuffle.ts
|
|
1598
|
+
var shuffleMethod = (array) => {
|
|
1599
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
1600
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1601
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
1602
|
+
}
|
|
1603
|
+
return array;
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
// src/adapterServerside/uuid.ts
|
|
1607
|
+
var uuidMethod = () => {
|
|
1608
|
+
return globalThis.crypto.randomUUID();
|
|
1609
|
+
};
|
|
1610
|
+
|
|
1611
|
+
// src/core/domain/errors/domainErrors.ts
|
|
1612
|
+
var ErrorCode = {
|
|
1613
|
+
// Game flow errors
|
|
1614
|
+
INVALID_STEP: "INVALID_STEP",
|
|
1615
|
+
STEP_EXECUTION_FAILED: "STEP_EXECUTION_FAILED",
|
|
1616
|
+
ACTION_EXECUTION_FAILED: "ACTION_EXECUTION_FAILED",
|
|
1617
|
+
// Entity not found errors
|
|
1618
|
+
LORD_NOT_FOUND: "LORD_NOT_FOUND",
|
|
1619
|
+
PLAYER_NOT_FOUND: "PLAYER_NOT_FOUND",
|
|
1620
|
+
DOMINO_NOT_FOUND: "DOMINO_NOT_FOUND",
|
|
1621
|
+
MODE_NOT_FOUND: "MODE_NOT_FOUND",
|
|
1622
|
+
// Validation errors
|
|
1623
|
+
INVALID_PLAYER_COUNT: "INVALID_PLAYER_COUNT",
|
|
1624
|
+
INVALID_PLAYER_NAME: "INVALID_PLAYER_NAME",
|
|
1625
|
+
INVALID_PLACEMENT: "INVALID_PLACEMENT",
|
|
1626
|
+
PLACEMENT_NOT_EMPTY: "PLACEMENT_NOT_EMPTY",
|
|
1627
|
+
PLACEMENT_NOT_ADJACENT: "PLACEMENT_NOT_ADJACENT",
|
|
1628
|
+
PLACEMENT_INVALID_TERRAIN: "PLACEMENT_INVALID_TERRAIN",
|
|
1629
|
+
PLACEMENT_OUT_OF_BOUNDS: "PLACEMENT_OUT_OF_BOUNDS",
|
|
1630
|
+
PLACEMENT_EXCEEDS_KINGDOM_SIZE: "PLACEMENT_EXCEEDS_KINGDOM_SIZE",
|
|
1631
|
+
// Action errors
|
|
1632
|
+
NOT_YOUR_TURN: "NOT_YOUR_TURN",
|
|
1633
|
+
CANNOT_PICK: "CANNOT_PICK",
|
|
1634
|
+
CANNOT_PLACE: "CANNOT_PLACE",
|
|
1635
|
+
DOMINO_ALREADY_PICKED: "DOMINO_ALREADY_PICKED",
|
|
1636
|
+
// History errors
|
|
1637
|
+
UNDO_UNAVAILABLE: "UNDO_UNAVAILABLE",
|
|
1638
|
+
REDO_UNAVAILABLE: "REDO_UNAVAILABLE",
|
|
1639
|
+
// Queendomino errors
|
|
1640
|
+
CANNOT_PLACE_KNIGHT: "CANNOT_PLACE_KNIGHT",
|
|
1641
|
+
MAX_KNIGHTS_REACHED: "MAX_KNIGHTS_REACHED",
|
|
1642
|
+
KNIGHT_TERRITORY_OCCUPIED: "KNIGHT_TERRITORY_OCCUPIED",
|
|
1643
|
+
CANNOT_AFFORD_BUILDING: "CANNOT_AFFORD_BUILDING",
|
|
1644
|
+
NO_CONSTRUCTION_SQUARE: "NO_CONSTRUCTION_SQUARE",
|
|
1645
|
+
DRAGON_UNAVAILABLE: "DRAGON_UNAVAILABLE",
|
|
1646
|
+
QUEEN_HOLDER_CANNOT_USE_DRAGON: "QUEEN_HOLDER_CANNOT_USE_DRAGON",
|
|
1647
|
+
BUILDING_NOT_FOUND: "BUILDING_NOT_FOUND",
|
|
1648
|
+
INVALID_OPTIONAL_ACTION: "INVALID_OPTIONAL_ACTION"
|
|
1649
|
+
};
|
|
1650
|
+
var DomainException = class extends Error {
|
|
1651
|
+
code;
|
|
1652
|
+
context;
|
|
1653
|
+
constructor(error) {
|
|
1654
|
+
super(error.message);
|
|
1655
|
+
this.name = "DomainException";
|
|
1656
|
+
this.code = error.code;
|
|
1657
|
+
this.context = error.context;
|
|
1658
|
+
}
|
|
1659
|
+
};
|
|
1660
|
+
var InvalidStepError = class extends DomainException {
|
|
1661
|
+
constructor(message, context) {
|
|
1662
|
+
super({
|
|
1663
|
+
code: ErrorCode.INVALID_STEP,
|
|
1664
|
+
message,
|
|
1665
|
+
context
|
|
1666
|
+
});
|
|
1667
|
+
this.name = "InvalidStepError";
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
var StepExecutionError = class extends DomainException {
|
|
1671
|
+
constructor(message, code = ErrorCode.STEP_EXECUTION_FAILED, context) {
|
|
1672
|
+
super({
|
|
1673
|
+
code,
|
|
1674
|
+
message,
|
|
1675
|
+
context
|
|
1676
|
+
});
|
|
1677
|
+
this.name = "StepExecutionError";
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
var ActionExecutionError = class extends DomainException {
|
|
1681
|
+
constructor(message, code = ErrorCode.ACTION_EXECUTION_FAILED, context) {
|
|
1682
|
+
super({
|
|
1683
|
+
code,
|
|
1684
|
+
message,
|
|
1685
|
+
context
|
|
1686
|
+
});
|
|
1687
|
+
this.name = "ActionExecutionError";
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
var NotFoundError = class extends DomainException {
|
|
1691
|
+
constructor(message, code = ErrorCode.LORD_NOT_FOUND, context) {
|
|
1692
|
+
super({
|
|
1693
|
+
code,
|
|
1694
|
+
message,
|
|
1695
|
+
context
|
|
1696
|
+
});
|
|
1697
|
+
this.name = "NotFoundError";
|
|
1698
|
+
}
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1701
|
+
// src/core/i18n/translations.ts
|
|
1702
|
+
var TranslationKey = {
|
|
1703
|
+
// Error messages
|
|
1704
|
+
ERROR_INVALID_STEP: "error.invalidStep",
|
|
1705
|
+
ERROR_STEP_EXECUTION_FAILED: "error.stepExecutionFailed",
|
|
1706
|
+
ERROR_ACTION_EXECUTION_FAILED: "error.actionExecutionFailed",
|
|
1707
|
+
ERROR_LORD_NOT_FOUND: "error.lordNotFound",
|
|
1708
|
+
ERROR_PLAYER_NOT_FOUND: "error.playerNotFound",
|
|
1709
|
+
ERROR_DOMINO_NOT_FOUND: "error.dominoNotFound",
|
|
1710
|
+
ERROR_MODE_NOT_FOUND: "error.modeNotFound",
|
|
1711
|
+
ERROR_INVALID_PLACEMENT: "error.invalidPlacement",
|
|
1712
|
+
ERROR_PLACEMENT_NOT_EMPTY: "error.placementNotEmpty",
|
|
1713
|
+
ERROR_PLACEMENT_NOT_ADJACENT: "error.placementNotAdjacent",
|
|
1714
|
+
ERROR_PLACEMENT_INVALID_TERRAIN: "error.placementInvalidTerrain",
|
|
1715
|
+
ERROR_PLACEMENT_OUT_OF_BOUNDS: "error.placementOutOfBounds",
|
|
1716
|
+
ERROR_PLACEMENT_EXCEEDS_KINGDOM_SIZE: "error.placementExceedsKingdomSize",
|
|
1717
|
+
ERROR_NOT_YOUR_TURN: "error.notYourTurn",
|
|
1718
|
+
ERROR_CANNOT_PICK: "error.cannotPick",
|
|
1719
|
+
ERROR_CANNOT_PLACE: "error.cannotPlace",
|
|
1720
|
+
ERROR_DOMINO_ALREADY_PICKED: "error.dominoAlreadyPicked",
|
|
1721
|
+
ERROR_INVALID_PLAYER_COUNT: "error.invalidPlayerCount",
|
|
1722
|
+
ERROR_INVALID_PLAYER_NAME: "error.invalidPlayerName",
|
|
1723
|
+
ERROR_UNDO_UNAVAILABLE: "error.undoUnavailable",
|
|
1724
|
+
ERROR_REDO_UNAVAILABLE: "error.redoUnavailable",
|
|
1725
|
+
// Game mode names
|
|
1726
|
+
MODE_CLASSIC: "mode.classic",
|
|
1727
|
+
// Extra rule names
|
|
1728
|
+
RULE_MIDDLE_KINGDOM: "rule.middleKingdom",
|
|
1729
|
+
RULE_HARMONY: "rule.harmony",
|
|
1730
|
+
// Ground types
|
|
1731
|
+
GROUND_CASTLE: "ground.castle",
|
|
1732
|
+
GROUND_WHEAT: "ground.wheat",
|
|
1733
|
+
GROUND_FOREST: "ground.forest",
|
|
1734
|
+
GROUND_SEA: "ground.sea",
|
|
1735
|
+
GROUND_PLAIN: "ground.plain",
|
|
1736
|
+
GROUND_SWAMP: "ground.swamp",
|
|
1737
|
+
GROUND_MINE: "ground.mine",
|
|
1738
|
+
// Game phases
|
|
1739
|
+
PHASE_ADD_PLAYERS: "phase.addPlayers",
|
|
1740
|
+
PHASE_OPTIONS: "phase.options",
|
|
1741
|
+
PHASE_START: "phase.start",
|
|
1742
|
+
PHASE_RESULT: "phase.result",
|
|
1743
|
+
// Actions
|
|
1744
|
+
ACTION_PICK_DOMINO: "action.pickDomino",
|
|
1745
|
+
ACTION_PLACE_DOMINO: "action.placeDomino",
|
|
1746
|
+
ACTION_PASS: "action.pass",
|
|
1747
|
+
// Queendomino errors
|
|
1748
|
+
ERROR_CANNOT_PLACE_KNIGHT: "error.cannotPlaceKnight",
|
|
1749
|
+
ERROR_MAX_KNIGHTS_REACHED: "error.maxKnightsReached",
|
|
1750
|
+
ERROR_KNIGHT_TERRITORY_OCCUPIED: "error.knightTerritoryOccupied",
|
|
1751
|
+
ERROR_CANNOT_AFFORD_BUILDING: "error.cannotAffordBuilding",
|
|
1752
|
+
ERROR_NO_CONSTRUCTION_SQUARE: "error.noConstructionSquare",
|
|
1753
|
+
ERROR_DRAGON_UNAVAILABLE: "error.dragonUnavailable",
|
|
1754
|
+
ERROR_QUEEN_HOLDER_CANNOT_USE_DRAGON: "error.queenHolderCannotUseDragon",
|
|
1755
|
+
ERROR_BUILDING_NOT_FOUND: "error.buildingNotFound",
|
|
1756
|
+
ERROR_INVALID_OPTIONAL_ACTION: "error.invalidOptionalAction"
|
|
1757
|
+
};
|
|
1758
|
+
var defaultTranslations = {
|
|
1759
|
+
// Error messages
|
|
1760
|
+
[TranslationKey.ERROR_INVALID_STEP]: "Invalid game step",
|
|
1761
|
+
[TranslationKey.ERROR_STEP_EXECUTION_FAILED]: "Step execution failed",
|
|
1762
|
+
[TranslationKey.ERROR_ACTION_EXECUTION_FAILED]: "Action execution failed",
|
|
1763
|
+
[TranslationKey.ERROR_LORD_NOT_FOUND]: "Lord not found",
|
|
1764
|
+
[TranslationKey.ERROR_PLAYER_NOT_FOUND]: "Player not found",
|
|
1765
|
+
[TranslationKey.ERROR_DOMINO_NOT_FOUND]: "Domino not found",
|
|
1766
|
+
[TranslationKey.ERROR_MODE_NOT_FOUND]: "Game mode not found",
|
|
1767
|
+
[TranslationKey.ERROR_INVALID_PLACEMENT]: "Invalid placement",
|
|
1768
|
+
[TranslationKey.ERROR_PLACEMENT_NOT_EMPTY]: "Position is not empty",
|
|
1769
|
+
[TranslationKey.ERROR_PLACEMENT_NOT_ADJACENT]: "Not adjacent to existing tiles",
|
|
1770
|
+
[TranslationKey.ERROR_PLACEMENT_INVALID_TERRAIN]: "Terrain type does not match",
|
|
1771
|
+
[TranslationKey.ERROR_PLACEMENT_OUT_OF_BOUNDS]: "Placement is out of bounds",
|
|
1772
|
+
[TranslationKey.ERROR_PLACEMENT_EXCEEDS_KINGDOM_SIZE]: "Placement exceeds kingdom size",
|
|
1773
|
+
[TranslationKey.ERROR_NOT_YOUR_TURN]: "It is not your turn",
|
|
1774
|
+
[TranslationKey.ERROR_CANNOT_PICK]: "Cannot pick a domino now",
|
|
1775
|
+
[TranslationKey.ERROR_CANNOT_PLACE]: "Cannot place a domino now",
|
|
1776
|
+
[TranslationKey.ERROR_DOMINO_ALREADY_PICKED]: "Domino has already been picked",
|
|
1777
|
+
[TranslationKey.ERROR_INVALID_PLAYER_COUNT]: "Invalid number of players (1-4 required)",
|
|
1778
|
+
[TranslationKey.ERROR_INVALID_PLAYER_NAME]: "Player name must be at least 3 characters",
|
|
1779
|
+
[TranslationKey.ERROR_UNDO_UNAVAILABLE]: "No state to undo",
|
|
1780
|
+
[TranslationKey.ERROR_REDO_UNAVAILABLE]: "No state to redo",
|
|
1781
|
+
// Game mode names
|
|
1782
|
+
[TranslationKey.MODE_CLASSIC]: "Classic",
|
|
1783
|
+
// Extra rule names
|
|
1784
|
+
[TranslationKey.RULE_MIDDLE_KINGDOM]: "The Middle Kingdom",
|
|
1785
|
+
[TranslationKey.RULE_HARMONY]: "Harmony",
|
|
1786
|
+
// Ground types
|
|
1787
|
+
[TranslationKey.GROUND_CASTLE]: "Castle",
|
|
1788
|
+
[TranslationKey.GROUND_WHEAT]: "Wheat Field",
|
|
1789
|
+
[TranslationKey.GROUND_FOREST]: "Forest",
|
|
1790
|
+
[TranslationKey.GROUND_SEA]: "Sea",
|
|
1791
|
+
[TranslationKey.GROUND_PLAIN]: "Plain",
|
|
1792
|
+
[TranslationKey.GROUND_SWAMP]: "Swamp",
|
|
1793
|
+
[TranslationKey.GROUND_MINE]: "Mine",
|
|
1794
|
+
// Game phases
|
|
1795
|
+
[TranslationKey.PHASE_ADD_PLAYERS]: "Add Players",
|
|
1796
|
+
[TranslationKey.PHASE_OPTIONS]: "Game Options",
|
|
1797
|
+
[TranslationKey.PHASE_START]: "Start Game",
|
|
1798
|
+
[TranslationKey.PHASE_RESULT]: "Game Results",
|
|
1799
|
+
// Actions
|
|
1800
|
+
[TranslationKey.ACTION_PICK_DOMINO]: "Pick Domino",
|
|
1801
|
+
[TranslationKey.ACTION_PLACE_DOMINO]: "Place Domino",
|
|
1802
|
+
[TranslationKey.ACTION_PASS]: "Pass",
|
|
1803
|
+
// Queendomino errors
|
|
1804
|
+
[TranslationKey.ERROR_CANNOT_PLACE_KNIGHT]: "Cannot place knight here",
|
|
1805
|
+
[TranslationKey.ERROR_MAX_KNIGHTS_REACHED]: "Maximum number of knights reached (3)",
|
|
1806
|
+
[TranslationKey.ERROR_KNIGHT_TERRITORY_OCCUPIED]: "This territory already has a knight",
|
|
1807
|
+
[TranslationKey.ERROR_CANNOT_AFFORD_BUILDING]: "Not enough coins to purchase this building",
|
|
1808
|
+
[TranslationKey.ERROR_NO_CONSTRUCTION_SQUARE]: "No open construction square available",
|
|
1809
|
+
[TranslationKey.ERROR_DRAGON_UNAVAILABLE]: "The Dragon is not available this round",
|
|
1810
|
+
[TranslationKey.ERROR_QUEEN_HOLDER_CANNOT_USE_DRAGON]: "The Queen holder cannot use the Dragon",
|
|
1811
|
+
[TranslationKey.ERROR_BUILDING_NOT_FOUND]: "Building not found on the Builders Board",
|
|
1812
|
+
[TranslationKey.ERROR_INVALID_OPTIONAL_ACTION]: "Invalid optional action for this game state"
|
|
1813
|
+
};
|
|
1814
|
+
var errorCodeToTranslationKey = {
|
|
1815
|
+
[ErrorCode.INVALID_STEP]: TranslationKey.ERROR_INVALID_STEP,
|
|
1816
|
+
[ErrorCode.STEP_EXECUTION_FAILED]: TranslationKey.ERROR_STEP_EXECUTION_FAILED,
|
|
1817
|
+
[ErrorCode.ACTION_EXECUTION_FAILED]: TranslationKey.ERROR_ACTION_EXECUTION_FAILED,
|
|
1818
|
+
[ErrorCode.LORD_NOT_FOUND]: TranslationKey.ERROR_LORD_NOT_FOUND,
|
|
1819
|
+
[ErrorCode.PLAYER_NOT_FOUND]: TranslationKey.ERROR_PLAYER_NOT_FOUND,
|
|
1820
|
+
[ErrorCode.DOMINO_NOT_FOUND]: TranslationKey.ERROR_DOMINO_NOT_FOUND,
|
|
1821
|
+
[ErrorCode.MODE_NOT_FOUND]: TranslationKey.ERROR_MODE_NOT_FOUND,
|
|
1822
|
+
[ErrorCode.INVALID_PLAYER_COUNT]: TranslationKey.ERROR_INVALID_PLAYER_COUNT,
|
|
1823
|
+
[ErrorCode.INVALID_PLAYER_NAME]: TranslationKey.ERROR_INVALID_PLAYER_NAME,
|
|
1824
|
+
[ErrorCode.INVALID_PLACEMENT]: TranslationKey.ERROR_INVALID_PLACEMENT,
|
|
1825
|
+
[ErrorCode.PLACEMENT_NOT_EMPTY]: TranslationKey.ERROR_PLACEMENT_NOT_EMPTY,
|
|
1826
|
+
[ErrorCode.PLACEMENT_NOT_ADJACENT]: TranslationKey.ERROR_PLACEMENT_NOT_ADJACENT,
|
|
1827
|
+
[ErrorCode.PLACEMENT_INVALID_TERRAIN]: TranslationKey.ERROR_PLACEMENT_INVALID_TERRAIN,
|
|
1828
|
+
[ErrorCode.PLACEMENT_OUT_OF_BOUNDS]: TranslationKey.ERROR_PLACEMENT_OUT_OF_BOUNDS,
|
|
1829
|
+
[ErrorCode.PLACEMENT_EXCEEDS_KINGDOM_SIZE]: TranslationKey.ERROR_PLACEMENT_EXCEEDS_KINGDOM_SIZE,
|
|
1830
|
+
[ErrorCode.NOT_YOUR_TURN]: TranslationKey.ERROR_NOT_YOUR_TURN,
|
|
1831
|
+
[ErrorCode.CANNOT_PICK]: TranslationKey.ERROR_CANNOT_PICK,
|
|
1832
|
+
[ErrorCode.CANNOT_PLACE]: TranslationKey.ERROR_CANNOT_PLACE,
|
|
1833
|
+
[ErrorCode.DOMINO_ALREADY_PICKED]: TranslationKey.ERROR_DOMINO_ALREADY_PICKED,
|
|
1834
|
+
[ErrorCode.UNDO_UNAVAILABLE]: TranslationKey.ERROR_UNDO_UNAVAILABLE,
|
|
1835
|
+
[ErrorCode.REDO_UNAVAILABLE]: TranslationKey.ERROR_REDO_UNAVAILABLE,
|
|
1836
|
+
[ErrorCode.CANNOT_PLACE_KNIGHT]: TranslationKey.ERROR_CANNOT_PLACE_KNIGHT,
|
|
1837
|
+
[ErrorCode.MAX_KNIGHTS_REACHED]: TranslationKey.ERROR_MAX_KNIGHTS_REACHED,
|
|
1838
|
+
[ErrorCode.KNIGHT_TERRITORY_OCCUPIED]: TranslationKey.ERROR_KNIGHT_TERRITORY_OCCUPIED,
|
|
1839
|
+
[ErrorCode.CANNOT_AFFORD_BUILDING]: TranslationKey.ERROR_CANNOT_AFFORD_BUILDING,
|
|
1840
|
+
[ErrorCode.NO_CONSTRUCTION_SQUARE]: TranslationKey.ERROR_NO_CONSTRUCTION_SQUARE,
|
|
1841
|
+
[ErrorCode.DRAGON_UNAVAILABLE]: TranslationKey.ERROR_DRAGON_UNAVAILABLE,
|
|
1842
|
+
[ErrorCode.QUEEN_HOLDER_CANNOT_USE_DRAGON]: TranslationKey.ERROR_QUEEN_HOLDER_CANNOT_USE_DRAGON,
|
|
1843
|
+
[ErrorCode.BUILDING_NOT_FOUND]: TranslationKey.ERROR_BUILDING_NOT_FOUND,
|
|
1844
|
+
[ErrorCode.INVALID_OPTIONAL_ACTION]: TranslationKey.ERROR_INVALID_OPTIONAL_ACTION
|
|
1845
|
+
};
|
|
1846
|
+
var translateErrorCode = (translator, code, params) => {
|
|
1847
|
+
const key = errorCodeToTranslationKey[code];
|
|
1848
|
+
return translator.t(key, params);
|
|
1849
|
+
};
|
|
1850
|
+
var createTranslator = (translations = {}) => {
|
|
1851
|
+
const mergedTranslations = { ...defaultTranslations, ...translations };
|
|
1852
|
+
return {
|
|
1853
|
+
t(key, params) {
|
|
1854
|
+
let text = mergedTranslations[key] || key;
|
|
1855
|
+
if (params) {
|
|
1856
|
+
for (const [paramKey, value] of Object.entries(params)) {
|
|
1857
|
+
text = text.replace(
|
|
1858
|
+
new RegExp(`\\{\\{${paramKey}\\}\\}`, "g"),
|
|
1859
|
+
String(value)
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
return text;
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
};
|
|
1867
|
+
var defaultTranslator = createTranslator();
|
|
1868
|
+
|
|
1869
|
+
// src/utils/result.ts
|
|
1870
|
+
var ok = (value) => ({ tag: "Ok", value });
|
|
1871
|
+
var err = (error) => ({ tag: "Err", error });
|
|
1872
|
+
var isOk = (result) => result.tag === "Ok";
|
|
1873
|
+
var isErr = (result) => result.tag === "Err";
|
|
1874
|
+
var unwrap = (result) => {
|
|
1875
|
+
if (result.tag === "Err") {
|
|
1876
|
+
throw new Error(result.error);
|
|
1877
|
+
}
|
|
1878
|
+
return result.value;
|
|
1879
|
+
};
|
|
1880
|
+
|
|
1881
|
+
// src/application/handlers/addExtraRulesHandler.ts
|
|
1882
|
+
var addExtraRulesHandler = (logger, translator, useCase) => (command) => {
|
|
1883
|
+
const { game, extraRules } = command;
|
|
1884
|
+
logger.info(`Adding extra rules to game: ${game.id}`);
|
|
1885
|
+
logger.info(`Extra rules: ${extraRules}`);
|
|
1886
|
+
if (!isGameWithNextStep(game)) {
|
|
1887
|
+
logger.error(
|
|
1888
|
+
`Invalid game, required nextAction type: 'step' but got: ${game.nextAction.type}`
|
|
1889
|
+
);
|
|
1890
|
+
throw new InvalidStepError(
|
|
1891
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
1892
|
+
{ expected: "step", actual: game.nextAction.type, gameId: game.id }
|
|
1893
|
+
);
|
|
1894
|
+
}
|
|
1895
|
+
if (game.nextAction.step !== "options") {
|
|
1896
|
+
logger.error(
|
|
1897
|
+
`Required game with options step but got: ${game.nextAction.step}`
|
|
1898
|
+
);
|
|
1899
|
+
throw new InvalidStepError(
|
|
1900
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
1901
|
+
{ expected: "options", actual: game.nextAction.step, gameId: game.id }
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
const result = useCase(game, extraRules);
|
|
1905
|
+
if (isErr(result)) {
|
|
1906
|
+
logger.error(`Error adding extra rules: ${result.error}`);
|
|
1907
|
+
const code = result.error;
|
|
1908
|
+
throw new StepExecutionError(translateErrorCode(translator, code), code, {
|
|
1909
|
+
gameId: game.id,
|
|
1910
|
+
extraRules: command.extraRules
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
logger.info(`Extra rules added to game: ${game.id}`);
|
|
1914
|
+
return result.value;
|
|
1915
|
+
};
|
|
1916
|
+
|
|
1917
|
+
// src/application/handlers/addPlayersHandler.ts
|
|
1918
|
+
var addPlayersHandler = (logger, translator, useCase) => (command) => {
|
|
1919
|
+
const { game, players } = command;
|
|
1920
|
+
logger.info(`Adding players to game: ${game.id}`);
|
|
1921
|
+
if (!isGameWithNextStep(game)) {
|
|
1922
|
+
logger.error(
|
|
1923
|
+
`Invalid game, required nextAction type: 'step' but got: ${game.nextAction.type}`
|
|
1924
|
+
);
|
|
1925
|
+
throw new InvalidStepError(
|
|
1926
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
1927
|
+
{ expected: "step", actual: game.nextAction.type, gameId: game.id }
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
if (game.nextAction.step !== "addPlayers") {
|
|
1931
|
+
logger.error(
|
|
1932
|
+
`Required game with addPlayers step but got: ${game.nextAction.step}`
|
|
1933
|
+
);
|
|
1934
|
+
throw new InvalidStepError(
|
|
1935
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
1936
|
+
{
|
|
1937
|
+
expected: "addPlayers",
|
|
1938
|
+
actual: game.nextAction.step,
|
|
1939
|
+
gameId: game.id
|
|
1940
|
+
}
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
const result = useCase(game, players);
|
|
1944
|
+
if (isErr(result)) {
|
|
1945
|
+
logger.error(`Error adding players: ${result.error}`);
|
|
1946
|
+
const code = result.error;
|
|
1947
|
+
throw new StepExecutionError(translateErrorCode(translator, code), code, {
|
|
1948
|
+
gameId: game.id,
|
|
1949
|
+
players: command.players
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
logger.info(`Players added to game: ${game.id}`);
|
|
1953
|
+
return result.value;
|
|
1954
|
+
};
|
|
1955
|
+
|
|
1956
|
+
// src/application/handlers/calculateScoreHandler.ts
|
|
1957
|
+
var calculateScoreHandler = (logger, translator, useCase) => (command) => {
|
|
1958
|
+
const { kingdom } = command;
|
|
1959
|
+
logger.info(`Calculating score for kingdom`);
|
|
1960
|
+
const result = useCase(kingdom);
|
|
1961
|
+
if (isErr(result)) {
|
|
1962
|
+
logger.error(`Error calculating score: ${result.error}`);
|
|
1963
|
+
throw new Error(result.error);
|
|
1964
|
+
}
|
|
1965
|
+
logger.info(`Score calculated: ${result.value}`);
|
|
1966
|
+
return result.value;
|
|
1967
|
+
};
|
|
1968
|
+
|
|
1969
|
+
// src/application/handlers/canPlaceDominoHandler.ts
|
|
1970
|
+
var canPlaceDominoHandler = (logger, translator, useCase) => {
|
|
1971
|
+
return (command) => {
|
|
1972
|
+
logger.info(`Checking if domino ${command.domino.number} can be placed`);
|
|
1973
|
+
const canPlace = useCase(
|
|
1974
|
+
command.kingdom,
|
|
1975
|
+
command.domino,
|
|
1976
|
+
command.maxKingdomSize
|
|
1977
|
+
);
|
|
1978
|
+
logger.info(`Can place domino: ${canPlace}`);
|
|
1979
|
+
return canPlace;
|
|
1980
|
+
};
|
|
1981
|
+
};
|
|
1982
|
+
|
|
1983
|
+
// src/application/handlers/chooseDominoHandler.ts
|
|
1984
|
+
var chooseDominoHandler = (logger, translator, useCase) => (command) => {
|
|
1985
|
+
const { game, lordId, dominoPick } = command;
|
|
1986
|
+
logger.info(
|
|
1987
|
+
`Choosing domino: ${dominoPick} for lord: ${lordId} in game: ${game.id}`
|
|
1988
|
+
);
|
|
1989
|
+
if (!isGameWithNextAction(game)) {
|
|
1990
|
+
logger.error(
|
|
1991
|
+
`Invalid game, required nextAction type: 'action' but got: ${game.nextAction.type}`
|
|
1992
|
+
);
|
|
1993
|
+
throw new InvalidStepError(
|
|
1994
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
1995
|
+
{ expected: "action", actual: game.nextAction.type, gameId: game.id }
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
|
+
if (game.nextAction.nextAction !== "pickDomino") {
|
|
1999
|
+
logger.error(
|
|
2000
|
+
`Required game with pickDomino action but got: ${game.nextAction.nextAction}`
|
|
2001
|
+
);
|
|
2002
|
+
throw new InvalidStepError(
|
|
2003
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2004
|
+
{
|
|
2005
|
+
expected: "pickDomino",
|
|
2006
|
+
actual: game.nextAction.nextAction,
|
|
2007
|
+
gameId: game.id
|
|
2008
|
+
}
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
const result = useCase(game, lordId, dominoPick);
|
|
2012
|
+
if (isErr(result)) {
|
|
2013
|
+
logger.error(`Error choosing domino: ${result.error}`);
|
|
2014
|
+
const code = result.error;
|
|
2015
|
+
throw new ActionExecutionError(
|
|
2016
|
+
translateErrorCode(translator, code),
|
|
2017
|
+
code,
|
|
2018
|
+
{
|
|
2019
|
+
gameId: game.id,
|
|
2020
|
+
lordId: command.lordId,
|
|
2021
|
+
dominoPick: command.dominoPick
|
|
2022
|
+
}
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
logger.info(
|
|
2026
|
+
`Domino chosen: ${dominoPick} for lord: ${lordId} in game: ${game.id}`
|
|
2027
|
+
);
|
|
2028
|
+
return result.value;
|
|
2029
|
+
};
|
|
2030
|
+
|
|
2031
|
+
// src/application/handlers/createGameHandler.ts
|
|
2032
|
+
var createGameHandler = (logger, translator, useCase) => (command) => {
|
|
2033
|
+
logger.info(`Creating game with mode: ${command.mode}`);
|
|
2034
|
+
const result = useCase(command.mode, command.seed);
|
|
2035
|
+
if (isErr(result)) {
|
|
2036
|
+
logger.error(`Error creating game: ${result.error}`);
|
|
2037
|
+
const code = result.error;
|
|
2038
|
+
throw new StepExecutionError(translateErrorCode(translator, code), code, {
|
|
2039
|
+
mode: command.mode
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2042
|
+
logger.info(`Game created: ${result.value.id}`);
|
|
2043
|
+
return result.value;
|
|
2044
|
+
};
|
|
2045
|
+
|
|
2046
|
+
// src/application/handlers/discardDominoHandler.ts
|
|
2047
|
+
var discardDominoHandler = (logger, translator, useCase) => (command) => {
|
|
2048
|
+
const { game, lordId } = command;
|
|
2049
|
+
logger.info(`Discarding domino for lord: ${lordId} in game: ${game.id}`);
|
|
2050
|
+
if (!isGameWithNextAction(game)) {
|
|
2051
|
+
logger.error(
|
|
2052
|
+
`Invalid game, required nextAction type: 'action' but got: ${game.nextAction.type}`
|
|
2053
|
+
);
|
|
2054
|
+
throw new InvalidStepError(
|
|
2055
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2056
|
+
{ expected: "action", actual: game.nextAction.type, gameId: game.id }
|
|
2057
|
+
);
|
|
2058
|
+
}
|
|
2059
|
+
if (game.nextAction.nextAction !== "placeDomino") {
|
|
2060
|
+
logger.error(
|
|
2061
|
+
`Required game with placeDomino action but got: ${game.nextAction.nextAction}`
|
|
2062
|
+
);
|
|
2063
|
+
throw new InvalidStepError(
|
|
2064
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2065
|
+
{
|
|
2066
|
+
expected: "placeDomino",
|
|
2067
|
+
actual: game.nextAction.nextAction,
|
|
2068
|
+
gameId: game.id
|
|
2069
|
+
}
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
const result = useCase(game, lordId);
|
|
2073
|
+
if (isErr(result)) {
|
|
2074
|
+
logger.error(`Error discarding domino: ${result.error}`);
|
|
2075
|
+
const code = result.error;
|
|
2076
|
+
throw new ActionExecutionError(
|
|
2077
|
+
translateErrorCode(translator, code),
|
|
2078
|
+
code,
|
|
2079
|
+
{ gameId: game.id, lordId: command.lordId }
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
if (isGameWithNextAction(result.value)) {
|
|
2083
|
+
logger.info(`Domino discarded: ${lordId} in game: ${game.id}`);
|
|
2084
|
+
return result.value;
|
|
2085
|
+
}
|
|
2086
|
+
logger.info(`Domino discarded: ${lordId} in game: ${game.id}`);
|
|
2087
|
+
return result.value;
|
|
2088
|
+
};
|
|
2089
|
+
|
|
2090
|
+
// src/application/handlers/getDynastyResultHandler.ts
|
|
2091
|
+
var getDynastyResultHandler = (logger, translator, useCase) => {
|
|
2092
|
+
return (command) => {
|
|
2093
|
+
logger.info(
|
|
2094
|
+
`Calculating dynasty results for ${command.games.length} games`
|
|
2095
|
+
);
|
|
2096
|
+
const results = useCase(command.games);
|
|
2097
|
+
logger.info(`Dynasty results calculated`);
|
|
2098
|
+
return results;
|
|
2099
|
+
};
|
|
2100
|
+
};
|
|
2101
|
+
|
|
2102
|
+
// src/application/handlers/getExtraRulesHandler.ts
|
|
2103
|
+
var getExtraRulesHandler = (logger, translator, useCase) => (command) => {
|
|
2104
|
+
const { mode, players } = command;
|
|
2105
|
+
logger.info(
|
|
2106
|
+
`Getting extra rules for mode: ${mode} and players: ${players}`
|
|
2107
|
+
);
|
|
2108
|
+
const result = useCase(mode, players);
|
|
2109
|
+
if (isErr(result)) {
|
|
2110
|
+
logger.error(`Error getting extra rules: ${result.error}`);
|
|
2111
|
+
const code = result.error;
|
|
2112
|
+
throw new NotFoundError(translateErrorCode(translator, code), code, {
|
|
2113
|
+
mode: command.mode
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
logger.info(`Extra rules retrieved: ${result.value}`);
|
|
2117
|
+
return result.value;
|
|
2118
|
+
};
|
|
2119
|
+
|
|
2120
|
+
// src/application/handlers/getModesHandler.ts
|
|
2121
|
+
var getModesHandler = (logger, translator, useCase) => (_command) => {
|
|
2122
|
+
logger.info("Getting modes");
|
|
2123
|
+
const result = useCase();
|
|
2124
|
+
if (isErr(result)) {
|
|
2125
|
+
logger.error(`Error getting modes: ${result.error}`);
|
|
2126
|
+
const code = result.error;
|
|
2127
|
+
throw new NotFoundError(translateErrorCode(translator, code), code);
|
|
2128
|
+
}
|
|
2129
|
+
logger.info(`Modes retrieved: ${result.value}`);
|
|
2130
|
+
return result.value;
|
|
2131
|
+
};
|
|
2132
|
+
|
|
2133
|
+
// src/application/handlers/getResultHandler.ts
|
|
2134
|
+
var getResultHandler = (logger, translator, useCases) => (command) => {
|
|
2135
|
+
const { getResultUseCase: getResultUseCase2, calculateScoreUseCase: calculateScoreUseCase2 } = useCases;
|
|
2136
|
+
const { game } = command;
|
|
2137
|
+
logger.info(`Getting result for game: ${game.id}`);
|
|
2138
|
+
if (!isGameWithNextStep(game)) {
|
|
2139
|
+
logger.error(
|
|
2140
|
+
`Invalid game, required nextAction type: 'step' but got: ${game.nextAction.type}`
|
|
2141
|
+
);
|
|
2142
|
+
throw new InvalidStepError(
|
|
2143
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2144
|
+
{ expected: "step", actual: game.nextAction.type, gameId: game.id }
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
if (game.nextAction.step !== "result") {
|
|
2148
|
+
logger.error(
|
|
2149
|
+
`Required game with result step but got: ${game.nextAction.step}`
|
|
2150
|
+
);
|
|
2151
|
+
throw new InvalidStepError(
|
|
2152
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2153
|
+
{ expected: "result", actual: game.nextAction.step, gameId: game.id }
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
const { players } = game;
|
|
2157
|
+
const scoreResult = players.map((player) => {
|
|
2158
|
+
const { kingdom } = player;
|
|
2159
|
+
const result2 = calculateScoreUseCase2(kingdom);
|
|
2160
|
+
if (isErr(result2)) {
|
|
2161
|
+
logger.error(`Error when calculating score: ${result2.error}`);
|
|
2162
|
+
const code = result2.error;
|
|
2163
|
+
throw new StepExecutionError(
|
|
2164
|
+
translateErrorCode(translator, code),
|
|
2165
|
+
code,
|
|
2166
|
+
{
|
|
2167
|
+
gameId: game.id
|
|
2168
|
+
}
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
return {
|
|
2172
|
+
playerId: player.id,
|
|
2173
|
+
playerName: player.name,
|
|
2174
|
+
details: {
|
|
2175
|
+
...result2.value
|
|
2176
|
+
}
|
|
2177
|
+
};
|
|
2178
|
+
});
|
|
2179
|
+
const result = getResultUseCase2(game, scoreResult);
|
|
2180
|
+
if (isErr(result)) {
|
|
2181
|
+
logger.error(`Error getting result: ${result.error}`);
|
|
2182
|
+
const code = result.error;
|
|
2183
|
+
throw new NotFoundError(translateErrorCode(translator, code), code, {
|
|
2184
|
+
gameId: game.id
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
logger.info(`Result retrieved for game: ${game.id}`);
|
|
2188
|
+
return result.value;
|
|
2189
|
+
};
|
|
2190
|
+
|
|
2191
|
+
// src/application/handlers/getValidPlacementsHandler.ts
|
|
2192
|
+
var getValidPlacementsHandler = (logger, translator, useCase) => {
|
|
2193
|
+
return (command) => {
|
|
2194
|
+
logger.info(
|
|
2195
|
+
`Getting valid placements for domino: ${command.domino.number}`
|
|
2196
|
+
);
|
|
2197
|
+
const placements = useCase(
|
|
2198
|
+
command.kingdom,
|
|
2199
|
+
command.domino,
|
|
2200
|
+
command.maxKingdomSize
|
|
2201
|
+
);
|
|
2202
|
+
logger.info(`Found ${placements.length} valid placements`);
|
|
2203
|
+
return placements;
|
|
2204
|
+
};
|
|
2205
|
+
};
|
|
2206
|
+
|
|
2207
|
+
// src/application/handlers/placeDominoHandler.ts
|
|
2208
|
+
var placeDominoHandler = (logger, translator, useCase) => (command) => {
|
|
2209
|
+
const { game, lordId, position, rotation } = command;
|
|
2210
|
+
logger.info(
|
|
2211
|
+
`Placing domino: ${lordId} in position: ${JSON.stringify(
|
|
2212
|
+
position
|
|
2213
|
+
)} with rotation: ${rotation} in game: ${game.id}`
|
|
2214
|
+
);
|
|
2215
|
+
if (!isGameWithNextAction(game)) {
|
|
2216
|
+
logger.error(
|
|
2217
|
+
`Invalid game, required nextAction type: 'action' but got: ${game.nextAction.type}`
|
|
2218
|
+
);
|
|
2219
|
+
throw new InvalidStepError(
|
|
2220
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2221
|
+
{ expected: "action", actual: game.nextAction.type, gameId: game.id }
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
if (game.nextAction.nextAction !== "placeDomino") {
|
|
2225
|
+
logger.error(
|
|
2226
|
+
`Required game with placeDomino action but got: ${game.nextAction.nextAction}`
|
|
2227
|
+
);
|
|
2228
|
+
throw new InvalidStepError(
|
|
2229
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2230
|
+
{
|
|
2231
|
+
expected: "placeDomino",
|
|
2232
|
+
actual: game.nextAction.nextAction,
|
|
2233
|
+
gameId: game.id
|
|
2234
|
+
}
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
const result = useCase(game, lordId, position, rotation);
|
|
2238
|
+
if (isErr(result)) {
|
|
2239
|
+
logger.error(`Error placing domino: ${result.error}`);
|
|
2240
|
+
const code = result.error;
|
|
2241
|
+
throw new ActionExecutionError(
|
|
2242
|
+
translateErrorCode(translator, code),
|
|
2243
|
+
code,
|
|
2244
|
+
{
|
|
2245
|
+
gameId: game.id,
|
|
2246
|
+
lordId: command.lordId,
|
|
2247
|
+
position: command.position,
|
|
2248
|
+
rotation: command.rotation
|
|
2249
|
+
}
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
if (isGameWithNextAction(result.value)) {
|
|
2253
|
+
logger.info(
|
|
2254
|
+
`Domino placed: ${lordId} in position: ${JSON.stringify(
|
|
2255
|
+
position
|
|
2256
|
+
)} with rotation: ${rotation} in game: ${game.id}`
|
|
2257
|
+
);
|
|
2258
|
+
return result.value;
|
|
2259
|
+
}
|
|
2260
|
+
logger.info(
|
|
2261
|
+
`Domino placed: ${lordId} in position: ${JSON.stringify(
|
|
2262
|
+
position
|
|
2263
|
+
)} with rotation: ${rotation} in game: ${game.id}`
|
|
2264
|
+
);
|
|
2265
|
+
return result.value;
|
|
2266
|
+
};
|
|
2267
|
+
|
|
2268
|
+
// src/core/useCases/serialization.ts
|
|
2269
|
+
var SERIALIZATION_VERSION = 1;
|
|
2270
|
+
var serializeGame = (game) => {
|
|
2271
|
+
const serialized = {
|
|
2272
|
+
version: SERIALIZATION_VERSION,
|
|
2273
|
+
data: game,
|
|
2274
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2275
|
+
};
|
|
2276
|
+
return JSON.stringify(serialized);
|
|
2277
|
+
};
|
|
2278
|
+
var deserializeGame = (json) => {
|
|
2279
|
+
try {
|
|
2280
|
+
const parsed = JSON.parse(json);
|
|
2281
|
+
if (!parsed.version || !parsed.data) {
|
|
2282
|
+
return err("Invalid serialized game format");
|
|
2283
|
+
}
|
|
2284
|
+
if (parsed.version > SERIALIZATION_VERSION) {
|
|
2285
|
+
return err(
|
|
2286
|
+
`Unsupported serialization version: ${parsed.version}. Current version: ${SERIALIZATION_VERSION}`
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2289
|
+
const game = parsed.data;
|
|
2290
|
+
if (!game.id || !game.players || !game.lords || !game.nextAction) {
|
|
2291
|
+
return err("Invalid game state structure");
|
|
2292
|
+
}
|
|
2293
|
+
return ok(game);
|
|
2294
|
+
} catch (error) {
|
|
2295
|
+
return err(`Failed to parse game JSON: ${error.message}`);
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
2298
|
+
|
|
2299
|
+
// src/application/handlers/serializeGameHandler.ts
|
|
2300
|
+
var serializeGameHandler = (logger, _translator) => {
|
|
2301
|
+
return (command) => {
|
|
2302
|
+
logger.info(`Serializing game: ${command.game.id}`);
|
|
2303
|
+
const serialized = serializeGame(command.game);
|
|
2304
|
+
logger.info(`Game serialized successfully`);
|
|
2305
|
+
return serialized;
|
|
2306
|
+
};
|
|
2307
|
+
};
|
|
2308
|
+
var deserializeGameHandler = (logger, translator) => {
|
|
2309
|
+
return (command) => {
|
|
2310
|
+
logger.info(`Deserializing game from JSON`);
|
|
2311
|
+
const result = deserializeGame(command.json);
|
|
2312
|
+
if (isErr(result)) {
|
|
2313
|
+
logger.error(`Failed to deserialize game: ${result.error}`);
|
|
2314
|
+
const code = result.error;
|
|
2315
|
+
throw new StepExecutionError(translateErrorCode(translator, code), code);
|
|
2316
|
+
}
|
|
2317
|
+
const game = unwrap(result);
|
|
2318
|
+
logger.info(`Game deserialized successfully: ${game.id}`);
|
|
2319
|
+
return game;
|
|
2320
|
+
};
|
|
2321
|
+
};
|
|
2322
|
+
|
|
2323
|
+
// src/application/handlers/startGameHandler.ts
|
|
2324
|
+
var startGameHandler = (logger, translator, useCase) => (command) => {
|
|
2325
|
+
const { game } = command;
|
|
2326
|
+
logger.info(`Starting game: ${game.id}`);
|
|
2327
|
+
if (!isGameWithNextStep(game)) {
|
|
2328
|
+
logger.error(
|
|
2329
|
+
`Invalid game, required nextAction type: 'step' but got: ${game.nextAction.type}`
|
|
2330
|
+
);
|
|
2331
|
+
throw new InvalidStepError(
|
|
2332
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2333
|
+
{ expected: "step", actual: game.nextAction.type, gameId: game.id }
|
|
2334
|
+
);
|
|
2335
|
+
}
|
|
2336
|
+
if (game.nextAction.step !== "start" && game.nextAction.step !== "options") {
|
|
2337
|
+
logger.error(
|
|
2338
|
+
`Required game with start step or options step but got: ${game.nextAction.step}`
|
|
2339
|
+
);
|
|
2340
|
+
throw new InvalidStepError(
|
|
2341
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2342
|
+
{
|
|
2343
|
+
expected: "start|options",
|
|
2344
|
+
actual: game.nextAction.step,
|
|
2345
|
+
gameId: game.id
|
|
2346
|
+
}
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
const result = useCase(game);
|
|
2350
|
+
if (isErr(result)) {
|
|
2351
|
+
logger.error(`Error starting game: ${result.error}`);
|
|
2352
|
+
const code = result.error;
|
|
2353
|
+
throw new StepExecutionError(translateErrorCode(translator, code), code, {
|
|
2354
|
+
gameId: game.id
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
logger.info(`Game started: ${game.id}`);
|
|
2358
|
+
return result.value;
|
|
2359
|
+
};
|
|
2360
|
+
|
|
2361
|
+
// src/application/handlers/placeKnightHandler.ts
|
|
2362
|
+
var placeKnightHandler = (logger, translator, useCase) => (command) => {
|
|
2363
|
+
const { game, lordId, position } = command;
|
|
2364
|
+
logger.info(
|
|
2365
|
+
`Placing knight for lord: ${lordId} at position: ${JSON.stringify(
|
|
2366
|
+
position
|
|
2367
|
+
)} in game: ${game.id}`
|
|
2368
|
+
);
|
|
2369
|
+
if (game.nextAction.nextAction !== "placeKnight") {
|
|
2370
|
+
logger.error(
|
|
2371
|
+
`Required game with placeKnight action but got: ${game.nextAction.nextAction}`
|
|
2372
|
+
);
|
|
2373
|
+
throw new InvalidStepError(
|
|
2374
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2375
|
+
{
|
|
2376
|
+
expected: "placeKnight",
|
|
2377
|
+
actual: game.nextAction.nextAction,
|
|
2378
|
+
gameId: game.id
|
|
2379
|
+
}
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2382
|
+
const result = useCase(game, lordId, position);
|
|
2383
|
+
if (isErr(result)) {
|
|
2384
|
+
logger.error(`Error placing knight: ${result.error}`);
|
|
2385
|
+
const code = result.error;
|
|
2386
|
+
throw new ActionExecutionError(
|
|
2387
|
+
translateErrorCode(translator, code),
|
|
2388
|
+
code,
|
|
2389
|
+
{
|
|
2390
|
+
gameId: game.id,
|
|
2391
|
+
lordId: command.lordId,
|
|
2392
|
+
position: command.position
|
|
2393
|
+
}
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
logger.info(
|
|
2397
|
+
`Knight placed for lord: ${lordId} at position: ${JSON.stringify(
|
|
2398
|
+
position
|
|
2399
|
+
)} in game: ${game.id}`
|
|
2400
|
+
);
|
|
2401
|
+
return result.value;
|
|
2402
|
+
};
|
|
2403
|
+
|
|
2404
|
+
// src/application/handlers/constructBuildingHandler.ts
|
|
2405
|
+
var constructBuildingHandler = (logger, translator, useCase) => (command) => {
|
|
2406
|
+
const { game, lordId, buildingId, position } = command;
|
|
2407
|
+
logger.info(
|
|
2408
|
+
`Constructing building: ${buildingId} for lord: ${lordId} at position: ${JSON.stringify(
|
|
2409
|
+
position
|
|
2410
|
+
)} in game: ${game.id}`
|
|
2411
|
+
);
|
|
2412
|
+
if (game.nextAction.nextAction !== "constructBuilding") {
|
|
2413
|
+
logger.error(
|
|
2414
|
+
`Required game with constructBuilding action but got: ${game.nextAction.nextAction}`
|
|
2415
|
+
);
|
|
2416
|
+
throw new InvalidStepError(
|
|
2417
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2418
|
+
{
|
|
2419
|
+
expected: "constructBuilding",
|
|
2420
|
+
actual: game.nextAction.nextAction,
|
|
2421
|
+
gameId: game.id
|
|
2422
|
+
}
|
|
2423
|
+
);
|
|
2424
|
+
}
|
|
2425
|
+
const result = useCase(game, lordId, buildingId, position);
|
|
2426
|
+
if (isErr(result)) {
|
|
2427
|
+
logger.error(`Error constructing building: ${result.error}`);
|
|
2428
|
+
const code = result.error;
|
|
2429
|
+
throw new ActionExecutionError(
|
|
2430
|
+
translateErrorCode(translator, code),
|
|
2431
|
+
code,
|
|
2432
|
+
{
|
|
2433
|
+
gameId: game.id,
|
|
2434
|
+
lordId: command.lordId,
|
|
2435
|
+
buildingId: command.buildingId,
|
|
2436
|
+
position: command.position
|
|
2437
|
+
}
|
|
2438
|
+
);
|
|
2439
|
+
}
|
|
2440
|
+
logger.info(
|
|
2441
|
+
`Building constructed: ${buildingId} for lord: ${lordId} at position: ${JSON.stringify(
|
|
2442
|
+
position
|
|
2443
|
+
)} in game: ${game.id}`
|
|
2444
|
+
);
|
|
2445
|
+
return result.value;
|
|
2446
|
+
};
|
|
2447
|
+
|
|
2448
|
+
// src/application/handlers/useDragonHandler.ts
|
|
2449
|
+
var useDragonHandler = (logger, translator, useCase) => (command) => {
|
|
2450
|
+
const { game, lordId, buildingId } = command;
|
|
2451
|
+
logger.info(
|
|
2452
|
+
`Using dragon on building: ${buildingId} for lord: ${lordId} in game: ${game.id}`
|
|
2453
|
+
);
|
|
2454
|
+
if (game.nextAction.nextAction !== "useDragon") {
|
|
2455
|
+
logger.error(
|
|
2456
|
+
`Required game with useDragon action but got: ${game.nextAction.nextAction}`
|
|
2457
|
+
);
|
|
2458
|
+
throw new InvalidStepError(
|
|
2459
|
+
translateErrorCode(translator, ErrorCode.INVALID_STEP),
|
|
2460
|
+
{
|
|
2461
|
+
expected: "useDragon",
|
|
2462
|
+
actual: game.nextAction.nextAction,
|
|
2463
|
+
gameId: game.id
|
|
2464
|
+
}
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2467
|
+
const result = useCase(game, lordId, buildingId);
|
|
2468
|
+
if (isErr(result)) {
|
|
2469
|
+
logger.error(`Error using dragon: ${result.error}`);
|
|
2470
|
+
const code = result.error;
|
|
2471
|
+
throw new ActionExecutionError(
|
|
2472
|
+
translateErrorCode(translator, code),
|
|
2473
|
+
code,
|
|
2474
|
+
{
|
|
2475
|
+
gameId: game.id,
|
|
2476
|
+
lordId: command.lordId,
|
|
2477
|
+
buildingId: command.buildingId
|
|
2478
|
+
}
|
|
2479
|
+
);
|
|
2480
|
+
}
|
|
2481
|
+
logger.info(
|
|
2482
|
+
`Dragon used on building: ${buildingId} for lord: ${lordId} in game: ${game.id}`
|
|
2483
|
+
);
|
|
2484
|
+
return result.value;
|
|
2485
|
+
};
|
|
2486
|
+
|
|
2487
|
+
// src/application/handlers/skipOptionalActionHandler.ts
|
|
2488
|
+
var skipOptionalActionHandler = (logger, translator, useCase) => (command) => {
|
|
2489
|
+
const { game, lordId } = command;
|
|
2490
|
+
logger.info(
|
|
2491
|
+
`Skipping optional action for lord: ${lordId} in game: ${game.id}`
|
|
2492
|
+
);
|
|
2493
|
+
const result = useCase(game, lordId);
|
|
2494
|
+
if (isErr(result)) {
|
|
2495
|
+
logger.error(`Error skipping optional action: ${result.error}`);
|
|
2496
|
+
const code = result.error;
|
|
2497
|
+
throw new ActionExecutionError(
|
|
2498
|
+
translateErrorCode(translator, code),
|
|
2499
|
+
code,
|
|
2500
|
+
{ gameId: game.id, lordId: command.lordId }
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2503
|
+
logger.info(
|
|
2504
|
+
`Optional action skipped for lord: ${lordId} in game: ${game.id}`
|
|
2505
|
+
);
|
|
2506
|
+
return result.value;
|
|
2507
|
+
};
|
|
2508
|
+
|
|
2509
|
+
// src/core/domain/types/domino.ts
|
|
2510
|
+
var GROUND = [
|
|
2511
|
+
"castle",
|
|
2512
|
+
"wheat",
|
|
2513
|
+
"forest",
|
|
2514
|
+
"sea",
|
|
2515
|
+
"plain",
|
|
2516
|
+
"swamp",
|
|
2517
|
+
"mine"
|
|
2518
|
+
];
|
|
2519
|
+
|
|
2520
|
+
// src/core/domain/types/kingdom.ts
|
|
2521
|
+
var GRIDSIZE = 9;
|
|
2522
|
+
var MAX_KINGDOM_SIZE = 5;
|
|
2523
|
+
|
|
2524
|
+
// src/core/domain/types/player.ts
|
|
2525
|
+
var playerActions = {
|
|
2526
|
+
/** Place a previously picked domino on the kingdom */
|
|
2527
|
+
placeDomino: "placeDomino",
|
|
2528
|
+
/** Pick a domino from the revealed set */
|
|
2529
|
+
pickDomino: "pickDomino",
|
|
2530
|
+
/** Pass/discard when no valid placement exists */
|
|
2531
|
+
pass: "pass",
|
|
2532
|
+
/** Place a knight on the just-placed domino (Queendomino, optional) */
|
|
2533
|
+
placeKnight: "placeKnight",
|
|
2534
|
+
/** Construct a building on a construction square (Queendomino, optional) */
|
|
2535
|
+
constructBuilding: "constructBuilding",
|
|
2536
|
+
/** Use the Dragon to burn a building tile (Queendomino, optional) */
|
|
2537
|
+
useDragon: "useDragon",
|
|
2538
|
+
/** Skip the current optional action (Queendomino) */
|
|
2539
|
+
skipOptionalAction: "skipOptionalAction"
|
|
2540
|
+
};
|
|
2541
|
+
|
|
2542
|
+
// src/core/domain/entities/rule.ts
|
|
2543
|
+
var getAvailablesExtraRules = (extraRules, mode, numberOfPlayers) => {
|
|
2544
|
+
const availableExtraRules = extraRules.filter((rule) => {
|
|
2545
|
+
return rule.mode.filter((m) => m.name === mode).length > 0 && (!rule.playersLimit || rule.playersLimit === numberOfPlayers);
|
|
2546
|
+
});
|
|
2547
|
+
if (availableExtraRules.length === 0) {
|
|
2548
|
+
return err(ErrorCode.STEP_EXECUTION_FAILED);
|
|
2549
|
+
}
|
|
2550
|
+
return ok(availableExtraRules);
|
|
2551
|
+
};
|
|
2552
|
+
var toExtraRule = (rule, extraRules) => {
|
|
2553
|
+
const foundRule = extraRules.find((r) => r.name === rule);
|
|
2554
|
+
if (!foundRule) {
|
|
2555
|
+
return err(ErrorCode.STEP_EXECUTION_FAILED);
|
|
2556
|
+
}
|
|
2557
|
+
return ok(foundRule);
|
|
2558
|
+
};
|
|
2559
|
+
|
|
2560
|
+
// src/utils/seededShuffle.ts
|
|
2561
|
+
var hashSeed = (str) => {
|
|
2562
|
+
let hash = 2166136261;
|
|
2563
|
+
for (let i = 0; i < str.length; i++) {
|
|
2564
|
+
hash ^= str.charCodeAt(i);
|
|
2565
|
+
hash = Math.imul(hash, 16777619);
|
|
2566
|
+
}
|
|
2567
|
+
return hash >>> 0;
|
|
2568
|
+
};
|
|
2569
|
+
var mulberry32 = (seed) => {
|
|
2570
|
+
let state = seed;
|
|
2571
|
+
return () => {
|
|
2572
|
+
state |= 0;
|
|
2573
|
+
state = state + 1831565813 | 0;
|
|
2574
|
+
let t = Math.imul(state ^ state >>> 15, 1 | state);
|
|
2575
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2576
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2577
|
+
};
|
|
2578
|
+
};
|
|
2579
|
+
var createSeededShuffle = (seed, salt) => {
|
|
2580
|
+
const numericSeed = hashSeed(`${seed}|${salt}`);
|
|
2581
|
+
const random = mulberry32(numericSeed);
|
|
2582
|
+
return (array) => {
|
|
2583
|
+
const result = [...array];
|
|
2584
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
2585
|
+
const j = Math.floor(random() * (i + 1));
|
|
2586
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
2587
|
+
}
|
|
2588
|
+
return result;
|
|
2589
|
+
};
|
|
2590
|
+
};
|
|
2591
|
+
|
|
2592
|
+
// src/core/useCases/addExtraRules.ts
|
|
2593
|
+
var addExtraRulesUseCase = (deps) => (game, rules) => {
|
|
2594
|
+
const { ruleRepository, dominoesRepository, shuffleMethod: shuffleMethod2 } = deps;
|
|
2595
|
+
const availableExtraRules = ruleRepository.getAllExtra();
|
|
2596
|
+
const newExtraRules = [];
|
|
2597
|
+
for (const rule of rules) {
|
|
2598
|
+
const result = toExtraRule(rule, availableExtraRules);
|
|
2599
|
+
if (isErr(result)) {
|
|
2600
|
+
return result;
|
|
2601
|
+
}
|
|
2602
|
+
newExtraRules.push(result.value);
|
|
2603
|
+
}
|
|
2604
|
+
let updatedBasic = game.rules.basic;
|
|
2605
|
+
let updatedDominoes = game.dominoes;
|
|
2606
|
+
const hasMightyDuel = newExtraRules.some((r) => r.name === "The Mighty Duel");
|
|
2607
|
+
if (hasMightyDuel) {
|
|
2608
|
+
updatedBasic = {
|
|
2609
|
+
...updatedBasic,
|
|
2610
|
+
maxDominoes: 48,
|
|
2611
|
+
maxTurns: 12,
|
|
2612
|
+
maxKingdomSize: 7
|
|
2613
|
+
};
|
|
2614
|
+
const allDominoes = dominoesRepository.getForMode(game.mode);
|
|
2615
|
+
if (allDominoes) {
|
|
2616
|
+
const shuffle = game.seed ? createSeededShuffle(game.seed, "mighty-duel") : shuffleMethod2;
|
|
2617
|
+
updatedDominoes = shuffle(allDominoes);
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
const updatedRules = {
|
|
2621
|
+
basic: updatedBasic,
|
|
2622
|
+
extra: newExtraRules
|
|
2623
|
+
};
|
|
2624
|
+
const next = {
|
|
2625
|
+
type: "step",
|
|
2626
|
+
step: gameSteps.start
|
|
2627
|
+
};
|
|
2628
|
+
const updatedGame = {
|
|
2629
|
+
...game,
|
|
2630
|
+
dominoes: updatedDominoes,
|
|
2631
|
+
rules: updatedRules,
|
|
2632
|
+
nextAction: next
|
|
2633
|
+
};
|
|
2634
|
+
return ok(updatedGame);
|
|
2635
|
+
};
|
|
2636
|
+
|
|
2637
|
+
// src/core/domain/entities/economy.ts
|
|
2638
|
+
var STARTING_COINS = 7;
|
|
2639
|
+
var MAX_KNIGHTS = 3;
|
|
2640
|
+
var addCoins = (player, amount) => ({
|
|
2641
|
+
...player,
|
|
2642
|
+
coins: (player.coins ?? 0) + amount
|
|
2643
|
+
});
|
|
2644
|
+
var removeCoins = (player, amount) => ({
|
|
2645
|
+
...player,
|
|
2646
|
+
coins: (player.coins ?? 0) - amount
|
|
2647
|
+
});
|
|
2648
|
+
var canAfford = (player, cost) => (player.coins ?? 0) >= cost;
|
|
2649
|
+
var addTowers = (player, amount) => ({
|
|
2650
|
+
...player,
|
|
2651
|
+
towers: (player.towers ?? 0) + amount
|
|
2652
|
+
});
|
|
2653
|
+
var getTowerCount = (player) => player.towers ?? 0;
|
|
2654
|
+
|
|
2655
|
+
// src/core/domain/entities/domino.ts
|
|
2656
|
+
var isGround = (ground) => {
|
|
2657
|
+
return GROUND.includes(ground);
|
|
2658
|
+
};
|
|
2659
|
+
var createTile = (ground, crowns = 0) => {
|
|
2660
|
+
if (isGround(ground)) {
|
|
2661
|
+
return { type: ground, crowns };
|
|
2662
|
+
}
|
|
2663
|
+
return { type: ground, crowns: 0 };
|
|
2664
|
+
};
|
|
2665
|
+
|
|
2666
|
+
// src/core/domain/entities/kingdom.ts
|
|
2667
|
+
var createEmptyKingdom = () => {
|
|
2668
|
+
const emptyTile = createTile("empty");
|
|
2669
|
+
return Array.from(
|
|
2670
|
+
{ length: GRIDSIZE },
|
|
2671
|
+
() => Array.from({ length: GRIDSIZE }, () => emptyTile)
|
|
2672
|
+
);
|
|
2673
|
+
};
|
|
2674
|
+
var placeCastle = (kingdom) => {
|
|
2675
|
+
const castle = createTile("castle");
|
|
2676
|
+
const newKingdom = deepCopyKingdom(kingdom);
|
|
2677
|
+
newKingdom[4][4] = castle;
|
|
2678
|
+
return newKingdom;
|
|
2679
|
+
};
|
|
2680
|
+
var placeTile = (kingdom, position, tile) => {
|
|
2681
|
+
const newKingdom = deepCopyKingdom(kingdom);
|
|
2682
|
+
newKingdom[position.y][position.x] = tile;
|
|
2683
|
+
return newKingdom;
|
|
2684
|
+
};
|
|
2685
|
+
var getTile = (kingdom, position) => {
|
|
2686
|
+
const tile = kingdom[position.y]?.[position.x];
|
|
2687
|
+
if (!tile) {
|
|
2688
|
+
return err(ErrorCode.PLACEMENT_OUT_OF_BOUNDS);
|
|
2689
|
+
}
|
|
2690
|
+
return ok(tile);
|
|
2691
|
+
};
|
|
2692
|
+
var calculateDominoPosition = (position, rotation, domino) => {
|
|
2693
|
+
let firstTile;
|
|
2694
|
+
let secondTile;
|
|
2695
|
+
switch (rotation) {
|
|
2696
|
+
case 0:
|
|
2697
|
+
firstTile = {
|
|
2698
|
+
tile: domino.left,
|
|
2699
|
+
position: {
|
|
2700
|
+
x: position.x,
|
|
2701
|
+
y: position.y
|
|
2702
|
+
}
|
|
2703
|
+
};
|
|
2704
|
+
secondTile = {
|
|
2705
|
+
tile: domino.right,
|
|
2706
|
+
position: {
|
|
2707
|
+
x: position.x + 1,
|
|
2708
|
+
y: position.y
|
|
2709
|
+
}
|
|
2710
|
+
};
|
|
2711
|
+
break;
|
|
2712
|
+
case 90:
|
|
2713
|
+
firstTile = {
|
|
2714
|
+
tile: domino.left,
|
|
2715
|
+
position: {
|
|
2716
|
+
x: position.x,
|
|
2717
|
+
y: position.y
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2720
|
+
secondTile = {
|
|
2721
|
+
tile: domino.right,
|
|
2722
|
+
position: {
|
|
2723
|
+
x: position.x,
|
|
2724
|
+
y: position.y + 1
|
|
2725
|
+
}
|
|
2726
|
+
};
|
|
2727
|
+
break;
|
|
2728
|
+
case 180:
|
|
2729
|
+
firstTile = {
|
|
2730
|
+
tile: domino.right,
|
|
2731
|
+
position: {
|
|
2732
|
+
x: position.x,
|
|
2733
|
+
y: position.y
|
|
2734
|
+
}
|
|
2735
|
+
};
|
|
2736
|
+
secondTile = {
|
|
2737
|
+
tile: domino.left,
|
|
2738
|
+
position: {
|
|
2739
|
+
x: position.x + 1,
|
|
2740
|
+
y: position.y
|
|
2741
|
+
}
|
|
2742
|
+
};
|
|
2743
|
+
break;
|
|
2744
|
+
case 270:
|
|
2745
|
+
firstTile = {
|
|
2746
|
+
tile: domino.right,
|
|
2747
|
+
position: {
|
|
2748
|
+
x: position.x,
|
|
2749
|
+
y: position.y
|
|
2750
|
+
}
|
|
2751
|
+
};
|
|
2752
|
+
secondTile = {
|
|
2753
|
+
tile: domino.left,
|
|
2754
|
+
position: {
|
|
2755
|
+
x: position.x,
|
|
2756
|
+
y: position.y + 1
|
|
2757
|
+
}
|
|
2758
|
+
};
|
|
2759
|
+
break;
|
|
2760
|
+
}
|
|
2761
|
+
return [firstTile, secondTile];
|
|
2762
|
+
};
|
|
2763
|
+
var placeDomino = (kingdom, position, rotation, domino, maxKingdomSize = MAX_KINGDOM_SIZE) => {
|
|
2764
|
+
const [firstTile, secondTile] = calculateDominoPosition(
|
|
2765
|
+
position,
|
|
2766
|
+
rotation,
|
|
2767
|
+
domino
|
|
2768
|
+
);
|
|
2769
|
+
const isFreePlaceResult = isFreePlace(
|
|
2770
|
+
kingdom,
|
|
2771
|
+
firstTile.position,
|
|
2772
|
+
secondTile.position
|
|
2773
|
+
);
|
|
2774
|
+
if (isErr(isFreePlaceResult)) {
|
|
2775
|
+
return isFreePlaceResult;
|
|
2776
|
+
}
|
|
2777
|
+
if (exceedsMaxSize(kingdom, firstTile.position, secondTile.position, maxKingdomSize)) {
|
|
2778
|
+
return err(ErrorCode.PLACEMENT_EXCEEDS_KINGDOM_SIZE);
|
|
2779
|
+
}
|
|
2780
|
+
const adjacentTiles = isAdjacent(
|
|
2781
|
+
kingdom,
|
|
2782
|
+
firstTile.position,
|
|
2783
|
+
secondTile.position
|
|
2784
|
+
);
|
|
2785
|
+
if (adjacentTiles.length === 0) {
|
|
2786
|
+
return err(ErrorCode.PLACEMENT_NOT_ADJACENT);
|
|
2787
|
+
}
|
|
2788
|
+
const hasValidAdjacentTiles = hasValidAdjacent(adjacentTiles, [
|
|
2789
|
+
firstTile.tile,
|
|
2790
|
+
secondTile.tile
|
|
2791
|
+
]);
|
|
2792
|
+
if (!hasValidAdjacentTiles) {
|
|
2793
|
+
return err(ErrorCode.PLACEMENT_INVALID_TERRAIN);
|
|
2794
|
+
}
|
|
2795
|
+
return ok(placeTiles(kingdom, [firstTile, secondTile]));
|
|
2796
|
+
};
|
|
2797
|
+
var placeTiles = (kingdom, tiles) => {
|
|
2798
|
+
let newKingdom = deepCopyKingdom(kingdom);
|
|
2799
|
+
for (const { tile, position } of tiles) {
|
|
2800
|
+
newKingdom = placeTile(newKingdom, position, tile);
|
|
2801
|
+
}
|
|
2802
|
+
return newKingdom;
|
|
2803
|
+
};
|
|
2804
|
+
var isFreePlace = (kingdom, firstPosition, secondPosition) => {
|
|
2805
|
+
const firstTile = getTile(kingdom, firstPosition);
|
|
2806
|
+
const secondTile = getTile(kingdom, secondPosition);
|
|
2807
|
+
if (isErr(firstTile)) {
|
|
2808
|
+
return firstTile;
|
|
2809
|
+
}
|
|
2810
|
+
if (isErr(secondTile)) {
|
|
2811
|
+
return secondTile;
|
|
2812
|
+
}
|
|
2813
|
+
if (firstTile.value.type !== "empty" || secondTile.value.type !== "empty") {
|
|
2814
|
+
return err(ErrorCode.PLACEMENT_NOT_EMPTY);
|
|
2815
|
+
}
|
|
2816
|
+
return ok(true);
|
|
2817
|
+
};
|
|
2818
|
+
var isAdjacent = (kingdom, firstPosition, secondPosition) => {
|
|
2819
|
+
const offsets = [
|
|
2820
|
+
{ x: -1, y: 0 },
|
|
2821
|
+
{ x: 1, y: 0 },
|
|
2822
|
+
{ x: 0, y: -1 },
|
|
2823
|
+
{ x: 0, y: 1 }
|
|
2824
|
+
];
|
|
2825
|
+
const isOwnPosition = (x, y) => x === firstPosition.x && y === firstPosition.y || x === secondPosition.x && y === secondPosition.y;
|
|
2826
|
+
const adjacentTiles = [];
|
|
2827
|
+
for (const pos of [firstPosition, secondPosition]) {
|
|
2828
|
+
for (const offset of offsets) {
|
|
2829
|
+
const nx = pos.x + offset.x;
|
|
2830
|
+
const ny = pos.y + offset.y;
|
|
2831
|
+
if (isOwnPosition(nx, ny)) continue;
|
|
2832
|
+
const tile = getTile(kingdom, { x: nx, y: ny });
|
|
2833
|
+
if (isOk(tile) && tile.value.type !== "empty") {
|
|
2834
|
+
adjacentTiles.push(tile.value);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
return adjacentTiles;
|
|
2839
|
+
};
|
|
2840
|
+
var hasValidAdjacent = (adjacentTiles, tiles) => {
|
|
2841
|
+
return adjacentTiles.some((adjacentTile) => {
|
|
2842
|
+
return tiles.some((tile) => {
|
|
2843
|
+
return tile.type === adjacentTile.type || adjacentTile.type === "castle";
|
|
2844
|
+
});
|
|
2845
|
+
});
|
|
2846
|
+
};
|
|
2847
|
+
var checkCastleIsInMiddle = (kingdom) => {
|
|
2848
|
+
let castleX;
|
|
2849
|
+
let castleY;
|
|
2850
|
+
for (let y = 0; y < kingdom.length; y++) {
|
|
2851
|
+
const x = kingdom[y].findIndex((tile) => tile.type === "castle");
|
|
2852
|
+
if (x !== -1) {
|
|
2853
|
+
castleX = x;
|
|
2854
|
+
castleY = y;
|
|
2855
|
+
break;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
if (castleX === void 0 || castleY === void 0) {
|
|
2859
|
+
return false;
|
|
2860
|
+
}
|
|
2861
|
+
const bbox = getBoundingBox(kingdom);
|
|
2862
|
+
if (!bbox) return false;
|
|
2863
|
+
const centerX = (bbox.minX + bbox.maxX) / 2;
|
|
2864
|
+
const centerY = (bbox.minY + bbox.maxY) / 2;
|
|
2865
|
+
return castleX === centerX && castleY === centerY;
|
|
2866
|
+
};
|
|
2867
|
+
var getBoundingBox = (kingdom) => {
|
|
2868
|
+
let minX = Infinity;
|
|
2869
|
+
let maxX = -Infinity;
|
|
2870
|
+
let minY = Infinity;
|
|
2871
|
+
let maxY = -Infinity;
|
|
2872
|
+
for (let y = 0; y < kingdom.length; y++) {
|
|
2873
|
+
for (let x = 0; x < kingdom[y].length; x++) {
|
|
2874
|
+
if (kingdom[y][x].type !== "empty") {
|
|
2875
|
+
if (x < minX) minX = x;
|
|
2876
|
+
if (x > maxX) maxX = x;
|
|
2877
|
+
if (y < minY) minY = y;
|
|
2878
|
+
if (y > maxY) maxY = y;
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
if (minX === Infinity) return null;
|
|
2883
|
+
return { minX, maxX, minY, maxY };
|
|
2884
|
+
};
|
|
2885
|
+
var exceedsMaxSize = (kingdom, firstPosition, secondPosition, maxSize = MAX_KINGDOM_SIZE) => {
|
|
2886
|
+
const bbox = getBoundingBox(kingdom);
|
|
2887
|
+
let minX = bbox?.minX ?? Infinity;
|
|
2888
|
+
let maxX = bbox?.maxX ?? -Infinity;
|
|
2889
|
+
let minY = bbox?.minY ?? Infinity;
|
|
2890
|
+
let maxY = bbox?.maxY ?? -Infinity;
|
|
2891
|
+
for (const pos of [firstPosition, secondPosition]) {
|
|
2892
|
+
if (pos.x < minX) minX = pos.x;
|
|
2893
|
+
if (pos.x > maxX) maxX = pos.x;
|
|
2894
|
+
if (pos.y < minY) minY = pos.y;
|
|
2895
|
+
if (pos.y > maxY) maxY = pos.y;
|
|
2896
|
+
}
|
|
2897
|
+
return maxX - minX + 1 > maxSize || maxY - minY + 1 > maxSize;
|
|
2898
|
+
};
|
|
2899
|
+
var countDominoes = (kingdom) => {
|
|
2900
|
+
const notEmpties = kingdom.flat().filter((tile) => tile.type !== "empty");
|
|
2901
|
+
return (notEmpties.length - 1) / 2;
|
|
2902
|
+
};
|
|
2903
|
+
var deepCopyKingdom = (kingdom) => {
|
|
2904
|
+
return kingdom.map((line) => line.map((tile) => ({ ...tile })));
|
|
2905
|
+
};
|
|
2906
|
+
|
|
2907
|
+
// src/core/domain/entities/player.ts
|
|
2908
|
+
var MINPLAYERSNAMELENGTH = 3;
|
|
2909
|
+
var validatePlayer = (name) => {
|
|
2910
|
+
if (!name || name.trim().length < MINPLAYERSNAMELENGTH) {
|
|
2911
|
+
return err(ErrorCode.INVALID_PLAYER_NAME);
|
|
2912
|
+
}
|
|
2913
|
+
return ok(name);
|
|
2914
|
+
};
|
|
2915
|
+
var createPlayer = (playerName, id, kingdom, bot) => {
|
|
2916
|
+
const name = validatePlayer(playerName);
|
|
2917
|
+
if (isErr(name)) {
|
|
2918
|
+
return name;
|
|
2919
|
+
}
|
|
2920
|
+
return ok({
|
|
2921
|
+
name: name.value,
|
|
2922
|
+
id,
|
|
2923
|
+
kingdom,
|
|
2924
|
+
...bot && { bot }
|
|
2925
|
+
});
|
|
2926
|
+
};
|
|
2927
|
+
|
|
2928
|
+
// src/core/useCases/addPlayers.ts
|
|
2929
|
+
var normalizePlayerInput = (input2) => {
|
|
2930
|
+
if (typeof input2 === "string") {
|
|
2931
|
+
return { name: input2 };
|
|
2932
|
+
}
|
|
2933
|
+
return { name: input2.name, bot: input2.bot };
|
|
2934
|
+
};
|
|
2935
|
+
var addPlayersUseCase = (deps) => (game, players) => {
|
|
2936
|
+
const { uuidMethod: uuidMethod3, shuffleMethod: shuffleMethod2, ruleRepository } = deps;
|
|
2937
|
+
if (!players || players.length < 1 || players.length > 4) {
|
|
2938
|
+
return err(ErrorCode.INVALID_PLAYER_COUNT);
|
|
2939
|
+
}
|
|
2940
|
+
const newPlayers = [];
|
|
2941
|
+
for (const player of players) {
|
|
2942
|
+
const { name, bot } = normalizePlayerInput(player);
|
|
2943
|
+
let kingdom = createEmptyKingdom();
|
|
2944
|
+
kingdom = placeCastle(kingdom);
|
|
2945
|
+
let id = uuidMethod3();
|
|
2946
|
+
const newPlayer = createPlayer(name, id, kingdom, bot);
|
|
2947
|
+
if (isErr(newPlayer)) {
|
|
2948
|
+
return newPlayer;
|
|
2949
|
+
}
|
|
2950
|
+
if (game.mode.name === "QueenDomino") {
|
|
2951
|
+
newPlayer.value.coins = STARTING_COINS;
|
|
2952
|
+
newPlayer.value.towers = 0;
|
|
2953
|
+
newPlayer.value.knights = [];
|
|
2954
|
+
newPlayer.value.buildings = [];
|
|
2955
|
+
}
|
|
2956
|
+
newPlayers.push(newPlayer.value);
|
|
2957
|
+
}
|
|
2958
|
+
const rules = ruleRepository.getAll();
|
|
2959
|
+
const basicRules = rules.basic[newPlayers.length];
|
|
2960
|
+
if (!basicRules) {
|
|
2961
|
+
return err(ErrorCode.STEP_EXECUTION_FAILED);
|
|
2962
|
+
}
|
|
2963
|
+
const dominoes = game.dominoes;
|
|
2964
|
+
const { maxDominoes } = basicRules;
|
|
2965
|
+
const shuffle = game.seed ? createSeededShuffle(game.seed, "dominoes") : shuffleMethod2;
|
|
2966
|
+
const shuffledDominoes = shuffle(dominoes);
|
|
2967
|
+
const splicedDominoes = shuffledDominoes.slice(0, maxDominoes);
|
|
2968
|
+
const next = {
|
|
2969
|
+
type: "step",
|
|
2970
|
+
step: "options"
|
|
2971
|
+
};
|
|
2972
|
+
const updatedGame = {
|
|
2973
|
+
...game,
|
|
2974
|
+
players: newPlayers,
|
|
2975
|
+
dominoes: splicedDominoes,
|
|
2976
|
+
rules: {
|
|
2977
|
+
...game.rules,
|
|
2978
|
+
basic: basicRules
|
|
2979
|
+
},
|
|
2980
|
+
nextAction: next
|
|
2981
|
+
};
|
|
2982
|
+
return ok(updatedGame);
|
|
2983
|
+
};
|
|
2984
|
+
|
|
2985
|
+
// src/core/useCases/calculateScore.ts
|
|
2986
|
+
var calculateScoreUseCase = (kingdom) => {
|
|
2987
|
+
const rows = kingdom.length;
|
|
2988
|
+
const cols = kingdom[0].length;
|
|
2989
|
+
const visited = kingdom.map((row) => row.map(() => false));
|
|
2990
|
+
const properties = [];
|
|
2991
|
+
const directions = [
|
|
2992
|
+
[-1, 0],
|
|
2993
|
+
[1, 0],
|
|
2994
|
+
[0, -1],
|
|
2995
|
+
[0, 1]
|
|
2996
|
+
];
|
|
2997
|
+
for (let y = 0; y < rows; y++) {
|
|
2998
|
+
for (let x = 0; x < cols; x++) {
|
|
2999
|
+
const tile = kingdom[y][x];
|
|
3000
|
+
if (visited[y][x] || tile.type === "empty" || tile.type === "castle") {
|
|
3001
|
+
continue;
|
|
3002
|
+
}
|
|
3003
|
+
const type = tile.type;
|
|
3004
|
+
let size = 0;
|
|
3005
|
+
let crowns = 0;
|
|
3006
|
+
const queue = [[x, y]];
|
|
3007
|
+
visited[y][x] = true;
|
|
3008
|
+
while (queue.length > 0) {
|
|
3009
|
+
const [cx, cy] = queue.pop();
|
|
3010
|
+
const current = kingdom[cy][cx];
|
|
3011
|
+
size++;
|
|
3012
|
+
crowns += current.crowns;
|
|
3013
|
+
for (const [dx, dy] of directions) {
|
|
3014
|
+
const nx = cx + dx;
|
|
3015
|
+
const ny = cy + dy;
|
|
3016
|
+
if (nx >= 0 && ny >= 0 && nx < cols && ny < rows && !visited[ny][nx] && kingdom[ny][nx].type === type) {
|
|
3017
|
+
visited[ny][nx] = true;
|
|
3018
|
+
queue.push([nx, ny]);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
properties.push({ size, crowns });
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
if (properties.length === 0) {
|
|
3026
|
+
return ok({
|
|
3027
|
+
points: 0,
|
|
3028
|
+
maxPropertiesSize: 0,
|
|
3029
|
+
totalCrowns: 0
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
let points = 0;
|
|
3033
|
+
let maxPropertiesSize = 0;
|
|
3034
|
+
let totalCrowns = 0;
|
|
3035
|
+
for (const property of properties) {
|
|
3036
|
+
points += property.size * property.crowns;
|
|
3037
|
+
if (property.size > maxPropertiesSize) {
|
|
3038
|
+
maxPropertiesSize = property.size;
|
|
3039
|
+
}
|
|
3040
|
+
totalCrowns += property.crowns;
|
|
3041
|
+
}
|
|
3042
|
+
return ok({
|
|
3043
|
+
points,
|
|
3044
|
+
maxPropertiesSize,
|
|
3045
|
+
totalCrowns
|
|
3046
|
+
});
|
|
3047
|
+
};
|
|
3048
|
+
|
|
3049
|
+
// src/core/domain/entities/lord.ts
|
|
3050
|
+
var createLord = (id, playerId) => {
|
|
3051
|
+
return {
|
|
3052
|
+
id,
|
|
3053
|
+
playerId,
|
|
3054
|
+
turnEnded: false,
|
|
3055
|
+
hasPick: false,
|
|
3056
|
+
hasPlace: true
|
|
3057
|
+
};
|
|
3058
|
+
};
|
|
3059
|
+
var hasPick = (lord) => lord.hasPick;
|
|
3060
|
+
var hasPlace = (lord) => lord.hasPlace;
|
|
3061
|
+
var hasTurnEnded = (lord) => lord.turnEnded;
|
|
3062
|
+
var canPass = (lord) => {
|
|
3063
|
+
return !hasPick(lord) && !hasPlace(lord) && !hasTurnEnded(lord);
|
|
3064
|
+
};
|
|
3065
|
+
var canPick = (lord) => {
|
|
3066
|
+
return !hasPick(lord) && hasPlace(lord) && !hasTurnEnded(lord);
|
|
3067
|
+
};
|
|
3068
|
+
var canPlaceAndDominoPickedIsDefined = (lord) => {
|
|
3069
|
+
return !hasPick(lord) && !hasPlace(lord) && !hasTurnEnded(lord) && lord.dominoPicked !== void 0;
|
|
3070
|
+
};
|
|
3071
|
+
var updateLordOrder = (lords) => {
|
|
3072
|
+
return [...lords].sort((a, b) => {
|
|
3073
|
+
return (a.dominoPicked?.number ?? 0) - (b.dominoPicked?.number ?? 0);
|
|
3074
|
+
});
|
|
3075
|
+
};
|
|
3076
|
+
var resetLordsActions = (lords) => {
|
|
3077
|
+
return lords.map((lord) => ({
|
|
3078
|
+
...lord,
|
|
3079
|
+
turnEnded: false,
|
|
3080
|
+
hasPick: false,
|
|
3081
|
+
hasPlace: false
|
|
3082
|
+
}));
|
|
3083
|
+
};
|
|
3084
|
+
var nextLordWithAction = (lords) => {
|
|
3085
|
+
const nextLord = findNextLord(lords);
|
|
3086
|
+
return {
|
|
3087
|
+
type: "action",
|
|
3088
|
+
nextLord: nextLord.id,
|
|
3089
|
+
nextAction: hasPlace(nextLord) ? playerActions.pickDomino : playerActions.placeDomino
|
|
3090
|
+
};
|
|
3091
|
+
};
|
|
3092
|
+
var findNextLord = (lords) => {
|
|
3093
|
+
const lord = lords.find((lord2) => !hasTurnEnded(lord2));
|
|
3094
|
+
if (!lord) {
|
|
3095
|
+
return lords[0];
|
|
3096
|
+
}
|
|
3097
|
+
return lord;
|
|
3098
|
+
};
|
|
3099
|
+
var allLordsHavePlayed = (lords) => {
|
|
3100
|
+
return lords.every((lord) => lord.turnEnded);
|
|
3101
|
+
};
|
|
3102
|
+
var QUEENDOMINO_ACTION_SEQUENCE = [
|
|
3103
|
+
playerActions.placeKnight,
|
|
3104
|
+
playerActions.constructBuilding,
|
|
3105
|
+
playerActions.useDragon,
|
|
3106
|
+
playerActions.pickDomino
|
|
3107
|
+
];
|
|
3108
|
+
var nextQueenDominoAction = (currentAction) => {
|
|
3109
|
+
const index = QUEENDOMINO_ACTION_SEQUENCE.indexOf(currentAction);
|
|
3110
|
+
if (index === -1 || index >= QUEENDOMINO_ACTION_SEQUENCE.length - 1) {
|
|
3111
|
+
return playerActions.pickDomino;
|
|
3112
|
+
}
|
|
3113
|
+
return QUEENDOMINO_ACTION_SEQUENCE[index + 1];
|
|
3114
|
+
};
|
|
3115
|
+
|
|
3116
|
+
// src/core/useCases/chooseDomino.ts
|
|
3117
|
+
var chooseDominoUseCase = (game, lordId, dominoPick) => {
|
|
3118
|
+
const currentLord = game.lords.find(
|
|
3119
|
+
(lord) => lord.id === game.nextAction.nextLord
|
|
3120
|
+
);
|
|
3121
|
+
if (!currentLord) {
|
|
3122
|
+
return err(ErrorCode.LORD_NOT_FOUND);
|
|
3123
|
+
}
|
|
3124
|
+
if (currentLord.id !== lordId) {
|
|
3125
|
+
return err(ErrorCode.NOT_YOUR_TURN);
|
|
3126
|
+
}
|
|
3127
|
+
if (!canPick(currentLord)) {
|
|
3128
|
+
return err(ErrorCode.CANNOT_PICK);
|
|
3129
|
+
}
|
|
3130
|
+
const selectedDomino = game.currentDominoes.find(
|
|
3131
|
+
(domino) => domino.domino.number === dominoPick
|
|
3132
|
+
);
|
|
3133
|
+
if (!selectedDomino) {
|
|
3134
|
+
return err(ErrorCode.DOMINO_NOT_FOUND);
|
|
3135
|
+
}
|
|
3136
|
+
if (selectedDomino.picked) {
|
|
3137
|
+
return err(ErrorCode.DOMINO_ALREADY_PICKED);
|
|
3138
|
+
}
|
|
3139
|
+
const updatedCurrentDominoes = game.currentDominoes.map(
|
|
3140
|
+
(domino) => domino.domino.number === dominoPick ? { ...domino, picked: true, lordId: currentLord.id } : domino
|
|
3141
|
+
);
|
|
3142
|
+
const updatedLords = game.lords.map(
|
|
3143
|
+
(lord) => lord.id === currentLord.id ? { ...lord, hasPick: true, turnEnded: true, dominoPicked: selectedDomino.domino } : lord
|
|
3144
|
+
);
|
|
3145
|
+
const turnEnded = allLordsHavePlayed(updatedLords);
|
|
3146
|
+
if (turnEnded) {
|
|
3147
|
+
const lords = resetLordsActions(updateLordOrder(updatedLords));
|
|
3148
|
+
const updatedTurn = game.turn + 1;
|
|
3149
|
+
const next2 = nextLordWithAction(lords);
|
|
3150
|
+
const dominoesCopy = [...game.dominoes];
|
|
3151
|
+
const dominoesDrawn = dominoesCopy.splice(
|
|
3152
|
+
0,
|
|
3153
|
+
game.rules.basic.dominoesPerTurn
|
|
3154
|
+
);
|
|
3155
|
+
dominoesDrawn.sort((a, b) => a.number - b.number);
|
|
3156
|
+
const updatedCurrentDominoes2 = dominoesDrawn.map((domino, index) => {
|
|
3157
|
+
return {
|
|
3158
|
+
domino,
|
|
3159
|
+
picked: false,
|
|
3160
|
+
lordId: null,
|
|
3161
|
+
position: index + 1
|
|
3162
|
+
};
|
|
3163
|
+
});
|
|
3164
|
+
return ok({
|
|
3165
|
+
...game,
|
|
3166
|
+
currentDominoes: updatedCurrentDominoes2,
|
|
3167
|
+
dominoes: dominoesCopy,
|
|
3168
|
+
lords,
|
|
3169
|
+
turn: updatedTurn,
|
|
3170
|
+
nextAction: next2
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
const next = nextLordWithAction(updatedLords);
|
|
3174
|
+
const updatedState = {
|
|
3175
|
+
...game,
|
|
3176
|
+
currentDominoes: updatedCurrentDominoes,
|
|
3177
|
+
lords: updatedLords,
|
|
3178
|
+
nextAction: next
|
|
3179
|
+
};
|
|
3180
|
+
return ok(updatedState);
|
|
3181
|
+
};
|
|
3182
|
+
|
|
3183
|
+
// src/core/domain/entities/game.ts
|
|
3184
|
+
var create = (payload) => {
|
|
3185
|
+
const { id, mode, dominoes, seed, buildings } = payload;
|
|
3186
|
+
let queendomino;
|
|
3187
|
+
if (mode.name === "QueenDomino" && buildings) {
|
|
3188
|
+
queendomino = {
|
|
3189
|
+
buildersBoard: {
|
|
3190
|
+
slots: [],
|
|
3191
|
+
drawPile: buildings
|
|
3192
|
+
},
|
|
3193
|
+
queenHolderId: null,
|
|
3194
|
+
dragonAvailable: true,
|
|
3195
|
+
dragonUsedThisRound: false
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
3198
|
+
return {
|
|
3199
|
+
id,
|
|
3200
|
+
dominoes,
|
|
3201
|
+
currentDominoes: [],
|
|
3202
|
+
players: [],
|
|
3203
|
+
lords: [],
|
|
3204
|
+
turn: 0,
|
|
3205
|
+
nextAction: {
|
|
3206
|
+
type: "step",
|
|
3207
|
+
step: gameSteps.addPlayers
|
|
3208
|
+
},
|
|
3209
|
+
rules: {
|
|
3210
|
+
basic: {
|
|
3211
|
+
lords: 0,
|
|
3212
|
+
maxDominoes: 0,
|
|
3213
|
+
dominoesPerTurn: 0,
|
|
3214
|
+
maxTurns: 0,
|
|
3215
|
+
maxKingdomSize: 5
|
|
3216
|
+
},
|
|
3217
|
+
extra: []
|
|
3218
|
+
},
|
|
3219
|
+
mode,
|
|
3220
|
+
...seed !== void 0 && { seed },
|
|
3221
|
+
...queendomino !== void 0 && { queendomino }
|
|
3222
|
+
};
|
|
3223
|
+
};
|
|
3224
|
+
|
|
3225
|
+
// src/core/domain/entities/mode.ts
|
|
3226
|
+
var findMode = (mode, availableMode) => {
|
|
3227
|
+
return availableMode.find((m) => m.name === mode);
|
|
3228
|
+
};
|
|
3229
|
+
var createMode = (mode, availableMode) => {
|
|
3230
|
+
const foundMode = findMode(mode, availableMode);
|
|
3231
|
+
if (foundMode) {
|
|
3232
|
+
return ok(foundMode);
|
|
3233
|
+
}
|
|
3234
|
+
return err(ErrorCode.MODE_NOT_FOUND);
|
|
3235
|
+
};
|
|
3236
|
+
|
|
3237
|
+
// src/core/useCases/createGame.ts
|
|
3238
|
+
var createGameUseCase = (deps) => (mode, seed) => {
|
|
3239
|
+
const { modeRepository, dominoesRepository, uuidMethod: uuidMethod3, buildingsRepository } = deps;
|
|
3240
|
+
const id = uuidMethod3();
|
|
3241
|
+
const gameSeed = seed ?? uuidMethod3();
|
|
3242
|
+
const availableMode = modeRepository.getAvailables();
|
|
3243
|
+
const newMode = createMode(mode, availableMode);
|
|
3244
|
+
if (isErr(newMode)) {
|
|
3245
|
+
return newMode;
|
|
3246
|
+
}
|
|
3247
|
+
const dominoes = dominoesRepository.getForMode(newMode.value);
|
|
3248
|
+
if (!dominoes || dominoes.length === 0) {
|
|
3249
|
+
return err(ErrorCode.DOMINO_NOT_FOUND);
|
|
3250
|
+
}
|
|
3251
|
+
const isQueenDomino = newMode.value.name === "QueenDomino";
|
|
3252
|
+
let buildings;
|
|
3253
|
+
if (isQueenDomino && buildingsRepository) {
|
|
3254
|
+
buildings = buildingsRepository.getAll();
|
|
3255
|
+
}
|
|
3256
|
+
const payload = {
|
|
3257
|
+
id,
|
|
3258
|
+
mode: newMode.value,
|
|
3259
|
+
dominoes,
|
|
3260
|
+
seed: gameSeed,
|
|
3261
|
+
buildings
|
|
3262
|
+
};
|
|
3263
|
+
const newGame = create(payload);
|
|
3264
|
+
return ok(newGame);
|
|
3265
|
+
};
|
|
3266
|
+
|
|
3267
|
+
// src/core/useCases/getValidPlacements.ts
|
|
3268
|
+
var getValidPlacementsUseCase = (kingdom, domino, maxKingdomSize = MAX_KINGDOM_SIZE) => {
|
|
3269
|
+
const validPlacements = [];
|
|
3270
|
+
const rotations = [0, 90, 180, 270];
|
|
3271
|
+
for (let y = 0; y < GRIDSIZE; y++) {
|
|
3272
|
+
for (let x = 0; x < GRIDSIZE; x++) {
|
|
3273
|
+
const position = { x, y };
|
|
3274
|
+
for (const rotation of rotations) {
|
|
3275
|
+
const result = placeDomino(kingdom, position, rotation, domino, maxKingdomSize);
|
|
3276
|
+
if (isOk(result)) {
|
|
3277
|
+
validPlacements.push({ position, rotation });
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
return validPlacements;
|
|
3283
|
+
};
|
|
3284
|
+
var canPlaceDominoUseCase = (kingdom, domino, maxKingdomSize = MAX_KINGDOM_SIZE) => {
|
|
3285
|
+
const rotations = [0, 90, 180, 270];
|
|
3286
|
+
for (let y = 0; y < GRIDSIZE; y++) {
|
|
3287
|
+
for (let x = 0; x < GRIDSIZE; x++) {
|
|
3288
|
+
const position = { x, y };
|
|
3289
|
+
for (const rotation of rotations) {
|
|
3290
|
+
const result = placeDomino(kingdom, position, rotation, domino, maxKingdomSize);
|
|
3291
|
+
if (isOk(result)) {
|
|
3292
|
+
return true;
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
return false;
|
|
3298
|
+
};
|
|
3299
|
+
|
|
3300
|
+
// src/core/useCases/discardDomino.ts
|
|
3301
|
+
var discardDominoUseCase = (game, lordId) => {
|
|
3302
|
+
const nextAction = game.nextAction;
|
|
3303
|
+
const currentLord = game.lords.find(
|
|
3304
|
+
(lord) => lord.id === nextAction.nextLord
|
|
3305
|
+
);
|
|
3306
|
+
if (!currentLord) {
|
|
3307
|
+
return err(ErrorCode.LORD_NOT_FOUND);
|
|
3308
|
+
}
|
|
3309
|
+
if (currentLord.id !== lordId) {
|
|
3310
|
+
return err(ErrorCode.NOT_YOUR_TURN);
|
|
3311
|
+
}
|
|
3312
|
+
if (!canPass(currentLord)) {
|
|
3313
|
+
return err(ErrorCode.CANNOT_PLACE);
|
|
3314
|
+
}
|
|
3315
|
+
if (currentLord.dominoPicked) {
|
|
3316
|
+
const currentPlayer = game.players.find(
|
|
3317
|
+
(player) => player.id === currentLord.playerId
|
|
3318
|
+
);
|
|
3319
|
+
if (currentPlayer && canPlaceDominoUseCase(currentPlayer.kingdom, currentLord.dominoPicked, game.rules.basic.maxKingdomSize)) {
|
|
3320
|
+
return err(ErrorCode.CANNOT_PLACE);
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
const maxTurns = game.rules.basic.maxTurns;
|
|
3324
|
+
const currentTurn = game.turn;
|
|
3325
|
+
const isLastTurn = currentTurn === maxTurns;
|
|
3326
|
+
const updatedLord = game.lords.map((lord) => {
|
|
3327
|
+
if (lord.id === currentLord.id) {
|
|
3328
|
+
if (isLastTurn) {
|
|
3329
|
+
return {
|
|
3330
|
+
...lord,
|
|
3331
|
+
turnEnded: true,
|
|
3332
|
+
hasPlace: true
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
return {
|
|
3336
|
+
...lord,
|
|
3337
|
+
hasPlace: true
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
return lord;
|
|
3341
|
+
});
|
|
3342
|
+
let updatedGame;
|
|
3343
|
+
const resultStep = {
|
|
3344
|
+
type: "step",
|
|
3345
|
+
step: gameSteps.result
|
|
3346
|
+
};
|
|
3347
|
+
if (isLastTurn && allLordsHavePlayed(updatedLord)) {
|
|
3348
|
+
updatedGame = {
|
|
3349
|
+
...game,
|
|
3350
|
+
lords: updatedLord,
|
|
3351
|
+
nextAction: resultStep
|
|
3352
|
+
};
|
|
3353
|
+
} else {
|
|
3354
|
+
updatedGame = {
|
|
3355
|
+
...game,
|
|
3356
|
+
lords: updatedLord,
|
|
3357
|
+
nextAction: nextLordWithAction(updatedLord)
|
|
3358
|
+
};
|
|
3359
|
+
}
|
|
3360
|
+
return ok(updatedGame);
|
|
3361
|
+
};
|
|
3362
|
+
|
|
3363
|
+
// src/core/useCases/getDynastyResult.ts
|
|
3364
|
+
var getDynastyResultUseCase = (games) => {
|
|
3365
|
+
const playerMap = /* @__PURE__ */ new Map();
|
|
3366
|
+
for (const game of games) {
|
|
3367
|
+
for (const result of game.result) {
|
|
3368
|
+
const existing = playerMap.get(result.playerId);
|
|
3369
|
+
if (existing) {
|
|
3370
|
+
existing.points.push(result.details.points);
|
|
3371
|
+
} else {
|
|
3372
|
+
playerMap.set(result.playerId, {
|
|
3373
|
+
name: result.playerName,
|
|
3374
|
+
points: [result.details.points]
|
|
3375
|
+
});
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
const results = [];
|
|
3380
|
+
for (const [playerId, data] of playerMap) {
|
|
3381
|
+
results.push({
|
|
3382
|
+
playerId,
|
|
3383
|
+
playerName: data.name,
|
|
3384
|
+
totalPoints: data.points.reduce((sum, p) => sum + p, 0),
|
|
3385
|
+
gamesPoints: data.points
|
|
3386
|
+
});
|
|
3387
|
+
}
|
|
3388
|
+
results.sort((a, b) => b.totalPoints - a.totalPoints);
|
|
3389
|
+
const finalResults = [];
|
|
3390
|
+
for (let i = 0; i < results.length; i++) {
|
|
3391
|
+
const current = results[i];
|
|
3392
|
+
const previous = i > 0 ? finalResults[i - 1] : null;
|
|
3393
|
+
const position = previous && current.totalPoints === previous.totalPoints ? previous.position : i + 1;
|
|
3394
|
+
finalResults.push({ ...current, position });
|
|
3395
|
+
}
|
|
3396
|
+
return finalResults;
|
|
3397
|
+
};
|
|
3398
|
+
|
|
3399
|
+
// src/core/useCases/getExtraRules.ts
|
|
3400
|
+
var getExtraRulesUseCase = (deps) => (mode, players) => {
|
|
3401
|
+
const { ruleRepository } = deps;
|
|
3402
|
+
const allExtraRules = ruleRepository.getAllExtra();
|
|
3403
|
+
if (!allExtraRules) {
|
|
3404
|
+
return err(ErrorCode.STEP_EXECUTION_FAILED);
|
|
3405
|
+
}
|
|
3406
|
+
return getAvailablesExtraRules(allExtraRules, mode, players);
|
|
3407
|
+
};
|
|
3408
|
+
|
|
3409
|
+
// src/core/useCases/getModes.ts
|
|
3410
|
+
var getModesUseCase = (deps) => () => {
|
|
3411
|
+
const { modeRepository } = deps;
|
|
3412
|
+
return ok(modeRepository.getAvailables());
|
|
3413
|
+
};
|
|
3414
|
+
|
|
3415
|
+
// src/core/useCases/getResult.ts
|
|
3416
|
+
var getResultUseCase = (game, scoreResult) => {
|
|
3417
|
+
const { rules } = game;
|
|
3418
|
+
const { basic, extra } = rules;
|
|
3419
|
+
const finalScoreResult = scoreResult.map((score) => {
|
|
3420
|
+
let finalScore = score.details.points;
|
|
3421
|
+
const player = game.players.find((p) => p.id === score.playerId);
|
|
3422
|
+
if (!player) {
|
|
3423
|
+
return score;
|
|
3424
|
+
}
|
|
3425
|
+
const { kingdom } = player;
|
|
3426
|
+
if (extra.length > 0) {
|
|
3427
|
+
extra.forEach((extraRule) => {
|
|
3428
|
+
if (extraRule.name === "The middle Kingdom") {
|
|
3429
|
+
const castleIsInMiddle = checkCastleIsInMiddle(kingdom);
|
|
3430
|
+
if (castleIsInMiddle) {
|
|
3431
|
+
finalScore += 10;
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
if (extraRule.name === "Harmony") {
|
|
3435
|
+
const totalDominoes = countDominoes(kingdom);
|
|
3436
|
+
const dominoLimit = basic.maxDominoes;
|
|
3437
|
+
const totalPlayers = game.players.length;
|
|
3438
|
+
const dominoesPerPlayer = dominoLimit / totalPlayers;
|
|
3439
|
+
if (totalDominoes === dominoesPerPlayer) {
|
|
3440
|
+
finalScore += 5;
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
});
|
|
3444
|
+
}
|
|
3445
|
+
return {
|
|
3446
|
+
...score,
|
|
3447
|
+
details: {
|
|
3448
|
+
...score.details,
|
|
3449
|
+
points: finalScore
|
|
3450
|
+
}
|
|
3451
|
+
};
|
|
3452
|
+
});
|
|
3453
|
+
finalScoreResult.sort((a, b) => {
|
|
3454
|
+
if (a.details.points !== b.details.points) {
|
|
3455
|
+
return b.details.points - a.details.points;
|
|
3456
|
+
}
|
|
3457
|
+
return b.details.maxPropertiesSize - a.details.maxPropertiesSize;
|
|
3458
|
+
});
|
|
3459
|
+
const result = [];
|
|
3460
|
+
let isTie = false;
|
|
3461
|
+
result.push({
|
|
3462
|
+
playerId: finalScoreResult[0].playerId,
|
|
3463
|
+
playerName: finalScoreResult[0].playerName,
|
|
3464
|
+
details: finalScoreResult[0].details,
|
|
3465
|
+
position: 1
|
|
3466
|
+
});
|
|
3467
|
+
for (let i = 1; i < finalScoreResult.length; i++) {
|
|
3468
|
+
const currentScore = finalScoreResult[i];
|
|
3469
|
+
const previousScore = finalScoreResult[i - 1];
|
|
3470
|
+
if (previousScore && currentScore && currentScore.details.points === previousScore.details.points && currentScore.details.maxPropertiesSize === previousScore.details.maxPropertiesSize) {
|
|
3471
|
+
isTie = true;
|
|
3472
|
+
}
|
|
3473
|
+
if (isTie) {
|
|
3474
|
+
result.push({
|
|
3475
|
+
...currentScore,
|
|
3476
|
+
position: result[i - 1].position
|
|
3477
|
+
});
|
|
3478
|
+
} else {
|
|
3479
|
+
result.push({
|
|
3480
|
+
...currentScore,
|
|
3481
|
+
position: i + 1
|
|
3482
|
+
});
|
|
3483
|
+
}
|
|
3484
|
+
isTie = false;
|
|
3485
|
+
}
|
|
3486
|
+
return ok({
|
|
3487
|
+
...game,
|
|
3488
|
+
result
|
|
3489
|
+
});
|
|
3490
|
+
};
|
|
3491
|
+
|
|
3492
|
+
// src/core/useCases/placeDomino.ts
|
|
3493
|
+
var placeDominoUseCase = (game, lordId, position, rotation) => {
|
|
3494
|
+
const nextAction = game.nextAction;
|
|
3495
|
+
const currentLord = game.lords.find(
|
|
3496
|
+
(lord) => lord.id === nextAction.nextLord
|
|
3497
|
+
);
|
|
3498
|
+
if (!currentLord) {
|
|
3499
|
+
return err(ErrorCode.LORD_NOT_FOUND);
|
|
3500
|
+
}
|
|
3501
|
+
if (currentLord.id !== lordId) {
|
|
3502
|
+
return err(ErrorCode.NOT_YOUR_TURN);
|
|
3503
|
+
}
|
|
3504
|
+
if (!canPlaceAndDominoPickedIsDefined(currentLord)) {
|
|
3505
|
+
return err(ErrorCode.CANNOT_PLACE);
|
|
3506
|
+
}
|
|
3507
|
+
const domino = currentLord.dominoPicked;
|
|
3508
|
+
const currentPlayer = game.players.find(
|
|
3509
|
+
(player) => player.id === currentLord.playerId
|
|
3510
|
+
);
|
|
3511
|
+
if (!currentPlayer) {
|
|
3512
|
+
return err(ErrorCode.PLAYER_NOT_FOUND);
|
|
3513
|
+
}
|
|
3514
|
+
const updatedKingdom = placeDomino(
|
|
3515
|
+
currentPlayer.kingdom,
|
|
3516
|
+
position,
|
|
3517
|
+
rotation,
|
|
3518
|
+
domino,
|
|
3519
|
+
game.rules.basic.maxKingdomSize
|
|
3520
|
+
);
|
|
3521
|
+
if (isErr(updatedKingdom)) {
|
|
3522
|
+
return updatedKingdom;
|
|
3523
|
+
}
|
|
3524
|
+
const updatedPlayers = game.players.map((player) => {
|
|
3525
|
+
if (player.id === currentPlayer.id) {
|
|
3526
|
+
player.kingdom = updatedKingdom.value;
|
|
3527
|
+
}
|
|
3528
|
+
return player;
|
|
3529
|
+
});
|
|
3530
|
+
const maxTurns = game.rules.basic.maxTurns;
|
|
3531
|
+
const currentTurn = game.turn;
|
|
3532
|
+
const isLastTurn = currentTurn === maxTurns;
|
|
3533
|
+
const updatedLords = game.lords.map((lord) => {
|
|
3534
|
+
if (lord.id === currentLord.id) {
|
|
3535
|
+
if (isLastTurn) {
|
|
3536
|
+
return {
|
|
3537
|
+
...lord,
|
|
3538
|
+
turnEnded: true,
|
|
3539
|
+
hasPlace: true
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
return {
|
|
3543
|
+
...lord,
|
|
3544
|
+
hasPlace: true
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3547
|
+
return lord;
|
|
3548
|
+
});
|
|
3549
|
+
let updatedGame;
|
|
3550
|
+
const resultStep = {
|
|
3551
|
+
type: "step",
|
|
3552
|
+
step: gameSteps.result
|
|
3553
|
+
};
|
|
3554
|
+
const isQueenDomino = game.mode.name === "QueenDomino";
|
|
3555
|
+
if (isLastTurn && allLordsHavePlayed(updatedLords)) {
|
|
3556
|
+
updatedGame = {
|
|
3557
|
+
...game,
|
|
3558
|
+
lords: updatedLords,
|
|
3559
|
+
nextAction: resultStep,
|
|
3560
|
+
players: updatedPlayers
|
|
3561
|
+
};
|
|
3562
|
+
} else if (isQueenDomino && !isLastTurn) {
|
|
3563
|
+
updatedGame = {
|
|
3564
|
+
...game,
|
|
3565
|
+
lords: updatedLords,
|
|
3566
|
+
nextAction: {
|
|
3567
|
+
type: "action",
|
|
3568
|
+
nextLord: currentLord.id,
|
|
3569
|
+
nextAction: playerActions.placeKnight
|
|
3570
|
+
},
|
|
3571
|
+
players: updatedPlayers
|
|
3572
|
+
};
|
|
3573
|
+
} else if (isQueenDomino && isLastTurn) {
|
|
3574
|
+
const queenDominoAction = {
|
|
3575
|
+
type: "action",
|
|
3576
|
+
nextLord: currentLord.id,
|
|
3577
|
+
nextAction: playerActions.placeKnight
|
|
3578
|
+
};
|
|
3579
|
+
updatedGame = {
|
|
3580
|
+
...game,
|
|
3581
|
+
lords: updatedLords,
|
|
3582
|
+
nextAction: queenDominoAction,
|
|
3583
|
+
players: updatedPlayers
|
|
3584
|
+
};
|
|
3585
|
+
} else {
|
|
3586
|
+
updatedGame = {
|
|
3587
|
+
...game,
|
|
3588
|
+
lords: updatedLords,
|
|
3589
|
+
nextAction: nextLordWithAction(updatedLords),
|
|
3590
|
+
players: updatedPlayers
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
return ok(updatedGame);
|
|
3594
|
+
};
|
|
3595
|
+
|
|
3596
|
+
// src/core/domain/entities/knight.ts
|
|
3597
|
+
var countConstructionSquaresInTerritory = (kingdom, position) => {
|
|
3598
|
+
const rows = kingdom.length;
|
|
3599
|
+
const cols = kingdom[0]?.length ?? 0;
|
|
3600
|
+
const startTile = kingdom[position.y]?.[position.x];
|
|
3601
|
+
if (!startTile || startTile.type === "empty" || startTile.type === "castle") {
|
|
3602
|
+
return 0;
|
|
3603
|
+
}
|
|
3604
|
+
const terrainType = startTile.type;
|
|
3605
|
+
const visited = kingdom.map((row) => row.map(() => false));
|
|
3606
|
+
const queue = [[position.x, position.y]];
|
|
3607
|
+
visited[position.y][position.x] = true;
|
|
3608
|
+
let count = 0;
|
|
3609
|
+
const directions = [
|
|
3610
|
+
[-1, 0],
|
|
3611
|
+
[1, 0],
|
|
3612
|
+
[0, -1],
|
|
3613
|
+
[0, 1]
|
|
3614
|
+
];
|
|
3615
|
+
while (queue.length > 0) {
|
|
3616
|
+
const [cx, cy] = queue.pop();
|
|
3617
|
+
const tile = kingdom[cy][cx];
|
|
3618
|
+
if (tile.type !== "empty" && tile.type !== "castle" && "hasConstructionSquare" in tile && tile.hasConstructionSquare) {
|
|
3619
|
+
count++;
|
|
3620
|
+
}
|
|
3621
|
+
for (const [dx, dy] of directions) {
|
|
3622
|
+
const nx = cx + dx;
|
|
3623
|
+
const ny = cy + dy;
|
|
3624
|
+
if (nx >= 0 && ny >= 0 && nx < cols && ny < rows && !visited[ny][nx] && kingdom[ny][nx].type === terrainType) {
|
|
3625
|
+
visited[ny][nx] = true;
|
|
3626
|
+
queue.push([nx, ny]);
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
return count;
|
|
3631
|
+
};
|
|
3632
|
+
var getTerritoryTerrain = (kingdom, position) => {
|
|
3633
|
+
const tile = kingdom[position.y]?.[position.x];
|
|
3634
|
+
if (!tile || tile.type === "empty" || tile.type === "castle") return null;
|
|
3635
|
+
return tile.type;
|
|
3636
|
+
};
|
|
3637
|
+
var territoryHasKnight = (kingdom, position, knights) => {
|
|
3638
|
+
const terrain = getTerritoryTerrain(kingdom, position);
|
|
3639
|
+
if (!terrain) return false;
|
|
3640
|
+
const rows = kingdom.length;
|
|
3641
|
+
const cols = kingdom[0]?.length ?? 0;
|
|
3642
|
+
const visited = kingdom.map((row) => row.map(() => false));
|
|
3643
|
+
const queue = [[position.x, position.y]];
|
|
3644
|
+
visited[position.y][position.x] = true;
|
|
3645
|
+
const territoryPositions = /* @__PURE__ */ new Set();
|
|
3646
|
+
const directions = [
|
|
3647
|
+
[-1, 0],
|
|
3648
|
+
[1, 0],
|
|
3649
|
+
[0, -1],
|
|
3650
|
+
[0, 1]
|
|
3651
|
+
];
|
|
3652
|
+
while (queue.length > 0) {
|
|
3653
|
+
const [cx, cy] = queue.pop();
|
|
3654
|
+
territoryPositions.add(`${cx},${cy}`);
|
|
3655
|
+
for (const [dx, dy] of directions) {
|
|
3656
|
+
const nx = cx + dx;
|
|
3657
|
+
const ny = cy + dy;
|
|
3658
|
+
if (nx >= 0 && ny >= 0 && nx < cols && ny < rows && !visited[ny][nx] && kingdom[ny][nx].type === terrain) {
|
|
3659
|
+
visited[ny][nx] = true;
|
|
3660
|
+
queue.push([nx, ny]);
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
return knights.some(
|
|
3665
|
+
(k) => territoryPositions.has(`${k.position.x},${k.position.y}`)
|
|
3666
|
+
);
|
|
3667
|
+
};
|
|
3668
|
+
var canPlaceKnight = (player, kingdom, position) => {
|
|
3669
|
+
const knights = player.knights ?? [];
|
|
3670
|
+
if (knights.length >= MAX_KNIGHTS) return false;
|
|
3671
|
+
const tile = kingdom[position.y]?.[position.x];
|
|
3672
|
+
if (!tile || tile.type === "empty" || tile.type === "castle") return false;
|
|
3673
|
+
if (territoryHasKnight(kingdom, position, knights)) return false;
|
|
3674
|
+
return true;
|
|
3675
|
+
};
|
|
3676
|
+
var placeKnight = (player, kingdom, position) => {
|
|
3677
|
+
const knight = { playerId: player.id, position };
|
|
3678
|
+
const knights = [...player.knights ?? [], knight];
|
|
3679
|
+
const taxAmount = countConstructionSquaresInTerritory(kingdom, position);
|
|
3680
|
+
const updatedPlayer = addCoins({ ...player, knights }, taxAmount);
|
|
3681
|
+
return updatedPlayer;
|
|
3682
|
+
};
|
|
3683
|
+
|
|
3684
|
+
// src/core/useCases/placeKnight.ts
|
|
3685
|
+
var placeKnightUseCase = (game, lordId, position) => {
|
|
3686
|
+
const currentAction = game.nextAction;
|
|
3687
|
+
if (currentAction.nextLord !== lordId) {
|
|
3688
|
+
return err(ErrorCode.NOT_YOUR_TURN);
|
|
3689
|
+
}
|
|
3690
|
+
if (currentAction.nextAction !== playerActions.placeKnight) {
|
|
3691
|
+
return err(ErrorCode.INVALID_OPTIONAL_ACTION);
|
|
3692
|
+
}
|
|
3693
|
+
const currentLord = game.lords.find((l) => l.id === lordId);
|
|
3694
|
+
if (!currentLord) return err(ErrorCode.LORD_NOT_FOUND);
|
|
3695
|
+
const currentPlayer = game.players.find(
|
|
3696
|
+
(p) => p.id === currentLord.playerId
|
|
3697
|
+
);
|
|
3698
|
+
if (!currentPlayer) return err(ErrorCode.PLAYER_NOT_FOUND);
|
|
3699
|
+
if (!canPlaceKnight(currentPlayer, currentPlayer.kingdom, position)) {
|
|
3700
|
+
return err(ErrorCode.CANNOT_PLACE_KNIGHT);
|
|
3701
|
+
}
|
|
3702
|
+
const updatedPlayer = placeKnight(
|
|
3703
|
+
currentPlayer,
|
|
3704
|
+
currentPlayer.kingdom,
|
|
3705
|
+
position
|
|
3706
|
+
);
|
|
3707
|
+
const updatedPlayers = game.players.map(
|
|
3708
|
+
(p) => p.id === currentPlayer.id ? updatedPlayer : p
|
|
3709
|
+
);
|
|
3710
|
+
const nextAction = nextQueenDominoAction(playerActions.placeKnight);
|
|
3711
|
+
return ok({
|
|
3712
|
+
...game,
|
|
3713
|
+
players: updatedPlayers,
|
|
3714
|
+
nextAction: {
|
|
3715
|
+
type: "action",
|
|
3716
|
+
nextLord: lordId,
|
|
3717
|
+
nextAction
|
|
3718
|
+
}
|
|
3719
|
+
});
|
|
3720
|
+
};
|
|
3721
|
+
|
|
3722
|
+
// src/core/domain/entities/building.ts
|
|
3723
|
+
var BOARD_SLOTS = 4;
|
|
3724
|
+
var createBuildersBoard = (tiles, shuffle) => {
|
|
3725
|
+
const shuffled = shuffle([...tiles]);
|
|
3726
|
+
const slots = shuffled.slice(0, BOARD_SLOTS);
|
|
3727
|
+
const drawPile = shuffled.slice(BOARD_SLOTS);
|
|
3728
|
+
return {
|
|
3729
|
+
slots,
|
|
3730
|
+
drawPile
|
|
3731
|
+
};
|
|
3732
|
+
};
|
|
3733
|
+
var findBuildingOnBoard = (board, buildingId) => {
|
|
3734
|
+
for (let i = 0; i < board.slots.length; i++) {
|
|
3735
|
+
const slot = board.slots[i];
|
|
3736
|
+
if (slot && slot.id === buildingId) {
|
|
3737
|
+
return { building: slot, slotIndex: i };
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
return null;
|
|
3741
|
+
};
|
|
3742
|
+
var removeBuildingFromBoard = (board, slotIndex) => {
|
|
3743
|
+
const newSlots = [...board.slots];
|
|
3744
|
+
newSlots[slotIndex] = null;
|
|
3745
|
+
return { ...board, slots: newSlots };
|
|
3746
|
+
};
|
|
3747
|
+
var purchaseBuilding = (board, player, buildingId) => {
|
|
3748
|
+
const found = findBuildingOnBoard(board, buildingId);
|
|
3749
|
+
if (!found) return null;
|
|
3750
|
+
const { building, slotIndex } = found;
|
|
3751
|
+
if (!canAfford(player, building.cost)) return null;
|
|
3752
|
+
let updatedPlayer = removeCoins(player, building.cost);
|
|
3753
|
+
if (building.immediateBonus) {
|
|
3754
|
+
switch (building.immediateBonus.type) {
|
|
3755
|
+
case "coins":
|
|
3756
|
+
updatedPlayer = addCoins(updatedPlayer, building.immediateBonus.amount);
|
|
3757
|
+
break;
|
|
3758
|
+
case "tower":
|
|
3759
|
+
updatedPlayer = addTowers(
|
|
3760
|
+
updatedPlayer,
|
|
3761
|
+
building.immediateBonus.amount
|
|
3762
|
+
);
|
|
3763
|
+
break;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
if (building.towers > 0) {
|
|
3767
|
+
updatedPlayer = addTowers(updatedPlayer, building.towers);
|
|
3768
|
+
}
|
|
3769
|
+
const updatedBoard = removeBuildingFromBoard(board, slotIndex);
|
|
3770
|
+
return { updatedBoard, updatedPlayer, building };
|
|
3771
|
+
};
|
|
3772
|
+
var isValidConstructionPosition = (kingdom, placedBuildings, position) => {
|
|
3773
|
+
const tile = kingdom[position.y]?.[position.x];
|
|
3774
|
+
if (!tile || tile.type === "empty" || tile.type === "castle") return false;
|
|
3775
|
+
if (!("hasConstructionSquare" in tile) || !tile.hasConstructionSquare)
|
|
3776
|
+
return false;
|
|
3777
|
+
const occupied = placedBuildings.some(
|
|
3778
|
+
(b) => b.position.x === position.x && b.position.y === position.y
|
|
3779
|
+
);
|
|
3780
|
+
return !occupied;
|
|
3781
|
+
};
|
|
3782
|
+
var placeBuildingOnKingdom = (kingdom, building, position) => {
|
|
3783
|
+
const newKingdom = kingdom.map((row) => [...row]);
|
|
3784
|
+
const tile = newKingdom[position.y][position.x];
|
|
3785
|
+
if (tile.type !== "empty" && tile.type !== "castle") {
|
|
3786
|
+
newKingdom[position.y][position.x] = {
|
|
3787
|
+
...tile,
|
|
3788
|
+
crowns: tile.crowns + building.crowns
|
|
3789
|
+
};
|
|
3790
|
+
}
|
|
3791
|
+
return newKingdom;
|
|
3792
|
+
};
|
|
3793
|
+
var destroyWithDragon = (board, buildingId, player) => {
|
|
3794
|
+
const found = findBuildingOnBoard(board, buildingId);
|
|
3795
|
+
if (!found) return null;
|
|
3796
|
+
const { building, slotIndex } = found;
|
|
3797
|
+
if (!canAfford(player, building.cost)) return null;
|
|
3798
|
+
const updatedPlayer = removeCoins(player, building.cost);
|
|
3799
|
+
const updatedBoard = removeBuildingFromBoard(board, slotIndex);
|
|
3800
|
+
return { updatedBoard, updatedPlayer };
|
|
3801
|
+
};
|
|
3802
|
+
|
|
3803
|
+
// src/core/domain/entities/queen.ts
|
|
3804
|
+
var determineQueenHolder = (players, currentHolderId) => {
|
|
3805
|
+
let maxTowers = 0;
|
|
3806
|
+
let leaderId = null;
|
|
3807
|
+
let isTied = false;
|
|
3808
|
+
for (const player of players) {
|
|
3809
|
+
const towers = getTowerCount(player);
|
|
3810
|
+
if (towers > maxTowers) {
|
|
3811
|
+
maxTowers = towers;
|
|
3812
|
+
leaderId = player.id;
|
|
3813
|
+
isTied = false;
|
|
3814
|
+
} else if (towers === maxTowers && towers > 0 && leaderId !== null) {
|
|
3815
|
+
isTied = true;
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
if (isTied || maxTowers === 0) {
|
|
3819
|
+
return currentHolderId;
|
|
3820
|
+
}
|
|
3821
|
+
return leaderId;
|
|
3822
|
+
};
|
|
3823
|
+
|
|
3824
|
+
// src/core/useCases/constructBuilding.ts
|
|
3825
|
+
var constructBuildingUseCase = (game, lordId, buildingId, position) => {
|
|
3826
|
+
const currentAction = game.nextAction;
|
|
3827
|
+
if (currentAction.nextLord !== lordId) {
|
|
3828
|
+
return err(ErrorCode.NOT_YOUR_TURN);
|
|
3829
|
+
}
|
|
3830
|
+
if (currentAction.nextAction !== playerActions.constructBuilding) {
|
|
3831
|
+
return err(ErrorCode.INVALID_OPTIONAL_ACTION);
|
|
3832
|
+
}
|
|
3833
|
+
if (!game.queendomino) {
|
|
3834
|
+
return err(ErrorCode.INVALID_OPTIONAL_ACTION);
|
|
3835
|
+
}
|
|
3836
|
+
const currentLord = game.lords.find((l) => l.id === lordId);
|
|
3837
|
+
if (!currentLord) return err(ErrorCode.LORD_NOT_FOUND);
|
|
3838
|
+
const currentPlayer = game.players.find(
|
|
3839
|
+
(p) => p.id === currentLord.playerId
|
|
3840
|
+
);
|
|
3841
|
+
if (!currentPlayer) return err(ErrorCode.PLAYER_NOT_FOUND);
|
|
3842
|
+
if (!isValidConstructionPosition(
|
|
3843
|
+
currentPlayer.kingdom,
|
|
3844
|
+
currentPlayer.buildings ?? [],
|
|
3845
|
+
position
|
|
3846
|
+
)) {
|
|
3847
|
+
return err(ErrorCode.NO_CONSTRUCTION_SQUARE);
|
|
3848
|
+
}
|
|
3849
|
+
const purchaseResult = purchaseBuilding(
|
|
3850
|
+
game.queendomino.buildersBoard,
|
|
3851
|
+
currentPlayer,
|
|
3852
|
+
buildingId
|
|
3853
|
+
);
|
|
3854
|
+
if (!purchaseResult) {
|
|
3855
|
+
return err(ErrorCode.CANNOT_AFFORD_BUILDING);
|
|
3856
|
+
}
|
|
3857
|
+
const { updatedBoard, updatedPlayer, building } = purchaseResult;
|
|
3858
|
+
const updatedKingdom = placeBuildingOnKingdom(
|
|
3859
|
+
updatedPlayer.kingdom,
|
|
3860
|
+
building,
|
|
3861
|
+
position
|
|
3862
|
+
);
|
|
3863
|
+
const placedBuilding = { building, position };
|
|
3864
|
+
const finalPlayer = {
|
|
3865
|
+
...updatedPlayer,
|
|
3866
|
+
kingdom: updatedKingdom,
|
|
3867
|
+
buildings: [...updatedPlayer.buildings ?? [], placedBuilding]
|
|
3868
|
+
};
|
|
3869
|
+
const updatedPlayers = game.players.map(
|
|
3870
|
+
(p) => p.id === currentPlayer.id ? finalPlayer : p
|
|
3871
|
+
);
|
|
3872
|
+
const newQueenHolder = determineQueenHolder(
|
|
3873
|
+
updatedPlayers,
|
|
3874
|
+
game.queendomino.queenHolderId
|
|
3875
|
+
);
|
|
3876
|
+
const nextAction = nextQueenDominoAction(playerActions.constructBuilding);
|
|
3877
|
+
return ok({
|
|
3878
|
+
...game,
|
|
3879
|
+
players: updatedPlayers,
|
|
3880
|
+
queendomino: {
|
|
3881
|
+
...game.queendomino,
|
|
3882
|
+
buildersBoard: updatedBoard,
|
|
3883
|
+
queenHolderId: newQueenHolder
|
|
3884
|
+
},
|
|
3885
|
+
nextAction: {
|
|
3886
|
+
type: "action",
|
|
3887
|
+
nextLord: lordId,
|
|
3888
|
+
nextAction
|
|
3889
|
+
}
|
|
3890
|
+
});
|
|
3891
|
+
};
|
|
3892
|
+
|
|
3893
|
+
// src/core/useCases/useDragon.ts
|
|
3894
|
+
var useDragonUseCase = (game, lordId, buildingId) => {
|
|
3895
|
+
const currentAction = game.nextAction;
|
|
3896
|
+
if (currentAction.nextLord !== lordId) {
|
|
3897
|
+
return err(ErrorCode.NOT_YOUR_TURN);
|
|
3898
|
+
}
|
|
3899
|
+
if (currentAction.nextAction !== playerActions.useDragon) {
|
|
3900
|
+
return err(ErrorCode.INVALID_OPTIONAL_ACTION);
|
|
3901
|
+
}
|
|
3902
|
+
if (!game.queendomino) {
|
|
3903
|
+
return err(ErrorCode.INVALID_OPTIONAL_ACTION);
|
|
3904
|
+
}
|
|
3905
|
+
if (!game.queendomino.dragonAvailable || game.queendomino.dragonUsedThisRound) {
|
|
3906
|
+
return err(ErrorCode.DRAGON_UNAVAILABLE);
|
|
3907
|
+
}
|
|
3908
|
+
const currentLord = game.lords.find((l) => l.id === lordId);
|
|
3909
|
+
if (!currentLord) return err(ErrorCode.LORD_NOT_FOUND);
|
|
3910
|
+
const currentPlayer = game.players.find(
|
|
3911
|
+
(p) => p.id === currentLord.playerId
|
|
3912
|
+
);
|
|
3913
|
+
if (!currentPlayer) return err(ErrorCode.PLAYER_NOT_FOUND);
|
|
3914
|
+
if (game.queendomino.queenHolderId === currentPlayer.id) {
|
|
3915
|
+
return err(ErrorCode.QUEEN_HOLDER_CANNOT_USE_DRAGON);
|
|
3916
|
+
}
|
|
3917
|
+
const result = destroyWithDragon(
|
|
3918
|
+
game.queendomino.buildersBoard,
|
|
3919
|
+
buildingId,
|
|
3920
|
+
currentPlayer
|
|
3921
|
+
);
|
|
3922
|
+
if (!result) {
|
|
3923
|
+
return err(ErrorCode.BUILDING_NOT_FOUND);
|
|
3924
|
+
}
|
|
3925
|
+
const { updatedBoard, updatedPlayer } = result;
|
|
3926
|
+
const updatedPlayers = game.players.map(
|
|
3927
|
+
(p) => p.id === currentPlayer.id ? updatedPlayer : p
|
|
3928
|
+
);
|
|
3929
|
+
const maxTurns = game.rules.basic.maxTurns;
|
|
3930
|
+
const isLastTurn = game.turn === maxTurns;
|
|
3931
|
+
if (isLastTurn && allLordsHavePlayed(game.lords)) {
|
|
3932
|
+
return ok({
|
|
3933
|
+
...game,
|
|
3934
|
+
players: updatedPlayers,
|
|
3935
|
+
queendomino: {
|
|
3936
|
+
...game.queendomino,
|
|
3937
|
+
buildersBoard: updatedBoard,
|
|
3938
|
+
dragonUsedThisRound: true
|
|
3939
|
+
},
|
|
3940
|
+
nextAction: { type: "step", step: gameSteps.result }
|
|
3941
|
+
});
|
|
3942
|
+
}
|
|
3943
|
+
return ok({
|
|
3944
|
+
...game,
|
|
3945
|
+
players: updatedPlayers,
|
|
3946
|
+
queendomino: {
|
|
3947
|
+
...game.queendomino,
|
|
3948
|
+
buildersBoard: updatedBoard,
|
|
3949
|
+
dragonUsedThisRound: true
|
|
3950
|
+
},
|
|
3951
|
+
nextAction: nextLordWithAction(game.lords)
|
|
3952
|
+
});
|
|
3953
|
+
};
|
|
3954
|
+
|
|
3955
|
+
// src/core/useCases/skipOptionalAction.ts
|
|
3956
|
+
var skipOptionalActionUseCase = (game, lordId) => {
|
|
3957
|
+
const currentAction = game.nextAction;
|
|
3958
|
+
if (currentAction.nextLord !== lordId) {
|
|
3959
|
+
return err(ErrorCode.NOT_YOUR_TURN);
|
|
3960
|
+
}
|
|
3961
|
+
const action = currentAction.nextAction;
|
|
3962
|
+
const isOptional = action === playerActions.placeKnight || action === playerActions.constructBuilding || action === playerActions.useDragon;
|
|
3963
|
+
if (!isOptional) {
|
|
3964
|
+
return err(ErrorCode.INVALID_OPTIONAL_ACTION);
|
|
3965
|
+
}
|
|
3966
|
+
const nextAction = nextQueenDominoAction(action);
|
|
3967
|
+
if (nextAction === playerActions.pickDomino) {
|
|
3968
|
+
const maxTurns = game.rules.basic.maxTurns;
|
|
3969
|
+
const isLastTurn = game.turn === maxTurns;
|
|
3970
|
+
if (isLastTurn && allLordsHavePlayed(game.lords)) {
|
|
3971
|
+
return ok({
|
|
3972
|
+
...game,
|
|
3973
|
+
nextAction: { type: "step", step: gameSteps.result }
|
|
3974
|
+
});
|
|
3975
|
+
}
|
|
3976
|
+
return ok({
|
|
3977
|
+
...game,
|
|
3978
|
+
nextAction: nextLordWithAction(game.lords)
|
|
3979
|
+
});
|
|
3980
|
+
}
|
|
3981
|
+
return ok({
|
|
3982
|
+
...game,
|
|
3983
|
+
nextAction: {
|
|
3984
|
+
type: "action",
|
|
3985
|
+
nextLord: lordId,
|
|
3986
|
+
nextAction
|
|
3987
|
+
}
|
|
3988
|
+
});
|
|
3989
|
+
};
|
|
3990
|
+
|
|
3991
|
+
// src/core/useCases/startGame.ts
|
|
3992
|
+
var startGameUseCase = (deps) => (game) => {
|
|
3993
|
+
const { uuidMethod: uuidMethod3, shuffleMethod: shuffleMethod2 } = deps;
|
|
3994
|
+
const {
|
|
3995
|
+
rules: {
|
|
3996
|
+
basic: { lords: maxLords, dominoesPerTurn }
|
|
3997
|
+
},
|
|
3998
|
+
players,
|
|
3999
|
+
dominoes
|
|
4000
|
+
} = game;
|
|
4001
|
+
const numberOfPlayers = players.length;
|
|
4002
|
+
const newLords = [];
|
|
4003
|
+
for (let i = 0; i < numberOfPlayers; i++) {
|
|
4004
|
+
for (let j = 0; j < maxLords; j++) {
|
|
4005
|
+
const lord = createLord(uuidMethod3(), players[i].id);
|
|
4006
|
+
newLords.push(lord);
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
const shuffle = game.seed ? createSeededShuffle(game.seed, "lords") : shuffleMethod2;
|
|
4010
|
+
const shuffledLords = shuffle(newLords);
|
|
4011
|
+
const dominoesCopy = [...dominoes];
|
|
4012
|
+
const dominoesDrawn = dominoesCopy.splice(0, dominoesPerTurn);
|
|
4013
|
+
dominoesDrawn.sort((a, b) => a.number - b.number);
|
|
4014
|
+
const nextAction = {
|
|
4015
|
+
type: "action",
|
|
4016
|
+
nextLord: shuffledLords[0].id,
|
|
4017
|
+
nextAction: playerActions.pickDomino
|
|
4018
|
+
};
|
|
4019
|
+
let queendomino = game.queendomino;
|
|
4020
|
+
if (queendomino && game.mode.name === "QueenDomino") {
|
|
4021
|
+
const buildingShuffle = game.seed ? createSeededShuffle(game.seed, "buildings") : shuffleMethod2;
|
|
4022
|
+
const board = createBuildersBoard(
|
|
4023
|
+
queendomino.buildersBoard.drawPile,
|
|
4024
|
+
buildingShuffle
|
|
4025
|
+
);
|
|
4026
|
+
queendomino = { ...queendomino, buildersBoard: board };
|
|
4027
|
+
}
|
|
4028
|
+
const newState = {
|
|
4029
|
+
...game,
|
|
4030
|
+
lords: shuffledLords,
|
|
4031
|
+
nextAction,
|
|
4032
|
+
dominoes: dominoesCopy,
|
|
4033
|
+
currentDominoes: dominoesDrawn.map((domino, index) => {
|
|
4034
|
+
return {
|
|
4035
|
+
domino,
|
|
4036
|
+
picked: false,
|
|
4037
|
+
lordId: null,
|
|
4038
|
+
position: index + 1
|
|
4039
|
+
};
|
|
4040
|
+
}),
|
|
4041
|
+
...queendomino !== void 0 && { queendomino }
|
|
4042
|
+
};
|
|
4043
|
+
return ok(newState);
|
|
4044
|
+
};
|
|
4045
|
+
|
|
4046
|
+
// src/config.ts
|
|
4047
|
+
var configureEngine = (config) => {
|
|
4048
|
+
const modeRepository = jsonModes_default();
|
|
4049
|
+
const dominoesRepository = jsonDominoes_default();
|
|
4050
|
+
const buildingsRepository = jsonBuildings_default();
|
|
4051
|
+
const ruleRepository = jsonRules_default();
|
|
4052
|
+
const uuid = config.uuidMethod || uuidMethod;
|
|
4053
|
+
const shuffle = config.shuffleMethod || shuffleMethod;
|
|
4054
|
+
const translator = config.translator || defaultTranslator;
|
|
4055
|
+
const logger = config.logger ? config.logger : config.logging ? consoleLogger(true) : silentLogger;
|
|
4056
|
+
return {
|
|
4057
|
+
createGameHandler: createGameHandler(
|
|
4058
|
+
logger,
|
|
4059
|
+
translator,
|
|
4060
|
+
createGameUseCase({
|
|
4061
|
+
modeRepository,
|
|
4062
|
+
dominoesRepository,
|
|
4063
|
+
uuidMethod: uuid,
|
|
4064
|
+
buildingsRepository
|
|
4065
|
+
})
|
|
4066
|
+
),
|
|
4067
|
+
getModesHandler: getModesHandler(
|
|
4068
|
+
logger,
|
|
4069
|
+
translator,
|
|
4070
|
+
getModesUseCase({ modeRepository })
|
|
4071
|
+
),
|
|
4072
|
+
getExtraRulesHandler: getExtraRulesHandler(
|
|
4073
|
+
logger,
|
|
4074
|
+
translator,
|
|
4075
|
+
getExtraRulesUseCase({ ruleRepository })
|
|
4076
|
+
),
|
|
4077
|
+
addPlayersHandler: addPlayersHandler(
|
|
4078
|
+
logger,
|
|
4079
|
+
translator,
|
|
4080
|
+
addPlayersUseCase({
|
|
4081
|
+
uuidMethod: uuid,
|
|
4082
|
+
shuffleMethod: shuffle,
|
|
4083
|
+
ruleRepository
|
|
4084
|
+
})
|
|
4085
|
+
),
|
|
4086
|
+
addExtraRulesHandler: addExtraRulesHandler(
|
|
4087
|
+
logger,
|
|
4088
|
+
translator,
|
|
4089
|
+
addExtraRulesUseCase({
|
|
4090
|
+
ruleRepository,
|
|
4091
|
+
dominoesRepository,
|
|
4092
|
+
shuffleMethod: shuffle
|
|
4093
|
+
})
|
|
4094
|
+
),
|
|
4095
|
+
startGameHandler: startGameHandler(
|
|
4096
|
+
logger,
|
|
4097
|
+
translator,
|
|
4098
|
+
startGameUseCase({ uuidMethod: uuid, shuffleMethod: shuffle })
|
|
4099
|
+
),
|
|
4100
|
+
chooseDominoHandler: chooseDominoHandler(
|
|
4101
|
+
logger,
|
|
4102
|
+
translator,
|
|
4103
|
+
chooseDominoUseCase
|
|
4104
|
+
),
|
|
4105
|
+
placeDominoHandler: placeDominoHandler(
|
|
4106
|
+
logger,
|
|
4107
|
+
translator,
|
|
4108
|
+
placeDominoUseCase
|
|
4109
|
+
),
|
|
4110
|
+
discardDominoHandler: discardDominoHandler(
|
|
4111
|
+
logger,
|
|
4112
|
+
translator,
|
|
4113
|
+
discardDominoUseCase
|
|
4114
|
+
),
|
|
4115
|
+
getResultHandler: getResultHandler(logger, translator, {
|
|
4116
|
+
getResultUseCase,
|
|
4117
|
+
calculateScoreUseCase
|
|
4118
|
+
}),
|
|
4119
|
+
calculateScoreHandler: calculateScoreHandler(
|
|
4120
|
+
logger,
|
|
4121
|
+
translator,
|
|
4122
|
+
calculateScoreUseCase
|
|
4123
|
+
),
|
|
4124
|
+
getValidPlacementsHandler: getValidPlacementsHandler(
|
|
4125
|
+
logger,
|
|
4126
|
+
translator,
|
|
4127
|
+
getValidPlacementsUseCase
|
|
4128
|
+
),
|
|
4129
|
+
canPlaceDominoHandler: canPlaceDominoHandler(
|
|
4130
|
+
logger,
|
|
4131
|
+
translator,
|
|
4132
|
+
canPlaceDominoUseCase
|
|
4133
|
+
),
|
|
4134
|
+
serializeGameHandler: serializeGameHandler(logger, translator),
|
|
4135
|
+
deserializeGameHandler: deserializeGameHandler(logger, translator),
|
|
4136
|
+
getDynastyResultHandler: getDynastyResultHandler(
|
|
4137
|
+
logger,
|
|
4138
|
+
translator,
|
|
4139
|
+
getDynastyResultUseCase
|
|
4140
|
+
),
|
|
4141
|
+
placeKnightHandler: placeKnightHandler(
|
|
4142
|
+
logger,
|
|
4143
|
+
translator,
|
|
4144
|
+
placeKnightUseCase
|
|
4145
|
+
),
|
|
4146
|
+
constructBuildingHandler: constructBuildingHandler(
|
|
4147
|
+
logger,
|
|
4148
|
+
translator,
|
|
4149
|
+
constructBuildingUseCase
|
|
4150
|
+
),
|
|
4151
|
+
useDragonHandler: useDragonHandler(
|
|
4152
|
+
logger,
|
|
4153
|
+
translator,
|
|
4154
|
+
useDragonUseCase
|
|
4155
|
+
),
|
|
4156
|
+
skipOptionalActionHandler: skipOptionalActionHandler(
|
|
4157
|
+
logger,
|
|
4158
|
+
translator,
|
|
4159
|
+
skipOptionalActionUseCase
|
|
4160
|
+
)
|
|
4161
|
+
};
|
|
4162
|
+
};
|
|
4163
|
+
|
|
4164
|
+
// src/core/domain/entities/validateGameState.ts
|
|
4165
|
+
var validGrounds = /* @__PURE__ */ new Set([...GROUND, "empty"]);
|
|
4166
|
+
var validPlayerActions = new Set(Object.values(playerActions));
|
|
4167
|
+
var validGameSteps = new Set(Object.values(gameSteps));
|
|
4168
|
+
|
|
4169
|
+
// src/index.ts
|
|
4170
|
+
var createGameEngine = (config) => {
|
|
4171
|
+
const {
|
|
4172
|
+
createGameHandler: createGameHandler2,
|
|
4173
|
+
getModesHandler: getModesHandler2,
|
|
4174
|
+
getExtraRulesHandler: getExtraRulesHandler2,
|
|
4175
|
+
addPlayersHandler: addPlayersHandler2,
|
|
4176
|
+
addExtraRulesHandler: addExtraRulesHandler2,
|
|
4177
|
+
startGameHandler: startGameHandler2,
|
|
4178
|
+
chooseDominoHandler: chooseDominoHandler2,
|
|
4179
|
+
placeDominoHandler: placeDominoHandler2,
|
|
4180
|
+
discardDominoHandler: discardDominoHandler2,
|
|
4181
|
+
getResultHandler: getResultHandler2,
|
|
4182
|
+
calculateScoreHandler: calculateScoreHandler2,
|
|
4183
|
+
getValidPlacementsHandler: getValidPlacementsHandler2,
|
|
4184
|
+
canPlaceDominoHandler: canPlaceDominoHandler2,
|
|
4185
|
+
serializeGameHandler: serializeGameHandler2,
|
|
4186
|
+
deserializeGameHandler: deserializeGameHandler2,
|
|
4187
|
+
getDynastyResultHandler: getDynastyResultHandler2,
|
|
4188
|
+
placeKnightHandler: placeKnightHandler2,
|
|
4189
|
+
constructBuildingHandler: constructBuildingHandler2,
|
|
4190
|
+
useDragonHandler: useDragonHandler2,
|
|
4191
|
+
skipOptionalActionHandler: skipOptionalActionHandler2
|
|
4192
|
+
} = configureEngine(config);
|
|
4193
|
+
let engine2 = {
|
|
4194
|
+
getModes: (command) => getModesHandler2(command),
|
|
4195
|
+
getExtraRules: (command) => getExtraRulesHandler2(command),
|
|
4196
|
+
createGame: (command) => createGameHandler2(command),
|
|
4197
|
+
addPlayers: (command) => addPlayersHandler2(command),
|
|
4198
|
+
addExtraRules: (command) => addExtraRulesHandler2(command),
|
|
4199
|
+
startGame: (command) => startGameHandler2(command),
|
|
4200
|
+
chooseDomino: (command) => chooseDominoHandler2(command),
|
|
4201
|
+
placeDomino: (command) => placeDominoHandler2(command),
|
|
4202
|
+
discardDomino: (command) => discardDominoHandler2(command),
|
|
4203
|
+
getResults: (command) => getResultHandler2(command),
|
|
4204
|
+
calculateScore: (command) => calculateScoreHandler2(command),
|
|
4205
|
+
getValidPlacements: (command) => getValidPlacementsHandler2(command),
|
|
4206
|
+
canPlaceDomino: (command) => canPlaceDominoHandler2(command),
|
|
4207
|
+
serialize: (command) => serializeGameHandler2(command),
|
|
4208
|
+
deserialize: (command) => deserializeGameHandler2(command),
|
|
4209
|
+
getDynastyResults: (command) => getDynastyResultHandler2(command),
|
|
4210
|
+
placeKnight: (command) => placeKnightHandler2(command),
|
|
4211
|
+
constructBuilding: (command) => constructBuildingHandler2(command),
|
|
4212
|
+
useDragon: (command) => useDragonHandler2(command),
|
|
4213
|
+
skipOptionalAction: (command) => skipOptionalActionHandler2(command)
|
|
4214
|
+
};
|
|
4215
|
+
if (config.events) {
|
|
4216
|
+
engine2 = wrapWithEvents(engine2, config.events);
|
|
4217
|
+
}
|
|
4218
|
+
if (config.debug) {
|
|
4219
|
+
const debugOptions = typeof config.debug === "object" ? config.debug : void 0;
|
|
4220
|
+
engine2 = wrapWithDebug(engine2, debugOptions);
|
|
4221
|
+
}
|
|
4222
|
+
return engine2;
|
|
4223
|
+
};
|
|
4224
|
+
|
|
4225
|
+
// src/wasm/dispatch.ts
|
|
4226
|
+
var ENGINE_METHODS = /* @__PURE__ */ new Set([
|
|
4227
|
+
"getModes",
|
|
4228
|
+
"getExtraRules",
|
|
4229
|
+
"createGame",
|
|
4230
|
+
"addPlayers",
|
|
4231
|
+
"addExtraRules",
|
|
4232
|
+
"startGame",
|
|
4233
|
+
"chooseDomino",
|
|
4234
|
+
"placeDomino",
|
|
4235
|
+
"discardDomino",
|
|
4236
|
+
"getResults",
|
|
4237
|
+
"calculateScore",
|
|
4238
|
+
"getValidPlacements",
|
|
4239
|
+
"canPlaceDomino",
|
|
4240
|
+
"serialize",
|
|
4241
|
+
"deserialize",
|
|
4242
|
+
"getDynastyResults"
|
|
4243
|
+
]);
|
|
4244
|
+
var dispatch = (engine2, request2) => {
|
|
4245
|
+
const { method, params } = request2;
|
|
4246
|
+
if (!ENGINE_METHODS.has(method)) {
|
|
4247
|
+
return {
|
|
4248
|
+
error: {
|
|
4249
|
+
code: "UNKNOWN_METHOD",
|
|
4250
|
+
message: `Unknown method: ${method}`
|
|
4251
|
+
}
|
|
4252
|
+
};
|
|
4253
|
+
}
|
|
4254
|
+
try {
|
|
4255
|
+
const fn = engine2[method];
|
|
4256
|
+
const result = fn(params);
|
|
4257
|
+
return { result };
|
|
4258
|
+
} catch (err3) {
|
|
4259
|
+
const code = isDomainException(err3) ? err3.code : "INTERNAL_ERROR";
|
|
4260
|
+
const message = err3 instanceof Error ? err3.message : String(err3);
|
|
4261
|
+
return { error: { code, message } };
|
|
4262
|
+
}
|
|
4263
|
+
};
|
|
4264
|
+
var isDomainException = (err3) => {
|
|
4265
|
+
return err3 instanceof Error && "code" in err3 && typeof err3.code === "string";
|
|
4266
|
+
};
|
|
4267
|
+
|
|
4268
|
+
// src/wasm/bridge.ts
|
|
4269
|
+
var uuidFallback = () => "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
4270
|
+
const r = Math.random() * 16 | 0;
|
|
4271
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
4272
|
+
});
|
|
4273
|
+
var uuidMethod2 = typeof globalThis.crypto?.randomUUID === "function" ? () => globalThis.crypto.randomUUID() : uuidFallback;
|
|
4274
|
+
var engine = createGameEngine({ uuidMethod: uuidMethod2 });
|
|
4275
|
+
var chunks = [];
|
|
4276
|
+
var buf = new Uint8Array(4096);
|
|
4277
|
+
for (; ; ) {
|
|
4278
|
+
const bytesRead = Javy.IO.readSync(0, buf);
|
|
4279
|
+
if (bytesRead <= 0) break;
|
|
4280
|
+
chunks.push(buf.slice(0, bytesRead));
|
|
4281
|
+
}
|
|
4282
|
+
var decoder = new TextDecoder();
|
|
4283
|
+
var input = chunks.map((c) => decoder.decode(c)).join("");
|
|
4284
|
+
var request = JSON.parse(input);
|
|
4285
|
+
var response = dispatch(engine, request);
|
|
4286
|
+
var encoder = new TextEncoder();
|
|
4287
|
+
Javy.IO.writeSync(1, encoder.encode(JSON.stringify(response)));
|