Seperti yang saya sebutkan di komentar saya di atas, saya sarankan Anda profil ini sebelum terlalu rumit kode Anda. for
Dadu penjumlahan putaran cepat jauh lebih mudah untuk dipahami dan dimodifikasi daripada rumus matematika yang rumit dan pembuatan / pencarian tabel. Selalu profil dulu untuk memastikan Anda memecahkan masalah penting. ;)
Yang mengatakan, ada dua cara utama untuk sampel distribusi probabilitas canggih dalam satu gerakan:
1. Distribusi Probabilitas Kumulatif
Ada trik yang rapi untuk mengambil sampel dari distribusi probabilitas berkesinambungan dengan hanya menggunakan satu input acak seragam . Ini berkaitan dengan distribusi kumulatif , fungsi yang menjawab "Berapa probabilitas mendapatkan nilai yang tidak lebih besar dari x?"
Fungsi ini tidak menurun, mulai dari 0 dan naik ke 1 di atas domainnya. Contoh untuk jumlah dua dadu enam sisi ditunjukkan di bawah ini:
Jika fungsi distribusi kumulatif Anda memiliki invers yang mudah dihitung (atau Anda dapat memperkirakannya dengan fungsi piecewise seperti kurva Bézier), Anda dapat menggunakan ini untuk mengambil sampel dari fungsi probabilitas asli.
Fungsi invers menangani pembagian domain antara 0 dan 1 ke dalam interval yang dipetakan ke setiap output dari proses acak asli, dengan area tangkapan masing-masing sesuai dengan probabilitas aslinya. (Ini benar sangat kecil untuk distribusi kontinu. Untuk distribusi diskrit seperti gulungan dadu kita perlu menerapkan pembulatan yang cermat)
Berikut ini contoh penggunaan ini untuk meniru 2d6:
int SimRoll2d6()
{
// Get a random input in the half-open interval [0, 1).
float t = Random.Range(0f, 1f);
float v;
// Piecewise inverse calculated by hand. ;)
if(t <= 0.5f)
{
v = (1f + sqrt(1f + 288f * t)) * 0.5f;
}
else
{
v = (25f - sqrt(289f - 288f * t)) * 0.5f;
}
return floor(v + 1);
}
Bandingkan ini dengan:
int NaiveRollNd6(int n)
{
int sum = 0;
for(int i = 0; i < n; i++)
sum += Random.Range(1, 7); // I'm used to Range never returning its max
return sum;
}
Lihat apa yang saya maksud tentang perbedaan dalam kejelasan dan fleksibilitas kode? Cara naif mungkin naif dengan loop-nya, tetapi pendek dan sederhana, segera jelas tentang apa yang dilakukannya, dan mudah untuk skala ke berbagai ukuran dan angka die. Membuat perubahan pada kode distribusi kumulatif membutuhkan beberapa matematika non-sepele, dan akan mudah untuk istirahat dan menyebabkan hasil yang tidak terduga tanpa kesalahan yang jelas. (Yang saya harap saya belum membuat di atas)
Jadi, sebelum Anda menghapus lingkaran yang jelas, pastikan benar-benar masalah kinerja yang layak untuk pengorbanan semacam ini.
2. Metode Alias
Metode distribusi kumulatif bekerja dengan baik ketika Anda dapat mengekspresikan kebalikan dari fungsi distribusi kumulatif sebagai ekspresi matematika sederhana, tetapi itu tidak selalu mudah atau bahkan mungkin. Alternatif yang andal untuk distribusi diskrit adalah sesuatu yang disebut Metode Alias .
Ini memungkinkan Anda mengambil sampel dari distribusi probabilitas diskrit arbitrer apa pun dengan menggunakan hanya dua input acak independen yang didistribusikan secara seragam.
Ini bekerja dengan mengambil distribusi seperti di bawah ini di sebelah kiri (jangan khawatir bahwa area / bobot tidak berjumlah 1, untuk Metode Alias kita peduli dengan berat relatif ) dan mengubahnya menjadi tabel seperti yang ada di tempat yang tepat:
- Ada satu kolom untuk setiap hasil.
- Setiap kolom dibagi menjadi paling banyak dua bagian, masing-masing terkait dengan salah satu hasil asli.
- Area / berat relatif dari setiap hasil dipertahankan.
(Diagram berdasarkan gambar dari artikel hebat ini tentang metode pengambilan sampel )
Dalam kode, kami menyatakan ini dengan dua tabel (atau tabel objek dengan dua properti) yang mewakili probabilitas untuk memilih hasil alternatif dari setiap kolom, dan identitas (atau "alias") dari hasil alternatif tersebut. Maka kita dapat sampel dari distribusi seperti:
int SampleFromTables(float[] probabiltyTable, int[] aliasTable)
{
int column = Random.Range(0, probabilityTable.Length);
float p = Random.Range(0f, 1f);
if(p < probabilityTable[column])
{
return column;
}
else
{
return aliasTable[column];
}
}
Ini melibatkan sedikit pengaturan:
Hitung probabilitas relatif setiap hasil yang mungkin (jadi jika Anda memutar 1000d6, kita perlu menghitung jumlah cara untuk mendapatkan setiap jumlah dari 1000 hingga 6000)
Buat sepasang tabel dengan entri untuk setiap hasil. Metode lengkap melampaui lingkup jawaban ini, jadi saya sangat merekomendasikan merujuk pada penjelasan tentang algoritma Metode Alias ini .
Simpan tabel-tabel itu dan lihat kembali setiap kali Anda membutuhkan die roll acak baru dari distribusi ini.
Ini adalah pertukaran ruang-waktu . Langkah precomputation agak melelahkan, dan kita perlu menyisihkan memori sebanding dengan jumlah hasil yang kita miliki (meskipun bahkan untuk 1000d6, kita berbicara kilobyte satu digit, sehingga tidak ada yang kurang tidur), tetapi sebagai gantinya pengambilan sampel kami adalah waktu yang konstan tidak peduli seberapa rumit distribusi kami.
Saya berharap satu atau yang lain dari metode-metode itu dapat berguna (atau bahwa saya telah meyakinkan Anda bahwa kesederhanaan metode naif sepadan dengan waktu yang diperlukan untuk mengulangi);)