В свое время Diablo2 занял приличное кол-во времени перед монитором и был лишь вопрос времени пока ремастер затянет меня снова. Время от времени вспоминая былые времена, уже в Resurrected версии, одной из основных задачей в игре это поиск рун. Точно как в оригинале.
Переходите по ссылке чтобы увидеть как происходит улучшение рун - https://d2r.annoyed.dev/
Руны в игре считаются ценными предметами, которые используется как валюта. Игрок может улучшить свое снаряжение посредством рун, либо купить за них предметы у других игроков. В игре всего 33 руны, каждая последующая являясь уровнем выше предыдущей. Улучшение рун сводится к превращению некоторого количества одинаковых рун в одну следующего уровня.
Бывалые игроки хорошо знакомы насколько сложно получить руны высокого уровня. Одним из способов, помимо их поиска, это превращение рун посредством хорадрического куба. Последний вариант требует много рун, очень много. И это занятие выглядит простым и результативным только с первого взгляда.
Если пойти по совсем экстремальному пути и попытаться превратить самые простые El руны в самую редкую Zod - сколько рун потребуется? И сколько по времени?
Немного расчетов... До 21-й руны - Pul - игроку потребуется по 3 одинаковых руны (не считая камней). Следующие 12 уровней, потребуется уже по две руны и так до самой Zod.
Таким образом, перемножая наборы - 320*212 - получаем примерно 14 триллионов (14.281.868.906.496 если точно) El рун!
Как это бы выглядело в реальности? Для визуализации процесса был создан симулятор улучшения рун - https://d2r.annoyed.dev/
Достаточно сказать, что со времени релиза Diablo2 (в 2000 году) и взяв за основу что каждую секунду в сундук добавляется по одной руне El, то на данный момент мы дошли только до руны Fal.
Как это работает
Идея была создать интерфейс который симулирует добавление в сундук рун и их превращение. Каждая руна это список из 5 элементов: название, камень (если нужен) и три слота для рун. В зависимости от уровня руны, показаны 3 или 2 руны соответственно.
Каждый список приравнен к набору рун, в котором, к примеру, 3 руны нужны для превращения в руну следующего уровня.
Страница использует обычный javascript для визуализации процесса.
При каждом проходе, сценарий добавляет по одной руне El в набор. Если набор полон, двигаемся к следующему набору и добавляем руну туда. При каждом заполнении набора, двигаемся дальше, если нет - возвращаемся к первому набору с руной El. Повторяем.
setInterval(function () {
if (runeSetFilled(current)) {
// Whether the rune set is filled (all runes exist to transmute), reset it.
// Move forward until there are full rune sets.
while (runeSetFilled(current)) {
runeSetReset(current);
current = next(current);
// We reached Zod, which is unlikely in near future.
if (current === null) {
current = elRune;
}
}
}
// Add rune to current set.
runeSetFill(current);
// If we moved forward, get back to El rune, to start over.
if (!runeSetFilled(current)) {
current = elRune;
}
}, timeout);
Задан временной интервал для каждого прохода, присвоенный к переменной timeout. Временной интервал по умолчанию равен 1000, что является 1000 миллисекундам или 1 секунде.
Задаем начальное состояние счетчиков
Для каждого набора рун выведен их счетчик. Следует инициализировать эти счетчики и задать им кол-во рун созданных до текущего момента. Стартовая дата для расчетов по умолчанию задана как дата релиза игры - 29 июня 2000.
После загрузки страницы, код высчитывает разницу в секундах между стартовой и текущей датой (вплоть до секунды). Исходя из того, что скорость генерации/улучшения рун равна одной секунде, получаем что разница в датах в секундах равна кол-ву рун El которые могли быть собраны за это время.
Для подсчета кол-ва рун в каждом наборе, зададим понятие веса. Вес в этом случае, это кол-во шагов требуемых для создания руны заданного уровня. Для экономии в расчетах, вес каждой руны заранее просчитан и задан как data атрибут в каждом наборе. На практике вес, равен кол-ву рун El плюс кол-во шагов затраченных для их превращения.
В цифрах, вес рун выглядит следующим образом:
Руна | Вес |
---|---|
El | 1 |
Eld | 4 |
Tir | 13 |
Nef | 40 |
Eth | 121 |
Ith | 364 |
Tal | 1093 |
Ral | 3280 |
Ort | 9841 |
Thul | 29524 |
Amn | 88573 |
Sol | 265720 |
Shael | 797161 |
Dol | 2391484 |
Hel | 7174453 |
Io | 21523360 |
Lum | 64570081 |
Ko | 193710244 |
Fal | 581130733 |
Lem | 1743392200 |
Pul | 3486784401 |
Um | 6973568803 |
Mal | 13947137607 |
Ist | 27894275215 |
Gul | 55788550431 |
Vex | 111577100863 |
Ohm | 223154201727 |
Lo | 446308403455 |
Sur | 892616806911 |
Ber | 1785233613823 |
Jah | 3570467227647 |
Cham | 7140934455295 |
Zod | 14281868910591 |
Вес руны Eld равен 4, т.к. нужны три руны El и одно превращение.
Вес руны Tir равен 13, т.к. требуется 9 рун El для получения 3-х рун Eld и одно превращение этих рун в руну Tir. Иначе говоря, нужны 4 шага чтобы получить руну Eld, затем три этих руны чтобы получить руну Tir. 3*4 = 12 и один шаг на превращение - 12+1=13. Аналогичный подход используется для всех остальных рун. Разница лишь в том, что расчеты приобретают характер вычислений с большими числами. И тем они больше, чем выше уровень руны.
Весь процесс расчетов можно поделить на два этапа:
- Рассчитать какие руны подсветить;
- Рассчитать кол-во рун в счетчиках в каждом наборе.
Расчет видимых (подсвеченных) рун
Расчет начинаем с руны с максимально большим возможным весом. Удобно делать расчет с самой "дорогой" руны, т.к. это позволяет легко вычислить кол-во рун нужных для получения этой самой дорогой руны. К примеру, легко вычислить что для руны Fal нужны три руны Ko, 9 рун Lum и т.д.
Выполняем поиск руны, чей вес помещается в разницу времени мы вычислили ранее. Наша цель, вычислить сколько рун нужно подсветить т.к. мы знаем что разница во времени это на самом деле кол-во рун El плюс превращения. Подсветить мы можем только до трех рун в каждом наборе, соответственно вес самой дорогой руны равен максимум трем проходам. Иначе - это руна выше уровнем.
Предположим, что в разница во времени равна 20 секундам. Самая дорогая руна, чей вес вмещается в эту разницу, является руна Tir, с весом 13. 20 секунд минус 13 "веса" равен 7 секундам разницы. В эти 7 секунд можно уместить лишь одну руну Eld с весом 4. 7 секунд минус 4, равен 3, что равно весу трех рун El. Легко заметить, что сумма кол-ва рун в итоге равно разнице во времени полученной изначально.
Расчет рун в счетчиках
После того, как были подсвечены руны в каждом наборе, осталось высчитать кол-во рун в счетчиках в этих наборах. В случае если мы на руне Ort, а до этого мы посчитали 6 рун Tal, требуется добавить к текущему кол0ву рун Ort результат умножения кол-ва этих рун требуемых для получения текущего кол-ва рун Tal.
function initializeRunesCount() {
let start = new Date('2000-06-29T00:00:00.000Z');
let now = new Date();
let diff = Math.round((now.getTime() - start.getTime()) / 1000);
let remainder = diff;
let runes = document.querySelectorAll('.rune__item');
// Rune "cost";
let runeWeight = 0;
// Count of previous, high level, runes.
let prevCount = 0;
// How many runes we need to get next rune.
let runeMultiplier = 1;
// Move backwards.
for (let i = runes.length - 1; i >= 0; i--) {
runeWeight = parseInt(runes[i].dataset.weight);
runeMultiplier = parseInt(runes[i].dataset.required);
// Seek further for higher weighted rune.
if (runeWeight > diff) {
continue;
}
// Rune's weight is within the time diff.
// Visually mark the runes that are left over.
while (runeWeight <= remainder) {
if (remainder > 0) {
remainder -= runeWeight;
}
runeSetMark(runes[i]);
runeSetCountSet(runes[i], runeSetCountGet(runes[i]) + 1);
}
// Now calculate their total amount, assuming next level rune count.
runeSetCountSet(runes[i], runeSetCountGet(runes[i]) + prevCount * runeMultiplier);
prevCount = runeSetCountGet(runes[i]);
}
}
Реализация на javascript довольно прямолинейна и не требует каких-либо сторонних библиотек. В силу специфики языка и подхода к манипуляции DOM, это довольно простецкий подход, без попыток "сделать красиво". В будущих статьях, посмотрим как этот код можно привести в более читаемый и переносимый вид, используя более современные и удобные подходы.