Потребность в предсказуемом случайном генераторе
Я разработчик веб-игр, и у меня возникла проблема со случайными числами. Скажем, у игрока есть 20% шанс получить критический удар мечом. Это означает, что 1 из 5 хитов должен быть критическим. Проблема в том, что у меня очень плохие реальные результаты жизни; иногда игроки получают 3 крита в 5 хитов, иногда ни один из 15 хитов. Сражения довольно короткие (3-10 ударов), поэтому важно получить хорошее случайное распределение.
В настоящее время я использую PHP mt_rand()
, но мы просто перемещаем наш код на С++, поэтому я хочу решить эту проблему в нашей новой игре.
Я не знаю, является ли решение некоторой однородной случайной генерацией или, возможно, помнить предыдущие случайные состояния, чтобы принудительно распределить распределение.
Ответы
Ответ 1
Я согласен с более ранними ответами, что реальная случайность в небольших тиражах некоторых игр нежелательна - для некоторых случаев использования она кажется слишком несправедливой.
Я написал простой пакет Shuffle Bag, как реализация в Ruby, и сделал некоторые тесты. Реализация сделала это:
- Если это все еще кажется справедливым или мы не достигли порога минимальных бросков, он возвращает справедливый хит, основанный на нормальной вероятности.
- Если наблюдаемая вероятность прошлых рулонов делает ее нечестной, она возвращает "честный" удар.
Это считается несправедливым на основе граничных вероятностей. Например, для вероятности 20% вы можете установить 10% в качестве нижней границы и 40% в качестве верхней границы.
Используя эти границы, я обнаружил, что с прогонами из 10 ударов 14,2% времени, когда истинная псевдослучайная реализация дала результаты, которые были вне этих границ. Примерно в 11% случаев, 0 критических ударов были забиты в 10 попытках. 3,3% времени, 5 или более критических ударов были высажены из 10. Естественно, используя этот алгоритм (с минимальным количеством рулонов 5), гораздо меньшее количество (0,03%) пробелов "Fairish" было за пределами границ, Даже если приведенная ниже реализация не подходит (возможно, более умные вещи можно сделать, конечно), стоит отметить, что часто пользователи часто чувствуют, что это несправедливо с реальным псевдослучайным решением.
Вот мясо моего FairishBag
, написанного на Ruby. Вся реализация и быстрое моделирование методом Монте-Карло доступны здесь (gist).
def fire!
hit = if @rolls >= @min_rolls && observed_probability > @unfair_high
false
elsif @rolls >= @min_rolls && observed_probability < @unfair_low
true
else
rand <= @probability
end
@hits += 1 if hit
@rolls += 1
return hit
end
def observed_probability
@hits.to_f / @rolls
end
Обновление: Использование этого метода увеличивает общую вероятность получения критического попадания, примерно до 22%, используя границы выше. Вы можете компенсировать это, установив свою "реальную" вероятность немного ниже. Вероятность 17,5% с помощью волшебной модификации дает наблюдаемую долгосрочную вероятность около 20% и сохраняет справедливость в краткосрочной перспективе.
Ответ 2
Это означает, что 1 из 5 хитов должен быть критическим. Проблема в том, что у меня очень плохие результаты в реальной жизни - иногда игроки получают 3 крита в 5 хитов, иногда ни один из 15 хитов.
Что вам нужно, это сумка в случайном порядке. Это решает проблему случайного случайного, слишком случайного для игр.
Алгоритм примерно такой: вы ставите 1 критический и 4 некритических попадания в сумку. Затем вы производите свой заказ в сумке и выбираете их по одному. Когда сумка пуста, вы снова заполняете ее теми же значениями и производите ее. Таким образом, вы получите в среднем 1 критический удар за 5 ударов и не более 2 критических и 8 некритических ударов подряд. Увеличьте количество предметов в сумке для большей случайности.
Вот пример реализация (на Java) и ее которые я написал некоторое время назад.
Ответ 3
У вас есть непонимание того, что означает случайное.
Какие из них более случайны?
В то время как второй график выглядит более равномерно распределенным, более случайным является фактически первый сюжет. Человеческий разум часто видит закономерности в случайности, поэтому мы видим скопления в первом сюжете как шаблоны, но они не являются - они просто являются частью случайно выбранного образца.
Ответ 4
Учитывая поведение, о котором вы просите, я думаю, что вы производите неправильную переменную.
Вместо того, чтобы рандомизировать, будет ли этот удар критически важным, попробуйте рандомизировать количество поворотов до следующего критического попадания. Например, просто выберите число между 2 и 9 каждый раз, когда игрок получает критический момент, а затем дайте им следующий критический после того, как прошло много раундов. Вы также можете использовать методы кубиков, чтобы приблизиться к нормальному распределению - например, вы получите следующий критический момент при поворотах 2D4.
Я считаю, что эта техника используется в RPG, у которых также есть случайные встречи в мире, - вы производите шаговый счетчик, и после этого много шагов вы снова получаете удар. Он чувствует себя намного более справедливым, потому что вы почти никогда не попадаете в два столкновения подряд - если это происходит даже один раз, игроки становятся раздражительными.
Ответ 5
Сначала определите "правильное" распределение. Случайные числа, ну, случайные - результаты, которые вы видите, полностью согласуются с (псевдо) случайностью.
Расширяясь на этом, я предполагаю, что вам нужно какое-то чувство "справедливости", поэтому пользователь не может пройти 100 оборотов без успеха. Если это так, я буду отслеживать количество сбоев с момента последнего успеха и взвешивать сгенерированный результат. Предположим, вы хотите, чтобы 1 из 5 рулонов "преуспел". Таким образом, вы произвольно генерируете число от 1 до 5, и если оно 5, отлично.
Если нет, запишите сбой и в следующий раз сгенерируйте число от 1 до 5, но добавьте, скажем, пол (numFailures/2). Так что на этот раз, опять же, у них есть шанс 1 на 5. Если они терпят неудачу, в следующий раз выигрышный интервал составляет 4 и 5; шансы на успех в 2-5. С этими вариантами, после 8 неудач, они наверняка преуспеют.
Ответ 6
Как насчет замены mt_rand() на что-то вроде этого?
(RFC 1149.5 указывает 4 как стандартное случайное число, прошедшее проверку IEEE.)
От XKCD.
Ответ 7
Надеюсь, эта статья поможет вам:
http://web.archive.org/web/20090103063439/http://www.gamedev.net:80/reference/design/features/randomness/
Этот метод генерации "случайных чисел" распространен в играх rpg/mmorpg.
Проблема, которую он решает, - это (выдержка):
Лезвие паука у вас в горле. Он бьет, и вы пропустите. Он снова бьет, и вы снова пропустите. И снова и снова, пока вам не удастся ударить. Ты мертв, и над твоим трупом раздался двухтонный арахнид. Невозможно? Нет. Невероятно? Да. Но, учитывая достаточно игроков и учитывая достаточно времени, невероятное становится почти уверенным. Дело не в том, что лезвие паука было трудным, это была просто невезение. Как расстраивает. Этого достаточно, чтобы игрок хотел выйти.
Ответ 8
То, что вы хотите, это не случайные числа, а числа, которые кажутся случайными для человека. Другие уже предложили отдельные алгоритмы, которые могут вам помочь, например Shuffle Bad.
Для подробного и подробного анализа этого домена см. AI Game Programming Wisdom 2. Вся книга стоит прочитать для любого разработчика игр, идея "казалось бы случайных чисел" обрабатывается в главе:
Отфильтрованная случайность для решений ИИ и игровой логики:
Аннотация: Обычная мудрость предполагает, что чем лучше генератор случайных чисел, тем более непредсказуемой будет ваша игра. Однако, согласно исследованиям психологии, истинная случайность в краткосрочной перспективе часто выглядит явно неслучайно для людей. В этой статье показано, как сделать случайные AI-решения и логику игры более случайными для игроков, сохраняя при этом сильную статистическую случайность.
Вы также можете найти другую интересную главу:
Статистика случайных чисел
Аннотация: случайные числа наиболее часто используются искусственным интеллектом и играми в целом. Чтобы игнорировать их потенциал, нужно сделать игру предсказуемой и скучной. Использование их неправильно может быть столь же плохим, как игнорировать их прямо. Понимание того, как генерируются случайные числа, их ограничения и их возможности, может устранить многие трудности с их использованием в вашей игре. В этой статье дается представление о случайных числах, их генерации и методах отделить хорошие от плохих.
Ответ 9
Несомненно, у любой генерации случайных чисел есть вероятность создания таких прогонов? Вы не получите достаточно большой набор образцов в 3-10 рулонах, чтобы увидеть соответствующие проценты.
Возможно, что вы хотите, это порог милосердия... помните последние 10 рулонов, и если у них не было критического попадания, дайте им халяву. Сгладьте стропы и стрелы случайности.
Ответ 10
Лучшим решением может быть игровое тестирование с несколькими различными не случайными схемами и выбрать тот, который сделает игроков счастливыми.
Вы также можете попробовать политику возврата для одного и того же номера в данной встрече, например, если игрок перебрасывает 1
в первый раз, примите его. Чтобы получить еще один 1
, им нужно катить 2 1
подряд. Чтобы получить третий 1
, им нужно 3 подряд, до бесконечности.
Ответ 11
К сожалению, то, о чем вы просите, является генератором неслучайных чисел - потому что вы хотите, чтобы предыдущие результаты учитывались при определении следующего числа. Это не то, как работают генераторы случайных чисел.
Если вы хотите, чтобы 1 из каждых 5 хитов был критическим, просто выберите число от 1 до 5 и скажите, что этот удар будет критическим.
Ответ 12
mt_rand() основан на Mersenne Twister, что означает, что он дает одно из лучших случайных распределений, которое вы можете получить.
Очевидно, что вы хотите не хаотичность вообще, поэтому вы должны начать с того, чтобы точно указать, что вы хотите. Вероятно, вы поймете, что у вас есть противоречивые ожидания - результаты должны быть действительно случайными и не предсказуемыми, но в то же время они не должны проявлять локальные отклонения от указанной вероятности, но тогда это становится предсказуемым. Если вы установите максимум 10 некритов подряд, вы только что сказали игрокам: "Если у вас было 9 некритов подряд, следующая будет критической со 100% уверенностью" - вы могли бы как хорошо не беспокоиться о случайности вообще.
Ответ 13
За такое небольшое количество тестов вы должны ожидать такие результаты:
Истинная случайность только предсказуема над огромным размерным размером, так что в первый раз вполне возможно перевернуть монету и получить головы 3 раза подряд, однако в течение нескольких миллионов переворотов вы получите примерно 50-50.
Ответ 14
Я вижу много ответов, предлагающих отслеживать ранее сгенерированные числа или перетасовывать все возможные значения.
Лично я не согласен, что 3 крита подряд плохо. Я также не согласен с тем, что 15 некритов подряд плохие.
Я бы решил проблему, изменив шанс крита на себя, после каждого числа.
Пример (чтобы продемонстрировать идею):
int base_chance = 20;
int current_chance = base_chance;
int hit = generate_random_number(0, 100) + 1; // anything from 1 to 100
if(hit < current_chance)//Or whatever method you use to check
{
//crit!
if(current_chance > base_chance)
current_chance = base_chance; // reset the chance.
else
current_chance *= 0.8; // decrease the crit chance for the NEXT hit.
}
else
{
//no crit.
if(current_chance < base_chance)
current_chance = base_chance; // reset the chance.
else
current_chance *= 1.1; // increase the crit chance for the NEXT hit.
//raise the current_chance
}
Чем дольше вы не получите крита, тем выше вероятность того, что у вас будет одно действие для крита. Включенный reset полностью не обязателен, и ему нужно было проверить, нужно ли это или нет. Может быть или не желательно давать более высокую вероятность крита более чем за одно действие в строке после длинной цепочки действий без критики.
Просто бросаю мои 2 цента...
Ответ 15
Самые верные ответы - отличные объяснения, поэтому я просто сосредоточусь на алгоритме, который даст вам возможность контролировать вероятность "плохих полос", не становясь детерминированным. Вот что я думаю, что вы должны сделать:
Вместо указания p параметр распределения Бернулли, который является вашей вероятностью критического попадания, задает a и b параметры бета-распределения, "сопряженные до" распределения Бернулли. Вам нужно отслеживать A и B, количество критических и некритических ударов до сих пор.
Теперь, чтобы указать a и b, убедитесь, что a/(a + b) = p, вероятность критического попадания. Оптимальным является то, что (a + b) количественно определяет, насколько близко вы хотите, чтобы A/(A + B) был равным p в целом.
Вы делаете свою выборку следующим образом:
let p(x)
- функция плотности вероятности бета-распределения. Он доступен во многих местах, но вы можете найти его в GSL как gsl_ran_beta_pdf.
S = A+B+1
p_1 = p((A+1)/S)
p_2 = p(A/S)
Выберите критический удар по выборке из распределения bernoulli с вероятностью p_1/(p_1 + p_2)
Если вы обнаружите, что в случайных числах слишком много "плохих полос", увеличьте значения a и b, но в пределе, когда a и b перейдите в бесконечность, вы будете иметь описанный выше метод суммирования в случайном порядке.
Если вы реализуете это, пожалуйста, дайте мне знать, как это происходит!
Ответ 16
Если вам нужен дистрибутив, который не рекомендует повторять значения, вы можете использовать простой алгоритм отмены повтора.
например.
int GetRand(int nSize)
{
return 1 + (::rand() % nSize);
}
int GetDice()
{
static int nPrevious=-1;
while (1) {
int nValue = GetRand(6);
// only allow repeat 5% of the time
if (nValue==nPrevious && GetRand(100)<95)
continue;
nPrevious = nValue;
return nValue;
}
}
Этот код отклоняет значения повторения 95% времени, делая повторы маловероятными, но не невозможными.
Статистически это немного уродливо, но это, вероятно, даст желаемые результаты. Конечно, это не помешает распространению вроде "5 4 5 4 5". Вы могли бы стать фаворитом и отказаться от второго последнего (скажем) 60% времени и третьего последнего (скажем) 30%.
Я не рекомендую это как хороший игровой дизайн. Просто предлагая, как добиться того, чего вы хотите.
Ответ 17
Не совсем понятно, что вы хотите. Можно создать такую функцию, чтобы первые 5 раз вы ее вызывали, она возвращает числа 1-5 в случайном порядке.
Но это на самом деле не случайное. Игрок будет знать, что он получит ровно 5 в следующих 5 атаках. Это может быть то, что вы хотите, хотя, и в этом случае вам просто нужно закодировать его самостоятельно. (создайте массив, содержащий числа, а затем перетасуйте их)
В качестве альтернативы вы можете продолжать использовать свой текущий подход и считаете, что ваши текущие результаты вызваны неправильным случайным генератором. Обратите внимание, что с вашими текущими номерами ничего не может быть. Случайные значения случайны. иногда вы получаете 2, 3 или 8 одного и того же значения в строке. Потому что они случайные. Хороший случайный генератор просто гарантирует, что в среднем все числа будут возвращаться одинаково часто.
Конечно, если вы использовали плохой случайный генератор, который мог бы исказить ваши результаты, и если это так, просто переключение на лучший случайный генератор должно устранить проблему. (Проверьте библиотеку Boost.Random для лучших генераторов)
В качестве альтернативы вы можете вспомнить последние N значений, возвращаемых вашей случайной функцией, и взвешивать результат с помощью этих. (простой пример: "для каждого появления нового результата существует 50% -ный шанс, мы должны отбросить значение и получить новый"
Если бы я должен был догадаться, я бы сказал, что придерживаться "фактической" случайности - ваш лучший выбор. Убедитесь, что вы используете хороший генератор случайных чисел, а затем продолжайте идти так, как вы делаете это сейчас.
Ответ 18
Вы можете создать список, содержащий числа от 1 до 5, и отсортировать их по случайности. Затем просто просмотрите список, который вы создали. У вас есть гарантия работать в каждом номере хотя бы один раз... Когда вы закончите с первыми 5, просто создайте еще 5 номеров...
Ответ 19
Ну, если вы немного по математике, вы можете попробовать Экспоненциальное распределение
Например, если lambda = 0.5, ожидаемое значение равно 2 (go прочитайте эту статью!), значит, вы, скорее всего, будете хитом/критом/независимо от каждого второго хода (например, 50%, да?). Но с таким распределением вероятностей вы будете определенно промахиваться (или делать что-то противоположное) на 0-м повороте (тот, в котором событие уже произошло, и turn_counter был сброшен), имеют 40% шанс попасть в следующий ход, около 65% шанс сделать это 2-й (следующий после следующего) поворот, около 80% на 3-й и т.д.
Вся цель этого распределения - если у кого есть шанс на 50% шанс, и он пропускает 3 раза подряд, он wire shurely (ну, более 80% шанс, и он увеличивается каждый следующий ход). Это приводит к более "справедливым" результатам, сохраняя при этом 50% -ную вероятность без изменений.
Принимая 20% -ный шанс крита, у вас есть
- 17% до крита 1-й ход
- 32% для крита 2-го поворота, если крит не возникает во всех предыдущих.
- 45% до крита 3-го поворота, если никакой крит не происходит во всех предыдущих.
- 54% до крита 4-го поворота, если никакой крит не происходит во всех предыдущих.
- ...
- 80% до крита 8-го поворота, если крит не возникает во всех предыдущих.
Его все еще около 0,2% (против этих 5%) шансов 3 крита + 2 некрита в 5 последующих поворотах.
И есть 14% вероятность 4 последующих некритов, 5% из 5, 1,5% для 6, 0,3% за 7,07% за 8 последующих некритов. Я уверен, что это "более справедливо", чем 41%, 32%, 26%, 21% и 16%.
Надеюсь, вам все равно не надоедает до смерти.
Ответ 20
Я рекомендую прогрессивную процентную систему, такую как Blizzard:
http://www.shacknews.com/onearticle.x/57886
Как правило, вы запускаете RNG, а затем сравниваете его со значением, чтобы определить, успешно ли это или нет. Это может выглядеть так:
if ( randNumber <= .2 ) {
//Critical
} else {
//Normal
}
Все, что вам нужно сделать, это добавить прогрессивное увеличение базового шанса...
if (randNumber <= .2 + progressiveChance ) {
progressiveChance = 0;
//Critical
} else {
progressiveChance += CHANCE_MODIFIER;
//Normal hit
}
Если вам нужно, чтобы это было более причудливо, довольно легко добавить больше. Вы можете ограничить количество, которое progressiveChance может получить, чтобы избежать 100% -ной критической вероятности или reset на определенных событиях. Вы также можете увеличить progressiveChance в меньших количествах с каждым повышением с помощью чего-то вроде progressiveChance + = (1 - progressiveChance) * SCALE, где SCALE < 1.
Ответ 21
Как насчет возможности критической зависимости от последних N атак. Одна простая схема - это какая-то цепочка марков: http://en.wikipedia.org/wiki/Markov_chain, но код очень прост.
IF turns_since_last_critical < M THEN
critial = false
turns_since_last_critical++;
ELSE
critial = IsCritical(chance);
IF Critial THEN
turns_since_last_critica = 0;
ELSE
turns_since_last_critica++;
END IF;
END IF;
Конечно, вы должны сделать свою математику, потому что вероятность критического значения ниже, чем вероятность критического, когда вы знаете, что было достаточно поворотов со времени последнего.
Ответ 22
О. П.,
В значительной степени, если вы хотите, чтобы это было честно, это не будет случайным.
Проблема вашей игры - это фактическая длина матча. Чем дольше будет матч, тем меньше вероятность того, что вы увидите (криты будут составлять 20%), и это приблизится к вашим предполагаемым значениям.
У вас есть два варианта: предварительно вычислить атаки на основе предыдущих рулонов. Который вы получите один крит каждые 5 атак (на основе ваших 20%), но вы можете сделать заказ случайным.
listOfFlowingAttacks = {Хит, Хит, Хит, Мисс, Крит};
Это шаблон, который вы хотите. Поэтому сделайте выбор случайным образом из этого списка, пока его не будет пустым, они его заново создадут.
Это шаблон, который я создал для своей игры, он работает очень хорошо, для чего я хочу, чтобы он делал.
ваш второй вариант, скорее всего, увеличит вероятность крита, вы, вероятно, увидите более четное число в конце всех атак (предполагая, что ваши матчи заканчиваются довольно быстро). Чем меньше вероятность, тем больше вы получаете RNG'd.
Ответ 23
Вы смотрите на линейное распределение, когда вы, вероятно, хотите нормальное распределение.
Если вы помните, что в юности играли D & D, вас попросили перевернуть несколько n-сторонних штампов, а затем суммировать результаты.
Например, прокатка 4 х 6-сторонней матрицы отличается от прокатки 1 х 24-сторонних кубиков.
Ответ 24
это действительно предсказуемо... но вы никогда не можете быть уверены.
Ответ 25
"Город героев" на самом деле имеет механика, называемого "полосатой", который решает именно эту проблему. Способ, которым он работает, состоит в том, что после последовательности промахов длины, связанной с наименьшей вероятностью попадания в строку, следующая атака гарантированно станет хитом. Например, если вы пропустите атаку с более чем 90%, чтобы нанести удар, тогда ваша следующая атака будет автоматически ударяться, но если ваш шанс попадания будет ниже, чем 60%, тогда вам нужно будет иметь несколько последовательных промахов, чтобы вызвать "полосатор" (I не знаю точных чисел)
Ответ 26
Как насчет взвешивания значения?
Например, если у вас есть 20% вероятность критического попадания, сгенерируйте число от 1 до 5 с одним числом, представляющим критический удар, или числом от 1 до 100, при этом 20 чисел будут критическим.
Но пока вы работаете со случайными или псевдослучайными номерами, нет возможности потенциально избежать результатов, которые вы сейчас видите. Это характер случайности.
Ответ 27
Предварительно вычислите случайный критический удар для каждого игрока.
// OBJECT
//...
// OnAttack()
//...
c_h = c_h -1;
if ( c_h == 0 ) {
// Yes, critical hit!
c_h = random(5) + 1 // for the next time
// ...
}
Ответ 28
Реакция на:
"Проблема в том, что у меня очень плохие результаты в реальной жизни - иногда игроки получают 3 крита в 5 хитов, иногда ни один из 15 хитов".
У вас есть шанс где-то между 3 и 4% получить ничего в 15 хитов...
Ответ 29
Я бы предложил следующую "случайную задержку штамповки":
- Поддерживать два массива, один (
in-array
), первоначально заполненный значениями от 0 до n-1, другой (out-array
) пустым
- Когда запрашивается результат:
- возвращает случайное значение из всех определенных значений в
in-array
- переместите это значение с
in-array
на out-array
- переместите один случайный (по всем элементам, включая элемент undefined!) из
out-array
обратно в in-array
Это свойство обладает тем, что он будет "реагировать" медленнее, чем больше n. Например, если вы хотите 20% -ный шанс, установив n на 5 и нажав на 0, это будет "менее случайным", чем установка n до 10 и нажатие на 0 или 1, и от 0 до 199 из 1000 будет почти неотличимы от истинной случайности над небольшим образцом. Вам нужно будет отрегулировать n на ваш размер выборки.
Ответ 30
Я думаю, что, возможно, вы используете неправильную функцию распределения.
Вы, вероятно, не хотите равномерного распределения по номерам. Вместо этого попробуйте нормальное распределение, чтобы критические попадания стали более необычными, чем "регулярные" хиты.
Я работаю с Java, поэтому я не уверен, где вы можете найти что-то для С++, который дает вам случайные числа с нормальным распределением, но там должно быть что-то.