landlord-ts 1.0.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.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/data/all_cards.json.gz +0 -0
- package/dist/chunk-I6ETCO5F.js +803 -0
- package/dist/index.cjs +2487 -0
- package/dist/index.d.cts +504 -0
- package/dist/index.d.ts +504 -0
- package/dist/index.js +1681 -0
- package/dist/simulation-worker.cjs +208 -0
- package/dist/simulation-worker.d.cts +2 -0
- package/dist/simulation-worker.d.ts +2 -0
- package/dist/simulation-worker.js +33 -0
- package/package.json +102 -0
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
// src/card/mana-cost.ts
|
|
2
|
+
var R_BITS = 1;
|
|
3
|
+
var G_BITS = 2;
|
|
4
|
+
var B_BITS = 4;
|
|
5
|
+
var U_BITS = 8;
|
|
6
|
+
var W_BITS = 16;
|
|
7
|
+
var C_BITS = 32;
|
|
8
|
+
function emptyManaCost() {
|
|
9
|
+
return { bits: 0, r: 0, w: 0, b: 0, u: 0, g: 0, c: 0 };
|
|
10
|
+
}
|
|
11
|
+
function manaCostFromRgbuwc(r, g, b, u, w, c) {
|
|
12
|
+
return {
|
|
13
|
+
bits: calculateSignature(r, g, b, u, w, c),
|
|
14
|
+
r,
|
|
15
|
+
w,
|
|
16
|
+
b,
|
|
17
|
+
u,
|
|
18
|
+
g,
|
|
19
|
+
c
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function manaCostCmc(cost) {
|
|
23
|
+
return cost.r + cost.w + cost.b + cost.u + cost.g + cost.c;
|
|
24
|
+
}
|
|
25
|
+
function updateManaCostBits(cost) {
|
|
26
|
+
return {
|
|
27
|
+
...cost,
|
|
28
|
+
bits: calculateSignature(cost.r, cost.g, cost.b, cost.u, cost.w, cost.c)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function calculateSignature(r, g, b, u, w, c) {
|
|
32
|
+
return Math.min(1, r) << 0 & R_BITS | Math.min(1, g) << 1 & G_BITS | Math.min(1, b) << 2 & B_BITS | Math.min(1, u) << 3 & U_BITS | Math.min(1, w) << 4 & W_BITS | Math.min(1, c) << 5 & C_BITS;
|
|
33
|
+
}
|
|
34
|
+
function manaColorFromStr(color) {
|
|
35
|
+
switch (color.charAt(0)) {
|
|
36
|
+
case "B":
|
|
37
|
+
return 2 /* Black */;
|
|
38
|
+
case "U":
|
|
39
|
+
return 3 /* Blue */;
|
|
40
|
+
case "G":
|
|
41
|
+
return 1 /* Green */;
|
|
42
|
+
case "R":
|
|
43
|
+
return 0 /* Red */;
|
|
44
|
+
case "W":
|
|
45
|
+
return 4 /* White */;
|
|
46
|
+
default:
|
|
47
|
+
return 5 /* Colorless */;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function applyColor(cost, color, count) {
|
|
51
|
+
switch (color) {
|
|
52
|
+
case 2 /* Black */:
|
|
53
|
+
cost.b += count;
|
|
54
|
+
break;
|
|
55
|
+
case 3 /* Blue */:
|
|
56
|
+
cost.u += count;
|
|
57
|
+
break;
|
|
58
|
+
case 1 /* Green */:
|
|
59
|
+
cost.g += count;
|
|
60
|
+
break;
|
|
61
|
+
case 0 /* Red */:
|
|
62
|
+
cost.r += count;
|
|
63
|
+
break;
|
|
64
|
+
case 4 /* White */:
|
|
65
|
+
cost.w += count;
|
|
66
|
+
break;
|
|
67
|
+
case 5 /* Colorless */:
|
|
68
|
+
cost.c += count;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function manaCostKey(cost) {
|
|
73
|
+
return `${cost.r},${cost.g},${cost.b},${cost.u},${cost.w},${cost.c},${cost.bits}`;
|
|
74
|
+
}
|
|
75
|
+
function compareManaCost(a, b) {
|
|
76
|
+
if (a.bits !== b.bits) return a.bits - b.bits;
|
|
77
|
+
if (a.r !== b.r) return a.r - b.r;
|
|
78
|
+
if (a.w !== b.w) return a.w - b.w;
|
|
79
|
+
if (a.b !== b.b) return a.b - b.b;
|
|
80
|
+
if (a.u !== b.u) return a.u - b.u;
|
|
81
|
+
if (a.g !== b.g) return a.g - b.g;
|
|
82
|
+
return a.c - b.c;
|
|
83
|
+
}
|
|
84
|
+
function manaCostsFromStr(manaCostStr) {
|
|
85
|
+
const symbolStack = manaCostSymbolsFromStr(manaCostStr);
|
|
86
|
+
const results = /* @__PURE__ */ new Map();
|
|
87
|
+
manaCostsFromStrRecur(results, emptyManaCost(), symbolStack, 0);
|
|
88
|
+
return [...results.values()].sort(compareManaCost);
|
|
89
|
+
}
|
|
90
|
+
function manaCostsFromStrRecur(results, current, symbolStack, idx) {
|
|
91
|
+
if (symbolStack.length <= idx) {
|
|
92
|
+
const updated = updateManaCostBits(current);
|
|
93
|
+
results.set(manaCostKey(updated), updated);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const [leftSym, rightSym] = symbolStack[idx];
|
|
97
|
+
const left = {
|
|
98
|
+
bits: 0,
|
|
99
|
+
r: leftSym.r + current.r,
|
|
100
|
+
g: leftSym.g + current.g,
|
|
101
|
+
b: leftSym.b + current.b,
|
|
102
|
+
u: leftSym.u + current.u,
|
|
103
|
+
w: leftSym.w + current.w,
|
|
104
|
+
c: leftSym.c + current.c
|
|
105
|
+
};
|
|
106
|
+
manaCostsFromStrRecur(results, left, symbolStack, idx + 1);
|
|
107
|
+
if (rightSym) {
|
|
108
|
+
const right = {
|
|
109
|
+
bits: 0,
|
|
110
|
+
r: rightSym.r + current.r,
|
|
111
|
+
g: rightSym.g + current.g,
|
|
112
|
+
b: rightSym.b + current.b,
|
|
113
|
+
u: rightSym.u + current.u,
|
|
114
|
+
w: rightSym.w + current.w,
|
|
115
|
+
c: rightSym.c + current.c
|
|
116
|
+
};
|
|
117
|
+
manaCostsFromStrRecur(results, right, symbolStack, idx + 1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function manaCostSymbolsFromStr(manaCostStr) {
|
|
121
|
+
let sigil = "";
|
|
122
|
+
const symbolStack = [];
|
|
123
|
+
let shouldPushRight = false;
|
|
124
|
+
let idx = 0;
|
|
125
|
+
for (const ch of manaCostStr) {
|
|
126
|
+
switch (ch) {
|
|
127
|
+
case "{": {
|
|
128
|
+
sigil = "";
|
|
129
|
+
symbolStack.push([emptyManaCost(), null]);
|
|
130
|
+
idx = symbolStack.length - 1;
|
|
131
|
+
shouldPushRight = false;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case "/":
|
|
135
|
+
case "\\": {
|
|
136
|
+
const color = manaColorFromStr(sigil);
|
|
137
|
+
const count = Number.parseInt(sigil, 10);
|
|
138
|
+
const n = Number.isFinite(count) ? count : 1;
|
|
139
|
+
const cost = emptyManaCost();
|
|
140
|
+
applyColor(cost, color, n);
|
|
141
|
+
symbolStack[idx][0] = cost;
|
|
142
|
+
shouldPushRight = true;
|
|
143
|
+
sigil = "";
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "}": {
|
|
147
|
+
const color = manaColorFromStr(sigil);
|
|
148
|
+
const count = Number.parseInt(sigil, 10);
|
|
149
|
+
const n = Number.isFinite(count) ? count : 1;
|
|
150
|
+
const cost = emptyManaCost();
|
|
151
|
+
applyColor(cost, color, n);
|
|
152
|
+
if (shouldPushRight) {
|
|
153
|
+
symbolStack[idx][1] = cost;
|
|
154
|
+
} else {
|
|
155
|
+
symbolStack[idx][0] = cost;
|
|
156
|
+
}
|
|
157
|
+
sigil = "";
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
default:
|
|
161
|
+
sigil += ch;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return symbolStack;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/card/types.ts
|
|
169
|
+
var CardKind = /* @__PURE__ */ ((CardKind2) => {
|
|
170
|
+
CardKind2["BasicLand"] = "BasicLand";
|
|
171
|
+
CardKind2["TapLand"] = "TapLand";
|
|
172
|
+
CardKind2["CheckLand"] = "CheckLand";
|
|
173
|
+
CardKind2["ShockLand"] = "ShockLand";
|
|
174
|
+
CardKind2["FastLand"] = "FastLand";
|
|
175
|
+
CardKind2["SlowLand"] = "SlowLand";
|
|
176
|
+
CardKind2["BattleLand"] = "BattleLand";
|
|
177
|
+
CardKind2["TurnLand"] = "TurnLand";
|
|
178
|
+
CardKind2["SurveilLand"] = "SurveilLand";
|
|
179
|
+
CardKind2["BounceLand"] = "BounceLand";
|
|
180
|
+
CardKind2["TriomeLand"] = "TriomeLand";
|
|
181
|
+
CardKind2["CyclingLand"] = "CyclingLand";
|
|
182
|
+
CardKind2["PainLand"] = "PainLand";
|
|
183
|
+
CardKind2["FetchLand"] = "FetchLand";
|
|
184
|
+
CardKind2["CanopyLand"] = "CanopyLand";
|
|
185
|
+
CardKind2["PathwayLand"] = "PathwayLand";
|
|
186
|
+
CardKind2["OtherLand"] = "OtherLand";
|
|
187
|
+
CardKind2["ForcedLand"] = "ForcedLand";
|
|
188
|
+
CardKind2["Creature"] = "Creature";
|
|
189
|
+
CardKind2["Spell"] = "Spell";
|
|
190
|
+
CardKind2["Enchantment"] = "Enchantment";
|
|
191
|
+
CardKind2["Instant"] = "Instant";
|
|
192
|
+
CardKind2["Planeswalker"] = "Planeswalker";
|
|
193
|
+
CardKind2["Sorcery"] = "Sorcery";
|
|
194
|
+
CardKind2["Artifact"] = "Artifact";
|
|
195
|
+
CardKind2["Unknown"] = "Unknown";
|
|
196
|
+
return CardKind2;
|
|
197
|
+
})(CardKind || {});
|
|
198
|
+
var Rarity = /* @__PURE__ */ ((Rarity2) => {
|
|
199
|
+
Rarity2["Common"] = "common";
|
|
200
|
+
Rarity2["Uncommon"] = "uncommon";
|
|
201
|
+
Rarity2["Rare"] = "rare";
|
|
202
|
+
Rarity2["Mythic"] = "mythic";
|
|
203
|
+
Rarity2["Special"] = "special";
|
|
204
|
+
Rarity2["Bonus"] = "bonus";
|
|
205
|
+
Rarity2["Other"] = "other";
|
|
206
|
+
return Rarity2;
|
|
207
|
+
})(Rarity || {});
|
|
208
|
+
function cloneCard(card) {
|
|
209
|
+
return {
|
|
210
|
+
...card,
|
|
211
|
+
manaCost: { ...card.manaCost },
|
|
212
|
+
allManaCosts: card.allManaCosts.map((c) => ({ ...c })),
|
|
213
|
+
basicLandTypes: card.basicLandTypes ?? 0,
|
|
214
|
+
checkTypes: card.checkTypes ?? 0,
|
|
215
|
+
manaPerTap: card.manaPerTap ?? 1
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function isLandKind(kind) {
|
|
219
|
+
return kind === "BasicLand" /* BasicLand */ || kind === "ShockLand" /* ShockLand */ || kind === "CheckLand" /* CheckLand */ || kind === "TapLand" /* TapLand */ || kind === "FastLand" /* FastLand */ || kind === "SlowLand" /* SlowLand */ || kind === "BattleLand" /* BattleLand */ || kind === "TurnLand" /* TurnLand */ || kind === "SurveilLand" /* SurveilLand */ || kind === "BounceLand" /* BounceLand */ || kind === "TriomeLand" /* TriomeLand */ || kind === "CyclingLand" /* CyclingLand */ || kind === "PainLand" /* PainLand */ || kind === "FetchLand" /* FetchLand */ || kind === "CanopyLand" /* CanopyLand */ || kind === "PathwayLand" /* PathwayLand */ || kind === "OtherLand" /* OtherLand */ || kind === "ForcedLand" /* ForcedLand */;
|
|
220
|
+
}
|
|
221
|
+
function isLand(card) {
|
|
222
|
+
return isLandKind(card.kind);
|
|
223
|
+
}
|
|
224
|
+
function hashCardName(name) {
|
|
225
|
+
let hash = 0xcbf29ce484222325n;
|
|
226
|
+
const prime = 0x100000001b3n;
|
|
227
|
+
for (let i = 0; i < name.length; i++) {
|
|
228
|
+
hash ^= BigInt(name.charCodeAt(i));
|
|
229
|
+
hash = BigInt.asUintN(64, hash * prime);
|
|
230
|
+
}
|
|
231
|
+
return Number(hash & 0x1fffffffffffffn);
|
|
232
|
+
}
|
|
233
|
+
function parseRarity(raw) {
|
|
234
|
+
switch (raw.toLowerCase()) {
|
|
235
|
+
case "common":
|
|
236
|
+
return "common" /* Common */;
|
|
237
|
+
case "uncommon":
|
|
238
|
+
return "uncommon" /* Uncommon */;
|
|
239
|
+
case "rare":
|
|
240
|
+
return "rare" /* Rare */;
|
|
241
|
+
case "mythic":
|
|
242
|
+
return "mythic" /* Mythic */;
|
|
243
|
+
case "special":
|
|
244
|
+
return "special" /* Special */;
|
|
245
|
+
case "bonus":
|
|
246
|
+
return "bonus" /* Bonus */;
|
|
247
|
+
default:
|
|
248
|
+
return "other" /* Other */;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function parseSetCode(raw) {
|
|
252
|
+
return raw.toLowerCase();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/card/land-types.ts
|
|
256
|
+
var BasicLandType = {
|
|
257
|
+
Plains: 1 << 0,
|
|
258
|
+
Island: 1 << 1,
|
|
259
|
+
Swamp: 1 << 2,
|
|
260
|
+
Mountain: 1 << 3,
|
|
261
|
+
Forest: 1 << 4
|
|
262
|
+
};
|
|
263
|
+
var TYPE_NAME_TO_BIT = [
|
|
264
|
+
["plains", BasicLandType.Plains],
|
|
265
|
+
["island", BasicLandType.Island],
|
|
266
|
+
["swamp", BasicLandType.Swamp],
|
|
267
|
+
["mountain", BasicLandType.Mountain],
|
|
268
|
+
["forest", BasicLandType.Forest]
|
|
269
|
+
];
|
|
270
|
+
function basicLandTypesFromTypeLine(typeLine) {
|
|
271
|
+
const lower = typeLine.toLowerCase();
|
|
272
|
+
let bits = 0;
|
|
273
|
+
for (const [name, bit] of TYPE_NAME_TO_BIT) {
|
|
274
|
+
if (lower.includes(name)) bits |= bit;
|
|
275
|
+
}
|
|
276
|
+
return bits;
|
|
277
|
+
}
|
|
278
|
+
function basicLandTypeCount(typeLine) {
|
|
279
|
+
const bits = basicLandTypesFromTypeLine(typeLine);
|
|
280
|
+
let n = 0;
|
|
281
|
+
for (let b = bits; b !== 0; b >>= 1) n += b & 1;
|
|
282
|
+
return n;
|
|
283
|
+
}
|
|
284
|
+
function checkTypesFromOracleText(oracleText) {
|
|
285
|
+
const lower = oracleText.toLowerCase();
|
|
286
|
+
const idx = lower.indexOf("unless you control");
|
|
287
|
+
if (idx === -1) return 0;
|
|
288
|
+
const clause = lower.slice(idx);
|
|
289
|
+
let bits = 0;
|
|
290
|
+
for (const [name, bit] of TYPE_NAME_TO_BIT) {
|
|
291
|
+
if (clause.includes(name)) bits |= bit;
|
|
292
|
+
}
|
|
293
|
+
return bits;
|
|
294
|
+
}
|
|
295
|
+
function normalizeOracleForEtb(oracleText) {
|
|
296
|
+
return oracleText.toLowerCase().replace(/enters the battlefield tapped/g, "enters tapped").replace(/\s+/g, " ").trim();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/card/mana-color-count.ts
|
|
300
|
+
function newManaColorCount() {
|
|
301
|
+
return {
|
|
302
|
+
total: 0,
|
|
303
|
+
b: 0,
|
|
304
|
+
u: 0,
|
|
305
|
+
g: 0,
|
|
306
|
+
r: 0,
|
|
307
|
+
w: 0,
|
|
308
|
+
c: 0,
|
|
309
|
+
wu: 0,
|
|
310
|
+
wb: 0,
|
|
311
|
+
ub: 0,
|
|
312
|
+
ur: 0,
|
|
313
|
+
br: 0,
|
|
314
|
+
bg: 0,
|
|
315
|
+
rg: 0,
|
|
316
|
+
rw: 0,
|
|
317
|
+
gw: 0,
|
|
318
|
+
gu: 0
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function countManaColor(mcc, card) {
|
|
322
|
+
mcc.total += 1;
|
|
323
|
+
mcc.u += card.u;
|
|
324
|
+
mcc.r += card.r;
|
|
325
|
+
mcc.b += card.b;
|
|
326
|
+
mcc.g += card.g;
|
|
327
|
+
mcc.w += card.w;
|
|
328
|
+
mcc.c += card.c;
|
|
329
|
+
const key = `${card.r},${card.g},${card.b},${card.u},${card.w}`;
|
|
330
|
+
switch (key) {
|
|
331
|
+
case "1,1,0,0,0":
|
|
332
|
+
mcc.rg += 1;
|
|
333
|
+
break;
|
|
334
|
+
case "1,0,1,0,0":
|
|
335
|
+
mcc.br += 1;
|
|
336
|
+
break;
|
|
337
|
+
case "1,0,0,1,0":
|
|
338
|
+
mcc.ur += 1;
|
|
339
|
+
break;
|
|
340
|
+
case "1,0,0,0,1":
|
|
341
|
+
mcc.rw += 1;
|
|
342
|
+
break;
|
|
343
|
+
case "0,1,1,0,0":
|
|
344
|
+
mcc.bg += 1;
|
|
345
|
+
break;
|
|
346
|
+
case "0,1,0,1,0":
|
|
347
|
+
mcc.gu += 1;
|
|
348
|
+
break;
|
|
349
|
+
case "0,1,0,0,1":
|
|
350
|
+
mcc.gw += 1;
|
|
351
|
+
break;
|
|
352
|
+
case "0,0,1,1,0":
|
|
353
|
+
mcc.ub += 1;
|
|
354
|
+
break;
|
|
355
|
+
case "0,0,1,0,1":
|
|
356
|
+
mcc.wb += 1;
|
|
357
|
+
break;
|
|
358
|
+
case "0,0,0,1,1":
|
|
359
|
+
mcc.wu += 1;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/mulligan/types.ts
|
|
365
|
+
function createMulberry32(seed) {
|
|
366
|
+
let t = seed >>> 0;
|
|
367
|
+
return {
|
|
368
|
+
genRange(min, max) {
|
|
369
|
+
t = t + 1831565813 >>> 0;
|
|
370
|
+
let r = Math.imul(t ^ t >>> 15, 1 | t);
|
|
371
|
+
r ^= r + Math.imul(r ^ r >>> 7, 61 | r);
|
|
372
|
+
const u = ((r ^ r >>> 14) >>> 0) / 4294967296;
|
|
373
|
+
return min + Math.floor(u * (max - min));
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function createEntropyRng() {
|
|
378
|
+
return createMulberry32(Math.random() * 4294967295 >>> 0);
|
|
379
|
+
}
|
|
380
|
+
function partialShuffle(slice, rng, amount) {
|
|
381
|
+
const n = Math.min(amount, slice.length);
|
|
382
|
+
for (let i = 0; i < n; i++) {
|
|
383
|
+
const j = rng.genRange(i, slice.length);
|
|
384
|
+
const tmp = slice[i];
|
|
385
|
+
slice[i] = slice[j];
|
|
386
|
+
slice[j] = tmp;
|
|
387
|
+
}
|
|
388
|
+
return slice.slice(0, n);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/bipartite.ts
|
|
392
|
+
function maximumBipartiteMatching(edges, mCount, nCount, seen, matches) {
|
|
393
|
+
let matchCount = 0;
|
|
394
|
+
for (let i = 0; i < matches.length; i++) matches[i] = -1;
|
|
395
|
+
for (let m = 0; m < mCount; m++) {
|
|
396
|
+
for (let i = 0; i < seen.length; i++) seen[i] = false;
|
|
397
|
+
if (recursiveFindMatch(edges, nCount, m, seen, matches)) {
|
|
398
|
+
matchCount += 1;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return matchCount;
|
|
402
|
+
}
|
|
403
|
+
function recursiveFindMatch(edges, nCount, m, seen, matches) {
|
|
404
|
+
for (let n = 0; n < nCount; n++) {
|
|
405
|
+
const i = nCount * m + n;
|
|
406
|
+
if (edges[i] !== 0 && !seen[n]) {
|
|
407
|
+
seen[n] = true;
|
|
408
|
+
const prior = matches[n];
|
|
409
|
+
const available = prior < 0 || recursiveFindMatch(edges, nCount, prior, seen, matches);
|
|
410
|
+
if (available) {
|
|
411
|
+
matches[n] = m;
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/hand.ts
|
|
420
|
+
function simCardFromCard(card) {
|
|
421
|
+
return {
|
|
422
|
+
hash: card.hash,
|
|
423
|
+
kind: card.kind,
|
|
424
|
+
manaCost: { ...card.manaCost },
|
|
425
|
+
basicLandTypes: card.basicLandTypes ?? 0,
|
|
426
|
+
checkTypes: card.checkTypes ?? 0,
|
|
427
|
+
manaPerTap: card.manaPerTap ?? 1
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
var PlayOrder = /* @__PURE__ */ ((PlayOrder2) => {
|
|
431
|
+
PlayOrder2["First"] = "First";
|
|
432
|
+
PlayOrder2["Second"] = "Second";
|
|
433
|
+
return PlayOrder2;
|
|
434
|
+
})(PlayOrder || {});
|
|
435
|
+
function newScratch(maxLandCount, maxPipCount) {
|
|
436
|
+
return {
|
|
437
|
+
lands: [],
|
|
438
|
+
edges: new Array(maxLandCount * maxPipCount).fill(0),
|
|
439
|
+
seen: new Array(maxLandCount).fill(false),
|
|
440
|
+
matches: new Array(maxLandCount).fill(-1)
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function handFromOpeningAndDraws(opening, draws, startingHandSize = 7) {
|
|
444
|
+
const cards = [];
|
|
445
|
+
for (const card of opening) cards.push(simCardFromCard(card));
|
|
446
|
+
for (const card of draws) cards.push(simCardFromCard(card));
|
|
447
|
+
const openingHandSize = opening.length;
|
|
448
|
+
return {
|
|
449
|
+
cards,
|
|
450
|
+
startingHandSize,
|
|
451
|
+
openingHandSize,
|
|
452
|
+
mulliganCount: startingHandSize - openingHandSize
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function handFromMulligan(mulligan, rng, deck, draws) {
|
|
456
|
+
return mulligan.simulateHand(rng, deck, draws);
|
|
457
|
+
}
|
|
458
|
+
function handOpening(hand) {
|
|
459
|
+
return hand.cards.slice(0, hand.openingHandSize);
|
|
460
|
+
}
|
|
461
|
+
function handDraws(hand, drawCount) {
|
|
462
|
+
return hand.cards.slice(hand.openingHandSize, hand.openingHandSize + drawCount);
|
|
463
|
+
}
|
|
464
|
+
function handOpeningWithDraws(hand, drawCount) {
|
|
465
|
+
return hand.cards.slice(0, hand.openingHandSize + drawCount);
|
|
466
|
+
}
|
|
467
|
+
function countInOpeningWithDraws(hand, drawCount, predicate) {
|
|
468
|
+
let count = 0;
|
|
469
|
+
for (const card of handOpeningWithDraws(hand, drawCount)) {
|
|
470
|
+
if (predicate(card)) count += 1;
|
|
471
|
+
}
|
|
472
|
+
return count;
|
|
473
|
+
}
|
|
474
|
+
function autoTapByTurn(hand, goal, turn, playOrder) {
|
|
475
|
+
const scratch = newScratch(30, 8);
|
|
476
|
+
return autoTapWithScratch(hand, simCardFromCard(goal), turn, playOrder, scratch);
|
|
477
|
+
}
|
|
478
|
+
function playCmcAutoTap(hand, goal) {
|
|
479
|
+
return autoTapByTurn(hand, goal, Math.max(1, goal.turn), "First" /* First */);
|
|
480
|
+
}
|
|
481
|
+
function drawCmcAutoTap(hand, goal) {
|
|
482
|
+
return autoTapByTurn(hand, goal, Math.max(1, goal.turn), "Second" /* Second */);
|
|
483
|
+
}
|
|
484
|
+
function isAlwaysAvailableKind(kind) {
|
|
485
|
+
return kind === "BasicLand" /* BasicLand */ || kind === "OtherLand" /* OtherLand */ || kind === "ForcedLand" /* ForcedLand */ || kind === "ShockLand" /* ShockLand */ || kind === "PainLand" /* PainLand */ || kind === "FetchLand" /* FetchLand */ || kind === "CanopyLand" /* CanopyLand */ || kind === "PathwayLand" /* PathwayLand */;
|
|
486
|
+
}
|
|
487
|
+
function isConditionalEtbKind(kind) {
|
|
488
|
+
return kind === "TapLand" /* TapLand */ || kind === "CheckLand" /* CheckLand */ || kind === "FastLand" /* FastLand */ || kind === "SlowLand" /* SlowLand */ || kind === "BattleLand" /* BattleLand */ || kind === "TurnLand" /* TurnLand */ || kind === "SurveilLand" /* SurveilLand */ || kind === "BounceLand" /* BounceLand */ || kind === "TriomeLand" /* TriomeLand */ || kind === "CyclingLand" /* CyclingLand */;
|
|
489
|
+
}
|
|
490
|
+
function pushLandColumns(lands, card) {
|
|
491
|
+
const copies = card.manaPerTap ?? 1;
|
|
492
|
+
for (let k = 0; k < copies; k++) lands.push(card);
|
|
493
|
+
}
|
|
494
|
+
function availableTurnForLand(card, playTurn, otherLands, boardTypes, basicsOnBoard = 0) {
|
|
495
|
+
switch (card.kind) {
|
|
496
|
+
case "TapLand" /* TapLand */:
|
|
497
|
+
case "SurveilLand" /* SurveilLand */:
|
|
498
|
+
case "BounceLand" /* BounceLand */:
|
|
499
|
+
case "TriomeLand" /* TriomeLand */:
|
|
500
|
+
case "CyclingLand" /* CyclingLand */:
|
|
501
|
+
return playTurn + 1;
|
|
502
|
+
case "CheckLand" /* CheckLand */: {
|
|
503
|
+
const unlocked = (boardTypes & card.checkTypes) !== 0;
|
|
504
|
+
return unlocked ? playTurn : playTurn + 1;
|
|
505
|
+
}
|
|
506
|
+
case "FastLand" /* FastLand */:
|
|
507
|
+
return otherLands <= 2 ? playTurn : playTurn + 1;
|
|
508
|
+
case "SlowLand" /* SlowLand */:
|
|
509
|
+
return otherLands >= 2 ? playTurn : playTurn + 1;
|
|
510
|
+
case "BattleLand" /* BattleLand */:
|
|
511
|
+
return basicsOnBoard >= 2 ? playTurn : playTurn + 1;
|
|
512
|
+
case "TurnLand" /* TurnLand */:
|
|
513
|
+
return playTurn <= 3 ? playTurn : playTurn + 1;
|
|
514
|
+
default:
|
|
515
|
+
return playTurn;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function scheduleLandPlays(openingHand, drawn, playOrder) {
|
|
519
|
+
const candidates = [];
|
|
520
|
+
let openingLandIdx = 0;
|
|
521
|
+
for (const card of openingHand) {
|
|
522
|
+
if (!isLandKind(card.kind)) continue;
|
|
523
|
+
openingLandIdx += 1;
|
|
524
|
+
candidates.push({ card, earliest: openingLandIdx });
|
|
525
|
+
}
|
|
526
|
+
for (let drawIdx = 0; drawIdx < drawn.length; drawIdx++) {
|
|
527
|
+
const card = drawn[drawIdx];
|
|
528
|
+
if (!isLandKind(card.kind)) continue;
|
|
529
|
+
const earliest = playOrder === "First" /* First */ ? drawIdx + 2 : drawIdx + 1;
|
|
530
|
+
candidates.push({ card, earliest });
|
|
531
|
+
}
|
|
532
|
+
const scheduled = [];
|
|
533
|
+
let lastPlayTurn = 0;
|
|
534
|
+
for (const { card, earliest } of candidates) {
|
|
535
|
+
const playTurn = Math.max(earliest, lastPlayTurn + 1);
|
|
536
|
+
scheduled.push({ card, playTurn });
|
|
537
|
+
lastPlayTurn = playTurn;
|
|
538
|
+
}
|
|
539
|
+
return scheduled;
|
|
540
|
+
}
|
|
541
|
+
function autoTapWithScratch(hand, goal, turn, playOrder, scratch) {
|
|
542
|
+
const drawCount = playOrder === "First" /* First */ ? turn - 1 : turn;
|
|
543
|
+
const openingHand = handOpening(hand);
|
|
544
|
+
const drawn = handDraws(hand, drawCount);
|
|
545
|
+
let inOpeningHand = false;
|
|
546
|
+
let inDrawHand = false;
|
|
547
|
+
for (const card of openingHand) {
|
|
548
|
+
if (card.hash === goal.hash) inOpeningHand = true;
|
|
549
|
+
}
|
|
550
|
+
for (const card of drawn) {
|
|
551
|
+
if (card.hash === goal.hash) inDrawHand = true;
|
|
552
|
+
}
|
|
553
|
+
const scheduled = scheduleLandPlays(openingHand, drawn, playOrder);
|
|
554
|
+
scratch.lands.length = 0;
|
|
555
|
+
let boardTypes = 0;
|
|
556
|
+
let basicsOnBoard = 0;
|
|
557
|
+
for (let i = 0; i < scheduled.length; i++) {
|
|
558
|
+
const { card, playTurn } = scheduled[i];
|
|
559
|
+
const otherLands = i;
|
|
560
|
+
if (isAlwaysAvailableKind(card.kind)) {
|
|
561
|
+
pushLandColumns(scratch.lands, card);
|
|
562
|
+
} else if (isConditionalEtbKind(card.kind)) {
|
|
563
|
+
const availableTurn = availableTurnForLand(
|
|
564
|
+
card,
|
|
565
|
+
playTurn,
|
|
566
|
+
otherLands,
|
|
567
|
+
boardTypes,
|
|
568
|
+
basicsOnBoard
|
|
569
|
+
);
|
|
570
|
+
if (availableTurn <= turn) pushLandColumns(scratch.lands, card);
|
|
571
|
+
} else {
|
|
572
|
+
pushLandColumns(scratch.lands, card);
|
|
573
|
+
}
|
|
574
|
+
boardTypes |= card.basicLandTypes;
|
|
575
|
+
if (card.kind === "BasicLand" /* BasicLand */) basicsOnBoard += 1;
|
|
576
|
+
}
|
|
577
|
+
const pipCount = goal.manaCost.r + goal.manaCost.g + goal.manaCost.b + goal.manaCost.u + goal.manaCost.w + goal.manaCost.c;
|
|
578
|
+
const landCount = scratch.lands.length;
|
|
579
|
+
if (landCount < pipCount) {
|
|
580
|
+
return { paid: false, cmc: false, inOpeningHand, inDrawHand };
|
|
581
|
+
}
|
|
582
|
+
const edgeLen = pipCount * landCount;
|
|
583
|
+
if (scratch.edges.length !== edgeLen) {
|
|
584
|
+
scratch.edges = new Array(edgeLen).fill(0);
|
|
585
|
+
} else {
|
|
586
|
+
scratch.edges.fill(0);
|
|
587
|
+
}
|
|
588
|
+
if (scratch.seen.length !== landCount) {
|
|
589
|
+
scratch.seen = new Array(landCount).fill(false);
|
|
590
|
+
}
|
|
591
|
+
if (scratch.matches.length !== landCount) {
|
|
592
|
+
scratch.matches = new Array(landCount).fill(-1);
|
|
593
|
+
}
|
|
594
|
+
let row = 0;
|
|
595
|
+
for (let m = 0; m < goal.manaCost.r; m++, row++) {
|
|
596
|
+
for (let n = 0; n < landCount; n++) {
|
|
597
|
+
scratch.edges[landCount * row + n] = scratch.lands[n].manaCost.r;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
for (let m = 0; m < goal.manaCost.g; m++, row++) {
|
|
601
|
+
for (let n = 0; n < landCount; n++) {
|
|
602
|
+
scratch.edges[landCount * row + n] = scratch.lands[n].manaCost.g;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
for (let m = 0; m < goal.manaCost.b; m++, row++) {
|
|
606
|
+
for (let n = 0; n < landCount; n++) {
|
|
607
|
+
scratch.edges[landCount * row + n] = scratch.lands[n].manaCost.b;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
for (let m = 0; m < goal.manaCost.u; m++, row++) {
|
|
611
|
+
for (let n = 0; n < landCount; n++) {
|
|
612
|
+
scratch.edges[landCount * row + n] = scratch.lands[n].manaCost.u;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
for (let m = 0; m < goal.manaCost.w; m++, row++) {
|
|
616
|
+
for (let n = 0; n < landCount; n++) {
|
|
617
|
+
scratch.edges[landCount * row + n] = scratch.lands[n].manaCost.w;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
for (let m = 0; m < goal.manaCost.c; m++, row++) {
|
|
621
|
+
for (let n = 0; n < landCount; n++) {
|
|
622
|
+
scratch.edges[landCount * row + n] = 1;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const pipsPaid = maximumBipartiteMatching(
|
|
626
|
+
scratch.edges,
|
|
627
|
+
pipCount,
|
|
628
|
+
landCount,
|
|
629
|
+
scratch.seen,
|
|
630
|
+
scratch.matches
|
|
631
|
+
);
|
|
632
|
+
return {
|
|
633
|
+
paid: pipsPaid === pipCount,
|
|
634
|
+
cmc: true,
|
|
635
|
+
inOpeningHand,
|
|
636
|
+
inDrawHand
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/mulligan/london.ts
|
|
641
|
+
var STARTING_HAND_SIZE = 7;
|
|
642
|
+
function londonNever() {
|
|
643
|
+
return {
|
|
644
|
+
startingHandSize: STARTING_HAND_SIZE,
|
|
645
|
+
mulliganDownTo: STARTING_HAND_SIZE,
|
|
646
|
+
mulliganOnLands: /* @__PURE__ */ new Set(),
|
|
647
|
+
acceptableHandList: []
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function londonSimulateHand(self, rng, deck, draws) {
|
|
651
|
+
const deckSize = deck.length;
|
|
652
|
+
const startingHandSize = Math.min(self.startingHandSize, deckSize);
|
|
653
|
+
const mulliganDownTo = Math.min(self.mulliganDownTo, startingHandSize);
|
|
654
|
+
const maxMulliganRounds = startingHandSize - mulliganDownTo + 1;
|
|
655
|
+
const cardsToDraw = Math.min(startingHandSize + draws + maxMulliganRounds, deckSize);
|
|
656
|
+
const indexRange = Array.from({ length: deckSize }, (_, i) => i);
|
|
657
|
+
const mustKeepCardIndices = [];
|
|
658
|
+
const seenCardHashes = /* @__PURE__ */ new Set();
|
|
659
|
+
for (let round = 0; round < maxMulliganRounds; round++) {
|
|
660
|
+
const shuffledIndices2 = partialShuffle(indexRange.slice(), rng, cardsToDraw);
|
|
661
|
+
const shuffledDeck2 = shuffledIndices2.map((i) => deck[i]);
|
|
662
|
+
const startingHand = shuffledDeck2.slice(0, startingHandSize);
|
|
663
|
+
const isLastRound = round === maxMulliganRounds - 1;
|
|
664
|
+
let landCount = 0;
|
|
665
|
+
for (const c of startingHand) {
|
|
666
|
+
if (isLand(c)) landCount += 1;
|
|
667
|
+
}
|
|
668
|
+
const sufficientLandCount = !self.mulliganOnLands.has(landCount);
|
|
669
|
+
if (!isLastRound && !sufficientLandCount) continue;
|
|
670
|
+
let foundAcceptableHand = false;
|
|
671
|
+
for (const acceptableHand of self.acceptableHandList) {
|
|
672
|
+
mustKeepCardIndices.length = 0;
|
|
673
|
+
seenCardHashes.clear();
|
|
674
|
+
for (let i = 0; i < startingHand.length; i++) {
|
|
675
|
+
const card = startingHand[i];
|
|
676
|
+
if (seenCardHashes.has(card.hash)) continue;
|
|
677
|
+
if (acceptableHand.has(card.hash)) mustKeepCardIndices.push(i);
|
|
678
|
+
seenCardHashes.add(card.hash);
|
|
679
|
+
}
|
|
680
|
+
foundAcceptableHand = mustKeepCardIndices.length === acceptableHand.size;
|
|
681
|
+
if (foundAcceptableHand) break;
|
|
682
|
+
}
|
|
683
|
+
const disregardFoundAcceptableHand = self.acceptableHandList.length === 0;
|
|
684
|
+
const keep = isLastRound || sufficientLandCount && (disregardFoundAcceptableHand || foundAcceptableHand);
|
|
685
|
+
if (!keep) continue;
|
|
686
|
+
const openingHandSize = startingHandSize - round;
|
|
687
|
+
let landsSaved = 0;
|
|
688
|
+
for (let i = 0; i < startingHand.length; i++) {
|
|
689
|
+
const card = startingHand[i];
|
|
690
|
+
if (!isLandKind(card.kind)) continue;
|
|
691
|
+
const needMoreLands = self.mulliganOnLands.has(landsSaved) && landsSaved < openingHandSize;
|
|
692
|
+
if (needMoreLands) {
|
|
693
|
+
mustKeepCardIndices.push(i);
|
|
694
|
+
landsSaved += 1;
|
|
695
|
+
} else {
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
mustKeepCardIndices.sort((a, b) => a - b);
|
|
700
|
+
{
|
|
701
|
+
let w = 0;
|
|
702
|
+
for (let r = 0; r < mustKeepCardIndices.length; r++) {
|
|
703
|
+
if (r === 0 || mustKeepCardIndices[r] !== mustKeepCardIndices[r - 1]) {
|
|
704
|
+
mustKeepCardIndices[w++] = mustKeepCardIndices[r];
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
mustKeepCardIndices.length = w;
|
|
708
|
+
}
|
|
709
|
+
for (let i = 0; i < mustKeepCardIndices.length; i++) {
|
|
710
|
+
const mustKeepI = mustKeepCardIndices[i];
|
|
711
|
+
const tmp = shuffledDeck2[i];
|
|
712
|
+
shuffledDeck2[i] = shuffledDeck2[mustKeepI];
|
|
713
|
+
shuffledDeck2[mustKeepI] = tmp;
|
|
714
|
+
}
|
|
715
|
+
let discardCount = 0;
|
|
716
|
+
for (let i = openingHandSize; i < startingHandSize; i++) {
|
|
717
|
+
const j = cardsToDraw - 1 - discardCount;
|
|
718
|
+
const tmp = shuffledDeck2[i];
|
|
719
|
+
shuffledDeck2[i] = shuffledDeck2[j];
|
|
720
|
+
shuffledDeck2[j] = tmp;
|
|
721
|
+
discardCount += 1;
|
|
722
|
+
}
|
|
723
|
+
return handFromOpeningAndDraws(
|
|
724
|
+
shuffledDeck2.slice(0, openingHandSize),
|
|
725
|
+
shuffledDeck2.slice(openingHandSize),
|
|
726
|
+
startingHandSize
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
const shuffledIndices = partialShuffle(indexRange.slice(), rng, cardsToDraw);
|
|
730
|
+
const shuffledDeck = shuffledIndices.map((i) => deck[i]);
|
|
731
|
+
return handFromOpeningAndDraws(
|
|
732
|
+
shuffledDeck.slice(0, startingHandSize),
|
|
733
|
+
shuffledDeck.slice(startingHandSize),
|
|
734
|
+
startingHandSize
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
function asLondonMulligan(london) {
|
|
738
|
+
return {
|
|
739
|
+
simulateHand: (rng, deck, draws) => londonSimulateHand(london, rng, deck, draws)
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/mulligan/never.ts
|
|
744
|
+
var STARTING_HAND_SIZE2 = 7;
|
|
745
|
+
function newNever() {
|
|
746
|
+
return { startingHandSize: STARTING_HAND_SIZE2 };
|
|
747
|
+
}
|
|
748
|
+
function neverSimulateHand(self, rng, deck, draws) {
|
|
749
|
+
const deckLen = deck.length;
|
|
750
|
+
const cardsToDraw = Math.min(deckLen, self.startingHandSize + draws);
|
|
751
|
+
const startingHandSize = Math.min(deckLen, self.startingHandSize);
|
|
752
|
+
const indexRange = Array.from({ length: deckLen }, (_, i) => i);
|
|
753
|
+
const shuffledIndices = partialShuffle(indexRange, rng, cardsToDraw);
|
|
754
|
+
const shuffledDeck = shuffledIndices.map((i) => deck[i]);
|
|
755
|
+
return handFromOpeningAndDraws(
|
|
756
|
+
shuffledDeck.slice(0, startingHandSize),
|
|
757
|
+
shuffledDeck.slice(startingHandSize),
|
|
758
|
+
startingHandSize
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
function asNeverMulligan(n = newNever()) {
|
|
762
|
+
return {
|
|
763
|
+
simulateHand: (rng, deck, draws) => neverSimulateHand(n, rng, deck, draws)
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
export {
|
|
768
|
+
emptyManaCost,
|
|
769
|
+
manaCostFromRgbuwc,
|
|
770
|
+
manaCostCmc,
|
|
771
|
+
manaCostsFromStr,
|
|
772
|
+
CardKind,
|
|
773
|
+
Rarity,
|
|
774
|
+
cloneCard,
|
|
775
|
+
isLandKind,
|
|
776
|
+
isLand,
|
|
777
|
+
hashCardName,
|
|
778
|
+
parseRarity,
|
|
779
|
+
parseSetCode,
|
|
780
|
+
BasicLandType,
|
|
781
|
+
basicLandTypesFromTypeLine,
|
|
782
|
+
basicLandTypeCount,
|
|
783
|
+
checkTypesFromOracleText,
|
|
784
|
+
normalizeOracleForEtb,
|
|
785
|
+
newManaColorCount,
|
|
786
|
+
countManaColor,
|
|
787
|
+
createMulberry32,
|
|
788
|
+
createEntropyRng,
|
|
789
|
+
PlayOrder,
|
|
790
|
+
newScratch,
|
|
791
|
+
handFromOpeningAndDraws,
|
|
792
|
+
handFromMulligan,
|
|
793
|
+
handOpening,
|
|
794
|
+
countInOpeningWithDraws,
|
|
795
|
+
autoTapByTurn,
|
|
796
|
+
playCmcAutoTap,
|
|
797
|
+
drawCmcAutoTap,
|
|
798
|
+
availableTurnForLand,
|
|
799
|
+
autoTapWithScratch,
|
|
800
|
+
londonNever,
|
|
801
|
+
asLondonMulligan,
|
|
802
|
+
asNeverMulligan
|
|
803
|
+
};
|