@pompidup/kingdomino-engine 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -168,6 +168,25 @@ var wrapWithDebug = (engine, options) => {
168
168
  engine.getDynastyResults,
169
169
  logger,
170
170
  level
171
+ ),
172
+ placeKnight: wrapMethod(
173
+ "placeKnight",
174
+ engine.placeKnight,
175
+ logger,
176
+ level
177
+ ),
178
+ constructBuilding: wrapMethod(
179
+ "constructBuilding",
180
+ engine.constructBuilding,
181
+ logger,
182
+ level
183
+ ),
184
+ useDragon: wrapMethod("useDragon", engine.useDragon, logger, level),
185
+ skipOptionalAction: wrapMethod(
186
+ "skipOptionalAction",
187
+ engine.skipOptionalAction,
188
+ logger,
189
+ level
171
190
  )
172
191
  };
173
192
  };
@@ -232,7 +251,12 @@ var wrapWithEvents = (engine, callbacks) => ({
232
251
  callbacks.onGameEnd?.({ game });
233
252
  }
234
253
  return game;
235
- }
254
+ },
255
+ // Queendomino methods — pass through (no specific events yet)
256
+ placeKnight: (cmd) => engine.placeKnight(cmd),
257
+ constructBuilding: (cmd) => engine.constructBuilding(cmd),
258
+ useDragon: (cmd) => engine.useDragon(cmd),
259
+ skipOptionalAction: (cmd) => engine.skipOptionalAction(cmd)
236
260
  });
237
261
 
238
262
  // src/adapterServerside/consoleLogger.ts
@@ -261,6 +285,61 @@ var silentLogger = {
261
285
  }
262
286
  };
263
287
 
288
+ // src/datasources/buildings.json
289
+ var buildings_default = [
290
+ { id: 1, name: "The Bakery", cost: 1, immediateBonus: { type: "coins", amount: 2 }, crowns: 0, towers: 0 },
291
+ { id: 2, name: "The Bakery", cost: 1, immediateBonus: { type: "coins", amount: 2 }, crowns: 0, towers: 0 },
292
+ { id: 3, name: "The Sawmill", cost: 2, crowns: 0, towers: 0, endGameScoring: { type: "perTerrain", terrain: "forest", points: 1 } },
293
+ { id: 4, name: "The Fishmonger's", cost: 2, immediateBonus: { type: "coins", amount: 1 }, crowns: 0, towers: 0, endGameScoring: { type: "perTerrain", terrain: "sea", points: 1 } },
294
+ { id: 5, name: "The Fishmonger's", cost: 2, immediateBonus: { type: "coins", amount: 1 }, crowns: 0, towers: 0, endGameScoring: { type: "perTerrain", terrain: "sea", points: 1 } },
295
+ { id: 6, name: "The Weaving Shop", cost: 3, crowns: 1, towers: 0, endGameScoring: { type: "perTerrain", terrain: "wheat", points: 1 } },
296
+ { id: 7, name: "The Magic School", cost: 4, crowns: 1, towers: 0, endGameScoring: { type: "perCrown", points: 1 } },
297
+ { id: 8, name: "The Fountain", cost: 3, crowns: 0, towers: 1, endGameScoring: { type: "perTower", points: 2 } },
298
+ { id: 9, name: "The Church", cost: 4, crowns: 1, towers: 0, endGameScoring: { type: "perBuilding", points: 2 } },
299
+ { id: 10, name: "The Bath", cost: 2, immediateBonus: { type: "coins", amount: 1 }, crowns: 0, towers: 0, endGameScoring: { type: "flat", points: 4 } },
300
+ { id: 11, name: "The Guard Tower", cost: 1, crowns: 0, towers: 1, endGameScoring: { type: "flat", points: 2 } },
301
+ { id: 12, name: "The Guard Tower", cost: 1, crowns: 0, towers: 1, endGameScoring: { type: "flat", points: 2 } },
302
+ { id: 13, name: "The Big Tower", cost: 3, crowns: 0, towers: 2, endGameScoring: { type: "flat", points: 4 } },
303
+ { id: 14, name: "The Guardhouse", cost: 2, crowns: 0, towers: 1, endGameScoring: { type: "perBuilding", points: 1 } },
304
+ { id: 15, name: "The Fortified Castle", cost: 5, crowns: 2, towers: 1, endGameScoring: { type: "flat", points: 6 } },
305
+ { id: 16, name: "The Travelling Castle", cost: 3, crowns: 1, towers: 1, endGameScoring: { type: "perTower", points: 1 } },
306
+ { id: 17, name: "The Travelling Group", cost: 2, immediateBonus: { type: "coins", amount: 1 }, crowns: 0, towers: 0, endGameScoring: { type: "perBuilding", points: 1 } },
307
+ { id: 18, name: "The Harbour", cost: 3, crowns: 0, towers: 0, endGameScoring: { type: "perTerrain", terrain: "sea", points: 2 } }
308
+ ];
309
+
310
+ // src/adapterServerside/jsonBuildings.ts
311
+ var jsonBuildings = () => {
312
+ const buildings = buildings_default.map((b) => {
313
+ const tile = {
314
+ id: b.id,
315
+ name: b.name,
316
+ cost: b.cost,
317
+ crowns: b.crowns,
318
+ towers: b.towers
319
+ };
320
+ if (b.immediateBonus) {
321
+ tile.immediateBonus = {
322
+ type: b.immediateBonus.type,
323
+ amount: b.immediateBonus.amount
324
+ };
325
+ }
326
+ if ("endGameScoring" in b && b.endGameScoring) {
327
+ tile.endGameScoring = {
328
+ type: b.endGameScoring.type,
329
+ points: b.endGameScoring.points,
330
+ ..."terrain" in b.endGameScoring && {
331
+ terrain: b.endGameScoring.terrain
332
+ }
333
+ };
334
+ }
335
+ return tile;
336
+ });
337
+ return {
338
+ getAll: () => buildings
339
+ };
340
+ };
341
+ var jsonBuildings_default = jsonBuildings;
342
+
264
343
  // src/datasources/dominoes.json
