Attack vs Defense และใครคือผู้ชนะ? [ปิด]


12

ฉันอยู่ในขั้นตอนการสร้างเกมง่ายๆใหม่ในมือถือและฉันใช้เวลาหลายวันในส่วนต่อไปนี้

เพื่อความง่ายสมมติว่าฉันมีนักสู้สองคน คุณลักษณะเฉพาะของพวกเขาคือการโจมตีและการป้องกัน เมื่อการโจมตีครั้งแรกสิ่งเดียวที่สำคัญคือการโจมตีของเขาและการป้องกันของฝ่ายตรงข้าม และในทางกลับกัน.

พวกเขาไม่มีอุปกรณ์สิ่งของความแข็งแกร่งหรือสุขภาพ เพียงแค่โจมตีเทียบกับการป้องกัน

ตัวอย่าง:

  • นักสู้ 1:

    การโจมตี: 50, การป้องกัน: 35

  • นักสู้ 2:

    โจมตี 20 ป้องกัน: 80

กระบวนการต่อสู้จะเป็นการโจมตีเพียงครั้งเดียวซึ่งจะเป็นตัวตัดสินผู้ชนะ ดังนั้นจึงไม่มีการโจมตีหรือปัดเศษหลายครั้ง ฉันไม่ต้องการทำให้มันยาก แต่เพิ่มรุ่นที่ไม่คาดคิด นักสู้ที่มีการจู่โจมต่ำกว่าจะสามารถชนะนักสู้อีกคนที่มีการป้องกันที่ดีกว่าได้ (แต่ไม่ใช่ทุกครั้ง)

ความคิดแรกของฉันคือทำให้มันเป็นเส้นตรงและเรียกเครื่องมือสร้างตัวเลขสุ่ม

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

ตัวอย่างที่มีการโจมตี 50 และการป้องกัน 80, นักสู้โจมตีจะมีประมาณ 38% ที่จะชนะ อย่างไรก็ตามสำหรับฉันแล้วดูเหมือนว่าสิ่งที่คาดไม่ถึงนั้นไกลเกินไปและนักสู้ที่แย่ที่สุดจะชนะมาก

ฉันสงสัยว่าคุณทำงานในสถานการณ์ที่คล้ายกันอย่างไร

ป.ล. ฉันค้นหาข้อมูลจำนวนมากใน QnA นี้และแหล่งข้อมูลอื่น ๆ และฉันพบคำถามที่คล้ายกันซึ่งกล่าวถึงกว้างเกินไปสำหรับ SE แต่สิ่งเหล่านั้นมีคุณสมบัติมากมายอาวุธรายการคลาส ฯลฯ ที่อาจทำให้มันซับซ้อนเกินไป ฉันคิดว่าเวอร์ชั่นของฉันง่ายกว่าที่จะใส่ลงในสไตล์ QnA ของ SE


1
คุณกำลังมองหากรณีใดบ้าง? คุณกำลังมองหาช่วงของค่าสำหรับการโจมตีและการป้องกันและตัวเลขสองตัวใดในช่วงเหล่านั้นที่เคยมีผลคงที่? ตัวอย่างเช่นนักสู้ที่มีการโจมตี 10 สามารถเอาชนะนักสู้ที่ป้องกันได้ 90 หรือ
Niels

@ user2645227 ฉันสามารถพูดได้ว่าช่วงอยู่ระหว่าง 1 ถึง 400 ไม่ไม่ฉันไม่ต้องการมีการตัดสินใจที่แน่นอนและให้โอกาสในการโจมตี 1 ชนะการป้องกัน 400 แต่ในกรณีที่หายากจริงๆ
Tasos

1
ดังนั้นหากคุณใช้ Att (min) -def (max) และ Att (max) -def (min) ที่ให้ช่วงของ 800 จาก -400 ถึง +400 คุณจะต้องการให้ช่วงสุ่มของคุณครอบคลุมช่วงทั้งหมด การป้องกัน - การโจมตีจะทำให้คุณได้รับการปรับสเกลในรูปแบบของเกณฑ์ที่คุณจะต้องชนะเพื่อเอาชนะ สิ่งนี้จะช่วยลดการสุ่มเล็กน้อย หากต้องการรวมศูนย์ผลลัพธ์ให้มากขึ้นคุณสามารถใช้ตัวอย่างหรือฟิลลิปส์ได้ทุกมุมจนกว่าคุณจะตีโค้งที่คุณต้องการ
Niels

คำตอบ:


24

หากคุณต้องการผลการต่อสู้ของคุณจะคาดการณ์มากขึ้น แต่ไม่กำหนดสมบูรณ์ได้ที่ดีที่สุดของ nระบบ

ทำซ้ำnเวลาการต่อสู้(ซึ่งnควรเป็นจำนวนที่ไม่เท่ากัน) และประกาศให้ผู้ชนะที่ชนะการรบบ่อยขึ้น ยิ่งมูลค่าของคุณมากขึ้นสำหรับnการชนะและการสูญเสียที่คุณจะได้รับ

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
} 

