@dreamboard-games/sdk 0.4.0-alpha.0 → 0.4.0-alpha.2

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.
Files changed (84) hide show
  1. package/README.md +8 -1
  2. package/REFERENCE.md +1849 -379
  3. package/dist/{ResourceCounter-BTXklCEZ.d.ts → ResourceCounter-BuiNstXs.d.ts} +5 -1
  4. package/dist/{ThemeProvider-DvMl17Ny.d.ts → ThemeProvider-B1sIoBlX.d.ts} +1 -1
  5. package/dist/attributes-DbvyMbXw.d.ts +68 -0
  6. package/dist/authoring/index.d.ts +66 -0
  7. package/dist/authoring/index.js +560 -0
  8. package/dist/authoring/index.js.map +1 -0
  9. package/dist/browser-interaction.d.ts +246 -44
  10. package/dist/browser-interaction.js +14 -1
  11. package/dist/{chunk-Q5O7GPVY.js → chunk-45FI6XII.js} +630 -375
  12. package/dist/chunk-45FI6XII.js.map +1 -0
  13. package/dist/{chunk-4G7552LO.js → chunk-6JQFGSLD.js} +216 -47
  14. package/dist/chunk-6JQFGSLD.js.map +1 -0
  15. package/dist/{chunk-NBAEEHAU.js → chunk-CP35NPPY.js} +5926 -5266
  16. package/dist/chunk-CP35NPPY.js.map +1 -0
  17. package/dist/{chunk-2LDZ5C3T.js → chunk-FWVV2X74.js} +2 -2
  18. package/dist/{chunk-MNKDSGIA.js → chunk-HWJS3VOJ.js} +6 -2
  19. package/dist/chunk-HWJS3VOJ.js.map +1 -0
  20. package/dist/chunk-K5ZRJTZZ.js +328 -0
  21. package/dist/chunk-K5ZRJTZZ.js.map +1 -0
  22. package/dist/{chunk-U5KBV2BA.js → chunk-N6ZDIMRB.js} +444 -223
  23. package/dist/chunk-N6ZDIMRB.js.map +1 -0
  24. package/dist/chunk-O6M6PN4E.js +1225 -0
  25. package/dist/chunk-O6M6PN4E.js.map +1 -0
  26. package/dist/chunk-PF7L4BMG.js +117 -0
  27. package/dist/chunk-PF7L4BMG.js.map +1 -0
  28. package/dist/{chunk-P7F4L2FF.js → chunk-WOWFONLZ.js} +609 -1287
  29. package/dist/chunk-WOWFONLZ.js.map +1 -0
  30. package/dist/{chunk-QLG6VEMW.js → chunk-WUIVY2KE.js} +318 -21
  31. package/dist/chunk-WUIVY2KE.js.map +1 -0
  32. package/dist/chunk-ZAS2SENW.js +12 -0
  33. package/dist/chunk-ZAS2SENW.js.map +1 -0
  34. package/dist/codegen.d.ts +11 -1
  35. package/dist/codegen.js +1 -1
  36. package/dist/{components-DaIqi9QM.d.ts → components-BbWPnZCF.d.ts} +37 -70
  37. package/dist/{hex-board-view-UCUjMALd.d.ts → hex-board-view-Kz9Q_zsv.d.ts} +1 -1
  38. package/dist/index.js +2 -1
  39. package/dist/package-set.d.ts +2 -2
  40. package/dist/package-set.js +2 -1
  41. package/dist/player-state-Cqpyeql0.d.ts +371 -0
  42. package/dist/protocol-BP193lva.d.ts +267 -0
  43. package/dist/reducer/advanced.d.ts +2 -2
  44. package/dist/reducer/advanced.js +1 -1
  45. package/dist/reducer-contract.js +1 -1
  46. package/dist/reducer.d.ts +7 -7
  47. package/dist/reducer.js +190 -183
  48. package/dist/reducer.js.map +1 -1
  49. package/dist/runtime/primitives.d.ts +10 -9
  50. package/dist/runtime/primitives.js +9 -6
  51. package/dist/runtime/runtime-api.d.ts +2 -2
  52. package/dist/runtime/workspace-contract.d.ts +28 -76
  53. package/dist/runtime/workspace-contract.js +7 -5
  54. package/dist/runtime-api-BJharVaj.d.ts +198 -0
  55. package/dist/runtime-api-C6aWo9c0.d.ts +95 -0
  56. package/dist/runtime-json-CQ9QbLZ5.d.ts +5 -0
  57. package/dist/runtime.d.ts +80 -11
  58. package/dist/runtime.js +89 -445
  59. package/dist/runtime.js.map +1 -1
  60. package/dist/testing.d.ts +643 -22
  61. package/dist/testing.js +1371 -76
  62. package/dist/testing.js.map +1 -1
  63. package/dist/types-CYeN0wLK.d.ts +29 -0
  64. package/dist/{attributes-BeRyboMS.d.ts → types-DJj5MJkl.d.ts} +32 -55
  65. package/dist/types.d.ts +14 -7
  66. package/dist/ui/components.d.ts +4 -5
  67. package/dist/ui/components.js +19 -1
  68. package/dist/ui/player-state.d.ts +2 -365
  69. package/dist/ui/plugin-styles.css +1 -1
  70. package/dist/{ui-contract-BUC6iS3s.d.ts → ui-contract-CY7cxsCC.d.ts} +17 -26
  71. package/dist/ui.d.ts +8 -13
  72. package/dist/ui.js +20 -2
  73. package/dist/{views-B0hlW6IT.d.ts → views-fn2RuXOx.d.ts} +59 -44
  74. package/package.json +28 -4
  75. package/dist/chunk-4G7552LO.js.map +0 -1
  76. package/dist/chunk-MNKDSGIA.js.map +0 -1
  77. package/dist/chunk-NBAEEHAU.js.map +0 -1
  78. package/dist/chunk-P7F4L2FF.js.map +0 -1
  79. package/dist/chunk-Q5O7GPVY.js.map +0 -1
  80. package/dist/chunk-QLG6VEMW.js.map +0 -1
  81. package/dist/chunk-U5KBV2BA.js.map +0 -1
  82. package/dist/hex-color-MhOyuY-o.d.ts +0 -8
  83. package/dist/runtime-api-BXd70c2e.d.ts +0 -490
  84. /package/dist/{chunk-2LDZ5C3T.js.map → chunk-FWVV2X74.js.map} +0 -0