265
344
  var dominoes_default = {
266
345
  Classic: [
@@ -314,84 +393,614 @@ var dominoes_default = {
314
393
  crowns: 0
315
394
  },
316
395
  right: {
317
- type: "forest",
396
+ type: "forest",
397
+ crowns: 0
398
+ },
399
+ number: 5
400
+ },
401
+ {
402
+ left: {
403
+ type: "forest",
404
+ crowns: 0
405
+ },
406
+ right: {
407
+ type: "forest",
408
+ crowns: 0
409
+ },
410
+ number: 6
411
+ },
412
+ {
413
+ left: {
414
+ type: "sea",
415
+ crowns: 0
416
+ },
417
+ right: {
418
+ type: "sea",
419
+ crowns: 0
420
+ },
421
+ number: 7
422
+ },
423
+ {
424
+ left: {
425
+ type: "sea",
426
+ crowns: 0
427
+ },
428
+ right: {
429
+ type: "sea",
430
+ crowns: 0
431
+ },
432
+ number: 8
433
+ },
434
+ {
435
+ left: {
436
+ type: "sea",
437
+ crowns: 0
438
+ },
439
+ right: {
440
+ type: "sea",
441
+ crowns: 0
442
+ },
443
+ number: 9
444
+ },
445
+ {
446
+ left: {
447
+ type: "plain",
448
+ crowns: 0
449
+ },
450
+ right: {
451
+ type: "plain",
452
+ crowns: 0
453
+ },
454
+ number: 10
455
+ },
456
+ {
457
+ left: {
458
+ type: "plain",
459
+ crowns: 0
460
+ },
461
+ right: {
462
+ type: "plain",
463
+ crowns: 0
464
+ },
465
+ number: 11
466
+ },
467
+ {
468
+ left: {
469
+ type: "swamp",
470
+ crowns: 0
471
+ },
472
+ right: {
473
+ type: "swamp",
474
+ crowns: 0
475
+ },
476
+ number: 12
477
+ },
478
+ {
479
+ left: {
480
+ type: "wheat",
481
+ crowns: 0
482
+ },
483
+ right: {
484
+ type: "forest",
485
+ crowns: 0
486
+ },
487
+ number: 13
488
+ },
489
+ {
490
+ left: {
491
+ type: "wheat",
492
+ crowns: 0
493
+ },
494
+ right: {
495
+ type: "sea",
496
+ crowns: 0
497
+ },
498
+ number: 14
499
+ },
500
+ {
501
+ left: {
502
+ type: "wheat",
503
+ crowns: 0
504
+ },
505
+ right: {
506
+ type: "plain",
507
+ crowns: 0
508
+ },
509
+ number: 15
510
+ },
511
+ {
512
+ left: {
513
+ type: "wheat",
514
+ crowns: 0
515
+ },
516
+ right: {
517
+ type: "swamp",
518
+ crowns: 0
519
+ },
520
+ number: 16
521
+ },
522
+ {
523
+ left: {
524
+ type: "forest",
525
+ crowns: 0
526
+ },
527
+ right: {
528
+ type: "sea",
529
+ crowns: 0
530
+ },
531
+ number: 17
532
+ },
533
+ {
534
+ left: {
535
+ type: "forest",
536
+ crowns: 0
537
+ },
538
+ right: {
539
+ type: "plain",
540
+ crowns: 0
541
+ },
542
+ number: 18
543
+ },
544
+ {
545
+ left: {
546
+ type: "wheat",
547
+ crowns: 1
548
+ },
549
+ right: {
550
+ type: "forest",
551
+ crowns: 0
552
+ },
553
+ number: 19
554
+ },
555
+ {
556
+ left: {
557
+ type: "wheat",
558
+ crowns: 1
559
+ },
560
+ right: {
561
+ type: "sea",
562
+ crowns: 0
563
+ },
564
+ number: 20
565
+ },
566
+ {
567
+ left: {
568
+ type: "wheat",
569
+ crowns: 1
570
+ },
571
+ right: {
572
+ type: "plain",
573
+ crowns: 0
574
+ },
575
+ number: 21
576
+ },
577
+ {
578
+ left: {
579
+ type: "wheat",
580
+ crowns: 1
581
+ },
582
+ right: {
583
+ type: "swamp",
584
+ crowns: 0
585
+ },
586
+ number: 22
587
+ },
588
+ {
589
+ left: {
590
+ type: "wheat",
591
+ crowns: 1
592
+ },
593
+ right: {
594
+ type: "mine",
595
+ crowns: 0
596
+ },
597
+ number: 23
598
+ },
599
+ {
600
+ left: {
601
+ type: "forest",
602
+ crowns: 1
603
+ },
604
+ right: {
605
+ type: "wheat",
606
+ crowns: 0
607
+ },
608
+ number: 24
609
+ },
610
+ {
611
+ left: {
612
+ type: "forest",
613
+ crowns: 1
614
+ },
615
+ right: {
616
+ type: "wheat",
617
+ crowns: 0
618
+ },
619
+ number: 25
620
+ },
621
+ {
622
+ left: {
623
+ type: "forest",
624
+ crowns: 1
625
+ },
626
+ right: {
627
+ type: "wheat",
628
+ crowns: 0
629
+ },
630
+ number: 26
631
+ },
632
+ {
633
+ left: {
634
+ type: "forest",
635
+ crowns: 1
636
+ },
637
+ right: {
638
+ type: "wheat",
639
+ crowns: 0
640
+ },
641
+ number: 27
642
+ },
643
+ {
644
+ left: {
645
+ type: "forest",
646
+ crowns: 1
647
+ },
648
+ right: {
649
+ type: "sea",
650
+ crowns: 0
651
+ },
652
+ number: 28
653
+ },
654
+ {
655
+ left: {
656
+ type: "forest",
657
+ crowns: 1
658
+ },
659
+ right: {
660
+ type: "plain",
661
+ crowns: 0
662
+ },
663
+ number: 29
664
+ },
665
+ {
666
+ left: {
667
+ type: "sea",
668
+ crowns: 1
669
+ },
670
+ right: {
671
+ type: "wheat",
672
+ crowns: 0
673
+ },
674
+ number: 30
675
+ },
676
+ {
677
+ left: {
678
+ type: "sea",
679
+ crowns: 1
680
+ },
681
+ right: {
682
+ type: "wheat",
683
+ crowns: 0
684
+ },
685
+ number: 31
686
+ },
687
+ {
688
+ left: {
689
+ type: "sea",
690
+ crowns: 1
691
+ },
692
+ right: {
693
+ type: "forest",
694
+ crowns: 0
695
+ },
696
+ number: 32
697
+ },
698
+ {
699
+ left: {
700
+ type: "sea",
701
+ crowns: 1
702
+ },
703
+ right: {
704
+ type: "forest",
705
+ crowns: 0
706
+ },
707
+ number: 33
708
+ },
709
+ {
710
+ left: {
711
+ type: "sea",
712
+ crowns: 1
713
+ },
714
+ right: {
715
+ type: "forest",
716
+ crowns: 0
717
+ },
718
+ number: 34
719
+ },
720
+ {
721
+ left: {
722
+ type: "sea",
723
+ crowns: 1
724
+ },
725
+ right: {
726
+ type: "forest",
727
+ crowns: 0
728
+ },
729
+ number: 35
730
+ },
731
+ {
732
+ left: {
733
+ type: "wheat",
734
+ crowns: 0
735
+ },
736
+ right: {
737
+ type: "plain",
738
+ crowns: 1
739
+ },
740
+ number: 36
741
+ },
742
+ {
743
+ left: {
744
+ type: "sea",
745
+ crowns: 0
746
+ },
747
+ right: {
748
+ type: "plain",
749
+ crowns: 1
750
+ },
751
+ number: 37
752
+ },
753
+ {
754
+ left: {
755
+ type: "wheat",
756
+ crowns: 0
757
+ },
758
+ right: {
759
+ type: "swamp",
760
+ crowns: 1
761
+ },
762
+ number: 38
763
+ },
764
+ {
765
+ left: {
766
+ type: "plain",
767
+ crowns: 0
768
+ },
769
+ right: {
770
+ type: "swamp",
771
+ crowns: 0
772
+ },
773
+ number: 39
774
+ },
775
+ {
776
+ left: {
777
+ type: "mine",
778
+ crowns: 1
779
+ },
780
+ right: {
781
+ type: "wheat",
782
+ crowns: 0
783
+ },
784
+ number: 40
785
+ },
786
+ {
787
+ left: {
788
+ type: "wheat",
789
+ crowns: 0
790
+ },
791
+ right: {
792
+ type: "plain",
793
+ crowns: 2
794
+ },
795
+ number: 41
796
+ },
797
+ {
798
+ left: {
799
+ type: "sea",
800
+ crowns: 0
801
+ },
802
+ right: {
803
+ type: "plain",
804
+ crowns: 2
805
+ },
806
+ number: 42
807
+ },
808
+ {
809
+ left: {
810
+ type: "wheat",
811
+ crowns: 0
812
+ },
813
+ right: {
814
+ type: "swamp",
815
+ crowns: 2
816
+ },
817
+ number: 43
818
+ },
819
+ {
820
+ left: {
821
+ type: "plain",
822
+ crowns: 0
823
+ },
824
+ right: {
825
+ type: "swamp",
826
+ crowns: 2
827
+ },
828
+ number: 44
829
+ },
830
+ {
831
+ left: {
832
+ type: "mine",
833
+ crowns: 2
834
+ },
835
+ right: {
836
+ type: "wheat",
837
+ crowns: 0
838
+ },
839
+ number: 45
840
+ },
841
+ {
842
+ left: {
843
+ type: "swamp",
844
+ crowns: 0
845
+ },
846
+ right: {
847
+ type: "mine",
848
+ crowns: 2
849
+ },
850
+ number: 46
851
+ },
852
+ {
853
+ left: {
854
+ type: "swamp",
855
+ crowns: 0
856
+ },
857
+ right: {
858
+ type: "mine",
859
+ crowns: 2
860
+ },
861
+ number: 47
862
+ },
863
+ {
864
+ left: {
865
+ type: "wheat",
866
+ crowns: 0
867
+ },
868
+ right: {
869
+ type: "mine",
870
+ crowns: 3
871
+ },
872
+ number: 48
873
+ }
874
+ ],
875
+ QueenDomino: [
876
+ {
877
+ left: {
878
+ type: "wheat",
879
+ crowns: 0
880
+ },
881
+ right: {
882
+ type: "wheat",
883
+ crowns: 0
884
+ },
885
+ number: 1
886
+ },
887
+ {
888
+ left: {
889
+ type: "wheat",
890
+ crowns: 0
891
+ },
892
+ right: {
893
+ type: "wheat",
894
+ crowns: 0
895
+ },
896
+ number: 2
897
+ },
898
+ {
899
+ left: {
900
+ type: "forest",
901
+ crowns: 0
902
+ },
903
+ right: {
904
+ type: "forest",
905
+ crowns: 0
906
+ },
907
+ number: 3
908
+ },
909
+ {
910
+ left: {
911
+ type: "forest",
912
+ crowns: 0
913
+ },
914
+ right: {
915
+ type: "forest",
916
+ crowns: 0
917
+ },
918
+ number: 4
919
+ },
920
+ {
921
+ left: {
922
+ type: "sea",
923
+ crowns: 0
924
+ },
925
+ right: {
926
+ type: "sea",
318
927
  crowns: 0
319
928
  },
320
929
  number: 5
321
930
  },
322
931
  {
323
932
  left: {
324
- type: "forest",
933
+ type: "sea",
325
934
  crowns: 0
326
935
  },
327
936
  right: {
328
- type: "forest",
937
+ type: "sea",
329
938
  crowns: 0
330
939
  },
331
940
  number: 6
332
941
  },
333
942
  {
334
943
  left: {
335
- type: "sea",
944
+ type: "plain",
336
945
  crowns: 0
337
946
  },
338
947
  right: {
339
- type: "sea",
948
+ type: "plain",
340
949
  crowns: 0
341
950
  },
342
951
  number: 7
343
952
  },
344
953
  {
345
954
  left: {
346
- type: "sea",
955
+ type: "plain",
347
956
  crowns: 0
348
957
  },
349
958
  right: {
350
- type: "sea",
959
+ type: "plain",
351
960
  crowns: 0
352
961
  },
353
962
  number: 8
354
963
  },
355
964
  {
356
965
  left: {
357
- type: "sea",
966
+ type: "swamp",
358
967
  crowns: 0
359
968
  },
360
969
  right: {
361
- type: "sea",
970
+ type: "swamp",
362
971
  crowns: 0
363
972
  },
364
973
  number: 9
365
974
  },
366
975
  {
367
976
  left: {
368
- type: "plain",
977
+ type: "swamp",
369
978
  crowns: 0
370
979
  },
371
980
  right: {
372
- type: "plain",
981
+ type: "swamp",
373
982
  crowns: 0
374
983
  },
375
984
  number: 10
376
985
  },
377
986
  {
378
987
  left: {
379
- type: "plain",
988
+ type: "mine",
380
989
  crowns: 0
381
990
  },
382
991
  right: {
383
- type: "plain",
992
+ type: "mine",
384
993
  crowns: 0
385
994
  },
386
995
  number: 11
387
996
  },
388
997
  {
389
998
  left: {
390
- type: "swamp",
999
+ type: "wheat",
391
1000
  crowns: 0
392
1001
  },
393
1002
  right: {
394
- type: "swamp",
1003
+ type: "forest",
395
1004
  crowns: 0
396
1005
  },
397
1006
  number: 12
@@ -399,10 +1008,11 @@ var dominoes_default = {
399
1008
  {
400
1009
  left: {
401
1010
  type: "wheat",
402
- crowns: 0
1011
+ crowns: 0,
1012
+ hasConstructionSquare: true
403
1013
  },
404
1014
  right: {
405
- type: "forest",
1015
+ type: "sea",
406
1016
  crowns: 0
407
1017
  },
408
1018
  number: 13
@@ -413,51 +1023,54 @@ var dominoes_default = {
413
1023
  crowns: 0
414
1024
  },
415
1025
  right: {
416
- type: "sea",
417
- crowns: 0
1026
+ type: "plain",
1027
+ crowns: 0,
1028
+ hasConstructionSquare: true
418
1029
  },
419
1030
  number: 14
420
1031
  },
421
1032
  {
422
1033
  left: {
423
- type: "wheat",
424
- crowns: 0
1034
+ type: "forest",
1035
+ crowns: 0,
1036
+ hasConstructionSquare: true
425
1037
  },
426
1038
  right: {
427
- type: "plain",
1039
+ type: "sea",
428
1040
  crowns: 0
429
1041
  },
430
1042
  number: 15
431
1043
  },
432
1044
  {
433
1045
  left: {
434
- type: "wheat",
1046
+ type: "forest",
435
1047
  crowns: 0
436
1048
  },
437
1049
  right: {
438
- type: "swamp",
1050
+ type: "plain",
439
1051
  crowns: 0
440
1052
  },
441
1053
  number: 16
442
1054
  },
443
1055
  {
444
1056
  left: {
445
- type: "forest",
1057
+ type: "sea",
446
1058
  crowns: 0
447
1059
  },
448
1060
  right: {
449
- type: "sea",
450
- crowns: 0
1061
+ type: "plain",
1062
+ crowns: 0,
1063
+ hasConstructionSquare: true
451
1064
  },
452
1065
  number: 17
453
1066
  },
454
1067
  {
455
1068
  left: {
456
- type: "forest",
1069
+ type: "wheat",
457
1070
  crowns: 0
458
1071
  },
459
1072
  right: {
460
- type: "plain",
1073
+ type: "swamp",
461
1074
  crowns: 0
462
1075
  },
463
1076
  number: 18
@@ -465,7 +1078,8 @@ var dominoes_default = {
465
1078
  {
466
1079
  left: {
467
1080
  type: "wheat",
468
- crowns: 1
1081
+ crowns: 1,
1082
+ hasConstructionSquare: true
469
1083
  },
470
1084
  right: {
471
1085
  type: "forest",
@@ -491,28 +1105,30 @@ var dominoes_default = {
491
1105
  },
492
1106
  right: {
493
1107
  type: "plain",
494
- crowns: 0
1108
+ crowns: 0,
1109
+ hasConstructionSquare: true
495
1110
  },
496
1111
  number: 21
497
1112
  },
498
1113
  {
499
1114
  left: {
500
- type: "wheat",
1115
+ type: "forest",
501
1116
  crowns: 1
502
1117
  },
503
1118
  right: {
504
- type: "swamp",
505
- crowns: 0
1119
+ type: "wheat",
1120
+ crowns: 0,
1121
+ hasConstructionSquare: true
506
1122
  },
507
1123
  number: 22
508
1124
  },
509
1125
  {
510
1126
  left: {
511
- type: "wheat",
1127
+ type: "forest",
512
1128
  crowns: 1
513
1129
  },
514
1130
  right: {
515
- type: "mine",
1131
+ type: "sea",
516
1132
  crowns: 0
517
1133
  },
518
1134
  number: 23
@@ -520,17 +1136,18 @@ var dominoes_default = {
520
1136
  {
521
1137
  left: {
522
1138
  type: "forest",
523
- crowns: 1
1139
+ crowns: 1,
1140
+ hasConstructionSquare: true
524
1141
  },
525
1142
  right: {
526
- type: "wheat",
1143
+ type: "plain",
527
1144
  crowns: 0
528
1145
  },
529
1146
  number: 24
530
1147
  },
531
1148
  {
532
1149
  left: {
533
- type: "forest",
1150
+ type: "sea",
534
1151
  crowns: 1
535
1152
  },
536
1153
  right: {
@@ -541,18 +1158,19 @@ var dominoes_default = {
541
1158
  },
542
1159
  {
543
1160
  left: {
544
- type: "forest",
1161
+ type: "sea",
545
1162
  crowns: 1
546
1163
  },
547
1164
  right: {
548
- type: "wheat",
549
- crowns: 0
1165
+ type: "forest",
1166
+ crowns: 0,
1167
+ hasConstructionSquare: true
550
1168
  },
551
1169
  number: 26
552
1170
  },
553
1171
  {
554
1172
  left: {
555
- type: "forest",
1173
+ type: "plain",
556
1174
  crowns: 1
557
1175
  },
558
1176
  right: {
@@ -563,22 +1181,23 @@ var dominoes_default = {
563
1181
  },
564
1182
  {
565
1183
  left: {
566
- type: "forest",
567
- crowns: 1
1184
+ type: "plain",
1185
+ crowns: 1,
1186
+ hasConstructionSquare: true
568
1187
  },
569
1188
  right: {
570
- type: "sea",
1189
+ type: "forest",
571
1190
  crowns: 0
572
1191
  },
573
1192
  number: 28
574
1193
  },
575
1194
  {
576
1195
  left: {
577
- type: "forest",
1196
+ type: "wheat",
578
1197
  crowns: 1
579
1198
  },
580
1199
  right: {
581
- type: "plain",
1200
+ type: "mine",
582
1201
  crowns: 0
583
1202
  },
584
1203
  number: 29
@@ -589,30 +1208,33 @@ var dominoes_default = {
589
1208
  crowns: 1
590
1209
  },
591
1210
  right: {
592
- type: "wheat",
593
- crowns: 0
1211
+ type: "plain",
1212
+ crowns: 0,
1213
+ hasConstructionSquare: true
594
1214
  },
595
1215
  number: 30
596
1216
  },
597
1217
  {
598
1218
  left: {
599
- type: "sea",
600
- crowns: 1
1219
+ type: "wheat",
1220
+ crowns: 1,
1221
+ hasConstructionSquare: true
601
1222
  },
602
1223
  right: {
603
- type: "wheat",
604
- crowns: 0
1224
+ type: "swamp",
1225
+ crowns: 1
605
1226
  },
606
1227
  number: 31
607
1228
  },
608
1229
  {
609
1230
  left: {
610
- type: "sea",
1231
+ type: "forest",
611
1232
  crowns: 1
612
1233
  },
613
1234
  right: {
614
- type: "forest",
615
- crowns: 0
1235
+ type: "sea",
1236
+ crowns: 1,
1237
+ hasConstructionSquare: true
616
1238
  },
617
1239
  number: 32
618
1240
  },
@@ -622,73 +1244,77 @@ var dominoes_default = {
622
1244
  crowns: 1
623
1245
  },
624
1246
  right: {
625
- type: "forest",
626
- crowns: 0
1247
+ type: "plain",
1248
+ crowns: 1
627
1249
  },
628
1250
  number: 33
629
1251
  },
630
1252
  {
631
1253
  left: {
632
- type: "sea",
633
- crowns: 1
1254
+ type: "plain",
1255
+ crowns: 1,
1256
+ hasConstructionSquare: true
634
1257
  },
635
1258
  right: {
636
- type: "forest",
637
- crowns: 0
1259
+ type: "swamp",
1260
+ crowns: 1
638
1261
  },
639
1262
  number: 34
640
1263
  },
641
1264
  {
642
1265
  left: {
643
- type: "sea",
644
- crowns: 1
1266
+ type: "forest",
1267
+ crowns: 1,
1268
+ hasConstructionSquare: true
645
1269
  },
646
1270
  right: {
647
- type: "forest",
648
- crowns: 0
1271
+ type: "mine",
1272
+ crowns: 1
649
1273
  },
650
1274
  number: 35
651
1275
  },
652
1276
  {
653
1277
  left: {
654
1278
  type: "wheat",
655
- crowns: 0
1279
+ crowns: 2,
1280
+ hasConstructionSquare: true
656
1281
  },
657
1282
  right: {
658
- type: "plain",
659
- crowns: 1
1283
+ type: "forest",
1284
+ crowns: 0
660
1285
  },
661
1286
  number: 36
662
1287
  },
663
1288
  {
664
1289
  left: {
665
1290
  type: "sea",
666
- crowns: 0
1291
+ crowns: 2
667
1292
  },
668
1293
  right: {
669
- type: "plain",
670
- crowns: 1
1294
+ type: "wheat",
1295
+ crowns: 0,
1296
+ hasConstructionSquare: true
671
1297
  },
672
1298
  number: 37
673
1299
  },
674
1300
  {
675
1301
  left: {
676
- type: "wheat",
677
- crowns: 0
1302
+ type: "plain",
1303
+ crowns: 2
678
1304
  },
679
1305
  right: {
680
- type: "swamp",
681
- crowns: 1
1306
+ type: "forest",
1307
+ crowns: 0
682
1308
  },
683
1309
  number: 38
684
1310
  },
685
1311
  {
686
1312
  left: {
687
- type: "plain",
688
- crowns: 0
1313
+ type: "swamp",
1314
+ crowns: 2
689
1315
  },
690
1316
  right: {
691
- type: "swamp",
1317
+ type: "wheat",
692
1318
  crowns: 0
693
1319
  },
694
1320
  number: 39
@@ -696,7 +1322,8 @@ var dominoes_default = {
696
1322
  {
697
1323
  left: {
698
1324
  type: "mine",
699
- crowns: 1
1325
+ crowns: 2,
1326
+ hasConstructionSquare: true
700
1327
  },
701
1328
  right: {
702
1329
  type: "wheat",
@@ -707,7 +1334,7 @@ var dominoes_default = {
707
1334
  {
708
1335
  left: {
709
1336
  type: "wheat",
710
- crowns: 0
1337
+ crowns: 2
711
1338
  },
712
1339
  right: {
713
1340
  type: "plain",
@@ -718,73 +1345,75 @@ var dominoes_default = {
718
1345
  {
719
1346
  left: {
720
1347
  type: "sea",
721
- crowns: 0
1348
+ crowns: 2
722
1349
  },
723
1350
  right: {
724
- type: "plain",
725
- crowns: 2
1351
+ type: "forest",
1352
+ crowns: 1,
1353
+ hasConstructionSquare: true
726
1354
  },
727
1355
  number: 42
728
1356
  },
729
1357
  {
730
1358
  left: {
731
- type: "wheat",
732
- crowns: 0
733
- },
734
- right: {
735
1359
  type: "swamp",
736
1360
  crowns: 2
737
1361
  },
1362
+ right: {
1363
+ type: "plain",
1364
+ crowns: 1
1365
+ },
738
1366
  number: 43
739
1367
  },
740
1368
  {
741
1369
  left: {
742
- type: "plain",
743
- crowns: 0
1370
+ type: "mine",
1371
+ crowns: 2
744
1372
  },
745
1373
  right: {
746
1374
  type: "swamp",
747
- crowns: 2
1375
+ crowns: 1
748
1376
  },
749
1377
  number: 44
750
1378
  },
751
1379
  {
752
1380
  left: {
753
- type: "mine",
1381
+ type: "wheat",
754
1382
  crowns: 2
755
1383
  },
756
1384
  right: {
757
- type: "wheat",
758
- crowns: 0
1385
+ type: "mine",
1386
+ crowns: 2
759
1387
  },
760
1388
  number: 45
761
1389
  },
762
1390
  {
763
1391
  left: {
764
- type: "swamp",
765
- crowns: 0
1392
+ type: "forest",
1393
+ crowns: 2
766
1394
  },
767
1395
  right: {
768
1396
  type: "mine",
769
- crowns: 2
1397
+ crowns: 2,
1398
+ hasConstructionSquare: true
770
1399
  },
771
1400
  number: 46
772
1401
  },
773
1402
  {
774
1403
  left: {
775
1404
  type: "swamp",
776
- crowns: 0
1405
+ crowns: 2
777
1406
  },
778
1407
  right: {
779
1408
  type: "mine",
780
- crowns: 2
1409
+ crowns: 3
781
1410
  },
782
1411
  number: 47
783
1412
  },
784
1413
  {
785
1414
  left: {
786
- type: "wheat",
787
- crowns: 0
1415
+ type: "plain",
1416
+ crowns: 2
788
1417
  },
789
1418
  right: {
790
1419
  type: "mine",
@@ -796,20 +1425,24 @@ var dominoes_default = {
796
1425
  };
797
1426
 
798
1427
  // src/adapterServerside/jsonDominoes.ts
1428
+ var mapTile = (raw) => {
1429
+ const tile = {
1430
+ type: raw.type,
1431
+ crowns: raw.crowns
1432
+ };
1433
+ if (raw.hasConstructionSquare) {
1434
+ tile.hasConstructionSquare = true;
1435
+ }
1436
+ return tile;
1437
+ };
799
1438
  var jsonDominoes = () => {
800
1439
  const dominoes = {};
801
1440
  for (const [key, value] of Object.entries(dominoes_default)) {
802
1441
  const currentDominoes = [];
803
1442
  value.forEach((domino) => {
804
1443
  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
- },
1444
+ left: mapTile(domino.left),
1445
+ right: mapTile(domino.right),
813
1446
  number: domino.number
814
1447
  });
815
1448
  });
@@ -828,6 +1461,10 @@ var modes_default = [
828
1461
  {
829
1462
  name: "Classic",
830
1463
  description: "Original KingDomino rules"
1464
+ },
1465
+ {
1466
+ name: "QueenDomino",
1467
+ description: "Queendomino expansion with buildings, knights, coins, the Queen and the Dragon"
831
1468
  }
832
1469
  ];
833
1470
 
@@ -888,23 +1525,35 @@ var rules_default = {
888
1525
  {
889
1526
  name: "The middle Kingdom",
890
1527
  description: "Gain 10 additional points if your castle is in the middle of the kingdom.",
891
- mode: [{ name: "Classic", description: "King Domino classic mode" }]
1528
+ mode: [
1529
+ { name: "Classic", description: "King Domino classic mode" },
1530
+ { name: "QueenDomino", description: "Queendomino expansion with buildings, knights, coins, the Queen and the Dragon" }
1531
+ ]
892
1532
  },
893
1533
  {
894
1534
  name: "Harmony",
895
1535
  description: "Gain 5 additional points if your kingdom is complete (no discarded dominoes).",
896
- mode: [{ name: "Classic", description: "King Domino classic mode" }]
1536
+ mode: [
1537
+ { name: "Classic", description: "King Domino classic mode" },
1538
+ { name: "QueenDomino", description: "Queendomino expansion with buildings, knights, coins, the Queen and the Dragon" }
1539
+ ]
897
1540
  },
898
1541
  {
899
1542
  name: "The Mighty Duel",
900
1543
  description: "Use all 48 dominoes and build a 7x7 kingdom. For 2 players only.",
901
- mode: [{ name: "Classic", description: "King Domino classic mode" }],
1544
+ mode: [
1545
+ { name: "Classic", description: "King Domino classic mode" },
1546
+ { name: "QueenDomino", description: "Queendomino expansion with buildings, knights, coins, the Queen and the Dragon" }
1547
+ ],
902
1548
  playersLimit: 2
903
1549
  },
904
1550
  {
905
1551
  name: "Dynasty",
906
1552
  description: "Play 3 games in a row. The player with the highest total points wins.",
907
- mode: [{ name: "Classic", description: "King Domino classic mode" }]
1553
+ mode: [
1554
+ { name: "Classic", description: "King Domino classic mode" },
1555
+ { name: "QueenDomino", description: "Queendomino expansion with buildings, knights, coins, the Queen and the Dragon" }
1556
+ ]
908
1557
  }
909
1558
  ]
910
1559
  };
@@ -955,9 +1604,8 @@ var shuffleMethod = (array) => {
955
1604
  };
956
1605
 
957
1606
  // src/adapterServerside/uuid.ts
958
- import { randomUUID } from "crypto";
959
1607
  var uuidMethod = () => {
960
- return randomUUID();
1608
+ return globalThis.crypto.randomUUID();
961
1609
  };
962
1610
 
963
1611
  // src/core/domain/errors/domainErrors.ts
@@ -987,7 +1635,17 @@ var ErrorCode = {
987
1635
  DOMINO_ALREADY_PICKED: "DOMINO_ALREADY_PICKED",
988
1636
  // History errors
989
1637
  UNDO_UNAVAILABLE: "UNDO_UNAVAILABLE",
990
- REDO_UNAVAILABLE: "REDO_UNAVAILABLE"
1638
+ REDO_UNAVAILABLE: "REDO_UNAVAILABLE",
1639
+ // Queendomino errors
1640
+ CANNOT_PLACE_KNIGHT: "CANNOT_PLACE_KNIGHT",
1641
+ MAX_KNIGHTS_REACHED: "MAX_KNIGHTS_REACHED",
1642
+ KNIGHT_TERRITORY_OCCUPIED: "KNIGHT_TERRITORY_OCCUPIED",
1643
+ CANNOT_AFFORD_BUILDING: "CANNOT_AFFORD_BUILDING",
1644
+ NO_CONSTRUCTION_SQUARE: "NO_CONSTRUCTION_SQUARE",
1645
+ DRAGON_UNAVAILABLE: "DRAGON_UNAVAILABLE",
1646
+ QUEEN_HOLDER_CANNOT_USE_DRAGON: "QUEEN_HOLDER_CANNOT_USE_DRAGON",
1647
+ BUILDING_NOT_FOUND: "BUILDING_NOT_FOUND",
1648
+ INVALID_OPTIONAL_ACTION: "INVALID_OPTIONAL_ACTION"
991
1649
  };
992
1650
  var createDomainError = (code, message, context) => ({
993
1651
  code,
@@ -1120,7 +1778,17 @@ var TranslationKey = {
1120
1778
  // Actions
1121
1779
  ACTION_PICK_DOMINO: "action.pickDomino",
1122
1780
  ACTION_PLACE_DOMINO: "action.placeDomino",
1123
- ACTION_PASS: "action.pass"
1781
+ ACTION_PASS: "action.pass",
1782
+ // Queendomino errors
1783
+ ERROR_CANNOT_PLACE_KNIGHT: "error.cannotPlaceKnight",
1784
+ ERROR_MAX_KNIGHTS_REACHED: "error.maxKnightsReached",
1785
+ ERROR_KNIGHT_TERRITORY_OCCUPIED: "error.knightTerritoryOccupied",
1786
+ ERROR_CANNOT_AFFORD_BUILDING: "error.cannotAffordBuilding",
1787
+ ERROR_NO_CONSTRUCTION_SQUARE: "error.noConstructionSquare",
1788
+ ERROR_DRAGON_UNAVAILABLE: "error.dragonUnavailable",
1789
+ ERROR_QUEEN_HOLDER_CANNOT_USE_DRAGON: "error.queenHolderCannotUseDragon",
1790
+ ERROR_BUILDING_NOT_FOUND: "error.buildingNotFound",
1791
+ ERROR_INVALID_OPTIONAL_ACTION: "error.invalidOptionalAction"
1124
1792
  };
1125
1793
  var defaultTranslations = {
1126
1794
  // Error messages
@@ -1166,7 +1834,17 @@ var defaultTranslations = {
1166
1834
  // Actions
1167
1835
  [TranslationKey.ACTION_PICK_DOMINO]: "Pick Domino",
1168
1836
  [TranslationKey.ACTION_PLACE_DOMINO]: "Place Domino",
1169
- [TranslationKey.ACTION_PASS]: "Pass"
1837
+ [TranslationKey.ACTION_PASS]: "Pass",
1838
+ // Queendomino errors
1839
+ [TranslationKey.ERROR_CANNOT_PLACE_KNIGHT]: "Cannot place knight here",
1840
+ [TranslationKey.ERROR_MAX_KNIGHTS_REACHED]: "Maximum number of knights reached (3)",
1841
+ [TranslationKey.ERROR_KNIGHT_TERRITORY_OCCUPIED]: "This territory already has a knight",
1842
+ [TranslationKey.ERROR_CANNOT_AFFORD_BUILDING]: "Not enough coins to purchase this building",
1843
+ [TranslationKey.ERROR_NO_CONSTRUCTION_SQUARE]: "No open construction square available",
1844
+ [TranslationKey.ERROR_DRAGON_UNAVAILABLE]: "The Dragon is not available this round",
1845
+ [TranslationKey.ERROR_QUEEN_HOLDER_CANNOT_USE_DRAGON]: "The Queen holder cannot use the Dragon",
1846
+ [TranslationKey.ERROR_BUILDING_NOT_FOUND]: "Building not found on the Builders Board",
1847
+ [TranslationKey.ERROR_INVALID_OPTIONAL_ACTION]: "Invalid optional action for this game state"
1170
1848
  };
1171
1849
  var errorCodeToTranslationKey = {
1172
1850
  [ErrorCode.INVALID_STEP]: TranslationKey.ERROR_INVALID_STEP,
@@ -1189,7 +1867,16 @@ var errorCodeToTranslationKey = {
1189
1867
  [ErrorCode.CANNOT_PLACE]: TranslationKey.ERROR_CANNOT_PLACE,
1190
1868
  [ErrorCode.DOMINO_ALREADY_PICKED]: TranslationKey.ERROR_DOMINO_ALREADY_PICKED,
1191
1869
  [ErrorCode.UNDO_UNAVAILABLE]: TranslationKey.ERROR_UNDO_UNAVAILABLE,
1192
- [ErrorCode.REDO_UNAVAILABLE]: TranslationKey.ERROR_REDO_UNAVAILABLE
1870
+ [ErrorCode.REDO_UNAVAILABLE]: TranslationKey.ERROR_REDO_UNAVAILABLE,
1871
+ [ErrorCode.CANNOT_PLACE_KNIGHT]: TranslationKey.ERROR_CANNOT_PLACE_KNIGHT,
1872
+ [ErrorCode.MAX_KNIGHTS_REACHED]: TranslationKey.ERROR_MAX_KNIGHTS_REACHED,
1873
+ [ErrorCode.KNIGHT_TERRITORY_OCCUPIED]: TranslationKey.ERROR_KNIGHT_TERRITORY_OCCUPIED,
1874
+ [ErrorCode.CANNOT_AFFORD_BUILDING]: TranslationKey.ERROR_CANNOT_AFFORD_BUILDING,
1875
+ [ErrorCode.NO_CONSTRUCTION_SQUARE]: TranslationKey.ERROR_NO_CONSTRUCTION_SQUARE,
1876
+ [ErrorCode.DRAGON_UNAVAILABLE]: TranslationKey.ERROR_DRAGON_UNAVAILABLE,
1877
+ [ErrorCode.QUEEN_HOLDER_CANNOT_USE_DRAGON]: TranslationKey.ERROR_QUEEN_HOLDER_CANNOT_USE_DRAGON,
1878
+ [ErrorCode.BUILDING_NOT_FOUND]: TranslationKey.ERROR_BUILDING_NOT_FOUND,
1879
+ [ErrorCode.INVALID_OPTIONAL_ACTION]: TranslationKey.ERROR_INVALID_OPTIONAL_ACTION
1193
1880
  };
1194
1881
  var translateErrorCode = (translator, code, params) => {
1195
1882
  const key = errorCodeToTranslationKey[code];
@@ -1679,47 +2366,191 @@ var deserializeGameHandler = (logger, translator) => {
1679
2366
  };
1680
2367
  };
1681
2368
 
1682
- // src/application/handlers/startGameHandler.ts
1683
- var startGameHandler = (logger, translator, useCase) => (command) => {
1684
- const { game } = command;
1685
- logger.info(`Starting game: ${game.id}`);
1686
- if (!isGameWithNextStep(game)) {
2369
+ // src/application/handlers/startGameHandler.ts
2370
+ var startGameHandler = (logger, translator, useCase) => (command) => {
2371
+ const { game } = command;
2372
+ logger.info(`Starting game: ${game.id}`);
2373
+ if (!isGameWithNextStep(game)) {
2374
+ logger.error(
2375
+ `Invalid game, required nextAction type: 'step' but got: ${game.nextAction.type}`
2376
+ );
2377
+ throw new InvalidStepError(
2378
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
2379
+ { expected: "step", actual: game.nextAction.type, gameId: game.id }
2380
+ );
2381
+ }
2382
+ if (game.nextAction.step !== "start" && game.nextAction.step !== "options") {
2383
+ logger.error(
2384
+ `Required game with start step or options step but got: ${game.nextAction.step}`
2385
+ );
2386
+ throw new InvalidStepError(
2387
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
2388
+ {
2389
+ expected: "start|options",
2390
+ actual: game.nextAction.step,
2391
+ gameId: game.id
2392
+ }
2393
+ );
2394
+ }
2395
+ const result = useCase(game);
2396
+ if (isErr(result)) {
2397
+ logger.error(`Error starting game: ${result.error}`);
2398
+ const code = result.error;
2399
+ throw new StepExecutionError(translateErrorCode(translator, code), code, {
2400
+ gameId: game.id
2401
+ });
2402
+ }
2403
+ logger.info(`Game started: ${game.id}`);
2404
+ return result.value;
2405
+ };
2406
+
2407
+ // src/application/handlers/placeKnightHandler.ts
2408
+ var placeKnightHandler = (logger, translator, useCase) => (command) => {
2409
+ const { game, lordId, position } = command;
2410
+ logger.info(
2411
+ `Placing knight for lord: ${lordId} at position: ${JSON.stringify(
2412
+ position
2413
+ )} in game: ${game.id}`
2414
+ );
2415
+ if (game.nextAction.nextAction !== "placeKnight") {
2416
+ logger.error(
2417
+ `Required game with placeKnight action but got: ${game.nextAction.nextAction}`
2418
+ );
2419
+ throw new InvalidStepError(
2420
+ translateErrorCode(translator, ErrorCode.INVALID_STEP),
2421
+ {
2422
+ expected: "placeKnight",
2423
+ actual: game.nextAction.nextAction,
2424
+ gameId: game.id
2425
+ }
2426
+ );
2427
+ }
2428
+ const result = useCase(game, lordId, position);
2429
+ if (isErr(result)) {
2430
+ logger.error(`Error placing knight: ${result.error}`);
2431
+ const code = result.error;
2432
+ throw new ActionExecutionError(
2433
+ translateErrorCode(translator, code),
2434
+ code,
2435
+ {
2436
+ gameId: game.id,
2437
+ lordId: command.lordId,
2438
+ position: command.position
2439
+ }
2440
+ );
2441
+ }
2442
+ logger.info(
2443
+ `Knight placed for lord: ${lordId} at position: ${JSON.stringify(
2444
+ position
2445
+ )} in game: ${game.id}`
2446
+ );
2447
+ return result.value;
2448
+ };
2449
+
2450
+ // src/application/handlers/constructBuildingHandler.ts
2451
+ var constructBuildingHandler = (logger, translator, useCase) => (command) => {
2452
+ const { game, lordId, buildingId, position } = command;
2453
+ logger.info(
2454
+ `Constructing building: ${buildingId} for lord: ${lordId} at position: ${JSON.stringify(
2455
+ position
2456
+ )} in game: ${game.id}`
2457
+ );
2458
+ if (game.nextAction.nextAction !== "constructBuilding") {
1687
2459
  logger.error(
1688
- `Invalid game, required nextAction type: 'step' but got: ${game.nextAction.type}`
2460
+ `Required game with constructBuilding action but got: ${game.nextAction.nextAction}`
1689
2461
  );
1690
2462
  throw new InvalidStepError(
1691
2463
  translateErrorCode(translator, ErrorCode.INVALID_STEP),
1692
- { expected: "step", actual: game.nextAction.type, gameId: game.id }
2464
+ {
2465
+ expected: "constructBuilding",
2466
+ actual: game.nextAction.nextAction,
2467
+ gameId: game.id
2468
+ }
1693
2469
  );
1694
2470
  }
1695
- if (game.nextAction.step !== "start" && game.nextAction.step !== "options") {
2471
+ const result = useCase(game, lordId, buildingId, position);
2472
+ if (isErr(result)) {
2473
+ logger.error(`Error constructing building: ${result.error}`);
2474
+ const code = result.error;
2475
+ throw new ActionExecutionError(
2476
+ translateErrorCode(translator, code),
2477
+ code,
2478
+ {
2479
+ gameId: game.id,
2480
+ lordId: command.lordId,
2481
+ buildingId: command.buildingId,
2482
+ position: command.position
2483
+ }
2484
+ );
2485
+ }
2486
+ logger.info(
2487
+ `Building constructed: ${buildingId} for lord: ${lordId} at position: ${JSON.stringify(
2488
+ position
2489
+ )} in game: ${game.id}`
2490
+ );
2491
+ return result.value;
2492
+ };
2493
+
2494
+ // src/application/handlers/useDragonHandler.ts
2495
+ var useDragonHandler = (logger, translator, useCase) => (command) => {
2496
+ const { game, lordId, buildingId } = command;
2497
+ logger.info(
2498
+ `Using dragon on building: ${buildingId} for lord: ${lordId} in game: ${game.id}`
2499
+ );
2500
+ if (game.nextAction.nextAction !== "useDragon") {
1696
2501
  logger.error(
1697
- `Required game with start step or options step but got: ${game.nextAction.step}`
2502
+ `Required game with useDragon action but got: ${game.nextAction.nextAction}`
1698
2503
  );
1699
2504
  throw new InvalidStepError(
1700
2505
  translateErrorCode(translator, ErrorCode.INVALID_STEP),
1701
2506
  {
1702
- expected: "start|options",
1703
- actual: game.nextAction.step,
2507
+ expected: "useDragon",
2508
+ actual: game.nextAction.nextAction,
1704
2509
  gameId: game.id
1705
2510
  }
1706
2511
  );
1707
2512
  }
1708
- const result = useCase(game);
2513
+ const result = useCase(game, lordId, buildingId);
1709
2514
  if (isErr(result)) {
1710
- logger.error(`Error starting game: ${result.error}`);
2515
+ logger.error(`Error using dragon: ${result.error}`);
1711
2516
  const code = result.error;
1712
- throw new StepExecutionError(translateErrorCode(translator, code), code, {
1713
- gameId: game.id
1714
- });
2517
+ throw new ActionExecutionError(
2518
+ translateErrorCode(translator, code),
2519
+ code,
2520
+ {
2521
+ gameId: game.id,
2522
+ lordId: command.lordId,
2523
+ buildingId: command.buildingId
2524
+ }
2525
+ );
1715
2526
  }
1716
- logger.info(`Game started: ${game.id}`);
2527
+ logger.info(
2528
+ `Dragon used on building: ${buildingId} for lord: ${lordId} in game: ${game.id}`
2529
+ );
1717
2530
  return result.value;
1718
2531
  };
1719
2532
 
1720
- // src/core/domain/types/kingdom.ts
1721
- var GRIDSIZE = 9;
1722
- var MAX_KINGDOM_SIZE = 5;
2533
+ // src/application/handlers/skipOptionalActionHandler.ts
2534
+ var skipOptionalActionHandler = (logger, translator, useCase) => (command) => {
2535
+ const { game, lordId } = command;
2536
+ logger.info(
2537
+ `Skipping optional action for lord: ${lordId} in game: ${game.id}`
2538
+ );
2539
+ const result = useCase(game, lordId);
2540
+ if (isErr(result)) {
2541
+ logger.error(`Error skipping optional action: ${result.error}`);
2542
+ const code = result.error;
2543
+ throw new ActionExecutionError(
2544
+ translateErrorCode(translator, code),
2545
+ code,
2546
+ { gameId: game.id, lordId: command.lordId }
2547
+ );
2548
+ }
2549
+ logger.info(
2550
+ `Optional action skipped for lord: ${lordId} in game: ${game.id}`
2551
+ );
2552
+ return result.value;
2553
+ };
1723
2554
 
1724
2555
  // src/core/domain/types/domino.ts
1725
2556
  var GROUND = [
@@ -1732,6 +2563,10 @@ var GROUND = [
1732
2563
  "mine"
1733
2564
  ];
1734
2565
 
2566
+ // src/core/domain/types/kingdom.ts
2567
+ var GRIDSIZE = 9;
2568
+ var MAX_KINGDOM_SIZE = 5;
2569
+
1735
2570
  // src/core/domain/types/player.ts
1736
2571
  var playerActions = {
1737
2572
  /** Place a previously picked domino on the kingdom */
@@ -1739,7 +2574,15 @@ var playerActions = {
1739
2574
  /** Pick a domino from the revealed set */
1740
2575
  pickDomino: "pickDomino",
1741
2576
  /** Pass/discard when no valid placement exists */
1742
- pass: "pass"
2577
+ pass: "pass",
2578
+ /** Place a knight on the just-placed domino (Queendomino, optional) */
2579
+ placeKnight: "placeKnight",
2580
+ /** Construct a building on a construction square (Queendomino, optional) */
2581
+ constructBuilding: "constructBuilding",
2582
+ /** Use the Dragon to burn a building tile (Queendomino, optional) */
2583
+ useDragon: "useDragon",
2584
+ /** Skip the current optional action (Queendomino) */
2585
+ skipOptionalAction: "skipOptionalAction"
1743
2586
  };
1744
2587
 
1745
2588
  // src/core/domain/entities/rule.ts
@@ -1837,6 +2680,24 @@ var addExtraRulesUseCase = (deps) => (game, rules) => {
1837
2680
  return ok(updatedGame);
1838
2681
  };
1839
2682
 
2683
+ // src/core/domain/entities/economy.ts
2684
+ var STARTING_COINS = 7;
2685
+ var MAX_KNIGHTS = 3;
2686
+ var addCoins = (player, amount) => ({
2687
+ ...player,
2688
+ coins: (player.coins ?? 0) + amount
2689
+ });
2690
+ var removeCoins = (player, amount) => ({
2691
+ ...player,
2692
+ coins: (player.coins ?? 0) - amount
2693
+ });
2694
+ var canAfford = (player, cost) => (player.coins ?? 0) >= cost;
2695
+ var addTowers = (player, amount) => ({
2696
+ ...player,
2697
+ towers: (player.towers ?? 0) + amount
2698
+ });
2699
+ var getTowerCount = (player) => player.towers ?? 0;
2700
+
1840
2701
  // src/core/domain/entities/domino.ts
1841
2702
  var isGround = (ground) => {
1842
2703
  return GROUND.includes(ground);
@@ -2132,6 +2993,12 @@ var addPlayersUseCase = (deps) => (game, players) => {
2132
2993
  if (isErr(newPlayer)) {
2133
2994
  return newPlayer;
2134
2995
  }
2996
+ if (game.mode.name === "QueenDomino") {
2997
+ newPlayer.value.coins = STARTING_COINS;
2998
+ newPlayer.value.towers = 0;
2999
+ newPlayer.value.knights = [];
3000
+ newPlayer.value.buildings = [];
3001
+ }
2135
3002
  newPlayers.push(newPlayer.value);
2136
3003
  }
2137
3004
  const rules = ruleRepository.getAll();
@@ -2278,6 +3145,19 @@ var findNextLord = (lords) => {
2278
3145
  var allLordsHavePlayed = (lords) => {
2279
3146
  return lords.every((lord) => lord.turnEnded);
2280
3147
  };
3148
+ var QUEENDOMINO_ACTION_SEQUENCE = [
3149
+ playerActions.placeKnight,
3150
+ playerActions.constructBuilding,
3151
+ playerActions.useDragon,
3152
+ playerActions.pickDomino
3153
+ ];
3154
+ var nextQueenDominoAction = (currentAction) => {
3155
+ const index = QUEENDOMINO_ACTION_SEQUENCE.indexOf(currentAction);
3156
+ if (index === -1 || index >= QUEENDOMINO_ACTION_SEQUENCE.length - 1) {
3157
+ return playerActions.pickDomino;
3158
+ }
3159
+ return QUEENDOMINO_ACTION_SEQUENCE[index + 1];
3160
+ };
2281
3161
 
2282
3162
  // src/core/useCases/chooseDomino.ts
2283
3163
  var chooseDominoUseCase = (game, lordId, dominoPick) => {
@@ -2348,7 +3228,19 @@ var chooseDominoUseCase = (game, lordId, dominoPick) => {
2348
3228
 
2349
3229
  // src/core/domain/entities/game.ts
2350
3230
  var create = (payload) => {
2351
- const { id, mode, dominoes, seed } = payload;
3231
+ const { id, mode, dominoes, seed, buildings } = payload;
3232
+ let queendomino;
3233
+ if (mode.name === "QueenDomino" && buildings) {
3234
+ queendomino = {
3235
+ buildersBoard: {
3236
+ slots: [],
3237
+ drawPile: buildings
3238
+ },
3239
+ queenHolderId: null,
3240
+ dragonAvailable: true,
3241
+ dragonUsedThisRound: false
3242
+ };
3243
+ }
2352
3244
  return {
2353
3245
  id,
2354
3246
  dominoes,
@@ -2371,7 +3263,8 @@ var create = (payload) => {
2371
3263
  extra: []
2372
3264
  },
2373
3265
  mode,
2374
- ...seed !== void 0 && { seed }
3266
+ ...seed !== void 0 && { seed },
3267
+ ...queendomino !== void 0 && { queendomino }
2375
3268
  };
2376
3269
  };
2377
3270
 
@@ -2389,7 +3282,7 @@ var createMode = (mode, availableMode) => {
2389
3282
 
2390
3283
  // src/core/useCases/createGame.ts
2391
3284
  var createGameUseCase = (deps) => (mode, seed) => {
2392
- const { modeRepository, dominoesRepository, uuidMethod: uuidMethod2 } = deps;
3285
+ const { modeRepository, dominoesRepository, uuidMethod: uuidMethod2, buildingsRepository } = deps;
2393
3286
  const id = uuidMethod2();
2394
3287
  const gameSeed = seed ?? uuidMethod2();
2395
3288
  const availableMode = modeRepository.getAvailables();
@@ -2401,11 +3294,17 @@ var createGameUseCase = (deps) => (mode, seed) => {
2401
3294
  if (!dominoes || dominoes.length === 0) {
2402
3295
  return err(ErrorCode.DOMINO_NOT_FOUND);
2403
3296
  }
3297
+ const isQueenDomino = newMode.value.name === "QueenDomino";
3298
+ let buildings;
3299
+ if (isQueenDomino && buildingsRepository) {
3300
+ buildings = buildingsRepository.getAll();
3301
+ }
2404
3302
  const payload = {
2405
3303
  id,
2406
3304
  mode: newMode.value,
2407
3305
  dominoes,
2408
- seed: gameSeed
3306
+ seed: gameSeed,
3307
+ buildings
2409
3308
  };
2410
3309
  const newGame = create(payload);
2411
3310
  return ok(newGame);
@@ -2698,6 +3597,7 @@ var placeDominoUseCase = (game, lordId, position, rotation) => {
2698
3597
  type: "step",
2699
3598
  step: gameSteps.result
2700
3599
  };
3600
+ const isQueenDomino = game.mode.name === "QueenDomino";
2701
3601
  if (isLastTurn && allLordsHavePlayed(updatedLords)) {
2702
3602
  updatedGame = {
2703
3603
  ...game,
@@ -2705,6 +3605,29 @@ var placeDominoUseCase = (game, lordId, position, rotation) => {
2705
3605
  nextAction: resultStep,
2706
3606
  players: updatedPlayers
2707
3607
  };
3608
+ } else if (isQueenDomino && !isLastTurn) {
3609
+ updatedGame = {
3610
+ ...game,
3611
+ lords: updatedLords,
3612
+ nextAction: {
3613
+ type: "action",
3614
+ nextLord: currentLord.id,
3615
+ nextAction: playerActions.placeKnight
3616
+ },
3617
+ players: updatedPlayers
3618
+ };
3619
+ } else if (isQueenDomino && isLastTurn) {
3620
+ const queenDominoAction = {
3621
+ type: "action",
3622
+ nextLord: currentLord.id,
3623
+ nextAction: playerActions.placeKnight
3624
+ };
3625
+ updatedGame = {
3626
+ ...game,
3627
+ lords: updatedLords,
3628
+ nextAction: queenDominoAction,
3629
+ players: updatedPlayers
3630
+ };
2708
3631
  } else {
2709
3632
  updatedGame = {
2710
3633
  ...game,
@@ -2716,6 +3639,401 @@ var placeDominoUseCase = (game, lordId, position, rotation) => {
2716
3639
  return ok(updatedGame);
2717
3640
  };
2718
3641
 
3642
+ // src/core/domain/entities/knight.ts
3643
+ var countConstructionSquaresInTerritory = (kingdom, position) => {
3644
+ const rows = kingdom.length;
3645
+ const cols = kingdom[0]?.length ?? 0;
3646
+ const startTile = kingdom[position.y]?.[position.x];
3647
+ if (!startTile || startTile.type === "empty" || startTile.type === "castle") {
3648
+ return 0;
3649
+ }
3650
+ const terrainType = startTile.type;
3651
+ const visited = kingdom.map((row) => row.map(() => false));
3652
+ const queue = [[position.x, position.y]];
3653
+ visited[position.y][position.x] = true;
3654
+ let count = 0;
3655
+ const directions = [
3656
+ [-1, 0],
3657
+ [1, 0],
3658
+ [0, -1],
3659
+ [0, 1]
3660
+ ];
3661
+ while (queue.length > 0) {
3662
+ const [cx, cy] = queue.pop();
3663
+ const tile = kingdom[cy][cx];
3664
+ if (tile.type !== "empty" && tile.type !== "castle" && "hasConstructionSquare" in tile && tile.hasConstructionSquare) {
3665
+ count++;
3666
+ }
3667
+ for (const [dx, dy] of directions) {
3668
+ const nx = cx + dx;
3669
+ const ny = cy + dy;
3670
+ if (nx >= 0 && ny >= 0 && nx < cols && ny < rows && !visited[ny][nx] && kingdom[ny][nx].type === terrainType) {
3671
+ visited[ny][nx] = true;
3672
+ queue.push([nx, ny]);
3673
+ }
3674
+ }
3675
+ }
3676
+ return count;
3677
+ };
3678
+ var getTerritoryTerrain = (kingdom, position) => {
3679
+ const tile = kingdom[position.y]?.[position.x];
3680
+ if (!tile || tile.type === "empty" || tile.type === "castle") return null;
3681
+ return tile.type;
3682
+ };
3683
+ var territoryHasKnight = (kingdom, position, knights) => {
3684
+ const terrain = getTerritoryTerrain(kingdom, position);
3685
+ if (!terrain) return false;
3686
+ const rows = kingdom.length;
3687
+ const cols = kingdom[0]?.length ?? 0;
3688
+ const visited = kingdom.map((row) => row.map(() => false));
3689
+ const queue = [[position.x, position.y]];
3690
+ visited[position.y][position.x] = true;
3691
+ const territoryPositions = /* @__PURE__ */ new Set();
3692
+ const directions = [
3693
+ [-1, 0],
3694
+ [1, 0],
3695
+ [0, -1],
3696
+ [0, 1]
3697
+ ];
3698
+ while (queue.length > 0) {
3699
+ const [cx, cy] = queue.pop();
3700
+ territoryPositions.add(`${cx},${cy}`);
3701
+ for (const [dx, dy] of directions) {
3702
+ const nx = cx + dx;
3703
+ const ny = cy + dy;
3704
+ if (nx >= 0 && ny >= 0 && nx < cols && ny < rows && !visited[ny][nx] && kingdom[ny][nx].type === terrain) {
3705
+ visited[ny][nx] = true;
3706
+ queue.push([nx, ny]);
3707
+ }
3708
+ }
3709
+ }
3710
+ return knights.some(
3711
+ (k) => territoryPositions.has(`${k.position.x},${k.position.y}`)
3712
+ );
3713
+ };
3714
+ var canPlaceKnight = (player, kingdom, position) => {
3715
+ const knights = player.knights ?? [];
3716
+ if (knights.length >= MAX_KNIGHTS) return false;
3717
+ const tile = kingdom[position.y]?.[position.x];
3718
+ if (!tile || tile.type === "empty" || tile.type === "castle") return false;
3719
+ if (territoryHasKnight(kingdom, position, knights)) return false;
3720
+ return true;
3721
+ };
3722
+ var placeKnight = (player, kingdom, position) => {
3723
+ const knight = { playerId: player.id, position };
3724
+ const knights = [...player.knights ?? [], knight];
3725
+ const taxAmount = countConstructionSquaresInTerritory(kingdom, position);
3726
+ const updatedPlayer = addCoins({ ...player, knights }, taxAmount);
3727
+ return updatedPlayer;
3728
+ };
3729
+
3730
+ // src/core/useCases/placeKnight.ts
3731
+ var placeKnightUseCase = (game, lordId, position) => {
3732
+ const currentAction = game.nextAction;
3733
+ if (currentAction.nextLord !== lordId) {
3734
+ return err(ErrorCode.NOT_YOUR_TURN);
3735
+ }
3736
+ if (currentAction.nextAction !== playerActions.placeKnight) {
3737
+ return err(ErrorCode.INVALID_OPTIONAL_ACTION);
3738
+ }
3739
+ const currentLord = game.lords.find((l) => l.id === lordId);
3740
+ if (!currentLord) return err(ErrorCode.LORD_NOT_FOUND);
3741
+ const currentPlayer = game.players.find(
3742
+ (p) => p.id === currentLord.playerId
3743
+ );
3744
+ if (!currentPlayer) return err(ErrorCode.PLAYER_NOT_FOUND);
3745
+ if (!canPlaceKnight(currentPlayer, currentPlayer.kingdom, position)) {
3746
+ return err(ErrorCode.CANNOT_PLACE_KNIGHT);
3747
+ }
3748
+ const updatedPlayer = placeKnight(
3749
+ currentPlayer,
3750
+ currentPlayer.kingdom,
3751
+ position
3752
+ );
3753
+ const updatedPlayers = game.players.map(
3754
+ (p) => p.id === currentPlayer.id ? updatedPlayer : p
3755
+ );
3756
+ const nextAction = nextQueenDominoAction(playerActions.placeKnight);
3757
+ return ok({
3758
+ ...game,
3759
+ players: updatedPlayers,
3760
+ nextAction: {
3761
+ type: "action",
3762
+ nextLord: lordId,
3763
+ nextAction
3764
+ }
3765
+ });
3766
+ };
3767
+
3768
+ // src/core/domain/entities/building.ts
3769
+ var BOARD_SLOTS = 4;
3770
+ var createBuildersBoard = (tiles, shuffle) => {
3771
+ const shuffled = shuffle([...tiles]);
3772
+ const slots = shuffled.slice(0, BOARD_SLOTS);
3773
+ const drawPile = shuffled.slice(BOARD_SLOTS);
3774
+ return {
3775
+ slots,
3776
+ drawPile
3777
+ };
3778
+ };
3779
+ var findBuildingOnBoard = (board, buildingId) => {
3780
+ for (let i = 0; i < board.slots.length; i++) {
3781
+ const slot = board.slots[i];
3782
+ if (slot && slot.id === buildingId) {
3783
+ return { building: slot, slotIndex: i };
3784
+ }
3785
+ }
3786
+ return null;
3787
+ };
3788
+ var removeBuildingFromBoard = (board, slotIndex) => {
3789
+ const newSlots = [...board.slots];
3790
+ newSlots[slotIndex] = null;
3791
+ return { ...board, slots: newSlots };
3792
+ };
3793
+ var purchaseBuilding = (board, player, buildingId) => {
3794
+ const found = findBuildingOnBoard(board, buildingId);
3795
+ if (!found) return null;
3796
+ const { building, slotIndex } = found;
3797
+ if (!canAfford(player, building.cost)) return null;
3798
+ let updatedPlayer = removeCoins(player, building.cost);
3799
+ if (building.immediateBonus) {
3800
+ switch (building.immediateBonus.type) {
3801
+ case "coins":
3802
+ updatedPlayer = addCoins(updatedPlayer, building.immediateBonus.amount);
3803
+ break;
3804
+ case "tower":
3805
+ updatedPlayer = addTowers(
3806
+ updatedPlayer,
3807
+ building.immediateBonus.amount
3808
+ );
3809
+ break;
3810
+ }
3811
+ }
3812
+ if (building.towers > 0) {
3813
+ updatedPlayer = addTowers(updatedPlayer, building.towers);
3814
+ }
3815
+ const updatedBoard = removeBuildingFromBoard(board, slotIndex);
3816
+ return { updatedBoard, updatedPlayer, building };
3817
+ };
3818
+ var isValidConstructionPosition = (kingdom, placedBuildings, position) => {
3819
+ const tile = kingdom[position.y]?.[position.x];
3820
+ if (!tile || tile.type === "empty" || tile.type === "castle") return false;
3821
+ if (!("hasConstructionSquare" in tile) || !tile.hasConstructionSquare)
3822
+ return false;
3823
+ const occupied = placedBuildings.some(
3824
+ (b) => b.position.x === position.x && b.position.y === position.y
3825
+ );
3826
+ return !occupied;
3827
+ };
3828
+ var placeBuildingOnKingdom = (kingdom, building, position) => {
3829
+ const newKingdom = kingdom.map((row) => [...row]);
3830
+ const tile = newKingdom[position.y][position.x];
3831
+ if (tile.type !== "empty" && tile.type !== "castle") {
3832
+ newKingdom[position.y][position.x] = {
3833
+ ...tile,
3834
+ crowns: tile.crowns + building.crowns
3835
+ };
3836
+ }
3837
+ return newKingdom;
3838
+ };
3839
+ var destroyWithDragon = (board, buildingId, player) => {
3840
+ const found = findBuildingOnBoard(board, buildingId);
3841
+ if (!found) return null;
3842
+ const { building, slotIndex } = found;
3843
+ if (!canAfford(player, building.cost)) return null;
3844
+ const updatedPlayer = removeCoins(player, building.cost);
3845
+ const updatedBoard = removeBuildingFromBoard(board, slotIndex);
3846
+ return { updatedBoard, updatedPlayer };
3847
+ };
3848
+
3849
+ // src/core/domain/entities/queen.ts
3850
+ var determineQueenHolder = (players, currentHolderId) => {
3851
+ let maxTowers = 0;
3852
+ let leaderId = null;
3853
+ let isTied = false;
3854
+ for (const player of players) {
3855
+ const towers = getTowerCount(player);
3856
+ if (towers > maxTowers) {
3857
+ maxTowers = towers;
3858
+ leaderId = player.id;
3859
+ isTied = false;
3860
+ } else if (towers === maxTowers && towers > 0 && leaderId !== null) {
3861
+ isTied = true;
3862
+ }
3863
+ }
3864
+ if (isTied || maxTowers === 0) {
3865
+ return currentHolderId;
3866
+ }
3867
+ return leaderId;
3868
+ };
3869
+
3870
+ // src/core/useCases/constructBuilding.ts
3871
+ var constructBuildingUseCase = (game, lordId, buildingId, position) => {
3872
+ const currentAction = game.nextAction;
3873
+ if (currentAction.nextLord !== lordId) {
3874
+ return err(ErrorCode.NOT_YOUR_TURN);
3875
+ }
3876
+ if (currentAction.nextAction !== playerActions.constructBuilding) {
3877
+ return err(ErrorCode.INVALID_OPTIONAL_ACTION);
3878
+ }
3879
+ if (!game.queendomino) {
3880
+ return err(ErrorCode.INVALID_OPTIONAL_ACTION);
3881
+ }
3882
+ const currentLord = game.lords.find((l) => l.id === lordId);
3883
+ if (!currentLord) return err(ErrorCode.LORD_NOT_FOUND);
3884
+ const currentPlayer = game.players.find(
3885
+ (p) => p.id === currentLord.playerId
3886
+ );
3887
+ if (!currentPlayer) return err(ErrorCode.PLAYER_NOT_FOUND);
3888
+ if (!isValidConstructionPosition(
3889
+ currentPlayer.kingdom,
3890
+ currentPlayer.buildings ?? [],
3891
+ position
3892
+ )) {
3893
+ return err(ErrorCode.NO_CONSTRUCTION_SQUARE);
3894
+ }
3895
+ const purchaseResult = purchaseBuilding(
3896
+ game.queendomino.buildersBoard,
3897
+ currentPlayer,
3898
+ buildingId
3899
+ );
3900
+ if (!purchaseResult) {
3901
+ return err(ErrorCode.CANNOT_AFFORD_BUILDING);
3902
+ }
3903
+ const { updatedBoard, updatedPlayer, building } = purchaseResult;
3904
+ const updatedKingdom = placeBuildingOnKingdom(
3905
+ updatedPlayer.kingdom,
3906
+ building,
3907
+ position
3908
+ );
3909
+ const placedBuilding = { building, position };
3910
+ const finalPlayer = {
3911
+ ...updatedPlayer,
3912
+ kingdom: updatedKingdom,
3913
+ buildings: [...updatedPlayer.buildings ?? [], placedBuilding]
3914
+ };
3915
+ const updatedPlayers = game.players.map(
3916
+ (p) => p.id === currentPlayer.id ? finalPlayer : p
3917
+ );
3918
+ const newQueenHolder = determineQueenHolder(
3919
+ updatedPlayers,
3920
+ game.queendomino.queenHolderId
3921
+ );
3922
+ const nextAction = nextQueenDominoAction(playerActions.constructBuilding);
3923
+ return ok({
3924
+ ...game,
3925
+ players: updatedPlayers,
3926
+ queendomino: {
3927
+ ...game.queendomino,
3928
+ buildersBoard: updatedBoard,
3929
+ queenHolderId: newQueenHolder
3930
+ },
3931
+ nextAction: {
3932
+ type: "action",
3933
+ nextLord: lordId,
3934
+ nextAction
3935
+ }
3936
+ });
3937
+ };
3938
+
3939
+ // src/core/useCases/useDragon.ts
3940
+ var useDragonUseCase = (game, lordId, buildingId) => {
3941
+ const currentAction = game.nextAction;
3942
+ if (currentAction.nextLord !== lordId) {
3943
+ return err(ErrorCode.NOT_YOUR_TURN);
3944
+ }
3945
+ if (currentAction.nextAction !== playerActions.useDragon) {
3946
+ return err(ErrorCode.INVALID_OPTIONAL_ACTION);
3947
+ }
3948
+ if (!game.queendomino) {
3949
+ return err(ErrorCode.INVALID_OPTIONAL_ACTION);
3950
+ }
3951
+ if (!game.queendomino.dragonAvailable || game.queendomino.dragonUsedThisRound) {
3952
+ return err(ErrorCode.DRAGON_UNAVAILABLE);
3953
+ }
3954
+ const currentLord = game.lords.find((l) => l.id === lordId);
3955
+ if (!currentLord) return err(ErrorCode.LORD_NOT_FOUND);
3956
+ const currentPlayer = game.players.find(
3957
+ (p) => p.id === currentLord.playerId
3958
+ );
3959
+ if (!currentPlayer) return err(ErrorCode.PLAYER_NOT_FOUND);
3960
+ if (game.queendomino.queenHolderId === currentPlayer.id) {
3961
+ return err(ErrorCode.QUEEN_HOLDER_CANNOT_USE_DRAGON);
3962
+ }
3963
+ const result = destroyWithDragon(
3964
+ game.queendomino.buildersBoard,
3965
+ buildingId,
3966
+ currentPlayer
3967
+ );
3968
+ if (!result) {
3969
+ return err(ErrorCode.BUILDING_NOT_FOUND);
3970
+ }
3971
+ const { updatedBoard, updatedPlayer } = result;
3972
+ const updatedPlayers = game.players.map(
3973
+ (p) => p.id === currentPlayer.id ? updatedPlayer : p
3974
+ );
3975
+ const maxTurns = game.rules.basic.maxTurns;
3976
+ const isLastTurn = game.turn === maxTurns;
3977
+ if (isLastTurn && allLordsHavePlayed(game.lords)) {
3978
+ return ok({
3979
+ ...game,
3980
+ players: updatedPlayers,
3981
+ queendomino: {
3982
+ ...game.queendomino,
3983
+ buildersBoard: updatedBoard,
3984
+ dragonUsedThisRound: true
3985
+ },
3986
+ nextAction: { type: "step", step: gameSteps.result }
3987
+ });
3988
+ }
3989
+ return ok({
3990
+ ...game,
3991
+ players: updatedPlayers,
3992
+ queendomino: {
3993
+ ...game.queendomino,
3994
+ buildersBoard: updatedBoard,
3995
+ dragonUsedThisRound: true
3996
+ },
3997
+ nextAction: nextLordWithAction(game.lords)
3998
+ });
3999
+ };
4000
+
4001
+ // src/core/useCases/skipOptionalAction.ts
4002
+ var skipOptionalActionUseCase = (game, lordId) => {
4003
+ const currentAction = game.nextAction;
4004
+ if (currentAction.nextLord !== lordId) {
4005
+ return err(ErrorCode.NOT_YOUR_TURN);
4006
+ }
4007
+ const action = currentAction.nextAction;
4008
+ const isOptional = action === playerActions.placeKnight || action === playerActions.constructBuilding || action === playerActions.useDragon;
4009
+ if (!isOptional) {
4010
+ return err(ErrorCode.INVALID_OPTIONAL_ACTION);
4011
+ }
4012
+ const nextAction = nextQueenDominoAction(action);
4013
+ if (nextAction === playerActions.pickDomino) {
4014
+ const maxTurns = game.rules.basic.maxTurns;
4015
+ const isLastTurn = game.turn === maxTurns;
4016
+ if (isLastTurn && allLordsHavePlayed(game.lords)) {
4017
+ return ok({
4018
+ ...game,
4019
+ nextAction: { type: "step", step: gameSteps.result }
4020
+ });
4021
+ }
4022
+ return ok({
4023
+ ...game,
4024
+ nextAction: nextLordWithAction(game.lords)
4025
+ });
4026
+ }
4027
+ return ok({
4028
+ ...game,
4029
+ nextAction: {
4030
+ type: "action",
4031
+ nextLord: lordId,
4032
+ nextAction
4033
+ }
4034
+ });
4035
+ };
4036
+
2719
4037
  // src/core/useCases/startGame.ts
2720
4038
  var startGameUseCase = (deps) => (game) => {
2721
4039
  const { uuidMethod: uuidMethod2, shuffleMethod: shuffleMethod2 } = deps;
@@ -2744,6 +4062,15 @@ var startGameUseCase = (deps) => (game) => {
2744
4062
  nextLord: shuffledLords[0].id,
2745
4063
  nextAction: playerActions.pickDomino
2746
4064
  };
4065
+ let queendomino = game.queendomino;
4066
+ if (queendomino && game.mode.name === "QueenDomino") {
4067
+ const buildingShuffle = game.seed ? createSeededShuffle(game.seed, "buildings") : shuffleMethod2;
4068
+ const board = createBuildersBoard(
4069
+ queendomino.buildersBoard.drawPile,
4070
+ buildingShuffle
4071
+ );
4072
+ queendomino = { ...queendomino, buildersBoard: board };
4073
+ }
2747
4074
  const newState = {
2748
4075
  ...game,
2749
4076
  lords: shuffledLords,
@@ -2756,7 +4083,8 @@ var startGameUseCase = (deps) => (game) => {
2756
4083
  lordId: null,
2757
4084
  position: index + 1
2758
4085
  };
2759
- })
4086
+ }),
4087
+ ...queendomino !== void 0 && { queendomino }
2760
4088
  };
2761
4089
  return ok(newState);
2762
4090
  };
@@ -2765,6 +4093,7 @@ var startGameUseCase = (deps) => (game) => {
2765
4093
  var configureEngine = (config) => {
2766
4094
  const modeRepository = jsonModes_default();
2767
4095
  const dominoesRepository = jsonDominoes_default();
4096
+ const buildingsRepository = jsonBuildings_default();
2768
4097
  const ruleRepository = jsonRules_default();
2769
4098
  const uuid = config.uuidMethod || uuidMethod;
2770
4099
  const shuffle = config.shuffleMethod || shuffleMethod;
@@ -2777,7 +4106,8 @@ var configureEngine = (config) => {
2777
4106
  createGameUseCase({
2778
4107
  modeRepository,
2779
4108
  dominoesRepository,
2780
- uuidMethod: uuid
4109
+ uuidMethod: uuid,
4110
+ buildingsRepository
2781
4111
  })
2782
4112
  ),
2783
4113
  getModesHandler: getModesHandler(
@@ -2853,6 +4183,26 @@ var configureEngine = (config) => {
2853
4183
  logger,
2854
4184
  translator,
2855
4185
  getDynastyResultUseCase
4186
+ ),
4187
+ placeKnightHandler: placeKnightHandler(
4188
+ logger,
4189
+ translator,
4190
+ placeKnightUseCase
4191
+ ),
4192
+ constructBuildingHandler: constructBuildingHandler(
4193
+ logger,
4194
+ translator,
4195
+ constructBuildingUseCase
4196
+ ),
4197
+ useDragonHandler: useDragonHandler(
4198
+ logger,
4199
+ translator,
4200
+ useDragonUseCase
4201
+ ),
4202
+ skipOptionalActionHandler: skipOptionalActionHandler(
4203
+ logger,
4204
+ translator,
4205
+ skipOptionalActionUseCase
2856
4206
  )
2857
4207
  };
2858
4208
  };
@@ -3576,7 +4926,12 @@ var wrapWithActionLog = (engine) => {
3576
4926
  const game = engine.discardDomino(cmd);
3577
4927
  record("discardDomino", { lordId: cmd.lordId }, game.turn);
3578
4928
  return game;
3579
- }
4929
+ },
4930
+ // Queendomino methods — pass through (no action log recording yet)
4931
+ placeKnight: (cmd) => engine.placeKnight(cmd),
4932
+ constructBuilding: (cmd) => engine.constructBuilding(cmd),
4933
+ useDragon: (cmd) => engine.useDragon(cmd),
4934
+ skipOptionalAction: (cmd) => engine.skipOptionalAction(cmd)
3580
4935
  };
3581
4936
  return {
3582
4937
  engine: wrappedEngine,
@@ -3717,7 +5072,11 @@ var createGameEngine = (config) => {
3717
5072
  canPlaceDominoHandler: canPlaceDominoHandler2,
3718
5073
  serializeGameHandler: serializeGameHandler2,
3719
5074
  deserializeGameHandler: deserializeGameHandler2,
3720
- getDynastyResultHandler: getDynastyResultHandler2
5075
+ getDynastyResultHandler: getDynastyResultHandler2,
5076
+ placeKnightHandler: placeKnightHandler2,
5077
+ constructBuildingHandler: constructBuildingHandler2,
5078
+ useDragonHandler: useDragonHandler2,
5079
+ skipOptionalActionHandler: skipOptionalActionHandler2
3721
5080
  } = configureEngine(config);
3722
5081
  let engine = {
3723
5082
  getModes: (command) => getModesHandler2(command),
@@ -3735,7 +5094,11 @@ var createGameEngine = (config) => {
3735
5094
  canPlaceDomino: (command) => canPlaceDominoHandler2(command),
3736
5095
  serialize: (command) => serializeGameHandler2(command),
3737
5096
  deserialize: (command) => deserializeGameHandler2(command),
3738
- getDynastyResults: (command) => getDynastyResultHandler2(command)
5097
+ getDynastyResults: (command) => getDynastyResultHandler2(command),
5098
+ placeKnight: (command) => placeKnightHandler2(command),
5099
+ constructBuilding: (command) => constructBuildingHandler2(command),
5100
+ useDragon: (command) => useDragonHandler2(command),
5101
+ skipOptionalAction: (command) => skipOptionalActionHandler2(command)
3739
5102
  };
3740
5103
  if (config.events) {
3741
5104
  engine = wrapWithEvents(engine, config.events);