Атака проти оборони і хто переможець? [зачинено]


12

Я зараз створюю нову просту гру в мобільному, і я провів кілька днів на наступній частині.

Для простоти скажімо, що у мене є два бійці. Єдиний атрибут їх - «Атака та оборона». Коли перші атаки, єдине, що має значення, - це напад на нього та захист супротивника. І навпаки.

У них немає обладнання, предметів, витривалості чи здоров'я. Просто атака проти оборони.

Приклад:

  • Винищувач 1:

    Атака: 50, оборона: 35

  • Винищувач 2:

    Атака 20, оборона: 80

Бойовий процес буде лише однією атакою, яка визначить переможця. Отже, без декількох атак чи патронів. Я не хочу робити це детерміновано, але додаю легку версію несподіваного. Боєць із нижчою атакою зможе виграти іншого винищувача з вищою захистом (але, звичайно, не кожен раз)

Моя перша ідея полягала в тому, щоб зробити його лінійним і викликати рівномірний генератор випадкових чисел.

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

Наприклад, з атакою 50 та захистом 80, боєць, що атакує, матиме 38% для перемоги. Однак мені здається, що несподіване занадто далеко, і найгірші бійці багато виграють.

Мені було цікаво, як ви працювали в подібних ситуаціях.

PS Я багато шукав у цьому QnA та інших джерелах, і я виявив подібні питання, згадані як занадто широкі для SE. Але у них було багато атрибутів, зброї, предметів, класів тощо, які могли б зробити це занадто складним. Я думаю, що моя версія набагато простіша, щоб вписати її у стиль QnA SE.


1
Які справи ви шукаєте? Який діапазон значень для атаки та оборони ви дивитесь і чи повинні будь-які два числа в цих діапазонах колись мати фіксований результат? Наприклад, чи може боєць з атакою 10 перемогти винищувача в обороні 90?
Нільс

@ user2645227 Я можу сказати, що діапазон становить від 1 до 400. Ні, я не хочу приймати жодних детермінованих рішень і давати можливість атакувати 1, виграти захист 400, але в дуже рідкісних випадках.
Тасос

1
Тож якщо ви візьмете Att (min) -def (max) і Att (max) -def (min), що дає діапазон 800 від -400 до +400. Ви хочете, щоб ваш випадковий діапазон охопив весь діапазон. Захист - Атака дасть вам масштабний запас у вигляді порогу, який вам знадобиться вдарити, щоб виграти. Це повинно трохи зменшити випадковість. Для подальшої централізації результатів ви можете скористатися прикладом Філіпса або загадати в будь-якому куточку, поки не потрапите на криву, яку шукаєте.
Нільс

Відповіді:


24

Якщо ви хочете, щоб ваші результати боротьби були більш передбачуваними, але не були повністю детермінованими, майте кращу російську систему.

Повторіть бойові 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 так, має бути +, а не *. У мене також є випадкова функція, яка множує декілька вибірок. Це дає вам функцію випадкових чисел із сильним ухилом для менших значень, що також може бути корисним у деяких ситуаціях.
Філіп

1
Я буду тримати питання відкритим протягом 1-2 днів, і якщо у мене немає іншої відповіді, я виберу вашу. Я підтримав це, але хочу дати шанс і на інші відповіді, якщо ви не заперечуєте.
Тасос

Я думаю, що ця відповідь вже набирає достатньо голосів, що робить цю відповідь прийнятною для позначення її як відповіді: P
Хамза Хасан

1
Мені б також цікаво, якби деякі люди придумали альтернативні підходи. Одна людина відмовилася від цієї відповіді. Можливо, вони хотіли б надати альтернативну.
Філіп

8

Це те, що я використовував для визначення переможця битви в моєму аплеті «Імітатор Господарів». У цій грі, аналогічній вашій ситуації, є лише значення атаки та захисне значення. Вірогідність того, що нападник виграє, тим більше, чим більше очок має нападник, і чим менше, тим більше очок має захист, при рівних значеннях, що оцінюють 50% шанс успіху атаки.

Алгоритм

  1. Переверніть випадкову монету.

    1а. Глави: захист втрачає очко.

    1б. Хвости: голови втрачають крапку.

  2. Якщо у оборонців та у нападника все ще є очки, поверніться до кроку 1.

  3. Хто знизився до 0 очок, програє битву.

    3а. Нападник знижується до 0: Атака не вдається.

    3б. Захист до 0: Атака вдається.

Я написав це на Java, але це має бути легко перекладене на інші мови.

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 Ls, задається функцією комбінування:

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)повертає поплавок, відібраний з нормального розподілу, зосередженого на x0, із сигмою стандартного відхилення. Більшість мов програмування надають бібліотеці таку функцію, але якщо ви хочете зробити це самостійно, погляньте на це питання . Вам доведеться відрегулювати сигму таким чином, щоб вона "почувалася правильно", але значення 10-20 може бути хорошим місцем для початку.

Для кількох значень сигми ймовірність перемоги для даної att1 - def2виглядає приблизно так: Ймовірність перемоги


Можливо, варто також зазначити, що нормальні розподілені значення не мають фактичних меж, тому при використанні нормально розподілених випадкових значень у грі може бути доцільним затиснути результат, щоб уникнути навряд чи, але не неможливої ​​ситуації, коли генеруються дуже екстремальні значення, які може зламати гру.
Філіп
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.