Attack vs Defense dan siapa pemenangnya? [Tutup]

12

Saya sedang dalam proses membuat game sederhana baru di ponsel dan saya telah menghabiskan beberapa hari di bagian berikut.

Untuk kesederhanaan, katakanlah saya punya dua pejuang. Satu-satunya atribut dari mereka adalah Serangan dan Pertahanan. Saat serangan pertama, satu-satunya hal yang penting adalah serangannya dan pertahanan lawan. Dan sebaliknya.

Mereka tidak memiliki peralatan, barang, stamina atau kesehatan. Hanya Serang vs Pertahanan.

Contoh:

  • Petarung 1:

    Serangan: 50, Pertahanan: 35

  • Petarung 2:

    Attack 20, Defense: 80

Proses pertarungan akan menjadi hanya satu serangan yang akan menentukan pemenang. Jadi, tidak ada banyak serangan atau putaran. Saya tidak ingin membuatnya menjadi deterministik, tetapi tambahkan versi ringan yang tidak terduga. Seorang pejuang dengan serangan yang lebih rendah akan dapat memenangkan pejuang lain dengan pertahanan yang lebih tinggi (tapi tentu saja tidak setiap waktu)

Ide pertama saya adalah membuatnya linier dan memanggil generator nomor acak yang seragam.

If Random() < att1 / (att1 + def2) {
    winner = fighter1
} else {
    winner = fighter2
} 

Contoh dengan serangan 50 dan pertahanan 80, pejuang penyerang akan memiliki sekitar 38% untuk menang. Namun, menurut saya hal yang tak terduga itu terlalu jauh dan petarung terburuk akan menang banyak.

Saya bertanya-tanya bagaimana Anda bekerja dalam situasi yang sama.

PS Saya mencari banyak di QnA ini dan sumber lain dan saya menemukan pertanyaan serupa yang disebut terlalu luas untuk SE. Tetapi mereka memiliki banyak atribut, senjata, barang, kelas dll yang bisa membuatnya terlalu rumit. Saya pikir versi saya jauh lebih sederhana agar pas dengan gaya QnA dari SE.

Tasos
sumber
1
Kasus apa yang Anda cari? Berapa rentang nilai untuk serangan dan pertahanan yang Anda lihat dan haruskah dua angka dalam rentang itu memiliki hasil yang pasti? Misalnya, dapatkah seorang pejuang dengan serangan 10 mengalahkan seorang pejuang di pertahanan 90?
Niels
@ user2645227 Saya dapat mengatakan bahwa jangkauannya antara 1 dan 400. Tidak, saya tidak ingin memiliki keputusan deterministik dan memberikan kemungkinan untuk menyerang 1 memenangkan pertahanan 400, tetapi dalam kasus yang sangat jarang.
Tasos
1
Jadi jika Anda menggunakan Att (min) -def (max) dan Att (max) -def (min) yang memberi Anda kisaran 800 dari -400 hingga +400. Anda ingin rentang acak Anda mencakup seluruh rentang. Defense - Attack akan memberi Anda margin scaling dalam bentuk ambang yang harus Anda tekan untuk menang. Ini harus mengurangi keacakan sedikit. Untuk lebih memusatkan hasil, Anda dapat menggunakan contoh Philipps atau bermain-main di anydice sampai Anda menekan kurva yang Anda cari.
Niels

Jawaban:

24

Jika Anda ingin hasil pertarungan Anda lebih dapat diprediksi tetapi tidak sepenuhnya deterministik, miliki sistem terbaik .

Ulangi waktu pertarungan n(di mana nharus angka yang tidak rata) dan nyatakan pejuang sebagai pemenang yang menang lebih sering. Semakin besar nilai Anda untuk nkemenangan dan kerugian yang tidak terlalu mengejutkan yang akan Anda miliki.

const int FIGHT_REPETITONS = 5 // best 3 of 5. Adjust to taste.

int fighter1wins = 0;
int fighter2wins = 0;

for (int i = 0; I < FIGHT_REPETITONS; I++) {

    If (Random() < att1 / (att1 + def2)) {
        fighter1wins++;
    } else {
        fighter2wins++;
    } 

}

If (fighter1wins > fighter2wins) {
    winner = fighter1
} else {
    winner = fighter2
} 

Sistem ini hanya bekerja dalam kasus khusus di mana perkelahian adalah hasil biner sederhana dari menang atau kalah. Ketika pertempuran memiliki hasil yang lebih kompleks, seperti ketika pemenang masih kehilangan beberapa poin hit tergantung pada seberapa dekat kemenangan itu, pendekatan ini tidak berfungsi lagi. Solusi yang lebih umum adalah mengubah cara Anda menghasilkan angka acak. Saat Anda menghasilkan beberapa angka acak dan kemudian mengambil rata-rata, hasilnya akan mengelompok di dekat pusat rentang dan hasil yang lebih ekstrem akan lebih jarang. Sebagai contoh:

double averagedRandom3() {
    return (Random() + Random() + Random()) / 3.0;
}

akan memiliki kurva distribusi seperti ini:

Distribusi 3d20 / 3

