Jak uniknąć „zbyt” szczęśliwych / nieszczęśliwych pasm podczas generowania liczb losowych?

30

Obecnie mam do czynienia z systemem walki wieloosobowej, w którym obrażenia zadawane przez graczy są zawsze mnożone przez losowy współczynnik od 0,8 do 1,2.

Teoretycznie prawdziwie losowy RNG może ostatecznie uzyskać tę samą liczbę wiele razy (patrz dylemat Tetris ). Może to doprowadzić do meczu, w którym gracz zawsze zadaje bardzo duże obrażenia, podczas gdy drugi zawsze zadaje bardzo niskie obrażenia.

Co mogę zrobić, aby upewnić się, że tak się nie stanie? Czy niektóre RNG są lepsze od innych w unikaniu powtórzeń?

Użytkownik nie znaleziony
źródło
Nie rozumiem, jak to działa. Oczywiście otrzymasz sekwencję x1, x2, x3, x4 .. gdzie wszystkie x są duże. Czy to nie tylko przypadek?
Kaczka komunistyczna

Odpowiedzi:

26

Możesz rozwiązać to w ten sam sposób, w jaki robi to Tetris, tworząc wstępnie ustaloną listę wyników obrażeń i przetasowania.

Powiedzmy, że wiesz, że gracz zada liniowe obrażenia o wartości od 0,8x do 1,2x. Weź listę [0,8, 0,9, 1,0, 1,1, 1,2]. Potasuj losowo , aby uzyskać np. [1.2, 1.0, 0.8, 0.9, 1.1].

Za pierwszym razem, gdy gracz zadaje obrażenia, zadaje 1,2x. Następnie 1x. Następnie itd. Do 1,1x. Tylko gdy tablica jest pusta, należy wygenerować i przetasować nową tablicę.

W praktyce prawdopodobnie zechcesz to zrobić jednocześnie dla ponad 4 tablic (np. Zacznij od [0,8,0.8,0.8,0.8,0.9,0.9,0.9,0.9, ...]). W przeciwnym razie okres sekwencji jest wystarczająco krótki, aby gracze mogli dowiedzieć się, czy ich kolejne trafienie jest „dobre”, czy nie. (Chociaż może to również dodać więcej strategii do walki, jak w tabeli Hoimi Dragon Quest IX , w której ludzie wymyślili, jak sondować, patrząc na leczące liczby i modyfikować, dopóki nie zagwarantujesz rzadkiego spadku.)


źródło
3
Aby było trochę bardziej losowe, zawsze możesz mieć połowę listy jako liczby losowe, a drugą połowę obliczać jako (2-x), aby uzyskać średnią poprawną.
Adam
2
@Adam: Ta metoda naprawdę działa tylko w tym konkretnym przykładzie; jeśli rozdajesz pionki Tetris zamiast mnożników obrażeń, co to jest blok 2-S?
6
Typowym terminem na określenie tego systemu jest „losowy bez wymiany”. To naprawdę analogiczne do używania talii kart zamiast kości.
Kylotan
Co więcej, możesz zrobić połowę liczb naprawdę losowych, a tylko połowa z nich podlega tej zasadzie.
o0 ”.
1
Nadal może powodować, że dystrybucja lokalna nie będzie przypominać dystrybucji globalnej, czego dokładnie nie chce pytanie. Terminy takie jak „naprawdę losowy” są niejasną pseudomatematyką; im bardziej zdefiniujesz pożądane właściwości statystyczne, tym wyraźniejsze będą Twoje zamiary i projekt gry.
5

Napisałem do tego kod . Istotą tego jest używanie statystyk do korygowania pechowych serii. Możesz to zrobić, aby śledzić, ile razy zdarzenie miało miejsce, i użyć go do odchylenia liczby wygenerowanej przez PRNG.

