Wie vermeide ich "zu" glückliche / unglückliche Streifen bei der Zufallsgenerierung?


30

Ich habe es derzeit mit einem Mehrspieler-Kampfsystem zu tun, bei dem der von den Spielern verursachte Schaden immer mit einem Zufallsfaktor zwischen 0,8 und 1,2 multipliziert wird.

Theoretisch kann ein wirklich zufälliger RNG am Ende viele Male dieselbe Zahl ergeben (siehe das Tetris-Dilemma ). Dies könnte zu einem Match führen, in dem der Spieler immer sehr hohen Schaden verursacht, während der andere immer sehr geringen Schaden verursacht.

Was kann ich tun, um sicherzustellen, dass dies nicht passiert? Können manche RNGs Wiederholungen besser vermeiden als andere?


Ich verstehe nicht, wie das funktioniert. Natürlich erhalten Sie eine Folge von x1, x2, x3, x4, wobei alle x groß sind. Ist das nicht zufällig?
Die kommunistische Ente

Antworten:


26

Sie können es auf die gleiche Weise wie Tetris lösen, indem Sie eine voreingestellte Liste der Schadensergebnisse und des Mischens erstellen.

Angenommen, Sie wissen, dass der Spieler mit einer linearen Verteilung 0,8x bis 1,2x Schaden verursachen wird. Nehmen Sie die Liste [0.8, 0.9, 1.0, 1.1, 1.2]. Mische es zufällig , so dass du zB [1.2, 1.0, 0.8, 0.9, 1.1] erhältst.

Wenn der Spieler zum ersten Mal Schaden verursacht, verursacht er 1,2x Schaden. Dann 1x. Dann usw auf 1.1x.Nur wenn das Array leer ist, sollten Sie ein neues Array generieren und mischen.

In der Praxis möchten Sie dies wahrscheinlich mit 4+ Arrays gleichzeitig tun (z. B. beginnen Sie mit [0.8,0.8,0.8,0.8,0.9,0.9.9,0.9, ...]). Andernfalls ist der Zeitraum der Sequenz so kurz, dass die Spieler herausfinden können, ob ihr nächster Treffer "gut" ist oder nicht. (Auch wenn dies dem Kampf mehr Strategie verleihen kann, wie in der Hoimi-Tabelle von Dragon Quest IX, in der anhand von Heilungszahlen und Optimierungen herausgefunden wurde, wie man prüft , bis ein seltener Fall garantiert ist.)


3
Um es etwas zufälliger zu machen, könnte man immer die Hälfte der Liste als Zufallszahlen und die andere Hälfte als (2-x) berechnen, um den Durchschnitt korrekt zu erhalten.
Adam

2
@Adam: Diese Methode funktioniert wirklich nur für dieses Beispiel. Was ist der 2-S-Block, wenn Sie Tetris-Teile verteilen, anstatt Multiplikatoren zu beschädigen?

6
Der übliche Begriff für diese Art von System ist "zufällig ohne Ersatz". Eigentlich ist es nur analog zur Verwendung eines Kartenspiels anstelle von Würfeln.
Kylotan

Noch besser, Sie könnten die Hälfte der Zahlen wirklich zufällig machen, und nur die Hälfte von ihnen unterliegt dieser Regel.
o0 '.

1
Es kann immer noch dazu führen, dass die lokale Verteilung nicht der globalen Verteilung ähnelt. Genau das ist es, was die Frage nicht will. Begriffe wie "wirklich zufällig" sind vage Pseudomathematik; Je mehr Sie definieren, welche statistischen Eigenschaften Sie wünschen, desto klarer werden Ihre Absichten und Ihr Spieldesign.

5

Ich habe tatsächlich welche geschrieben Code geschrieben . Der Kern davon ist die Verwendung von Statistiken, um Pechsträhnen zu korrigieren. Sie können dies tun, indem Sie nachverfolgen, wie oft das Ereignis aufgetreten ist, und die vom PRNG generierte Zahl anhand dieser Werte beeinflussen.

Erstens, wie behalten wir den Prozentsatz der Ereignisse im Auge? Die naive Art, dies zu tun, wäre, alle jemals generierten Zahlen im Speicher zu halten und sie zu mitteln: Das würde funktionieren, ist aber schrecklich ineffizient. Nachdem ich ein wenig nachgedacht hatte, kam ich auf Folgendes (das ist im Grunde genommen a kumulativer gleitender Durchschnitt ist ).

