Tấn công vs Quốc phòng và ai là người chiến thắng? [đóng cửa]


12

Tôi đang trong quá trình tạo một trò chơi đơn giản mới trên thiết bị di động và tôi đã dành vài ngày cho phần sau.

Để đơn giản, hãy nói rằng tôi có hai máy bay chiến đấu. Thuộc tính duy nhất của chúng là Tấn công và Phòng thủ. Khi những cuộc tấn công đầu tiên, điều duy nhất quan trọng là sự tấn công của anh ta và sự phòng thủ của đối thủ. Và ngược lại.

Họ không có thiết bị, vật phẩm, sức chịu đựng hay sức khỏe. Chỉ cần Tấn công vs Phòng thủ.

Thí dụ:

  • Máy bay chiến đấu 1:

    Tấn công: 50, Phòng thủ: 35

  • Máy bay chiến đấu 2:

    Tấn công 20, Phòng thủ: 80

Quá trình chiến đấu sẽ chỉ là một cuộc tấn công duy nhất sẽ quyết định người chiến thắng. Vì vậy, không có nhiều cuộc tấn công hoặc vòng. Tôi không muốn làm cho nó mang tính quyết định, nhưng thêm một phiên bản nhẹ bất ngờ. Một máy bay chiến đấu có sức tấn công thấp hơn sẽ có thể chiến thắng một máy bay chiến đấu khác có khả năng phòng thủ cao hơn (nhưng tất nhiên không phải lúc nào cũng vậy)

Ý tưởng đầu tiên của tôi là làm cho nó tuyến tính và gọi một trình tạo số ngẫu nhiên thống nhất.

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

Ví dụ với tấn công 50 và phòng thủ 80, máy bay chiến đấu tấn công sẽ có khoảng 38% để giành chiến thắng. Tuy nhiên, dường như với tôi rằng điều bất ngờ là quá xa và những chiến binh tồi tệ nhất sẽ chiến thắng rất nhiều.

Tôi đã tự hỏi làm thế nào bạn đã làm việc trên các tình huống tương tự.

PS Tôi đã tìm kiếm rất nhiều trong QnA này và các nguồn khác và tôi thấy các câu hỏi tương tự được đề cập là quá rộng cho SE. Nhưng những thứ đó có nhiều thuộc tính, vũ khí, vật phẩm, lớp học v.v ... có thể khiến nó trở nên quá phức tạp. Tôi nghĩ rằng phiên bản của tôi đơn giản hơn nhiều để phù hợp với phong cách QnA của SE.


1
Các trường hợp bạn đang tìm kiếm là gì? Phạm vi giá trị nào cho tấn công và phòng thủ mà bạn đang xem và liệu hai số trong các phạm vi đó có bao giờ có kết quả cố định không? Ví dụ, một máy bay chiến đấu với cuộc tấn công 10 có thể đánh bại một máy bay chiến đấu ở phòng thủ 90 không?
Niels

@ user2645227 Tôi có thể nói rằng phạm vi nằm trong khoảng từ 1 đến 400. Không, tôi không muốn có bất kỳ quyết định xác định nào và đưa ra khả năng tấn công 1 để giành chiến thắng trong phòng thủ 400, nhưng trong những trường hợp thực sự hiếm.
Tasos

1
Vì vậy, nếu bạn tham gia (tối thiểu) -def (tối đa) và Att (tối đa) -def (tối thiểu) cung cấp cho bạn phạm vi từ 800 đến -400 đến +400. Bạn sẽ muốn phạm vi ngẫu nhiên của bạn bao gồm toàn bộ phạm vi. Phòng thủ - Tấn công sẽ cung cấp cho bạn tỷ lệ chênh lệch theo hình thức ngưỡng bạn cần đạt được để giành chiến thắng. Điều này sẽ làm giảm sự ngẫu nhiên một chút. Để tập trung hơn vào kết quả, bạn có thể sử dụng ví dụ Philipps hoặc fiddle xung quanh trong bất kỳ lúc nào cho đến khi bạn đạt được đường cong mà bạn đang tìm kiếm.
Niels

Câu trả lời:


24

