乱数生成で「あまりにも」ラッキー/アンラッキーなストリークを回避するにはどうすればよいですか?


30

私は現在、プレイヤーが与えるダメージに常に0.8から1.2の間のランダムな係数を掛けるマルチプレイヤー戦闘システムを扱っています。

理論的には、真にランダムなRNGは、最終的に同じ数を何度も返す可能性があります(テトリスのジレンマを参照)。これにより、プレイヤーが常に非常に高いダメージを与え、他のプレイヤーが常に非常に低いダメージを与えるマッチになります。

これが起こらないようにするにはどうすればよいですか?一部のRNGは、繰り返しを避ける点で他のRNGよりも優れていますか?


これがどのように機能するかわかりません。もちろん、x1、x2、x3、x4のシーケンスを取得します。ここで、xはすべて大きいです。それはただランダムではありませんか?
共産主義のカモ

回答:


26

ダメージ結果の事前設定リストを作成してシャッフルすることで、テトリスと同じ方法で解決できます。

プレイヤーが線形分布で0.8倍から1.2倍のダメージを与えることを知っているとしましょう。リスト[0.8、0.9、1.0、1.1、1.2]を取得します。ランダムシャッフルしますので、例えば[1.2、1.0、0.8、0.9、1.1]を取得します。

プレイヤーが初めてダメージを与えるとき、彼らは1.2xを与えます。その後、1x。その後、等、1.1x。配列が空の場合のみ、新しい配列を生成してシャッフルする必要があります。

実際には、一度に4つ以上の配列に対してこれを行うことができます(たとえば、[0.8,0.8,0.8,0.8,0.9,0.9,0.9,0.9、...]で開始します)。それ以外の場合、シーケンスの期間は十分に短く、プレイヤーは次のヒットが「良い」かどうかを判断できます。(ただし、ドラゴンクエストIXのHoimiテーブルのように、戦闘により多くの戦略を追加することもできます。


3
もう少しランダムにするには、リストの半分を常に乱数として使用し、残りの半分を(2-x)として計算して平均を正しくすることができます。
アダム

2
@Adam:この方法は、この特定の例でのみ有効です。ダメージマルチプライヤーではなくテトリスのピースを扱う場合、2-Sブロックとは何ですか?

6
これに対する通常の用語は、一種のシステムであり、「置換なしのランダム」です。実際、サイコロの代わりにカードのデッキを使用するのと似ています。
キロタン

さらに良いことに、あなたは半分の数字を本当にランダムに行うことができ、そのうちの半分だけがこのルールの対象となります。
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%に貢献し、値の50%に貢献することに注意してください1。さらに少し:

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.6から0.5」(つまり「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つのリスクは、各結果が以前の結果と大幅に逆相関することですが、この相関は時間とともに減少します。
キロタン

1
私はこれを変更して、代わりに「漏れやすい積分器」システムを使用するように誘惑されます:0.5に初期化された平均から開始し、サンプルをカウントする代わりに、増分されない任意の定数値(例えば10、20、50、または100)を選択します。その場合、少なくとも2つの後続の値の間の相関は、ジェネレーターの使用全体を通じて一定です。また、定数値を微調整することもできます-値が大きいほど、補正が遅くなり、ランダム性が明らかになります。
キロタン

@Kylotanありがとう、名前を提供してくれてありがとう。2番目のコメントで何を意味するのか正確にはわかりません。新しい答えを提供してください。
ジョナサンディキンソン

それは非常に賢く、配列の制限はありません。最初からsamples最大値(この場合は100)で初期化するというKylotanの提案を理解しています。そうすれば、RNGが安定するのに99回の反復は必要ありません。いずれにせよ、この方法で見られる欠点の1つは、公平性を保証せず、単に一定の平均を保証することです。
ユーザーが

@jSepia-確かに、あなたはまだ公平/不公平の実行を取得しますが、それらの後に(通常)バランスの取れた実行が続きます。たとえば、ユニットテストでは、100個の非プロセスを「強制」し、実際のサンプルを実行したときに〜60個のプロセスに遭遇しました。影響を受けていない状況では(コードを見ると)、通常、50%のプロシージャは、最悪の場合、どちらの方向でも2/3の実行を確認します。しかし、1人のプレイヤーが他のプレイヤーを倒すことができるように走ることができます。より公平にバイアスしたい場合:total = (average / 2) + desired
ジョナサンディキンソン

3

あなたが求めているのは、実際にはほとんどのPRNGの反対であり、非線形分布です。1.0xを超えるすべてが何らかの「クリティカルヒット」であると仮定して、ある種の減少リターンロジックをルールに入れて、各ラウンドでクリティカルが発生する可能性がXになるまで、そのポイントがYにリセットされます。その後、各ラウンドで2つのロールを実行します。


1
これは私がとる一般的なアプローチです。RNGの均一な分布を使用しますが、変換します。RNGの出力を、最近の履歴に基づいて再調整する独自のカスタム分布への入力として使用することもできます。つまり、出力の分散を強制し、人間の知覚用語で「よりランダム」に見えます。
マイケル

3
私は実際にこのようなことを行うMMOを知っていますが、クリティカルの可能性は実際に取得するたびに増加し、その後取得しないと非常に低い値にリセットされます。これは、プレイヤーにとって非常に満足のいくクリティカルのまれな縞につながります。
-coderanger

良い藻のように聞こえますが、長く乾いた呪文は常にイライラさせられますが、それは狂気の狂気の筋につながりません。
マイケル

2
これを修正するには、非線形分布は必要ありません。分布の短い時系列のサブセットが、分布自体と同じプロパティを持っていることだけが必要です。

これがBlizzardゲームのやり方です
。Warcraft3

2

Sid Meierは、このトピックとCivilizationゲームについてGDC 2010で素晴らしい講演を行いました。後でリンクを見つけて貼り付けようとします。本質的に-知覚されるランダム性は、真のランダム性とは異なります。物事を公平に感じるためには、以前の結果を分析し、プレイヤーの心理に注意を払う必要があります。

すべてのコストで不運の連続を避けます(前の2ターンが不運だった場合、次のターンは運が良いと保証されます)。プレイヤーは常にAIの対戦相手よりも運がいいはずです。


0

シフトバイアスを使用する

01rb0

全体的な分布は、次の式によってバイアスされます。

rexp(b)

b1b0

この数値を取得し、目的の範囲に適切にスケーリングします。

プレイヤーが好転するたびに、バイアスから差し引く。プレイヤーが不利に振るたびに、バイアスを追加します。変更された量は、ロールがどの程度(不)好ましいかによってスケーリングされるか、フラット量(または組み合わせ)になる可能性があります。特定の値を調整して、目的の感触に合わせる必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.