(Gambar milik anydice - alat yang sangat berguna untuk merancang rumus mekanik permainan yang melibatkan keacakan, bukan hanya untuk permainan meja)

Dalam proyek saya saat ini, saya menggunakan fungsi pembantu yang memungkinkan untuk mengatur ukuran sampel sewenang-wenang:

double averagedRandom(int averageness) {
     double result = 0.0;
     for (var i = 0; i < averageness; i++) {
         result += Random();
     }
     return result / (double)averageness;
}
Philipp
sumber
Tampaknya pendekatan yang lebih baik. Satu pertanyaan. Dalam fungsi averagedRandom3 (), haruskah Anda menggunakan +alih-alih *atau saya salah mengerti apa fungsinya?
Tasos
@ Taco ya, seharusnya +, bukan *. Saya juga memiliki fungsi acak yang mengalikan banyak sampel. Ini memberi Anda fungsi angka acak dengan bias kuat untuk nilai yang lebih rendah, yang juga dapat berguna dalam beberapa situasi.
Philipp
1
Saya akan membuka pertanyaan selama 1-2 hari dan jika saya tidak memiliki jawaban lain, saya akan memilih pertanyaan Anda. Saya telah membatalkannya tetapi ingin memberikan kesempatan untuk jawaban lain juga jika Anda tidak keberatan.
Tasos
Saya pikir jawaban ini sudah mendapatkan cukup suara yang membuat jawaban ini memenuhi syarat untuk menandainya sebagai jawaban: P
Hamza Hasan
1
Saya juga akan penasaran jika beberapa orang datang dengan pendekatan alternatif. Satu orang menurunkan jawaban ini. Mungkin mereka ingin memberikan alternatif.
Philipp
8

Ini adalah apa yang saya gunakan untuk menentukan pemenang pertempuran di applet Lords of Conquest Imitator saya. Dalam game ini, mirip dengan situasi Anda, hanya ada nilai serangan dan nilai pertahanan. Probabilitas bahwa penyerang menang adalah lebih besar semakin banyak poin yang dimiliki penyerang, dan semakin sedikit poin yang dimiliki oleh penyerang, dengan nilai yang sama dievaluasi dengan peluang 50% dari serangan yang berhasil.

Algoritma

  1. Balikkan koin acak.

    1a. Kepala: pertahanan kehilangan satu poin.

    1b. Ekor: kepala kehilangan satu poin.

  2. Jika pertahanan dan penyerang masih memiliki poin, kembali ke langkah 1.

  3. Siapa pun yang turun ke 0 poin kalah dalam pertempuran.

    3a. Penyerang ke 0: Serangan gagal.

    3b. Pertahanan ke 0: Serangan berhasil.

Saya menulisnya di Jawa, tetapi harus mudah diterjemahkan ke bahasa lain.

Random rnd = new Random();
while (att > 0 && def > 0)
{
    if (rnd.nextDouble() < 0.5)
        def--;
    else
        att--;
}
boolean attackSucceeds = att > 0;

Sebuah contoh

Misalnya, katakanlah att = 2 dan def = 2, hanya untuk memastikan bahwa probabilitasnya adalah 50%.

Pertempuran akan ditentukan dalam jumlah maksimum n = att + def - 1koin membalik, atau 3 dalam contoh ini (pada dasarnya ini adalah yang terbaik dari 3 di sini). Ada 2 n kemungkinan kombinasi membalik koin. Di sini, "W" berarti penyerang memenangkan flip koin, dan "L" berarti penyerang kehilangan flip koin.

L,L,L - Attacker loses
L,L,W - Attacker loses
L,W,L - Attacker loses
L,W,W - Attacker wins
W,L,L - Attacker loses
W,L,W - Attacker wins
W,W,L - Attacker wins
W,W,W - Attacker wins

Penyerang menang di 4/8, atau 50% dari kasus.

Matematika

Probabilitas matematika yang timbul dari algoritma sederhana ini lebih rumit daripada algoritma itu sendiri.

Jumlah kombinasi di mana tepatnya x Ls diberikan oleh fungsi kombinasi:

C(n, x) = n! / (x! * (n - x)!)

Penyerang menang ketika ada di antara 0dan att - 1Ls. Jumlah kombinasi pemenang sama dengan jumlah kombinasi dari 0through att - 1, distribusi binomial kumulatif:

    (att - 1)
w =     Σ     C(n, x)
      x = 0

Probabilitas penyerang menang adalah w dibagi 2 n , probabilitas binomial kumulatif:

p = w / 2^n

Berikut adalah kode di Jawa untuk menghitung probabilitas ini untuk nilai arbitrer attdan def:

/**
 * Returns the probability of the attacker winning.
 * @param att The attacker's points.
 * @param def The defense's points.
 * @return The probability of the attacker winning, between 0.0 and 1.0.
 */
public static double probWin(int att, int def)
{
    long w = 0;
    int n = att + def - 1;
    if (n < 0)
        return Double.NaN;
    for (int i = 0; i < att; i++)
        w += combination(n, i);

    return (double) w / (1 << n);
}

