@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.
@@ -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)));