Матчасть
May 2

Расчет шансов предметов в лутбоксе на основе цен

Сделал инструмент для геймдизайнеров, которым можно быстро считать шансы в лутбоксах.

Видео-инструкция:

Если посчитаете инструмент полезным, не стесняйтесь делиться ссылкой.

Если работали с похожими системами, буду рад услышать мнение со стороны.

Телеграмм-канал: https://t.me/SecretRoom_Gamedesign

Задача. Дано:

  • Лутбокс и его цена.
  • Лутбокс выдает один предмет.
  • Список возможных предметов и их цен.

Найти:

  • Шанс каждого предмета.

Условие:

  • Среднее (матожидание) выпадающих предметов из лутбокса = Стоимость лутбокса * (100% - House Edge)

«House Edge» — комиссия казино. Сколько процентов от оборота товаров оставляет за собой владелец, разыгрывая предметы.

Например, может быть равна 10%. В этом случае, если лутбокс будет стоить 100 долларов, то в среднем предметы будут падать из него на 90 долларов.

Другими словами, это сколько процентов комиссии зарабатывает казино.

Пример использования

Представьте, что у нас есть магазин кейсов с оружием из Counter Strike. Цена оружия динамическая и зависит от ситуации на рынке.

Вы, как обладатель площадки, владеете складом различного оружия на своих аккаунтах.

Вы хотите продавать кейсы с оружием. Из кейса можно получить только одно оружие на основе шансов. Цену кейсов и оружие в них вы формируете заранее.

Вам нужен инструмент, чтобы вычислять шансы.

Можно считать это руками довольно простой таблицей, перебирая шансы каждого предмета до тех пор, пока ваше матожидание не сойдется с ценой лутбокса.

Но что, если таких кейсов очень много, а пересчитывать их нужно часто? Нужна математическая модель для автоматического расчета.

Пример такого сайта: https://skin.club/en

Пример кейса с сайта:

Пример лутбокса за 3.59$. Цены и шансы обновляются почти каждый день

Давайте посчитаем матожидание из примера на скриншоте и выясним комиссию продавца.

Матожидание = Сумма произведений (Item price * Item chance)

Комиссия примерно равна 20%

Ссылка на таблицу.

Как вычислять шансы?

Первое рассуждение

Довольно быстро стало понятно, что решений у задачи может быть много.

Я взял пример и перераспределил шансы — получил то же самое матожидание.

При одних и тех же входных значениях может быть несколько решений. Значит нам нужен параметр, который будет дополнительно регулировать распределение шансов.

Функция распределения

Нам нужна функция, которая будет распределять шансы относительно цен. Я взял за основу степенную функцию (гиперболу). Про нее у меня есть отдельный пост. Этой функцией мы будем считать веса предметов. В свою очередь шансы будут считаться на основе весов.

Шанс предмета = Вес предмета / Сумма всех весов.

Степенная функция

Очевидно, что в списке предметов должны быть предметы разных цен. Обязательно должны быть предметы ниже и выше стоимости сундука. Только в таких случаях задача может быть решаема.

Сводим задачу к поиску коэффициентов формулы

Итак, у нас есть формула для вычисления веса предмета:

Задача сводится к тому, чтобы подобрать коэффициенты.

Какие-то коэффициенты стоит зафиксировать константами. Зафиксируем power = -1, потому что функция должна быть гиперболой и b = 1, потому что вес предмета должен быть больше нуля. (По сути b — минимальный вес).

В качестве x будет выступать цена предмета.

Мы будем искать k и a.

Допустим, что у нас есть предметы со стоимостями x1, x2 ... xn и весами w1, w2 ... wn.

Матожидание будет равно:

В свою очередь веса будут выражены нашей степенной функцией. Подставляем ее вместо w1 ... wn.

Раскроем скобки:

Знаменатель в левую часть:

Все, что с коэффициентом k в левую сторону, все остальное в правую:

Получаем k:

Приведу это к приличному виду через промежуточные переменные:

Таким образом:

А что насчет коэффициента «a», а?