ระบบนี้ใช้งานได้เฉพาะในกรณีพิเศษที่การต่อสู้เป็นผลไบนารีแบบง่าย ๆ ของการชนะหรือแพ้ เมื่อการต่อสู้มีผลลัพธ์ที่ซับซ้อนมากขึ้นเช่นเมื่อผู้ชนะยังคงเสียคะแนนบางอย่างขึ้นอยู่กับว่าใกล้ชนะชนะวิธีนี้ไม่ทำงานอีกต่อไป วิธีแก้ปัญหาทั่วไปเพิ่มเติมคือการเปลี่ยนวิธีการสร้างตัวเลขสุ่ม เมื่อคุณสร้างตัวเลขสุ่มหลายตัวแล้วหาค่าเฉลี่ยผลลัพธ์จะจัดกลุ่มใกล้กับกึ่งกลางของช่วงและผลลัพธ์ที่มากขึ้นจะหายากมากขึ้น ตัวอย่างเช่น:

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

จะมีเส้นโค้งการกระจายดังนี้:

การกระจายตัวของ 3d20 / 3

(รูปภาพเอื้อเฟื้อภาพของ anydice - เป็นเครื่องมือที่มีประโยชน์มากสำหรับการออกแบบสูตรเกมช่างที่เกี่ยวข้องกับการสุ่มไม่ใช่แค่สำหรับเกมบนโต๊ะ)

ในโครงการปัจจุบันของฉันฉันใช้ฟังก์ชันผู้ช่วยซึ่งอนุญาตให้ตั้งขนาดตัวอย่างโดยพลการ:

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

ดูเหมือนจะเป็นวิธีที่ดีกว่า หนึ่งคำถาม. ในฟังก์ชัน averagedRandom3 () คุณควรใช้+แทน*หรือฉันเข้าใจผิดว่ามันทำอะไร?
Tasos

@Tasos ใช่ควรเป็น + ไม่ใช่ * ฉันยังมีฟังก์ชั่นสุ่มซึ่งคูณตัวอย่างหลายตัว สิ่งนี้ทำให้คุณมีฟังก์ชั่นตัวเลขสุ่มที่มีอคติที่แข็งแกร่งสำหรับค่าที่ต่ำกว่าซึ่งอาจเป็นประโยชน์ในบางสถานการณ์
ฟิลิปป์

1
ฉันจะเปิดคำถามต่อไปอีก 1-2 วันและหากฉันไม่มีคำตอบอื่นฉันจะเลือกคำถามของคุณ ฉันได้ลงคะแนนแล้ว แต่ต้องการให้โอกาสสำหรับคำตอบอื่น ๆ เช่นกันถ้าคุณไม่รังเกียจ
Tasos

ฉันคิดว่าคำตอบนี้ได้รับคะแนนเสียงเพียงพอแล้วซึ่งทำให้คำตอบนี้มีสิทธิ์ทำเครื่องหมายเป็นคำตอบ: P
Hamza Hasan

1
ฉันก็จะอยากรู้อยากเห็นถ้าบางคนมาด้วยวิธีการทางเลือก มีคนหนึ่งลงคะแนนให้กับคำตอบนี้ บางทีพวกเขาต้องการที่จะให้ทางเลือกหนึ่ง
ฟิลิปป์

8

นี่คือสิ่งที่ฉันใช้เพื่อกำหนดผู้ชนะการต่อสู้ในแอปเพล็ต Lords of Conquest Imitator ของฉัน ในเกมนี้คล้ายกับสถานการณ์ของคุณมีเพียงค่าการโจมตีและค่าการป้องกัน ความน่าจะเป็นที่ผู้โจมตีชนะจะยิ่งได้คะแนนมากขึ้นเท่าที่ผู้โจมตีจะได้รับและยิ่งมีคะแนนน้อยลงเท่าใดนักที่มีค่าเท่ากันประเมินว่ามีโอกาส 50% ที่การโจมตีสำเร็จ

ขั้นตอนวิธี

  1. พลิกเหรียญสุ่ม

    1a หัว: การป้องกันสูญเสียจุด

    1b ก้อย: หัวเสียคะแนน

  2. หากทั้งการป้องกันและการโจมตียังคงมีคะแนนให้กลับไปที่ขั้นตอนที่ 1

  3. ใครก็ตามที่ลงไป 0 คะแนนแพ้การต่อสู้

    3a ผู้โจมตีถึง 0: การโจมตีล้มเหลว

    3b ป้องกันลงถึง 0: การโจมตีสำเร็จ

ฉันเขียนเป็นภาษาจาวา แต่ควรแปลเป็นภาษาอื่นได้ง่าย

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

ตัวอย่าง

ตัวอย่างเช่นสมมุติว่า att = 2 และ def = 2 เพียงเพื่อให้แน่ใจว่าความน่าจะเป็นคือ 50%