Nếu bạn muốn kết quả chiến đấu của mình dễ dự đoán hơn nhưng không hoàn toàn xác định, hãy có hệ thống n tốt nhất .

Lặp lại nthời gian chiến đấu (trong đó nnên là một số không đồng đều) và tuyên bố người chiến đấu là người chiến thắng thường xuyên hơn. Giá trị của bạn càng lớn đối với nnhững chiến thắng và thua lỗ ít bất ngờ hơn bạn sẽ có.

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
} 

Hệ thống này chỉ hoạt động trong trường hợp đặc biệt trong đó một cuộc chiến là kết quả nhị phân đơn giản của thắng hoặc thua. Khi một trận chiến có kết quả phức tạp hơn, như khi người chiến thắng vẫn mất một số điểm nhấn tùy thuộc vào mức độ thắng, cách tiếp cận này không còn hiệu quả nữa. Một giải pháp tổng quát hơn là thay đổi cách bạn tạo số ngẫu nhiên. Khi bạn tạo nhiều số ngẫu nhiên và sau đó lấy trung bình, kết quả sẽ phân cụm gần trung tâm của phạm vi và kết quả cực đoan hơn sẽ hiếm hơn. Ví dụ:

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

sẽ có một đường cong phân phối như thế này:

Phân phối 3d20 / 3

(hình ảnh lịch sự của anydice - một công cụ thực sự hữu ích để thiết kế các công thức cơ học trò chơi liên quan đến tính ngẫu nhiên, không chỉ cho các trò chơi trên bàn)

Trong dự án hiện tại của tôi, tôi đang sử dụng một hàm trợ giúp cho phép đặt kích thước mẫu tùy ý:

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

Có vẻ là một cách tiếp cận tốt hơn. Một câu hỏi. Trong hàm averagedRandom3 (), bạn nên sử dụng +thay vì *hoặc tôi hiểu nhầm nó làm gì?
Tasos

@Tasos có, nên là +, không phải *. Tôi cũng có một hàm ngẫu nhiên nhân nhiều mẫu. Điều này cung cấp cho bạn một hàm số ngẫu nhiên với độ lệch mạnh cho các giá trị thấp hơn, cũng có thể hữu ích trong một số trường hợp.
Philipp

1
Tôi sẽ giữ câu hỏi mở trong 1-2 ngày và nếu tôi không có câu trả lời khác, tôi sẽ chọn câu hỏi của bạn. Tôi đã nâng cấp nó nhưng cũng muốn tạo cơ hội cho những câu trả lời khác nếu bạn không phiền.
Tasos

Tôi nghĩ rằng câu trả lời này đã nhận đủ số phiếu bầu khiến câu trả lời này đủ điều kiện để đánh dấu nó là câu trả lời: P
Hamza Hasan

1
Tôi cũng sẽ tò mò nếu một số người đưa ra các phương pháp thay thế. Một người đã đánh giá thấp câu trả lời này. Có lẽ họ muốn cung cấp một cái khác.
Philipp

8

Đây là những gì tôi đã sử dụng để xác định người chiến thắng trong một trận chiến trong applet Lords of Conquest Imitator của tôi. Trong trò chơi này, tương tự như tình huống của bạn, chỉ có giá trị tấn công và giá trị phòng thủ. Xác suất mà kẻ tấn công giành được càng nhiều thì kẻ tấn công càng có nhiều điểm và phòng thủ càng có nhiều điểm, với các giá trị bằng nhau đánh giá khả năng thành công của cuộc tấn công là 50%.

Thuật toán

  1. Lật một đồng xu ngẫu nhiên.

    1a. Thủ trưởng: phòng thủ mất một điểm.

    1b. Đuôi: đầu mất một điểm.

  2. Nếu cả phòng thủ và kẻ tấn công vẫn có điểm, hãy quay lại bước 1.

  3. Ai bị hạ 0 điểm thì thua trận.

    3a. Kẻ tấn công xuống 0: Tấn công thất bại.

    3b. Phòng thủ xuống 0: Tấn công thành công.

Tôi đã viết nó bằng Java, nhưng nó có thể dễ dàng dịch sang các ngôn ngữ khác.

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

