-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
305 lines (262 loc) · 12.6 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
<!DOCTYPE html>
<html lang="en">
<head>
<title>Dark Souls 3 level up consumables calculator</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="keywords" content="Dark Souls 3, ds3, dsiii, fromsoftware, calculator, level up, consumables">
<meta name="robots" content="index, follow">
<style>
body {
font-family: Arial, sans-serif;
font-size: 16px;
}
h2, h3 {
color: #333;
}
form {
margin-top: 2rem;
}
form div {
margin-bottom: 0.5rem;
}
form div label {
display: inline-block;
width: 20rem;
text-align: right;
}
button {
border: none;
padding: 1rem 2rem;
text-align: center;
display: inline-block;
font-size: 1rem;
margin: 1rem 0.2rem;
cursor: pointer;
}
.primary-button {
background-color: #2f4f4f;
color: white;
text-decoration: none;
}
.secondary-button {
background-color: transparent;
color: #708090;
text-decoration: underline;
}
p#output {
background: #f8f8f8;
padding: 0.5rem 2rem;
border: 1px solid #ddd;
border-radius: 5px;
margin-top: 1rem;
}
</style>
</head>
<body>
<header>
<h2>Dark Souls 3 level up consumables calculator</h2>
<p>
This calculator will help you optimize the use of soul consumables to level up in Dark Souls 3. It will calculate
the highest level you can reach, and tell you exactly which items to use to minimize waste (i.e. leftover soft
souls).
</p>
</header>
<main>
<form id="ds3Calc">
<div class="wideInput"><label>Current character level: <input type="number" id="currLevel" min="1" max="801"
value="1"/></label></div>
<div class="wideInput"><label>Current soft souls: <input type="number" id="softSouls" min="0" max="9999999999"
value="0"/></label></div>
<h3>New Game Items</h3>
<div id="newGameItems"></div>
<h3>New Game+ Items</h3>
<div id="newGamePlusItems"></div>
<button type="button" class="primary-button" onclick="calculate()">Calculate</button>
<button type="button" class="secondary-button" onclick="resetInputs()">Reset</button>
</form>
<p id="output"></p>
</main>
<footer>
<ul>
<li><a href="https://github.com/drauf/ds3-level-up-calculator/issues">Report an issue</a></li>
<li><a href="https://github.com/drauf/ds3-level-up-calculator">Source code</a></li>
</ul>
</footer>
<script>
// Pre-calculate the cost of each level using the formula from DS3 wiki
const MAX_LEVEL = 802;
const levelCosts = [0, 0, 673, 689, 706, 723, 740, 757, 775, 793, 811, 829, 847];
for (let level = 13; level <= MAX_LEVEL; level++) {
levelCosts[level] = Math.floor(0.02 * Math.pow(level, 3) + 3.06 * Math.pow(level, 2) + 105.6 * level - 895);
}
// Define the soul items
const newGameItems = [
{id: "fadingSoul", name: "Fading Soul", value: 50},
{id: "soulDesertedCorpse", name: "Soul of a Deserted Corpse", value: 200},
{id: "largeSoulDesertedCorpse", name: "Large Soul of a Deserted Corpse", value: 400},
{id: "soulUnknownTraveler", name: "Soul of an Unknown Traveler", value: 800},
{id: "largeSoulUnknownTraveler", name: "Large Soul of an Unknown Traveler", value: 1000},
{id: "soulNamelessSoldier", name: "Soul of a Nameless Soldier", value: 2000},
{id: "largeSoulNamelessSoldier", name: "Large Soul of a Nameless Soldier", value: 3000},
{id: "soulWearyWarrior", name: "Soul of a Weary Warrior", value: 5000},
{id: "largeSoulWearyWarrior", name: "Large Soul of a Weary Warrior", value: 8000},
{id: "soulCrestfallenKnight", name: "Soul of a Crestfallen Knight", value: 10000},
{id: "largeSoulCrestfallenKnight", name: "Large Soul of a Crestfallen Knight", value: 20000}
];
const newGamePlusItems = [
{id: "soulProudPaladin", name: "Soul of a Proud Paladin", value: 500},
{id: "largeSoulProudPaladin", name: "Large Soul of a Proud Paladin", value: 1000},
{id: "soulIntrepidHero", name: "Soul of an Intrepid Hero", value: 2000},
{id: "largeSoulIntrepidHero", name: "Large Soul of an Intrepid Hero", value: 2500},
{id: "soulSeasonedWarrior", name: "Soul of a Seasoned Warrior", value: 5000},
{id: "largeSoulSeasonedWarrior", name: "Large Soul of a Seasoned Warrior", value: 7500},
{id: "soulOldHand", name: "Soul of an Old Hand", value: 12500},
{id: "soulVenerableOldHand", name: "Soul of a Venerable Old Hand", value: 20000},
{id: "soulChampion", name: "Soul of a Champion", value: 25000},
{id: "soulGreatChampion", name: "Soul of a Great Champion", value: 50000}
];
// Generate the inputs for the soul items
function generateInputs(items, containerId) {
const container = document.getElementById(containerId);
items.forEach(item => {
const div = document.createElement('div');
const label = document.createElement('label');
const input = document.createElement('input');
input.type = 'number';
input.id = item.id;
input.min = "0";
input.max = "999";
input.value = "0";
label.textContent = item.name + ": ";
label.appendChild(input);
div.appendChild(label);
container.appendChild(div);
});
}
generateInputs(newGameItems, "newGameItems");
generateInputs(newGamePlusItems, "newGamePlusItems");
// https://en.wikipedia.org/wiki/Knapsack_problem
function solveKnapsackProblem(maximum, items) {
const weights = Array.from({length: maximum + 1}, () => 0);
const usedItems = Array.from({length: maximum + 1}, () => []);
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
const itemValue = items[itemIndex].value;
for (let remainingValueIndex = maximum; remainingValueIndex >= itemValue; remainingValueIndex--) {
for (let quantityIndex = items[itemIndex].quantity; quantityIndex >= 1; quantityIndex--) {
if (remainingValueIndex < quantityIndex * itemValue) continue;
const newValue = weights[remainingValueIndex - quantityIndex * itemValue] + quantityIndex * itemValue;
if (weights[remainingValueIndex] < newValue) {
weights[remainingValueIndex] = newValue;
usedItems[remainingValueIndex] = [...usedItems[remainingValueIndex - quantityIndex * itemValue], ...Array(quantityIndex).fill(items[itemIndex].id)];
items[itemIndex].quantity -= quantityIndex;
break;
}
}
}
}
const finalItemsValue = weights[maximum];
const finalUsedItems = usedItems[maximum];
return {finalItemsValue, finalUsedItems};
}
function calculateUsedItems(soulsLeftAfterLevellingUp, inventory) {
// use the fact that all item values are multiples of 50 to reduce the problem size
const ITEM_VALUE_MULTIPLIER = 50;
const scaledSoulsLeftAfterLevellingUp = Math.floor(soulsLeftAfterLevellingUp / ITEM_VALUE_MULTIPLIER);
const scaledInventory = JSON.parse(JSON.stringify(inventory)).map(item => {
item.value = Math.floor(item.value / ITEM_VALUE_MULTIPLIER);
return item;
})
// solve the knapsack problem, i.e. find the combination of items that goes as close to soulsLeftAfterLevellingUp
// as possible without going over - we look for items we can skip consuming and still reach the same level
const {
finalItemsValue,
finalUsedItems
} = solveKnapsackProblem(scaledSoulsLeftAfterLevellingUp, scaledInventory);
const unusedItemsSoulsValue = finalItemsValue * ITEM_VALUE_MULTIPLIER;
// because we calculated items that we DO NOT consume, we need to "flip" the result to get the items we DO consume
const unusedQuantities = {};
finalUsedItems.forEach(id => {
unusedQuantities[id] = (unusedQuantities[id] || 0) + 1;
});
const usedItems = inventory.map(item => ({
...item,
quantity: item.quantity - (unusedQuantities[item.id] || 0)
})).filter(item => item.quantity > 0);
return {unusedItemsSoulsValue, usedItems};
}
function optimizeLevelUp(allItems, levelCosts) {
// fetch current level, soft souls & quantity of each item
const currLevel = parseInt(document.getElementById("currLevel").value, 10) || 1;
const softSouls = parseInt(document.getElementById("softSouls").value, 10) || 0;
const inventory = allItems.map(item => {
return {
...item,
quantity: parseInt(document.getElementById(item.id).value, 10) || 0
}
}).filter(item => item.quantity > 0);
// calculate total souls available
const totalSoulsAvailable = softSouls + inventory.reduce((sum, item) => sum + (item.value * item.quantity), 0);
// calculate the highest level that can be reached with the available souls
let soulsLeftAfterLevellingUp = totalSoulsAvailable;
let maxLevel = currLevel;
while (levelCosts[maxLevel + 1] <= soulsLeftAfterLevellingUp) {
soulsLeftAfterLevellingUp -= levelCosts[maxLevel + 1];
maxLevel++;
}
// if maxLevel is already at the cap, return early to not waste time on the knapsack problem
if (maxLevel >= MAX_LEVEL) {
return {
highestLevel: MAX_LEVEL,
nextLevelCost: "N/A",
soulsUsed: "N/A",
remainingSoftSouls: "N/A",
remainingItemsSouls: "N/A",
usedItems: []
};
}
// if there were no level ups, also return early to not waste time on the knapsack problem
if (maxLevel === currLevel) {
return {
highestLevel: currLevel,
nextLevelCost: levelCosts[currLevel + 1],
soulsUsed: 0,
remainingSoftSouls: softSouls,
remainingItemsSouls: totalSoulsAvailable - softSouls,
usedItems: []
};
}
const {unusedItemsSoulsValue, usedItems} = calculateUsedItems(soulsLeftAfterLevellingUp, inventory);
const usedSouls = Math.min(totalSoulsAvailable - unusedItemsSoulsValue, levelCosts.slice(currLevel + 1, maxLevel + 1).reduce((a, b) => a + b, 0));
const remainingSoftSouls = Math.max(0, totalSoulsAvailable - usedSouls - unusedItemsSoulsValue);
return {
highestLevel: maxLevel,
nextLevelCost: levelCosts[maxLevel + 1],
soulsUsed: usedSouls,
remainingSoftSouls: remainingSoftSouls,
remainingItemsSouls: unusedItemsSoulsValue,
usedItems: usedItems
};
}
function calculate() {
const result = optimizeLevelUp(newGameItems.concat(newGamePlusItems), levelCosts);
const levelInfo = `<p>Highest Level: ${result.highestLevel}<br/>Souls to Next Level: ${result.nextLevelCost}</p>`;
const leftoverSouls = result.remainingSoftSouls + result.remainingItemsSouls;
const soulsInfo = `<p>Total souls used: ${result.soulsUsed}<br/>Total leftover souls: ${leftoverSouls}<br/>Leftover soft souls: ${result.remainingSoftSouls}<br/>Leftover souls in consumables: ${result.remainingItemsSouls}</p>`;
const usedItemsList = result.usedItems.length === 0 ? '<li>None</li>' : `${result.usedItems.map(item => `<li>${item.quantity}x ${item.name}</li>`).join("")}`;
const usedItemsInfo = `<p>Consumables used:<br/><ul>${usedItemsList}</ul></p>`;
document.getElementById("output").innerHTML = levelInfo + soulsInfo + usedItemsInfo;
}
function resetInputs() {
document.getElementById("currLevel").value = 1;
document.getElementById("softSouls").value = 0;
const allItems = newGameItems.concat(newGamePlusItems);
allItems.forEach(item => {
document.getElementById(item.id).value = 0;
});
calculate();
}
calculate();
</script>
</body>
</html>