@block52/poker-vm-sdk 1.10.0 → 1.12.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.d.ts CHANGED
@@ -44960,6 +44960,14 @@ interface MsgCreateGame {
44960
44960
  * 0 = unscheduled: starts only when full. (poker-vm#2680)
44961
44961
  */
44962
44962
  startTime: Long;
44963
+ /**
44964
+ * Blind structure for a sit-and-go / tournament (field 22): JSON of the SDK's
44965
+ * BlindStructureDTO — a preset ({"preset":"turbo"}) or an explicit double /
44966
+ * linear / percentage / table structure. Empty = the blinds double every
44967
+ * blind_level_duration minutes. Cash games must leave it empty.
44968
+ * (poker-vm#1734, block52/pokerchain#434)
44969
+ */
44970
+ blindStructure: string;
44963
44971
  }
44964
44972
  declare const MsgCreateGame: MessageFns<MsgCreateGame>;
44965
44973
  /** MsgJoinGame defines the MsgJoinGame message. */
@@ -46144,6 +46152,34 @@ declare const isEmptyAddress: (address: string | null | undefined) => boolean;
46144
46152
  */
46145
46153
  declare const isValidPlayerAddress: (address: string | null | undefined) => boolean;
46146
46154
 
46155
+ /**
46156
+ * Parses the text form into the typed structure. Throws on anything the
46157
+ * engine would refuse that can be seen without expanding levels; the engine
46158
+ * validates the result again on load.
46159
+ */
46160
+ declare function parseBlindStructure(text: string): BlindStructureDTO;
46161
+ /**
46162
+ * The inverse of parseBlindStructure. A preset formats as `BLINDS <preset>`;
46163
+ * anything else as a block under `name` (default "custom").
46164
+ */
46165
+ declare function formatBlindStructure(structure: BlindStructureDTO, name?: string): string;
46166
+ /**
46167
+ * #1734's preset library, as the engine resolves it (pvm/go/engine
46168
+ * tournament_blinds.go blindPresets). The engine resolves a preset on the
46169
+ * table's first action; `resolveBlindPreset` does the same client-side so the
46170
+ * levels can be previewed before the table exists.
46171
+ */
46172
+ declare const BLIND_PRESET_STRUCTURES: Readonly<Record<BlindPreset, Exclude<BlindStructureDTO, BlindStructurePresetDTO>>>;
46173
+ /** Replaces a preset with its concrete structure (a deep copy); anything else is returned as is. */
46174
+ declare function resolveBlindPreset(structure: BlindStructureDTO): Exclude<BlindStructureDTO, BlindStructurePresetDTO>;
46175
+ /**
46176
+ * Level n (0-based) of a structure, exactly as the engine expands it: the cap
46177
+ * (`maxLevel`) freezes the blinds while the level number keeps counting; a
46178
+ * table schedule repeats its last row once it runs out; the ante rule applies
46179
+ * from its start level. A preset must be resolved first (`resolveBlindPreset`).
46180
+ */
46181
+ declare function expandBlindLevel(structure: BlindStructureDTO, n: number): BlindLevelDTO;
46182
+
46147
46183
  /**
46148
46184
  * Poker hand types in order of strength (weakest to strongest)
46149
46185
  */
@@ -46746,6 +46782,7 @@ interface SNGConfig {
46746
46782
  blindLevelDuration: number;
46747
46783
  entryFee?: bigint;
46748
46784
  startTime?: number;
46785
+ blindStructure?: BlindStructureDTO;
46749
46786
  }
46750
46787
  /**
46751
46788
  * Signing Cosmos Client that extends the read-only CosmosClient with transaction capabilities
@@ -47353,5 +47390,5 @@ declare const Client: typeof IgniteClient & Constructor<{
47353
47390
  }>;
47354
47391
  declare const registry: Registry;
47355
47392
 
47356
- export { AllPlayerActions, BLIND_PRESETS, BLIND_PROGRESSIONS, BLOCK52_HD_PATH, BettingLimit, Board, COSMOS_CONSTANTS, Client, CosmosClient, Deck, GAME_NAME_MICRO_PER_CHAR, GameFormat, GameStatus, GameVariant, HandType, KEYS, MAX_GAME_NAME_LENGTH, MSG_CREATE_VALIDATOR_TYPE_URL, MissingWalletError, NonPlayerActionType, Params, PlayerActionType, PlayerStatus, PokerGameIntegration, PokerSolver, RPCMethods, SIT_IN_METHOD_NEXT_BB, SIT_IN_METHOD_POST_NOW, SIT_OUT_METHOD_NEXT_BB, SIT_OUT_METHOD_NEXT_HAND, SUIT, SigningCosmosClient, TexasHoldemRound, TxExecutionError, ValidatorBond, WithdrawalRequest, assertCreatableBettingLimit, assertExecuted, buildMsgCreateValidator, computeGameNameFee, createSigningClientFromMnemonic, createWalletFromMnemonic, decToAtomics, formatGameFormatDisplay, formatGameVariantDisplay, generateWallet, getActionByType, getAddressFromMnemonic, getBettingLimit, getDefaultCosmosConfig, getGameFormat, getGameFormatForCosmos, getGameFormatFromObject, getGameVariant, hasAction, holeCardsPerPlayer, isCashFormat, isEmptyAddress, isPotLimit, isSitAndGoFormat, isTournamentFormat, isValidMnemonic, isValidPlayerAddress, parseGameFormat, parseGameVariant, registry, toValoperAddress, txFailureHash, txFailurePhase };
47393
+ export { AllPlayerActions, BLIND_PRESETS, BLIND_PRESET_STRUCTURES, BLIND_PROGRESSIONS, BLOCK52_HD_PATH, BettingLimit, Board, COSMOS_CONSTANTS, Client, CosmosClient, Deck, GAME_NAME_MICRO_PER_CHAR, GameFormat, GameStatus, GameVariant, HandType, KEYS, MAX_GAME_NAME_LENGTH, MSG_CREATE_VALIDATOR_TYPE_URL, MissingWalletError, NonPlayerActionType, Params, PlayerActionType, PlayerStatus, PokerGameIntegration, PokerSolver, RPCMethods, SIT_IN_METHOD_NEXT_BB, SIT_IN_METHOD_POST_NOW, SIT_OUT_METHOD_NEXT_BB, SIT_OUT_METHOD_NEXT_HAND, SUIT, SigningCosmosClient, TexasHoldemRound, TxExecutionError, ValidatorBond, WithdrawalRequest, assertCreatableBettingLimit, assertExecuted, buildMsgCreateValidator, computeGameNameFee, createSigningClientFromMnemonic, createWalletFromMnemonic, decToAtomics, expandBlindLevel, formatBlindStructure, formatGameFormatDisplay, formatGameVariantDisplay, generateWallet, getActionByType, getAddressFromMnemonic, getBettingLimit, getDefaultCosmosConfig, getGameFormat, getGameFormatForCosmos, getGameFormatFromObject, getGameVariant, hasAction, holeCardsPerPlayer, isCashFormat, isEmptyAddress, isPotLimit, isSitAndGoFormat, isTournamentFormat, isValidMnemonic, isValidPlayerAddress, parseBlindStructure, parseGameFormat, parseGameVariant, registry, resolveBlindPreset, toValoperAddress, txFailureHash, txFailurePhase };
47357
47394
  export type { AccountDTO, ActionDTO, AnteRuleDTO, BlindLevelDTO, BlindPreset, BlindProgression, BlindStructureDTO, BlindStructurePresetDTO, Block, BlockDTO, BlockHeader, BlockHeaderDTO, BlockID, BlockResponse, BurnResponseDTO, Card, CreateValidatorParams, DoubleBlindStructureDTO, EquityHand, EquityRequest, EquityResponse, EquityResult, ExecutionResult, GameListItem, GameOptions, GameOptionsDTO, GameOptionsResponse, GameStateResponse, GameStateResponseDTO, HandEvaluation, ISignedResponse, LegalActionDTO, LinearBlindStructureDTO, PayoutPlaceDTO, PercentageBlindStructureDTO, PerformActionResponse, PlayerDTO, RPCRequest, RPCRequestParams, RPCResponse, RakeConfig, RakeConfigDTO, ResultDTO, SealedAwaiting, SealedDTO, SitInMethod, SitOutMethod, TableBlindStructureDTO, TexasHoldemGameState, TexasHoldemStateDTO, TransactionDTO, TransactionResponse, TxFailurePhase, WalletInfo, WinnerDTO, WithdrawResponseDTO };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAkC/C,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEV,CAAC;AAEH,QAAA,MAAM,QAAQ,UAiCZ,CAAA;AAGF,cAAc,6BAA6B,CAAA;AAG3C,cAAc,cAAc,CAAA;AAG5B,cAAc,SAAS,CAAA;AAGvB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAGpG,cAAc,eAAe,CAAA;AAG7B,cAAc,eAAe,CAAA;AAG7B,cAAc,yBAAyB,CAAA;AAGvC,cAAc,qBAAqB,CAAA;AAGnC,cAAc,sBAAsB,CAAA;AAGpC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAGrE,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAG7B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAG/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAG7D,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACvF,OAAO,EAAE,mBAAmB,EAAE,+BAA+B,EAAE,2BAA2B,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAIpI,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAA;AAC9I,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAGvI,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAGzF,OAAO,EACL,MAAM,EACN,QAAQ,EACR,kBAAkB,EACnB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAkC/C,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEV,CAAC;AAEH,QAAA,MAAM,QAAQ,UAiCZ,CAAA;AAGF,cAAc,6BAA6B,CAAA;AAG3C,cAAc,cAAc,CAAA;AAG5B,cAAc,SAAS,CAAA;AAGvB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAGpG,cAAc,eAAe,CAAA;AAG7B,cAAc,eAAe,CAAA;AAG7B,cAAc,yBAAyB,CAAA;AAGvC,cAAc,qBAAqB,CAAA;AAGnC,cAAc,sBAAsB,CAAA;AAGpC,cAAc,wBAAwB,CAAA;AAGtC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAGrE,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAG7B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAG/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAG7D,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACvF,OAAO,EAAE,mBAAmB,EAAE,+BAA+B,EAAE,2BAA2B,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAIpI,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAA;AAC9I,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAGvI,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAGzF,OAAO,EACL,MAAM,EACN,QAAQ,EACR,kBAAkB,EACnB,CAAA"}
package/dist/index.esm.js CHANGED
@@ -2847,6 +2847,7 @@ function createBaseMsgCreateGame() {
2847
2847
  name: "",
2848
2848
  bettingLimit: "",
2849
2849
  startTime: Long.ZERO,
2850
+ blindStructure: "",
2850
2851
  };
2851
2852
  }
2852
2853
  const MsgCreateGame = {
@@ -2911,6 +2912,9 @@ const MsgCreateGame = {
2911
2912
  if (!message.startTime.equals(Long.ZERO)) {
2912
2913
  writer.uint32(168).int64(message.startTime.toString());
2913
2914
  }
2915
+ if (message.blindStructure !== "") {
2916
+ writer.uint32(178).string(message.blindStructure);
2917
+ }
2914
2918
  return writer;
2915
2919
  },
2916
2920
  decode(input, length) {
@@ -3060,6 +3064,13 @@ const MsgCreateGame = {
3060
3064
  message.startTime = Long.fromString(reader.int64().toString());
3061
3065
  continue;
3062
3066
  }
3067
+ case 22: {
3068
+ if (tag !== 178) {
3069
+ break;
3070
+ }
3071
+ message.blindStructure = reader.string();
3072
+ continue;
3073
+ }
3063
3074
  }
3064
3075
  if ((tag & 7) === 4 || tag === 0) {
3065
3076
  break;
@@ -3090,6 +3101,7 @@ const MsgCreateGame = {
3090
3101
  name: isSet$2b(object.name) ? globalThis.String(object.name) : "",
3091
3102
  bettingLimit: isSet$2b(object.bettingLimit) ? globalThis.String(object.bettingLimit) : "",
3092
3103
  startTime: isSet$2b(object.startTime) ? Long.fromValue(object.startTime) : Long.ZERO,
3104
+ blindStructure: isSet$2b(object.blindStructure) ? globalThis.String(object.blindStructure) : "",
3093
3105
  };
3094
3106
  },
3095
3107
  toJSON(message) {
@@ -3154,6 +3166,9 @@ const MsgCreateGame = {
3154
3166
  if (!message.startTime.equals(Long.ZERO)) {
3155
3167
  obj.startTime = (message.startTime || Long.ZERO).toString();
3156
3168
  }
3169
+ if (message.blindStructure !== "") {
3170
+ obj.blindStructure = message.blindStructure;
3171
+ }
3157
3172
  return obj;
3158
3173
  },
3159
3174
  create(base) {
@@ -3202,6 +3217,7 @@ const MsgCreateGame = {
3202
3217
  message.startTime = (object.startTime !== undefined && object.startTime !== null)
3203
3218
  ? Long.fromValue(object.startTime)
3204
3219
  : Long.ZERO;
3220
+ message.blindStructure = object.blindStructure ?? "";
3205
3221
  message.entryFee = (object.entryFee !== undefined && object.entryFee !== null)
3206
3222
  ? Long.fromValue(object.entryFee)
3207
3223
  : Long.UZERO;
@@ -56601,6 +56617,451 @@ const isValidPlayerAddress = (address) => {
56601
56617
  return !isEmptyAddress(address);
56602
56618
  };
56603
56619
 
56620
+ /**
56621
+ * The text form of blind structures (poker-vm#1734's "SPL", decided on #2613 /
56622
+ * #2713): a human-readable notation over the typed `BlindStructureDTO`, which
56623
+ * stays canonical in the engine and on chain. These two functions are the only
56624
+ * conversion (Commandment 12); no parser runs inside consensus code.
56625
+ *
56626
+ * BLINDS turbo a preset, no block
56627
+ *
56628
+ * BLINDS custom { algorithmic
56629
+ * start: 25/50
56630
+ * duration: 5m
56631
+ * progression: double | linear 25 | percentage 1.5
56632
+ * round: 25 percentage only (the engine's roundTo)
56633
+ * ante: bb | bb * 0.1 | level >= 4 ? bb * 0.1 : 0
56634
+ * cap: 15
56635
+ * }
56636
+ *
56637
+ * BLINDS wsop_main { table
56638
+ * duration: 120m default for rows without their own
56639
+ * ante: bb rule applied per row unless the row gives an amount
56640
+ * levels {
56641
+ * 0: 100/200
56642
+ * 1: 200/300 ante 300 duration 90m
56643
+ * 2: break 20m
56644
+ * }
56645
+ * }
56646
+ *
56647
+ * Fields may share a line, separated by spaces. `#` starts a comment. Level
56648
+ * numbers are the engine's 0-based levels everywhere (`levels` rows, `cap`,
56649
+ * `level >=`): a table row's position is its level in the engine, so rows run
56650
+ * 0, 1, 2… without gaps. Amounts are chip-unit integers; `ante` multipliers
56651
+ * and `percentage` factors have at most four decimals (basis points).
56652
+ * `parseBlindStructure(formatBlindStructure(x))` deep-equals `x`.
56653
+ */
56654
+ const BPS = 10000n;
56655
+ const IDENT = /^[A-Za-z_][\w-]*$/;
56656
+ const FIELD_KEY = /^([A-Za-z_]\w*):$/;
56657
+ const ROW_KEY = /^(\d+):$/;
56658
+ function fail(message) {
56659
+ throw new Error(`blind structure: ${message}`);
56660
+ }
56661
+ function tokenize(text) {
56662
+ const noComments = text
56663
+ .split("\n")
56664
+ .map(line => line.replace(/#.*$/, ""))
56665
+ .join("\n");
56666
+ return noComments
56667
+ .replace(/\{/g, " { ")
56668
+ .replace(/\}/g, " } ")
56669
+ .split(/\s+/)
56670
+ .filter(t => t.length > 0);
56671
+ }
56672
+ function parseAmount(what, s) {
56673
+ if (!/^\d+$/.test(s))
56674
+ fail(`${what} must be a whole number of chips, got "${s}"`);
56675
+ return BigInt(s).toString();
56676
+ }
56677
+ function parseMinutes(what, s) {
56678
+ const m = /^(\d+)(m|h)$/.exec(s);
56679
+ if (!m)
56680
+ fail(`${what} must be a duration like 5m or 2h, got "${s}"`);
56681
+ const n = Number(m[1]) * (m[2] === "h" ? 60 : 1);
56682
+ if (n <= 0)
56683
+ fail(`${what} must be positive`);
56684
+ return n;
56685
+ }
56686
+ function parseInteger(what, s) {
56687
+ if (!/^\d+$/.test(s) || !Number.isSafeInteger(Number(s)))
56688
+ fail(`${what} must be a whole number, got "${s}"`);
56689
+ return Number(s);
56690
+ }
56691
+ /** "1.5" → 15000, "0.125" → 1250 (exact; at most four decimals). */
56692
+ function parseBps(what, s) {
56693
+ const m = /^(\d+)(?:\.(\d{1,4}))?$/.exec(s);
56694
+ if (!m)
56695
+ fail(`${what} must be a decimal with at most four places, got "${s}"`);
56696
+ return Number(m[1]) * 10000 + Number((m[2] ?? "").padEnd(4, "0"));
56697
+ }
56698
+ function formatBps(bps) {
56699
+ const whole = Math.floor(bps / 10000);
56700
+ const frac = String(bps % 10000).padStart(4, "0").replace(/0+$/, "");
56701
+ return frac === "" ? String(whole) : `${whole}.${frac}`;
56702
+ }
56703
+ function parseAnteRule(expr) {
56704
+ const text = expr.replace(/\s+/g, " ").trim();
56705
+ if (text === "0")
56706
+ return undefined;
56707
+ const conditional = /^level >= (\d+) \? (.+) : 0$/.exec(text);
56708
+ const startLevel = conditional ? parseInteger("ante start level", conditional[1]) : 0;
56709
+ const multiplier = (conditional ? conditional[2] : text).replace(/\s+/g, "");
56710
+ if (multiplier === "bb")
56711
+ return { startLevel, bps: 10000 };
56712
+ const m = /^bb\*(\d+(?:\.\d+)?)$/.exec(multiplier);
56713
+ if (!m)
56714
+ fail(`ante must be "bb", "bb * <fraction>" or "level >= N ? bb * <fraction> : 0", got "${expr}"`);
56715
+ const bps = parseBps("ante fraction", m[1]);
56716
+ if (bps <= 0 || bps > 10000)
56717
+ fail(`ante fraction must be within 0..1, got "${m[1]}"`);
56718
+ return { startLevel, bps };
56719
+ }
56720
+ function formatAnteRule(rule) {
56721
+ const multiplier = rule.bps === 10000 ? "bb" : `bb * ${formatBps(rule.bps)}`;
56722
+ return rule.startLevel > 0 ? `level >= ${rule.startLevel} ? ${multiplier} : 0` : multiplier;
56723
+ }
56724
+ function anteFor(bigBlind, rule, level) {
56725
+ if (!rule || level < rule.startLevel)
56726
+ return "0";
56727
+ return ((BigInt(bigBlind) * BigInt(rule.bps)) / BPS).toString();
56728
+ }
56729
+ /** Splits the block body into `key:` fields (value = the tokens up to the next key) and the `levels { … }` rows. */
56730
+ function readBody(toks) {
56731
+ const fields = new Map();
56732
+ let rows;
56733
+ let i = 0;
56734
+ while (i < toks.length) {
56735
+ const tok = toks[i];
56736
+ if (tok === "levels") {
56737
+ if (toks[i + 1] !== "{")
56738
+ fail("levels must be followed by { … }");
56739
+ if (rows)
56740
+ fail("levels given twice");
56741
+ rows = new Map();
56742
+ i += 2;
56743
+ let current;
56744
+ for (; i < toks.length && toks[i] !== "}"; i++) {
56745
+ const row = ROW_KEY.exec(toks[i]);
56746
+ if (row) {
56747
+ current = Number(row[1]);
56748
+ if (rows.has(current))
56749
+ fail(`level ${current} given twice`);
56750
+ rows.set(current, []);
56751
+ }
56752
+ else if (current === undefined) {
56753
+ fail(`expected a level number like "0:" before "${toks[i]}"`);
56754
+ }
56755
+ else {
56756
+ rows.get(current).push(toks[i]);
56757
+ }
56758
+ }
56759
+ if (i >= toks.length)
56760
+ fail("levels block is not closed");
56761
+ i++;
56762
+ continue;
56763
+ }
56764
+ const key = FIELD_KEY.exec(tok);
56765
+ if (!key)
56766
+ fail(`expected a field like "start:" but found "${tok}"`);
56767
+ if (fields.has(key[1]))
56768
+ fail(`${key[1]} given twice`);
56769
+ const value = [];
56770
+ for (i++; i < toks.length && !FIELD_KEY.test(toks[i]) && toks[i] !== "levels"; i++)
56771
+ value.push(toks[i]);
56772
+ if (value.length === 0)
56773
+ fail(`${key[1]} has no value`);
56774
+ fields.set(key[1], value);
56775
+ }
56776
+ return { fields, rows };
56777
+ }
56778
+ const ALGORITHMIC_FIELDS = new Set(["start", "duration", "progression", "round", "ante", "cap"]);
56779
+ const TABLE_FIELDS = new Set(["duration", "ante", "cap"]);
56780
+ function parseRow(level, toks, defaults) {
56781
+ if (toks[0] === "break") {
56782
+ if (toks.length !== 2)
56783
+ fail(`level ${level}: a break is "break <duration>"`);
56784
+ return { level, smallBlind: "", bigBlind: "", ante: "", durationMinutes: parseMinutes(`level ${level} break`, toks[1]), isBreak: true };
56785
+ }
56786
+ const blinds = /^(\d+)\/(\d+)$/.exec(toks[0] ?? "");
56787
+ if (!blinds)
56788
+ fail(`level ${level}: expected "<sb>/<bb>" or "break <duration>", got "${toks.join(" ")}"`);
56789
+ const smallBlind = parseAmount(`level ${level} small blind`, blinds[1]);
56790
+ const bigBlind = parseAmount(`level ${level} big blind`, blinds[2]);
56791
+ if (BigInt(smallBlind) <= 0n || BigInt(bigBlind) < BigInt(smallBlind)) {
56792
+ fail(`level ${level}: blinds must be positive with the big blind at least the small blind`);
56793
+ }
56794
+ let ante;
56795
+ let durationMinutes;
56796
+ for (let i = 1; i < toks.length; i += 2) {
56797
+ const value = toks[i + 1];
56798
+ if (value === undefined)
56799
+ fail(`level ${level}: "${toks[i]}" needs a value`);
56800
+ if (toks[i] === "ante") {
56801
+ if (ante !== undefined)
56802
+ fail(`level ${level}: ante given twice`);
56803
+ ante = parseAmount(`level ${level} ante`, value);
56804
+ }
56805
+ else if (toks[i] === "duration") {
56806
+ if (durationMinutes !== undefined)
56807
+ fail(`level ${level}: duration given twice`);
56808
+ durationMinutes = parseMinutes(`level ${level} duration`, value);
56809
+ }
56810
+ else {
56811
+ fail(`level ${level}: unknown row option "${toks[i]}" (ante or duration)`);
56812
+ }
56813
+ }
56814
+ if (durationMinutes === undefined)
56815
+ durationMinutes = defaults.duration;
56816
+ if (durationMinutes === undefined)
56817
+ fail(`level ${level} has no duration and the structure sets no default "duration:"`);
56818
+ return { level, smallBlind, bigBlind, ante: ante ?? anteFor(bigBlind, defaults.ante, level), durationMinutes, isBreak: false };
56819
+ }
56820
+ function parseTable(body) {
56821
+ for (const key of body.fields.keys()) {
56822
+ if (!TABLE_FIELDS.has(key))
56823
+ fail(`"${key}" is not a table-mode field (a table takes its blinds from levels)`);
56824
+ }
56825
+ const defaults = {
56826
+ duration: body.fields.has("duration") ? parseMinutes("duration", body.fields.get("duration").join(" ")) : undefined,
56827
+ ante: body.fields.has("ante") ? parseAnteRule(body.fields.get("ante").join(" ")) : undefined
56828
+ };
56829
+ const rows = body.rows;
56830
+ if (rows.size === 0)
56831
+ fail("levels has no rows");
56832
+ const levels = [];
56833
+ const numbers = [...rows.keys()];
56834
+ numbers.forEach((n, i) => {
56835
+ if (n !== i)
56836
+ fail(`levels must run 0, 1, 2… in order (0-based, as the engine numbers them); found level ${n} at position ${i}`);
56837
+ levels.push(parseRow(n, rows.get(n), defaults));
56838
+ });
56839
+ if (levels[0].isBreak)
56840
+ fail("the first level must not be a break");
56841
+ if (levels[levels.length - 1].isBreak)
56842
+ fail("the last level must not be a break (it repeats once the schedule runs out)");
56843
+ const out = { progression: "table", levels };
56844
+ if (body.fields.has("cap"))
56845
+ out.maxLevel = parseInteger("cap", body.fields.get("cap").join(" "));
56846
+ return out;
56847
+ }
56848
+ function parseAlgorithmic(body) {
56849
+ const f = body.fields;
56850
+ for (const key of f.keys()) {
56851
+ if (!ALGORITHMIC_FIELDS.has(key))
56852
+ fail(`unknown field "${key}" (start, duration, progression, round, ante, cap, levels)`);
56853
+ }
56854
+ for (const required of ["start", "duration", "progression"]) {
56855
+ if (!f.has(required))
56856
+ fail(`${required} is required (or give levels { … } for a table)`);
56857
+ }
56858
+ const start = /^(\d+)\/(\d+)$/.exec(f.get("start").join(""));
56859
+ if (!start)
56860
+ fail(`start must be "<sb>/<bb>", got "${f.get("start").join(" ")}"`);
56861
+ const startSmallBlind = parseAmount("start small blind", start[1]);
56862
+ const startBigBlind = parseAmount("start big blind", start[2]);
56863
+ if (BigInt(startSmallBlind) <= 0n || BigInt(startBigBlind) !== 2n * BigInt(startSmallBlind)) {
56864
+ fail(`opening blinds ${startSmallBlind}/${startBigBlind}: the algorithmic progressions need a positive small blind and big blind = 2 × small blind`);
56865
+ }
56866
+ const base = {
56867
+ startSmallBlind,
56868
+ startBigBlind,
56869
+ durationMinutes: parseMinutes("duration", f.get("duration").join(" ")),
56870
+ ...(f.has("cap") ? { maxLevel: parseInteger("cap", f.get("cap").join(" ")) } : {}),
56871
+ ...(f.has("ante") ? (() => { const ante = parseAnteRule(f.get("ante").join(" ")); return ante ? { ante } : {}; })() : {})
56872
+ };
56873
+ const [kind, ...rest] = f.get("progression");
56874
+ switch (kind) {
56875
+ case "double":
56876
+ if (rest.length !== 0 || f.has("round"))
56877
+ fail("double takes no argument and no round");
56878
+ return { progression: "double", ...base };
56879
+ case "linear": {
56880
+ if (rest.length !== 1 || f.has("round"))
56881
+ fail('linear needs one step, e.g. "linear 25", and no round');
56882
+ const step = parseAmount("linear step", rest[0]);
56883
+ if (BigInt(step) <= 0n)
56884
+ fail("linear step must be positive");
56885
+ return { progression: "linear", ...base, step };
56886
+ }
56887
+ case "percentage": {
56888
+ if (rest.length !== 1)
56889
+ fail('percentage needs one factor, e.g. "percentage 1.5"');
56890
+ const factorBps = parseBps("percentage factor", rest[0]);
56891
+ if (factorBps <= 10000)
56892
+ fail(`percentage factor must exceed 1, got "${rest[0]}"`);
56893
+ if (!f.has("round"))
56894
+ fail('percentage needs "round: <chips>" (the engine rounds each level to a multiple of it)');
56895
+ const roundTo = parseAmount("round", f.get("round").join(" "));
56896
+ if (BigInt(roundTo) <= 0n)
56897
+ fail("round must be positive");
56898
+ return { progression: "percentage", ...base, factorBps, roundTo };
56899
+ }
56900
+ default:
56901
+ fail(`progression must be double, "linear <step>" or "percentage <factor>", got "${[kind, ...rest].join(" ")}"`);
56902
+ }
56903
+ }
56904
+ /**
56905
+ * Parses the text form into the typed structure. Throws on anything the
56906
+ * engine would refuse that can be seen without expanding levels; the engine
56907
+ * validates the result again on load.
56908
+ */
56909
+ function parseBlindStructure(text) {
56910
+ const toks = tokenize(text);
56911
+ if (toks[0] !== "BLINDS")
56912
+ fail('must start with "BLINDS <name>"');
56913
+ const name = toks[1];
56914
+ if (name === undefined || !IDENT.test(name))
56915
+ fail(`"BLINDS" needs a name, got "${name ?? ""}"`);
56916
+ if (toks.length === 2) {
56917
+ if (!BLIND_PRESETS.includes(name)) {
56918
+ fail(`"${name}" is not a preset (${BLIND_PRESETS.join(", ")}); a custom structure needs a { … } block`);
56919
+ }
56920
+ return { preset: name };
56921
+ }
56922
+ if (toks[2] !== "{" || toks[toks.length - 1] !== "}")
56923
+ fail("the structure must be a { … } block after the name");
56924
+ if (BLIND_PRESETS.includes(name))
56925
+ fail(`"${name}" is a preset and takes no block`);
56926
+ const body = readBody(toks.slice(3, -1));
56927
+ return body.rows ? parseTable(body) : parseAlgorithmic(body);
56928
+ }
56929
+ /**
56930
+ * The inverse of parseBlindStructure. A preset formats as `BLINDS <preset>`;
56931
+ * anything else as a block under `name` (default "custom").
56932
+ */
56933
+ function formatBlindStructure(structure, name = "custom") {
56934
+ if ("preset" in structure)
56935
+ return `BLINDS ${structure.preset}`;
56936
+ if (!IDENT.test(name) || BLIND_PRESETS.includes(name))
56937
+ fail(`"${name}" is not a usable structure name`);
56938
+ const lines = [];
56939
+ if (structure.progression === "table") {
56940
+ lines.push(" levels {");
56941
+ for (const l of structure.levels) {
56942
+ if (l.isBreak) {
56943
+ lines.push(` ${l.level}: break ${l.durationMinutes}m`);
56944
+ }
56945
+ else {
56946
+ const ante = l.ante !== "0" ? ` ante ${l.ante}` : "";
56947
+ lines.push(` ${l.level}: ${l.smallBlind}/${l.bigBlind}${ante} duration ${l.durationMinutes}m`);
56948
+ }
56949
+ }
56950
+ lines.push(" }");
56951
+ }
56952
+ else {
56953
+ lines.push(` start: ${structure.startSmallBlind}/${structure.startBigBlind}`);
56954
+ lines.push(` duration: ${structure.durationMinutes}m`);
56955
+ switch (structure.progression) {
56956
+ case "double":
56957
+ lines.push(" progression: double");
56958
+ break;
56959
+ case "linear":
56960
+ lines.push(` progression: linear ${structure.step}`);
56961
+ break;
56962
+ case "percentage":
56963
+ lines.push(` progression: percentage ${formatBps(structure.factorBps)}`);
56964
+ lines.push(` round: ${structure.roundTo}`);
56965
+ break;
56966
+ }
56967
+ if (structure.ante)
56968
+ lines.push(` ante: ${formatAnteRule(structure.ante)}`);
56969
+ }
56970
+ if (structure.maxLevel !== undefined)
56971
+ lines.push(` cap: ${structure.maxLevel}`);
56972
+ return [`BLINDS ${name} {`, ...lines, "}"].join("\n");
56973
+ }
56974
+ // --- Level expansion (poker-vm#2718) --------------------------------------
56975
+ //
56976
+ // A TypeScript port of the engine's ExpandLevel (pvm/go/engine/tournament_blinds.go),
56977
+ // integer-exact with BigInt, so a create-form preview or a table's "Blinds"
56978
+ // panel shows exactly the levels the engine will play. Pinned against the
56979
+ // engine's own expansions in blindStructure.test.ts.
56980
+ /**
56981
+ * #1734's preset library, as the engine resolves it (pvm/go/engine
56982
+ * tournament_blinds.go blindPresets). The engine resolves a preset on the
56983
+ * table's first action; `resolveBlindPreset` does the same client-side so the
56984
+ * levels can be previewed before the table exists.
56985
+ */
56986
+ const BLIND_PRESET_STRUCTURES = {
56987
+ turbo: { progression: "double", startSmallBlind: "25", startBigBlind: "50", durationMinutes: 5, ante: { startLevel: 4, bps: 1000 } },
56988
+ standard: { progression: "double", startSmallBlind: "25", startBigBlind: "50", durationMinutes: 10, ante: { startLevel: 5, bps: 1000 } },
56989
+ deep_stack: {
56990
+ progression: "percentage",
56991
+ startSmallBlind: "25",
56992
+ startBigBlind: "50",
56993
+ factorBps: 15000,
56994
+ roundTo: "25",
56995
+ durationMinutes: 15,
56996
+ ante: { startLevel: 6, bps: 1250 }
56997
+ },
56998
+ hyper_turbo: { progression: "double", startSmallBlind: "50", startBigBlind: "100", durationMinutes: 3, ante: { startLevel: 3, bps: 1000 } }
56999
+ };
57000
+ /** Replaces a preset with its concrete structure (a deep copy); anything else is returned as is. */
57001
+ function resolveBlindPreset(structure) {
57002
+ if (!("preset" in structure))
57003
+ return structure;
57004
+ const preset = BLIND_PRESET_STRUCTURES[structure.preset];
57005
+ if (!preset)
57006
+ fail(`"${structure.preset}" is not a preset (${BLIND_PRESETS.join(", ")})`);
57007
+ return JSON.parse(JSON.stringify(preset));
57008
+ }
57009
+ function algorithmicSmallBlind(structure, n) {
57010
+ const sb0 = BigInt(structure.startSmallBlind);
57011
+ switch (structure.progression) {
57012
+ case "double":
57013
+ return sb0 << BigInt(n);
57014
+ case "linear":
57015
+ return sb0 + BigInt(structure.step) * BigInt(n);
57016
+ case "percentage": {
57017
+ const roundTo = BigInt(structure.roundTo);
57018
+ const factor = BigInt(structure.factorBps);
57019
+ let sb = sb0;
57020
+ for (let i = 0; i < n; i++) {
57021
+ const raw = (sb * factor) / BPS;
57022
+ let q = raw / roundTo;
57023
+ const r = raw % roundTo;
57024
+ if (r * 2n >= roundTo)
57025
+ q += 1n;
57026
+ let rounded = q * roundTo;
57027
+ const floor = sb + roundTo;
57028
+ if (rounded < floor)
57029
+ rounded = floor;
57030
+ sb = rounded;
57031
+ }
57032
+ return sb;
57033
+ }
57034
+ }
57035
+ }
57036
+ /**
57037
+ * Level n (0-based) of a structure, exactly as the engine expands it: the cap
57038
+ * (`maxLevel`) freezes the blinds while the level number keeps counting; a
57039
+ * table schedule repeats its last row once it runs out; the ante rule applies
57040
+ * from its start level. A preset must be resolved first (`resolveBlindPreset`).
57041
+ */
57042
+ function expandBlindLevel(structure, n) {
57043
+ if (!Number.isInteger(n) || n < 0)
57044
+ fail(`blind level ${n} must be a non-negative integer`);
57045
+ if ("preset" in structure)
57046
+ fail(`resolve the "${structure.preset}" preset before expanding it (resolveBlindPreset)`);
57047
+ const m = structure.maxLevel !== undefined && n > structure.maxLevel ? structure.maxLevel : n;
57048
+ if (structure.progression === "table") {
57049
+ const rows = structure.levels;
57050
+ if (rows.length === 0)
57051
+ fail("a table structure needs at least one level");
57052
+ const row = { ...rows[Math.min(m, rows.length - 1)] };
57053
+ row.level = n;
57054
+ row.durationMinutes = n >= rows.length ? rows[rows.length - 1].durationMinutes : rows[n].durationMinutes;
57055
+ return row;
57056
+ }
57057
+ const sb = algorithmicSmallBlind(structure, m);
57058
+ const bb = sb * 2n;
57059
+ let ante = 0n;
57060
+ if (structure.ante && n >= structure.ante.startLevel)
57061
+ ante = (bb * BigInt(structure.ante.bps)) / BPS;
57062
+ return { level: n, smallBlind: sb.toString(), bigBlind: bb.toString(), ante: ante.toString(), durationMinutes: structure.durationMinutes, isBreak: false };
57063
+ }
57064
+
56604
57065
  /**
56605
57066
  * Poker hand types in order of strength (weakest to strongest)
56606
57067
  */
@@ -71184,8 +71645,8 @@ class SigningCosmosClient extends CosmosClient {
71184
71645
  if (sngConfig.startingStack <= 0n) {
71185
71646
  throw new Error(`${gameFormat} games require a positive startingStack; got ${sngConfig.startingStack}.`);
71186
71647
  }
71187
- if (!sngConfig.blindLevelDuration || sngConfig.blindLevelDuration <= 0) {
71188
- throw new Error(`${gameFormat} games require a positive blindLevelDuration; got ${sngConfig.blindLevelDuration}.`);
71648
+ if (!sngConfig.blindStructure && (!sngConfig.blindLevelDuration || sngConfig.blindLevelDuration <= 0)) {
71649
+ throw new Error(`${gameFormat} games require a positive blindLevelDuration (or a blindStructure); got ${sngConfig.blindLevelDuration}.`);
71189
71650
  }
71190
71651
  assertCreatableSngField(minPlayers, maxPlayers, sngConfig);
71191
71652
  }
@@ -71228,6 +71689,9 @@ class SigningCosmosClient extends CosmosClient {
71228
71689
  // Scheduled start (poker-vm#2680), unix seconds. 0 = unscheduled, and
71229
71690
  // a zero proto3 int64 is not encoded at all.
71230
71691
  startTime: sngConfig?.startTime ? Long.fromNumber(sngConfig.startTime) : Long.ZERO,
71692
+ // Blind structure (block52/pokerchain#434) as JSON; "" = doubling, and
71693
+ // an empty proto3 string is not encoded at all.
71694
+ blindStructure: sngConfig?.blindStructure ? JSON.stringify(sngConfig.blindStructure) : "",
71231
71695
  });
71232
71696
  // Create the transaction message
71233
71697
  const msg = {
@@ -71257,7 +71721,8 @@ class SigningCosmosClient extends CosmosClient {
71257
71721
  ...(sngConfig && {
71258
71722
  startingStack: sngConfig.startingStack.toString(),
71259
71723
  blindLevelDuration: sngConfig.blindLevelDuration,
71260
- entryFee: sngConfig.entryFee?.toString() ?? "0"
71724
+ entryFee: sngConfig.entryFee?.toString() ?? "0",
71725
+ ...(sngConfig.blindStructure && { blindStructure: sngConfig.blindStructure })
71261
71726
  })
71262
71727
  });
71263
71728
  try {
@@ -72178,5 +72643,5 @@ const registry = new Registry([
72178
72643
  ...msgTypes,
72179
72644
  ]);
72180
72645
 
72181
- export { AllPlayerActions, BLIND_PRESETS, BLIND_PROGRESSIONS, BLOCK52_HD_PATH, BettingLimit, Board, COSMOS_CONSTANTS, Client, CosmosClient, Deck, GAME_NAME_MICRO_PER_CHAR, GameFormat, GameStatus, GameVariant, HandType, KEYS, MAX_GAME_NAME_LENGTH, MSG_CREATE_VALIDATOR_TYPE_URL, MissingWalletError, NonPlayerActionType, Params$c as Params, PlayerActionType, PlayerStatus, PokerGameIntegration, PokerSolver, RPCMethods, SIT_IN_METHOD_NEXT_BB, SIT_IN_METHOD_POST_NOW, SIT_OUT_METHOD_NEXT_BB, SIT_OUT_METHOD_NEXT_HAND, SUIT, SigningCosmosClient, TexasHoldemRound, TxExecutionError, ValidatorBond, WithdrawalRequest, assertCreatableBettingLimit, assertExecuted, buildMsgCreateValidator, computeGameNameFee, createSigningClientFromMnemonic, createWalletFromMnemonic, decToAtomics, formatGameFormatDisplay, formatGameVariantDisplay, generateWallet, getActionByType, getAddressFromMnemonic, getBettingLimit, getDefaultCosmosConfig, getGameFormat, getGameFormatForCosmos, getGameFormatFromObject, getGameVariant, hasAction, holeCardsPerPlayer, isCashFormat, isEmptyAddress, isPotLimit, isSitAndGoFormat, isTournamentFormat, isValidMnemonic, isValidPlayerAddress, parseGameFormat, parseGameVariant, registry, toValoperAddress, txFailureHash, txFailurePhase };
72646
+ export { AllPlayerActions, BLIND_PRESETS, BLIND_PRESET_STRUCTURES, BLIND_PROGRESSIONS, BLOCK52_HD_PATH, BettingLimit, Board, COSMOS_CONSTANTS, Client, CosmosClient, Deck, GAME_NAME_MICRO_PER_CHAR, GameFormat, GameStatus, GameVariant, HandType, KEYS, MAX_GAME_NAME_LENGTH, MSG_CREATE_VALIDATOR_TYPE_URL, MissingWalletError, NonPlayerActionType, Params$c as Params, PlayerActionType, PlayerStatus, PokerGameIntegration, PokerSolver, RPCMethods, SIT_IN_METHOD_NEXT_BB, SIT_IN_METHOD_POST_NOW, SIT_OUT_METHOD_NEXT_BB, SIT_OUT_METHOD_NEXT_HAND, SUIT, SigningCosmosClient, TexasHoldemRound, TxExecutionError, ValidatorBond, WithdrawalRequest, assertCreatableBettingLimit, assertExecuted, buildMsgCreateValidator, computeGameNameFee, createSigningClientFromMnemonic, createWalletFromMnemonic, decToAtomics, expandBlindLevel, formatBlindStructure, formatGameFormatDisplay, formatGameVariantDisplay, generateWallet, getActionByType, getAddressFromMnemonic, getBettingLimit, getDefaultCosmosConfig, getGameFormat, getGameFormatForCosmos, getGameFormatFromObject, getGameVariant, hasAction, holeCardsPerPlayer, isCashFormat, isEmptyAddress, isPotLimit, isSitAndGoFormat, isTournamentFormat, isValidMnemonic, isValidPlayerAddress, parseBlindStructure, parseGameFormat, parseGameVariant, registry, resolveBlindPreset, toValoperAddress, txFailureHash, txFailurePhase };
72182
72647
  //# sourceMappingURL=index.esm.js.map