Nehmen Sie die folgenden PRNG-Proben (wo wir verarbeiten, wenn die Probe> = 0,5 ist):

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

Beachten Sie, dass jeder Wert zu 1/5 des Endergebnisses beiträgt. Schauen wir es uns anders an:

Values: 0.1, 0.5
Events: 0  , 1

Beachten Sie, dass das 0zu 50% des Wertes beiträgt und das1 beiträgt. Etwas weiter genommen:

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

Jetzt tragen die ersten Werte 66% und die letzten 33% zum Wert bei. Wir können dies im Grunde auf den folgenden Prozess zurückführen:

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

Jetzt müssen wir das Ergebnis des vom PRNG abgetasteten Werts verzerren, da wir hier eine prozentuale Chance haben, dass die Dinge viel einfacher sind (im Vergleich zu beispielsweise zufälligen Schadensbeträgen in einem RTS). Das wird schwer zu erklären sein, weil es mir gerade eingefallen ist. Wenn der Durchschnitt niedriger ist, bedeutet dies, dass wir die Wahrscheinlichkeit des Eintretens des Ereignisses erhöhen müssen und umgekehrt. Also einige Beispiele

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%

Was mir jetzt einfiel, war, dass im ersten Beispiel 83% nur "0,5 von 0,6" waren (mit anderen Worten "0,5 von 0,5 plus 0,1"). In zufälligen Ereignissen bedeutet dies entweder:

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

Um also ein Ereignis zu generieren, würden Sie grundsätzlich den folgenden Code verwenden:

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

Und deshalb bekommst du den Code, den ich in den Kern geschrieben habe. Ich bin mir ziemlich sicher, dass dies alles in dem zufälligen Schadensfall-Szenario verwendet werden kann, aber ich habe mir nicht die Zeit genommen, dies herauszufinden.

Haftungsausschluss: Dies ist alles einheimische Statistik, ich habe keine Ausbildung in diesem Bereich. Meine Unit-Tests bestehen allerdings.


In Ihrem ersten Beispiel sieht es nach einem Fehler aus, da sowohl ein Wert von 0,1 als auch ein Wert von 0 zu einem Ereignis von 0 führen. Aber Sie beschreiben im Grunde, wie Sie einen kumulativen gleitenden Durchschnitt ( en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average ) halten und basierend darauf korrigieren. Ein Risiko besteht darin, dass jedes Ergebnis signifikant umgekehrt mit dem vorherigen Ergebnis korreliert wird, obwohl diese Korrelation mit der Zeit abnimmt.
Kylotan,

1
Ich wäre versucht, dies zu ändern, um stattdessen ein "Leaky Integrator" -System zu verwenden: Beginnen Sie mit dem auf 0,5 initialisierten Mittelwert und wählen Sie statt der Abtastwerte einen beliebigen konstanten Wert (z. B. 10, 20, 50 oder 100), der nicht inkrementiert wird . Dann ist zumindest die Korrelation zwischen 2 aufeinanderfolgenden Werten während der Verwendung des Generators konstant. Sie können auch den konstanten Wert ändern. Größere Werte bedeuten eine langsamere Korrektur und eine offensichtlichere Zufälligkeit.
Kylotan

@Kylotan danke, danke für den Namen. Ich weiß nicht genau, was Sie mit Ihrem zweiten Kommentar meinen - vielleicht eine neue Antwort?
Jonathan Dickinson

Das ist ziemlich clever und hat nicht die Einschränkungen von Arrays. Ich verstehe den Vorschlag von Kylotan, samplesvon Anfang an mit dem Maximalwert (in diesem Fall 100) zu initialisieren . Auf diese Weise benötigt das RNG keine 99 Iterationen, um sich zu stabilisieren. Der einzige Nachteil, den ich bei dieser Methode feststellen kann, ist, dass sie keine Fairness garantiert , sondern lediglich einen konstanten Durchschnitt gewährleistet.
Benutzer nicht gefunden