Một ví dụ

Ví dụ: giả sử att = 2 và def = 2, chỉ để đảm bảo rằng xác suất là 50%.

Trận chiến sẽ được quyết định trong tối đa số n = att + def - 1lần lật đồng xu, hoặc 3 trong ví dụ này (về cơ bản là tốt nhất trong số 3 ở đây). Có 2 n kết hợp có thể của lật đồng xu. Ở đây, "W" có nghĩa là kẻ tấn công đã giành được lật đồng xu và "L" có nghĩa là kẻ tấn công bị mất đồng xu lật.

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

Kẻ tấn công thắng trong 4/8, hoặc 50% các trường hợp.

Toán

Các xác suất toán học phát sinh từ thuật toán đơn giản này phức tạp hơn chính thuật toán.

Số lượng kết hợp trong đó có chính xác x Ls được đưa ra bởi hàm kết hợp:

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

Kẻ tấn công chiến thắng khi có giữa 0att - 1Ls. Số lượng kết hợp chiến thắng bằng tổng số kết hợp từ 0xuyên qua att - 1, phân phối nhị thức tích lũy:

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

Xác suất của những kẻ tấn công chiến thắng được w chia cho 2 n , một xác suất nhị thức tích lũy:

p = w / 2^n

Đây là mã trong Java để tính xác suất này cho các giá trị attdefgiá trị tùy ý :

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

Mã kiểm tra:

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

Đầu ra:

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

Quan sát

Xác suất là 0.0nếu kẻ tấn công có 0điểm, 1.0nếu kẻ tấn công có điểm nhưng phòng thủ có 0điểm, 0.5nếu điểm bằng nhau, ít hơn 0.5nếu kẻ tấn công có ít điểm hơn phòng thủ và lớn hơn 0.5nếu kẻ tấn công có nhiều điểm hơn phòng thủ .

Lấy att = 50def = 80, tôi cần chuyển sang BigDecimals để tránh tràn, nhưng tôi có xác suất khoảng 0,0040.

Bạn có thể làm cho xác suất gần bằng 0,5 bằng cách thay đổi attgiá trị thành trung bình của các giá trị attdef. Att = 50, Def = 80 trở thành (65, 80), mang lại xác suất 0,1056.


1
Một cách tiếp cận thú vị khác. Thuật toán cũng có thể dễ dàng hình dung, có thể trông khá thú vị.
Philipp

5

Bạn có thể sửa đổi cuộc tấn công bằng một số ngẫu nhiên được lấy mẫu từ một phân phối bình thường. Bằng cách này, phần lớn thời gian kết quả sẽ là những gì bạn mong đợi, nhưng đôi khi một cuộc tấn công cao hơn sẽ thua trước một phòng thủ thấp hơn hoặc một cuộc tấn công thấp hơn sẽ giành chiến thắng trước một phòng thủ cao hơn. Xác suất xảy ra điều này sẽ nhỏ hơn khi sự khác biệt giữa tấn công và phòng thủ tăng lên.

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

Hàm norm(x0, sigma)trả về một float được lấy mẫu từ một phân phối bình thường tập trung tại x0, với sigma độ lệch chuẩn. Hầu hết các ngôn ngữ lập trình cung cấp một thư viện với chức năng như vậy, nhưng nếu bạn muốn tự mình làm nó hãy xem câu hỏi này . Bạn sẽ phải điều chỉnh sigma sao cho 'cảm thấy đúng', nhưng giá trị 10-20 có thể là một nơi tốt để bắt đầu.

Đối với một vài giá trị sigma, xác suất chiến thắng cho một att1 - def2hình nhất định sẽ như thế này: Xác suất chiến thắng


Cũng có thể chỉ ra rằng các giá trị phân phối bình thường không có giới hạn thực tế, vì vậy khi sử dụng các giá trị ngẫu nhiên phân phối bình thường trong trò chơi, có thể có ý nghĩa để kẹp kết quả để tránh tình huống không thể xảy ra nhưng không thể xảy ra của các giá trị cực đoan được tạo ra có thể phá vỡ trò chơi
Philipp
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.