@@ -1195,6 +1195,13 @@ function getCardVisibility(table, cardId) {
1195
1195
  return table.visibility[cardId];
1196
1196
  }
1197
1197
 
1198
+ // src/reducer/table/numeric.ts
1199
+ function assertNonNegativeSafeInteger(value, label) {
1200
+ if (!Number.isSafeInteger(value) || value < 0) {
1201
+ throw new Error(`${label} must be a non-negative safe integer.`);
1202
+ }
1203
+ }
1204
+
1198
1205
  // src/reducer/table/resource-ops.ts
1199
1206
  function getPlayerOrder(table) {
1200
1207
  return table.playerOrder;
@@ -1212,7 +1219,14 @@ function getPlayerResourceAmount(table, playerId, resourceId) {
1212
1219
  );
1213
1220
  if (!playerResources) return 0;
1214
1221
  const value = playerResources[resourceId];
1215
- return typeof value === "number" ? value : 0;
1222
+ if (value === void 0) return 0;
1223
+ if (typeof value !== "number") {
1224
+ throw new Error(
1225
+ `Resource '${resourceId}' balance must be a non-negative safe integer.`
1226
+ );
1227
+ }
1228
+ assertNonNegativeSafeInteger(value, `Resource '${resourceId}' balance`);
1229
+ return value;
1216
1230
  }
