事前のイベントによってバイアスされる「ランダム」ジェネレーターを作成するにはどうすればよいですか?


37

事前のイベントによって偏ったチャンスベースのシステムを実装したいと考えています。

背景:数年前、私はWorld of Warcraftのアップデートを思い出します。彼らは、スパイクチェーンのイベントに対抗する新しいチャンス計算機を実装したことを発表しました。(たとえば、クリティカルストライクを行うか、連続して数回回避する)。アイデアは、あなたがヒットをかわす場合、次のヒットをかわす可能性は減少するが、それは両方の方法で働くということでした。ヒットを回避しないと、次のヒットを回避する機会が等しく増加します。ここでの主要なトリックは、いくつかの試行にわたって、覆い焼きの可能性がプレイヤーの統計シートでプレイヤーに与えられた割合に対応することでした。

この種のシステムは当時非常に興味をそそられ、今ではそのような解決策が必要な状況にあります。

私のトラブルは次のとおりです。

  • このようなシステムの実装に関するオンラインリソースを見つけることができると思いますが、それを見つけるための関連する話題の言葉が欠けているだけかもしれません。
  • また、2項式(つまり2つの結果)ではなく、4つの相互に排他的なイベントを含むシステムに適合するために、このアプローチが必要です。

私の現在のアプローチは、ラッフルチケットシステムのアプローチに似ています。イベントが発生すると、他のすべてのイベントを優先して重みを変更します。4つのイベントが等しく発生する可能性がある場合、これは機能する可能性がありますが、私の場合は、より広く普及する必要があります。しかし、一般的なイベントがより頻繁に発生するため、他のものの重みが意図したよりもはるかに大きくシフトし、イベントがあった初期値の周りに平均チケット数を維持するために必要な重みシフトの数値を見つけることができないようです与えられた。

いくつかの方向ポインターまたは明確な例が大歓迎です。


4
非常に微妙な、または洗練された回答が必要な場合は、Mathematics.SEで質問することもできます。数学者は、確率に関する複雑な質問に気軽に答えます。math.stackexchange.com
ケビン-


6
答えを理解する可能性が高い数学サイトの代わりに、Programmers.SEがあります。アルゴリズム設計はMathで特に話題になっているわけでないので、おそらく有用な入力を得るには初期設計を考え出す必要があります。
リリエンタール

1
私はケビンとリリエンタールにあなたがより良い答えを得るかもしれないことに同意しますが、mklingenの答えを読むと、ここで説明されていることはマルコフ連鎖としてモデル化でき、ゲーム開発者が知るのに便利なツールかもしれないことに気付きました。これについては後で詳しく説明します。
-nwellcome

1
ここでいくつかの回答の数値を実行しているので、さまざまな制約があり、それらすべてを解決するソリューションは必要なものよりも複雑になる可能性があります。ユースケースのいくつかの詳細が、最適なオプションの絞り込みに役立つ場合があります。たとえば、イベントの確率はかなり似ていますか(例:それぞれ20%の確率で5つの異なる結果)、または非常に異なります(例:10%ミス80%ヒット10%クリティカル)?実行(例:連続して3回のミス)またはクランプ/待機(例:8回の試行のうち3回のミス、またはクリティカルになる前に20回の試行)を最小化しますか?
DMGregory

回答:


19

基本的に、あなたが求めているのは、以下のプロパティを持つイベントを生成する「セミランダム」イベントジェネレータです。

  1. 各イベントが発生する平均レートは事前に指定されています。

  2. 同じイベントがランダムに発生するよりも、連続して2回発生する可能性は低くなります。

  3. イベントは完全には予測できません。

そのための1つの方法は、最初に目標1と2を満たす非ランダムイベントジェネレーターを実装し、次に、ランダム性を追加して目標3を満たすことです。


非ランダムイベントジェネレーターには、単純なディザリングアルゴリズムを使用できます。具体的には、聞かせてP 1P 2、...、P Nにイベント1の相対尤度であるN、およびlet S = P 1 + P 2 + ... + Pをn個の重みの合計です。その後、次のアルゴリズムを使用して、イベントの非ランダムな最大分散シーケンスを生成できます。

  1. 最初に、e 1 = e 2 = ... = e n = 0とします。

  2. イベントを生成するには、各e ip iだけインクリメントし、e kが最大のイベントkを出力します(好きな方法でタイを壊します)。

  3. e ksだけ減らし、手順2から繰り返します。

