Как избежать «слишком» счастливых / неудачных полос в генерации случайных чисел?

30

В настоящее время я имею дело с многопользовательской боевой системой, в которой урон, наносимый игроками, всегда умножается на случайный коэффициент от 0,8 до 1,2.

Теоретически, действительно случайный ГСЧ может в конечном итоге давать одно и то же количество много раз (см. Дилемму Тетриса ). Это может привести к матчу, в котором игрок всегда наносит очень большой урон, а другой всегда наносит очень низкий урон.

Что я могу сделать, чтобы этого не случилось? Некоторые RNG лучше других избегают повторения?

Пользователь не найден
источник
Я не понимаю, как это работает. Конечно, вы получите последовательность x1, x2, x3, x4 .. где все x большие. Разве это не случайно?
Коммунистическая утка

Ответы:

26

Вы можете решить ее так же, как это делает Тетрис, составив заранее заданный список результатов урона и перетасовки.

Допустим, вы знаете, что игрок собирается нанести 0,8x-1,2x урона с линейным распределением. Возьмите список [0,8, 0,9, 1,0, 1,1, 1,2]. Перемешайте это случайно , так что вы получите, например, [1.2, 1.0, 0.8, 0.9, 1.1].

В первый раз игрок наносит урон, он наносит 1,2х. Тогда 1х. Затем и т. Д. До 1,1х. Только когда массив пуст, вы должны генерировать и перемешивать новый массив.

На практике вы, вероятно, захотите сделать это для 4+ массивов одновременно (например, начните с [0.8,0.8,0.8,0.8,0.9,0.9,0.9,0.9, ...]). В противном случае период последовательности достаточно мал, чтобы игроки могли выяснить, является ли их следующий удар «хорошим» или нет. (Хотя это также может добавить больше стратегии к бою, как, например, в таблице Hoimi Dragon Quest IX , в которой люди придумали, как исследовать, просматривая исцеляющие цифры и подправляя, пока вам не гарантировано редкое падение.)


источник
3
Чтобы сделать его немного более случайным, вы всегда можете иметь половину списка в виде случайных чисел, а другую половину рассчитать как (2-x), чтобы получить среднее значение правильно.
Адам
2
@Adam: этот метод действительно работает только для этого конкретного примера; если вы раздаваете фигуры тетриса, а не множители урона, что такое блок 2 - S?
6
Обычный термин для такого рода системы - «случайный без замены». Это действительно аналогично использованию колоды карт вместо игры в кости.
Килотан
Более того, вы можете сделать половину чисел действительно случайными, и только половина из них подпадает под это правило.
о0 '.
1
Это может привести к тому, что локальный дистрибутив не будет похож на глобальный, а это именно то, чего не хочет вопрос. Такие термины, как «действительно случайный», являются неопределенной псевдоматематикой; чем больше вы определяете, какие статистические свойства вы хотите, тем яснее будут ваши намерения и дизайн игры.
5

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

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

Возьмите следующие образцы PRNG (где мы выполняем, если выборка> = 0.5):

Values: 0.1, 0.5, 0.9, 0.4, 0.8
Events: 0  , 1  , 1  , 0  , 1
Percentage: 60%

Обратите внимание, что каждое значение вносит вклад в 1/5 конечного результата. Давайте посмотрим на это по-другому:

Values: 0.1, 0.5
Events: 0  , 1

Обратите внимание, что 0вклад составляет 50% стоимости, а 1вклад - 50% стоимости. Взятые чуть дальше:

Values: [0.1, 0.5], 0.9
Events: [0  , 1  ], 1

Теперь первые значения дают 66% стоимости, а последние 33%. Мы можем в основном отнести это к следующему процессу:

result = // 0 or 1 depending on the result of the event that was just generated
new_samples = samples + 1

average = (average * samples / new_samples) + (result * 1 / new_samples)
// Essentially:
average = (average * samples / new_samples) + (result / new_samples)

// You might want to limit this to, say, 100.
// Leaving it to carry on increasing can lead to unfairness
// if the game draws on forever.
samples = new_samples

Теперь нам нужно сместить результат значения, отобранного из PRNG, потому что мы рассчитываем на процентный шанс, здесь все намного проще (по сравнению, скажем, со случайными величинами ущерба в RTS). Это будет трудно объяснить, потому что это «только что пришло мне в голову». Если среднее значение ниже, это означает, что нам нужно увеличить вероятность того, что событие произойдет, и наоборот. Итак, несколько примеров

average = 0.1
desired = 0.5
corrected_chance = 83%

average = 0.2
desired = 0.5
corrected_chance = 71%

average = 0.5
desired = 0.5
corrected_change = 50%

Теперь «пришло мне в голову», что в первом примере 83% были просто «0,5 из 0,6» (другими словами «0,5 из 0,5 плюс 0,1»). В терминах случайного события это означает либо:

