Допустим, у меня есть эта формула, например:
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}
for (var i = 1; i < 100; i++) {
console.info(`Level ${i}: ${getExperience(i)}`);
}Чтобы получить опыт, необходимый для 50-го уровня, вам нужно сделать: getExperience(50).
Но как бы вы изменили это и получили УРОВЕНЬ, необходимый для получения опыта? Таким образом, getLevel(20010272) выведет 50.
Кажется, опечатка. getExperience(50) - это 20012272, а не 20010272. Поэтому я думаю, что getLevel(20010272) должен возвращать 49, а не 50.
Это бесконечный ряд, но вы можете упростить и получить обратную функцию.
См. также Как я могу рассчитать текущий уровень из общего количества XP, когда каждый уровень требует пропорционально больше XP? на Разработка игр.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Грубым (но неэлегантным) решением будет просто вызывать getExperience для получения уровней, пока вы не достигнете уровня, требующего больше опыта, чем пройденный exp:
function getLevel(exp) {
if (exp === 0) return 0;
let level = 0;
let calcExp = 0;
while (exp > calcExp) {
calcExp = getExperience(level);
if (calcExp > exp) break;
level++;
}
return level - 1;
}
console.info(getLevel(20012272)); // experience required for 50 on the dot
console.info(getLevel(20012270));
console.info(getLevel(20012274));
console.info(getLevel(0));
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}Просто интересно, можем ли мы минимизировать итерацию, установив level на относительно близкое значение
Простое решение, которое выполняет свою работу. Почему я подумал об этом. Арх. Спасибо.
Конечно, вы можете предварительно вычислить все значения и получить их в таблице поиска. Я сомневаюсь, что требования или уровни XP будут постоянно меняться, поэтому их динамическое вычисление каждый раз кажется излишним.
Если бы вы также знали минимальный/максимальный уровни, вы могли бы выполнить псевдобинарный поиск (то есть, если бы вы знали, что существует ровно 100 уровней, вы видите, меньше или больше ваш опыт, чем уровень 50, а затем 25/75 в зависимости от этого). , затем 38/12/62/88 и т. д.). Или где-то между этими шагами (делите пополам, пока у вас не будет 3 варианта, затем проверьте все).
Вы можете использовать бинарный поиск для более быстрого поиска значения уровня - максимум за 7 шагов.
(хотя я сомневаюсь, что прирост значителен для списка длиной 100)
Вы можете использовать алгоритм бинарного поиска, чтобы избежать перебора всех возможностей.
Здесь — это пример, который я адаптировал к вашему случаю.
Вам сначала нужно создать массив для сопоставления всех ваших level => experience, это действие нужно сделать только ОДИН РАЗ, потом больше никогда не придется это делать.
Как вы можете видеть в моем примере, даже с 1000 уровней вам никогда не придется повторять более 9 раз любой уровень, который вы пытаетесь найти.
// You first have to create an array with all your levels.
// This has to be done only ONCE because it's an expensive one!
const list = [];
for (let i = 1; i <= 1000; i++) {
list[i] = getExperience(i);
}
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}
function getLevel(value) {
// initial values for start, middle and end
let start = 0
let stop = list.length - 1
let middle = Math.floor((start + stop) / 2)
let iterations = 0;
// While the middle is not what we're looking for and the list does not have a single item.
while (list[middle] !== value && start < stop) {
iterations++;
if (value < list[middle]) {
stop = middle - 1
} else {
start = middle + 1
}
// Recalculate middle on every iteration.
middle = Math.floor((start + stop) / 2)
}
console.info(`${value} is Level ${middle} (Result found after ${iterations} iterations)`);
return middle;
}
// Then you can search your level according to the experience
getLevel(0);
getLevel(72);
getLevel(20010272);
getLevel(getExperience(50));
getLevel(33578608987644589722);Вы можете улучшить создание списка, зациклив Math.floor(x + (200 * (2 ** (x / 3)))) от 1 до 1000 и сохранив значения a, а затем снова зациклив, чтобы получить Math.floor(a / 4).
@JollyJoker Хм, возможно, вы можете отредактировать мой ответ с вашим предложением?
@EricDuminil Безусловно, это была проблема index с инициализацией списка, я исправил ее, спасибо.
Вам не нужно составить список всех впечатлений.
@SalmanA Да, это зависит от того, как вы используете это в своем приложении. По крайней мере, у Green Mtn есть несколько решений, и он может выбрать лучшее для своего случая.
Вы можете использовать 4.328085 * Math.log(0.00519842 * xp + 1.259921045) как очень хорошее приближение к соответствующему уровню.
Если вам нужно точное значение, вы можете перебирать все уровни, пока не найдете нужный диапазон, как в этом отвечать.
Я не думаю, что можно найти точную выражение в закрытой форме для обратной этой функции. Однако это должно быть возможно, если вы немного модифицируете getExperience(level).
x растет много медленнее, чем 2 ** (x / 3).Math.floor не имеет большого влияния на большие числа.Итак, давайте удалим их! Вот немного измененная функция:
function getExperienceEstimate(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += 200 * (2 ** (x / 3));
}
return a / 4;
}
Преимущество этого метода в том, что теперь это геометрический ряд, поэтому можно вычислить сумму напрямую без какого-либо цикла:
function getExperienceEstimate(level) {
let a = 50;
let r = 2 ** (1 / 3);
return a * (r**level - r) / (r - 1);
};
getExperienceEstimate(50) возвращает 20011971.993575357, что всего на 0,0015% меньше, чем getExperience(50).
Согласно вольфрам Альфа, вот функция, обратная getExperienceEstimate:
function getLevelEstimate(xp){
let a = 50;
let r = 2 ** (1 / 3);
return Math.log(xp * (r - 1) / a + r) / Math.log(r);
};
С некоторой незначительной потерей точности вы можете упростить его еще больше:
function getLevelEstimate(xp){
return 4.328085 * Math.log(0.00519842 * xp + 1.259921045)
};
Это всего лишь оценка, но она работает довольно хорошо и не требует никакого цикла!
Для XP 20012272 приближенная обратная функция возвращает 50.00006263463371, что должно быть хорошей отправной точкой, если вы хотите найти точный результат.
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}
function getLevelEstimate(xp){
return 4.328085 * Math.log(0.00519842 * xp + 1.259921045)
};
for (var i = 1; i < 100; i++) {
console.info(`Level ${i} (XP = ${getExperience(i)}). Estimated level : ${getLevelEstimate(getExperience(i))}`);
}Это решение, которое я бы выбрал. В своей игре я убедился, что могу напрямую инвертировать формулу уровня, используя один и тот же показатель степени как для log, так и для pow. Я бы порекомендовал OP сделать это, если это вообще возможно, чтобы свести к минимуму беспокойство о приближении и вместо этого сделать его точным соответствием.
Вы можете начать с этого приближения, а затем перебрать несколько ближайших чисел, чтобы найти правильное.
Судя по вашим результатам, простое использование Math.round(getLevelEstimate(experience)) сработает.
Я также пытался найти обратную формулу опыта, но потерпел неудачу. +1.
@Barmar: Интересная информация заключается в том, для какого опыта getLevelEstimate переключается на следующий уровень. Если вы позвоните в Math.round, вы выбросите эту информацию. При идеальном getLevelEstimateMath.floor должен возвращать правильный уровень.
Я возвращаюсь и вижу это. Вы слишком добры. Спасибо.
@GreenMtn: С удовольствием, мне всегда нравятся короткие математические задачи. И я получил труднодоступный значок с этим ответом, так что тоже спасибо ;) Наконец: обратите внимание, что вам не нужно принимать мой ответ, поскольку он дает только приблизительный результат.
Вы можете тестировать getExperience в цикле, пока не получите тот же результат (уровень).