Po pierwsze, w jaki sposób śledzimy procent zdarzeń? Naiwnym sposobem na to byłoby zachowanie wszystkich liczb kiedykolwiek wygenerowanych w pamięci i uśrednienie ich: co by działało, ale jest okropnie nieefektywne. Po krótkiej refleksji wpadłem na następujące (które są w zasadzie skumulowaną średnią ruchomą ).

Weź następujące próbki PRNG (gdzie wykonujemy, jeśli próbka jest> = 0,5):

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

Zauważ, że każda wartość przyczynia się do 1/5 wyniku końcowego. Spójrzmy na to z innej strony:

Values: 0.1, 0.5
Events: 0  , 1

Zauważ, że 0przyczynia się do 50% wartości i 1przyczynia się do 50% wartości. Posunięto się nieco dalej:

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

Teraz pierwsze wartości stanowią 66% wartości, a ostatnie 33%. Możemy zasadniczo sprowadzić to do następującego procesu:

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

Teraz musimy przesunąć wynik wartości próbkowanej z PRNG, ponieważ wybieramy procentową szansę, tutaj rzeczy są znacznie łatwiejsze (w porównaniu z, powiedzmy, losowymi wielkościami obrażeń w RTS). Trudno to wytłumaczyć, ponieważ „przyszło mi to do głowy”. Jeśli średnia jest niższa, oznacza to, że musimy zwiększyć szansę wystąpienia zdarzenia i odwrotnie. Oto kilka przykładów

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%

Teraz „przyszło mi do głowy”, że w pierwszym przykładzie 83% było po prostu „0,5 z 0,6” (innymi słowy „0,5 z 0,5 plus 0,1”). W przypadku zdarzeń losowych oznacza to:

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

Aby wygenerować zdarzenie, należy zasadniczo użyć następującego kodu:

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

I dlatego dostajesz kod, który wpisałem w sedno. Jestem prawie pewien, że wszystko to można wykorzystać w scenariuszu przypadkowego uszkodzenia, ale nie poświęciłem czasu, aby to rozgryźć.

Oświadczenie: To są wszystkie statystyki domowe, nie mam wykształcenia w tej dziedzinie. Moje testy jednostkowe jednak zdają.

Jonathan Dickinson
źródło
Wygląda jak błąd w pierwszym przykładzie, ponieważ zarówno wartość 0,1, jak i 0,9 skutkuje zdarzeniem 0. Ale zasadniczo opisujesz utrzymywanie skumulowanej średniej ruchomej ( en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average ) i korygowanie na tej podstawie. Jednym z zagrożeń jest to, że każdy wynik byłby istotnie odwrotnie skorelowany z poprzednim wynikiem, chociaż korelacja ta zmniejszałaby się z czasem.
Kylotan
1
Kusiłbym, aby to zmienić, aby zamiast tego użyć systemu „nieszczelnego integratora”: zacznij od średniej zainicjowanej do 0,5 i zamiast zliczać próbki wybierz dowolną stałą wartość (np. 10, 20, 50 lub 100), która nie będzie zwiększana . Wtedy przynajmniej korelacja między 2 kolejnymi wartościami jest stała przez cały czas użytkowania generatora. Możesz także podkręcić stałą wartość - większe wartości oznaczają wolniejszą korektę i bardziej widoczną losowość.
Kylotan
@Kylotan dzięki, dziękuję za podanie nazwy. Nie jestem pewien, co dokładnie masz na myśli przez swój drugi komentarz - może podać nową odpowiedź?
Jonathan Dickinson
To dość sprytne i nie ma ograniczeń tablic. Rozumiem sugestię Kylotana, która polega na zainicjowaniu samplesod wartości maksymalnej (w tym przypadku 100) od samego początku. W ten sposób stabilizacja RNG nie wymaga 99 iteracji. Tak czy inaczej, jedyną wadą, którą widzę w przypadku tej metody, jest to, że nie gwarantuje ona uczciwości, po prostu zapewnia stałą średnią.
Nie znaleziono użytkownika
@ jSepia - rzeczywiście, nadal otrzymujesz serie uczciwości / niesprawiedliwości, ale po nich (zwykle) następuje zrównoważony bieg. Np. W moim teście jednostkowym „zmusiłem” 100 non-procs i spotkałem się z ~ 60 procs, kiedy zrobiłem prawdziwe próbki. W sytuacjach bez wpływu (jeśli spojrzysz na kod) 50% proc zazwyczaj widzi, w najgorszym przypadku, 2/3 w obu kierunkach. Ale jeden gracz może mieć bieg, który pozwoli mu pokonać drugiego gracza. Jeśli chcesz, aby odchylać go mocniej do targów: total = (average / 2) + desired.
Jonathan Dickinson
3