procced = (sample * 0.6) > 0.1
// or
procced = (sample * 0.6) <= 0.5

Таким образом, для генерации события вы должны использовать следующий код:

total = average + desired
sample = rng_sample() * total // where the RNG provides a value between 0 and 1
procced = sample <= desired

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

Отказ от ответственности: Это все домашние статистические данные, у меня нет образования в этой области. Мои юнит-тесты все же проходят.

Джонатан Дикинсон
источник
Выглядит как ошибка в вашем первом примере, потому что значение 0,1 и 0,9 приводит к событию 0. Но вы в основном описываете, как хранить кумулятивную скользящую среднюю ( en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average ) и корректируете на основе этого. Один риск состоит в том, что каждый результат будет значительно обратно коррелирован с предыдущим результатом, хотя эта корреляция будет со временем уменьшаться.
Kylotan
1
Я бы соблазнился изменить это, чтобы использовать вместо этого систему «неплотного интегратора»: начните со среднего, инициализированного до 0,5, и вместо подсчета выборок выберите произвольное постоянное значение (например, 10, 20, 50 или 100), которое не увеличивается. , Тогда, по крайней мере, корреляция между двумя последующими значениями постоянна на протяжении всего использования генератора. Вы также можете настроить постоянное значение - большие значения означают более медленную коррекцию и более очевидную случайность.
Килотан
@Kylotan спасибо, спасибо за предоставленное имя. Я не уверен, что именно вы имеете в виду под своим вторым комментарием - может быть, дать новый ответ?
Джонатан Дикинсон
Это довольно умно и не имеет ограничений для массивов. Я понимаю предложение Килотана о том, чтобы samplesс самого начала инициализировать его максимальное значение (в данном случае 100). Таким образом, для стабилизации ГСЧ не требуется 99 итераций. В любом случае, я могу видеть один недостаток этого метода в том, что он не гарантирует справедливость, он просто обеспечивает постоянное среднее значение.
Пользователь не найден
@jSepia - действительно, вы все равно будете испытывать честность / несправедливость, но за ними (обычно) последует сбалансированный забег. Например, в моем модульном тесте я «запустил» 100 непроцессов, и когда я делал реальные сэмплы, меня встретили ~ 60 процов. В ситуациях без влияния (если вы посмотрите на код) 50% -ый процесс обычно видит, в худшем случае, 2/3 в любом направлении. Но у одного игрока может быть бег, позволяющий ему победить другого игрока. Если хочешь сместить это сильнее, то к честности total = (average / 2) + desired.
Джонатан Дикинсон
3

То, что вы просите, на самом деле является противоположностью большинства PRNG - нелинейное распределение. Просто добавьте в свои правила какую-то логику убывающей отдачи. Предполагая, что все, что выше 1,0х, является каким-то «критическим ударом», просто скажите, что каждый раунд ваши шансы получить крит увеличиваются на X, пока вы не получите один на в какой момент они сбрасываются до Y. Затем вы делаете два броска каждый раунд, один для определения критического или нет, а затем другой для фактической величины.

coderanger
источник
1
Это общий подход, который я бы выбрал: вы используете равномерное распределение ГСЧ, но трансформируете его. Вы также можете использовать выходные данные ГСЧ в качестве входных данных для своего собственного пользовательского дистрибутива, который перенастраивается на основе недавней истории, то есть, чтобы вызвать отклонения в выходных данных, чтобы он выглядел «более случайным» в терминах человеческого восприятия.
Майкл
3
Я на самом деле знаю о ММО, которая делает что-то подобное, но шанс крита фактически увеличивается с каждым разом, когда вы получаете один, пока вы его не получите, тогда он сбрасывается до очень низкого значения. Это приводит к редким сериям критов, которые очень удовлетворяют игрока.
Coderanger
Похоже, хороший alg, длинные сухие заклинания всегда были разочаровывающими, но это не приводит к сумасшедшим сериям критов.
Майкл
2
Исправление этого не требует нелинейного распределения, оно просто требует, чтобы короткие последовательные по времени подмножества распределения имели те же свойства, что и само распределение.
Вот
2

Сид Майер выступил с великолепной речью на GDC 2010 именно об этой теме и играх Civilization. Я постараюсь найти и вставить ссылку позже. По сути, воспринимаемая случайность - это не то же самое, что истинная случайность. Чтобы все было честно, нужно проанализировать предыдущие результаты и обратить внимание на психологию игроков.

Избегайте полос неудачи любой ценой (если предыдущие два хода были неудачными, следующий должен быть гарантированно счастливым). Игрок должен быть всегда счастливее, чем искусственный противник.

Кромстер говорит, что поддерживает Монику
источник
0

Используйте сдвиг

01рб0 .

Общее распределение будет смещено по следующей формуле:

рехр(-б)

б1б0

Возьмите это число и масштабируйте его до желаемого диапазона.

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

Beefster
источник