たとえば、p A = 5、p B = 4、p C = 1の3つのイベントA、B、Cが与えられた場合、このアルゴリズムは次の出力シーケンスのようなものを生成します。

A B A B C A B A B A A B A B C A B A B A A B A B C A B A B A

この30個のイベントのシーケンスには、正確に15個のAs、12個のB、および3個のCが含まれています。それは完全に最適な分布ではありません(2つのAsが連続して発生することはいくつかありますが、回避することはできましたが)。


ここで、このシーケンスにランダム性を追加するには、いくつかの(必ずしも相互に排他的ではない)オプションがあります。

  • Philippのアドバイスに従い、適切なサイズの数Nに対して、N個の今後のイベントの「デッキ」を維持できます。イベントを生成する必要があるたびに、デッキからランダムなイベントを選択し、上記のディザリングアルゴリズムによって次のイベント出力に置き換えます。

    これを上記の例にN = 3で適用すると、次のようになります:

    A B A B C A B B A B A B C A A A A B B A B A C A B A B A B A

    一方、N = 10はよりランダムに見えるようになります。

    A A B A C A A B B B A A A A A A C B A B A A B A C A C B B B

    シャッフルにより、一般的なイベントAとBがより多くの実行で終了することに注意してください。一方、まれなCイベントは依然としてかなり間隔が空いています。

  • ディザリングアルゴリズムにランダム性を直接注入できます。たとえば、ステップ2でe ip iだけ増やす代わりに、p i ×random(0、2)だけ増やすことができます。ここで、random(ab)はabの間で一様に分布した乱数です。これにより、次のような出力が生成されます。

    A B B C A B A A B A A B A B A A B A A A B C A B A B A C A B

    または、e ip i + random(− cc)だけインクリメントすることができます(c = 0.1× sの場合)。

    B A A B C A B A B A B A B A C A B A B A B A A B C A B A B A

    または、c = 0.5× sの場合

    B A B A B A C A B A B A A C B C A A B C B A B B A B A B C A

    乗法スキームと比較して、一般的なイベントAおよびBよりもまれなイベントCの方が、加算スキームのランダム化効果がはるかに強いことに注意してください。これは望ましい場合と望ましくない場合があります。もちろん、e iの平均増分がp iに等しいというプロパティを保持する限り、これらのスキームの組み合わせ、または増分に対する他の調整を使用することもできます。

  • あるいは、選択したイベントkをランダムなイベント(生の重みp iに応じて選択)に置き換えることにより、ディザリングアルゴリズムの出力を乱すことができます。ステップ2で出力したのと同じkをステップ3 でも使用している限り、ディザリングプロセスはランダムな変動を均等にする傾向があります。

    たとえば、各イベントがランダムに選択される可能性が10%の場合の出力例を次に示します。

    B A C A B A B A C B A A B B A B A B A B C B A B A B C A B A

    そして、各出力がランダムである可能性が50%である例を次に示します。

    C B A B A C A B B B A A B A A A A A B B A C C A B B A B B C

    上記のように、純粋にランダムなイベントとディザリングされたイベントの混合をデッキ/ミキシングプールに供給すること、またはe i s(負の重みをゼロとして扱う)で重み付けされたkをランダムに選択してディザリングアルゴリズムをランダム化することも検討できます。

追伸 比較のために、同じ平均レートの完全にランダムなイベントシーケンスを次に示します。

A C A A C A B B A A A A B B C B A B B A B A B A A A A A A A
B C B A B C B A A B C A B A B C B A B A A A A B B B B B B B
C A A B A A B B C B B B A B A B A A B A A B A B A C A A B A

タンジェント:があったので、コメントの中でいくつかの議論それが再充填される前のデッキが空にできるようにするために、デッキベースのソリューションのために、それが必要なのかどうかについて、私はいくつかのデッキ充填戦略のグラフィカルな比較を行うことにしました。

プロット
セミランダムコインフリップを生成するためのいくつかの戦略のプロット(平均して頭と尾の比率が50:50)。横軸はフリップの数、縦軸は予想される比率からの累積距離で、(heads-tails)/ 2 = heads-flips / 2として測定されます。

