libram 0.3.2 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/Clan.js +268 -485
  2. package/dist/Copier.js +11 -48
  3. package/dist/Dungeon.js +77 -157
  4. package/dist/Kmail.d.ts +13 -7
  5. package/dist/Kmail.js +92 -233
  6. package/dist/Path.js +68 -120
  7. package/dist/ascend.js +153 -172
  8. package/dist/combat.d.ts +86 -1
  9. package/dist/combat.js +295 -387
  10. package/dist/console.js +13 -36
  11. package/dist/diet/index.d.ts +31 -0
  12. package/dist/diet/index.js +333 -0
  13. package/dist/diet/knapsack.d.ts +7 -0
  14. package/dist/diet/knapsack.js +106 -0
  15. package/dist/dungeons/Dreadsylvania.d.ts +4 -0
  16. package/dist/dungeons/Dreadsylvania.js +14 -0
  17. package/dist/dungeons/Dungeon.d.ts +28 -0
  18. package/dist/dungeons/Dungeon.js +99 -0
  19. package/dist/dungeons/Hobopolis.d.ts +4 -0
  20. package/dist/dungeons/Hobopolis.js +14 -0
  21. package/dist/dungeons/SlimeTube.d.ts +4 -0
  22. package/dist/dungeons/SlimeTube.js +14 -0
  23. package/dist/freerun.d.ts +23 -0
  24. package/dist/freerun.js +92 -0
  25. package/dist/index.d.ts +6 -1
  26. package/dist/index.js +21 -263
  27. package/dist/lib.d.ts +43 -0
  28. package/dist/lib.js +296 -405
  29. package/dist/logger.js +23 -63
  30. package/dist/maximize.d.ts +29 -12
  31. package/dist/maximize.js +318 -421
  32. package/dist/modifier.d.ts +13 -0
  33. package/dist/modifier.js +35 -0
  34. package/dist/modifierTypes.d.ts +16 -0
  35. package/dist/modifierTypes.js +9 -0
  36. package/dist/mood.js +220 -531
  37. package/dist/property.d.ts +2 -0
  38. package/dist/property.js +96 -242
  39. package/dist/propertyTypes.d.ts +9 -0
  40. package/dist/propertyTypes.js +1 -0
  41. package/dist/propertyTyping.d.ts +2 -9
  42. package/dist/propertyTyping.js +42 -53
  43. package/dist/resources/2007/CandyHearts.d.ts +9 -0
  44. package/dist/resources/2007/CandyHearts.js +24 -0
  45. package/dist/resources/2008/DivineFavors.d.ts +9 -0
  46. package/dist/resources/2008/DivineFavors.js +27 -0
  47. package/dist/resources/2009/Bandersnatch.js +37 -112
  48. package/dist/resources/2009/LoveSongs.d.ts +9 -0
  49. package/dist/resources/2009/LoveSongs.js +24 -0
  50. package/dist/resources/2009/SpookyPutty.js +20 -46
  51. package/dist/resources/2010/Brickos.d.ts +9 -0
  52. package/dist/resources/2010/Brickos.js +21 -0
  53. package/dist/resources/2010/CrownOfThrones.d.ts +18 -0
  54. package/dist/resources/2010/CrownOfThrones.js +550 -0
  55. package/dist/resources/2011/Gygaxian.d.ts +9 -0
  56. package/dist/resources/2011/Gygaxian.js +24 -0
  57. package/dist/resources/2011/ObtuseAngel.js +21 -63
  58. package/dist/resources/2012/RainDoh.js +14 -40
  59. package/dist/resources/2012/Resolutions.d.ts +9 -0
  60. package/dist/resources/2012/Resolutions.js +28 -0
  61. package/dist/resources/2013/Florist.d.ts +61 -0
  62. package/dist/resources/2013/Florist.js +149 -0
  63. package/dist/resources/2013/PulledTaffy.d.ts +9 -0
  64. package/dist/resources/2013/PulledTaffy.js +33 -0
  65. package/dist/resources/2014/WinterGarden.js +15 -43
  66. package/dist/resources/2015/ChateauMantegna.js +52 -86
  67. package/dist/resources/2015/MayoClinic.d.ts +13 -0
  68. package/dist/resources/2015/MayoClinic.js +36 -0
  69. package/dist/resources/2016/SourceTerminal.d.ts +1 -0
  70. package/dist/resources/2016/SourceTerminal.js +114 -237
  71. package/dist/resources/2016/Witchess.js +33 -59
  72. package/dist/resources/2017/TunnelOfLove.js +62 -111
  73. package/dist/resources/2018/SongBoom.js +32 -68
  74. package/dist/resources/2019/BeachComb.d.ts +2 -0
  75. package/dist/resources/2019/BeachComb.js +26 -0
  76. package/dist/resources/2019/Snapper.d.ts +28 -0
  77. package/dist/resources/2019/Snapper.js +70 -0
  78. package/dist/resources/2020/Guzzlr.js +79 -163
  79. package/dist/resources/LibramSummon.d.ts +12 -0
  80. package/dist/resources/LibramSummon.js +66 -0
  81. package/dist/resources/index.d.ts +18 -11
  82. package/dist/resources/index.js +19 -85
  83. package/dist/resources/putty-likes.js +15 -30
  84. package/dist/ring-buffer.d.ts +24 -0
  85. package/dist/ring-buffer.js +135 -0
  86. package/dist/since.d.ts +1 -0
  87. package/dist/since.js +56 -112
  88. package/dist/template-string.js +40 -132
  89. package/dist/utils.d.ts +14 -0
  90. package/dist/utils.js +50 -114
  91. package/package.json +5 -3
  92. package/dist/libram-example-briefcase.js +0 -16073
  93. package/dist/libram-example-clan.js +0 -8898
  94. package/dist/libram-example-consult.js +0 -6179
  95. package/dist/libram-example-item.js +0 -3248
  96. package/dist/libram-example-kmail.js +0 -2065
  97. package/dist/libram-example-lib.js +0 -7608
  98. package/dist/libram-example-props.js +0 -4770
  99. package/dist/libram-example-resources.js +0 -12226