Обращусь к своей статье о степенной функции. Там я показывал, что коэффициент «a», по своей сути, определяет вертикальную асимптоту гиперболы, то есть границу функции. Очевидно, что граница должна быть левее цены самого дешевого предмета.

То есть нужно, чтобы a по модулю была меньше цены самого дешевого предмета.

В какой-то момент, я понял, что будет лучше всего работать не с абстрактным коэффициентом, который нужно подгонять каждый раз под ситуацию, а с каким-то более осмысленным. Поэтому я ввел понятие Grouping.

Grouping — это расстояние от минимальной цены предметов до асимптоты функции.

В свою очередь коэффициент a выразил так:

По-русски я назвал бы этот новый коэффициент «кучностью». Потому что чем больше Grouping, тем более пологая будет кривая за пределами минимальной цены. То есть как бы веса у предметов будут кучковаться или группироваться и быть более похожими.

Низкий Grouping. Малая кучность. W1 далеко от W2
Высокий Grouping. Большая кучность. W1 близко к W2

Первая версия модели

С этими рассуждениями получилось создать первую версию модели.

В таблице можно указать:

  • Стоимость лутбокса.
  • House edge (комиссия).
  • Кучность призов (Grouping). В зависимости от призов нужно подбирать, можно экспериментировать с разной кучностью.
  • Строчки с ценами призов. Можно добавлять новые строчки или удалять лишние. Но обязательно продлевайте формулы из строчек на новые строчки.
После добавления строчки потяните ячейки с формулами вниз
Вбейте цену нового предмета

Автоматически рассчитываются шансы призов в строчках.

Могут выдаваться следующие ошибки, которые очевидно как исправлять:

Псевдокод модели:

// Constants
power = -1
b = 1

// Input
LootboxPrice
HouseEdge // От 0 до 1
Grouping // Строго > 0
List PriceList // Список цен

// Calculations
SumOfPrices = Sum of elements from PriceList
ExpectedPriceValue = LootboxPrice * (1 - HouseEdge)
MinPrice = Minimum from PriceList
MaxPrice = Minimum from PriceList
If MinPrice >= ExpectedPriceValue then Print(Error: Price of lootbox is too low)
If MaxPrice <= ExpectedPriceValue then Print(Error: Price of lootbox is too high)
a = Grouping - MinPrice
List XList
XList[i] = (PriceList[i] + a) ^ power
SumX = Sum of elements from XList
SumProdX = Sum of (XList[i] * PriceList[i])
Denominator = ExpectedPriceValue * SumX - SumProdX
If Denominator <= 0 then Print(Error: Value of grouping is too high))
k = b * (SumOfPrices - PriceList.Length * ExpectedPriceValue) / Denominator
List WeightList
WeightList[i] = k * (PriceList[i] + a) ^ power + b
SumOfWeights = Sum of elements from WeightList
List ChanceList
ChanceList[i] = WeightList[i] / SumOfWeights
ExpectedPriceValueForVerification = Sum of (PriceList[i] * ChanceList[i])

// Output
ChanceList
ExpectedPriceValueForVerification

Вторая версия

Полученная модель хорошо работает не для всех случаев.

Мне не понравилось как работает модель в случае, если у нас несколько дешевых предметов, которые несильно отличаются в цене, при этом у нас есть очень дорогие предметы.

Вот пример:

Шансы у почти одинаковых предметов (8 и 9 долларов) слишком разные. Я бы хотел, чтобы они были примерно одинаковые. Увеличение кучности в этом случае не сильно помогает.

Я подумал, что в такой ситуации есть смысл упаковать все дешевые предметы в одну оболочку, и уже внутри нее решать, какой предмет из нее выпадет.

Упаковываем первые предметы в Internal box

Модифицировал таблицу, изменив первый предмет на ссылку на матожидание от internal box:

С помощью такого внутреннего бокса можно модифицировать гиперболу, добавляя в нее начальную ступеньку, настраивая ее вручную.

Ссылка на таблицу

https://docs.google.com/spreadsheets/d/1kVMa3sadsIP7ehYju5Seq0WoTvBv9lY_DQlrLIOByos/edit?usp=sharing