プロットの赤と緑の線は、比較のために2つの非デッキベースのアルゴリズムを示しています。

  • 赤い線、決定論的ディザリング:偶数の結果は常にヘッドであり、奇数の結果は常にテールです。
  • 緑の線、独立したランダムフリップ:各結果はランダムに独立して選択され、50%の確率で頭を、50%の確率で尾を引きます。

他の3行(青、紫、シアン)は、最初に20枚の「ヘッド」カードと20枚の「テール」カードで満たされた40枚のカードのデッキを使用して実装された3つのデッキベースの戦略の結果を示しています:

  • 青い線、空のとき塗りつぶす:カードはデッキが空になるまでランダムに描かれ、その後デッキには20枚の「ヘッド」カードと20枚の「テール」カードが補充されます。
  • 紫の線、半分空になったら塗りつぶし:デッキに20枚のカードが残るまで、カードはランダムに描かれます。次に、デッキに10枚の「ヘッド」カードと10枚の「テール」カードを追加します。
  • シアン線、連続塗りつぶし:カードはランダムに描かれます。偶数のドローはすぐに「ヘッド」カードに置き換えられ、奇数のドローは「テール」カードに置き換えられます。

もちろん、上記のプロットはランダムなプロセスの1つの実現にすぎませんが、合理的に代表しています。特に、すべてのデッキベースのプロセスにはバイアスが制限されており、赤(決定論的)線にかなり近づいていますが、純粋にランダムな緑の線は最終的にはさまよいます。

(実際、青、紫、シアンの線のゼロからのずれは、デッキのサイズに厳密に制限されています。青の線はゼロから10ステップ以上ドリフトすることはありません。紫の線はゼロから15ステップしか移動できません。もちろん、シアンの線はゼロから最大20ステップ離れてドリフトする可能性があります。もちろん、実際にその限界に到達するラインは、あまり遠くにさまれるとゼロに近づく傾向が強いため、実際にはほとんどありません。オフ。)

一見したところ、異なるデッキベースの戦略の間に明らかな違いはありませんが(平均して、青い線は赤い線にやや近く、シアンの線はやや遠くに留まります)、青い線を詳しく調べますは明確な決定的パターンを明らかにします:40の描画(灰色の点線の縦線でマーク)ごとに、青い線はゼロで赤い線と正確に一致します。紫とシアンの線はそれほど厳しく制限されておらず、どの点でもゼロから離れたままにできます。

すべてのデッキベースの戦略において、バリエーションを制限する重要な機能は、カードがデッキからランダムに引き出される一方で、デッキが決定論的に補充されるという事実です。デッキの補充に使用されるカード自体がランダムに選択された場合、デッキベースの戦略はすべて、純粋なランダム選択と区別できなくなります(緑の線)。


非常に精巧な答え。ディザリングアルゴリズムにランダム係数を追加するのは簡単です。:)
そなて

あなたの答えに行くことにしました。:)しかし、メソッド概要の追加を上部に置くことをお勧めします。あなたの答えに基づいて私がやろうとしていることは、「赤」と「紫」の両方のソリューションを試すことです。
そなて

53

サイコロを振らないで、カードを配ります。

RNGのすべての可能な結果を​​取得し、それらをリストに入れ、ランダムにシャッフルし、ランダムな順序で結果を返します。リストの最後に達したら、繰り返します。

結果は依然として均一に分散されますが、リストの最後が次のリストの最初でもない限り、個々の結果は繰り返されません。

これがあなたの好みに対して少し予測しやすい場合、n可能な結果の数倍のリストを使用し、nシャッフルする前に各可能な結果を​​その時間に入れることができます。または、リストを完全に繰り返す前にリストをシャッフルできます。


1
(でも、このサイト上で)検索「シャッフルバッグ」
jhocking

3
これは、多くのテトリスゲームで、プレイヤーがキーピースに長時間飢えたままにならないようにすることです。設定された間隔で発生を制御する場合、新しいカードを挿入する前にPhilippが提案するように、バッグ/デッキを空にすることが重要です。進行中にカードを再挿入(または重みを再調整)することにより、計算が難しく、間違いを起こしやすい方法で確率分布を歪めることができます。
DMGregory

