@pompidup/kingdomino-engine 3.2.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,2928 @@
1
+ // src/core/domain/types/game.ts
2
+ var gameSteps = {
3
+ /** Waiting for players to be added */
4
+ addPlayers: "addPlayers",
5
+ /** Optional rules selection phase */
6
+ options: "options",
7
+ /** Ready to start the game */
8
+ start: "start",
9
+ /** Game has ended, results available */
10
+ result: "result"
11
+ };
12
+ var isGameWithNextAction = (game) => {
13
+ return game.nextAction.type === "action";
14
+ };
15
+ var isGameWithNextStep = (game) => {
16
+ return game.nextAction.type === "step";
17
+ };
18
+
19
+ // src/core/useCases/gameDebug.ts
20
+ var summarizeGameState = (game) => {
21
+ const nextAction = isGameWithNextAction(game) ? `action:${game.nextAction.nextAction}` : `step:${game.nextAction.step}`;
22
+ return {
23
+ gameId: game.id,
24
+ turn: game.turn,
25
+ nextAction,
26
+ playersCount: game.players.length,
27
+ dominoesRemaining: game.dominoes.length,
28
+ currentDominoesCount: game.currentDominoes.length,
29
+ lordsCount: game.lords.length
30
+ };
31
+ };
32
+ var defaultDebugLogger = {
33
+ log: (entry) => {
34
+ const phase = entry.phase.toUpperCase().padEnd(5);
35
+ const base = `[KDomino:Debug] ${entry.timestamp} ${phase} ${entry.method}`;
36
+ if (entry.phase === "error") {
37
+ console.error(`${base} | error: ${entry.error}`);
38
+ return;
39
+ }
40
+ if (entry.summary) {
41
+ const s = entry.summary;
42
+ console.log(
43
+ `${base} | turn=${s.turn} next=${s.nextAction} players=${s.playersCount} dominoes=${s.dominoesRemaining}`
44
+ );
45
+ } else {
46
+ console.log(base);
47
+ }
48
+ }
49
+ };
50
+ var isGameState = (value) => {
51
+ return typeof value === "object" && value !== null && "id" in value && "nextAction" in value && "players" in value && "dominoes" in value;
52
+ };
53
+ var hasGameInCommand = (cmd) => {
54
+ return typeof cmd === "object" && cmd !== null && "game" in cmd && isGameState(cmd.game);
55
+ };
56
+ var extractParams = (cmd) => {
57
+ if (typeof cmd !== "object" || cmd === null) return void 0;
58
+ const record = cmd;
59
+ const params = {};
60
+ if ("mode" in record) params.mode = record.mode;
61
+ if ("lordId" in record) params.lordId = record.lordId;
62
+ if ("dominoPick" in record) params.dominoPick = record.dominoPick;
63
+ if ("position" in record) params.position = record.position;
64
+ if ("rotation" in record) params.rotation = record.rotation;
65
+ if ("players" in record) params.players = record.players;
66
+ if ("extraRules" in record) params.extraRules = record.extraRules;
67
+ return Object.keys(params).length > 0 ? params : void 0;
68
+ };
69
+ var wrapMethod = (method, fn, logger, level) => {
70
+ return (cmd) => {
71
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
72
+ const beforeSummary = level !== "minimal" && hasGameInCommand(cmd) ? summarizeGameState(cmd.game) : null;
73
+ const beforeEntry = {
74
+ timestamp,
75
+ method,
76
+ phase: "before",
77
+ summary: beforeSummary,
78
+ params: level !== "minimal" ? extractParams(cmd) : void 0
79
+ };
80
+ logger.log(beforeEntry);
81
+ const start = performance.now();
82
+ let result;
83
+ try {
84
+ result = fn(cmd);
85
+ } catch (err3) {
86
+ const durationMs2 = Math.round((performance.now() - start) * 100) / 100;
87
+ const errorEntry = {
88
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
89
+ method,
90
+ phase: "error",
91
+ summary: beforeSummary,
92
+ error: err3 instanceof Error ? err3.message : String(err3),
93
+ durationMs: durationMs2
94
+ };
95
+ logger.log(errorEntry);
96
+ throw err3;
97
+ }
98
+ const durationMs = Math.round((performance.now() - start) * 100) / 100;
99
+ const afterSummary = level !== "minimal" && isGameState(result) ? summarizeGameState(result) : null;
100
+ const afterEntry = {
101
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
102
+ method,
103
+ phase: "after",
104
+ summary: afterSummary,
105
+ durationMs,
106
+ fullState: level === "verbose" && isGameState(result) ? result : void 0
107
+ };
108
+ logger.log(afterEntry);
109
+ return result;
110
+ };
111
+ };
112
+ var wrapWithDebug = (engine2, options) => {
113
+ const logger = options?.logger ?? defaultDebugLogger;
114
+ const level = options?.level ?? "standard";
115
+ return {
116
+ getModes: wrapMethod("getModes", engine2.getModes, logger, level),
117
+ getExtraRules: wrapMethod(
118
+ "getExtraRules",
119
+ engine2.getExtraRules,
120
+ logger,
121
+ level
122
+ ),
123
+ createGame: wrapMethod("createGame", engine2.createGame, logger, level),
124
+ addPlayers: wrapMethod("addPlayers", engine2.addPlayers, logger, level),
125
+ addExtraRules: wrapMethod(
126
+ "addExtraRules",
127
+ engine2.addExtraRules,
128
+ logger,
129
+ level
130
+ ),
131
+ startGame: wrapMethod("startGame", engine2.startGame, logger, level),
132
+ chooseDomino: wrapMethod(
133
+ "chooseDomino",
134
+ engine2.chooseDomino,
135
+ logger,
136
+ level
137
+ ),
138
+ placeDomino: wrapMethod("placeDomino", engine2.placeDomino, logger, level),
139
+ discardDomino: wrapMethod(
140
+ "discardDomino",
141
+ engine2.discardDomino,
142
+ logger,
143
+ level
144
+ ),
145
+ getResults: wrapMethod("getResults", engine2.getResults, logger, level),
146
+ calculateScore: wrapMethod(
147
+ "calculateScore",
148
+ engine2.calculateScore,
149
+ logger,
150
+ level
151
+ ),
152
+ getValidPlacements: wrapMethod(
153
+ "getValidPlacements",
154
+ engine2.getValidPlacements,
155
+ logger,
156
+ level
157
+ ),
158
+ canPlaceDomino: wrapMethod(
159
+ "canPlaceDomino",
160
+ engine2.canPlaceDomino,
161
+ logger,
162
+ level
163
+ ),
164
+ serialize: wrapMethod("serialize", engine2.serialize, logger, level),
165
+ deserialize: wrapMethod("deserialize", engine2.deserialize, logger, level),
166
+ getDynastyResults: wrapMethod(
167
+ "getDynastyResults",
168
+ engine2.getDynastyResults,
169
+ logger,
170
+ level
171
+ )
172
+ };
173
+ };
174
+
175
+ // src/core/useCases/gameEvents.ts
176
+ var isGameEnded = (game) => isGameWithNextStep(game) && game.nextAction.step === "result";
177
+ var wrapWithEvents = (engine2, callbacks) => ({
178
+ // Read-only methods — pass through
179
+ getModes: (cmd) => engine2.getModes(cmd),
180
+ getExtraRules: (cmd) => engine2.getExtraRules(cmd),
181
+ calculateScore: (cmd) => engine2.calculateScore(cmd),
182
+ getValidPlacements: (cmd) => engine2.getValidPlacements(cmd),
183
+ canPlaceDomino: (cmd) => engine2.canPlaceDomino(cmd),
184
+ serialize: (cmd) => engine2.serialize(cmd),
185
+ deserialize: (cmd) => engine2.deserialize(cmd),
186
+ getResults: (cmd) => engine2.getResults(cmd),
187
+ getDynastyResults: (cmd) => engine2.getDynastyResults(cmd),
188
+ // State-changing methods — emit events after execution
189
+ createGame: (cmd) => {
190
+ const game = engine2.createGame(cmd);
191
+ callbacks.onGameCreated?.({ game });
192
+ return game;
193
+ },
194
+ addPlayers: (cmd) => {
195
+ const game = engine2.addPlayers(cmd);
196
+ callbacks.onPlayersAdded?.({ game });
197
+ return game;
198
+ },
199
+ addExtraRules: (cmd) => engine2.addExtraRules(cmd),
200
+ startGame: (cmd) => {
201
+ const game = engine2.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 = engine2.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 = engine2.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 = engine2.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 DomainException = class extends Error {
992
+ code;
993
+ context;
994
+ constructor(error) {
995
+ super(error.message);
996
+ this.name = "DomainException";
997
+ this.code = error.code;
998
+ this.context = error.context;
999
+ }
1000
+ };
1001
+ var InvalidStepError = class extends DomainException {
1002
+ constructor(message, context) {
1003
+ super({
1004
+ code: ErrorCode.INVALID_STEP,
1005
+ message,
1006
+ context
1007
+ });
1008
+ this.name = "InvalidStepError";
1009
+ }
1010
+ };
1011
+ var StepExecutionError = class extends DomainException {
1012
+ constructor(message, code = ErrorCode.STEP_EXECUTION_FAILED, context) {
1013
+ super({
1014
+ code,
1015
+ message,
1016
+ context
1017
+ });
1018
+ this.name = "StepExecutionError";
1019
+ }
1020
+ };
1021
+ var ActionExecutionError = class extends DomainException {
1022
+ constructor(message, code = ErrorCode.ACTION_EXECUTION_FAILED, context) {
1023
+ super({
1024
+ code,
1025
+ message,
1026
+ context
1027
+ });
1028
+ this.name = "ActionExecutionError";
1029
+ }
1030
+ };
1031
+ var NotFoundError = class extends DomainException {
1032
+ constructor(message, code = ErrorCode.LORD_NOT_FOUND, context) {
1033
+ super({
1034
+ code,
1035
+ message,
1036
+ context
1037
+ });
1038
+ this.name = "NotFoundError";
1039
+ }
1040
+ };
1041
+
1042
+ // src/core/i18n/translations.ts
1043
+ var TranslationKey = {
1044
+ // Error messages
1045
+ ERROR_INVALID_STEP: "error.invalidStep",
1046
+ ERROR_STEP_EXECUTION_FAILED: "error.stepExecutionFailed",
1047
+ ERROR_ACTION_EXECUTION_FAILED: "error.actionExecutionFailed",
1048
+ ERROR_LORD_NOT_FOUND: "error.lordNotFound",
1049
+ ERROR_PLAYER_NOT_FOUND: "error.playerNotFound",
1050
+ ERROR_DOMINO_NOT_FOUND: "error.dominoNotFound",
1051
+ ERROR_MODE_NOT_FOUND: "error.modeNotFound",
1052
+ ERROR_INVALID_PLACEMENT: "error.invalidPlacement",
1053
+ ERROR_PLACEMENT_NOT_EMPTY: "error.placementNotEmpty",
1054
+ ERROR_PLACEMENT_NOT_ADJACENT: "error.placementNotAdjacent",
1055
+ ERROR_PLACEMENT_INVALID_TERRAIN: "error.placementInvalidTerrain",
1056
+ ERROR_PLACEMENT_OUT_OF_BOUNDS: "error.placementOutOfBounds",
1057
+ ERROR_PLACEMENT_EXCEEDS_KINGDOM_SIZE: "error.placementExceedsKingdomSize",
1058
+ ERROR_NOT_YOUR_TURN: "error.notYourTurn",
1059
+ ERROR_CANNOT_PICK: "error.cannotPick",
1060
+ ERROR_CANNOT_PLACE: "error.cannotPlace",
1061
+ ERROR_DOMINO_ALREADY_PICKED: "error.dominoAlreadyPicked",
1062
+ ERROR_INVALID_PLAYER_COUNT: "error.invalidPlayerCount",
1063
+ ERROR_INVALID_PLAYER_NAME: "error.invalidPlayerName",
1064
+ ERROR_UNDO_UNAVAILABLE: "error.undoUnavailable",
1065
+ ERROR_REDO_UNAVAILABLE: "error.redoUnavailable",
1066
+ // Game mode names
1067
+ MODE_CLASSIC: "mode.classic",
1068
+ // Extra rule names
1069
+ RULE_MIDDLE_KINGDOM: "rule.middleKingdom",
1070
+ RULE_HARMONY: "rule.harmony",
1071
+ // Ground types
1072
+ GROUND_CASTLE: "ground.castle",
1073
+ GROUND_WHEAT: "ground.wheat",
1074
+ GROUND_FOREST: "ground.forest",
1075
+ GROUND_SEA: "ground.sea",
1076
+ GROUND_PLAIN: "ground.plain",
1077
+ GROUND_SWAMP: "ground.swamp",
1078
+ GROUND_MINE: "ground.mine",
1079
+ // Game phases
1080
+ PHASE_ADD_PLAYERS: "phase.addPlayers",
1081
+ PHASE_OPTIONS: "phase.options",
1082
+ PHASE_START: "phase.start",
1083
+ PHASE_RESULT: "phase.result",
1084
+ // Actions
1085
+ ACTION_PICK_DOMINO: "action.pickDomino",
1086
+ ACTION_PLACE_DOMINO: "action.placeDomino",
1087
+ ACTION_PASS: "action.pass"
1088
+ };
1089
+ var defaultTranslations = {
1090
+ // Error messages
1091
+ [TranslationKey.ERROR_INVALID_STEP]: "Invalid game step",
1092
+ [TranslationKey.ERROR_STEP_EXECUTION_FAILED]: "Step execution failed",
1093
+ [TranslationKey.ERROR_ACTION_EXECUTION_FAILED]: "Action execution failed",
1094
+ [TranslationKey.ERROR_LORD_NOT_FOUND]: "Lord not found",
1095
+ [TranslationKey.ERROR_PLAYER_NOT_FOUND]: "Player not found",
1096
+ [TranslationKey.ERROR_DOMINO_NOT_FOUND]: "Domino not found",
1097
+ [TranslationKey.ERROR_MODE_NOT_FOUND]: "Game mode not found",
1098
+ [TranslationKey.ERROR_INVALID_PLACEMENT]: "Invalid placement",
1099
+ [TranslationKey.ERROR_PLACEMENT_NOT_EMPTY]: "Position is not empty",
1100
+ [TranslationKey.ERROR_PLACEMENT_NOT_ADJACENT]: "Not adjacent to existing tiles",
1101
+ [TranslationKey.ERROR_PLACEMENT_INVALID_TERRAIN]: "Terrain type does not match",
1102
+ [TranslationKey.ERROR_PLACEMENT_OUT_OF_BOUNDS]: "Placement is out of bounds",
1103
+ [TranslationKey.ERROR_PLACEMENT_EXCEEDS_KINGDOM_SIZE]: "Placement exceeds kingdom size",
1104
+ [TranslationKey.ERROR_NOT_YOUR_TURN]: "It is not your turn",
1105
+ [TranslationKey.ERROR_CANNOT_PICK]: "Cannot pick a domino now",
1106
+ [TranslationKey.ERROR_CANNOT_PLACE]: "Cannot place a domino now",
1107
+ [TranslationKey.ERROR_DOMINO_ALREADY_PICKED]: "Domino has already been picked",
1108
+ [TranslationKey.ERROR_INVALID_PLAYER_COUNT]: "Invalid number of players (1-4 required)",
1109
+ [TranslationKey.ERROR_INVALID_PLAYER_NAME]: "Player name must be at least 3 characters",
1110
+ [TranslationKey.ERROR_UNDO_UNAVAILABLE]: "No state to undo",
1111
+ [TranslationKey.ERROR_REDO_UNAVAILABLE]: "No state to redo",
1112
+ // Game mode names
1113
+ [TranslationKey.MODE_CLASSIC]: "Classic",
1114
+ // Extra rule names
1115
+ [TranslationKey.RULE_MIDDLE_KINGDOM]: "The Middle Kingdom",
1116
+ [TranslationKey.RULE_HARMONY]: "Harmony",
1117
+ // Ground types
1118
+ [TranslationKey.GROUND_CASTLE]: "Castle",
1119
+ [TranslationKey.GROUND_WHEAT]: "Wheat Field",
1120
+ [TranslationKey.GROUND_FOREST]: "Forest",
1121
+ [TranslationKey.GROUND_SEA]: "Sea",
1122
+ [TranslationKey.GROUND_PLAIN]: "Plain",
1123
+ [TranslationKey.GROUND_SWAMP]: "Swamp",
1124
+ [TranslationKey.GROUND_MINE]: "Mine",
1125
+ // Game phases
1126
+ [TranslationKey.PHASE_ADD_PLAYERS]: "Add Players",
1127
+ [TranslationKey.PHASE_OPTIONS]: "Game Options",
1128
+ [TranslationKey.PHASE_START]: "Start Game",
1129
+ [TranslationKey.PHASE_RESULT]: "Game Results",
1130
+ // Actions
1131
+ [TranslationKey.ACTION_PICK_DOMINO]: "Pick Domino",
1132
+ [TranslationKey.ACTION_PLACE_DOMINO]: "Place Domino",
1133
+ [TranslationKey.ACTION_PASS]: "Pass"
1134
+ };
1135
+ var errorCodeToTranslationKey = {
1136
+ [ErrorCode.INVALID_STEP]: TranslationKey.ERROR_INVALID_STEP,
1137
+ [ErrorCode.STEP_EXECUTION_FAILED]: TranslationKey.ERROR_STEP_EXECUTION_FAILED,
1138
+ [ErrorCode.ACTION_EXECUTION_FAILED]: TranslationKey.ERROR_ACTION_EXECUTION_FAILED,
1139
+ [ErrorCode.LORD_NOT_FOUND]: TranslationKey.ERROR_LORD_NOT_FOUND,
1140
+ [ErrorCode.PLAYER_NOT_FOUND]: TranslationKey.ERROR_PLAYER_NOT_FOUND,
1141
+ [ErrorCode.DOMINO_NOT_FOUND]: TranslationKey.ERROR_DOMINO_NOT_FOUND,
1142
+ [ErrorCode.MODE_NOT_FOUND]: TranslationKey.ERROR_MODE_NOT_FOUND,
1143
+ [ErrorCode.INVALID_PLAYER_COUNT]: TranslationKey.ERROR_INVALID_PLAYER_COUNT,
1144
+ [ErrorCode.INVALID_PLAYER_NAME]: TranslationKey.ERROR_INVALID_PLAYER_NAME,
1145
+ [ErrorCode.INVALID_PLACEMENT]: TranslationKey.ERROR_INVALID_PLACEMENT,
1146
+ [ErrorCode.PLACEMENT_NOT_EMPTY]: TranslationKey.ERROR_PLACEMENT_NOT_EMPTY,
1147
+ [ErrorCode.PLACEMENT_NOT_ADJACENT]: TranslationKey.ERROR_PLACEMENT_NOT_ADJACENT,
1148
+ [ErrorCode.PLACEMENT_INVALID_TERRAIN]: TranslationKey.ERROR_PLACEMENT_INVALID_TERRAIN,
1149
+ [ErrorCode.PLACEMENT_OUT_OF_BOUNDS]: TranslationKey.ERROR_PLACEMENT_OUT_OF_BOUNDS,
1150
+ [ErrorCode.PLACEMENT_EXCEEDS_KINGDOM_SIZE]: TranslationKey.ERROR_PLACEMENT_EXCEEDS_KINGDOM_SIZE,
1151
+ [ErrorCode.NOT_YOUR_TURN]: TranslationKey.ERROR_NOT_YOUR_TURN,
1152
+ [ErrorCode.CANNOT_PICK]: TranslationKey.ERROR_CANNOT_PICK,
1153
+ [ErrorCode.CANNOT_PLACE]: TranslationKey.ERROR_CANNOT_PLACE,
1154
+ [ErrorCode.DOMINO_ALREADY_PICKED]: TranslationKey.ERROR_DOMINO_ALREADY_PICKED,
1155
+ [ErrorCode.UNDO_UNAVAILABLE]: TranslationKey.ERROR_UNDO_UNAVAILABLE,
1156
+ [ErrorCode.REDO_UNAVAILABLE]: TranslationKey.ERROR_REDO_UNAVAILABLE
1157
+ };
1158
+ var translateErrorCode = (translator, code, params) => {
1159
+ const key = errorCodeToTranslationKey[code];
1160
+ return translator.t(key, params);
1161
+ };
1162
+ var createTranslator = (translations = {}) => {
1163
+ const mergedTranslations = { ...defaultTranslations, ...translations };
1164
+ return {
1165
+ t(key, params) {
1166
+ let text = mergedTranslations[key] || key;
1167
+ if (params) {
1168
+ for (const [paramKey, value] of Object.entries(params)) {
1169
+ text = text.replace(
1170
+ new RegExp(`\\{\\{${paramKey}\\}\\}`, "g"),
1171
+ String(value)
1172
+ );
1173
+ }
1174
+ }
1175
+ return text;
1176
+ }
1177
+ };
1178
+ };
1179
+ var defaultTranslator = createTranslator();
1180
+
1181
+ // src/utils/result.ts
1182
+ var ok = (value) => ({ tag: "Ok", value });
1183
+ var err = (error) => ({ tag: "Err", error });
1184
+ var isOk = (result) => result.tag === "Ok";
1185
+ var isErr = (result) => result.tag === "Err";
1186
+ var unwrap = (result) => {
1187
+ if (result.tag === "Err") {
1188
+ throw new Error(result.error);
1189
+ }
1190
+ return result.value;
1191
+ };
1192
+
1193
+ // src/application/handlers/addExtraRulesHandler.ts
1194
+ var addExtraRulesHandler = (logger, translator, useCase) => (command) => {
1195
+ const { game, extraRules } = command;
1196
+ logger.info(`Adding extra rules to game: ${game.id}`);
1197
+ logger.info(`Extra rules: ${extraRules}`);
1198
+ if (!isGameWithNextStep(game)) {
1199
+ logger.error(
1200
+ `Invalid game, required nextAction type: 'step' but got: ${game.nextAction.type}`
1201
+ );
1202
+ throw new InvalidStepError(
1203
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1204
+ { expected: "step", actual: game.nextAction.type, gameId: game.id }
1205
+ );
1206
+ }
1207
+ if (game.nextAction.step !== "options") {
1208
+ logger.error(
1209
+ `Required game with options step but got: ${game.nextAction.step}`
1210
+ );
1211
+ throw new InvalidStepError(
1212
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1213
+ { expected: "options", actual: game.nextAction.step, gameId: game.id }
1214
+ );
1215
+ }
1216
+ const result = useCase(game, extraRules);
1217
+ if (isErr(result)) {
1218
+ logger.error(`Error adding extra rules: ${result.error}`);
1219
+ const code = result.error;
1220
+ throw new StepExecutionError(translateErrorCode(translator, code), code, {
1221
+ gameId: game.id,
1222
+ extraRules: command.extraRules
1223
+ });
1224
+ }
1225
+ logger.info(`Extra rules added to game: ${game.id}`);
1226
+ return result.value;
1227
+ };
1228
+
1229
+ // src/application/handlers/addPlayersHandler.ts
1230
+ var addPlayersHandler = (logger, translator, useCase) => (command) => {
1231
+ const { game, players } = command;
1232
+ logger.info(`Adding players to game: ${game.id}`);
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 !== "addPlayers") {
1243
+ logger.error(
1244
+ `Required game with addPlayers step but got: ${game.nextAction.step}`
1245
+ );
1246
+ throw new InvalidStepError(
1247
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1248
+ {
1249
+ expected: "addPlayers",
1250
+ actual: game.nextAction.step,
1251
+ gameId: game.id
1252
+ }
1253
+ );
1254
+ }
1255
+ const result = useCase(game, players);
1256
+ if (isErr(result)) {
1257
+ logger.error(`Error adding players: ${result.error}`);
1258
+ const code = result.error;
1259
+ throw new StepExecutionError(translateErrorCode(translator, code), code, {
1260
+ gameId: game.id,
1261
+ players: command.players
1262
+ });
1263
+ }
1264
+ logger.info(`Players added to game: ${game.id}`);
1265
+ return result.value;
1266
+ };
1267
+
1268
+ // src/application/handlers/calculateScoreHandler.ts
1269
+ var calculateScoreHandler = (logger, translator, useCase) => (command) => {
1270
+ const { kingdom } = command;
1271
+ logger.info(`Calculating score for kingdom`);
1272
+ const result = useCase(kingdom);
1273
+ if (isErr(result)) {
1274
+ logger.error(`Error calculating score: ${result.error}`);
1275
+ throw new Error(result.error);
1276
+ }
1277
+ logger.info(`Score calculated: ${result.value}`);
1278
+ return result.value;
1279
+ };
1280
+
1281
+ // src/application/handlers/canPlaceDominoHandler.ts
1282
+ var canPlaceDominoHandler = (logger, translator, useCase) => {
1283
+ return (command) => {
1284
+ logger.info(`Checking if domino ${command.domino.number} can be placed`);
1285
+ const canPlace = useCase(
1286
+ command.kingdom,
1287
+ command.domino,
1288
+ command.maxKingdomSize
1289
+ );
1290
+ logger.info(`Can place domino: ${canPlace}`);
1291
+ return canPlace;
1292
+ };
1293
+ };
1294
+
1295
+ // src/application/handlers/chooseDominoHandler.ts
1296
+ var chooseDominoHandler = (logger, translator, useCase) => (command) => {
1297
+ const { game, lordId, dominoPick } = command;
1298
+ logger.info(
1299
+ `Choosing domino: ${dominoPick} for lord: ${lordId} in game: ${game.id}`
1300
+ );
1301
+ if (!isGameWithNextAction(game)) {
1302
+ logger.error(
1303
+ `Invalid game, required nextAction type: 'action' but got: ${game.nextAction.type}`
1304
+ );
1305
+ throw new InvalidStepError(
1306
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1307
+ { expected: "action", actual: game.nextAction.type, gameId: game.id }
1308
+ );
1309
+ }
1310
+ if (game.nextAction.nextAction !== "pickDomino") {
1311
+ logger.error(
1312
+ `Required game with pickDomino action but got: ${game.nextAction.nextAction}`
1313
+ );
1314
+ throw new InvalidStepError(
1315
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1316
+ {
1317
+ expected: "pickDomino",
1318
+ actual: game.nextAction.nextAction,
1319
+ gameId: game.id
1320
+ }
1321
+ );
1322
+ }
1323
+ const result = useCase(game, lordId, dominoPick);
1324
+ if (isErr(result)) {
1325
+ logger.error(`Error choosing domino: ${result.error}`);
1326
+ const code = result.error;
1327
+ throw new ActionExecutionError(
1328
+ translateErrorCode(translator, code),
1329
+ code,
1330
+ {
1331
+ gameId: game.id,
1332
+ lordId: command.lordId,
1333
+ dominoPick: command.dominoPick
1334
+ }
1335
+ );
1336
+ }
1337
+ logger.info(
1338
+ `Domino chosen: ${dominoPick} for lord: ${lordId} in game: ${game.id}`
1339
+ );
1340
+ return result.value;
1341
+ };
1342
+
1343
+ // src/application/handlers/createGameHandler.ts
1344
+ var createGameHandler = (logger, translator, useCase) => (command) => {
1345
+ logger.info(`Creating game with mode: ${command.mode}`);
1346
+ const result = useCase(command.mode, command.seed);
1347
+ if (isErr(result)) {
1348
+ logger.error(`Error creating game: ${result.error}`);
1349
+ const code = result.error;
1350
+ throw new StepExecutionError(translateErrorCode(translator, code), code, {
1351
+ mode: command.mode
1352
+ });
1353
+ }
1354
+ logger.info(`Game created: ${result.value.id}`);
1355
+ return result.value;
1356
+ };
1357
+
1358
+ // src/application/handlers/discardDominoHandler.ts
1359
+ var discardDominoHandler = (logger, translator, useCase) => (command) => {
1360
+ const { game, lordId } = command;
1361
+ logger.info(`Discarding domino for lord: ${lordId} in game: ${game.id}`);
1362
+ if (!isGameWithNextAction(game)) {
1363
+ logger.error(
1364
+ `Invalid game, required nextAction type: 'action' but got: ${game.nextAction.type}`
1365
+ );
1366
+ throw new InvalidStepError(
1367
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1368
+ { expected: "action", actual: game.nextAction.type, gameId: game.id }
1369
+ );
1370
+ }
1371
+ if (game.nextAction.nextAction !== "placeDomino") {
1372
+ logger.error(
1373
+ `Required game with placeDomino action but got: ${game.nextAction.nextAction}`
1374
+ );
1375
+ throw new InvalidStepError(
1376
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1377
+ {
1378
+ expected: "placeDomino",
1379
+ actual: game.nextAction.nextAction,
1380
+ gameId: game.id
1381
+ }
1382
+ );
1383
+ }
1384
+ const result = useCase(game, lordId);
1385
+ if (isErr(result)) {
1386
+ logger.error(`Error discarding domino: ${result.error}`);
1387
+ const code = result.error;
1388
+ throw new ActionExecutionError(
1389
+ translateErrorCode(translator, code),
1390
+ code,
1391
+ { gameId: game.id, lordId: command.lordId }
1392
+ );
1393
+ }
1394
+ if (isGameWithNextAction(result.value)) {
1395
+ logger.info(`Domino discarded: ${lordId} in game: ${game.id}`);
1396
+ return result.value;
1397
+ }
1398
+ logger.info(`Domino discarded: ${lordId} in game: ${game.id}`);
1399
+ return result.value;
1400
+ };
1401
+
1402
+ // src/application/handlers/getDynastyResultHandler.ts
1403
+ var getDynastyResultHandler = (logger, translator, useCase) => {
1404
+ return (command) => {
1405
+ logger.info(
1406
+ `Calculating dynasty results for ${command.games.length} games`
1407
+ );
1408
+ const results = useCase(command.games);
1409
+ logger.info(`Dynasty results calculated`);
1410
+ return results;
1411
+ };
1412
+ };
1413
+
1414
+ // src/application/handlers/getExtraRulesHandler.ts
1415
+ var getExtraRulesHandler = (logger, translator, useCase) => (command) => {
1416
+ const { mode, players } = command;
1417
+ logger.info(
1418
+ `Getting extra rules for mode: ${mode} and players: ${players}`
1419
+ );
1420
+ const result = useCase(mode, players);
1421
+ if (isErr(result)) {
1422
+ logger.error(`Error getting extra rules: ${result.error}`);
1423
+ const code = result.error;
1424
+ throw new NotFoundError(translateErrorCode(translator, code), code, {
1425
+ mode: command.mode
1426
+ });
1427
+ }
1428
+ logger.info(`Extra rules retrieved: ${result.value}`);
1429
+ return result.value;
1430
+ };
1431
+
1432
+ // src/application/handlers/getModesHandler.ts
1433
+ var getModesHandler = (logger, translator, useCase) => (_command) => {
1434
+ logger.info("Getting modes");
1435
+ const result = useCase();
1436
+ if (isErr(result)) {
1437
+ logger.error(`Error getting modes: ${result.error}`);
1438
+ const code = result.error;
1439
+ throw new NotFoundError(translateErrorCode(translator, code), code);
1440
+ }
1441
+ logger.info(`Modes retrieved: ${result.value}`);
1442
+ return result.value;
1443
+ };
1444
+
1445
+ // src/application/handlers/getResultHandler.ts
1446
+ var getResultHandler = (logger, translator, useCases) => (command) => {
1447
+ const { getResultUseCase: getResultUseCase2, calculateScoreUseCase: calculateScoreUseCase2 } = useCases;
1448
+ const { game } = command;
1449
+ logger.info(`Getting result for game: ${game.id}`);
1450
+ if (!isGameWithNextStep(game)) {
1451
+ logger.error(
1452
+ `Invalid game, required nextAction type: 'step' but got: ${game.nextAction.type}`
1453
+ );
1454
+ throw new InvalidStepError(
1455
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1456
+ { expected: "step", actual: game.nextAction.type, gameId: game.id }
1457
+ );
1458
+ }
1459
+ if (game.nextAction.step !== "result") {
1460
+ logger.error(
1461
+ `Required game with result step but got: ${game.nextAction.step}`
1462
+ );
1463
+ throw new InvalidStepError(
1464
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1465
+ { expected: "result", actual: game.nextAction.step, gameId: game.id }
1466
+ );
1467
+ }
1468
+ const { players } = game;
1469
+ const scoreResult = players.map((player) => {
1470
+ const { kingdom } = player;
1471
+ const result2 = calculateScoreUseCase2(kingdom);
1472
+ if (isErr(result2)) {
1473
+ logger.error(`Error when calculating score: ${result2.error}`);
1474
+ const code = result2.error;
1475
+ throw new StepExecutionError(
1476
+ translateErrorCode(translator, code),
1477
+ code,
1478
+ {
1479
+ gameId: game.id
1480
+ }
1481
+ );
1482
+ }
1483
+ return {
1484
+ playerId: player.id,
1485
+ playerName: player.name,
1486
+ details: {
1487
+ ...result2.value
1488
+ }
1489
+ };
1490
+ });
1491
+ const result = getResultUseCase2(game, scoreResult);
1492
+ if (isErr(result)) {
1493
+ logger.error(`Error getting result: ${result.error}`);
1494
+ const code = result.error;
1495
+ throw new NotFoundError(translateErrorCode(translator, code), code, {
1496
+ gameId: game.id
1497
+ });
1498
+ }
1499
+ logger.info(`Result retrieved for game: ${game.id}`);
1500
+ return result.value;
1501
+ };
1502
+
1503
+ // src/application/handlers/getValidPlacementsHandler.ts
1504
+ var getValidPlacementsHandler = (logger, translator, useCase) => {
1505
+ return (command) => {
1506
+ logger.info(
1507
+ `Getting valid placements for domino: ${command.domino.number}`
1508
+ );
1509
+ const placements = useCase(
1510
+ command.kingdom,
1511
+ command.domino,
1512
+ command.maxKingdomSize
1513
+ );
1514
+ logger.info(`Found ${placements.length} valid placements`);
1515
+ return placements;
1516
+ };
1517
+ };
1518
+
1519
+ // src/application/handlers/placeDominoHandler.ts
1520
+ var placeDominoHandler = (logger, translator, useCase) => (command) => {
1521
+ const { game, lordId, position, rotation } = command;
1522
+ logger.info(
1523
+ `Placing domino: ${lordId} in position: ${JSON.stringify(
1524
+ position
1525
+ )} with rotation: ${rotation} in game: ${game.id}`
1526
+ );
1527
+ if (!isGameWithNextAction(game)) {
1528
+ logger.error(
1529
+ `Invalid game, required nextAction type: 'action' but got: ${game.nextAction.type}`
1530
+ );
1531
+ throw new InvalidStepError(
1532
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1533
+ { expected: "action", actual: game.nextAction.type, gameId: game.id }
1534
+ );
1535
+ }
1536
+ if (game.nextAction.nextAction !== "placeDomino") {
1537
+ logger.error(
1538
+ `Required game with placeDomino action but got: ${game.nextAction.nextAction}`
1539
+ );
1540
+ throw new InvalidStepError(
1541
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1542
+ {
1543
+ expected: "placeDomino",
1544
+ actual: game.nextAction.nextAction,
1545
+ gameId: game.id
1546
+ }
1547
+ );
1548
+ }
1549
+ const result = useCase(game, lordId, position, rotation);
1550
+ if (isErr(result)) {
1551
+ logger.error(`Error placing domino: ${result.error}`);
1552
+ const code = result.error;
1553
+ throw new ActionExecutionError(
1554
+ translateErrorCode(translator, code),
1555
+ code,
1556
+ {
1557
+ gameId: game.id,
1558
+ lordId: command.lordId,
1559
+ position: command.position,
1560
+ rotation: command.rotation
1561
+ }
1562
+ );
1563
+ }
1564
+ if (isGameWithNextAction(result.value)) {
1565
+ logger.info(
1566
+ `Domino placed: ${lordId} in position: ${JSON.stringify(
1567
+ position
1568
+ )} with rotation: ${rotation} in game: ${game.id}`
1569
+ );
1570
+ return result.value;
1571
+ }
1572
+ logger.info(
1573
+ `Domino placed: ${lordId} in position: ${JSON.stringify(
1574
+ position
1575
+ )} with rotation: ${rotation} in game: ${game.id}`
1576
+ );
1577
+ return result.value;
1578
+ };
1579
+
1580
+ // src/core/useCases/serialization.ts
1581
+ var SERIALIZATION_VERSION = 1;
1582
+ var serializeGame = (game) => {
1583
+ const serialized = {
1584
+ version: SERIALIZATION_VERSION,
1585
+ data: game,
1586
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1587
+ };
1588
+ return JSON.stringify(serialized);
1589
+ };
1590
+ var deserializeGame = (json) => {
1591
+ try {
1592
+ const parsed = JSON.parse(json);
1593
+ if (!parsed.version || !parsed.data) {
1594
+ return err("Invalid serialized game format");
1595
+ }
1596
+ if (parsed.version > SERIALIZATION_VERSION) {
1597
+ return err(
1598
+ `Unsupported serialization version: ${parsed.version}. Current version: ${SERIALIZATION_VERSION}`
1599
+ );
1600
+ }
1601
+ const game = parsed.data;
1602
+ if (!game.id || !game.players || !game.lords || !game.nextAction) {
1603
+ return err("Invalid game state structure");
1604
+ }
1605
+ return ok(game);
1606
+ } catch (error) {
1607
+ return err(`Failed to parse game JSON: ${error.message}`);
1608
+ }
1609
+ };
1610
+
1611
+ // src/application/handlers/serializeGameHandler.ts
1612
+ var serializeGameHandler = (logger, _translator) => {
1613
+ return (command) => {
1614
+ logger.info(`Serializing game: ${command.game.id}`);
1615
+ const serialized = serializeGame(command.game);
1616
+ logger.info(`Game serialized successfully`);
1617
+ return serialized;
1618
+ };
1619
+ };
1620
+ var deserializeGameHandler = (logger, translator) => {
1621
+ return (command) => {
1622
+ logger.info(`Deserializing game from JSON`);
1623
+ const result = deserializeGame(command.json);
1624
+ if (isErr(result)) {
1625
+ logger.error(`Failed to deserialize game: ${result.error}`);
1626
+ const code = result.error;
1627
+ throw new StepExecutionError(translateErrorCode(translator, code), code);
1628
+ }
1629
+ const game = unwrap(result);
1630
+ logger.info(`Game deserialized successfully: ${game.id}`);
1631
+ return game;
1632
+ };
1633
+ };
1634
+
1635
+ // src/application/handlers/startGameHandler.ts
1636
+ var startGameHandler = (logger, translator, useCase) => (command) => {
1637
+ const { game } = command;
1638
+ logger.info(`Starting game: ${game.id}`);
1639
+ if (!isGameWithNextStep(game)) {
1640
+ logger.error(
1641
+ `Invalid game, required nextAction type: 'step' but got: ${game.nextAction.type}`
1642
+ );
1643
+ throw new InvalidStepError(
1644
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1645
+ { expected: "step", actual: game.nextAction.type, gameId: game.id }
1646
+ );
1647
+ }
1648
+ if (game.nextAction.step !== "start" && game.nextAction.step !== "options") {
1649
+ logger.error(
1650
+ `Required game with start step or options step but got: ${game.nextAction.step}`
1651
+ );
1652
+ throw new InvalidStepError(
1653
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
1654
+ {
1655
+ expected: "start|options",
1656
+ actual: game.nextAction.step,
1657
+ gameId: game.id
1658
+ }
1659
+ );
1660
+ }
1661
+ const result = useCase(game);
1662
+ if (isErr(result)) {
1663
+ logger.error(`Error starting game: ${result.error}`);
1664
+ const code = result.error;
1665
+ throw new StepExecutionError(translateErrorCode(translator, code), code, {
1666
+ gameId: game.id
1667
+ });
1668
+ }
1669
+ logger.info(`Game started: ${game.id}`);
1670
+ return result.value;
1671
+ };
1672
+
1673
+ // src/core/domain/types/kingdom.ts
1674
+ var GRIDSIZE = 9;
1675
+ var MAX_KINGDOM_SIZE = 5;
1676
+
1677
+ // src/core/domain/types/domino.ts
1678
+ var GROUND = [
1679
+ "castle",
1680
+ "wheat",
1681
+ "forest",
1682
+ "sea",
1683
+ "plain",
1684
+ "swamp",
1685
+ "mine"
1686
+ ];
1687
+
1688
+ // src/core/domain/types/player.ts
1689
+ var playerActions = {
1690
+ /** Place a previously picked domino on the kingdom */
1691
+ placeDomino: "placeDomino",
1692
+ /** Pick a domino from the revealed set */
1693
+ pickDomino: "pickDomino",
1694
+ /** Pass/discard when no valid placement exists */
1695
+ pass: "pass"
1696
+ };
1697
+
1698
+ // src/core/domain/entities/rule.ts
1699
+ var getAvailablesExtraRules = (extraRules, mode, numberOfPlayers) => {
1700
+ const availableExtraRules = extraRules.filter((rule) => {
1701
+ return rule.mode.filter((m) => m.name === mode).length > 0 && (!rule.playersLimit || rule.playersLimit === numberOfPlayers);
1702
+ });
1703
+ if (availableExtraRules.length === 0) {
1704
+ return err(ErrorCode.STEP_EXECUTION_FAILED);
1705
+ }
1706
+ return ok(availableExtraRules);
1707
+ };
1708
+ var toExtraRule = (rule, extraRules) => {
1709
+ const foundRule = extraRules.find((r) => r.name === rule);
1710
+ if (!foundRule) {
1711
+ return err(ErrorCode.STEP_EXECUTION_FAILED);
1712
+ }
1713
+ return ok(foundRule);
1714
+ };
1715
+
1716
+ // src/utils/seededShuffle.ts
1717
+ var hashSeed = (str) => {
1718
+ let hash = 2166136261;
1719
+ for (let i = 0; i < str.length; i++) {
1720
+ hash ^= str.charCodeAt(i);
1721
+ hash = Math.imul(hash, 16777619);
1722
+ }
1723
+ return hash >>> 0;
1724
+ };
1725
+ var mulberry32 = (seed) => {
1726
+ let state = seed;
1727
+ return () => {
1728
+ state |= 0;
1729
+ state = state + 1831565813 | 0;
1730
+ let t = Math.imul(state ^ state >>> 15, 1 | state);
1731
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
1732
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1733
+ };
1734
+ };
1735
+ var createSeededShuffle = (seed, salt) => {
1736
+ const numericSeed = hashSeed(`${seed}|${salt}`);
1737
+ const random = mulberry32(numericSeed);
1738
+ return (array) => {
1739
+ const result = [...array];
1740
+ for (let i = result.length - 1; i > 0; i--) {
1741
+ const j = Math.floor(random() * (i + 1));
1742
+ [result[i], result[j]] = [result[j], result[i]];
1743
+ }
1744
+ return result;
1745
+ };
1746
+ };
1747
+
1748
+ // src/core/useCases/addExtraRules.ts
1749
+ var addExtraRulesUseCase = (deps) => (game, rules) => {
1750
+ const { ruleRepository, dominoesRepository, shuffleMethod: shuffleMethod2 } = deps;
1751
+ const availableExtraRules = ruleRepository.getAllExtra();
1752
+ const newExtraRules = [];
1753
+ for (const rule of rules) {
1754
+ const result = toExtraRule(rule, availableExtraRules);
1755
+ if (isErr(result)) {
1756
+ return result;
1757
+ }
1758
+ newExtraRules.push(result.value);
1759
+ }
1760
+ let updatedBasic = game.rules.basic;
1761
+ let updatedDominoes = game.dominoes;
1762
+ const hasMightyDuel = newExtraRules.some((r) => r.name === "The Mighty Duel");
1763
+ if (hasMightyDuel) {
1764
+ updatedBasic = {
1765
+ ...updatedBasic,
1766
+ maxDominoes: 48,
1767
+ maxTurns: 12,
1768
+ maxKingdomSize: 7
1769
+ };
1770
+ const allDominoes = dominoesRepository.getForMode(game.mode);
1771
+ if (allDominoes) {
1772
+ const shuffle = game.seed ? createSeededShuffle(game.seed, "mighty-duel") : shuffleMethod2;
1773
+ updatedDominoes = shuffle(allDominoes);
1774
+ }
1775
+ }
1776
+ const updatedRules = {
1777
+ basic: updatedBasic,
1778
+ extra: newExtraRules
1779
+ };
1780
+ const next = {
1781
+ type: "step",
1782
+ step: gameSteps.start
1783
+ };
1784
+ const updatedGame = {
1785
+ ...game,
1786
+ dominoes: updatedDominoes,
1787
+ rules: updatedRules,
1788
+ nextAction: next
1789
+ };
1790
+ return ok(updatedGame);
1791
+ };
1792
+
1793
+ // src/core/domain/entities/domino.ts
1794
+ var isGround = (ground) => {
1795
+ return GROUND.includes(ground);
1796
+ };
1797
+ var createTile = (ground, crowns = 0) => {
1798
+ if (isGround(ground)) {
1799
+ return { type: ground, crowns };
1800
+ }
1801
+ return { type: ground, crowns: 0 };
1802
+ };
1803
+
1804
+ // src/core/domain/entities/kingdom.ts
1805
+ var createEmptyKingdom = () => {
1806
+ const emptyTile = createTile("empty");
1807
+ return Array.from(
1808
+ { length: GRIDSIZE },
1809
+ () => Array.from({ length: GRIDSIZE }, () => emptyTile)
1810
+ );
1811
+ };
1812
+ var placeCastle = (kingdom) => {
1813
+ const castle = createTile("castle");
1814
+ const newKingdom = deepCopyKingdom(kingdom);
1815
+ newKingdom[4][4] = castle;
1816
+ return newKingdom;
1817
+ };
1818
+ var placeTile = (kingdom, position, tile) => {
1819
+ const newKingdom = deepCopyKingdom(kingdom);
1820
+ newKingdom[position.y][position.x] = tile;
1821
+ return newKingdom;
1822
+ };
1823
+ var getTile = (kingdom, position) => {
1824
+ const tile = kingdom[position.y]?.[position.x];
1825
+ if (!tile) {
1826
+ return err(ErrorCode.PLACEMENT_OUT_OF_BOUNDS);
1827
+ }
1828
+ return ok(tile);
1829
+ };
1830
+ var calculateDominoPosition = (position, rotation, domino) => {
1831
+ let firstTile;
1832
+ let secondTile;
1833
+ switch (rotation) {
1834
+ case 0:
1835
+ firstTile = {
1836
+ tile: domino.left,
1837
+ position: {
1838
+ x: position.x,
1839
+ y: position.y
1840
+ }
1841
+ };
1842
+ secondTile = {
1843
+ tile: domino.right,
1844
+ position: {
1845
+ x: position.x + 1,
1846
+ y: position.y
1847
+ }
1848
+ };
1849
+ break;
1850
+ case 90:
1851
+ firstTile = {
1852
+ tile: domino.left,
1853
+ position: {
1854
+ x: position.x,
1855
+ y: position.y
1856
+ }
1857
+ };
1858
+ secondTile = {
1859
+ tile: domino.right,
1860
+ position: {
1861
+ x: position.x,
1862
+ y: position.y + 1
1863
+ }
1864
+ };
1865
+ break;
1866
+ case 180:
1867
+ firstTile = {
1868
+ tile: domino.right,
1869
+ position: {
1870
+ x: position.x,
1871
+ y: position.y
1872
+ }
1873
+ };
1874
+ secondTile = {
1875
+ tile: domino.left,
1876
+ position: {
1877
+ x: position.x + 1,
1878
+ y: position.y
1879
+ }
1880
+ };
1881
+ break;
1882
+ case 270:
1883
+ firstTile = {
1884
+ tile: domino.right,
1885
+ position: {
1886
+ x: position.x,
1887
+ y: position.y
1888
+ }
1889
+ };
1890
+ secondTile = {
1891
+ tile: domino.left,
1892
+ position: {
1893
+ x: position.x,
1894
+ y: position.y + 1
1895
+ }
1896
+ };
1897
+ break;
1898
+ }
1899
+ return [firstTile, secondTile];
1900
+ };
1901
+ var placeDomino = (kingdom, position, rotation, domino, maxKingdomSize = MAX_KINGDOM_SIZE) => {
1902
+ const [firstTile, secondTile] = calculateDominoPosition(
1903
+ position,
1904
+ rotation,
1905
+ domino
1906
+ );
1907
+ const isFreePlaceResult = isFreePlace(
1908
+ kingdom,
1909
+ firstTile.position,
1910
+ secondTile.position
1911
+ );
1912
+ if (isErr(isFreePlaceResult)) {
1913
+ return isFreePlaceResult;
1914
+ }
1915
+ if (exceedsMaxSize(kingdom, firstTile.position, secondTile.position, maxKingdomSize)) {
1916
+ return err(ErrorCode.PLACEMENT_EXCEEDS_KINGDOM_SIZE);
1917
+ }
1918
+ const adjacentTiles = isAdjacent(
1919
+ kingdom,
1920
+ firstTile.position,
1921
+ secondTile.position
1922
+ );
1923
+ if (adjacentTiles.length === 0) {
1924
+ return err(ErrorCode.PLACEMENT_NOT_ADJACENT);
1925
+ }
1926
+ const hasValidAdjacentTiles = hasValidAdjacent(adjacentTiles, [
1927
+ firstTile.tile,
1928
+ secondTile.tile
1929
+ ]);
1930
+ if (!hasValidAdjacentTiles) {
1931
+ return err(ErrorCode.PLACEMENT_INVALID_TERRAIN);
1932
+ }
1933
+ return ok(placeTiles(kingdom, [firstTile, secondTile]));
1934
+ };
1935
+ var placeTiles = (kingdom, tiles) => {
1936
+ let newKingdom = deepCopyKingdom(kingdom);
1937
+ for (const { tile, position } of tiles) {
1938
+ newKingdom = placeTile(newKingdom, position, tile);
1939
+ }
1940
+ return newKingdom;
1941
+ };
1942
+ var isFreePlace = (kingdom, firstPosition, secondPosition) => {
1943
+ const firstTile = getTile(kingdom, firstPosition);
1944
+ const secondTile = getTile(kingdom, secondPosition);
1945
+ if (isErr(firstTile)) {
1946
+ return firstTile;
1947
+ }
1948
+ if (isErr(secondTile)) {
1949
+ return secondTile;
1950
+ }
1951
+ if (firstTile.value.type !== "empty" || secondTile.value.type !== "empty") {
1952
+ return err(ErrorCode.PLACEMENT_NOT_EMPTY);
1953
+ }
1954
+ return ok(true);
1955
+ };
1956
+ var isAdjacent = (kingdom, firstPosition, secondPosition) => {
1957
+ const offsets = [
1958
+ { x: -1, y: 0 },
1959
+ { x: 1, y: 0 },
1960
+ { x: 0, y: -1 },
1961
+ { x: 0, y: 1 }
1962
+ ];
1963
+ const isOwnPosition = (x, y) => x === firstPosition.x && y === firstPosition.y || x === secondPosition.x && y === secondPosition.y;
1964
+ const adjacentTiles = [];
1965
+ for (const pos of [firstPosition, secondPosition]) {
1966
+ for (const offset of offsets) {
1967
+ const nx = pos.x + offset.x;
1968
+ const ny = pos.y + offset.y;
1969
+ if (isOwnPosition(nx, ny)) continue;
1970
+ const tile = getTile(kingdom, { x: nx, y: ny });
1971
+ if (isOk(tile) && tile.value.type !== "empty") {
1972
+ adjacentTiles.push(tile.value);
1973
+ }
1974
+ }
1975
+ }
1976
+ return adjacentTiles;
1977
+ };
1978
+ var hasValidAdjacent = (adjacentTiles, tiles) => {
1979
+ return adjacentTiles.some((adjacentTile) => {
1980
+ return tiles.some((tile) => {
1981
+ return tile.type === adjacentTile.type || adjacentTile.type === "castle";
1982
+ });
1983
+ });
1984
+ };
1985
+ var checkCastleIsInMiddle = (kingdom) => {
1986
+ let castleX;
1987
+ let castleY;
1988
+ for (let y = 0; y < kingdom.length; y++) {
1989
+ const x = kingdom[y].findIndex((tile) => tile.type === "castle");
1990
+ if (x !== -1) {
1991
+ castleX = x;
1992
+ castleY = y;
1993
+ break;
1994
+ }
1995
+ }
1996
+ if (castleX === void 0 || castleY === void 0) {
1997
+ return false;
1998
+ }
1999
+ const bbox = getBoundingBox(kingdom);
2000
+ if (!bbox) return false;
2001
+ const centerX = (bbox.minX + bbox.maxX) / 2;
2002
+ const centerY = (bbox.minY + bbox.maxY) / 2;
2003
+ return castleX === centerX && castleY === centerY;
2004
+ };
2005
+ var getBoundingBox = (kingdom) => {
2006
+ let minX = Infinity;
2007
+ let maxX = -Infinity;
2008
+ let minY = Infinity;
2009
+ let maxY = -Infinity;
2010
+ for (let y = 0; y < kingdom.length; y++) {
2011
+ for (let x = 0; x < kingdom[y].length; x++) {
2012
+ if (kingdom[y][x].type !== "empty") {
2013
+ if (x < minX) minX = x;
2014
+ if (x > maxX) maxX = x;
2015
+ if (y < minY) minY = y;
2016
+ if (y > maxY) maxY = y;
2017
+ }
2018
+ }
2019
+ }
2020
+ if (minX === Infinity) return null;
2021
+ return { minX, maxX, minY, maxY };
2022
+ };
2023
+ var exceedsMaxSize = (kingdom, firstPosition, secondPosition, maxSize = MAX_KINGDOM_SIZE) => {
2024
+ const bbox = getBoundingBox(kingdom);
2025
+ let minX = bbox?.minX ?? Infinity;
2026
+ let maxX = bbox?.maxX ?? -Infinity;
2027
+ let minY = bbox?.minY ?? Infinity;
2028
+ let maxY = bbox?.maxY ?? -Infinity;
2029
+ for (const pos of [firstPosition, secondPosition]) {
2030
+ if (pos.x < minX) minX = pos.x;
2031
+ if (pos.x > maxX) maxX = pos.x;
2032
+ if (pos.y < minY) minY = pos.y;
2033
+ if (pos.y > maxY) maxY = pos.y;
2034
+ }
2035
+ return maxX - minX + 1 > maxSize || maxY - minY + 1 > maxSize;
2036
+ };
2037
+ var countDominoes = (kingdom) => {
2038
+ const notEmpties = kingdom.flat().filter((tile) => tile.type !== "empty");
2039
+ return (notEmpties.length - 1) / 2;
2040
+ };
2041
+ var deepCopyKingdom = (kingdom) => {
2042
+ return kingdom.map((line) => line.map((tile) => ({ ...tile })));
2043
+ };
2044
+
2045
+ // src/core/domain/entities/player.ts
2046
+ var MINPLAYERSNAMELENGTH = 3;
2047
+ var validatePlayer = (name) => {
2048
+ if (!name || name.trim().length < MINPLAYERSNAMELENGTH) {
2049
+ return err(ErrorCode.INVALID_PLAYER_NAME);
2050
+ }
2051
+ return ok(name);
2052
+ };
2053
+ var createPlayer = (playerName, id, kingdom, bot) => {
2054
+ const name = validatePlayer(playerName);
2055
+ if (isErr(name)) {
2056
+ return name;
2057
+ }
2058
+ return ok({
2059
+ name: name.value,
2060
+ id,
2061
+ kingdom,
2062
+ ...bot && { bot }
2063
+ });
2064
+ };
2065
+
2066
+ // src/core/useCases/addPlayers.ts
2067
+ var normalizePlayerInput = (input2) => {
2068
+ if (typeof input2 === "string") {
2069
+ return { name: input2 };
2070
+ }
2071
+ return { name: input2.name, bot: input2.bot };
2072
+ };
2073
+ var addPlayersUseCase = (deps) => (game, players) => {
2074
+ const { uuidMethod: uuidMethod3, shuffleMethod: shuffleMethod2, ruleRepository } = deps;
2075
+ if (!players || players.length < 1 || players.length > 4) {
2076
+ return err(ErrorCode.INVALID_PLAYER_COUNT);
2077
+ }
2078
+ const newPlayers = [];
2079
+ for (const player of players) {
2080
+ const { name, bot } = normalizePlayerInput(player);
2081
+ let kingdom = createEmptyKingdom();
2082
+ kingdom = placeCastle(kingdom);
2083
+ let id = uuidMethod3();
2084
+ const newPlayer = createPlayer(name, id, kingdom, bot);
2085
+ if (isErr(newPlayer)) {
2086
+ return newPlayer;
2087
+ }
2088
+ newPlayers.push(newPlayer.value);
2089
+ }
2090
+ const rules = ruleRepository.getAll();
2091
+ const basicRules = rules.basic[newPlayers.length];
2092
+ if (!basicRules) {
2093
+ return err(ErrorCode.STEP_EXECUTION_FAILED);
2094
+ }
2095
+ const dominoes = game.dominoes;
2096
+ const { maxDominoes } = basicRules;
2097
+ const shuffle = game.seed ? createSeededShuffle(game.seed, "dominoes") : shuffleMethod2;
2098
+ const shuffledDominoes = shuffle(dominoes);
2099
+ const splicedDominoes = shuffledDominoes.slice(0, maxDominoes);
2100
+ const next = {
2101
+ type: "step",
2102
+ step: "options"
2103
+ };
2104
+ const updatedGame = {
2105
+ ...game,
2106
+ players: newPlayers,
2107
+ dominoes: splicedDominoes,
2108
+ rules: {
2109
+ ...game.rules,
2110
+ basic: basicRules
2111
+ },
2112
+ nextAction: next
2113
+ };
2114
+ return ok(updatedGame);
2115
+ };
2116
+
2117
+ // src/core/useCases/calculateScore.ts
2118
+ var calculateScoreUseCase = (kingdom) => {
2119
+ const rows = kingdom.length;
2120
+ const cols = kingdom[0].length;
2121
+ const visited = kingdom.map((row) => row.map(() => false));
2122
+ const properties = [];
2123
+ const directions = [
2124
+ [-1, 0],
2125
+ [1, 0],
2126
+ [0, -1],
2127
+ [0, 1]
2128
+ ];
2129
+ for (let y = 0; y < rows; y++) {
2130
+ for (let x = 0; x < cols; x++) {
2131
+ const tile = kingdom[y][x];
2132
+ if (visited[y][x] || tile.type === "empty" || tile.type === "castle") {
2133
+ continue;
2134
+ }
2135
+ const type = tile.type;
2136
+ let size = 0;
2137
+ let crowns = 0;
2138
+ const queue = [[x, y]];
2139
+ visited[y][x] = true;
2140
+ while (queue.length > 0) {
2141
+ const [cx, cy] = queue.pop();
2142
+ const current = kingdom[cy][cx];
2143
+ size++;
2144
+ crowns += current.crowns;
2145
+ for (const [dx, dy] of directions) {
2146
+ const nx = cx + dx;
2147
+ const ny = cy + dy;
2148
+ if (nx >= 0 && ny >= 0 && nx < cols && ny < rows && !visited[ny][nx] && kingdom[ny][nx].type === type) {
2149
+ visited[ny][nx] = true;
2150
+ queue.push([nx, ny]);
2151
+ }
2152
+ }
2153
+ }
2154
+ properties.push({ size, crowns });
2155
+ }
2156
+ }
2157
+ if (properties.length === 0) {
2158
+ return ok({
2159
+ points: 0,
2160
+ maxPropertiesSize: 0,
2161
+ totalCrowns: 0
2162
+ });
2163
+ }
2164
+ let points = 0;
2165
+ let maxPropertiesSize = 0;
2166
+ let totalCrowns = 0;
2167
+ for (const property of properties) {
2168
+ points += property.size * property.crowns;
2169
+ if (property.size > maxPropertiesSize) {
2170
+ maxPropertiesSize = property.size;
2171
+ }
2172
+ totalCrowns += property.crowns;
2173
+ }
2174
+ return ok({
2175
+ points,
2176
+ maxPropertiesSize,
2177
+ totalCrowns
2178
+ });
2179
+ };
2180
+
2181
+ // src/core/domain/entities/lord.ts
2182
+ var createLord = (id, playerId) => {
2183
+ return {
2184
+ id,
2185
+ playerId,
2186
+ turnEnded: false,
2187
+ hasPick: false,
2188
+ hasPlace: true
2189
+ };
2190
+ };
2191
+ var hasPick = (lord) => lord.hasPick;
2192
+ var hasPlace = (lord) => lord.hasPlace;
2193
+ var hasTurnEnded = (lord) => lord.turnEnded;
2194
+ var canPass = (lord) => {
2195
+ return !hasPick(lord) && !hasPlace(lord) && !hasTurnEnded(lord);
2196
+ };
2197
+ var canPick = (lord) => {
2198
+ return !hasPick(lord) && hasPlace(lord) && !hasTurnEnded(lord);
2199
+ };
2200
+ var canPlaceAndDominoPickedIsDefined = (lord) => {
2201
+ return !hasPick(lord) && !hasPlace(lord) && !hasTurnEnded(lord) && lord.dominoPicked !== void 0;
2202
+ };
2203
+ var updateLordOrder = (lords) => {
2204
+ return [...lords].sort((a, b) => {
2205
+ return (a.dominoPicked?.number ?? 0) - (b.dominoPicked?.number ?? 0);
2206
+ });
2207
+ };
2208
+ var resetLordsActions = (lords) => {
2209
+ return lords.map((lord) => ({
2210
+ ...lord,
2211
+ turnEnded: false,
2212
+ hasPick: false,
2213
+ hasPlace: false
2214
+ }));
2215
+ };
2216
+ var nextLordWithAction = (lords) => {
2217
+ const nextLord = findNextLord(lords);
2218
+ return {
2219
+ type: "action",
2220
+ nextLord: nextLord.id,
2221
+ nextAction: hasPlace(nextLord) ? playerActions.pickDomino : playerActions.placeDomino
2222
+ };
2223
+ };
2224
+ var findNextLord = (lords) => {
2225
+ const lord = lords.find((lord2) => !hasTurnEnded(lord2));
2226
+ if (!lord) {
2227
+ return lords[0];
2228
+ }
2229
+ return lord;
2230
+ };
2231
+ var allLordsHavePlayed = (lords) => {
2232
+ return lords.every((lord) => lord.turnEnded);
2233
+ };
2234
+
2235
+ // src/core/useCases/chooseDomino.ts
2236
+ var chooseDominoUseCase = (game, lordId, dominoPick) => {
2237
+ const currentLord = game.lords.find(
2238
+ (lord) => lord.id === game.nextAction.nextLord
2239
+ );
2240
+ if (!currentLord) {
2241
+ return err(ErrorCode.LORD_NOT_FOUND);
2242
+ }
2243
+ if (currentLord.id !== lordId) {
2244
+ return err(ErrorCode.NOT_YOUR_TURN);
2245
+ }
2246
+ if (!canPick(currentLord)) {
2247
+ return err(ErrorCode.CANNOT_PICK);
2248
+ }
2249
+ const selectedDomino = game.currentDominoes.find(
2250
+ (domino) => domino.domino.number === dominoPick
2251
+ );
2252
+ if (!selectedDomino) {
2253
+ return err(ErrorCode.DOMINO_NOT_FOUND);
2254
+ }
2255
+ if (selectedDomino.picked) {
2256
+ return err(ErrorCode.DOMINO_ALREADY_PICKED);
2257
+ }
2258
+ const updatedCurrentDominoes = game.currentDominoes.map(
2259
+ (domino) => domino.domino.number === dominoPick ? { ...domino, picked: true, lordId: currentLord.id } : domino
2260
+ );
2261
+ const updatedLords = game.lords.map(
2262
+ (lord) => lord.id === currentLord.id ? { ...lord, hasPick: true, turnEnded: true, dominoPicked: selectedDomino.domino } : lord
2263
+ );
2264
+ const turnEnded = allLordsHavePlayed(updatedLords);
2265
+ if (turnEnded) {
2266
+ const lords = resetLordsActions(updateLordOrder(updatedLords));
2267
+ const updatedTurn = game.turn + 1;
2268
+ const next2 = nextLordWithAction(lords);
2269
+ const dominoesCopy = [...game.dominoes];
2270
+ const dominoesDrawn = dominoesCopy.splice(
2271
+ 0,
2272
+ game.rules.basic.dominoesPerTurn
2273
+ );
2274
+ dominoesDrawn.sort((a, b) => a.number - b.number);
2275
+ const updatedCurrentDominoes2 = dominoesDrawn.map((domino, index) => {
2276
+ return {
2277
+ domino,
2278
+ picked: false,
2279
+ lordId: null,
2280
+ position: index + 1
2281
+ };
2282
+ });
2283
+ return ok({
2284
+ ...game,
2285
+ currentDominoes: updatedCurrentDominoes2,
2286
+ dominoes: dominoesCopy,
2287
+ lords,
2288
+ turn: updatedTurn,
2289
+ nextAction: next2
2290
+ });
2291
+ }
2292
+ const next = nextLordWithAction(updatedLords);
2293
+ const updatedState = {
2294
+ ...game,
2295
+ currentDominoes: updatedCurrentDominoes,
2296
+ lords: updatedLords,
2297
+ nextAction: next
2298
+ };
2299
+ return ok(updatedState);
2300
+ };
2301
+
2302
+ // src/core/domain/entities/game.ts
2303
+ var create = (payload) => {
2304
+ const { id, mode, dominoes, seed } = payload;
2305
+ return {
2306
+ id,
2307
+ dominoes,
2308
+ currentDominoes: [],
2309
+ players: [],
2310
+ lords: [],
2311
+ turn: 0,
2312
+ nextAction: {
2313
+ type: "step",
2314
+ step: gameSteps.addPlayers
2315
+ },
2316
+ rules: {
2317
+ basic: {
2318
+ lords: 0,
2319
+ maxDominoes: 0,
2320
+ dominoesPerTurn: 0,
2321
+ maxTurns: 0,
2322
+ maxKingdomSize: 5
2323
+ },
2324
+ extra: []
2325
+ },
2326
+ mode,
2327
+ ...seed !== void 0 && { seed }
2328
+ };
2329
+ };
2330
+
2331
+ // src/core/domain/entities/mode.ts
2332
+ var findMode = (mode, availableMode) => {
2333
+ return availableMode.find((m) => m.name === mode);
2334
+ };
2335
+ var createMode = (mode, availableMode) => {
2336
+ const foundMode = findMode(mode, availableMode);
2337
+ if (foundMode) {
2338
+ return ok(foundMode);
2339
+ }
2340
+ return err(ErrorCode.MODE_NOT_FOUND);
2341
+ };
2342
+
2343
+ // src/core/useCases/createGame.ts
2344
+ var createGameUseCase = (deps) => (mode, seed) => {
2345
+ const { modeRepository, dominoesRepository, uuidMethod: uuidMethod3 } = deps;
2346
+ const id = uuidMethod3();
2347
+ const gameSeed = seed ?? uuidMethod3();
2348
+ const availableMode = modeRepository.getAvailables();
2349
+ const newMode = createMode(mode, availableMode);
2350
+ if (isErr(newMode)) {
2351
+ return newMode;
2352
+ }
2353
+ const dominoes = dominoesRepository.getForMode(newMode.value);
2354
+ if (!dominoes || dominoes.length === 0) {
2355
+ return err(ErrorCode.DOMINO_NOT_FOUND);
2356
+ }
2357
+ const payload = {
2358
+ id,
2359
+ mode: newMode.value,
2360
+ dominoes,
2361
+ seed: gameSeed
2362
+ };
2363
+ const newGame = create(payload);
2364
+ return ok(newGame);
2365
+ };
2366
+
2367
+ // src/core/useCases/getValidPlacements.ts
2368
+ var getValidPlacementsUseCase = (kingdom, domino, maxKingdomSize = MAX_KINGDOM_SIZE) => {
2369
+ const validPlacements = [];
2370
+ const rotations = [0, 90, 180, 270];
2371
+ for (let y = 0; y < GRIDSIZE; y++) {
2372
+ for (let x = 0; x < GRIDSIZE; x++) {
2373
+ const position = { x, y };
2374
+ for (const rotation of rotations) {
2375
+ const result = placeDomino(kingdom, position, rotation, domino, maxKingdomSize);
2376
+ if (isOk(result)) {
2377
+ validPlacements.push({ position, rotation });
2378
+ }
2379
+ }
2380
+ }
2381
+ }
2382
+ return validPlacements;
2383
+ };
2384
+ var canPlaceDominoUseCase = (kingdom, domino, maxKingdomSize = MAX_KINGDOM_SIZE) => {
2385
+ const rotations = [0, 90, 180, 270];
2386
+ for (let y = 0; y < GRIDSIZE; y++) {
2387
+ for (let x = 0; x < GRIDSIZE; x++) {
2388
+ const position = { x, y };
2389
+ for (const rotation of rotations) {
2390
+ const result = placeDomino(kingdom, position, rotation, domino, maxKingdomSize);
2391
+ if (isOk(result)) {
2392
+ return true;
2393
+ }
2394
+ }
2395
+ }
2396
+ }
2397
+ return false;
2398
+ };
2399
+
2400
+ // src/core/useCases/discardDomino.ts
2401
+ var discardDominoUseCase = (game, lordId) => {
2402
+ const nextAction = game.nextAction;
2403
+ const currentLord = game.lords.find(
2404
+ (lord) => lord.id === nextAction.nextLord
2405
+ );
2406
+ if (!currentLord) {
2407
+ return err(ErrorCode.LORD_NOT_FOUND);
2408
+ }
2409
+ if (currentLord.id !== lordId) {
2410
+ return err(ErrorCode.NOT_YOUR_TURN);
2411
+ }
2412
+ if (!canPass(currentLord)) {
2413
+ return err(ErrorCode.CANNOT_PLACE);
2414
+ }
2415
+ if (currentLord.dominoPicked) {
2416
+ const currentPlayer = game.players.find(
2417
+ (player) => player.id === currentLord.playerId
2418
+ );
2419
+ if (currentPlayer && canPlaceDominoUseCase(currentPlayer.kingdom, currentLord.dominoPicked, game.rules.basic.maxKingdomSize)) {
2420
+ return err(ErrorCode.CANNOT_PLACE);
2421
+ }
2422
+ }
2423
+ const maxTurns = game.rules.basic.maxTurns;
2424
+ const currentTurn = game.turn;
2425
+ const isLastTurn = currentTurn === maxTurns;
2426
+ const updatedLord = game.lords.map((lord) => {
2427
+ if (lord.id === currentLord.id) {
2428
+ if (isLastTurn) {
2429
+ return {
2430
+ ...lord,
2431
+ turnEnded: true,
2432
+ hasPlace: true
2433
+ };
2434
+ }
2435
+ return {
2436
+ ...lord,
2437
+ hasPlace: true
2438
+ };
2439
+ }
2440
+ return lord;
2441
+ });
2442
+ let updatedGame;
2443
+ const resultStep = {
2444
+ type: "step",
2445
+ step: gameSteps.result
2446
+ };
2447
+ if (isLastTurn && allLordsHavePlayed(updatedLord)) {
2448
+ updatedGame = {
2449
+ ...game,
2450
+ lords: updatedLord,
2451
+ nextAction: resultStep
2452
+ };
2453
+ } else {
2454
+ updatedGame = {
2455
+ ...game,
2456
+ lords: updatedLord,
2457
+ nextAction: nextLordWithAction(updatedLord)
2458
+ };
2459
+ }
2460
+ return ok(updatedGame);
2461
+ };
2462
+
2463
+ // src/core/useCases/getDynastyResult.ts
2464
+ var getDynastyResultUseCase = (games) => {
2465
+ const playerMap = /* @__PURE__ */ new Map();
2466
+ for (const game of games) {
2467
+ for (const result of game.result) {
2468
+ const existing = playerMap.get(result.playerId);
2469
+ if (existing) {
2470
+ existing.points.push(result.details.points);
2471
+ } else {
2472
+ playerMap.set(result.playerId, {
2473
+ name: result.playerName,
2474
+ points: [result.details.points]
2475
+ });
2476
+ }
2477
+ }
2478
+ }
2479
+ const results = [];
2480
+ for (const [playerId, data] of playerMap) {
2481
+ results.push({
2482
+ playerId,
2483
+ playerName: data.name,
2484
+ totalPoints: data.points.reduce((sum, p) => sum + p, 0),
2485
+ gamesPoints: data.points
2486
+ });
2487
+ }
2488
+ results.sort((a, b) => b.totalPoints - a.totalPoints);
2489
+ const finalResults = [];
2490
+ for (let i = 0; i < results.length; i++) {
2491
+ const current = results[i];
2492
+ const previous = i > 0 ? finalResults[i - 1] : null;
2493
+ const position = previous && current.totalPoints === previous.totalPoints ? previous.position : i + 1;
2494
+ finalResults.push({ ...current, position });
2495
+ }
2496
+ return finalResults;
2497
+ };
2498
+
2499
+ // src/core/useCases/getExtraRules.ts
2500
+ var getExtraRulesUseCase = (deps) => (mode, players) => {
2501
+ const { ruleRepository } = deps;
2502
+ const allExtraRules = ruleRepository.getAllExtra();
2503
+ if (!allExtraRules) {
2504
+ return err(ErrorCode.STEP_EXECUTION_FAILED);
2505
+ }
2506
+ return getAvailablesExtraRules(allExtraRules, mode, players);
2507
+ };
2508
+
2509
+ // src/core/useCases/getModes.ts
2510
+ var getModesUseCase = (deps) => () => {
2511
+ const { modeRepository } = deps;
2512
+ return ok(modeRepository.getAvailables());
2513
+ };
2514
+
2515
+ // src/core/useCases/getResult.ts
2516
+ var getResultUseCase = (game, scoreResult) => {
2517
+ const { rules } = game;
2518
+ const { basic, extra } = rules;
2519
+ const finalScoreResult = scoreResult.map((score) => {
2520
+ let finalScore = score.details.points;
2521
+ const player = game.players.find((p) => p.id === score.playerId);
2522
+ if (!player) {
2523
+ return score;
2524
+ }
2525
+ const { kingdom } = player;
2526
+ if (extra.length > 0) {
2527
+ extra.forEach((extraRule) => {
2528
+ if (extraRule.name === "The middle Kingdom") {
2529
+ const castleIsInMiddle = checkCastleIsInMiddle(kingdom);
2530
+ if (castleIsInMiddle) {
2531
+ finalScore += 10;
2532
+ }
2533
+ }
2534
+ if (extraRule.name === "Harmony") {
2535
+ const totalDominoes = countDominoes(kingdom);
2536
+ const dominoLimit = basic.maxDominoes;
2537
+ const totalPlayers = game.players.length;
2538
+ const dominoesPerPlayer = dominoLimit / totalPlayers;
2539
+ if (totalDominoes === dominoesPerPlayer) {
2540
+ finalScore += 5;
2541
+ }
2542
+ }
2543
+ });
2544
+ }
2545
+ return {
2546
+ ...score,
2547
+ details: {
2548
+ ...score.details,
2549
+ points: finalScore
2550
+ }
2551
+ };
2552
+ });
2553
+ finalScoreResult.sort((a, b) => {
2554
+ if (a.details.points !== b.details.points) {
2555
+ return b.details.points - a.details.points;
2556
+ }
2557
+ return b.details.maxPropertiesSize - a.details.maxPropertiesSize;
2558
+ });
2559
+ const result = [];
2560
+ let isTie = false;
2561
+ result.push({
2562
+ playerId: finalScoreResult[0].playerId,
2563
+ playerName: finalScoreResult[0].playerName,
2564
+ details: finalScoreResult[0].details,
2565
+ position: 1
2566
+ });
2567
+ for (let i = 1; i < finalScoreResult.length; i++) {
2568
+ const currentScore = finalScoreResult[i];
2569
+ const previousScore = finalScoreResult[i - 1];
2570
+ if (previousScore && currentScore && currentScore.details.points === previousScore.details.points && currentScore.details.maxPropertiesSize === previousScore.details.maxPropertiesSize) {
2571
+ isTie = true;
2572
+ }
2573
+ if (isTie) {
2574
+ result.push({
2575
+ ...currentScore,
2576
+ position: result[i - 1].position
2577
+ });
2578
+ } else {
2579
+ result.push({
2580
+ ...currentScore,
2581
+ position: i + 1
2582
+ });
2583
+ }
2584
+ isTie = false;
2585
+ }
2586
+ return ok({
2587
+ ...game,
2588
+ result
2589
+ });
2590
+ };
2591
+
2592
+ // src/core/useCases/placeDomino.ts
2593
+ var placeDominoUseCase = (game, lordId, position, rotation) => {
2594
+ const nextAction = game.nextAction;
2595
+ const currentLord = game.lords.find(
2596
+ (lord) => lord.id === nextAction.nextLord
2597
+ );
2598
+ if (!currentLord) {
2599
+ return err(ErrorCode.LORD_NOT_FOUND);
2600
+ }
2601
+ if (currentLord.id !== lordId) {
2602
+ return err(ErrorCode.NOT_YOUR_TURN);
2603
+ }
2604
+ if (!canPlaceAndDominoPickedIsDefined(currentLord)) {
2605
+ return err(ErrorCode.CANNOT_PLACE);
2606
+ }
2607
+ const domino = currentLord.dominoPicked;
2608
+ const currentPlayer = game.players.find(
2609
+ (player) => player.id === currentLord.playerId
2610
+ );
2611
+ if (!currentPlayer) {
2612
+ return err(ErrorCode.PLAYER_NOT_FOUND);
2613
+ }
2614
+ const updatedKingdom = placeDomino(
2615
+ currentPlayer.kingdom,
2616
+ position,
2617
+ rotation,
2618
+ domino,
2619
+ game.rules.basic.maxKingdomSize
2620
+ );
2621
+ if (isErr(updatedKingdom)) {
2622
+ return updatedKingdom;
2623
+ }
2624
+ const updatedPlayers = game.players.map((player) => {
2625
+ if (player.id === currentPlayer.id) {
2626
+ player.kingdom = updatedKingdom.value;
2627
+ }
2628
+ return player;
2629
+ });
2630
+ const maxTurns = game.rules.basic.maxTurns;
2631
+ const currentTurn = game.turn;
2632
+ const isLastTurn = currentTurn === maxTurns;
2633
+ const updatedLords = game.lords.map((lord) => {
2634
+ if (lord.id === currentLord.id) {
2635
+ if (isLastTurn) {
2636
+ return {
2637
+ ...lord,
2638
+ turnEnded: true,
2639
+ hasPlace: true
2640
+ };
2641
+ }
2642
+ return {
2643
+ ...lord,
2644
+ hasPlace: true
2645
+ };
2646
+ }
2647
+ return lord;
2648
+ });
2649
+ let updatedGame;
2650
+ const resultStep = {
2651
+ type: "step",
2652
+ step: gameSteps.result
2653
+ };
2654
+ if (isLastTurn && allLordsHavePlayed(updatedLords)) {
2655
+ updatedGame = {
2656
+ ...game,
2657
+ lords: updatedLords,
2658
+ nextAction: resultStep,
2659
+ players: updatedPlayers
2660
+ };
2661
+ } else {
2662
+ updatedGame = {
2663
+ ...game,
2664
+ lords: updatedLords,
2665
+ nextAction: nextLordWithAction(updatedLords),
2666
+ players: updatedPlayers
2667
+ };
2668
+ }
2669
+ return ok(updatedGame);
2670
+ };
2671
+
2672
+ // src/core/useCases/startGame.ts
2673
+ var startGameUseCase = (deps) => (game) => {
2674
+ const { uuidMethod: uuidMethod3, shuffleMethod: shuffleMethod2 } = deps;
2675
+ const {
2676
+ rules: {
2677
+ basic: { lords: maxLords, dominoesPerTurn }
2678
+ },
2679
+ players,
2680
+ dominoes
2681
+ } = game;
2682
+ const numberOfPlayers = players.length;
2683
+ const newLords = [];
2684
+ for (let i = 0; i < numberOfPlayers; i++) {
2685
+ for (let j = 0; j < maxLords; j++) {
2686
+ const lord = createLord(uuidMethod3(), players[i].id);
2687
+ newLords.push(lord);
2688
+ }
2689
+ }
2690
+ const shuffle = game.seed ? createSeededShuffle(game.seed, "lords") : shuffleMethod2;
2691
+ const shuffledLords = shuffle(newLords);
2692
+ const dominoesCopy = [...dominoes];
2693
+ const dominoesDrawn = dominoesCopy.splice(0, dominoesPerTurn);
2694
+ dominoesDrawn.sort((a, b) => a.number - b.number);
2695
+ const nextAction = {
2696
+ type: "action",
2697
+ nextLord: shuffledLords[0].id,
2698
+ nextAction: playerActions.pickDomino
2699
+ };
2700
+ const newState = {
2701
+ ...game,
2702
+ lords: shuffledLords,
2703
+ nextAction,
2704
+ dominoes: dominoesCopy,
2705
+ currentDominoes: dominoesDrawn.map((domino, index) => {
2706
+ return {
2707
+ domino,
2708
+ picked: false,
2709
+ lordId: null,
2710
+ position: index + 1
2711
+ };
2712
+ })
2713
+ };
2714
+ return ok(newState);
2715
+ };
2716
+
2717
+ // src/config.ts
2718
+ var configureEngine = (config) => {
2719
+ const modeRepository = jsonModes_default();
2720
+ const dominoesRepository = jsonDominoes_default();
2721
+ const ruleRepository = jsonRules_default();
2722
+ const uuid = config.uuidMethod || uuidMethod;
2723
+ const shuffle = config.shuffleMethod || shuffleMethod;
2724
+ const translator = config.translator || defaultTranslator;
2725
+ const logger = config.logger ? config.logger : config.logging ? consoleLogger(true) : silentLogger;
2726
+ return {
2727
+ createGameHandler: createGameHandler(
2728
+ logger,
2729
+ translator,
2730
+ createGameUseCase({
2731
+ modeRepository,
2732
+ dominoesRepository,
2733
+ uuidMethod: uuid
2734
+ })
2735
+ ),
2736
+ getModesHandler: getModesHandler(
2737
+ logger,
2738
+ translator,
2739
+ getModesUseCase({ modeRepository })
2740
+ ),
2741
+ getExtraRulesHandler: getExtraRulesHandler(
2742
+ logger,
2743
+ translator,
2744
+ getExtraRulesUseCase({ ruleRepository })
2745
+ ),
2746
+ addPlayersHandler: addPlayersHandler(
2747
+ logger,
2748
+ translator,
2749
+ addPlayersUseCase({
2750
+ uuidMethod: uuid,
2751
+ shuffleMethod: shuffle,
2752
+ ruleRepository
2753
+ })
2754
+ ),
2755
+ addExtraRulesHandler: addExtraRulesHandler(
2756
+ logger,
2757
+ translator,
2758
+ addExtraRulesUseCase({
2759
+ ruleRepository,
2760
+ dominoesRepository,
2761
+ shuffleMethod: shuffle
2762
+ })
2763
+ ),
2764
+ startGameHandler: startGameHandler(
2765
+ logger,
2766
+ translator,
2767
+ startGameUseCase({ uuidMethod: uuid, shuffleMethod: shuffle })
2768
+ ),
2769
+ chooseDominoHandler: chooseDominoHandler(
2770
+ logger,
2771
+ translator,
2772
+ chooseDominoUseCase
2773
+ ),
2774
+ placeDominoHandler: placeDominoHandler(
2775
+ logger,
2776
+ translator,
2777
+ placeDominoUseCase
2778
+ ),
2779
+ discardDominoHandler: discardDominoHandler(
2780
+ logger,
2781
+ translator,
2782
+ discardDominoUseCase
2783
+ ),
2784
+ getResultHandler: getResultHandler(logger, translator, {
2785
+ getResultUseCase,
2786
+ calculateScoreUseCase
2787
+ }),
2788
+ calculateScoreHandler: calculateScoreHandler(
2789
+ logger,
2790
+ translator,
2791
+ calculateScoreUseCase
2792
+ ),
2793
+ getValidPlacementsHandler: getValidPlacementsHandler(
2794
+ logger,
2795
+ translator,
2796
+ getValidPlacementsUseCase
2797
+ ),
2798
+ canPlaceDominoHandler: canPlaceDominoHandler(
2799
+ logger,
2800
+ translator,
2801
+ canPlaceDominoUseCase
2802
+ ),
2803
+ serializeGameHandler: serializeGameHandler(logger, translator),
2804
+ deserializeGameHandler: deserializeGameHandler(logger, translator),
2805
+ getDynastyResultHandler: getDynastyResultHandler(
2806
+ logger,
2807
+ translator,
2808
+ getDynastyResultUseCase
2809
+ )
2810
+ };
2811
+ };
2812
+
2813
+ // src/core/domain/entities/validateGameState.ts
2814
+ var validGrounds = /* @__PURE__ */ new Set([...GROUND, "empty"]);
2815
+ var validPlayerActions = new Set(Object.values(playerActions));
2816
+ var validGameSteps = new Set(Object.values(gameSteps));
2817
+
2818
+ // src/index.ts
2819
+ var createGameEngine = (config) => {
2820
+ const {
2821
+ createGameHandler: createGameHandler2,
2822
+ getModesHandler: getModesHandler2,
2823
+ getExtraRulesHandler: getExtraRulesHandler2,
2824
+ addPlayersHandler: addPlayersHandler2,
2825
+ addExtraRulesHandler: addExtraRulesHandler2,
2826
+ startGameHandler: startGameHandler2,
2827
+ chooseDominoHandler: chooseDominoHandler2,
2828
+ placeDominoHandler: placeDominoHandler2,
2829
+ discardDominoHandler: discardDominoHandler2,
2830
+ getResultHandler: getResultHandler2,
2831
+ calculateScoreHandler: calculateScoreHandler2,
2832
+ getValidPlacementsHandler: getValidPlacementsHandler2,
2833
+ canPlaceDominoHandler: canPlaceDominoHandler2,
2834
+ serializeGameHandler: serializeGameHandler2,
2835
+ deserializeGameHandler: deserializeGameHandler2,
2836
+ getDynastyResultHandler: getDynastyResultHandler2
2837
+ } = configureEngine(config);
2838
+ let engine2 = {
2839
+ getModes: (command) => getModesHandler2(command),
2840
+ getExtraRules: (command) => getExtraRulesHandler2(command),
2841
+ createGame: (command) => createGameHandler2(command),
2842
+ addPlayers: (command) => addPlayersHandler2(command),
2843
+ addExtraRules: (command) => addExtraRulesHandler2(command),
2844
+ startGame: (command) => startGameHandler2(command),
2845
+ chooseDomino: (command) => chooseDominoHandler2(command),
2846
+ placeDomino: (command) => placeDominoHandler2(command),
2847
+ discardDomino: (command) => discardDominoHandler2(command),
2848
+ getResults: (command) => getResultHandler2(command),
2849
+ calculateScore: (command) => calculateScoreHandler2(command),
2850
+ getValidPlacements: (command) => getValidPlacementsHandler2(command),
2851
+ canPlaceDomino: (command) => canPlaceDominoHandler2(command),
2852
+ serialize: (command) => serializeGameHandler2(command),
2853
+ deserialize: (command) => deserializeGameHandler2(command),
2854
+ getDynastyResults: (command) => getDynastyResultHandler2(command)
2855
+ };
2856
+ if (config.events) {
2857
+ engine2 = wrapWithEvents(engine2, config.events);
2858
+ }
2859
+ if (config.debug) {
2860
+ const debugOptions = typeof config.debug === "object" ? config.debug : void 0;
2861
+ engine2 = wrapWithDebug(engine2, debugOptions);
2862
+ }
2863
+ return engine2;
2864
+ };
2865
+
2866
+ // src/wasm/dispatch.ts
2867
+ var ENGINE_METHODS = /* @__PURE__ */ new Set([
2868
+ "getModes",
2869
+ "getExtraRules",
2870
+ "createGame",
2871
+ "addPlayers",
2872
+ "addExtraRules",
2873
+ "startGame",
2874
+ "chooseDomino",
2875
+ "placeDomino",
2876
+ "discardDomino",
2877
+ "getResults",
2878
+ "calculateScore",
2879
+ "getValidPlacements",
2880
+ "canPlaceDomino",
2881
+ "serialize",
2882
+ "deserialize",
2883
+ "getDynastyResults"
2884
+ ]);
2885
+ var dispatch = (engine2, request2) => {
2886
+ const { method, params } = request2;
2887
+ if (!ENGINE_METHODS.has(method)) {
2888
+ return {
2889
+ error: {
2890
+ code: "UNKNOWN_METHOD",
2891
+ message: `Unknown method: ${method}`
2892
+ }
2893
+ };
2894
+ }
2895
+ try {
2896
+ const fn = engine2[method];
2897
+ const result = fn(params);
2898
+ return { result };
2899
+ } catch (err3) {
2900
+ const code = isDomainException(err3) ? err3.code : "INTERNAL_ERROR";
2901
+ const message = err3 instanceof Error ? err3.message : String(err3);
2902
+ return { error: { code, message } };
2903
+ }
2904
+ };
2905
+ var isDomainException = (err3) => {
2906
+ return err3 instanceof Error && "code" in err3 && typeof err3.code === "string";
2907
+ };
2908
+
2909
+ // src/wasm/bridge.ts
2910
+ var uuidFallback = () => "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
2911
+ const r = Math.random() * 16 | 0;
2912
+ return (c === "x" ? r : r & 3 | 8).toString(16);
2913
+ });
2914
+ var uuidMethod2 = typeof globalThis.crypto?.randomUUID === "function" ? () => globalThis.crypto.randomUUID() : uuidFallback;
2915
+ var engine = createGameEngine({ uuidMethod: uuidMethod2 });
2916
+ var chunks = [];
2917
+ var buf = new Uint8Array(4096);
2918
+ for (; ; ) {
2919
+ const bytesRead = Javy.IO.readSync(0, buf);
2920
+ if (bytesRead <= 0) break;
2921
+ chunks.push(buf.slice(0, bytesRead));
2922
+ }
2923
+ var decoder = new TextDecoder();
2924
+ var input = chunks.map((c) => decoder.decode(c)).join("");
2925
+ var request = JSON.parse(input);
2926
+ var response = dispatch(engine, request);
2927
+ var encoder = new TextEncoder();
2928
+ Javy.IO.writeSync(1, encoder.encode(JSON.stringify(response)));