Attack vs Defense ve kazanan kim? [kapalı]


12

Mobilde yeni bir basit oyun oluşturma sürecindeyim ve bir sonraki gün birkaç gün geçirdim.

Basitleştirmek için iki savaşçım olduğunu varsayalım. Bunların tek özelliği Saldırı ve Savunma. İlk saldırılar olduğunda, önemli olan tek şey ona saldırı ve rakibin savunmasıdır. Ve tam tersi.

Ekipman, eşya, dayanıklılık veya sağlıkları yoktur. Sadece saldırı vs savunma.

Misal:

  • Savaşçı 1:

    Saldırı: 50, Savunma: 35

  • Savaşçı 2:

    Saldırı 20, Savunma: 80

Dövüş süreci kazananı belirleyecek tek bir saldırı olacak. Yani, çoklu saldırı veya mermi yok. Onu deterministik yapmak istemiyorum, ancak beklenmedik bir hafif versiyon ekliyoruz. Daha düşük saldırıya sahip bir savaşçı, daha yüksek savunmaya sahip başka bir savaşçı kazanabilir (ama elbette her zaman değil)

İlk fikrim onu ​​doğrusal hale getirmek ve düzgün bir rasgele sayı üreteci çağırmaktı.

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

50. saldırı ve 80. savunma ile örnek olarak, hücum eden savaşçının kazanması yaklaşık% 38 olacaktır. Ancak bana öyle geliyor ki beklenmedik şeyler çok uzak ve en kötü savaşçılar çok kazanacak.

Benzer durumlar üzerinde nasıl çalıştığını merak ediyordum.

PS Bu QnA ve diğer kaynaklarda çok araştırdım ve SE için çok geniş olarak belirtilen benzer sorular buldum. Ancak bunların çok karmaşık hale getirebilecek birçok özelliği, silahları, eşyaları, sınıfları vb. Benim versiyonumun SE'nin QnA stiline uyması çok daha kolay olduğunu düşünüyorum.


1
Aradığınız durumlar nelerdir? Hangi saldırı ve savunma değerlerine bakıyorsunuz ve bu aralıklardaki iki sayının sabit bir sonucu olmalı mı? Örneğin, saldırı 10'lu bir avcı, savunma 90'daki bir avcı uçağı yenebilir mi?
Niels

@ user2645227 Aralığın 1 ile 400 arasında olduğunu söyleyebilirim. Hayır, herhangi bir deterministik karar almak istemiyorum ve savunma 1'i kazanmak için 1'e saldırma olanağı vermek istemiyorum, ama çok nadir durumlarda.
Tasos

1
Yani Att (min) -def (max) ve Att (max) -def (min) alırsanız, size -400 ila +400 arasında 800 aralığı verir. Rastgele aralığınızın tüm aralığı kapsamasını istersiniz. Savunma - Saldırı, kazanmak için vurmanız gereken bir eşik şeklinde bir ölçeklendirme marjı verecektir. Bu, rastgeleliği biraz azaltmalıdır. Sonuçları daha da merkezileştirmek için, aradığınız eğriye ulaşıncaya kadar Philipps örneğini kullanabilir ya da dilediğiniz yerde dolaşabilirsiniz.
Niels

Yanıtlar:


24

Dövüş sonuçlarınızın daha öngörülebilir olmasını, ancak tamamen deterministik olmamasını istiyorsanız, en iyi n sistemine sahip olun.

Dövüş nsürelerini tekrarlayın ( neşit olmayan bir sayı olmalıdır) ve savaşçıyı daha sık kazanan kazanan ilan edin. Daha naz sürpriz kazanma ve kaybetme değeriniz arttıkça kazanırsınız.

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
} 

Bu sistem sadece kavga kazanmanın veya kaybetmenin basit bir ikili sonucu olduğu özel durumda çalışır. Bir savaş daha karmaşık sonuçlara sahip olduğunda, kazananın ne kadar yakın olduğuna bağlı olarak kazanan hala bazı isabet puanlarını kaybettiği zaman, bu yaklaşım artık işe yaramaz. Daha genel bir çözüm, rastgele sayılar üretme şeklinizi değiştirmektir. Birden çok rasgele sayı oluşturup ortalamayı aldığınızda, sonuçlar aralığın merkezine yakın kümelenecek ve daha uç sonuçlar daha nadir olacaktır. Örneğin:

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

bunun gibi bir dağıtım eğrisi olacaktır:

3d20 / 3 dağılımı