การต่อสู้จะตัดสินด้วยการn = att + def - 1พลิกเหรียญสูงสุดหรือ 3 ในตัวอย่างนี้ (โดยพื้นฐานแล้วมันจะดีที่สุด 3 อันดับ) มี 2 nผสมเป็นไปได้ของเหรียญพลิก ที่นี่ "W" หมายถึงผู้โจมตีชนะการโยนเหรียญและ "L" หมายถึงผู้โจมตีแพ้การพลิกเหรียญ

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

ผู้โจมตีชนะใน 4/8 หรือ 50% ของคดี

คณิตศาสตร์

ความน่าจะเป็นทางคณิตศาสตร์ที่เกิดจากอัลกอริธึมง่าย ๆ นี้ซับซ้อนกว่าอัลกอริธึมเอง

จำนวนรวมกันที่มีตรงxแอลเอสจะได้รับจากการทำงานรวมกัน:

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

ผู้โจมตีชนะเมื่ออยู่ระหว่าง0ถึงatt - 1Ls จำนวนชุดค่าผสมที่ชนะเท่ากับผลรวมของชุดค่าผสมจาก0ถึงatt - 1การแจกแจงแบบทวินามสะสม:

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

น่าจะเป็นของการโจมตีชนะคือWโดยแบ่งออกเป็น 2 n , ความน่าจะเป็นทวินามสะสม:

p = w / 2^n

นี่คือรหัสใน Java เพื่อคำนวณความน่าจะเป็นนี้โดยพลการattและ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;
}

รหัสการทดสอบ:

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

เอาท์พุท:

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

ข้อสังเกต

ความน่าจะเป็นคือ0.0ถ้าผู้โจมตีมี0คะแนน1.0ถ้าผู้โจมตีมีคะแนน แต่การป้องกันมี0คะแนน0.5ถ้าคะแนนเท่ากันน้อยกว่า0.5ถ้าผู้โจมตีมีคะแนนน้อยกว่าการป้องกันและสูงกว่า0.5หากผู้โจมตีมีคะแนนมากกว่าการป้องกัน .

การรับatt = 50และdef = 80ฉันจำเป็นต้องเปลี่ยนไปใช้BigDecimals เพื่อหลีกเลี่ยงการล้น แต่ฉันได้รับความน่าจะเป็นประมาณ 0.0040

คุณสามารถทำให้ความน่าจะเป็นใกล้เคียง 0.5 โดยการเปลี่ยนattค่าให้เป็นค่าเฉลี่ยของattและdefค่า Att = 50, Def = 80 กลายเป็น (65, 80) ซึ่งให้ความน่าจะเป็นที่ 0.1056


1
อีกวิธีที่น่าสนใจ อัลกอริทึมสามารถมองเห็นได้ง่ายซึ่งอาจดูน่าตื่นเต้นทีเดียว
ฟิลิปป์

5

คุณสามารถแก้ไขการจู่โจมได้ด้วยการสุ่มตัวเลขจากการแจกแจงแบบปกติ วิธีนี้ส่วนใหญ่ผลลัพธ์จะเป็นสิ่งที่คุณคาดหวัง แต่บางครั้งการโจมตีที่สูงขึ้นจะแพ้การป้องกันที่ต่ำกว่าหรือการโจมตีที่ต่ำกว่าจะชนะกับการป้องกันที่สูงขึ้น ความน่าจะเป็นของเหตุการณ์นี้จะลดลงเมื่อความแตกต่างระหว่างการโจมตีและการป้องกันเพิ่มขึ้น

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

ฟังก์ชันnorm(x0, sigma)ส่งคืน float ตัวอย่างจากการแจกแจงแบบปกติที่มีศูนย์กลางที่ x0 พร้อมซิกส่วนเบี่ยงเบนมาตรฐาน ภาษาโปรแกรมส่วนใหญ่จะมีฟังก์ชั่นดังกล่าวให้กับห้องสมุด แต่หากคุณต้องการทำให้มันเป็นของตัวเองให้ดูคำถามนี้ คุณต้องปรับ sigma เพื่อให้ 'รู้สึกถูกต้อง' แต่ค่า 10-20 อาจเป็นจุดเริ่มต้นที่ดี

สำหรับค่า sigma ไม่กี่ค่าความน่าจะเป็นของชัยชนะสำหรับค่าที่กำหนดatt1 - def2จะเป็นดังนี้: ความน่าจะเป็นของชัยชนะ


มันอาจคุ้มค่าที่ชี้ให้เห็นว่าค่าการแจกแจงแบบปกติไม่มีขอบเขตจริงดังนั้นเมื่อใช้การสุ่มค่าแบบกระจายในเกมก็สามารถจับผลลัพธ์ได้เพื่อหลีกเลี่ยงสถานการณ์ที่ไม่น่าจะเกิดขึ้น อาจทำลายเกม
ฟิลิปป์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.