/**
 * Computes C(n, k) = n! / (k! * (n - k)!)
 * @param n The number of possibilities.
 * @param k The number of choices.
 * @return The combination.
 */
public static long combination(int n, int k)
{
    long c = 1;
    for (long i = n; i > n - k; i--)
        c *= i;
    for (long i = 2; i <= k; i++)
        c /= i;
    return c;
}

Kode pengujian:

public static void main(String[] args)
{
    for (int n = 0; n < 10; n++)
        for (int k = 0; k <= n; k++)
            System.out.println("C(" + n + ", " + k + ") = " + combination(n, k));

    for (int att = 0; att < 5; att++)
        for (int def = 0; def < 10; def++)
            System.out.println("att: " + att + ", def: " + def + "; prob: " + probWin(att, def));
}

Keluaran:

att: 0, def: 0; prob: NaN
att: 0, def: 1; prob: 0.0
att: 0, def: 2; prob: 0.0
att: 0, def: 3; prob: 0.0
att: 0, def: 4; prob: 0.0
att: 1, def: 0; prob: 1.0
att: 1, def: 1; prob: 0.5
att: 1, def: 2; prob: 0.25
att: 1, def: 3; prob: 0.125
att: 1, def: 4; prob: 0.0625
att: 1, def: 5; prob: 0.03125
att: 2, def: 0; prob: 1.0
att: 2, def: 1; prob: 0.75
att: 2, def: 2; prob: 0.5
att: 2, def: 3; prob: 0.3125
att: 2, def: 4; prob: 0.1875
att: 2, def: 5; prob: 0.109375
att: 2, def: 6; prob: 0.0625
att: 3, def: 0; prob: 1.0
att: 3, def: 1; prob: 0.875
att: 3, def: 2; prob: 0.6875
att: 3, def: 3; prob: 0.5
att: 3, def: 4; prob: 0.34375
att: 3, def: 5; prob: 0.2265625
att: 3, def: 6; prob: 0.14453125
att: 3, def: 7; prob: 0.08984375
att: 4, def: 0; prob: 1.0
att: 4, def: 1; prob: 0.9375
att: 4, def: 2; prob: 0.8125
att: 4, def: 3; prob: 0.65625
att: 4, def: 4; prob: 0.5
att: 4, def: 5; prob: 0.36328125
att: 4, def: 6; prob: 0.25390625
att: 4, def: 7; prob: 0.171875
att: 4, def: 8; prob: 0.11328125

Pengamatan

Peluangnya adalah 0.0jika penyerang memiliki 0poin, 1.0jika penyerang memiliki poin tetapi pertahanan memiliki 0poin, 0.5jika poinnya sama, kurang dari 0.5jika penyerang memiliki lebih sedikit poin daripada pertahanan, dan lebih besar dari 0.5jika penyerang memiliki lebih banyak poin daripada pertahanan .

Mengambil att = 50dan def = 80, saya perlu beralih ke BigDecimals untuk menghindari overflow, tapi saya mendapatkan probabilitas sekitar 0,0040.

Anda dapat membuat probabilitas lebih dekat ke 0,5 dengan mengubah attnilai menjadi rata-rata attdan defnilai. Att = 50, Def = 80 menjadi (65, 80), yang menghasilkan probabilitas 0,1056.

rgettman
sumber
1
Pendekatan lain yang menarik. Algoritma juga dapat dengan mudah divisualisasikan, yang bisa terlihat sangat menarik.
Philipp
5

Anda dapat memodifikasi serangan dengan nomor acak yang diambil dari distribusi normal. Dengan cara ini sebagian besar hasilnya akan seperti yang Anda harapkan, tetapi kadang-kadang serangan yang lebih tinggi akan kalah melawan pertahanan yang lebih rendah atau serangan yang lebih rendah akan menang melawan pertahanan yang lebih tinggi. Kemungkinan terjadinya ini akan semakin kecil karena perbedaan antara serangan dan pertahanan meningkat.

if (att1 + norm(0, sigma) - def2 > 0) {
  winner = fighter1;
}
else {
  winner = fighter2;
}

Fungsi norm(x0, sigma)mengembalikan pelampung sampel dari distribusi normal yang berpusat di x0, dengan sigma deviasi standar. Sebagian besar bahasa pemrograman menyediakan perpustakaan dengan fungsi seperti itu, tetapi jika Anda ingin membuatnya sendiri lihatlah pertanyaan ini . Anda harus menyesuaikan sigma sedemikian rupa sehingga 'terasa benar', tetapi nilai 10-20 mungkin merupakan tempat yang baik untuk memulai.

Untuk beberapa nilai sigma, probabilitas kemenangan untuk yang diberikan att1 - def2terlihat seperti ini: Kemungkinan menang

rampok
sumber
Mungkin juga layak untuk menunjukkan bahwa nilai-nilai terdistribusi normal tidak memiliki batas yang sebenarnya, jadi ketika menggunakan nilai-nilai acak berdistribusi normal dalam permainan, masuk akal untuk menjepit hasilnya untuk menghindari situasi yang tidak mungkin tetapi bukan tidak mungkin dari nilai-nilai ekstrem yang dihasilkan. mungkin merusak permainan.
Philipp