Магія: збираючий бойовий гольф


30

Magic: Gathering - гра з торговими картами, де, крім іншого, гравці грають у карти, що представляють істот, які потім можуть атакувати іншого гравця або захищатися від атак іншого гравця, блокуючи їх.

У цьому виклику коду-гольфу ваша програма буде замість гравця Magic, який вирішить, як блокувати в бою.


Кожна істота має два релевантні ознаки: Сила та міцність. Сила істоти - це сума шкоди, яку вона може завдати в бою, а її міцність - це сума шкоди, необхідної для її знищення. Потужність завжди принаймні 0, а в'язкість - не менше 1.

Під час бою в Magic, гравець, у свою чергу якого оголошує, що деякі їхні істоти атакують супротивника. Тоді інший гравець, відомий як гравець, що захищається, може призначити своїх істот блокаторами. Істота може блокувати лише одне створіння за битву, але кілька істот можуть блокувати одне і те ж створіння.

Після того, як блокатори оголошені, гравець, що атакує, вирішує для кожної атакованої істоти, яка була заблокована, як розподілити шкоду (рівну її потужності), що ця істота наносить істотам, які її блокують.

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

Нарешті, будь-яка істота завдала шкоди, рівній або більшої, ніж її в'язкість, знищується та видаляється з поля бою. Будь-яка кількість шкоди, меншої за міцність створіння, не впливає.


Ось приклад цього процесу:

Істота з потужністю P і міцністю T представлена ​​як P/T

Attacking:
2/2, 3/3
Defending player's creatures:
1/4, 1/1, 0/1
Defending player declares blockers:
1/4 and 1/1 block 2/2, 0/1 does not block.
Attacking player distributes damage:
2/2 deals 1 damage to 1/4 and 1 damage to 1/1
Damage is dealt:
2/2 takes 2 damage, destroyed.
3/3 takes 0 damage.
1/1 takes 1 damage, destroyed.
1/4 takes 1 damage.
0/1 takes 0 damage.
Defending player is dealt 3 damage.

У гравця, який захищається, в бою є 3 цілі: знищуйте супротивника, зберігайте власні істоти і завдайте йому якомога менше шкоди. Крім того, істоти з більшою силою та міцністю є більш цінними.

Щоб об'єднати їх в єдиний захід, ми скажемо, що рахунок гравця, який захищається, від бою дорівнює сумі сил і міцності їхніх вижилих істот, мінус суми сил і міцності вижилих істот опонента, мінус один половина суми збитків, завданих гравцеві, що захищається. Гравець, що захищається, хоче максимально збільшити цей рахунок, а атакуючий гравець хоче його мінімізувати.

У бою, показаному вище, рахунок був:

Defending player's surviving creatures:
1/4, 0/1
1 + 4 + 0 + 1 = 6
Attacking player's surviving creature:
3/3
3 + 3 = 6
Damage dealt to defending player:
3
6 - 6 - 3/2 = -1.5

Якби гравець, що захищався, взагалі не заблокував описаний вище бій, рахунок був би

8 - 10 - (5/2) = -4.5

Оптимальним вибором гравця, що захищається, було б блокування 2/2з і 1/1та 1/4, а також блокування 3/3з 0/1. Якби вони зробили це, тільки 1/4і 3/3вижили б, і жодної шкоди не було б нанесено гравцеві, що захищається, зробивши рахунок

5 - 6 - (0/2) = -1

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

Це максимін: максимальний бал за розподіл шкоди, який мінімізує бал за кожну блокуючу комбінацію.


Введення: Вхід буде складатися з двох списків 2-кортежів, де кожен 2-кортеж має форму (Потужність, Міцність). Перший список містить повноваження та суворість кожної атакуючої істоти (істоти противника). Другий список буде містити сили та міцність кожного вашого створіння.

Кортежі та списки можуть бути представлені у будь-якому зручному форматі, наприклад:

[[2, 2], [3, 3]]
[[1, 4], [1, 1], [0, 1]]

Вихід: Вихід буде складатися з серії 2-кортежів у формі (блокування істоти, заблокована істота) - тобто одне із ваших істот, за яким слідує одне із їхніх створінь. Істот буде посилатися на їх індекс у списках вхідних даних. Індекси можуть бути 0 або 1 індексованими. Знову будь-який зручний формат. Будь-яке замовлення добре. Наприклад, оптимальний сценарій блокування зверху, враховуючи вищевказаний вклад, може бути представлений у вигляді:

[0, 0]    # 1/4 blocks 2/2
[1, 0]    # 1/1 blocks 2/2
[2, 1]    # 0/1 blocks 3/3

Приклади:

Input:
[[2, 2], [3, 3]]
[[1, 4], [1, 1], [0, 1]]
Output:
[0, 0]
[1, 0]
[2, 1]

Input:
[[3, 3], [3, 3]]
[[2, 3], [2, 2], [2, 2]]
Output:
[1, 0]
[2, 0]
or
[1, 1]
[2, 1]

Input:
[[3, 1], [7, 2]]
[[0, 4], [1, 1]]
Output:
[1, 0]
or
[0, 0]
[1, 0]