1217
1231
  function getPlayerResourceTotal(table, playerId) {
1218
1232
  const playerResources = perPlayerGet(
@@ -1223,7 +1237,16 @@ function getPlayerResourceTotal(table, playerId) {
1223
1237
  let total = 0;
1224
1238
  for (const key of Object.keys(playerResources)) {
1225
1239
  const value = playerResources[key];
1226
- if (typeof value === "number") total += value;
1240
+ if (value === void 0) continue;
1241
+ if (typeof value !== "number") {
1242
+ throw new Error(
1243
+ `Resource '${key}' balance must be a non-negative safe integer.`
1244
+ );
1245
+ }
1246
+ assertNonNegativeSafeInteger(value, `Resource '${key}' balance`);
1247
+ const nextTotal = total + value;
1248
+ assertNonNegativeSafeInteger(nextTotal, "Resource total");
1249
+ total = nextTotal;
1227
1250
  }
1228
1251
  return total;
1229
1252
  }
@@ -1234,17 +1257,23 @@ function getNextPlayerInOrder(table, playerId) {
1234
1257
  if (idx < 0) return null;
1235
1258
  return order[(idx + 1) % order.length] ?? null;
1236
1259
  }
1237
- function forEachResourceEntry(amounts, visit) {
1260
+ function resourceEntries(amounts) {
1261
+ const entries = [];
1238
1262
  for (const resourceId of Object.keys(amounts)) {
1239
1263
  const amount = amounts[resourceId];
1240
- if (typeof amount !== "number" || amount === 0) continue;
1241
- visit(resourceId, amount);
1264
+ if (amount === void 0 || amount === 0) continue;
1265
+ if (typeof amount !== "number") {
1266
+ throw new Error(
1267
+ `Resource '${resourceId}' amount must be a non-negative safe integer.`
1268
+ );
1269
+ }
1270
+ assertNonNegativeSafeInteger(amount, `Resource '${resourceId}' amount`);
1271
+ entries.push([resourceId, amount]);
1242
1272
  }
1273
+ return entries;
1243
1274
  }
1244
1275
  function canAffordResources(table, playerId, amounts) {
1245
- for (const resourceId of Object.keys(amounts)) {
1246
- const required = amounts[resourceId];
1247
- if (typeof required !== "number" || required <= 0) continue;
1276
+ for (const [resourceId, required] of resourceEntries(amounts)) {
1248
1277
  if (getPlayerResourceAmount(table, playerId, resourceId) < required) {
1249
1278
  return false;
1250
1279
  }
@@ -1253,9 +1282,7 @@ function canAffordResources(table, playerId, amounts) {
1253
1282
  }
1254
1283
  function getMissingResources(table, playerId, amounts) {
1255
1284
  const missing = {};
1256
- for (const resourceId of Object.keys(amounts)) {
1257
- const required = amounts[resourceId];
1258
- if (typeof required !== "number" || required <= 0) continue;
1285
+ for (const [resourceId, required] of resourceEntries(amounts)) {
1259
1286
  const have = getPlayerResourceAmount(table, playerId, resourceId);
1260
1287
  if (have < required) missing[resourceId] = required - have;
1261
1288
  }
@@ -1279,14 +1306,14 @@ function addPlayerResourcesInPlace(table, playerId, amounts) {
1279
1306
  playerId
1280
1307
  ) ?? {};
1281
1308
  const next = { ...prev };
1282
- forEachResourceEntry(amounts, (resourceId, amount) => {
1283
- if (amount < 0) {
1284
- throw new Error(
1285
- `addPlayerResources: negative amount for resource '${resourceId}'. Use spendPlayerResources or transferPlayerResources instead.`
1286
- );
1287
- }
1288
- next[resourceId] = (next[resourceId] ?? 0) + amount;
1289
- });
1309
+ for (const [resourceId, amount] of resourceEntries(amounts)) {
1310
+ const nextAmount = getPlayerResourceAmount(table, playerId, resourceId) + amount;
1311
+ assertNonNegativeSafeInteger(
1312
+ nextAmount,
1313
+ `Resource '${resourceId}' balance`
1314
+ );
1315
+ next[resourceId] = nextAmount;
1316
+ }
1290
1317
  writePlayerResources(table, playerId, next);
1291
1318
  }
1292
1319
  function spendPlayerResources(table, playerId, amounts) {
@@ -1295,6 +1322,7 @@ function spendPlayerResources(table, playerId, amounts) {
1295
1322
  return nextTable;
1296
1323
  }
1297
1324
  function spendPlayerResourcesInPlace(table, playerId, amounts) {
1325
+ const entries = resourceEntries(amounts);
1298
1326
  if (!canAffordResources(table, playerId, amounts)) {
1299
1327
  const missing = getMissingResources(table, playerId, amounts);
1300
1328
  throw new Error(
@@ -1308,14 +1336,14 @@ function spendPlayerResourcesInPlace(table, playerId, amounts) {
1308
1336
  playerId
1309
1337
  ) ?? {};
1310
1338
  const next = { ...prev };
1311
- forEachResourceEntry(amounts, (resourceId, amount) => {
1312
- if (amount < 0) {
1313
- throw new Error(
1314
- `spendPlayerResources: negative amount for resource '${resourceId}'. Pass positive amounts \u2014 the op deducts them from the player.`
1315
- );
1316
- }
1317
- next[resourceId] = Math.max(0, (next[resourceId] ?? 0) - amount);
1318
- });
1339
+ for (const [resourceId, amount] of entries) {
1340
+ const nextAmount = getPlayerResourceAmount(table, playerId, resourceId) - amount;
1341
+ assertNonNegativeSafeInteger(
1342
+ nextAmount,
1343
+ `Resource '${resourceId}' balance`
1344
+ );
1345
+ next[resourceId] = nextAmount;
1346
+ }
1319
1347
  writePlayerResources(table, playerId, next);
1320
1348
  }
1321
1349
  function transferPlayerResources(table, fromPlayerId, toPlayerId, amounts) {
@@ -1324,8 +1352,42 @@ function transferPlayerResources(table, fromPlayerId, toPlayerId, amounts) {
1324
1352
  return nextTable;
1325
1353
  }
1326
1354
  function transferPlayerResourcesInPlace(table, fromPlayerId, toPlayerId, amounts) {
1327
- spendPlayerResourcesInPlace(table, fromPlayerId, amounts);
1328
- addPlayerResourcesInPlace(table, toPlayerId, amounts);
1355
+ const entries = resourceEntries(amounts);
1356
+ if (!canAffordResources(table, fromPlayerId, amounts)) {
1357
+ const missing = getMissingResources(table, fromPlayerId, amounts);
1358
+ throw new Error(
1359
+ `spendPlayerResources: player '${fromPlayerId}' cannot afford ${JSON.stringify(
1360
+ missing
1361
+ )}. Check canAfford in your validate step first.`
1362
+ );
1363
+ }
1364
+ if (fromPlayerId === toPlayerId) return;
1365
+ const fromPrev = perPlayerGet(
1366
+ table.resources,
1367
+ fromPlayerId
1368
+ ) ?? {};
1369
+ const toPrev = perPlayerGet(
1370
+ table.resources,
1371
+ toPlayerId
1372
+ ) ?? {};
1373
+ const fromNext = { ...fromPrev };
1374
+ const toNext = { ...toPrev };
1375
+ for (const [resourceId, amount] of entries) {
1376
+ const sourceBalance = getPlayerResourceAmount(table, fromPlayerId, resourceId) - amount;
1377
+ const destinationBalance = getPlayerResourceAmount(table, toPlayerId, resourceId) + amount;
1378
+ assertNonNegativeSafeInteger(
1379
+ sourceBalance,
1380
+ `Resource '${resourceId}' balance`
1381
+ );
1382
+ assertNonNegativeSafeInteger(
1383
+ destinationBalance,
1384
+ `Resource '${resourceId}' balance`
1385
+ );
1386
+ fromNext[resourceId] = sourceBalance;
1387
+ toNext[resourceId] = destinationBalance;
1388
+ }
1389
+ writePlayerResources(table, fromPlayerId, fromNext);
1390
+ writePlayerResources(table, toPlayerId, toNext);
1329
1391
  }
1330
1392
  function setPlayerResource(table, playerId, resourceId, amount) {
1331
1393
  const nextTable = { ...table };
@@ -1333,11 +1395,7 @@ function setPlayerResource(table, playerId, resourceId, amount) {
1333
1395
  return nextTable;
1334
1396
  }
1335
1397
  function setPlayerResourceInPlace(table, playerId, resourceId, amount) {
1336
- if (!Number.isFinite(amount) || amount < 0) {
1337
- throw new Error(
1338
- `setPlayerResource: amount must be a non-negative finite number, got ${amount}.`
1339
- );
1340
- }
1398
+ assertNonNegativeSafeInteger(amount, `Resource '${resourceId}' amount`);
1341
1399
  const prev = perPlayerGet(
1342
1400
  table.resources,
1343
1401
  playerId
@@ -1603,6 +1661,116 @@ function moveComponentToVertexInPlace(table, componentId, boardId, vertexId) {
1603
1661
  }
1604
1662
 
1605
1663
  // src/reducer/table/card-mutations.ts
1664
+ function sharedZoneCards(table, zoneId) {
1665
+ return [...ensureArray(table.zones.shared[zoneId] ?? table.decks[zoneId])];
1666
+ }
1667
+ function playerZoneCards(table, zoneId, playerId) {
1668
+ return [
1669
+ ...ensureArray(
1670
+ ppRead(table.zones.perPlayer[zoneId] ?? table.hands[zoneId], playerId)
1671
+ )
1672
+ ];
1673
+ }
1674
+ function assertCardInSharedZone(table, zoneId, cardId) {
1675
+ if (!sharedZoneCards(table, zoneId).includes(cardId)) {
1676
+ throw new Error(`Card '${cardId}' is not in shared zone '${zoneId}'.`);
1677
+ }
1678
+ const location = table.componentLocations[cardId];
1679
+ if (location?.type !== "InDeck" || location.deckId !== zoneId) {
1680
+ throw new Error(
1681
+ `Card '${cardId}' has a location that disagrees with shared zone '${zoneId}'.`
1682
+ );
1683
+ }
1684
+ }
1685
+ function assertCardInPlayerZone(table, zoneId, playerId, cardId) {
1686
+ if (!playerZoneCards(table, zoneId, playerId).includes(cardId)) {
1687
+ throw new Error(
1688
+ `Card '${cardId}' is not in zone '${zoneId}' for player '${playerId}'.`
1689
+ );
1690
+ }
1691
+ const location = table.componentLocations[cardId];
1692
+ if (location?.type !== "InHand" || location.handId !== zoneId || location.playerId !== playerId) {
1693
+ throw new Error(
1694
+ `Card '${cardId}' has a location that disagrees with zone '${zoneId}' for player '${playerId}'.`
1695
+ );
1696
+ }
1697
+ }
1698
+ function assertCardDetached(table, cardId) {
1699
+ const location = table.componentLocations[cardId];
1700
+ if (location?.type !== "Detached") {
1701
+ throw new Error(`Card '${cardId}' must be detached before placement.`);
1702
+ }
1703
+ }
1704
+ function assertCardAbsentFromSharedZone(table, zoneId, cardId) {
1705
+ if (sharedZoneCards(table, zoneId).includes(cardId)) {
1706
+ throw new Error(`Card '${cardId}' is already in shared zone '${zoneId}'.`);
1707
+ }
1708
+ }
1709
+ function assertCardAbsentFromPlayerZone(table, zoneId, playerId, cardId) {
1710
+ if (playerZoneCards(table, zoneId, playerId).includes(cardId)) {
1711
+ throw new Error(
1712
+ `Card '${cardId}' is already in zone '${zoneId}' for player '${playerId}'.`
1713
+ );
1714
+ }
1715
+ }
1716
+ function reindexSharedZoneCards(table, zoneId, cardIds, playedByForCard) {
1717
+ for (const [index, currentCardId] of cardIds.entries()) {
1718
+ const existing = table.componentLocations[currentCardId];
1719
+ table.componentLocations[currentCardId] = {
1720
+ type: "InDeck",
1721
+ deckId: zoneId,
1722
+ playedBy: playedByForCard?.[currentCardId] ?? (existing?.type === "InDeck" ? existing.playedBy : null),
1723
+ position: index
1724
+ };
1725
+ }
1726
+ }
1727
+ function reindexPlayerZoneCards(table, zoneId, playerId, cardIds) {
1728
+ for (const [index, currentCardId] of cardIds.entries()) {
1729
+ table.componentLocations[currentCardId] = {
1730
+ type: "InHand",
1731
+ handId: zoneId,
1732
+ playerId,
1733
+ position: index
1734
+ };
1735
+ }
1736
+ }
1737
+ function insertCard(cardIds, cardId, position = "bottom") {
1738
+ return position === "top" ? [cardId, ...cardIds] : [...cardIds, cardId];
1739
+ }
1740
+ function removeCard(cardIds, cardId) {
1741
+ return cardIds.filter((candidate) => candidate !== cardId);
1742
+ }
1743
+ function appendCardToSharedZoneCollectionInPlace(table, deckId, cardId, playedBy, position = "bottom") {
1744
+ const nextCards = insertCard(
1745
+ sharedZoneCards(table, deckId),
1746
+ cardId,
1747
+ position
1748
+ );
1749
+ syncSharedZoneWithDeck(
1750
+ table,
1751
+ deckId,
1752
+ nextCards
1753
+ );
1754
+ reindexSharedZoneCards(table, deckId, nextCards, {
1755
+ [cardId]: playedBy
1756
+ });
1757
+ }
1758
+ function removeCardFromSharedZoneCollectionInPlace(table, deckId, cardId) {
1759
+ const remaining = removeCard(
1760
+ sharedZoneCards(table, deckId),
1761
+ cardId
1762
+ );
1763
+ syncSharedZoneWithDeck(
1764
+ table,
1765
+ deckId,
1766
+ remaining
1767
+ );
1768
+ reindexSharedZoneCards(table, deckId, remaining);
1769
+ }
1770
+ function setPlayerZoneCardsInPlace(table, zoneId, playerId, cardIds) {
1771
+ syncPlayerZoneWithHand(table, zoneId, playerId, cardIds);
1772
+ reindexPlayerZoneCards(table, zoneId, playerId, cardIds);
1773
+ }
1606
1774
  function shufflePlayerZoneCards(table, zoneId, playerId, nextCards) {
1607
1775
  if (nextCards === void 0) {
1608
1776
  const fromZone = ppRead(
@@ -1634,32 +1802,16 @@ function appendToDeckInPlace(table, deckId, cardId, playedBy = null, position =
1634
1802
  "addCardToSharedZone",
1635
1803
  "zoneId"
1636
1804
  );
1637
- const deckCards = [...ensureArray(table.decks[deckId])];
1805
+ assertCardDetached(table, cardId);
1806
+ assertCardAbsentFromSharedZone(table, deckId, cardId);
1638
1807
  assertCardAllowedInZone(table, deckId, cardId);
1639
- if (position === "top") {
1640
- deckCards.unshift(cardId);
1641
- } else {
1642
- deckCards.push(cardId);
1643
- }
1644
- syncSharedZoneWithDeck(table, deckId, deckCards);
1645
- for (const [index, currentCardId] of deckCards.entries()) {
1646
- if (currentCardId === cardId) {
1647
- table.componentLocations[currentCardId] = {
1648
- type: "InDeck",
1649
- deckId,
1650
- playedBy,
1651
- position: index
1652
- };
1653
- continue;
1654
- }
1655
- const existing = table.componentLocations[currentCardId];
1656
- table.componentLocations[currentCardId] = {
1657
- type: "InDeck",
1658
- deckId,
1659
- playedBy: existing?.type === "InDeck" ? existing.playedBy : null,
1660
- position: index
1661
- };
1662
- }
1808
+ appendCardToSharedZoneCollectionInPlace(
1809
+ table,
1810
+ deckId,
1811
+ cardId,
1812
+ playedBy,
1813
+ position
1814
+ );
1663
1815
  table.ownerOfCard[cardId] = playedBy;
1664
1816
  table.visibility[cardId] = {
1665
1817
  faceUp: true
@@ -1678,19 +1830,9 @@ function removeFromDeckInPlace(table, deckId, cardId) {
1678
1830
  "removeCardFromSharedZone",
1679
1831
  "zoneId"
1680
1832
  );
1681
- const remaining = ensureArray(table.decks[deckId]).filter(
1682
- (candidate) => candidate !== cardId
1683
- );
1684
- syncSharedZoneWithDeck(table, deckId, remaining);
1685
- for (const [index, currentCardId] of remaining.entries()) {
1686
- const currentLocation = table.componentLocations[currentCardId];
1687
- if (currentLocation?.type === "InDeck") {
1688
- table.componentLocations[currentCardId] = {
1689
- ...currentLocation,
1690
- position: index
1691
- };
1692
- }
1693
- }
1833
+ assertCardInSharedZone(table, deckId, cardId);
1834
+ removeCardFromSharedZoneCollectionInPlace(table, deckId, cardId);
1835
+ table.componentLocations[cardId] = { type: "Detached" };
1694
1836
  }
1695
1837
  function computeVisibilityForPlayerZone(table, zoneId, playerId) {
1696
1838
  const mode = table.handVisibility[zoneId];
@@ -1722,34 +1864,45 @@ function moveFromHandToDeckInPlace(options) {
1722
1864
  "moveCardFromPlayerZoneToSharedZone",
1723
1865
  "toZoneId"
1724
1866
  );
1725
- const currentHand = ensureArray(
1726
- ppRead(options.table.hands[options.handId], options.playerId)
1727
- ).filter((candidate) => candidate !== options.cardId);
1728
- syncPlayerZoneWithHand(
1867
+ assertCardInPlayerZone(
1729
1868
  options.table,
1730
1869
  options.handId,
1731
1870
  options.playerId,
1732
- currentHand
1871
+ options.cardId
1733
1872
  );
1734
- for (const [index, currentCardId] of currentHand.entries()) {
1735
- options.table.componentLocations[currentCardId] = {
1736
- type: "InHand",
1737
- handId: options.handId,
1738
- playerId: options.playerId,
1739
- position: index
1740
- };
1741
- }
1742
- options.table.ownerOfCard[options.cardId] = options.playedBy ?? options.playerId;
1743
- options.table.visibility[options.cardId] = {
1744
- faceUp: true
1745
- };
1746
- appendToDeckInPlace(
1873
+ assertCardAbsentFromSharedZone(
1874
+ options.table,
1875
+ options.deckId,
1876
+ options.cardId
1877
+ );
1878
+ assertCardAllowedInZone(
1879
+ options.table,
1880
+ options.deckId,
1881
+ options.cardId
1882
+ );
1883
+ const currentHand = playerZoneCards(
1884
+ options.table,
1885
+ options.handId,
1886
+ options.playerId
1887
+ );
1888
+ const nextHand = removeCard(currentHand, options.cardId);
1889
+ setPlayerZoneCardsInPlace(
1890
+ options.table,
1891
+ options.handId,
1892
+ options.playerId,
1893
+ nextHand
1894
+ );
1895
+ appendCardToSharedZoneCollectionInPlace(
1747
1896
  options.table,
1748
1897
  options.deckId,
1749
1898
  options.cardId,
1750
1899
  options.playedBy ?? options.playerId,
1751
1900
  options.position ?? "bottom"
1752
1901
  );
1902
+ options.table.ownerOfCard[options.cardId] = options.playedBy ?? options.playerId;
1903
+ options.table.visibility[options.cardId] = {
1904
+ faceUp: true
1905
+ };
1753
1906
  }
1754
1907
  function moveCardFromPlayerZoneToSharedZone(options) {
1755
1908
  return moveFromHandToDeck({
@@ -1782,25 +1935,71 @@ function dealCardsBetweenPlayerZones(options) {
1782
1935
  return nextTable;
1783
1936
  }
1784
1937
  function dealCardsBetweenPlayerZonesInPlace(options) {
1785
- let nextTable = options.table;
1786
- for (let i = 0; i < options.count; i += 1) {
1787
- const sourceCards = ensureArray(
1788
- ppRead(
1789
- nextTable.zones.perPlayer[options.fromZoneId] ?? nextTable.hands[options.fromZoneId],
1790
- options.playerId
1791
- )
1938
+ assertNonNegativeSafeInteger(options.count, "Deal count");
1939
+ if (options.count === 0) return;
1940
+ assertZoneScope(
1941
+ options.table,
1942
+ options.fromZoneId,
1943
+ "perPlayer",
1944
+ "dealCardsBetweenPlayerZones",
1945
+ "fromZoneId"
1946
+ );
1947
+ assertZoneScope(
1948
+ options.table,
1949
+ options.toZoneId,
1950
+ "perPlayer",
1951
+ "dealCardsBetweenPlayerZones",
1952
+ "toZoneId"
1953
+ );
1954
+ if (options.fromZoneId === options.toZoneId) {
1955
+ throw new Error("Deal source and destination must differ.");
1956
+ }
1957
+ const sourceCards = playerZoneCards(
1958
+ options.table,
1959
+ options.fromZoneId,
1960
+ options.playerId
1961
+ );
1962
+ const selectedCards = sourceCards.slice(0, options.count);
1963
+ const destinationCards = playerZoneCards(
1964
+ options.table,
1965
+ options.toZoneId,
1966
+ options.playerId
1967
+ );
1968
+ for (const cardId of selectedCards) {
1969
+ assertCardInPlayerZone(
1970
+ options.table,
1971
+ options.fromZoneId,
1972
+ options.playerId,
1973
+ cardId
1974
+ );
1975
+ assertCardAbsentFromPlayerZone(
1976
+ options.table,
1977
+ options.toZoneId,
1978
+ options.playerId,
1979
+ cardId
1980
+ );
1981
+ assertCardAllowedInZone(options.table, options.toZoneId, cardId);
1982
+ }
1983
+ const remainingSource = sourceCards.slice(selectedCards.length);
1984
+ const nextDestination = [...destinationCards, ...selectedCards];
1985
+ setPlayerZoneCardsInPlace(
1986
+ options.table,
1987
+ options.fromZoneId,
1988
+ options.playerId,
1989
+ remainingSource
1990
+ );
1991
+ setPlayerZoneCardsInPlace(
1992
+ options.table,
1993
+ options.toZoneId,
1994
+ options.playerId,
1995
+ nextDestination
1996
+ );
1997
+ for (const cardId of selectedCards) {
1998
+ options.table.visibility[cardId] = computeVisibilityForPlayerZone(
1999
+ options.table,
2000
+ options.toZoneId,
2001
+ options.playerId
1792
2002
  );
1793
- const topCardId = sourceCards[0];
1794
- if (topCardId === void 0) {
1795
- break;
1796
- }
1797
- moveCardBetweenPlayerZonesInPlace({
1798
- table: nextTable,
1799
- playerId: options.playerId,
1800
- fromZoneId: options.fromZoneId,
1801
- toZoneId: options.toZoneId,
1802
- cardId: topCardId
1803
- });
1804
2003
  }
1805
2004
  }
1806
2005
  function moveCardFromSharedZoneToPlayerZone(options) {
@@ -1826,55 +2025,43 @@ function moveCardFromSharedZoneToPlayerZoneInPlace(options) {
1826
2025
  "moveCardFromSharedZoneToPlayerZone",
1827
2026
  "toZoneId"
1828
2027
  );
1829
- const sourceCards = ensureArray(
1830
- options.table.zones.shared[options.fromZoneId] ?? options.table.decks[options.fromZoneId]
2028
+ assertCardInSharedZone(
2029
+ options.table,
2030
+ options.fromZoneId,
2031
+ options.cardId
2032
+ );
2033
+ assertCardAbsentFromPlayerZone(
2034
+ options.table,
2035
+ options.toZoneId,
2036
+ options.playerId,
2037
+ options.cardId
1831
2038
  );
1832
- if (!sourceCards.includes(options.cardId)) {
1833
- throw new Error(
1834
- `Card '${String(options.cardId)}' is not in shared zone '${String(
1835
- options.fromZoneId
1836
- )}'.`
1837
- );
1838
- }
1839
2039
  assertCardAllowedInZone(
1840
2040
  options.table,
1841
2041
  options.toZoneId,
1842
2042
  options.cardId
1843
2043
  );
1844
- const remainingSource = sourceCards.filter(
1845
- (candidate) => candidate !== options.cardId
2044
+ const destinationCards = playerZoneCards(
2045
+ options.table,
2046
+ options.toZoneId,
2047
+ options.playerId
1846
2048
  );
1847
- syncSharedZoneWithDeck(options.table, options.fromZoneId, remainingSource);
1848
- for (const [index, currentCardId] of remainingSource.entries()) {
1849
- const existing = options.table.componentLocations[currentCardId];
1850
- if (existing?.type === "InDeck") {
1851
- options.table.componentLocations[currentCardId] = {
1852
- ...existing,
1853
- position: index
1854
- };
1855
- }
1856
- }
1857
- const destinationCards = ensureArray(
1858
- ppRead(
1859
- options.table.zones.perPlayer[options.toZoneId] ?? options.table.hands[options.toZoneId],
1860
- options.playerId
1861
- )
2049
+ const nextDestination = insertCard(
2050
+ destinationCards,
2051
+ options.cardId,
2052
+ options.position ?? "bottom"
2053
+ );
2054
+ removeCardFromSharedZoneCollectionInPlace(
2055
+ options.table,
2056
+ options.fromZoneId,
2057
+ options.cardId
1862
2058
  );
1863
- const nextDestination = options.position === "top" ? [options.cardId, ...destinationCards] : [...destinationCards, options.cardId];
1864
- syncPlayerZoneWithHand(
2059
+ setPlayerZoneCardsInPlace(
1865
2060
  options.table,
1866
2061
  options.toZoneId,
1867
2062
  options.playerId,
1868
2063
  nextDestination
1869
2064
  );
1870
- for (const [index, currentCardId] of nextDestination.entries()) {
1871
- options.table.componentLocations[currentCardId] = {
1872
- type: "InHand",
1873
- handId: options.toZoneId,
1874
- playerId: options.playerId,
1875
- position: index
1876
- };
1877
- }
1878
2065
  options.table.ownerOfCard[options.cardId] = options.playerId;
1879
2066
  options.table.visibility[options.cardId] = computeVisibilityForPlayerZone(
1880
2067
  options.table,
@@ -1905,63 +2092,51 @@ function moveCardBetweenPlayerZonesInPlace(options) {
1905
2092
  "moveCardBetweenPlayerZones",
1906
2093
  "toZoneId"
1907
2094
  );
1908
- const sourceCards = ensureArray(
1909
- ppRead(
1910
- options.table.zones.perPlayer[options.fromZoneId] ?? options.table.hands[options.fromZoneId],
1911
- options.playerId
1912
- )
2095
+ assertCardInPlayerZone(
2096
+ options.table,
2097
+ options.fromZoneId,
2098
+ options.playerId,
2099
+ options.cardId
2100
+ );
2101
+ assertCardAbsentFromPlayerZone(
2102
+ options.table,
2103
+ options.toZoneId,
2104
+ options.playerId,
2105
+ options.cardId
1913
2106
  );
1914
- if (!sourceCards.includes(options.cardId)) {
1915
- throw new Error(
1916
- `Card '${String(options.cardId)}' is not in zone '${String(
1917
- options.fromZoneId
1918
- )}' for player '${String(options.playerId)}'.`
1919
- );
1920
- }
1921
2107
  assertCardAllowedInZone(
1922
2108
  options.table,
1923
2109
  options.toZoneId,
1924
2110
  options.cardId
1925
2111
  );
1926
- const remainingSource = sourceCards.filter(
1927
- (candidate) => candidate !== options.cardId
2112
+ const sourceCards = playerZoneCards(
2113
+ options.table,
2114
+ options.fromZoneId,
2115
+ options.playerId
1928
2116
  );
1929
- syncPlayerZoneWithHand(
2117
+ const destinationCards = playerZoneCards(
2118
+ options.table,
2119
+ options.toZoneId,
2120
+ options.playerId
2121
+ );
2122
+ const remainingSource = removeCard(sourceCards, options.cardId);
2123
+ const nextDestination = insertCard(
2124
+ destinationCards,
2125
+ options.cardId,
2126
+ options.position ?? "bottom"
2127
+ );
2128
+ setPlayerZoneCardsInPlace(
1930
2129
  options.table,
1931
2130
  options.fromZoneId,
1932
2131
  options.playerId,
1933
2132
  remainingSource
1934
2133
  );
1935
- for (const [index, currentCardId] of remainingSource.entries()) {
1936
- const existing = options.table.componentLocations[currentCardId];
1937
- if (existing?.type === "InHand") {
1938
- options.table.componentLocations[currentCardId] = {
1939
- ...existing,
1940
- position: index
1941
- };
1942
- }
1943
- }
1944
- const destinationCards = ensureArray(
1945
- ppRead(
1946
- options.table.zones.perPlayer[options.toZoneId] ?? options.table.hands[options.toZoneId],
1947
- options.playerId
1948
- )
1949
- );
1950
- const nextDestination = options.position === "top" ? [options.cardId, ...destinationCards] : [...destinationCards, options.cardId];
1951
- syncPlayerZoneWithHand(
2134
+ setPlayerZoneCardsInPlace(
1952
2135
  options.table,
1953
2136
  options.toZoneId,
1954
2137
  options.playerId,
1955
2138
  nextDestination
1956
2139
  );
1957
- for (const [index, currentCardId] of nextDestination.entries()) {
1958
- options.table.componentLocations[currentCardId] = {
1959
- type: "InHand",
1960
- handId: options.toZoneId,
1961
- playerId: options.playerId,
1962
- position: index
1963
- };
1964
- }
1965
2140
  options.table.visibility[options.cardId] = computeVisibilityForPlayerZone(
1966
2141
  options.table,
1967
2142
  options.toZoneId,
@@ -1977,14 +2152,51 @@ function moveCardBetweenSharedZones(options) {
1977
2152
  return nextTable;
1978
2153
  }
1979
2154
  function moveCardBetweenSharedZonesInPlace(options) {
1980
- removeFromDeckInPlace(options.table, options.fromZoneId, options.cardId);
1981
- appendToDeckInPlace(
2155
+ assertZoneScope(
2156
+ options.table,
2157
+ options.fromZoneId,
2158
+ "shared",
2159
+ "moveCardBetweenSharedZones",
2160
+ "fromZoneId"
2161
+ );
2162
+ assertZoneScope(
2163
+ options.table,
2164
+ options.toZoneId,
2165
+ "shared",
2166
+ "moveCardBetweenSharedZones",
2167
+ "toZoneId"
2168
+ );
2169
+ assertCardInSharedZone(
2170
+ options.table,
2171
+ options.fromZoneId,
2172
+ options.cardId
2173
+ );
2174
+ assertCardAbsentFromSharedZone(
2175
+ options.table,
2176
+ options.toZoneId,
2177
+ options.cardId
2178
+ );
2179
+ assertCardAllowedInZone(
2180
+ options.table,
2181
+ options.toZoneId,
2182
+ options.cardId
2183
+ );
2184
+ removeCardFromSharedZoneCollectionInPlace(
2185
+ options.table,
2186
+ options.fromZoneId,
2187
+ options.cardId
2188
+ );
2189
+ appendCardToSharedZoneCollectionInPlace(
1982
2190
  options.table,
1983
2191
  options.toZoneId,
1984
2192
  options.cardId,
1985
2193
  options.playedBy ?? null,
1986
2194
  options.position ?? "bottom"
1987
2195
  );
2196
+ options.table.ownerOfCard[options.cardId] = options.playedBy ?? null;
2197
+ options.table.visibility[options.cardId] = {
2198
+ faceUp: true
2199
+ };
1988
2200
  }
1989
2201
  function removeCardFromSharedZone(table, deckId, cardId) {
1990
2202
  return removeFromDeck(table, deckId, cardId);
@@ -2010,46 +2222,55 @@ function dealCardsFromDeckToHand(table, fromZoneId, playerId, toZoneId, count) {
2010
2222
  return nextTable;
2011
2223
  }
2012
2224
  function dealCardsFromDeckToHandInPlace(table, fromZoneId, playerId, toZoneId, count) {
2013
- const publicHands = new Set(
2014
- Object.entries(table.handVisibility).filter(([, mode]) => mode === "all" || mode === "public").map(([handId]) => handId)
2225
+ assertNonNegativeSafeInteger(count, "Deal count");
2226
+ if (count === 0) return;
2227
+ assertZoneScope(
2228
+ table,
2229
+ fromZoneId,
2230
+ "shared",
2231
+ "dealCardsFromDeckToHand",
2232
+ "fromZoneId"
2015
2233
  );
2016
- for (let index = 0; index < count; index += 1) {
2017
- const nextCard = ensureArray(table.decks[fromZoneId])[0];
2018
- if (!nextCard) {
2019
- break;
2020
- }
2021
- table.decks[fromZoneId] = ensureArray(table.decks[fromZoneId]).slice(
2022
- 1
2023
- );
2024
- table.zones.shared[fromZoneId] = [
2025
- ...ensureArray(table.decks[fromZoneId])
2026
- ];
2027
- const prevHand = ppRead(table.hands[toZoneId], playerId);
2028
- const nextHand = [...ensureArray(prevHand), nextCard];
2029
- assertCardAllowedInZone(table, toZoneId, nextCard);
2030
- table.hands[toZoneId] = ppWrite(
2031
- table.hands[toZoneId],
2234
+ assertZoneScope(
2235
+ table,
2236
+ toZoneId,
2237
+ "perPlayer",
2238
+ "dealCardsFromDeckToHand",
2239
+ "toZoneId"
2240
+ );
2241
+ const sourceCards = sharedZoneCards(table, fromZoneId);
2242
+ const selectedCards = sourceCards.slice(0, count);
2243
+ const destinationCards = playerZoneCards(
2244
+ table,
2245
+ toZoneId,
2246
+ playerId
2247
+ );
2248
+ for (const cardId of selectedCards) {
2249
+ assertCardInSharedZone(table, fromZoneId, cardId);
2250
+ assertCardAbsentFromPlayerZone(
2251
+ table,
2252
+ toZoneId,
2032
2253
  playerId,
2033
- nextHand
2254
+ cardId
2034
2255
  );
2035
- table.zones.perPlayer[toZoneId] = ppWrite(
2036
- table.zones.perPlayer[toZoneId],
2037
- playerId,
2038
- [...nextHand]
2256
+ assertCardAllowedInZone(table, toZoneId, cardId);
2257
+ }
2258
+ const remainingSource = sourceCards.slice(selectedCards.length);
2259
+ const nextHand = [...destinationCards, ...selectedCards];
2260
+ syncSharedZoneWithDeck(
2261
+ table,
2262
+ fromZoneId,
2263
+ remainingSource
2264
+ );
2265
+ reindexSharedZoneCards(table, fromZoneId, remainingSource);
2266
+ setPlayerZoneCardsInPlace(table, toZoneId, playerId, nextHand);
2267
+ for (const cardId of selectedCards) {
2268
+ table.ownerOfCard[cardId] = playerId;
2269
+ table.visibility[cardId] = computeVisibilityForPlayerZone(
2270
+ table,
2271
+ toZoneId,
2272
+ playerId
2039
2273
  );
2040
- table.componentLocations[nextCard] = {
2041
- type: "InHand",
2042
- handId: toZoneId,
2043
- playerId,
2044
- position: nextHand.length - 1
2045
- };
2046
- table.ownerOfCard[nextCard] = playerId;
2047
- table.visibility[nextCard] = publicHands.has(toZoneId) ? {
2048
- faceUp: true
2049
- } : {
2050
- faceUp: false,
2051
- visibleTo: [playerId]
2052
- };
2053
2274
  }
2054
2275
  }
2055
2276
 
@@ -2651,4 +2872,4 @@ export {
2651
2872
  collectReducerDefinitionIndex,
2652
2873
  applySetupBootstrap
2653
2874
  };
2654
- //# sourceMappingURL=chunk-U5KBV2BA.js.map
2875
+ //# sourceMappingURL=chunk-N6ZDIMRB.js.map