2
@DMGregory:実際、デッキが空になる前に新しいカードを混ぜても問題ありません(実際、結果をより自然で予測しにくくするためにこれを行うことをお勧めします)。重要なことは、シャッフル新しいカードの(平均)端数ことを確認することですのデッキはあなたが描きたいことを目的とする分画等しいアウトそれをします。
イルマリカロネン

4
Illmari Karonen:アイテムを交換すると、同一の結果の実行または特定の結果間の長いギャップを制限するという点でシャッフルバッグの利点を失う可能性があります。置換率が目標確率分布と等しい場合、各結果をランダムに独立して生成するのと同じ立場にあることが証明されています。ターゲットの確率分布と等しくない場合、予測し、バランスを取るのが難しい方法で有効な確率を歪めることができます。アスカーはまさにこの問題に苦労していると説明しています。
DMGregory

2
@DMGregoryと合意しました。新しいカードをシャッフルすると、システム自体が無効になります。カード取引システムは、特に望ましい結果に最適です。あなたは女王を削除する場合、例えば、デッキから(例えば、伝統的なカードを使用する)、女王を描画する確率が減少し、カード描画の確率他の女王増加よりを。自己調整システムです。
ボルテ

17

マルコフランダムグラフを試すことができます。発生する可能性のある各イベントをグラフ内のノードと見なします。各イベントから、後に発生する可能性のある他の各イベントへのリンクを作成します。これらの各リンクは、遷移確率と呼ばれるものによって重み付けされます。次に、遷移モデルに従ってグラフのランダムウォークを実行します。

たとえば、攻撃の結果(クリティカルヒット、回避など)を表すグラフを作成できます。開始ノードを、プレイヤーの統計情報からランダムに選択したノードに初期化します(「サイコロを転がす」だけです)。次に、次の攻撃で、遷移モデルを指定して次に何が起こるかを決定します。

遷移の重み付け方法を決定するには注意が必要です。1つの理由として、ノードから発生するすべての遷移は1の確率に達する必要があります。1つの簡単なことは、すべてのノードから他のすべてのノードへの遷移を行うことです。現在のイベントが二度と発生しないことを考えると、アプリオリ

たとえば、3つのイベントがある場合:

  Critical, P = 0.1
  Hit,      P = 0.3
  Miss,     P = 0.6

確率モデルを他のイベントに均一に再配布するだけで、クリティカルヒットが再び発生しないように遷移モデルを設定できます。

  Critical -> Critical,   P = 0.0
  Critical -> Hit,        P = 0.35
  Critical -> Miss,       P = 0.65

編集:以下のコメントが示すように、このモデルは、望ましい動作を得るのに十分複雑ではありません。代わりに、複数の状態を追加する必要がある場合があります!


1
提案する再重み付けスキームは、各状態の望ましい確率を保持しません。これらの数値を使用して経験的なテストを行うと、ミスは時間の約41%で発生し、クリティカルは約25%で、入力値から外れます。確率に比例した残りの状態への移行(たとえば、ミスはクリティカルに25%の確率、ヒットに75%の確率)は、44%のミス率と17%のクリティカルでわずかに改善されますが、それでもまだです。入力の望ましい確率を反映していません。
DMGregory