Input:
[[2, 2]]
[[1, 1]]
Output:

(No output tuples).

Вхід та вихід можуть здійснюватися через STDIN, STDOUT, CLA, функцію введення / повернення тощо. Застосовуються стандартні лазівки . Це код-гольф: найкоротший код у байтах виграє.


Щоб уточнити специфікацію та надати початкові ідеї, ця пастабін надає довідкове рішення в Python. Ця best_blockфункція є зразковим рішенням цього завдання, а запуск програми забезпечить більше детального введення та виводу.


18
Ви повинні зробити цього царем пагорба.
PyRulez

1
@Arnauld nope, це теж правдива відповідь.
isaacg

Відповіді:


6

JavaScript (ES7),  354  348 байт

Вводиться як " ([attackers], [defenders]).

(a,d,O,M)=>eval(`for(N=(A=a.push([,0]))**d.length;N--;)O=a[X='map'](([P,T],i)=>S-=((g=(n,l)=>n?l[X](([t,S],i)=>g(n-1,b=[...l],b[i]=[t-1,S])):m=l[X](([t,S])=>s+=t>0&&S,s=0)&&s>m?m:s)(P,l[n=0,i][X](m=([p,t])=>[t,p+t,n+=p])),n<T&&P+T)+(l[i]<1?T/2:-m),S=0,d[X]((x,i)=>l[(j=N/A**i%A|0)<A-1&&o.push([i,j]),j].push(x),o=[],l=a[X](_=>[])))&&S<M?O:(M=S,o)`)

Спробуйте в Інтернеті!

Менше гольфу та форматованих

Цей код ідентичний версії для гольфу, лише без mapпсевдонімів та eval()упаковки для читабельності.

(a, d, O, M) => {
  for(N = (A = a.push([, 0])) ** d.length; N--;)
    O =
      a.map(([P, T], i) =>
        S -=
          (
            (g = (n, l) =>
              n ?
                l.map(([t, S], i) => g(n - 1, b = [...l], b[i] = [t - 1, S]))
              :
                m = l.map(([t, S]) => s += t > 0 && S, s = 0) && s > m ? m : s
            )(
              P,
              l[n = 0, i].map(m = ([p, t]) => [t, p + t, n += p])
            ),
            n < T && P + T
          ) + (
            l[i] < 1 ? T / 2 : -m
          ),
        S = 0,
        d.map((x, i) =>
          l[
            (j = N / A ** i % A | 0) < A - 1 && o.push([i, j]),
            j
          ].push(x),
          o = [],
          l = a.map(_ => [])
        )
      ) && S < M ? O : (M = S, o)
  return O
}

Як?

Ініціалізація та основний цикл

0pushА

A = a.push([, 0])

Ми будемо блокувати цю маневрену істоту, а не блокувати жодну істоту. Це дозволяє деякі спрощення коду.

АDDN

for(N = (A = a.push([, 0])) ** d.length; N--;)

SМоО

МО

O = (...) && S < M ? O : (M = S, o)

Побудова нашої оборони

Нижче наведено блок коду, який виконується на початку кожної ітерації. Нова важлива зміннал

d.map((x, i) =>              // for each defender x at position i:
  l[                         //   update l[]:
    (j = N / A ** i % A | 0) //     j = index of the attacker that we're going to block
    < A - 1 &&               //     if this is not the 'dummy' creature:
    o.push([i, j]),          //       add the pair [i, j] to the current solution
    j                        //     use j as the actual index to update l[]
  ].push(x),                 //   push x in the list of blockers for this attacker
  o = [],                    //   initialize o to an empty list
  l = a.map(_ => [])         //   initialize l to an array containing as many empty lists
                             //   that there are attackers
)                            // end of map()

Оптимізація атаки

Рішення зловмисників несумісні між собою. Глобальний оптимум для атакуючої сторони - це сума локальної оптими для кожного нападника.

ПТi

a.map(([P, T], i) => ...)

л[i]

l[n = 0, i].map(m = ([p, t]) => [t, p + t, n += p])

н

гП

(g = (n, l) =>            // n = remaining damage points; l = list of blockers
  n ?                     // if we still have damage points:
    l.map(([t, S], i) =>  //   for each blocker of toughness t and score S at index i:
      g(                  //     do a recursive call:
        n - 1,            //       decrement the number of damage points
        b = [...l],       //       create a new instance b of l
        b[i] = [t - 1, S] //       decrement the toughness of blocker i
      )                   //     end of recursive call
    )                     //   end of map()
  :                       // else:
    m =                   //   update the best score m (the lower, the better):
      l.map(([t, S]) =>   //     for each blocker of toughness t and score S:
        s += t > 0 && S,  //       add S to s if this blocker has survived
        s = 0             //       start with s = 0
      ) &&                //     end of map()
      s > m ? m : s       //     set m = min(m, s)
)                         //

Оновлення рахунку захисника

Після кожної ітерації нападника ми оновлюємо рахунок захисника:

S -= (n < T && P + T) + (l[i] < 1 ? T / 2 : -m)

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

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