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