私は、ベイズ:(は後で再び再計算するルールを忘れて、それはCCHMまたはCHHMまたは可能性が非常に高いMMHM、などのような可能性のある配列アウト葉をスタンドとして遷移モデルは、ので、事前確率分布を維持することができないかもしれません。
mklingen

「繰り返しなし」の制約は、極端な高低の重みに関して、ここで手を縛る可能性があります。10回の試行のうち1回をクリティカルにしたい場合、この方法で達成できる唯一の方法は、5回のミスと5回のヒットを交互にすることです。連続したミスのないシーケンスは、ここでの入力の要件を満たすことができません。
DMGregory

4
@mklingen、私はDMGregoryに同意します。「厳密に繰り返さないこと」はここでは望ましくありません。むしろ、彼らは、同じ結果の長いチェーンの確率が、一様なランダムな確率の場合よりも低くなることを望んでいます。あなたはルックスが好きなこと(導かれ)マルコフ連鎖でこれを行うことができ、これを。これは複数の状態を使用して、「ヒット1」から「ヒット2」および「ヒット2」から「ヒット3+」に移行する確率が下がり、「ヒット1」および「クリティカル」に戻る確率が繰り返される繰り返しイベントを表します1 "上がります。
-nwellcome

@nwellcomeそれは素晴らしいアイデアです。
mklingen

3

C#で作成した実装は次のとおりです。

  • 確率に基づいてイベントをアクティブ化する
  • これらの確率を調整して、繰り返し発生するイベントの可能性を減らします
  • 元の確率から離れすぎない

あなたが私がしていることを見ることができるように、私はいくつかのコメントを追加しました。

    int percentageEvent1 = 15; //These are the starter values. So given a scenario, the
    int percentageEvent2 = 40; //player would have around a 15 percent chance of event
    int percentageEvent3 = 10; //one occuring, a 40 percent chance of event two occuring
    int percentageEvent4 = 35; //10 percent for event three, and 35 percent for event four.

    private void ResetValues()
    {
        percentageEvent1 = 15;
        percentageEvent2 = 40;
        percentageEvent3 = 10;
        percentageEvent4 = 35;
    }

    int resetCount = 0; //Reset the probabilities every so often so that they don't stray too far.

    int variability = 1; //This influences how much the chance of an event will increase or decrease
                           //based off of past events.

    Random RandomNumberGenerator = new Random();

    private void Activate() //When this is called, an "Event" will be activated based off of current probability.
    {
        int[] percent = new int[100];
        for (int i = 0; i < 100; i++) //Generate an array of 100 items, and select a random event from it.
        {
            if (i < percentageEvent1)
            {
                percent[i] = 1; //Event 1
            }
            else if (i < percentageEvent1 + percentageEvent2)
            {
                percent[i] = 2; //Event 2
            }
            else if (i < percentageEvent1 + percentageEvent2 + percentageEvent3)
            {
                percent[i] = 3; //Event 3
            }
            else
            {
                percent[i] = 4; //Event 4
            }
        }
        int SelectEvent = percent[RandomNumberGenerator.Next(0, 100)]; //Select a random event based on current probability.

        if (SelectEvent == 1)
        {
            if (!(percentageEvent1 - (3 * variability) < 1)) //Make sure that no matter what, probability for a certain event
            {                                                //does not go below one percent.
                percentageEvent1 -= 3 * variability;
                percentageEvent2 += variability;
                percentageEvent3 += variability;
                percentageEvent4 += variability;
            }
        }
        else if (SelectEvent == 2)
        {
            if (!(percentageEvent2 - (3 * variability) < 1))
            {
                percentageEvent2 -= 3 * variability;
                percentageEvent1 += variability;
                percentageEvent3 += variability;
                percentageEvent4 += variability;
            }
        }
        else if (SelectEvent == 3)
        {
            if (!(percentageEvent3 - (3 * variability) < 1))
            {
                percentageEvent3 -= 3 * variability;
                percentageEvent1 += variability;
                percentageEvent2 += variability;
                percentageEvent4 += variability;
            }
        }
        else
        {
            if (!(percentageEvent4 - (3 * variability) < 1))
            {
                percentageEvent4 -= 3 * variability;
                percentageEvent1 += variability;
                percentageEvent2 += variability;
                percentageEvent3 += variability;
            }
        }

        resetCount++;
        if (resetCount == 10)
        {
            resetCount = 0;
            ResetValues();
        }

        RunEvent(SelectEvent); //Run the event that was selected.
    }

これがお役に立てば幸いです、コメントでこのコードの改善を提案してください、ありがとう!


1
この再重み付けスキームは、イベントを同等の確率にする傾向があります。ウェイトを定期的にリセットすることは、実際には、10回に1回のロールでリウェイトのメリットがまったく得られないようにすることで、悪化の程度を制限する単なるバンドエイドです。また、1つのアルゴリズムに関する注意:ランダム選択を行うために100エントリのテーブルを埋めることにより、多くの作業を無駄にしています。代わりに、ランダムロールを生成し、4つの結果を反復処理して、進行する確率を合計できます。ロールが合計より少なくなるとすぐに、結果が出ます。テーブルへの入力は不要です。
DMGregory

3

mklingenの答えを少し一般化させてください。基本的には、ギャンブラーの誤 implementを実装する必要がありますが、ここではより一般的な方法を提供します。

n確率のある可能性のあるイベントがあるとしましょうp_1, p_2, ..., p_n。イベントは、ときにi起こった、その確率は係数で再スケールばならない0≤a_i≤1/p_i(持っている必要があり、後者は重要であるそうでなければ、1よりも確率も大きく、他のイベントで終わる負の確率、基本的には平均「かかわらず、」 -イベント。か何かを)通常a_i<1。たとえばa_i=p_i、を選択できます。これは、2回目に発生するイベントの確率が、イベントが連続して正確に2回発生する元の確率であることを意味します。たとえば、2回目のコイントスの確率は1/2ではなく1/4です。一方、a_i>1「運/不運の一撃」を引き起こすことを意味する、いくつかのを持つこともできます。

他のすべてのイベントは、相互に同等の確率を維持する必要がありますb_i。つまり、すべての確率の合計が1になるように、同じ要因ですべてのスケールを変更する必要があります。

1 = a_i*p_i + b_i*(1-p_i)  # Σ_{j≠i) p_j  = 1 - p_i
 b_i = (1 - a_i*p_i) / (1 - p_i).   (1)

これまでのところ、とても簡単です。しかし、別の要件を追加しましょう。2つのイベントのすべての可能なシーケンスを考慮して、そこから抽出された単一イベントの確率が元の確率でなければなりません。

させて

        / p_i * b_i * p_j  (ji)
p_ij = <
        \ a_i * (p_i     (j=i)

は、イベントのj後にイベントが発生する確率を示し、iそれ以外のp_ij≠p_ji場合b_i=b_j (2)(が(1)暗示するa_j = 1 - a_i*p_i + (1-a_i)*p_i/p_j)に注意してください。これは、ベイズの定理が要求するものでもあり、これはまた、

Σ_j p_ij = p_i * b_i * (1 - p_i) + a_i * (p_i
         = b_i * p_i + (a_i - b_i) * (p_i
         = p_i  # using (1)

望みどおり。これa_iが他のすべてを修正することを意味することに注意してください。


次に、この手順を複数回適用した場合、つまり3つ以上のイベントのシーケンスに対して何が起こるかを見てみましょう。3番目のイベントの不正確率の選択には、基本的に2つのオプションがあります。

a)最初のイベントとリグを忘れて、2番目のイベントのみが発生したかのように。

         / p_ij * a_j * p_j  (j=k)
p_ijk = <
         \ p_ij * b_j * p_l  (jk)

これは、たとえばp_jik≠p_ikjほとんどの場合、通常、ベイズに違反することに注意してください。

b)次に発生するイベントの新しい確率を取得する新しい確率として、p_ij(fixedのi)確率を使用しpi_jます。を変更するかどうかはあなた次第ですが、変更されたために新しいものは間違いなく異なることに注意してください。この場合も、同じ確率で発生するすべての順列を要求することにより、おそらく選択が制限されます。どれどれ...pi_jkkai_jbi_jpi_jai_jijk

         / p_ij * bi_j * pi_k  (jk)
p_ijk = <
         \ (p_ij * ai_j      (j=k)

         / b_i * bi_j * p_i * p_j * pi_k  (ijki)
         | b_i * ai_j * p_i * (p_j      (ij=k)
      = <  a_i * (p_i * bi_i * pi_k     (i=jk)
         | b_i * p_i * bi_j * p_k * pi_i  (i=kj)
         \ a_i * ai_i * (p_i * pi_i     (i=k=j)

およびその循環順列。それぞれの場合に等しくなければなりません。

私はこれに関する私の継続がしばらく待たなければならないのではないかと心配しています...


これを経験的にテストすると、多くの実行で入力確率から離れた歪みが発生します。たとえば、a_i / p_i = 0.5の場合(およびmklingenの回答からの数値を使用)、60%の入力ミス率は50.1%の観測率になり、10%の入力クリティカル率は13.8%として観測されます。結果の遷移行列を高出力にすることでこれを確認できます。a_i:p_iの比率を1に近い値にすると、歪みは少なくなりますが、実行を減らす効果は低くなります。
DMGregory

@DMGregoryの良い点:遷移行列の力を単純に利用することはできません。それについては後で答えを拡大します
トビアスキーンズラー

/:@DMGregory私は(変法b))完全なプロセスを記述し始めたが、それはかなり退屈取得し、私は時間に、現在の短いんだ
トビアスKienzlerを

1

最良の選択肢は、ランダムに重み付けされたアイテム選択を使用することだと思います。C#の実装はここにありますが、他の言語でも簡単に見つけることができます。

アイデアは、選択されるたびにオプションの重量を減らし、選択されないたびにオプションの重量を増やすことです。

たとえば、選択したオプションNumOptions-1の重みを1つ減らし、他のすべてのオプションの重みを1ずつ増やした場合(重みが0未満のアイテムを削除し、0を超えたときにそれらを読み込む)、すべてのオプションがおよそ選択されます長期間にわたって同じ回数ですが、最近選択されたオプションは選択される可能性がはるかに低くなります。


他の多くの回答で示唆されているように、ランダムな順序を使用する場合の問題は、1つを除いてすべてのオプションが選択された後、次に選択されるオプションを100%確実に予測できることです。それはあまりランダムではありません。


1

私の答えは間違っていて、テストに欠陥がありました。

この設計の欠陥を指摘する議論とコメントのためにこの回答を残していますが、実際のテストは間違っていました。

探しているのは加重ウェイトです。4つの可能な結果のウェイトは、全体の適切なウェイトを維持したまま、以前の結果によってさらに調整(加重)する必要があります。

これを実現する最も簡単な方法は、ロールされる特定の値の重みを減らし、他の重みを増やすことにより、各ロールのすべての重み変更することです。

例として、Fumble、Miss、Hit、Critの4つのウェイトがあるとしましょう。また、それらに必要な全体の重みは、Fumble = 10%、Miss = 50%、Hit = 30%、Crit = 10%であるとします。

乱数ジェネレーター(RNG)を使用して1から100の値を生成し、その値をこの範囲内の値と比較した場合(1-10ファンブル、11-60ミス、61-90ヒット、91-100クリティカル)、個々のロールを生成しています。

そのロールを行うときに、ロールされた値に基づいてそれらの範囲をすぐに調整する場合、将来のロールに重み付けすることになりますが、他のウェイトを増やすのと同じ合計量だけロールされたウェイトを減らす必要もあります。したがって、上記の例では、ロールされたウェイトを3減らし、他のウェイトをそれぞれ1ずつ増やします。

各ロールに対してこれを行うと、縞模様の可能性が依然としてありますが、各ロールについて、将来のロールが現在のロール以外のものになる可能性が増加するため、それらは大幅に減少します。この効果を大きくすることで、重みをより大きな係数で増加/減少させることにより(たとえば、電流を6減らし、他を2増やす)、ストリークの可能性をさらに減らすことができます。

このアプローチを検証するために簡単なアプリを実行し、それらの重みで32000回繰り返した後、次のグラフを作成します。上のグラフは各ロールでの4つの重みの即時値を示し、下のグラフはそのポイントまでロールアップされた各タイプの結果の合計数を示します。

ご覧のとおり、重みは目的の値を中心にわずかに変動しますが、全体の重みは目的の範囲内にとどまり、最初のさまざまな開始数が落ち着いた後、結果は目的の割合にほぼ完全に適合します。

この例は.NET System.Randomクラスを使用して作成されていることに注意してください。これは実際には優れたRNGの1つではないため、より優れたRNGを使用することでより正確な結果を得ることができます。また、このツールでグラフ化できる最大の結果は32000でしたが、私のテストツールは同じ全体パターンで5億件以上の結果を生成することができました。


これは、最近使用したウェイトではなく、元のウェイトに対して+ 1 / -3が適用される場合にのみ機能することに注意してください。(このように均一に重みを継続的に変更すると、重みが均等になる傾向があります)。これにより、長期的には確率が目標どおりに維持されますが、実行を減らすことはほとんどありません。一度見逃したことを考えると、このスキームではさらに2回見逃す可能性が22%であるのに対し、独立したドローでは25%です。大きな効果(+ 3 / -9など)の重みシフトを増やすと、長期的な確率にバイアスがかかります。
DMGregory

実際、上記のデータは、ロールが処理されるたびに、最新の重量に+ 1 / -3を適用しています。したがって、最初の50%の重量で一度見逃した場合、次の見逃した重量は47%になり、もう一度見逃した場合、次の重量は44%になります。これは実行を削減します(個別のメトリックは実行を追跡し、実行が24%減少します)が、このスキームには4つの重みのそれぞれがゼロ以外の確率で残る可能性が高いため、依然として避けられません(例えば、4つの連続したクリティカルが発生すると、クリティカルウェイトがゼロになる可能性があります。
デビッドCエリス

それがあなたの意図であれば、実装にはバグがあります。グラフを見てください-ファンブルウェイトは7から11の間でのみ跳ね返り、それ以外の値はありません。記述した連続修正を使用してシミュレーションを実行しましたが、グラフは大きく異なり、最初の100回の試行で各状態の確率がそれぞれ25%に収束しました。
DMGregory

ダンギット、あなたが指摘したように確かにそれは盗聴されました。さて、この答えを打つ。
デビッドCエリス

@DavidCEllisは、実装に欠陥があると言っているのですか、それともアイデアそのものですか?私のナプキンの直観は、おおよそあなたが説明するモデル(描画時に確率を調整し、徐々にすべての確率を元の値に復元する)になりましたが、それはまだ理にかなっています。
dimo414

0

本質的にフィルターであることができます。過去nイベントを追跡します。確率は、それらのイベントに適用されるフィルターの一部です。0番目のフィルターはベース確率で、0の場合は回避し、1の場合は失敗します。ベースが25%で、フィルターが各反復の半分に減少するとします。フィルタは次のようになります。

[.25 .125 .0625 .03125] 

ご希望であれば、お気軽に続けてください。このスキームの全体的な確率は、0.25の基本確率よりわずかに高くなります。実際、同じスキームが与えられた場合の確率は(xを実際の確率と呼び、pは確率入力です):

x=p+(1-x)*(p/2+p/4+p/8)

xを解くと、答えがp(1+1/2+1/4+1/8)/(1+p(1/2+1/4+1/8)であることがわかりx=0.38461538461ます。しかし、本当に欲しいのは、xが与えられたときにpを見つけることです。それはより難しい問題であることが判明しました。無限フィルターを想定した場合、問題はx+x*p=2*p、またはになりp=x/(2-x)ます。したがって、フィルターを増やすと、平均して同じ結果が得られる数値pについて解くことができますが、その割合は最近の成功率に依存します。

基本的に、以前の値を使用して、このラウンドの許容しきい値を決定し、ランダムな値を取得します。次に、フィルターを指定して次のランダム値を生成します。


-1

あなた自身が提案したように、これへのアプローチの1つは、重み付きランダムを実装することです。考え方は、重みと結果を変更できる乱数(または結果)ジェネレーターを作成することです。

これがJavaでの実装です。

import java.util.Map;
import java.util.Random;

/**
 * A psuedorandom weighted outcome generator
 * @param <E> object type to return
 */
public class WeightedRandom<E> {

    private Random random;
    private Map<E, Double> weights;

    public WeightedRandom(Map<E, Double> weights) {
        this.random = new Random();
        this.weights = weights;
    }

    /**
     * Returns a random outcome based on the weight of the outcomes
     * @return
     */
    public E nextOutcome() {
        double totalweight = 0;

        // determine the total weigth
        for (double w : weights.values()) totalweight += w;

        // determine a value between 0.0 and the total weight
        double remaining = random.nextDouble() * totalweight;

        for (E entry : weights.keySet()) {
            // subtract the weight of this entry
            remaining -= weights.get(entry);

            // if the remaining is smaller than 0, return this entry
            if (remaining <= 0) return entry;
        }

        return null;
    }

    /**
     * Returns the weight of an outcome
     * @param outcome the outcome to query
     * @return the weight of the outcome, if it exists
     */
    public double getWeight(E outcome) {
        return weights.get(outcome);
    }

    /**
     * Sets the weight of an outcome
     * @param outcome the outcome to change
     * @param weight the new weigth
     */
    public void setWeight(E outcome, double weight) {
        weights.put(outcome, weight);
    }
}

編集 重みを自動的に調整する場合、たとえば、結果がBだったときにAの可能性を増やします。

  1. nextOutcome()メソッドの動作を変更し、結果に応じて重みを変更します
  2. setWeight()結果に応じて重みを変更するために使用します。

あなたは質問を誤解したかもしれないと思います:OPは重み付けされたランダムな結果を生成する方法を求めているのではなく、同じ結果が連続して数回発生する可能性を減らすために重みを調整する方法を求めています。
イルマリカロネン

なるほど、このシステムを使用してそれがどのように可能になるかを説明するために、回答の一部を変更しました。
エリクガル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.