package/dist/console.js CHANGED
@@ -1,38 +1,15 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.error = exports.warn = exports.info = exports.log = void 0;
7
-
8
- require("core-js/modules/es.array.map.js");
9
-
10
- require("core-js/modules/es.object.to-string.js");
11
-
12
- require("core-js/modules/es.regexp.to-string.js");
13
-
14
- var _kolmafia = require("kolmafia");
15
-
1
+ import { print } from "kolmafia";
16
2
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- var logColor = color => function () {
18
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
19
- args[_key] = arguments[_key];
20
- }
21
-
22
- var output = args.map(x => x.toString()).join(" ");
23
-
24
- if (color) {
25
- (0, _kolmafia.print)(output, color);
26
- } else {
27
- (0, _kolmafia.print)(output);
28
- }
3
+ const logColor = (color) => (...args) => {
4
+ const output = args.map((x) => x.toString()).join(" ");
5
+ if (color) {
6
+ print(output, color);
7
+ }
8
+ else {
9
+ print(output);
10
+ }
29
11
  };
30
-
31
- var log = logColor();
32
- exports.log = log;
33
- var info = logColor("blue");
34
- exports.info = info;
35
- var warn = logColor("red");
36
- exports.warn = warn;
37
- var error = logColor("red");
38
- exports.error = error;
12
+ export const log = logColor();
13
+ export const info = logColor("blue");
14
+ export const warn = logColor("red");
15
+ export const error = logColor("red");
@@ -0,0 +1,31 @@
1
+ declare type MenuItemOptions = {
2
+ organ?: Organ;
3
+ size?: number;
4
+ maximum?: number | "auto";
5
+ additionalValue?: number;
6
+ wishEffect?: Effect;
7
+ };
8
+ export declare class MenuItem {
9
+ item: Item;
10
+ organ?: Organ;
11
+ size: number;
12
+ maximum?: number;
13
+ additionalValue?: number;
14
+ wishEffect?: Effect;
15
+ static defaultOptions: Map<Item, MenuItemOptions>;
16
+ constructor(item: Item, options?: MenuItemOptions);
17
+ equals(other: MenuItem): boolean;
18
+ toString(): string;
19
+ price(): number;
20
+ }
21
+ declare const organs: readonly ["food", "booze", "spleen item"];
22
+ declare type Organ = typeof organs[number];
23
+ /**
24
+ * Plan out an optimal diet using a knapsack algorithm.
25
+ * @param mpa Meat per adventure value.
26
+ * @param menu Array of MenuItems to consider for diet purposes.
27
+ * @param organCapacities Optional override of each organ's capacity.
28
+ * @returns Array of [menu item and helpers, count].
29
+ */
30
+ export declare function planDiet(mpa: number, menu: MenuItem[], organCapacities?: [Organ, number | null][]): [MenuItem[], number][];
31
+ export {};
@@ -0,0 +1,333 @@
1
+ import { fullnessLimit, getWorkshed, inebrietyLimit, itemType, mallPrice, mallPrices, myFullness, myPrimestat, mySpleenUse, npcPrice, spleenLimit, } from "kolmafia";
2
+ import { knapsack } from "./knapsack";
3
+ import { have } from "../lib";
4
+ import { $effect, $item, $items, $skill, $stat } from "../template-string";
5
+ import { sum } from "../utils";
6
+ // TODO: Include other consumption modifiers - Salty Mouth?
7
+ // TODO: Include Gar-ish etc.
8
+ function expectedAdventures(item, modifiers) {
9
+ if (item.adventures === "")
10
+ return 0;
11
+ const [min, recordedMax] = item.adventures
12
+ .split(/[-–—]/)
13
+ .map((s) => parseInt(s));
14
+ const max = recordedMax ?? min;
15
+ const interpolated = [...new Array(max - min + 1).keys()].map((n) => n + min);
16
+ const forkMugMultiplier = (itemType(item) === "food" && item.notes?.includes("SALAD")) ||
17
+ (itemType(item) === "booze" && item.notes?.includes("BEER"))
18
+ ? 1.5
19
+ : 1.3;
20
+ const refinedPalate = modifiers.refinedPalate && item.notes?.includes("WINE");
21
+ const pinkyRing = modifiers.pinkyRing && item.notes?.includes("WINE");
22
+ return (sum(interpolated, (baseAdventures) => {
23
+ let adventures = baseAdventures;
24
+ if (modifiers.forkMug) {
25
+ adventures = Math.floor(adventures * forkMugMultiplier);
26
+ }
27
+ if (refinedPalate)
28
+ adventures = Math.floor(adventures * 1.25);
29
+ if (pinkyRing)
30
+ adventures = Math.round(adventures * 1.125);
31
+ if (item.notes?.includes("MARTINI") && modifiers.tuxedoShirt) {
32
+ adventures += 2;
33
+ }
34
+ if (have($skill `Saucemaven`) && item.notes?.includes("SAUCY")) {
35
+ adventures += myPrimestat() === $stat `Mysticality` ? 5 : 3;
36
+ }
37
+ if (itemType(item) === "food" && modifiers.seasoning)
38
+ adventures++;
39
+ if (itemType(item) === "food" && modifiers.mayoflex)
40
+ adventures++;
41
+ return adventures;
42
+ }) / interpolated.length);
43
+ }
44
+ export class MenuItem {
45
+ constructor(item, options = {}) {
46
+ const { size, organ, maximum, additionalValue, wishEffect } = {
47
+ ...options,
48
+ ...(MenuItem.defaultOptions.get(item) ?? {}),
49
+ };
50
+ this.item = item;
51
+ this.maximum = maximum === "auto" ? item.dailyusesleft : maximum;
52
+ this.additionalValue = additionalValue;
53
+ this.wishEffect = wishEffect;
54
+ const typ = itemType(this.item);
55
+ this.organ = organ ?? (isOrgan(typ) ? typ : undefined);
56
+ this.size =
57
+ size ??
58
+ (this.organ === "food"
59
+ ? this.item.fullness
60
+ : this.organ === "booze"
61
+ ? this.item.inebriety
62
+ : this.organ === "spleen item"
63
+ ? this.item.spleen
64
+ : 0);
65
+ }
66
+ equals(other) {
67
+ return this.item === other.item && this.wishEffect === other.wishEffect;
68
+ }
69
+ toString() {
70
+ return this.item.toString();
71
+ }
72
+ price() {
73
+ return npcPrice(this.item) > 0 ? npcPrice(this.item) : mallPrice(this.item);
74
+ }
75
+ }
76
+ MenuItem.defaultOptions = new Map([
77
+ [$item `Mr. Burnsger`, { maximum: "auto" }],
78
+ [
79
+ $item `distention pill`,
80
+ {
81
+ organ: "food",
82
+ maximum: "auto",
83
+ size: -1,
84
+ },
85
+ ],
86
+ [
87
+ $item `synthetic dog hair pill`,
88
+ { organ: "booze", maximum: "auto", size: -1 },
89
+ ],
90
+ [$item `cuppa Voraci tea`, { organ: "food", maximum: "auto", size: -1 }],
91
+ [$item `cuppa Sobrie tea`, { organ: "booze", maximum: "auto", size: -1 }],
92
+ [$item `mojo filter`, { organ: "spleen item", maximum: "auto", size: -1 }],
93
+ ]);
94
+ const organs = ["food", "booze", "spleen item"];
95
+ function isOrgan(x) {
96
+ return organs.includes(x);
97
+ }
98
+ class DietPlanner {
99
+ constructor(mpa, menu) {
100
+ this.spleenValue = 0;
101
+ this.mpa = mpa;
102
+ this.fork = menu.find((item) => item.item === $item `Ol' Scratch's salad fork`);
103
+ this.mug = menu.find((item) => item.item === $item `Frosty's frosty mug`);
104
+ this.seasoning = menu.find((item) => item.item === $item `Special Seasoning`);
105
+ this.mayoflex =
106
+ getWorkshed() === $item `portable Mayo Clinic`
107
+ ? menu.find((item) => item.item === $item `Mayoflex`)
108
+ : undefined;
109
+ this.pinkyRing = have($item `mafia pinky ring`);
110
+ this.tuxedoShirt = have($item `tuxedo shirt`);
111
+ this.menu = menu.filter((item) => item.organ);
112
+ if (menu.length > 100) {
113
+ mallPrices("food");
114
+ mallPrices("booze");
115
+ }
116
+ const spleenItems = menu.filter((item) => itemType(item.item) === "spleen item");
117
+ spleenItems.sort((x, y) => -(this.consumptionValue(x) / x.item.spleen -
118
+ this.consumptionValue(y) / y.item.spleen));
119
+ if (spleenItems.length > 0) {
120
+ this.spleenValue =
121
+ this.consumptionValue(spleenItems[0]) / spleenItems[0].item.spleen;
122
+ }
123
+ }
124
+ consumptionValue(menuItem) {
125
+ return this.consumptionHelpersAndValue(menuItem, {})[1];
126
+ }
127
+ consumptionHelpersAndValue(menuItem, overrideModifiers) {
128
+ const helpers = [];
129
+ if (this.seasoning &&
130
+ itemType(menuItem.item) === "food" &&
131
+ this.mpa > mallPrice($item `Special Seasoning`)) {
132
+ helpers.push(this.seasoning);
133
+ }
134
+ if (this.mayoflex &&
135
+ itemType(menuItem.item) === "food" &&
136
+ this.mpa > npcPrice($item `Mayoflex`)) {
137
+ helpers.push(this.mayoflex);
138
+ }
139
+ const defaultModifiers = {
140
+ forkMug: false,
141
+ seasoning: this.seasoning ? helpers.includes(this.seasoning) : false,
142
+ mayoflex: this.mayoflex ? helpers.includes(this.mayoflex) : false,
143
+ refinedPalate: false,
144
+ pinkyRing: this.pinkyRing,
145
+ tuxedoShirt: this.tuxedoShirt,
146
+ ...overrideModifiers,
147
+ };
148
+ const forkMug = itemType(menuItem.item) === "food"
149
+ ? this.fork
150
+ : itemType(menuItem.item) === "booze"
151
+ ? this.mug
152
+ : null;
153
+ const forkMugPrice = forkMug ? forkMug.price() : Infinity;
154
+ const baseCost = menuItem.price() + sum(helpers, (item) => item.price());
155
+ const valueRaw = expectedAdventures(menuItem.item, defaultModifiers) * this.mpa -
156
+ baseCost +
157
+ (menuItem.additionalValue ?? 0);
158
+ const valueForkMug = expectedAdventures(menuItem.item, {
159
+ ...defaultModifiers,
160
+ forkMug: true,
161
+ }) *
162
+ this.mpa -
163
+ baseCost -
164
+ forkMugPrice +
165
+ (menuItem.additionalValue ?? 0);
166
+ const valueSpleen = $items `jar of fermented pickle juice, extra-greasy slider`.includes(menuItem.item)
167
+ ? 5 * this.spleenValue
168
+ : 0;
169
+ return forkMug && valueForkMug > valueRaw
170
+ ? [[...helpers, forkMug, menuItem], valueForkMug + valueSpleen]
171
+ : [[...helpers, menuItem], valueRaw + valueSpleen];
172
+ }
173
+ planOrgan(organ, capacity, overrideModifiers = {}) {
174
+ // print(`Plan ${organ} < ${capacity}`);
175
+ const submenu = this.menu.filter((item) => item.organ === organ);
176
+ const knapsackValues = submenu.map((menuItem) => [
177
+ ...this.consumptionHelpersAndValue(menuItem, overrideModifiers),
178
+ menuItem.size,
179
+ menuItem.maximum,
180
+ ]);
181
+ return knapsack(knapsackValues, capacity);
182
+ // print(
183
+ // `Items: ${itemList.length} ${([] as Item[])
184
+ // .concat(...itemList)
185
+ // .map((item) => item.name)
186
+ // .join(", ")}`
187
+ // );
188
+ }
189
+ planOrgans(organCapacities, overrideModifiers = {}) {
190
+ const valuePlans = organCapacities.map(([organ, capacity]) => this.planOrgan(organ, capacity, overrideModifiers));
191
+ return [
192
+ sum(valuePlans, ([value]) => value),
193
+ [].concat(...valuePlans.map(([, plan]) => plan)),
194
+ ];
195
+ }
196
+ planOrgansWithTrials(organCapacities, trialItems, overrideModifiers = {}) {
197
+ if (trialItems.length === 0) {
198
+ return this.planOrgans(organCapacities, overrideModifiers);
199
+ }
200
+ const organCapacitiesWithMap = new Map(organCapacities);
201
+ const [trialItem, organSizes] = trialItems[0];
202
+ // print(`TRYING ${trialItem.item.name}`);
203
+ for (const [organ, size] of organSizes) {
204
+ const current = organCapacitiesWithMap.get(organ);
205
+ if (current !== undefined) {
206
+ organCapacitiesWithMap.set(organ, current - size);
207
+ }
208
+ }
209
+ const organCapacitiesWith = [...organCapacitiesWithMap];
210
+ const isRefinedPalate = trialItem.item === $item `pocket wish` &&
211
+ trialItem.wishEffect === $effect `Refined Palate`;
212
+ const [valueWithout, planWithout] = this.planOrgansWithTrials(organCapacities, trialItems.slice(1), overrideModifiers);
213
+ const [valueWith, planWith] = this.planOrgansWithTrials(organCapacitiesWith, trialItems.slice(1), isRefinedPalate
214
+ ? { ...overrideModifiers, refinedPalate: true }
215
+ : overrideModifiers);
216
+ const [helpersAndItem, value] = this.consumptionHelpersAndValue(trialItem, {});
217
+ // print(
218
+ // `${new Array(trialItems.length).join(">")} ${
219
+ // valueWithout > valueWith + value ? "WITHOUT" : "WITH"
220
+ // } ${trialItem.item} ${trialItem.wishEffect ?? ""} ${value.toFixed(
221
+ // 0
222
+ // )}: ${valueWithout.toFixed(0)} vs. ${(valueWith + value).toFixed(0)}`
223
+ // );
224
+ return valueWithout > valueWith + value
225
+ ? [valueWithout, planWithout]
226
+ : [valueWith, [...planWith, [helpersAndItem, 1]]];
227
+ }
228
+ }
229
+ /**
230
+ * Because the knapsack solver is one-dimensional only, any items that touch
231
+ * multiple organs have to be treated specially. What we do is run the knapsack
232
+ * solver multiple times, trying with + without each interacting item.
233
+ */
234
+ const interactingItems = [
235
+ [
236
+ $item `spice melange`,
237
+ [
238
+ ["food", -3],
239
+ ["booze", -3],
240
+ ],
241
+ ],
242
+ [
243
+ $item `Ultra Mega Sour Ball`,
244
+ [
245
+ ["food", -3],
246
+ ["booze", -3],
247
+ ],
248
+ ],
249
+ [
250
+ $item `The Plumber's mushroom stew`,
251
+ [
252
+ ["food", 3],
253
+ ["booze", -1],
254
+ ],
255
+ ],
256
+ [
257
+ $item `The Mad Liquor`,
258
+ [
259
+ ["food", -1],
260
+ ["booze", 3],
261
+ ],
262
+ ],
263
+ [
264
+ $item `Doc Clock's thyme cocktail`,
265
+ [
266
+ ["food", -2],
267
+ ["booze", 4],
268
+ ],
269
+ ],
270
+ [
271
+ $item `Mr. Burnsger`,
272
+ [
273
+ ["food", 4],
274
+ ["booze", -2],
275
+ ],
276
+ ],
277
+ ];
278
+ /**
279
+ * Plan out an optimal diet using a knapsack algorithm.
280
+ * @param mpa Meat per adventure value.
281
+ * @param menu Array of MenuItems to consider for diet purposes.
282
+ * @param organCapacities Optional override of each organ's capacity.
283
+ * @returns Array of [menu item and helpers, count].
284
+ */
285
+ export function planDiet(mpa, menu, organCapacities = [
286
+ ["food", null],
287
+ ["booze", null],
288
+ ["spleen item", null],
289
+ ]) {
290
+ const dietPlanner = new DietPlanner(mpa, menu);
291
+ // print("MENU:");
292
+ for (const menuItem of menu) {
293
+ const [helpers, value] = dietPlanner.consumptionHelpersAndValue(menuItem, {});
294
+ // print(`${menuItem.item.name}: ${helpers.join(", ")} ${value}`);
295
+ }
296
+ const resolvedOrganCapacities = organCapacities.map(([organ, size]) => [
297
+ organ,
298
+ size ??
299
+ (organ === "food"
300
+ ? fullnessLimit() -
301
+ myFullness() +
302
+ (have($item `distention pill`) ? 1 : 0)
303
+ : organ === "booze"
304
+ ? inebrietyLimit() + (have($item `synthetic dog hair pill`) ? 1 : 0)
305
+ : organ === "spleen item"
306
+ ? spleenLimit() - mySpleenUse()
307
+ : 0),
308
+ ]);
309
+ const allItems = new Map(menu.map((menuItem) => [menuItem.item, menuItem]));
310
+ const includedInteractingItems = interactingItems
311
+ .map(([item, sizes]) => [allItems.get(item), sizes])
312
+ .filter(([menuItem]) => menuItem);
313
+ // print(
314
+ // `included interacting: ${includedInteractingItems
315
+ // .map(([menuItem]) => menuItem.item.name)
316
+ // .join(", ")}`
317
+ // );
318
+ // TODO: support toasted brie.
319
+ // Refined Palate must also be treated as an interacting item, as it's a one-time cost.
320
+ const palateWish = menu.find((menuItem) => menuItem.item === $item `pocket wish` &&
321
+ menuItem.wishEffect === $effect `Refined Palate`);
322
+ if (palateWish) {
323
+ includedInteractingItems.push([palateWish, []]);
324
+ }
325
+ const [, planFoodBooze] = dietPlanner.planOrgansWithTrials(resolvedOrganCapacities.filter(([organ]) => ["food", "booze"].includes(organ)), includedInteractingItems);
326
+ // Count sliders and pickle juice, figure out how much extra spleen we got.
327
+ const additionalSpleen = sum(planFoodBooze, ([items, number]) => items.some((menuItem) => $items `jar of fermented pickle juice, extra-greasy slider`.includes(menuItem.item))
328
+ ? 5 * number
329
+ : 0);
330
+ const [, availableSpleen] = resolvedOrganCapacities.find(([organ]) => organ === "spleen item") ?? ["spleen item", 0];
331
+ const [, planSpleen] = dietPlanner.planOrgan("spleen item", availableSpleen + additionalSpleen);
332
+ return [...planFoodBooze, ...planSpleen];
333
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Solve the knapsack problem.
3
+ * @param values Array of {[item, value, weight, maximum]} tuples for knapsack parameter.
4
+ * @param capacity Capacity of knapsack.
5
+ * @returns Tuple {[totalValue, items]} of selected items and total value of those items.
6
+ */
7
+ export declare function knapsack<T>(values: [T, number, number, number?][], capacity: number): [number, [T, number][]];
@@ -0,0 +1,106 @@
1
+ import { sum } from "../utils";
2
+ class Not {
3
+ constructor(thing) {
4
+ this.thing = thing;
5
+ }
6
+ }
7
+ // Assuming list is already sorted, count adjacent items.
8
+ // Effectively run-length encoding.
9
+ function aggregate(list, isEqual) {
10
+ const aggregatedList = [];
11
+ for (const item of list) {
12
+ if (aggregatedList.length === 0) {
13
+ aggregatedList.push([item, 1]);
14
+ }
15
+ else {
16
+ const last = aggregatedList[aggregatedList.length - 1];
17
+ const [lastItem] = last;
18
+ if (isEqual ? isEqual(item, lastItem) : item === lastItem) {
19
+ last[1]++;
20
+ }
21
+ else {
22
+ aggregatedList.push([item, 1]);
23
+ }
24
+ }
25
+ }
26
+ return aggregatedList;
27
+ }
28
+ /**
29
+ * Solve the knapsack problem.
30
+ * @param values Array of {[item, value, weight, maximum]} tuples for knapsack parameter.
31
+ * @param capacity Capacity of knapsack.
32
+ * @returns Tuple {[totalValue, items]} of selected items and total value of those items.
33
+ */
34
+ export function knapsack(values, capacity) {
35
+ // Invert negative values into a fake value for not using it.
36
+ const valuesInverted = values.map(([thing, value, weight, maximum]) => (weight < 0 && maximum
37
+ ? [new Not(thing), -value, -weight, maximum]
38
+ : [thing, value, weight, maximum]));
39
+ const capacityAdjustment = sum(values, ([, , weight, maximum]) => weight < 0 && maximum ? -weight * maximum : 0);
40
+ const adjustedCapacity = capacity + capacityAdjustment;
41
+ // Sort values by weight.
42
+ const valuesSorted = [...valuesInverted].sort((x, y) => x[2] - y[2]);
43
+ // Convert the problem into 0/1 knapsack - just include as many copies as possible of each item.
44
+ const values01 = [].concat(...valuesSorted.map(([thing, value, weight, maximum]) => {
45
+ const maxQuantity = maximum ?? Math.floor(adjustedCapacity / weight);
46
+ return new Array(maxQuantity).fill([
47
+ thing,
48
+ value,
49
+ weight,
50
+ ]);
51
+ }));
52
+ const memoizationTable = new Array(values01.length);
53
+ for (let i = 0; i < values01.length; i++) {
54
+ memoizationTable[i] = new Array(adjustedCapacity).fill(null);
55
+ }
56
+ const [value, invertedSolution] = bestSolution(memoizationTable, values01, values01.length - 1, adjustedCapacity);
57
+ // Still need to replace Not<T>s with right quantity of T's.
58
+ const aggregatedSolution = aggregate(invertedSolution);
59
+ const countMap = new Map(aggregatedSolution);
60
+ let valueAdjustment = 0;
61
+ const solution = aggregatedSolution.filter(([thingOrNot]) => !(thingOrNot instanceof Not));
62
+ for (const [thingOrNot, value, , maximum] of valuesSorted) {
63
+ if (thingOrNot instanceof Not) {
64
+ const notCount = countMap.get(thingOrNot) ?? 0;
65
+ if (maximum === undefined) {
66
+ throw new Error(`Cannot find maximum for item ${thingOrNot.thing}.`);
67
+ }
68
+ if (notCount > maximum) {
69
+ throw new Error(`Somehow picked ${notCount} more than the maximum ${notCount} for item ${thingOrNot.thing}.`);
70
+ }
71
+ if (notCount < maximum) {
72
+ solution.push([thingOrNot.thing, maximum - notCount]);
73
+ }
74
+ valueAdjustment -= maximum * value;
75
+ }
76
+ }
77
+ return [value + valueAdjustment, solution];
78
+ }
79
+ /**
80
+ * Find the best solution to a knapsack subproblem.
81
+ * @param memoizationTable Memoization table for dynamic programming approach.
82
+ * @param values Array of {[item, value, weight, maximum]} tuples for knapsack parameter.
83
+ * @param currentIndex Current index into values array - only add items before this index.
84
+ * @param remainingCapacity Remaining capacity of knapsack.
85
+ * @returns
86
+ */
87
+ function bestSolution(memoizationTable, values, currentIndex, remainingCapacity) {
88
+ // If we've used all our capacity, this solution is no good.
89
+ if (remainingCapacity < 0)
90
+ return [-Infinity, []];
91
+ if (remainingCapacity === 0 || currentIndex < 0)
92
+ return [0, []];
93
+ const memoized = memoizationTable[currentIndex][remainingCapacity - 1];
94
+ if (memoized !== null)
95
+ return memoized;
96
+ const [item, value, weight] = values[currentIndex];
97
+ const [valueIncludeRest, itemsInclude] = bestSolution(memoizationTable, values, currentIndex - 1, remainingCapacity - weight);
98
+ const valueInclude = valueIncludeRest + value;
99
+ const [valueExclude, itemsExclude] = bestSolution(memoizationTable, values, currentIndex - 1, remainingCapacity);
100
+ // Pick the better of the two options between including/excluding.
101
+ const result = valueInclude > valueExclude
102
+ ? [valueInclude, [...itemsInclude, item]]
103
+ : [valueExclude, itemsExclude];
104
+ memoizationTable[currentIndex][remainingCapacity - 1] = result;
105
+ return result;
106
+ }
@@ -0,0 +1,4 @@
1
+ export declare function close(): boolean;
2
+ export declare function open(paymentPolicy?: "None" | "All" | "Difference"): boolean;
3
+ export declare function distribute(idOrName?: number | string, loot?: Item | Item[] | Map<Item, number>, distributeAllOfAGivenItem?: boolean): void;
4
+ export declare function findLoot(): Map<Item, number>;
@@ -0,0 +1,14 @@
1
+ import { myId } from "kolmafia";
2
+ import { close as closeDungeon, distribute as distributeDungeon, Dreadsylvania, findLoot as findLootDungeon, open as openDungeon, } from "./Dungeon";
3
+ export function close() {
4
+ return closeDungeon(Dreadsylvania);
5
+ }
6
+ export function open(paymentPolicy = "Difference") {
7
+ return openDungeon(Dreadsylvania, paymentPolicy);
8
+ }
9
+ export function distribute(idOrName = myId(), loot = Dreadsylvania.loot, distributeAllOfAGivenItem = true) {
10
+ distributeDungeon(Dreadsylvania, idOrName, loot, distributeAllOfAGivenItem);
11
+ }
12
+ export function findLoot() {
13
+ return findLootDungeon(Dreadsylvania);
14
+ }
@@ -0,0 +1,28 @@
1
+ export declare type Dungeon = {
2
+ name: string;
3
+ loot: Item[];
4
+ openAction: string;
5
+ closeAction: string;
6
+ openCost: number;
7
+ openImage: string;
8
+ closedImage: string;
9
+ };
10
+ /**
11
+ * Distributes loot from given dungeon
12
+ * @param dungeon The dungeon to distribute loot from
13
+ * @param idOrName The player you're trying to distribute to, either as a username or a player ID. Defaults to self.
14
+ * @param loot The loot you're looking to distribute, specific to this dungeon
15
+ * @param distributeAllOfAGivenItem For items that you can get multiple of in a dungeon. When true, this will give everything of that ilk to your chosen player.
16
+ */
17
+ export declare function distribute(dungeon: Dungeon, idOrName?: number | string, loot?: Item | Item[] | Map<Item, number>, distributeAllOfAGivenItem?: boolean): void;
18
+ export declare function close(dungeon: Dungeon): boolean;
19
+ /**
20
+ * Opens clan dungeon and, if relevant, pays meat to do so
21
+ * @param dungeon The Dungeon to open
22
+ * @param paymentPolicy "None", "All", or "Difference". Difference pays into the stash the exact amount needed to open the dungeon.
23
+ */
24
+ export declare function open(dungeon: Dungeon, paymentPolicy?: "None" | "All" | "Difference"): boolean;
25
+ export declare const Dreadsylvania: Dungeon;
26
+ export declare const Hobopolis: Dungeon;
27
+ export declare const SlimeTube: Dungeon;
28
+ export declare function findLoot(dungeon: Dungeon): Map<Item, number>;