@jSepia - in der Tat würden Sie immer noch Läufe von Fairness / Ungerechtigkeit erhalten, aber ihnen würde (normalerweise) ein ausgeglichener Lauf folgen. ZB In meinem Unit-Test habe ich 100 Non-Procs "erzwungen" und bin bei den Real-Samples auf ca. 60 Procs gestoßen. In unbeeinflussten Situationen (wenn Sie sich den Code ansehen) sieht ein 50% -Prozessor normalerweise im schlimmsten Fall einen Lauf von 2/3 in beide Richtungen. Aber ein Spieler könnte einen Run haben, der es ihm erlaubt, den anderen Spieler zu besiegen. Wenn Sie Bias wollen sie stärker zur Messe: total = (average / 2) + desired.
Jonathan Dickinson

3

Was Sie fordern, ist eigentlich das Gegenteil der meisten PRNGs, eine nichtlineare Verteilung. Setzen Sie einfach eine Art von abnehmender Ertragslogik in Ihre Regeln ein. Angenommen, alles über 1.0x ist ein "kritischer Treffer". Sagen Sie einfach, dass Ihre Chancen, einen kritischen Treffer zu erzielen, in jeder Runde um X steigen, bis Sie einen erreichen Welchen Punkt sie auf Y zurücksetzen


1
Dies ist der allgemeine Ansatz, den ich verfolgen würde. Sie verwenden die einheitliche Verteilung des RNG, transformieren sie jedoch. Sie können die Ausgabe des RNG auch als Eingabe für Ihre eigene benutzerdefinierte Verteilung verwenden, die sich basierend auf der jüngsten Historie neu anpasst, dh Abweichungen in den Ausgaben erzwingt, sodass sie in Bezug auf die menschliche Wahrnehmung "zufälliger" aussehen.
Michael

3
Eigentlich kenne ich ein MMO, das so etwas macht, aber die Chance eines Kritikers steigt jedes Mal, wenn Sie eines bekommen, bis Sie keines bekommen. Dann wird es auf einen sehr niedrigen Wert zurückgesetzt. Dies führt zu seltenen Crit-Streifen, die den Spieler sehr befriedigen.
Coderanger

Klingt nach einer guten Alge, lange Trockenperioden waren schon immer frustrierend, führen aber nicht zu verrückten Krisenstreifen.
Michael

2
Um dies zu beheben, ist keine nichtlineare Verteilung erforderlich, sondern es müssen nur kurze zeitlich aufeinanderfolgende Untergruppen der Verteilung dieselben Eigenschaften wie die Verteilung selbst aufweisen.

So machen es Blizzard-Spiele, spätestens seit Warcraft 3
dreta 16.07.12

2

Sid Meier hielt auf der GDC 2010 eine hervorragende Rede zu diesem Thema und zu Civilization Games. Ich werde versuchen, den Link später zu finden und einzufügen. Im Wesentlichen - wahrgenommene Zufälligkeit ist nicht dasselbe wie wahre Zufälligkeit. Damit sich die Dinge fair anfühlen, müssen Sie frühere Ergebnisse analysieren und die Psychologie der Spieler berücksichtigen.

Vermeiden Sie Pechsträhnen um jeden Preis (wenn die vorherigen beiden Runden Pech hatten, sollte die nächste garantiert Glück haben). Der Spieler sollte immer mehr Glück haben als ein KI-Gegner.


0

Verwenden Sie eine Verschiebungsvorspannung

Der Basisgenerator verwendet eine gleichmäßige Verteilung zwischen 0 und 1 generieren r. Stellen Sie zunächst einen Bias-Wert ein.bzu 0.

Die Gesamtverteilung wird durch die folgende Formel verzerrt:

rexp(-b)

Der Effekt hier ist der wenn b positiv ist, wird die resultierende Zahl in Richtung voreingenommen 1. Wannb negativ ist, wird die resultierende Zahl in Richtung voreingenommen 0.

Nehmen Sie diese Zahl und skalieren Sie sie entsprechend auf den gewünschten Bereich.

Jedes Mal, wenn ein Spieler positiv würfelt, wird der Bias abgezogen. Jedes Mal, wenn der Spieler ungünstig würfelt, erhöhen Sie die Tendenz. Der Betrag, um den sich die Rolle ändert, kann skaliert werden, je nachdem, wie (ungünstig) die Rolle ist, oder es kann sich um einen Pauschalbetrag (oder eine Kombination) handeln. Sie müssen bestimmte Werte anpassen, um sie an das gewünschte Gefühl anzupassen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.