To, o co prosisz, jest w rzeczywistości przeciwieństwem większości PRNG, rozkład nieliniowy. Po prostu zastosuj w swoich regułach pewną malejącą logikę zwrotów, Zakładając, że wszystko powyżej 1,0x jest pewnego rodzaju „trafieniem krytycznym”, po prostu powiedz, że w każdej rundzie Twoje szanse na uzyskanie krytyka wzrosną o X, dopóki nie zdobędziesz które punkty resetują do Y. Następnie wykonujesz dwa rzuty w każdej rundzie, jeden dla określenia krytycznego lub nie, a drugi dla rzeczywistej wielkości.

koderanger
źródło
1
To ogólne podejście, które przyjmuję, używasz jednolitego rozkładu RNG, ale go przekształcasz. Możesz również użyć danych wyjściowych RNG jako danych wejściowych do własnego rozkładu niestandardowego, który ponownie dostosowuje się w oparciu o najnowszą historię, tj. Aby wymusić wariancję danych wyjściowych, aby wyglądała „bardziej losowo” w kategoriach ludzkiej percepcji.
Michael
3
Tak naprawdę znam MMO, które robi coś takiego, ale szansa na trafienie krytyczne zwiększa się za każdym razem, gdy go dostaniesz, dopóki go nie dostaniesz, a następnie resetuje się do bardzo niskiej wartości. Prowadzi to do rzadkich serii krytyków, które są bardzo satysfakcjonujące dla gracza.
coderanger
Brzmi jak dobry alg, długie suche zaklęcia zawsze były frustrujące, ale nie prowadzą do szalonych smug krytyków.
Michael
2
Naprawienie tego nie wymaga rozkładu nieliniowego, po prostu wymaga, aby podzbiory krótko-sekwencyjne rozkładu miały takie same właściwości jak sam rozkład.
tak właśnie robią gry Blizzard, przynajmniej od Warcraft 3
dreta
2

Sid Meier wygłosił doskonałe przemówienie na GDC 2010 na ten temat i na temat gier cywilizacyjnych. Spróbuję znaleźć i wkleić link później. W gruncie rzeczy postrzegana losowość to nie to samo, co prawdziwa losowość. Aby wszystko było w porządku, musisz przeanalizować poprzednie wyniki i zwrócić uwagę na psychologię graczy.

Za wszelką cenę unikaj serii pecha (jeśli poprzednie dwie tury były pechowe, następna powinna mieć szczęście). Gracz powinien zawsze mieć więcej szczęścia niż przeciwnik AI.

Kromster mówi, że popiera Monikę
źródło
0

Użyj zmiennego nastawienia

01rb0

Ogólny rozkład będzie tendencyjny według następującego wzoru:

rexp(b)

b1b0

Weź tę liczbę i skaluj ją odpowiednio do żądanego zakresu.

Za każdym razem, gdy gracz rzuci się pozytywnie, odejmij od błędu. Za każdym razem, gdy gracz rzuci się niekorzystnie, dodaj odchylenie. Zmienioną kwotę można przeskalować według tego, jak (nie) korzystny jest rzut lub może to być kwota płaska (lub kombinacja). Będziesz musiał dostosować określone wartości, aby dopasować je do swoich oczekiwań.

Wołowina
źródło