( anydice'in izniyle - sadece masa üstü oyunlar için değil, rasgelelik içeren oyun mekanik formülleri tasarlamak için gerçekten yararlı bir araç)

Şu anki projemde, keyfi bir örneklem büyüklüğü ayarlanmasına yardımcı olan bir yardımcı fonksiyon kullanıyorum:

double averagedRandom(int averageness) {
     double result = 0.0;
     for (var i = 0; i < averageness; i++) {
         result += Random();
     }
     return result / (double)averageness;
}

Daha iyi bir yaklaşım gibi görünüyor. Bir soru. AveragedRandom3 () işlevinde, +bunun yerine kullanmalı *mıyım veya ne anlama geldiğini anlamıyorum?
Tasos

@Tasos evet, + değil * olmalıdır. Ayrıca birden fazla örneği çoğaltan rastgele bir fonksiyon var. Bu, daha düşük değerler için güçlü bir önyargıya sahip, bazı durumlarda da yararlı olabilecek rastgele bir sayı işlevi sağlar.
Philipp

1
Soruyu 1-2 gün açık tutacağım ve başka bir cevabım yoksa, seninkini seçeceğim. Onu iptal ettim ama sakıncası yoksa diğer cevaplara da şans vermek istiyorum.
Tasos

Sanırım bu cevap zaten bu cevabı cevap olarak işaretlemeye uygun hale getiren yeterli oy alıyor: P
Hamza Hasan

1
Bazı insanların alternatif yaklaşımlar geliştirip geliştirmediğini de merak ediyorum. Bir kişi bu cevabı reddetti. Belki de alternatif bir tane sunmak isterler.
Philipp

8

Lords of Conquest Imitator uygulamamda bir savaşın galibini belirlemek için kullandım. Bu oyunda, durumunuza benzer şekilde, sadece bir saldırı değeri ve bir savunma değeri vardır. Saldırganın kazanma olasılığı, saldırganın puanı ne kadar fazlaysa ve savunmanın puanları da o kadar az olursa, eşit değerler saldırının başarı şansını% 50 olarak değerlendirir.

Algoritma

  1. Rastgele bir bozuk para çevirin.

    1 A. Heads: Savunma bir puan kaybeder.

    1b. Kuyruklar: kafalar bir puan kaybeder.

  2. Hem savunma hem de saldırganın hala puanları varsa, 1. adıma geri dönün.

  3. Kim 0 puana düşerse savaşı kaybeder.

    3 A. Saldırgan 0'a düştü: Saldırı başarısız.

    3b. 0'a kadar savunma: Saldırı başarılı.

Java ile yazdım, ancak diğer dillere kolayca çevrilebilir olmalıdır.

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

Bir örnek

Örneğin, sadece olasılığın% 50 olduğundan emin olmak için att = 2 ve def = 2 olduğunu varsayalım.

Savaş, n = att + def - 1bu örnekte maksimum jeton dönüşü veya 3 olarak kararlaştırılacaktır (aslında burada 3'ün en iyisi). Madeni para döndürme işlemlerinin 2 n olası kombinasyonu vardır. Burada, "W", saldırganın jetonlu flip kazandığı anlamına gelirken, "L", saldırganın jetonlu çevirmeyi kaybettiği anlamına gelir.

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

Saldırgan 4/8 veya% 50 oranında kazanır.

Matematik

Bu basit algoritmadan kaynaklanan matematiksel olasılıklar, algoritmanın kendisinden daha karmaşıktır.

Tam olarak x L'nin bulunduğu kombinasyon sayısı , kombinasyon işlevi tarafından verilir:

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

Saldırgan 0ile att - 1Ls arasında kazanır . Kazanan kombinasyonların sayısı, birikimli binom dağılımı 0ile arasındaki kombinasyonların toplamına eşittir att - 1:

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

Saldırgan olasılığıdır kazanan w 2 bölü n , kümülatif binom olasılık:

p = w / 2^n

Java'da rasgele attve defdeğerler için bu olasılığı hesaplamak için kod :

/**
 * 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;
}

Test kodu:

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));
}

Çıktı:

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

Gözlemler

Olasılıklar 0.0, saldırganın 0puanları varsa, saldırganın puanları 1.0varsa, ancak savunmanın 0puanları 0.5varsa, puanlar eşitse, 0.5saldırganın savunmadan daha az puanı 0.5varsa ve saldırganın savunmadan daha fazla puanı varsa daha fazladır .

Alarak att = 50ve def = 80ben geçmek için gerekli BigDecimalönlemek taşması s ama 0.0040 yaklaşık bir olasılık olsun.

attDeğeri attve defdeğerlerinin ortalaması olacak şekilde değiştirerek olasılığı 0,5'e yaklaştırabilirsiniz . Att = 50, Def = 80 olur (65, 80), bu da 0.1056 olasılığı verir.


1
Bir başka ilginç yaklaşım. Algoritma da kolayca görselleştirilebilir, bu da oldukça heyecan verici görünebilir.
Philipp

5

Saldırıyı normal bir dağılımdan örneklenen rastgele bir sayı ile değiştirebilirsiniz. Bu şekilde çoğu zaman sonuç beklediğiniz gibi olur, ancak bazen daha düşük bir savunmaya karşı daha yüksek bir saldırı kaybeder veya daha yüksek bir savunmaya karşı daha düşük bir saldırı kazanır. Saldırı ve savunma arasındaki fark arttıkça, bunun olma olasılığı azalır.

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

Fonksiyon norm(x0, sigma), standart sapma sigma ile x0 merkezli normal bir dağılımdan örneklenmiş bir şamandıra döndürür. Çoğu programlama dili böyle bir işleve sahip bir kütüphane sağlar, ancak bunu yapmak istiyorsanız bu soruya bir göz atın . Sigma'yı 'doğru hissettirecek' şekilde ayarlamanız gerekir, ancak 10-20 değeri başlamak için iyi bir yer olabilir.

Birkaç sigma değeri için, verilen bir zafer için zafer olasılığı att1 - def2şöyledir: Zafer olasılığı


Normal dağıtılmış değerlerin gerçek sınırları olmadığını da belirtmek gerekir, bu nedenle bir oyunda normal dağıtılmış rasgele değerler kullanıldığında, çok aşırı değerlerin olası olmayan ancak imkansız durumunun üretilmesini önlemek için sonucu sıkıştırmak mantıklı olabilir. oyunu kırabilir.
Philipp
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.