@block52/poker-vm-sdk 1.10.0 → 1.12.1
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 +80 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +579 -16
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +583 -15
- package/dist/index.js.map +1 -1
- package/dist/pokerGameIntegration.d.ts +38 -2
- package/dist/pokerGameIntegration.d.ts.map +1 -1
- package/dist/pokerSolver.d.ts +5 -1
- package/dist/pokerSolver.d.ts.map +1 -1
- package/dist/pokerchain.poker.v1/types/pokerchain/poker/v1/tx.d.ts +8 -0
- package/dist/pokerchain.poker.v1/types/pokerchain/poker/v1/tx.d.ts.map +1 -1
- package/dist/signingClient.d.ts +2 -1
- package/dist/signingClient.d.ts.map +1 -1
- package/dist/utils/blindStructure.d.ts +29 -0
- package/dist/utils/blindStructure.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2849,6 +2849,7 @@ function createBaseMsgCreateGame() {
|
|
|
2849
2849
|
name: "",
|
|
2850
2850
|
bettingLimit: "",
|
|
2851
2851
|
startTime: Long.ZERO,
|
|
2852
|
+
blindStructure: "",
|
|
2852
2853
|
};
|
|
2853
2854
|
}
|
|
2854
2855
|
const MsgCreateGame = {
|
|
@@ -2913,6 +2914,9 @@ const MsgCreateGame = {
|
|
|
2913
2914
|
if (!message.startTime.equals(Long.ZERO)) {
|
|
2914
2915
|
writer.uint32(168).int64(message.startTime.toString());
|
|
2915
2916
|
}
|
|
2917
|
+
if (message.blindStructure !== "") {
|
|
2918
|
+
writer.uint32(178).string(message.blindStructure);
|
|
2919
|
+
}
|
|
2916
2920
|
return writer;
|
|
2917
2921
|
},
|
|
2918
2922
|
decode(input, length) {
|
|
@@ -3062,6 +3066,13 @@ const MsgCreateGame = {
|
|
|
3062
3066
|
message.startTime = Long.fromString(reader.int64().toString());
|
|
3063
3067
|
continue;
|
|
3064
3068
|
}
|
|
3069
|
+
case 22: {
|
|
3070
|
+
if (tag !== 178) {
|
|
3071
|
+
break;
|
|
3072
|
+
}
|
|
3073
|
+
message.blindStructure = reader.string();
|
|
3074
|
+
continue;
|
|
3075
|
+
}
|
|
3065
3076
|
}
|
|
3066
3077
|
if ((tag & 7) === 4 || tag === 0) {
|
|
3067
3078
|
break;
|
|
@@ -3092,6 +3103,7 @@ const MsgCreateGame = {
|
|
|
3092
3103
|
name: isSet$2b(object.name) ? globalThis.String(object.name) : "",
|
|
3093
3104
|
bettingLimit: isSet$2b(object.bettingLimit) ? globalThis.String(object.bettingLimit) : "",
|
|
3094
3105
|
startTime: isSet$2b(object.startTime) ? Long.fromValue(object.startTime) : Long.ZERO,
|
|
3106
|
+
blindStructure: isSet$2b(object.blindStructure) ? globalThis.String(object.blindStructure) : "",
|
|
3095
3107
|
};
|
|
3096
3108
|
},
|
|
3097
3109
|
toJSON(message) {
|
|
@@ -3156,6 +3168,9 @@ const MsgCreateGame = {
|
|
|
3156
3168
|
if (!message.startTime.equals(Long.ZERO)) {
|
|
3157
3169
|
obj.startTime = (message.startTime || Long.ZERO).toString();
|
|
3158
3170
|
}
|
|
3171
|
+
if (message.blindStructure !== "") {
|
|
3172
|
+
obj.blindStructure = message.blindStructure;
|
|
3173
|
+
}
|
|
3159
3174
|
return obj;
|
|
3160
3175
|
},
|
|
3161
3176
|
create(base) {
|
|
@@ -3204,6 +3219,7 @@ const MsgCreateGame = {
|
|
|
3204
3219
|
message.startTime = (object.startTime !== undefined && object.startTime !== null)
|
|
3205
3220
|
? Long.fromValue(object.startTime)
|
|
3206
3221
|
: Long.ZERO;
|
|
3222
|
+
message.blindStructure = object.blindStructure ?? "";
|
|
3207
3223
|
message.entryFee = (object.entryFee !== undefined && object.entryFee !== null)
|
|
3208
3224
|
? Long.fromValue(object.entryFee)
|
|
3209
3225
|
: Long.UZERO;
|
|
@@ -56603,6 +56619,451 @@ const isValidPlayerAddress = (address) => {
|
|
|
56603
56619
|
return !isEmptyAddress(address);
|
|
56604
56620
|
};
|
|
56605
56621
|
|
|
56622
|
+
/**
|
|
56623
|
+
* The text form of blind structures (poker-vm#1734's "SPL", decided on #2613 /
|
|
56624
|
+
* #2713): a human-readable notation over the typed `BlindStructureDTO`, which
|
|
56625
|
+
* stays canonical in the engine and on chain. These two functions are the only
|
|
56626
|
+
* conversion (Commandment 12); no parser runs inside consensus code.
|
|
56627
|
+
*
|
|
56628
|
+
* BLINDS turbo a preset, no block
|
|
56629
|
+
*
|
|
56630
|
+
* BLINDS custom { algorithmic
|
|
56631
|
+
* start: 25/50
|
|
56632
|
+
* duration: 5m
|
|
56633
|
+
* progression: double | linear 25 | percentage 1.5
|
|
56634
|
+
* round: 25 percentage only (the engine's roundTo)
|
|
56635
|
+
* ante: bb | bb * 0.1 | level >= 4 ? bb * 0.1 : 0
|
|
56636
|
+
* cap: 15
|
|
56637
|
+
* }
|
|
56638
|
+
*
|
|
56639
|
+
* BLINDS wsop_main { table
|
|
56640
|
+
* duration: 120m default for rows without their own
|
|
56641
|
+
* ante: bb rule applied per row unless the row gives an amount
|
|
56642
|
+
* levels {
|
|
56643
|
+
* 0: 100/200
|
|
56644
|
+
* 1: 200/300 ante 300 duration 90m
|
|
56645
|
+
* 2: break 20m
|
|
56646
|
+
* }
|
|
56647
|
+
* }
|
|
56648
|
+
*
|
|
56649
|
+
* Fields may share a line, separated by spaces. `#` starts a comment. Level
|
|
56650
|
+
* numbers are the engine's 0-based levels everywhere (`levels` rows, `cap`,
|
|
56651
|
+
* `level >=`): a table row's position is its level in the engine, so rows run
|
|
56652
|
+
* 0, 1, 2… without gaps. Amounts are chip-unit integers; `ante` multipliers
|
|
56653
|
+
* and `percentage` factors have at most four decimals (basis points).
|
|
56654
|
+
* `parseBlindStructure(formatBlindStructure(x))` deep-equals `x`.
|
|
56655
|
+
*/
|
|
56656
|
+
const BPS = 10000n;
|
|
56657
|
+
const IDENT = /^[A-Za-z_][\w-]*$/;
|
|
56658
|
+
const FIELD_KEY = /^([A-Za-z_]\w*):$/;
|
|
56659
|
+
const ROW_KEY = /^(\d+):$/;
|
|
56660
|
+
function fail(message) {
|
|
56661
|
+
throw new Error(`blind structure: ${message}`);
|
|
56662
|
+
}
|
|
56663
|
+
function tokenize(text) {
|
|
56664
|
+
const noComments = text
|
|
56665
|
+
.split("\n")
|
|
56666
|
+
.map(line => line.replace(/#.*$/, ""))
|
|
56667
|
+
.join("\n");
|
|
56668
|
+
return noComments
|
|
56669
|
+
.replace(/\{/g, " { ")
|
|
56670
|
+
.replace(/\}/g, " } ")
|
|
56671
|
+
.split(/\s+/)
|
|
56672
|
+
.filter(t => t.length > 0);
|
|
56673
|
+
}
|
|
56674
|
+
function parseAmount(what, s) {
|
|
56675
|
+
if (!/^\d+$/.test(s))
|
|
56676
|
+
fail(`${what} must be a whole number of chips, got "${s}"`);
|
|
56677
|
+
return BigInt(s).toString();
|
|
56678
|
+
}
|
|
56679
|
+
function parseMinutes(what, s) {
|
|
56680
|
+
const m = /^(\d+)(m|h)$/.exec(s);
|
|
56681
|
+
if (!m)
|
|
56682
|
+
fail(`${what} must be a duration like 5m or 2h, got "${s}"`);
|
|
56683
|
+
const n = Number(m[1]) * (m[2] === "h" ? 60 : 1);
|
|
56684
|
+
if (n <= 0)
|
|
56685
|
+
fail(`${what} must be positive`);
|
|
56686
|
+
return n;
|
|
56687
|
+
}
|
|
56688
|
+
function parseInteger(what, s) {
|
|
56689
|
+
if (!/^\d+$/.test(s) || !Number.isSafeInteger(Number(s)))
|
|
56690
|
+
fail(`${what} must be a whole number, got "${s}"`);
|
|
56691
|
+
return Number(s);
|
|
56692
|
+
}
|
|
56693
|
+
/** "1.5" → 15000, "0.125" → 1250 (exact; at most four decimals). */
|
|
56694
|
+
function parseBps(what, s) {
|
|
56695
|
+
const m = /^(\d+)(?:\.(\d{1,4}))?$/.exec(s);
|
|
56696
|
+
if (!m)
|
|
56697
|
+
fail(`${what} must be a decimal with at most four places, got "${s}"`);
|
|
56698
|
+
return Number(m[1]) * 10000 + Number((m[2] ?? "").padEnd(4, "0"));
|
|
56699
|
+
}
|
|
56700
|
+
function formatBps(bps) {
|
|
56701
|
+
const whole = Math.floor(bps / 10000);
|
|
56702
|
+
const frac = String(bps % 10000).padStart(4, "0").replace(/0+$/, "");
|
|
56703
|
+
return frac === "" ? String(whole) : `${whole}.${frac}`;
|
|
56704
|
+
}
|
|
56705
|
+
function parseAnteRule(expr) {
|
|
56706
|
+
const text = expr.replace(/\s+/g, " ").trim();
|
|
56707
|
+
if (text === "0")
|
|
56708
|
+
return undefined;
|
|
56709
|
+
const conditional = /^level >= (\d+) \? (.+) : 0$/.exec(text);
|
|
56710
|
+
const startLevel = conditional ? parseInteger("ante start level", conditional[1]) : 0;
|
|
56711
|
+
const multiplier = (conditional ? conditional[2] : text).replace(/\s+/g, "");
|
|
56712
|
+
if (multiplier === "bb")
|
|
56713
|
+
return { startLevel, bps: 10000 };
|
|
56714
|
+
const m = /^bb\*(\d+(?:\.\d+)?)$/.exec(multiplier);
|
|
56715
|
+
if (!m)
|
|
56716
|
+
fail(`ante must be "bb", "bb * <fraction>" or "level >= N ? bb * <fraction> : 0", got "${expr}"`);
|
|
56717
|
+
const bps = parseBps("ante fraction", m[1]);
|
|
56718
|
+
if (bps <= 0 || bps > 10000)
|
|
56719
|
+
fail(`ante fraction must be within 0..1, got "${m[1]}"`);
|
|
56720
|
+
return { startLevel, bps };
|
|
56721
|
+
}
|
|
56722
|
+
function formatAnteRule(rule) {
|
|
56723
|
+
const multiplier = rule.bps === 10000 ? "bb" : `bb * ${formatBps(rule.bps)}`;
|
|
56724
|
+
return rule.startLevel > 0 ? `level >= ${rule.startLevel} ? ${multiplier} : 0` : multiplier;
|
|
56725
|
+
}
|
|
56726
|
+
function anteFor(bigBlind, rule, level) {
|
|
56727
|
+
if (!rule || level < rule.startLevel)
|
|
56728
|
+
return "0";
|
|
56729
|
+
return ((BigInt(bigBlind) * BigInt(rule.bps)) / BPS).toString();
|
|
56730
|
+
}
|
|
56731
|
+
/** Splits the block body into `key:` fields (value = the tokens up to the next key) and the `levels { … }` rows. */
|
|
56732
|
+
function readBody(toks) {
|
|
56733
|
+
const fields = new Map();
|
|
56734
|
+
let rows;
|
|
56735
|
+
let i = 0;
|
|
56736
|
+
while (i < toks.length) {
|
|
56737
|
+
const tok = toks[i];
|
|
56738
|
+
if (tok === "levels") {
|
|
56739
|
+
if (toks[i + 1] !== "{")
|
|
56740
|
+
fail("levels must be followed by { … }");
|
|
56741
|
+
if (rows)
|
|
56742
|
+
fail("levels given twice");
|
|
56743
|
+
rows = new Map();
|
|
56744
|
+
i += 2;
|
|
56745
|
+
let current;
|
|
56746
|
+
for (; i < toks.length && toks[i] !== "}"; i++) {
|
|
56747
|
+
const row = ROW_KEY.exec(toks[i]);
|
|
56748
|
+
if (row) {
|
|
56749
|
+
current = Number(row[1]);
|
|
56750
|
+
if (rows.has(current))
|
|
56751
|
+
fail(`level ${current} given twice`);
|
|
56752
|
+
rows.set(current, []);
|
|
56753
|
+
}
|
|
56754
|
+
else if (current === undefined) {
|
|
56755
|
+
fail(`expected a level number like "0:" before "${toks[i]}"`);
|
|
56756
|
+
}
|
|
56757
|
+
else {
|
|
56758
|
+
rows.get(current).push(toks[i]);
|
|
56759
|
+
}
|
|
56760
|
+
}
|
|
56761
|
+
if (i >= toks.length)
|
|
56762
|
+
fail("levels block is not closed");
|
|
56763
|
+
i++;
|
|
56764
|
+
continue;
|
|
56765
|
+
}
|
|
56766
|
+
const key = FIELD_KEY.exec(tok);
|
|
56767
|
+
if (!key)
|
|
56768
|
+
fail(`expected a field like "start:" but found "${tok}"`);
|
|
56769
|
+
if (fields.has(key[1]))
|
|
56770
|
+
fail(`${key[1]} given twice`);
|
|
56771
|
+
const value = [];
|
|
56772
|
+
for (i++; i < toks.length && !FIELD_KEY.test(toks[i]) && toks[i] !== "levels"; i++)
|
|
56773
|
+
value.push(toks[i]);
|
|
56774
|
+
if (value.length === 0)
|
|
56775
|
+
fail(`${key[1]} has no value`);
|
|
56776
|
+
fields.set(key[1], value);
|
|
56777
|
+
}
|
|
56778
|
+
return { fields, rows };
|
|
56779
|
+
}
|
|
56780
|
+
const ALGORITHMIC_FIELDS = new Set(["start", "duration", "progression", "round", "ante", "cap"]);
|
|
56781
|
+
const TABLE_FIELDS = new Set(["duration", "ante", "cap"]);
|
|
56782
|
+
function parseRow(level, toks, defaults) {
|
|
56783
|
+
if (toks[0] === "break") {
|
|
56784
|
+
if (toks.length !== 2)
|
|
56785
|
+
fail(`level ${level}: a break is "break <duration>"`);
|
|
56786
|
+
return { level, smallBlind: "", bigBlind: "", ante: "", durationMinutes: parseMinutes(`level ${level} break`, toks[1]), isBreak: true };
|
|
56787
|
+
}
|
|
56788
|
+
const blinds = /^(\d+)\/(\d+)$/.exec(toks[0] ?? "");
|
|
56789
|
+
if (!blinds)
|
|
56790
|
+
fail(`level ${level}: expected "<sb>/<bb>" or "break <duration>", got "${toks.join(" ")}"`);
|
|
56791
|
+
const smallBlind = parseAmount(`level ${level} small blind`, blinds[1]);
|
|
56792
|
+
const bigBlind = parseAmount(`level ${level} big blind`, blinds[2]);
|
|
56793
|
+
if (BigInt(smallBlind) <= 0n || BigInt(bigBlind) < BigInt(smallBlind)) {
|
|
56794
|
+
fail(`level ${level}: blinds must be positive with the big blind at least the small blind`);
|
|
56795
|
+
}
|
|
56796
|
+
let ante;
|
|
56797
|
+
let durationMinutes;
|
|
56798
|
+
for (let i = 1; i < toks.length; i += 2) {
|
|
56799
|
+
const value = toks[i + 1];
|
|
56800
|
+
if (value === undefined)
|
|
56801
|
+
fail(`level ${level}: "${toks[i]}" needs a value`);
|
|
56802
|
+
if (toks[i] === "ante") {
|
|
56803
|
+
if (ante !== undefined)
|
|
56804
|
+
fail(`level ${level}: ante given twice`);
|
|
56805
|
+
ante = parseAmount(`level ${level} ante`, value);
|
|
56806
|
+
}
|
|
56807
|
+
else if (toks[i] === "duration") {
|
|
56808
|
+
if (durationMinutes !== undefined)
|
|
56809
|
+
fail(`level ${level}: duration given twice`);
|
|
56810
|
+
durationMinutes = parseMinutes(`level ${level} duration`, value);
|
|
56811
|
+
}
|
|
56812
|
+
else {
|
|
56813
|
+
fail(`level ${level}: unknown row option "${toks[i]}" (ante or duration)`);
|
|
56814
|
+
}
|
|
56815
|
+
}
|
|
56816
|
+
if (durationMinutes === undefined)
|
|
56817
|
+
durationMinutes = defaults.duration;
|
|
56818
|
+
if (durationMinutes === undefined)
|
|
56819
|
+
fail(`level ${level} has no duration and the structure sets no default "duration:"`);
|
|
56820
|
+
return { level, smallBlind, bigBlind, ante: ante ?? anteFor(bigBlind, defaults.ante, level), durationMinutes, isBreak: false };
|
|
56821
|
+
}
|
|
56822
|
+
function parseTable(body) {
|
|
56823
|
+
for (const key of body.fields.keys()) {
|
|
56824
|
+
if (!TABLE_FIELDS.has(key))
|
|
56825
|
+
fail(`"${key}" is not a table-mode field (a table takes its blinds from levels)`);
|
|
56826
|
+
}
|
|
56827
|
+
const defaults = {
|
|
56828
|
+
duration: body.fields.has("duration") ? parseMinutes("duration", body.fields.get("duration").join(" ")) : undefined,
|
|
56829
|
+
ante: body.fields.has("ante") ? parseAnteRule(body.fields.get("ante").join(" ")) : undefined
|
|
56830
|
+
};
|
|
56831
|
+
const rows = body.rows;
|
|
56832
|
+
if (rows.size === 0)
|
|
56833
|
+
fail("levels has no rows");
|
|
56834
|
+
const levels = [];
|
|
56835
|
+
const numbers = [...rows.keys()];
|
|
56836
|
+
numbers.forEach((n, i) => {
|
|
56837
|
+
if (n !== i)
|
|
56838
|
+
fail(`levels must run 0, 1, 2… in order (0-based, as the engine numbers them); found level ${n} at position ${i}`);
|
|
56839
|
+
levels.push(parseRow(n, rows.get(n), defaults));
|
|
56840
|
+
});
|
|
56841
|
+
if (levels[0].isBreak)
|
|
56842
|
+
fail("the first level must not be a break");
|
|
56843
|
+
if (levels[levels.length - 1].isBreak)
|
|
56844
|
+
fail("the last level must not be a break (it repeats once the schedule runs out)");
|
|
56845
|
+
const out = { progression: "table", levels };
|
|
56846
|
+
if (body.fields.has("cap"))
|
|
56847
|
+
out.maxLevel = parseInteger("cap", body.fields.get("cap").join(" "));
|
|
56848
|
+
return out;
|
|
56849
|
+
}
|
|
56850
|
+
function parseAlgorithmic(body) {
|
|
56851
|
+
const f = body.fields;
|
|
56852
|
+
for (const key of f.keys()) {
|
|
56853
|
+
if (!ALGORITHMIC_FIELDS.has(key))
|
|
56854
|
+
fail(`unknown field "${key}" (start, duration, progression, round, ante, cap, levels)`);
|
|
56855
|
+
}
|
|
56856
|
+
for (const required of ["start", "duration", "progression"]) {
|
|
56857
|
+
if (!f.has(required))
|
|
56858
|
+
fail(`${required} is required (or give levels { … } for a table)`);
|
|
56859
|
+
}
|
|
56860
|
+
const start = /^(\d+)\/(\d+)$/.exec(f.get("start").join(""));
|
|
56861
|
+
if (!start)
|
|
56862
|
+
fail(`start must be "<sb>/<bb>", got "${f.get("start").join(" ")}"`);
|
|
56863
|
+
const startSmallBlind = parseAmount("start small blind", start[1]);
|
|
56864
|
+
const startBigBlind = parseAmount("start big blind", start[2]);
|
|
56865
|
+
if (BigInt(startSmallBlind) <= 0n || BigInt(startBigBlind) !== 2n * BigInt(startSmallBlind)) {
|
|
56866
|
+
fail(`opening blinds ${startSmallBlind}/${startBigBlind}: the algorithmic progressions need a positive small blind and big blind = 2 × small blind`);
|
|
56867
|
+
}
|
|
56868
|
+
const base = {
|
|
56869
|
+
startSmallBlind,
|
|
56870
|
+
startBigBlind,
|
|
56871
|
+
durationMinutes: parseMinutes("duration", f.get("duration").join(" ")),
|
|
56872
|
+
...(f.has("cap") ? { maxLevel: parseInteger("cap", f.get("cap").join(" ")) } : {}),
|
|
56873
|
+
...(f.has("ante") ? (() => { const ante = parseAnteRule(f.get("ante").join(" ")); return ante ? { ante } : {}; })() : {})
|
|
56874
|
+
};
|
|
56875
|
+
const [kind, ...rest] = f.get("progression");
|
|
56876
|
+
switch (kind) {
|
|
56877
|
+
case "double":
|
|
56878
|
+
if (rest.length !== 0 || f.has("round"))
|
|
56879
|
+
fail("double takes no argument and no round");
|
|
56880
|
+
return { progression: "double", ...base };
|
|
56881
|
+
case "linear": {
|
|
56882
|
+
if (rest.length !== 1 || f.has("round"))
|
|
56883
|
+
fail('linear needs one step, e.g. "linear 25", and no round');
|
|
56884
|
+
const step = parseAmount("linear step", rest[0]);
|
|
56885
|
+
if (BigInt(step) <= 0n)
|
|
56886
|
+
fail("linear step must be positive");
|
|
56887
|
+
return { progression: "linear", ...base, step };
|
|
56888
|
+
}
|
|
56889
|
+
case "percentage": {
|
|
56890
|
+
if (rest.length !== 1)
|
|
56891
|
+
fail('percentage needs one factor, e.g. "percentage 1.5"');
|
|
56892
|
+
const factorBps = parseBps("percentage factor", rest[0]);
|
|
56893
|
+
if (factorBps <= 10000)
|
|
56894
|
+
fail(`percentage factor must exceed 1, got "${rest[0]}"`);
|
|
56895
|
+
if (!f.has("round"))
|
|
56896
|
+
fail('percentage needs "round: <chips>" (the engine rounds each level to a multiple of it)');
|
|
56897
|
+
const roundTo = parseAmount("round", f.get("round").join(" "));
|
|
56898
|
+
if (BigInt(roundTo) <= 0n)
|
|
56899
|
+
fail("round must be positive");
|
|
56900
|
+
return { progression: "percentage", ...base, factorBps, roundTo };
|
|
56901
|
+
}
|
|
56902
|
+
default:
|
|
56903
|
+
fail(`progression must be double, "linear <step>" or "percentage <factor>", got "${[kind, ...rest].join(" ")}"`);
|
|
56904
|
+
}
|
|
56905
|
+
}
|
|
56906
|
+
/**
|
|
56907
|
+
* Parses the text form into the typed structure. Throws on anything the
|
|
56908
|
+
* engine would refuse that can be seen without expanding levels; the engine
|
|
56909
|
+
* validates the result again on load.
|
|
56910
|
+
*/
|
|
56911
|
+
function parseBlindStructure(text) {
|
|
56912
|
+
const toks = tokenize(text);
|
|
56913
|
+
if (toks[0] !== "BLINDS")
|
|
56914
|
+
fail('must start with "BLINDS <name>"');
|
|
56915
|
+
const name = toks[1];
|
|
56916
|
+
if (name === undefined || !IDENT.test(name))
|
|
56917
|
+
fail(`"BLINDS" needs a name, got "${name ?? ""}"`);
|
|
56918
|
+
if (toks.length === 2) {
|
|
56919
|
+
if (!BLIND_PRESETS.includes(name)) {
|
|
56920
|
+
fail(`"${name}" is not a preset (${BLIND_PRESETS.join(", ")}); a custom structure needs a { … } block`);
|
|
56921
|
+
}
|
|
56922
|
+
return { preset: name };
|
|
56923
|
+
}
|
|
56924
|
+
if (toks[2] !== "{" || toks[toks.length - 1] !== "}")
|
|
56925
|
+
fail("the structure must be a { … } block after the name");
|
|
56926
|
+
if (BLIND_PRESETS.includes(name))
|
|
56927
|
+
fail(`"${name}" is a preset and takes no block`);
|
|
56928
|
+
const body = readBody(toks.slice(3, -1));
|
|
56929
|
+
return body.rows ? parseTable(body) : parseAlgorithmic(body);
|
|
56930
|
+
}
|
|
56931
|
+
/**
|
|
56932
|
+
* The inverse of parseBlindStructure. A preset formats as `BLINDS <preset>`;
|
|
56933
|
+
* anything else as a block under `name` (default "custom").
|
|
56934
|
+
*/
|
|
56935
|
+
function formatBlindStructure(structure, name = "custom") {
|
|
56936
|
+
if ("preset" in structure)
|
|
56937
|
+
return `BLINDS ${structure.preset}`;
|
|
56938
|
+
if (!IDENT.test(name) || BLIND_PRESETS.includes(name))
|
|
56939
|
+
fail(`"${name}" is not a usable structure name`);
|
|
56940
|
+
const lines = [];
|
|
56941
|
+
if (structure.progression === "table") {
|
|
56942
|
+
lines.push(" levels {");
|
|
56943
|
+
for (const l of structure.levels) {
|
|
56944
|
+
if (l.isBreak) {
|
|
56945
|
+
lines.push(` ${l.level}: break ${l.durationMinutes}m`);
|
|
56946
|
+
}
|
|
56947
|
+
else {
|
|
56948
|
+
const ante = l.ante !== "0" ? ` ante ${l.ante}` : "";
|
|
56949
|
+
lines.push(` ${l.level}: ${l.smallBlind}/${l.bigBlind}${ante} duration ${l.durationMinutes}m`);
|
|
56950
|
+
}
|
|
56951
|
+
}
|
|
56952
|
+
lines.push(" }");
|
|
56953
|
+
}
|
|
56954
|
+
else {
|
|
56955
|
+
lines.push(` start: ${structure.startSmallBlind}/${structure.startBigBlind}`);
|
|
56956
|
+
lines.push(` duration: ${structure.durationMinutes}m`);
|
|
56957
|
+
switch (structure.progression) {
|
|
56958
|
+
case "double":
|
|
56959
|
+
lines.push(" progression: double");
|
|
56960
|
+
break;
|
|
56961
|
+
case "linear":
|
|
56962
|
+
lines.push(` progression: linear ${structure.step}`);
|
|
56963
|
+
break;
|
|
56964
|
+
case "percentage":
|
|
56965
|
+
lines.push(` progression: percentage ${formatBps(structure.factorBps)}`);
|
|
56966
|
+
lines.push(` round: ${structure.roundTo}`);
|
|
56967
|
+
break;
|
|
56968
|
+
}
|
|
56969
|
+
if (structure.ante)
|
|
56970
|
+
lines.push(` ante: ${formatAnteRule(structure.ante)}`);
|
|
56971
|
+
}
|
|
56972
|
+
if (structure.maxLevel !== undefined)
|
|
56973
|
+
lines.push(` cap: ${structure.maxLevel}`);
|
|
56974
|
+
return [`BLINDS ${name} {`, ...lines, "}"].join("\n");
|
|
56975
|
+
}
|
|
56976
|
+
// --- Level expansion (poker-vm#2718) --------------------------------------
|
|
56977
|
+
//
|
|
56978
|
+
// A TypeScript port of the engine's ExpandLevel (pvm/go/engine/tournament_blinds.go),
|
|
56979
|
+
// integer-exact with BigInt, so a create-form preview or a table's "Blinds"
|
|
56980
|
+
// panel shows exactly the levels the engine will play. Pinned against the
|
|
56981
|
+
// engine's own expansions in blindStructure.test.ts.
|
|
56982
|
+
/**
|
|
56983
|
+
* #1734's preset library, as the engine resolves it (pvm/go/engine
|
|
56984
|
+
* tournament_blinds.go blindPresets). The engine resolves a preset on the
|
|
56985
|
+
* table's first action; `resolveBlindPreset` does the same client-side so the
|
|
56986
|
+
* levels can be previewed before the table exists.
|
|
56987
|
+
*/
|
|
56988
|
+
const BLIND_PRESET_STRUCTURES = {
|
|
56989
|
+
turbo: { progression: "double", startSmallBlind: "25", startBigBlind: "50", durationMinutes: 5, ante: { startLevel: 4, bps: 1000 } },
|
|
56990
|
+
standard: { progression: "double", startSmallBlind: "25", startBigBlind: "50", durationMinutes: 10, ante: { startLevel: 5, bps: 1000 } },
|
|
56991
|
+
deep_stack: {
|
|
56992
|
+
progression: "percentage",
|
|
56993
|
+
startSmallBlind: "25",
|
|
56994
|
+
startBigBlind: "50",
|
|
56995
|
+
factorBps: 15000,
|
|
56996
|
+
roundTo: "25",
|
|
56997
|
+
durationMinutes: 15,
|
|
56998
|
+
ante: { startLevel: 6, bps: 1250 }
|
|
56999
|
+
},
|
|
57000
|
+
hyper_turbo: { progression: "double", startSmallBlind: "50", startBigBlind: "100", durationMinutes: 3, ante: { startLevel: 3, bps: 1000 } }
|
|
57001
|
+
};
|
|
57002
|
+
/** Replaces a preset with its concrete structure (a deep copy); anything else is returned as is. */
|
|
57003
|
+
function resolveBlindPreset(structure) {
|
|
57004
|
+
if (!("preset" in structure))
|
|
57005
|
+
return structure;
|
|
57006
|
+
const preset = BLIND_PRESET_STRUCTURES[structure.preset];
|
|
57007
|
+
if (!preset)
|
|
57008
|
+
fail(`"${structure.preset}" is not a preset (${BLIND_PRESETS.join(", ")})`);
|
|
57009
|
+
return JSON.parse(JSON.stringify(preset));
|
|
57010
|
+
}
|
|
57011
|
+
function algorithmicSmallBlind(structure, n) {
|
|
57012
|
+
const sb0 = BigInt(structure.startSmallBlind);
|
|
57013
|
+
switch (structure.progression) {
|
|
57014
|
+
case "double":
|
|
57015
|
+
return sb0 << BigInt(n);
|
|
57016
|
+
case "linear":
|
|
57017
|
+
return sb0 + BigInt(structure.step) * BigInt(n);
|
|
57018
|
+
case "percentage": {
|
|
57019
|
+
const roundTo = BigInt(structure.roundTo);
|
|
57020
|
+
const factor = BigInt(structure.factorBps);
|
|
57021
|
+
let sb = sb0;
|
|
57022
|
+
for (let i = 0; i < n; i++) {
|
|
57023
|
+
const raw = (sb * factor) / BPS;
|
|
57024
|
+
let q = raw / roundTo;
|
|
57025
|
+
const r = raw % roundTo;
|
|
57026
|
+
if (r * 2n >= roundTo)
|
|
57027
|
+
q += 1n;
|
|
57028
|
+
let rounded = q * roundTo;
|
|
57029
|
+
const floor = sb + roundTo;
|
|
57030
|
+
if (rounded < floor)
|
|
57031
|
+
rounded = floor;
|
|
57032
|
+
sb = rounded;
|
|
57033
|
+
}
|
|
57034
|
+
return sb;
|
|
57035
|
+
}
|
|
57036
|
+
}
|
|
57037
|
+
}
|
|
57038
|
+
/**
|
|
57039
|
+
* Level n (0-based) of a structure, exactly as the engine expands it: the cap
|
|
57040
|
+
* (`maxLevel`) freezes the blinds while the level number keeps counting; a
|
|
57041
|
+
* table schedule repeats its last row once it runs out; the ante rule applies
|
|
57042
|
+
* from its start level. A preset must be resolved first (`resolveBlindPreset`).
|
|
57043
|
+
*/
|
|
57044
|
+
function expandBlindLevel(structure, n) {
|
|
57045
|
+
if (!Number.isInteger(n) || n < 0)
|
|
57046
|
+
fail(`blind level ${n} must be a non-negative integer`);
|
|
57047
|
+
if ("preset" in structure)
|
|
57048
|
+
fail(`resolve the "${structure.preset}" preset before expanding it (resolveBlindPreset)`);
|
|
57049
|
+
const m = structure.maxLevel !== undefined && n > structure.maxLevel ? structure.maxLevel : n;
|
|
57050
|
+
if (structure.progression === "table") {
|
|
57051
|
+
const rows = structure.levels;
|
|
57052
|
+
if (rows.length === 0)
|
|
57053
|
+
fail("a table structure needs at least one level");
|
|
57054
|
+
const row = { ...rows[Math.min(m, rows.length - 1)] };
|
|
57055
|
+
row.level = n;
|
|
57056
|
+
row.durationMinutes = n >= rows.length ? rows[rows.length - 1].durationMinutes : rows[n].durationMinutes;
|
|
57057
|
+
return row;
|
|
57058
|
+
}
|
|
57059
|
+
const sb = algorithmicSmallBlind(structure, m);
|
|
57060
|
+
const bb = sb * 2n;
|
|
57061
|
+
let ante = 0n;
|
|
57062
|
+
if (structure.ante && n >= structure.ante.startLevel)
|
|
57063
|
+
ante = (bb * BigInt(structure.ante.bps)) / BPS;
|
|
57064
|
+
return { level: n, smallBlind: sb.toString(), bigBlind: bb.toString(), ante: ante.toString(), durationMinutes: structure.durationMinutes, isBreak: false };
|
|
57065
|
+
}
|
|
57066
|
+
|
|
56606
57067
|
/**
|
|
56607
57068
|
* Poker hand types in order of strength (weakest to strongest)
|
|
56608
57069
|
*/
|
|
@@ -56655,6 +57116,10 @@ class PokerSolver {
|
|
|
56655
57116
|
/**
|
|
56656
57117
|
* Evaluate a 5-card poker hand
|
|
56657
57118
|
*/
|
|
57119
|
+
/**
|
|
57120
|
+
* Evaluate exactly 5 cards (a winner's best hand, WinnerDTO.cards). Use
|
|
57121
|
+
* findBestHand for 7.
|
|
57122
|
+
*/
|
|
56658
57123
|
static evaluateHand(cards) {
|
|
56659
57124
|
if (cards.length !== 5) {
|
|
56660
57125
|
throw new Error("Hand evaluation requires exactly 5 cards");
|
|
@@ -57732,37 +58197,116 @@ class PokerGameIntegration {
|
|
|
57732
58197
|
};
|
|
57733
58198
|
}
|
|
57734
58199
|
/**
|
|
57735
|
-
*
|
|
58200
|
+
* The winning hand in words, from the winner's best five cards
|
|
58201
|
+
* (WinnerDTO.cards) and the board (communityCards): "Full House, Eights
|
|
58202
|
+
* over Sevens", "Diamond Flush, Six high", "Set of Sevens". The board tells
|
|
58203
|
+
* a set (pocket pair plus one on the board) from trips (one hole card plus a
|
|
58204
|
+
* board pair, or all three on the board), which the five cards alone can't.
|
|
58205
|
+
* @param cards The winner's five card mnemonics ("6D")
|
|
58206
|
+
* @param board The community cards
|
|
58207
|
+
* @returns The hand in words, or "" for an uncontested pot (no cards)
|
|
58208
|
+
*/
|
|
58209
|
+
static describeWinningHand(cards, board) {
|
|
58210
|
+
if (!cards || cards.length === 0)
|
|
58211
|
+
return "";
|
|
58212
|
+
if (cards.length !== 5) {
|
|
58213
|
+
throw new Error(`describeWinningHand expects the winning five cards, got ${cards.length}`);
|
|
58214
|
+
}
|
|
58215
|
+
const evaluation = PokerSolver.evaluateHand(cards.map(c => Deck.fromString(c)));
|
|
58216
|
+
if (evaluation.handType === exports.HandType.THREE_OF_A_KIND) {
|
|
58217
|
+
const tripRank = evaluation.rankValues[0];
|
|
58218
|
+
const onBoard = board.map(c => Deck.fromString(c)).filter(c => (c.rank === 1 ? 14 : c.rank) === tripRank).length;
|
|
58219
|
+
if (onBoard === 1)
|
|
58220
|
+
return `Set of ${this.rankWord(tripRank, true)}`;
|
|
58221
|
+
}
|
|
58222
|
+
return this.formatHandDescription(evaluation);
|
|
58223
|
+
}
|
|
58224
|
+
/**
|
|
58225
|
+
* Fill in each winner's name and description from its cards and the board.
|
|
58226
|
+
* The engine no longer writes the hand's wording into chain state
|
|
58227
|
+
* (poker-vm#2721), so callers run game state through this as it arrives and
|
|
58228
|
+
* keep reading winner.name / winner.description as before.
|
|
58229
|
+
*
|
|
58230
|
+
* A winner with five cards gets "Full House, Eights over Sevens" in both
|
|
58231
|
+
* fields. A winner without five cards (an uncontested pot, or an older state
|
|
58232
|
+
* that recorded hole cards) is returned unchanged.
|
|
58233
|
+
*
|
|
58234
|
+
* Returns a new state object; the input is not modified.
|
|
58235
|
+
* @param state Any game state with winners and community cards
|
|
58236
|
+
*/
|
|
58237
|
+
static withWinnerDescriptions(state) {
|
|
58238
|
+
const winners = state.winners.map(winner => {
|
|
58239
|
+
if (!winner.cards || winner.cards.length !== 5)
|
|
58240
|
+
return winner;
|
|
58241
|
+
const description = this.describeWinningHand(winner.cards, state.communityCards);
|
|
58242
|
+
return { ...winner, name: description, description };
|
|
58243
|
+
});
|
|
58244
|
+
return { ...state, winners };
|
|
58245
|
+
}
|
|
58246
|
+
/**
|
|
58247
|
+
* The hand description in words: "Full House, Eights over Sevens", "Quad
|
|
58248
|
+
* Fours", "Diamond Flush, Six high", "Ace high". A flush names its suit from
|
|
58249
|
+
* the best hand. The rank and suit words mirror @block52/deck's rankName /
|
|
58250
|
+
* suitName.
|
|
57736
58251
|
* @param evaluation Hand evaluation result
|
|
57737
58252
|
* @returns Human-readable hand description
|
|
57738
58253
|
*/
|
|
57739
58254
|
static formatHandDescription(evaluation) {
|
|
57740
|
-
const { handType, rankValues, description } = evaluation;
|
|
58255
|
+
const { handType, rankValues, description, bestHand } = evaluation;
|
|
58256
|
+
const suit = this.flushSuitWord(bestHand);
|
|
58257
|
+
const suited = (hand) => (suit ? `${suit} ${hand}` : hand);
|
|
57741
58258
|
switch (handType) {
|
|
57742
58259
|
case exports.HandType.ROYAL_FLUSH:
|
|
57743
|
-
return "Royal Flush";
|
|
58260
|
+
return suit ? `Royal Flush in ${suit}s` : "Royal Flush";
|
|
57744
58261
|
case exports.HandType.STRAIGHT_FLUSH:
|
|
57745
|
-
return
|
|
58262
|
+
return `${suited("Straight Flush")}, ${this.rankWord(rankValues[0])} high`;
|
|
57746
58263
|
case exports.HandType.FOUR_OF_A_KIND:
|
|
57747
|
-
return `
|
|
58264
|
+
return `Quad ${this.rankWord(rankValues[0], true)}`;
|
|
57748
58265
|
case exports.HandType.FULL_HOUSE:
|
|
57749
|
-
return `Full House, ${
|
|
58266
|
+
return `Full House, ${this.rankWord(rankValues[0], true)} over ${this.rankWord(rankValues[1], true)}`;
|
|
57750
58267
|
case exports.HandType.FLUSH:
|
|
57751
|
-
return
|
|
58268
|
+
return `${suited("Flush")}, ${this.rankWord(rankValues[0])} high`;
|
|
57752
58269
|
case exports.HandType.STRAIGHT:
|
|
57753
|
-
return `Straight, ${
|
|
58270
|
+
return `Straight, ${this.rankWord(rankValues[0])} high`;
|
|
57754
58271
|
case exports.HandType.THREE_OF_A_KIND:
|
|
57755
|
-
return `
|
|
58272
|
+
return `Trip ${this.rankWord(rankValues[0], true)}`;
|
|
57756
58273
|
case exports.HandType.TWO_PAIR:
|
|
57757
|
-
return `Two Pair, ${
|
|
58274
|
+
return `Two Pair, ${this.rankWord(rankValues[0], true)} and ${this.rankWord(rankValues[1], true)}`;
|
|
57758
58275
|
case exports.HandType.PAIR:
|
|
57759
|
-
return `Pair of ${
|
|
58276
|
+
return `Pair of ${this.rankWord(rankValues[0], true)}`;
|
|
57760
58277
|
case exports.HandType.HIGH_CARD:
|
|
57761
|
-
return `${
|
|
58278
|
+
return `${this.rankWord(rankValues[0])} high`;
|
|
57762
58279
|
default:
|
|
57763
58280
|
return description;
|
|
57764
58281
|
}
|
|
57765
58282
|
}
|
|
58283
|
+
/** A high-ace rank spelled out ("Ace", "Ten"); plural gives "Aces", "Sixes". */
|
|
58284
|
+
static rankWord(rank, plural = false) {
|
|
58285
|
+
const words = this.RANK_WORDS[rank === 1 ? 14 : rank];
|
|
58286
|
+
if (!words)
|
|
58287
|
+
return plural ? `${PokerSolver.formatRank(rank)}s` : PokerSolver.formatRank(rank);
|
|
58288
|
+
return plural ? words[1] : words[0];
|
|
58289
|
+
}
|
|
58290
|
+
/** The suit word every card of a flush shares ("Diamond"), or "" when the hand is empty or mixed. */
|
|
58291
|
+
static flushSuitWord(cards) {
|
|
58292
|
+
if (!cards || cards.length === 0)
|
|
58293
|
+
return "";
|
|
58294
|
+
const suit = cards[0].suit;
|
|
58295
|
+
if (cards.some(c => c.suit !== suit))
|
|
58296
|
+
return "";
|
|
58297
|
+
switch (suit) {
|
|
58298
|
+
case exports.SUIT.CLUBS:
|
|
58299
|
+
return "Club";
|
|
58300
|
+
case exports.SUIT.DIAMONDS:
|
|
58301
|
+
return "Diamond";
|
|
58302
|
+
case exports.SUIT.HEARTS:
|
|
58303
|
+
return "Heart";
|
|
58304
|
+
case exports.SUIT.SPADES:
|
|
58305
|
+
return "Spade";
|
|
58306
|
+
default:
|
|
58307
|
+
return "";
|
|
58308
|
+
}
|
|
58309
|
+
}
|
|
57766
58310
|
/**
|
|
57767
58311
|
* Example usage with string cards (for testing/debugging)
|
|
57768
58312
|
* @param playerCardStrings Array of player hands as mnemonic strings
|
|
@@ -57783,6 +58327,21 @@ class PokerGameIntegration {
|
|
|
57783
58327
|
return { winners, results };
|
|
57784
58328
|
}
|
|
57785
58329
|
}
|
|
58330
|
+
PokerGameIntegration.RANK_WORDS = {
|
|
58331
|
+
14: ["Ace", "Aces"],
|
|
58332
|
+
13: ["King", "Kings"],
|
|
58333
|
+
12: ["Queen", "Queens"],
|
|
58334
|
+
11: ["Jack", "Jacks"],
|
|
58335
|
+
10: ["Ten", "Tens"],
|
|
58336
|
+
9: ["Nine", "Nines"],
|
|
58337
|
+
8: ["Eight", "Eights"],
|
|
58338
|
+
7: ["Seven", "Sevens"],
|
|
58339
|
+
6: ["Six", "Sixes"],
|
|
58340
|
+
5: ["Five", "Fives"],
|
|
58341
|
+
4: ["Four", "Fours"],
|
|
58342
|
+
3: ["Three", "Threes"],
|
|
58343
|
+
2: ["Two", "Twos"]
|
|
58344
|
+
};
|
|
57786
58345
|
// Example usage:
|
|
57787
58346
|
/*
|
|
57788
58347
|
const example = PokerGameIntegration.exampleShowdown([
|
|
@@ -71186,8 +71745,8 @@ class SigningCosmosClient extends CosmosClient {
|
|
|
71186
71745
|
if (sngConfig.startingStack <= 0n) {
|
|
71187
71746
|
throw new Error(`${gameFormat} games require a positive startingStack; got ${sngConfig.startingStack}.`);
|
|
71188
71747
|
}
|
|
71189
|
-
if (!sngConfig.blindLevelDuration || sngConfig.blindLevelDuration <= 0) {
|
|
71190
|
-
throw new Error(`${gameFormat} games require a positive blindLevelDuration; got ${sngConfig.blindLevelDuration}.`);
|
|
71748
|
+
if (!sngConfig.blindStructure && (!sngConfig.blindLevelDuration || sngConfig.blindLevelDuration <= 0)) {
|
|
71749
|
+
throw new Error(`${gameFormat} games require a positive blindLevelDuration (or a blindStructure); got ${sngConfig.blindLevelDuration}.`);
|
|
71191
71750
|
}
|
|
71192
71751
|
assertCreatableSngField(minPlayers, maxPlayers, sngConfig);
|
|
71193
71752
|
}
|
|
@@ -71230,6 +71789,9 @@ class SigningCosmosClient extends CosmosClient {
|
|
|
71230
71789
|
// Scheduled start (poker-vm#2680), unix seconds. 0 = unscheduled, and
|
|
71231
71790
|
// a zero proto3 int64 is not encoded at all.
|
|
71232
71791
|
startTime: sngConfig?.startTime ? Long.fromNumber(sngConfig.startTime) : Long.ZERO,
|
|
71792
|
+
// Blind structure (block52/pokerchain#434) as JSON; "" = doubling, and
|
|
71793
|
+
// an empty proto3 string is not encoded at all.
|
|
71794
|
+
blindStructure: sngConfig?.blindStructure ? JSON.stringify(sngConfig.blindStructure) : "",
|
|
71233
71795
|
});
|
|
71234
71796
|
// Create the transaction message
|
|
71235
71797
|
const msg = {
|
|
@@ -71259,7 +71821,8 @@ class SigningCosmosClient extends CosmosClient {
|
|
|
71259
71821
|
...(sngConfig && {
|
|
71260
71822
|
startingStack: sngConfig.startingStack.toString(),
|
|
71261
71823
|
blindLevelDuration: sngConfig.blindLevelDuration,
|
|
71262
|
-
entryFee: sngConfig.entryFee?.toString() ?? "0"
|
|
71824
|
+
entryFee: sngConfig.entryFee?.toString() ?? "0",
|
|
71825
|
+
...(sngConfig.blindStructure && { blindStructure: sngConfig.blindStructure })
|
|
71263
71826
|
})
|
|
71264
71827
|
});
|
|
71265
71828
|
try {
|
|
@@ -72182,6 +72745,7 @@ const registry = new protoSigning.Registry([
|
|
|
72182
72745
|
|
|
72183
72746
|
exports.AllPlayerActions = AllPlayerActions;
|
|
72184
72747
|
exports.BLIND_PRESETS = BLIND_PRESETS;
|
|
72748
|
+
exports.BLIND_PRESET_STRUCTURES = BLIND_PRESET_STRUCTURES;
|
|
72185
72749
|
exports.BLIND_PROGRESSIONS = BLIND_PROGRESSIONS;
|
|
72186
72750
|
exports.BLOCK52_HD_PATH = BLOCK52_HD_PATH;
|
|
72187
72751
|
exports.Board = Board;
|
|
@@ -72212,6 +72776,8 @@ exports.computeGameNameFee = computeGameNameFee;
|
|
|
72212
72776
|
exports.createSigningClientFromMnemonic = createSigningClientFromMnemonic;
|
|
72213
72777
|
exports.createWalletFromMnemonic = createWalletFromMnemonic;
|
|
72214
72778
|
exports.decToAtomics = decToAtomics;
|
|
72779
|
+
exports.expandBlindLevel = expandBlindLevel;
|
|
72780
|
+
exports.formatBlindStructure = formatBlindStructure;
|
|
72215
72781
|
exports.formatGameFormatDisplay = formatGameFormatDisplay;
|
|
72216
72782
|
exports.formatGameVariantDisplay = formatGameVariantDisplay;
|
|
72217
72783
|
exports.generateWallet = generateWallet;
|
|
@@ -72232,9 +72798,11 @@ exports.isSitAndGoFormat = isSitAndGoFormat;
|
|
|
72232
72798
|
exports.isTournamentFormat = isTournamentFormat;
|
|
72233
72799
|
exports.isValidMnemonic = isValidMnemonic;
|
|
72234
72800
|
exports.isValidPlayerAddress = isValidPlayerAddress;
|
|
72801
|
+
exports.parseBlindStructure = parseBlindStructure;
|
|
72235
72802
|
exports.parseGameFormat = parseGameFormat;
|
|
72236
72803
|
exports.parseGameVariant = parseGameVariant;
|
|
72237
72804
|
exports.registry = registry;
|
|
72805
|
+
exports.resolveBlindPreset = resolveBlindPreset;
|
|
72238
72806
|
exports.toValoperAddress = toValoperAddress;
|
|
72239
72807
|
exports.txFailureHash = txFailureHash;
|
|
72240
72808
|
exports.txFailurePhase = txFailurePhase;
|