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


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. Затем вы делаете два броска каждый раунд, один для определения критического или нет, а затем другой для фактической величины.


1
Это общий подход, который я бы выбрал: вы используете равномерное распределение ГСЧ, но трансформируете его. Вы также можете использовать выходные данные ГСЧ в качестве входных данных для своего собственного пользовательского дистрибутива, который перенастраивается на основе недавней истории, то есть, чтобы вызвать отклонения в выходных данных, чтобы он выглядел «более случайным» в терминах человеческого восприятия.
Майкл

3
Я на самом деле знаю о ММО, которая делает что-то подобное, но шанс крита фактически увеличивается с каждым разом, когда вы получаете один, пока вы его не получите, тогда он сбрасывается до очень низкого значения. Это приводит к редким сериям критов, которые очень удовлетворяют игрока.
Coderanger

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

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


2

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

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


0

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

01рб0 .

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

рехр(-б)

б1б0

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.