Як я можу уникнути "надто" щасливих / нещасливих смуг у генерації випадкових чисел?


30

Зараз я маю справу з багатокористувацькою бойовою системою, де збитки, завдані гравцям, завжди множать на випадковий коефіцієнт між 0,8 і 1,2.

Теоретично, справді випадковий РНГ може в кінцевому підсумку давати те саме число багато разів (див. Дилему Тетріса ). Це може привести до гри , де гравець завжди робить дуже великих втрат , а інші завжди робить дуже низький шкоди.

Що я можу зробити, щоб цього не сталося? Чи є деякі RNG кращі за інших, уникаючи повторення?


Я не бачу, як це працює. Звичайно, ви отримаєте послідовність x1, x2, x3, x4 .. де всі х великі. Хіба це не випадково?
Качка комуністична

Відповіді:


26

Ви можете вирішити це так само, як це робить Тетріс, склавши попередньо встановлений список результатів пошкоджень і перетасовуючи.

Скажімо, ви знаєте, що програвач завдає шкоди від 0,8 до 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-х), щоб отримати середнє значення правильним.
Адам

2
@Adam: Цей метод справді працює лише для цього конкретного прикладу; якщо ви маєте справу з тетрисними шматками, а не з мультиплікаторами пошкодження, що таке блок 2 - S?

6
Звичайний термін для такої системи є "випадкова без заміни". Це просто аналогічно використанню колоди карт замість кісток, насправді.
Kylotan

Ще краще, ви могли б зробити половину чисел справді випадковими, і лише половина з них підпадає під дію цього правила.
o0 '.

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% вартості, а 150% вартості. Зроблено трохи далі:

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 ) та виправляючи на основі цього. Один із ризиків полягає в тому, що кожен результат буде суттєво обернено співвіднесений з попереднім результатом, хоча ця кореляція знизиться з часом.
Килотан

1
Мені б сподобатися змінити це, використовуючи систему "протікає інтегратор": почати із середнього ініціалізації до 0,5, а замість підрахунку зразків вибрати довільне постійне значення (наприклад, 10, 20, 50 або 100), яке не збільшується. . Тоді принаймні кореляція між двома наступними значеннями є постійною протягом усього використання генератора. Ви також можете налаштувати постійне значення - більші значення означають повільніше виправлення та більш очевидну випадковість.
Кілотан

@Kylotan дякую, дякую за надання імені. Я не впевнений, що саме ви маєте на увазі під своїм другим коментарем - можливо, надайте нову відповідь?
Джонатан Дікінсон

Це досить розумно і не має обмежень масивів. Я розумію пропозицію Кілотана, яка полягає в тому, щоб samplesз самого початку ініціалізувати його на максимальному значенні (у цьому випадку 100). Таким чином, для стабілізації RNG не потрібно 99 ітерацій. Так чи інакше, один недолік, який я бачу при цьому методі, полягає в тому, що він не гарантує справедливості, він просто забезпечує постійну середню.
Користувача не знайдено

@jSepia - дійсно, ви все одно будете отримувати справедливість / несправедливість, але за ними слід (як правило) врівноважуватися. Наприклад, у моєму тесті на модуль я «вимусив» 100 непрок. За невпливових ситуацій (якщо ви подивитесь на код) 50-відсоткова процесія зазвичай бачить, в гіршому випадку, 2/3 в будь-якому напрямку. Але один гравець міг би пробігти, що дозволило їм перемогти іншого гравця. Якщо ви хочете , щоб зміщувати його більш сильно виставки: total = (average / 2) + desired.
Джонатан Дікінсон

3

Те, що ви просите, насправді протилежне більшості PRNG, нелінійному розподілу. Просто введіть у свої правила якусь зменшувальну логіку повернення, припускаючи, що все, що перевищує 1,0x, є "критичним ударом" якогось типу, просто скажіть, що кожен раунд ваші шанси на те, щоб стати критичним, зростають на X, поки ви не отримаєте його на який момент вони скидають на Y. Потім ви робите два рулони кожен раунд, один для визначення критичного чи ні, а потім інший для фактичної величини.


1
Це загальний підхід, який я б взяв, ви використовуєте рівномірний розподіл RNG, але трансформуєте його. Ви також можете використати вихід RNG як вхід до власного користувальницького розподілу, який перенастроюється на основі недавньої історії, тобто для примусового розходження у результатах, щоб він виглядав "більш випадковим" у людському сприйнятті.
Майкл

3
Я фактично знаю про MMO, який робить щось подібне, але шанс критичного факту насправді збільшується щоразу, коли ви отримуєте його, поки ви не отримаєте його, то він скидається на дуже низьке значення. Це призводить до рідкісних нападів критиків, які дуже задовольняють гравця.
кодерангер

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

2
Для виправлення цього не потрібен нелінійний розподіл, він просто вимагає, щоб короткі послідовні підмножини розподілу мали такі самі властивості, як і сам розподіл.

ось як це роблять ігри Blizzard, принаймні з Warcraft 3
дрета

2

Сід Мейєр виступив з відмінною промовою на GDC 2010 саме на цю тему та ігри цивілізації. Я спробую пізніше знайти та вставити посилання. По суті - сприйнята випадковість не те саме, що справжня випадковість. Щоб справи відчували себе справедливо, вам потрібно проаналізувати попередні результати та звернути увагу на психологію гравців.

Уникайте смуг невдачі будь-якою ціною (якщо попередні два повороти були невдалі, наступному слід гарантувати удачу). Гравець повинен бути завжди щасливішим, ніж противник AI.


0

Використовуйте зміщення зміщення

01rb0

Загальний розподіл буде упереджено наступною формулою:

rexp(b)

b1b0

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

Кожен раз, коли гравець сприятливо котиться, віднімайте від упередженості. Кожен раз, коли гравець котиться несприятливо, додайте упередженості. Змінену суму можна масштабувати тим, наскільки (не) сприятливим є рулон або може бути рівна сума (або їх комбінація). Вам потрібно буде відрегулювати конкретні значення, щоб відповідати відчуттю, яке ви збираєтеся.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.