@pompidup/kingdomino-engine 3.0.0 → 3.1.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/README.md +1 -1
- package/dist/index.d.ts +700 -1
- package/dist/index.js +493 -102
- package/dist/index.js.map +1 -1
- package/package.json +13 -15
package/dist/index.js
CHANGED
|
@@ -673,73 +673,132 @@ var uuidMethod = () => {
|
|
|
673
673
|
return randomUUID();
|
|
674
674
|
};
|
|
675
675
|
|
|
676
|
-
// src/adapterServerside/
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
}
|
|
683
|
-
);
|
|
684
|
-
const logger = winston.createLogger({
|
|
685
|
-
level: "info",
|
|
686
|
-
format: winston.format.combine(
|
|
687
|
-
winston.format.label({ label: "KDomino_Engine" }),
|
|
688
|
-
winston.format.timestamp(),
|
|
689
|
-
winston.format.simple(),
|
|
690
|
-
winston.format.json(),
|
|
691
|
-
winston.format.colorize(),
|
|
692
|
-
myFormat
|
|
693
|
-
),
|
|
694
|
-
transports: [new winston.transports.Console()]
|
|
695
|
-
});
|
|
676
|
+
// src/adapterServerside/consoleLogger.ts
|
|
677
|
+
var consoleLogger = (isLoggingEnabled) => {
|
|
678
|
+
const formatMessage = (level, message) => {
|
|
679
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
680
|
+
return `${timestamp} [KDomino_Engine] ${level}: ${message}`;
|
|
681
|
+
};
|
|
696
682
|
return {
|
|
697
683
|
info: (message) => {
|
|
698
684
|
if (isLoggingEnabled) {
|
|
699
|
-
|
|
685
|
+
console.log(formatMessage("info", message));
|
|
700
686
|
}
|
|
701
|
-
return void 0;
|
|
702
687
|
},
|
|
703
688
|
error: (message) => {
|
|
704
689
|
if (isLoggingEnabled) {
|
|
705
|
-
|
|
690
|
+
console.error(formatMessage("error", message));
|
|
706
691
|
}
|
|
707
|
-
return void 0;
|
|
708
692
|
}
|
|
709
693
|
};
|
|
710
694
|
};
|
|
695
|
+
var silentLogger = {
|
|
696
|
+
info: () => {
|
|
697
|
+
},
|
|
698
|
+
error: () => {
|
|
699
|
+
}
|
|
700
|
+
};
|
|
711
701
|
|
|
712
702
|
// src/core/domain/errors/domainErrors.ts
|
|
713
|
-
var
|
|
714
|
-
|
|
715
|
-
|
|
703
|
+
var ErrorCode = {
|
|
704
|
+
// Game flow errors
|
|
705
|
+
INVALID_STEP: "INVALID_STEP",
|
|
706
|
+
STEP_EXECUTION_FAILED: "STEP_EXECUTION_FAILED",
|
|
707
|
+
ACTION_EXECUTION_FAILED: "ACTION_EXECUTION_FAILED",
|
|
708
|
+
// Entity not found errors
|
|
709
|
+
LORD_NOT_FOUND: "LORD_NOT_FOUND",
|
|
710
|
+
PLAYER_NOT_FOUND: "PLAYER_NOT_FOUND",
|
|
711
|
+
DOMINO_NOT_FOUND: "DOMINO_NOT_FOUND",
|
|
712
|
+
MODE_NOT_FOUND: "MODE_NOT_FOUND",
|
|
713
|
+
// Validation errors
|
|
714
|
+
INVALID_PLAYER_COUNT: "INVALID_PLAYER_COUNT",
|
|
715
|
+
INVALID_PLAYER_NAME: "INVALID_PLAYER_NAME",
|
|
716
|
+
INVALID_PLACEMENT: "INVALID_PLACEMENT",
|
|
717
|
+
PLACEMENT_NOT_EMPTY: "PLACEMENT_NOT_EMPTY",
|
|
718
|
+
PLACEMENT_NOT_ADJACENT: "PLACEMENT_NOT_ADJACENT",
|
|
719
|
+
PLACEMENT_INVALID_TERRAIN: "PLACEMENT_INVALID_TERRAIN",
|
|
720
|
+
PLACEMENT_OUT_OF_BOUNDS: "PLACEMENT_OUT_OF_BOUNDS",
|
|
721
|
+
// Action errors
|
|
722
|
+
NOT_YOUR_TURN: "NOT_YOUR_TURN",
|
|
723
|
+
CANNOT_PICK: "CANNOT_PICK",
|
|
724
|
+
CANNOT_PLACE: "CANNOT_PLACE",
|
|
725
|
+
DOMINO_ALREADY_PICKED: "DOMINO_ALREADY_PICKED"
|
|
726
|
+
};
|
|
727
|
+
var createDomainError = (code, message, context) => ({
|
|
728
|
+
code,
|
|
729
|
+
message,
|
|
730
|
+
context
|
|
731
|
+
});
|
|
732
|
+
var DomainException = class extends Error {
|
|
733
|
+
code;
|
|
734
|
+
context;
|
|
735
|
+
constructor(error) {
|
|
736
|
+
super(error.message);
|
|
737
|
+
this.name = "DomainException";
|
|
738
|
+
this.code = error.code;
|
|
739
|
+
this.context = error.context;
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
var InvalidStepError = class extends DomainException {
|
|
743
|
+
constructor(message, context) {
|
|
744
|
+
super({
|
|
745
|
+
code: ErrorCode.INVALID_STEP,
|
|
746
|
+
message,
|
|
747
|
+
context
|
|
748
|
+
});
|
|
716
749
|
this.name = "InvalidStepError";
|
|
717
750
|
}
|
|
718
751
|
};
|
|
719
|
-
var StepExecutionError = class extends
|
|
720
|
-
constructor(message) {
|
|
721
|
-
super(
|
|
752
|
+
var StepExecutionError = class extends DomainException {
|
|
753
|
+
constructor(message, context) {
|
|
754
|
+
super({
|
|
755
|
+
code: ErrorCode.STEP_EXECUTION_FAILED,
|
|
756
|
+
message,
|
|
757
|
+
context
|
|
758
|
+
});
|
|
722
759
|
this.name = "StepExecutionError";
|
|
723
760
|
}
|
|
724
761
|
};
|
|
725
|
-
var ActionExecutionError = class extends
|
|
726
|
-
constructor(message) {
|
|
727
|
-
super(
|
|
762
|
+
var ActionExecutionError = class extends DomainException {
|
|
763
|
+
constructor(message, code = ErrorCode.ACTION_EXECUTION_FAILED, context) {
|
|
764
|
+
super({
|
|
765
|
+
code,
|
|
766
|
+
message,
|
|
767
|
+
context
|
|
768
|
+
});
|
|
728
769
|
this.name = "ActionExecutionError";
|
|
729
770
|
}
|
|
730
771
|
};
|
|
731
|
-
var NotFoundError = class extends
|
|
732
|
-
constructor(message) {
|
|
733
|
-
super(
|
|
772
|
+
var NotFoundError = class extends DomainException {
|
|
773
|
+
constructor(message, code = ErrorCode.LORD_NOT_FOUND, context) {
|
|
774
|
+
super({
|
|
775
|
+
code,
|
|
776
|
+
message,
|
|
777
|
+
context
|
|
778
|
+
});
|
|
734
779
|
this.name = "NotFoundError";
|
|
735
780
|
}
|
|
736
781
|
};
|
|
782
|
+
var ValidationError = class extends DomainException {
|
|
783
|
+
constructor(message, code, context) {
|
|
784
|
+
super({
|
|
785
|
+
code,
|
|
786
|
+
message,
|
|
787
|
+
context
|
|
788
|
+
});
|
|
789
|
+
this.name = "ValidationError";
|
|
790
|
+
}
|
|
791
|
+
};
|
|
737
792
|
|
|
738
793
|
// src/core/domain/types/game.ts
|
|
739
794
|
var gameSteps = {
|
|
795
|
+
/** Waiting for players to be added */
|
|
740
796
|
addPlayers: "addPlayers",
|
|
797
|
+
/** Optional rules selection phase */
|
|
741
798
|
options: "options",
|
|
799
|
+
/** Ready to start the game */
|
|
742
800
|
start: "start",
|
|
801
|
+
/** Game has ended, results available */
|
|
743
802
|
result: "result"
|
|
744
803
|
};
|
|
745
804
|
var isGameWithNextAction = (game) => {
|
|
@@ -754,6 +813,12 @@ var ok = (value) => ({ tag: "Ok", value });
|
|
|
754
813
|
var err = (error) => ({ tag: "Err", error });
|
|
755
814
|
var isOk = (result) => result.tag === "Ok";
|
|
756
815
|
var isErr = (result) => result.tag === "Err";
|
|
816
|
+
var unwrap = (result) => {
|
|
817
|
+
if (result.tag === "Err") {
|
|
818
|
+
throw result.error;
|
|
819
|
+
}
|
|
820
|
+
return result.value;
|
|
821
|
+
};
|
|
757
822
|
|
|
758
823
|
// src/application/handlers/addExtraRulesHandler.ts
|
|
759
824
|
var addExtraRulesHandler = (logger, useCase) => (command) => {
|
|
@@ -1049,57 +1114,14 @@ var GROUND = [
|
|
|
1049
1114
|
|
|
1050
1115
|
// src/core/domain/types/player.ts
|
|
1051
1116
|
var playerActions = {
|
|
1117
|
+
/** Place a previously picked domino on the kingdom */
|
|
1052
1118
|
placeDomino: "placeDomino",
|
|
1119
|
+
/** Pick a domino from the revealed set */
|
|
1053
1120
|
pickDomino: "pickDomino",
|
|
1121
|
+
/** Pass/discard when no valid placement exists */
|
|
1054
1122
|
pass: "pass"
|
|
1055
1123
|
};
|
|
1056
1124
|
|
|
1057
|
-
// src/core/domain/entities/rule.ts
|
|
1058
|
-
var getAvailablesExtraRules = (extraRules, mode, numberOfPlayers) => {
|
|
1059
|
-
const availableExtraRules = extraRules.filter((rule) => {
|
|
1060
|
-
return rule.mode.filter((m) => m.name === mode).length > 0 && (!rule.playersLimit || rule.playersLimit === numberOfPlayers);
|
|
1061
|
-
});
|
|
1062
|
-
if (availableExtraRules.length === 0) {
|
|
1063
|
-
return err("No extra rules found");
|
|
1064
|
-
}
|
|
1065
|
-
return ok(availableExtraRules);
|
|
1066
|
-
};
|
|
1067
|
-
var toExtraRule = (rule, extraRules) => {
|
|
1068
|
-
const foundRule = extraRules.find((r) => r.name === rule);
|
|
1069
|
-
if (!foundRule) {
|
|
1070
|
-
return err(`Rule not found: ${rule}`);
|
|
1071
|
-
}
|
|
1072
|
-
return ok(foundRule);
|
|
1073
|
-
};
|
|
1074
|
-
|
|
1075
|
-
// src/core/useCases/addExtraRules.ts
|
|
1076
|
-
var addExtraRulesUseCase = (deps) => (game, rules) => {
|
|
1077
|
-
const { ruleRepository } = deps;
|
|
1078
|
-
const availableExtraRules = ruleRepository.getAllExtra();
|
|
1079
|
-
const newExtraRules = [];
|
|
1080
|
-
for (const rule of rules) {
|
|
1081
|
-
const result = toExtraRule(rule, availableExtraRules);
|
|
1082
|
-
if (isErr(result)) {
|
|
1083
|
-
return result;
|
|
1084
|
-
}
|
|
1085
|
-
newExtraRules.push(result.value);
|
|
1086
|
-
}
|
|
1087
|
-
const updatedRules = {
|
|
1088
|
-
basic: game.rules.basic,
|
|
1089
|
-
extra: newExtraRules
|
|
1090
|
-
};
|
|
1091
|
-
const next = {
|
|
1092
|
-
type: "step",
|
|
1093
|
-
step: gameSteps.start
|
|
1094
|
-
};
|
|
1095
|
-
const updatedGame = {
|
|
1096
|
-
...game,
|
|
1097
|
-
rules: updatedRules,
|
|
1098
|
-
nextAction: next
|
|
1099
|
-
};
|
|
1100
|
-
return ok(updatedGame);
|
|
1101
|
-
};
|
|
1102
|
-
|
|
1103
1125
|
// src/core/domain/entities/domino.ts
|
|
1104
1126
|
var isGround = (ground) => {
|
|
1105
1127
|
return GROUND.includes(ground);
|
|
@@ -1306,21 +1328,33 @@ var hasValidAdjacent = (adjacentTiles, tiles) => {
|
|
|
1306
1328
|
});
|
|
1307
1329
|
};
|
|
1308
1330
|
var checkCastleIsInMiddle = (kingdom) => {
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
(tile) => tile.type
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
).length;
|
|
1331
|
+
let castleX;
|
|
1332
|
+
let castleY;
|
|
1333
|
+
for (let y = 0; y < kingdom.length; y++) {
|
|
1334
|
+
const x = kingdom[y].findIndex((tile) => tile.type === "castle");
|
|
1335
|
+
if (x !== -1) {
|
|
1336
|
+
castleX = x;
|
|
1337
|
+
castleY = y;
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
if (castleX === void 0 || castleY === void 0) {
|
|
1342
|
+
return false;
|
|
1343
|
+
}
|
|
1344
|
+
const leftTilesCount = kingdom[castleY].slice(0, castleX).filter((tile) => tile.type !== "empty").length;
|
|
1345
|
+
const rightTilesCount = kingdom[castleY].slice(castleX + 1).filter((tile) => tile.type !== "empty").length;
|
|
1346
|
+
let topTilesCount = 0;
|
|
1347
|
+
for (let y = 0; y < castleY; y++) {
|
|
1348
|
+
if (kingdom[y][castleX].type !== "empty") {
|
|
1349
|
+
topTilesCount++;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
let bottomTilesCount = 0;
|
|
1353
|
+
for (let y = castleY + 1; y < kingdom.length; y++) {
|
|
1354
|
+
if (kingdom[y][castleX].type !== "empty") {
|
|
1355
|
+
bottomTilesCount++;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1324
1358
|
return leftTilesCount === rightTilesCount && topTilesCount === bottomTilesCount && leftTilesCount === topTilesCount;
|
|
1325
1359
|
};
|
|
1326
1360
|
var countDominoes = (kingdom) => {
|
|
@@ -1331,6 +1365,170 @@ var deepCopyKingdom = (kingdom) => {
|
|
|
1331
1365
|
return kingdom.map((line) => line.map((tile) => ({ ...tile })));
|
|
1332
1366
|
};
|
|
1333
1367
|
|
|
1368
|
+
// src/core/useCases/getValidPlacements.ts
|
|
1369
|
+
var getValidPlacementsUseCase = (kingdom, domino) => {
|
|
1370
|
+
const validPlacements = [];
|
|
1371
|
+
const rotations = [0, 90, 180, 270];
|
|
1372
|
+
for (let y = 0; y < GRIDSIZE; y++) {
|
|
1373
|
+
for (let x = 0; x < GRIDSIZE; x++) {
|
|
1374
|
+
const position = { x, y };
|
|
1375
|
+
for (const rotation of rotations) {
|
|
1376
|
+
const result = placeDomino(kingdom, position, rotation, domino);
|
|
1377
|
+
if (isOk(result)) {
|
|
1378
|
+
validPlacements.push({ position, rotation });
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
return validPlacements;
|
|
1384
|
+
};
|
|
1385
|
+
var canPlaceDominoUseCase = (kingdom, domino) => {
|
|
1386
|
+
const rotations = [0, 90, 180, 270];
|
|
1387
|
+
for (let y = 0; y < GRIDSIZE; y++) {
|
|
1388
|
+
for (let x = 0; x < GRIDSIZE; x++) {
|
|
1389
|
+
const position = { x, y };
|
|
1390
|
+
for (const rotation of rotations) {
|
|
1391
|
+
const result = placeDomino(kingdom, position, rotation, domino);
|
|
1392
|
+
if (isOk(result)) {
|
|
1393
|
+
return true;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return false;
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
// src/application/handlers/getValidPlacementsHandler.ts
|
|
1402
|
+
var getValidPlacementsHandler = (logger) => {
|
|
1403
|
+
return (command) => {
|
|
1404
|
+
logger.info(`Getting valid placements for domino: ${command.domino.number}`);
|
|
1405
|
+
const placements = getValidPlacementsUseCase(command.kingdom, command.domino);
|
|
1406
|
+
logger.info(`Found ${placements.length} valid placements`);
|
|
1407
|
+
return placements;
|
|
1408
|
+
};
|
|
1409
|
+
};
|
|
1410
|
+
|
|
1411
|
+
// src/application/handlers/canPlaceDominoHandler.ts
|
|
1412
|
+
var canPlaceDominoHandler = (logger) => {
|
|
1413
|
+
return (command) => {
|
|
1414
|
+
logger.info(`Checking if domino ${command.domino.number} can be placed`);
|
|
1415
|
+
const canPlace = canPlaceDominoUseCase(command.kingdom, command.domino);
|
|
1416
|
+
logger.info(`Can place domino: ${canPlace}`);
|
|
1417
|
+
return canPlace;
|
|
1418
|
+
};
|
|
1419
|
+
};
|
|
1420
|
+
|
|
1421
|
+
// src/core/useCases/serialization.ts
|
|
1422
|
+
var SERIALIZATION_VERSION = 1;
|
|
1423
|
+
var serializeGame = (game) => {
|
|
1424
|
+
const serialized = {
|
|
1425
|
+
version: SERIALIZATION_VERSION,
|
|
1426
|
+
data: game,
|
|
1427
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1428
|
+
};
|
|
1429
|
+
return JSON.stringify(serialized);
|
|
1430
|
+
};
|
|
1431
|
+
var deserializeGame = (json) => {
|
|
1432
|
+
try {
|
|
1433
|
+
const parsed = JSON.parse(json);
|
|
1434
|
+
if (!parsed.version || !parsed.data) {
|
|
1435
|
+
return err("Invalid serialized game format");
|
|
1436
|
+
}
|
|
1437
|
+
if (parsed.version > SERIALIZATION_VERSION) {
|
|
1438
|
+
return err(
|
|
1439
|
+
`Unsupported serialization version: ${parsed.version}. Current version: ${SERIALIZATION_VERSION}`
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
const game = parsed.data;
|
|
1443
|
+
if (!game.id || !game.players || !game.lords || !game.nextAction) {
|
|
1444
|
+
return err("Invalid game state structure");
|
|
1445
|
+
}
|
|
1446
|
+
return ok(game);
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
return err(`Failed to parse game JSON: ${error.message}`);
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
var createSavePoint = (game) => {
|
|
1452
|
+
return {
|
|
1453
|
+
serialized: serializeGame(game),
|
|
1454
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1455
|
+
gameId: game.id,
|
|
1456
|
+
turn: game.turn
|
|
1457
|
+
};
|
|
1458
|
+
};
|
|
1459
|
+
var restoreFromSavePoint = (savePoint) => {
|
|
1460
|
+
return deserializeGame(savePoint.serialized);
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
// src/application/handlers/serializeGameHandler.ts
|
|
1464
|
+
var serializeGameHandler = (logger) => {
|
|
1465
|
+
return (command) => {
|
|
1466
|
+
logger.info(`Serializing game: ${command.game.id}`);
|
|
1467
|
+
const serialized = serializeGame(command.game);
|
|
1468
|
+
logger.info(`Game serialized successfully`);
|
|
1469
|
+
return serialized;
|
|
1470
|
+
};
|
|
1471
|
+
};
|
|
1472
|
+
var deserializeGameHandler = (logger) => {
|
|
1473
|
+
return (command) => {
|
|
1474
|
+
logger.info(`Deserializing game from JSON`);
|
|
1475
|
+
const result = deserializeGame(command.json);
|
|
1476
|
+
if (isErr(result)) {
|
|
1477
|
+
logger.error(`Failed to deserialize game: ${result.error}`);
|
|
1478
|
+
throw new StepExecutionError(result.error);
|
|
1479
|
+
}
|
|
1480
|
+
const game = unwrap(result);
|
|
1481
|
+
logger.info(`Game deserialized successfully: ${game.id}`);
|
|
1482
|
+
return game;
|
|
1483
|
+
};
|
|
1484
|
+
};
|
|
1485
|
+
|
|
1486
|
+
// src/core/domain/entities/rule.ts
|
|
1487
|
+
var getAvailablesExtraRules = (extraRules, mode, numberOfPlayers) => {
|
|
1488
|
+
const availableExtraRules = extraRules.filter((rule) => {
|
|
1489
|
+
return rule.mode.filter((m) => m.name === mode).length > 0 && (!rule.playersLimit || rule.playersLimit === numberOfPlayers);
|
|
1490
|
+
});
|
|
1491
|
+
if (availableExtraRules.length === 0) {
|
|
1492
|
+
return err("No extra rules found");
|
|
1493
|
+
}
|
|
1494
|
+
return ok(availableExtraRules);
|
|
1495
|
+
};
|
|
1496
|
+
var toExtraRule = (rule, extraRules) => {
|
|
1497
|
+
const foundRule = extraRules.find((r) => r.name === rule);
|
|
1498
|
+
if (!foundRule) {
|
|
1499
|
+
return err(`Rule not found: ${rule}`);
|
|
1500
|
+
}
|
|
1501
|
+
return ok(foundRule);
|
|
1502
|
+
};
|
|
1503
|
+
|
|
1504
|
+
// src/core/useCases/addExtraRules.ts
|
|
1505
|
+
var addExtraRulesUseCase = (deps) => (game, rules) => {
|
|
1506
|
+
const { ruleRepository } = deps;
|
|
1507
|
+
const availableExtraRules = ruleRepository.getAllExtra();
|
|
1508
|
+
const newExtraRules = [];
|
|
1509
|
+
for (const rule of rules) {
|
|
1510
|
+
const result = toExtraRule(rule, availableExtraRules);
|
|
1511
|
+
if (isErr(result)) {
|
|
1512
|
+
return result;
|
|
1513
|
+
}
|
|
1514
|
+
newExtraRules.push(result.value);
|
|
1515
|
+
}
|
|
1516
|
+
const updatedRules = {
|
|
1517
|
+
basic: game.rules.basic,
|
|
1518
|
+
extra: newExtraRules
|
|
1519
|
+
};
|
|
1520
|
+
const next = {
|
|
1521
|
+
type: "step",
|
|
1522
|
+
step: gameSteps.start
|
|
1523
|
+
};
|
|
1524
|
+
const updatedGame = {
|
|
1525
|
+
...game,
|
|
1526
|
+
rules: updatedRules,
|
|
1527
|
+
nextAction: next
|
|
1528
|
+
};
|
|
1529
|
+
return ok(updatedGame);
|
|
1530
|
+
};
|
|
1531
|
+
|
|
1334
1532
|
// src/core/domain/entities/player.ts
|
|
1335
1533
|
var MINPLAYERSNAMELENGTH = 3;
|
|
1336
1534
|
var validatePlayer = (name) => {
|
|
@@ -1943,7 +2141,7 @@ var configureEngine = (config) => {
|
|
|
1943
2141
|
const ruleRepository = jsonRules_default();
|
|
1944
2142
|
const uuid = config.uuidMethod || uuidMethod;
|
|
1945
2143
|
const shuffle = config.shuffleMethod || shuffleMethod;
|
|
1946
|
-
const logger = config.logging ?
|
|
2144
|
+
const logger = config.logger ? config.logger : config.logging ? consoleLogger(true) : silentLogger;
|
|
1947
2145
|
return {
|
|
1948
2146
|
createGameHandler: createGameHandler(
|
|
1949
2147
|
logger,
|
|
@@ -1984,9 +2182,175 @@ var configureEngine = (config) => {
|
|
|
1984
2182
|
getResultUseCase,
|
|
1985
2183
|
calculateScoreUseCase
|
|
1986
2184
|
}),
|
|
1987
|
-
calculateScoreHandler: calculateScoreHandler(logger, calculateScoreUseCase)
|
|
2185
|
+
calculateScoreHandler: calculateScoreHandler(logger, calculateScoreUseCase),
|
|
2186
|
+
getValidPlacementsHandler: getValidPlacementsHandler(logger),
|
|
2187
|
+
canPlaceDominoHandler: canPlaceDominoHandler(logger),
|
|
2188
|
+
serializeGameHandler: serializeGameHandler(logger),
|
|
2189
|
+
deserializeGameHandler: deserializeGameHandler(logger)
|
|
2190
|
+
};
|
|
2191
|
+
};
|
|
2192
|
+
|
|
2193
|
+
// src/core/events/gameEvents.ts
|
|
2194
|
+
var GameEventType = {
|
|
2195
|
+
GAME_CREATED: "game:created",
|
|
2196
|
+
PLAYERS_ADDED: "game:playersAdded",
|
|
2197
|
+
GAME_STARTED: "game:started",
|
|
2198
|
+
DOMINO_PICKED: "domino:picked",
|
|
2199
|
+
DOMINO_PLACED: "domino:placed",
|
|
2200
|
+
DOMINO_DISCARDED: "domino:discarded",
|
|
2201
|
+
TURN_CHANGED: "turn:changed",
|
|
2202
|
+
GAME_ENDED: "game:ended"
|
|
2203
|
+
};
|
|
2204
|
+
var GameEventEmitter = class {
|
|
2205
|
+
listeners = /* @__PURE__ */ new Map();
|
|
2206
|
+
/**
|
|
2207
|
+
* Subscribe to a specific event type.
|
|
2208
|
+
*
|
|
2209
|
+
* @param eventType - The event type to listen for, or "*" for all events
|
|
2210
|
+
* @param listener - The callback function to invoke
|
|
2211
|
+
* @returns Unsubscribe function
|
|
2212
|
+
*/
|
|
2213
|
+
on(eventType, listener) {
|
|
2214
|
+
const listeners = this.listeners.get(eventType) || [];
|
|
2215
|
+
listeners.push(listener);
|
|
2216
|
+
this.listeners.set(eventType, listeners);
|
|
2217
|
+
return () => {
|
|
2218
|
+
const currentListeners = this.listeners.get(eventType) || [];
|
|
2219
|
+
const index = currentListeners.indexOf(listener);
|
|
2220
|
+
if (index > -1) {
|
|
2221
|
+
currentListeners.splice(index, 1);
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Emit an event to all registered listeners.
|
|
2227
|
+
*
|
|
2228
|
+
* @param event - The event to emit
|
|
2229
|
+
*/
|
|
2230
|
+
emit(event) {
|
|
2231
|
+
const typeListeners = this.listeners.get(event.type) || [];
|
|
2232
|
+
typeListeners.forEach((listener) => listener(event));
|
|
2233
|
+
const wildcardListeners = this.listeners.get("*") || [];
|
|
2234
|
+
wildcardListeners.forEach((listener) => listener(event));
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Remove all listeners for a specific event type or all listeners.
|
|
2238
|
+
*
|
|
2239
|
+
* @param eventType - Optional event type to clear, clears all if not provided
|
|
2240
|
+
*/
|
|
2241
|
+
clear(eventType) {
|
|
2242
|
+
if (eventType) {
|
|
2243
|
+
this.listeners.delete(eventType);
|
|
2244
|
+
} else {
|
|
2245
|
+
this.listeners.clear();
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
};
|
|
2249
|
+
var createEvent = (gameId, type, data) => {
|
|
2250
|
+
return {
|
|
2251
|
+
type,
|
|
2252
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2253
|
+
gameId,
|
|
2254
|
+
...data
|
|
2255
|
+
};
|
|
2256
|
+
};
|
|
2257
|
+
|
|
2258
|
+
// src/core/i18n/translations.ts
|
|
2259
|
+
var TranslationKey = {
|
|
2260
|
+
// Error messages
|
|
2261
|
+
ERROR_INVALID_STEP: "error.invalidStep",
|
|
2262
|
+
ERROR_LORD_NOT_FOUND: "error.lordNotFound",
|
|
2263
|
+
ERROR_PLAYER_NOT_FOUND: "error.playerNotFound",
|
|
2264
|
+
ERROR_DOMINO_NOT_FOUND: "error.dominoNotFound",
|
|
2265
|
+
ERROR_INVALID_PLACEMENT: "error.invalidPlacement",
|
|
2266
|
+
ERROR_PLACEMENT_NOT_EMPTY: "error.placementNotEmpty",
|
|
2267
|
+
ERROR_PLACEMENT_NOT_ADJACENT: "error.placementNotAdjacent",
|
|
2268
|
+
ERROR_PLACEMENT_INVALID_TERRAIN: "error.placementInvalidTerrain",
|
|
2269
|
+
ERROR_PLACEMENT_OUT_OF_BOUNDS: "error.placementOutOfBounds",
|
|
2270
|
+
ERROR_NOT_YOUR_TURN: "error.notYourTurn",
|
|
2271
|
+
ERROR_CANNOT_PICK: "error.cannotPick",
|
|
2272
|
+
ERROR_CANNOT_PLACE: "error.cannotPlace",
|
|
2273
|
+
ERROR_DOMINO_ALREADY_PICKED: "error.dominoAlreadyPicked",
|
|
2274
|
+
ERROR_INVALID_PLAYER_COUNT: "error.invalidPlayerCount",
|
|
2275
|
+
ERROR_INVALID_PLAYER_NAME: "error.invalidPlayerName",
|
|
2276
|
+
// Game mode names
|
|
2277
|
+
MODE_CLASSIC: "mode.classic",
|
|
2278
|
+
// Extra rule names
|
|
2279
|
+
RULE_MIDDLE_KINGDOM: "rule.middleKingdom",
|
|
2280
|
+
RULE_HARMONY: "rule.harmony",
|
|
2281
|
+
// Ground types
|
|
2282
|
+
GROUND_CASTLE: "ground.castle",
|
|
2283
|
+
GROUND_WHEAT: "ground.wheat",
|
|
2284
|
+
GROUND_FOREST: "ground.forest",
|
|
2285
|
+
GROUND_SEA: "ground.sea",
|
|
2286
|
+
GROUND_PLAIN: "ground.plain",
|
|
2287
|
+
GROUND_SWAMP: "ground.swamp",
|
|
2288
|
+
GROUND_MINE: "ground.mine",
|
|
2289
|
+
// Game phases
|
|
2290
|
+
PHASE_ADD_PLAYERS: "phase.addPlayers",
|
|
2291
|
+
PHASE_OPTIONS: "phase.options",
|
|
2292
|
+
PHASE_START: "phase.start",
|
|
2293
|
+
PHASE_RESULT: "phase.result",
|
|
2294
|
+
// Actions
|
|
2295
|
+
ACTION_PICK_DOMINO: "action.pickDomino",
|
|
2296
|
+
ACTION_PLACE_DOMINO: "action.placeDomino",
|
|
2297
|
+
ACTION_PASS: "action.pass"
|
|
2298
|
+
};
|
|
2299
|
+
var defaultTranslations = {
|
|
2300
|
+
// Error messages
|
|
2301
|
+
[TranslationKey.ERROR_INVALID_STEP]: "Invalid game step",
|
|
2302
|
+
[TranslationKey.ERROR_LORD_NOT_FOUND]: "Lord not found",
|
|
2303
|
+
[TranslationKey.ERROR_PLAYER_NOT_FOUND]: "Player not found",
|
|
2304
|
+
[TranslationKey.ERROR_DOMINO_NOT_FOUND]: "Domino not found",
|
|
2305
|
+
[TranslationKey.ERROR_INVALID_PLACEMENT]: "Invalid placement",
|
|
2306
|
+
[TranslationKey.ERROR_PLACEMENT_NOT_EMPTY]: "Position is not empty",
|
|
2307
|
+
[TranslationKey.ERROR_PLACEMENT_NOT_ADJACENT]: "Not adjacent to existing tiles",
|
|
2308
|
+
[TranslationKey.ERROR_PLACEMENT_INVALID_TERRAIN]: "Terrain type does not match",
|
|
2309
|
+
[TranslationKey.ERROR_PLACEMENT_OUT_OF_BOUNDS]: "Placement is out of bounds",
|
|
2310
|
+
[TranslationKey.ERROR_NOT_YOUR_TURN]: "It is not your turn",
|
|
2311
|
+
[TranslationKey.ERROR_CANNOT_PICK]: "Cannot pick a domino now",
|
|
2312
|
+
[TranslationKey.ERROR_CANNOT_PLACE]: "Cannot place a domino now",
|
|
2313
|
+
[TranslationKey.ERROR_DOMINO_ALREADY_PICKED]: "Domino has already been picked",
|
|
2314
|
+
[TranslationKey.ERROR_INVALID_PLAYER_COUNT]: "Invalid number of players (2-4 required)",
|
|
2315
|
+
[TranslationKey.ERROR_INVALID_PLAYER_NAME]: "Player name must be at least 3 characters",
|
|
2316
|
+
// Game mode names
|
|
2317
|
+
[TranslationKey.MODE_CLASSIC]: "Classic",
|
|
2318
|
+
// Extra rule names
|
|
2319
|
+
[TranslationKey.RULE_MIDDLE_KINGDOM]: "The Middle Kingdom",
|
|
2320
|
+
[TranslationKey.RULE_HARMONY]: "Harmony",
|
|
2321
|
+
// Ground types
|
|
2322
|
+
[TranslationKey.GROUND_CASTLE]: "Castle",
|
|
2323
|
+
[TranslationKey.GROUND_WHEAT]: "Wheat Field",
|
|
2324
|
+
[TranslationKey.GROUND_FOREST]: "Forest",
|
|
2325
|
+
[TranslationKey.GROUND_SEA]: "Sea",
|
|
2326
|
+
[TranslationKey.GROUND_PLAIN]: "Plain",
|
|
2327
|
+
[TranslationKey.GROUND_SWAMP]: "Swamp",
|
|
2328
|
+
[TranslationKey.GROUND_MINE]: "Mine",
|
|
2329
|
+
// Game phases
|
|
2330
|
+
[TranslationKey.PHASE_ADD_PLAYERS]: "Add Players",
|
|
2331
|
+
[TranslationKey.PHASE_OPTIONS]: "Game Options",
|
|
2332
|
+
[TranslationKey.PHASE_START]: "Start Game",
|
|
2333
|
+
[TranslationKey.PHASE_RESULT]: "Game Results",
|
|
2334
|
+
// Actions
|
|
2335
|
+
[TranslationKey.ACTION_PICK_DOMINO]: "Pick Domino",
|
|
2336
|
+
[TranslationKey.ACTION_PLACE_DOMINO]: "Place Domino",
|
|
2337
|
+
[TranslationKey.ACTION_PASS]: "Pass"
|
|
2338
|
+
};
|
|
2339
|
+
var createTranslator = (translations = {}) => {
|
|
2340
|
+
const mergedTranslations = { ...defaultTranslations, ...translations };
|
|
2341
|
+
return {
|
|
2342
|
+
t(key, params) {
|
|
2343
|
+
let text = mergedTranslations[key] || key;
|
|
2344
|
+
if (params) {
|
|
2345
|
+
Object.entries(params).forEach(([paramKey, value]) => {
|
|
2346
|
+
text = text.replace(new RegExp(`{{${paramKey}}}`, "g"), String(value));
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
return text;
|
|
2350
|
+
}
|
|
1988
2351
|
};
|
|
1989
2352
|
};
|
|
2353
|
+
var defaultTranslator = createTranslator();
|
|
1990
2354
|
|
|
1991
2355
|
// src/index.ts
|
|
1992
2356
|
var createGameEngine = (config) => {
|
|
@@ -2001,7 +2365,11 @@ var createGameEngine = (config) => {
|
|
|
2001
2365
|
placeDominoHandler: placeDominoHandler2,
|
|
2002
2366
|
discardDominoHandler: discardDominoHandler2,
|
|
2003
2367
|
getResultHandler: getResultHandler2,
|
|
2004
|
-
calculateScoreHandler: calculateScoreHandler2
|
|
2368
|
+
calculateScoreHandler: calculateScoreHandler2,
|
|
2369
|
+
getValidPlacementsHandler: getValidPlacementsHandler2,
|
|
2370
|
+
canPlaceDominoHandler: canPlaceDominoHandler2,
|
|
2371
|
+
serializeGameHandler: serializeGameHandler2,
|
|
2372
|
+
deserializeGameHandler: deserializeGameHandler2
|
|
2005
2373
|
} = configureEngine(config);
|
|
2006
2374
|
return {
|
|
2007
2375
|
getModes: (command) => getModesHandler2(command),
|
|
@@ -2014,16 +2382,39 @@ var createGameEngine = (config) => {
|
|
|
2014
2382
|
placeDomino: (command) => placeDominoHandler2(command),
|
|
2015
2383
|
discardDomino: (command) => discardDominoHandler2(command),
|
|
2016
2384
|
getResults: (command) => getResultHandler2(command),
|
|
2017
|
-
calculateScore: (command) => calculateScoreHandler2(command)
|
|
2385
|
+
calculateScore: (command) => calculateScoreHandler2(command),
|
|
2386
|
+
getValidPlacements: (command) => getValidPlacementsHandler2(command),
|
|
2387
|
+
canPlaceDomino: (command) => canPlaceDominoHandler2(command),
|
|
2388
|
+
serialize: (command) => serializeGameHandler2(command),
|
|
2389
|
+
deserialize: (command) => deserializeGameHandler2(command)
|
|
2018
2390
|
};
|
|
2019
2391
|
};
|
|
2020
2392
|
export {
|
|
2393
|
+
ActionExecutionError,
|
|
2394
|
+
DomainException,
|
|
2395
|
+
ErrorCode,
|
|
2021
2396
|
GRIDSIZE,
|
|
2022
2397
|
GROUND,
|
|
2398
|
+
GameEventEmitter,
|
|
2399
|
+
GameEventType,
|
|
2400
|
+
InvalidStepError,
|
|
2401
|
+
NotFoundError,
|
|
2402
|
+
StepExecutionError,
|
|
2403
|
+
TranslationKey,
|
|
2404
|
+
ValidationError,
|
|
2405
|
+
createDomainError,
|
|
2406
|
+
createEvent,
|
|
2023
2407
|
createGameEngine,
|
|
2408
|
+
createSavePoint,
|
|
2409
|
+
createTranslator,
|
|
2410
|
+
defaultTranslations,
|
|
2411
|
+
defaultTranslator,
|
|
2412
|
+
deserializeGame,
|
|
2024
2413
|
gameSteps,
|
|
2025
2414
|
isGameWithNextAction,
|
|
2026
2415
|
isGameWithNextStep,
|
|
2027
|
-
playerActions
|
|
2416
|
+
playerActions,
|
|
2417
|
+
restoreFromSavePoint,
|
|
2418
|
+
serializeGame
|
|
2028
2419
|
};
|
|
2029
2420
|
//# sourceMappingURL=index.js.map
|