@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/dist/index.js CHANGED
@@ -673,73 +673,132 @@ var uuidMethod = () => {
673
673
  return randomUUID();
674
674
  };
675
675
 
676
- // src/adapterServerside/winstonLogger.ts
677
- import winston from "winston";
678
- var winstonLogger = (isLoggingEnabled) => {
679
- const myFormat = winston.format.printf(
680
- ({ level, message, label, timestamp }) => {
681
- return `${timestamp} [${label}] ${level}: ${message}`;
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
- logger.info(message);
685
+ console.log(formatMessage("info", message));
700
686
  }
701
- return void 0;
702
687
  },
703
688
  error: (message) => {
704
689
  if (isLoggingEnabled) {
705
- logger.error(message);
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 InvalidStepError = class extends Error {
714
- constructor(message) {
715
- super(message);
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 Error {
720
- constructor(message) {
721
- super(message);
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 Error {
726
- constructor(message) {
727
- super(message);
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 Error {
732
- constructor(message) {
733
- super(message);
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
- const castlePosition = kingdom.map((row) => row.findIndex((tile) => tile.type === "castle")).find((index) => index !== -1);
1310
- const leftTiles = castlePosition !== void 0 ? kingdom[castlePosition].slice(0, castlePosition) : [];
1311
- const leftTilesCount = leftTiles.filter(
1312
- (tile) => tile.type !== "empty"
1313
- ).length;
1314
- const rightTiles = castlePosition !== void 0 ? kingdom[castlePosition].slice(castlePosition + 1) : [];
1315
- const rightTilesCount = rightTiles.filter(
1316
- (tile) => tile.type !== "empty"
1317
- ).length;
1318
- const topTiles = castlePosition !== void 0 ? kingdom[castlePosition].slice(0, castlePosition) : [];
1319
- const topTilesCount = topTiles.filter((tile) => tile.type !== "empty").length;
1320
- const bottomTiles = castlePosition != void 0 ? kingdom[castlePosition].slice(castlePosition + 1) : [];
1321
- const bottomTilesCount = bottomTiles.filter(
1322
- (tile) => tile.type !== "empty"
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 ? winstonLogger(true) : winstonLogger(false);
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