Ich habe eine Beutebox, die ich mit einem zufälligen Gegenstand füllen möchte. Aber ich möchte, dass jeder Gegenstand eine andere Chance hat, ausgewählt zu werden. Beispielsweise:
- 5% Chance auf 10 Gold
- 20% Chance auf Schwert
- 45% Schildchance
- 20% Rüstungschance
- 10% Chance auf Trank
Wie kann ich es schaffen, dass ich genau einen der oben genannten Gegenstände auswähle, bei denen diese Prozentsätze die jeweiligen Chancen auf die Beute darstellen?
Antworten:
Die Lösung für softcodierte Wahrscheinlichkeiten
Die hartcodierte Wahrscheinlichkeitslösung hat den Nachteil, dass Sie die Wahrscheinlichkeiten in Ihrem Code festlegen müssen. Sie können zur Laufzeit nicht ermittelt werden. Es ist auch schwer zu pflegen.
Hier ist eine dynamische Version desselben Algorithmus.
Hier ist eine Beispielimplementierung in Java in Form einer Vorlagenklasse, die Sie für jedes Objekt, das Ihr Spiel verwendet, instanziieren können. Anschließend können Sie mit der Methode Objekte hinzufügen
.addEntry(object, relativeWeight)
und einen der zuvor hinzugefügten Einträge auswählen.get()
Verwendung:
Hier ist dieselbe Klasse, die in C # für Ihr Unity-, XNA- oder MonoGame-Projekt implementiert ist:
Und hier ist einer in JavaScript :
Profi:
Contra:
O(n)
Laufzeitkomplexität). Wenn Sie also sehr viele Gegenstände haben und sehr oft zeichnen, kann es langsam werden. Eine einfache Optimierung besteht darin, die wahrscheinlichsten Elemente an die erste Stelle zu setzen, sodass der Algorithmus in den meisten Fällen vorzeitig beendet wird. Eine komplexere Optimierung, die Sie durchführen können, besteht darin, die Tatsache auszunutzen, dass das Array sortiert ist, und eine Halbierungssuche durchzuführen. Das braucht nurO(log n)
Zeit.O(n)
Worst-Case-Laufzeitquelle
Hinweis: Für genau dieses Problem habe ich eine C # -Bibliothek erstellt
Die anderen Lösungen sind in Ordnung, wenn Sie nur eine kleine Anzahl von Elementen haben und sich Ihre Wahrscheinlichkeiten nie ändern. Bei vielen Elementen oder sich ändernden Wahrscheinlichkeiten (z. B. Entfernen von Elementen nach ihrer Auswahl) möchten Sie jedoch etwas Stärkeres.
Hier sind die beiden häufigsten Lösungen (beide sind in der obigen Bibliothek enthalten)
Walkers Alias-Methode
Eine clevere Lösung, die extrem schnell (
O(1)
!) Ist, wenn Ihre Wahrscheinlichkeiten konstant sind. Im Wesentlichen erstellt der Algorithmus eine 2D-Dartscheibe ("Alias-Tabelle") aus Ihren Wahrscheinlichkeiten und wirft einen Pfeil darauf.Es gibt viele Artikel online darüber, wie es funktioniert, wenn Sie mehr erfahren möchten.
Das einzige Problem ist, dass Sie die Alias-Tabelle, die langsam ist, neu generieren müssen, wenn sich Ihre Wahrscheinlichkeiten ändern. Wenn Sie also Artikel entfernen müssen, nachdem sie kommissioniert wurden, ist dies nicht die richtige Lösung für Sie.
Baumbasierte Lösung
Die andere übliche Lösung besteht darin, ein Array zu erstellen, in dem jedes Element die Summe seiner Wahrscheinlichkeit und aller Elemente davor speichert. Dann generiere einfach eine Zufallszahl aus [0,1] und suche binär, wo diese Zahl in der Liste landet.
Diese Lösung ist sehr einfach zu codieren / zu verstehen, aber das Treffen einer Auswahl ist langsamer als Walkers Alias-Methode, und das Ändern der Wahrscheinlichkeiten ist noch nicht abgeschlossen
O(n)
. Wir können es verbessern, indem wir das Array in einen Binärsuchbaum verwandeln, in dem jeder Knoten die Summe der Wahrscheinlichkeiten in allen Elementen in seinem Unterbaum verfolgt. Wenn wir dann die Zahl aus [0,1] generieren, können wir einfach den Baum hinuntergehen, um das Objekt zu finden, das es darstellt.Dies gibt uns
O(log n)
die Möglichkeit, einen Artikel auszuwählen und die Wahrscheinlichkeiten zu ändern! Das machtNextWithRemoval()
extrem schnell!Die Ergebnisse
Im Folgenden finden Sie einige schnelle Benchmarks aus der obigen Bibliothek, in denen diese beiden Ansätze verglichen werden
Wie Sie sehen, ist die Walker-Alias-Methode für den Sonderfall statischer (sich nicht ändernder) Wahrscheinlichkeiten etwa 50-100% schneller. In den dynamischeren Fällen ist der Baum jedoch um mehrere Größenordnungen schneller !
quelle
nlog(n)
) beim Sortieren von Elementen nach Gewicht.Die Glücksrad-Lösung
Sie können diese Methode verwenden, wenn die Wahrscheinlichkeiten in Ihrem Artikelpool einen ziemlich großen gemeinsamen Nenner haben und Sie sehr oft daraus zeichnen müssen.
Erstellen Sie eine Reihe von Optionen. Setzen Sie jedoch jedes Element mehrmals ein, wobei die Anzahl der Duplikate jedes Elements proportional zur Wahrscheinlichkeit des Auftretens ist. Im obigen Beispiel haben alle Elemente Wahrscheinlichkeiten, die Multiplikatoren von 5% sind, sodass Sie ein Array mit 20 Elementen wie folgt erstellen können:
Wählen Sie dann einfach ein zufälliges Element dieser Liste aus, indem Sie eine zufällige Ganzzahl zwischen 0 und der Länge des Arrays - 1 generieren.
Nachteile:
Vorteile:
quelle
Epic Scepter of the Apocalypse
. Ein solcher zweistufiger Ansatz nutzt die Vorteile beider Ansätze.[('gold', 1),('sword',4),...]
addiere alle Gewichte und rolle dann eine Zufallszahl von 0 bis zur Summe, iteriere dann das Array und berechne, wo die Zufallszahl landet (d. H areduce
). Funktioniert gut für Arrays, die häufig aktualisiert werden und keine größeren Speicherprobleme aufweisen.Die Lösung für hartcodierte Wahrscheinlichkeiten
Der einfachste Weg, ein zufälliges Element aus einer gewichteten Sammlung zu finden, besteht darin, eine Kette von if-else-Anweisungen zu durchlaufen, wobei jedes if-else wahrscheinlich zunimmt, da das vorherige nicht trifft.
Der Grund, warum die Bedingungen gleich der Chance sind, zuzüglich aller vorherigen Bedingungen, ist, dass die vorherigen Bedingungen bereits die Möglichkeit beseitigt haben, dass es sich um diese Elemente handelt. Für die Bedingung des Schildes entsprechen
else if(rand <= 70)
70 der 45% igen Chance des Schildes plus der 5% igen Chance des Goldes und der 20% igen Chance des Schwertes.Vorteile:
Nachteile:
quelle
In C # könnten Sie einen Linq-Scan verwenden, um Ihren Akku mit einer Zufallszahl im Bereich von 0 bis 100.0f zu vergleichen und .First () zu erhalten. Also wie eine Codezeile.
Also so etwas wie:
sum
ist eine null initialisierte ganze Zahl unda
eine Liste von prob / item-Strukturen / Tupeln / Instanzen.rand
ist eine zuvor generierte Zufallszahl im Bereich.Dies summiert einfach die Summe über die Liste der Bereiche, bis sie die zuvor ausgewählte Zufallszahl überschreitet, und gibt entweder das Element oder null zurück, wobei null zurückgegeben würde, wenn der Zufallszahlenbereich (z. B. 100) versehentlich kleiner als der Gesamtgewichtungsbereich ist , und die ausgewählte Zufallszahl liegt außerhalb des gesamten Gewichtungsbereichs.
Sie werden jedoch feststellen, dass die Gewichte in OP einer Normalverteilung (Bell-Kurve) sehr nahe kommen. Ich denke, im Allgemeinen werden Sie keine bestimmten Bereiche wollen, Sie werden eher eine Verteilung wollen, die sich entweder um eine Glockenkurve oder nur auf einer abnehmenden Exponentialkurve (zum Beispiel) verjüngt. In diesem Fall können Sie einfach eine mathematische Formel verwenden, um einen Index für ein Array von Elementen zu generieren, die in der Reihenfolge der bevorzugten Wahrscheinlichkeit sortiert sind. Ein gutes Beispiel ist CDF in normaler Verteilung
Auch ein Beispiel hier .
Ein anderes Beispiel ist, dass Sie einen zufälligen Wert von 90 Grad bis 180 Grad verwenden könnten, um den unteren rechten Quadranten eines Kreises zu erhalten, die x-Komponente mit cos (r) zu nehmen und damit in eine priorisierte Liste zu indexieren.
Mit verschiedenen Formeln könnten Sie einen allgemeinen Ansatz verfolgen, bei dem Sie einfach eine priorisierte Liste beliebiger Länge (z. B. N) eingeben und das Ergebnis der Formel (z. B. cos (x) ist 0 zu 1) durch Multiplikation (z. B. Ncos (x) abbilden ) = 0 bis N), um den Index zu erhalten.
quelle
Wahrscheinlichkeiten müssen nicht fest codiert sein. Die Elemente und die Schwellenwerte können zusammen in einem Array sein.
Sie müssen die Schwellenwerte noch akkumulieren, können dies jedoch beim Erstellen einer Parameterdatei tun, anstatt sie zu codieren.
quelle
random()
in der Schleife zu haben?Ich habe diese Funktion ausgeführt: https://github.com/thewheelmaker/GDscript_Weighted_Random Now! in deinem Fall kannst du es so benutzen:
Es gibt nur eine Zahl zwischen 0 und 4, aber Sie können sie in ein Array einfügen, in dem Sie die Gegenstände erhalten haben.
Oder in Funktion:
Hier ist der Code. Ich habe es auf GDscript gemacht, das können Sie, aber es kann eine andere Sprache ändern, auch nach logischen Fehlern suchen:
Es funktioniert so: on_normal_case ([50,50], 0) Dies ergibt 0 oder 1, es hat beide die gleiche Wahrscheinlichkeit.
on_normal_case ([50,50], 1) Dies ergibt 1 oder 2, beide haben die gleiche Wahrscheinlichkeit.
on_normal_case ([20,80], 1) Dies ergibt 1 oder 2, es gibt eine größere Änderung, um zwei zu erhalten.
on_normal_case ([20,80,20,20,30], 1) Dies ergibt einen Zufallszahlenbereich von 1-5 und größere Zahlen sind wahrscheinlicher als kleinere Zahlen.
on_normal_case ([20,80,0,0,20,20,30,0,0,0,33], 45) Dieser Wurf würfelt zwischen den Zahlen 45,46,49,50,51,56, die Sie sehen, wenn Sie dort sind Null ist, kommt es nie vor.
Die Funktion gibt also nur eine Zufallszahl zurück, die von der Länge des Arrays und der Transformationsnummer abhängt, und die Angaben im Array sind Wahrscheinlichkeitsgewichte, mit denen eine Zahl auftreten kann, bei der sich diese Zahl auf dem Array befindet.
quelle