Magic: the Gathering Combat Golf


30

Magic: the Gathering adalah permainan kartu perdagangan di mana, antara lain, pemain bermain kartu mewakili makhluk, yang kemudian dapat menyerang pemain lain, atau bertahan melawan serangan pemain lain dengan memblokir.

Dalam tantangan kode-golf ini, program Anda akan menggantikan pemain Sihir yang memutuskan cara memblokir dalam pertempuran.


Setiap makhluk memiliki dua atribut yang relevan: Kekuatan, dan ketangguhan. Kekuatan makhluk adalah jumlah kerusakan yang bisa ditimbulkannya dalam pertempuran, dan ketangguhannya adalah jumlah kerusakan yang diperlukan untuk menghancurkannya. Kekuatan selalu minimal 0, dan ketangguhan selalu minimal 1.

Selama pertempuran di Magic, pemain yang gilirannya menyatakan beberapa makhluk mereka untuk menyerang lawan. Kemudian, pemain lain, yang dikenal sebagai pemain bertahan, dapat menetapkan makhluk mereka sebagai pemblokir. Makhluk dapat memblokir hanya satu makhluk per pertempuran, tetapi beberapa makhluk semuanya dapat memblokir makhluk yang sama.

Setelah pemblokir dinyatakan, pemain menyerang memutuskan, untuk setiap makhluk menyerang yang diblokir, cara mendistribusikan kerusakan (sama dengan kekuatannya) yang diberikan makhluk itu kepada makhluk yang memblokirnya.

Kemudian, kerusakan ditangani. Setiap makhluk menghasilkan Damage yang setara dengan kekuatannya. Makhluk penyerang yang diblokir akan memberikan damage seperti yang dijelaskan di atas. Makhluk penyerang yang tidak diblokir memberikan kerusakan pada pemain yang bertahan. Makhluk yang menghalangi memberikan kerusakan pada makhluk yang mereka blokir. Makhluk-makhluk yang termasuk pemain bertahan yang tidak menghalangi transaksi tidak mengalami kerusakan. (Makhluk tidak diharuskan untuk diblokir.)

Akhirnya, semua makhluk yang mengalami kerusakan sama atau lebih besar dari ketangguhannya dihancurkan, dan dikeluarkan dari medan perang. Jumlah kerusakan yang kurang dari ketangguhan makhluk tidak memiliki efek.


Berikut ini contoh proses ini:

Makhluk dengan kekuatan P dan ketangguhan T diwakili sebagai 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.

Pemain yang bertahan memiliki 3 gol dalam pertempuran: Hancurkan makhluk lawan, pertahankan makhluk sendiri, dan serahkan sesedikit mungkin kerusakan. Selain itu, makhluk dengan kekuatan lebih dan ketangguhan lebih berharga.

Untuk menggabungkan ini menjadi satu ukuran, kita akan mengatakan bahwa skor pemain bertahan dari pertempuran sama dengan jumlah kekuatan dan ketangguhan makhluk yang selamat, dikurangi jumlah kekuatan dan ketangguhan makhluk yang masih hidup lawan mereka, minus satu setengah dari jumlah damage yang diberikan kepada pemain bertahan. Pemain yang bertahan ingin memaksimalkan skor ini, sementara pemain yang menyerang ingin menguranginya.

Dalam pertarungan yang ditunjukkan di atas, skornya adalah:

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

Jika pemain yang bertahan tidak terhalang sama sekali dalam pertempuran yang dijelaskan di atas, skornya akan menjadi

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

Pilihan optimal untuk pemain bertahan adalah dengan memblokir 2/2dengan 1/1dan 1/4, dan untuk memblokir 3/3dengan 0/1. Jika mereka melakukannya, hanya 1/4dan itu 3/3akan selamat, dan tidak ada kerusakan akan diberikan kepada pemain bertahan, membuat skor

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

Tantangan Anda adalah menulis program yang akan menghasilkan pilihan pemblokiran optimal untuk pemain bertahan. "Optimal" berarti pilihan yang memaksimalkan skor, mengingat bahwa lawan mendistribusikan kerusakan dengan cara yang meminimalkan skor, mengingat cara Anda memblokir.

Ini adalah maximin: Skor maksimum dari distribusi kerusakan yang meminimalkan skor untuk setiap kombinasi pemblokiran.


Input: Input akan terdiri dari dua daftar 2-tupel, di mana masing-masing 2-tupel berbentuk (Daya, Ketangguhan). Daftar pertama akan berisi kekuatan dan ketangguhan dari setiap makhluk yang menyerang (makhluk lawan Anda). Daftar kedua akan berisi kekuatan dan ketangguhan dari masing-masing makhluk Anda.

Tuples dan daftar dapat direpresentasikan dalam format apa pun yang nyaman, seperti:

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

Keluaran: Hasil akan terdiri dari serangkaian 2-tupel, dalam bentuk (makhluk pemblokiran, makhluk terblokir) - yaitu, salah satu makhluk Anda diikuti oleh salah satu makhluk mereka. Makhluk akan dirujuk oleh indeks mereka dalam daftar input. Indeks mungkin 0 atau 1 diindeks. Sekali lagi, format apa pun yang nyaman. Pesanan apa pun baik-baik saja. Misalnya, skenario pemblokiran optimal dari atas, mengingat input di atas, dapat direpresentasikan sebagai:

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

Contoh:

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).

Input dan output dapat melalui STDIN, STDOUT, CLA, input fungsi / pengembalian, dll. Celah standar berlaku. Ini adalah kode-golf: kode terpendek dalam byte menang.


Untuk memperjelas spesifikasi dan memberikan ide awal, pastebin ini menyediakan solusi referensi dengan Python. The best_blockfungsi adalah solusi sampel untuk tantangan ini, dan menjalankan program ini akan memberikan masukan yang lebih verbose dan output.


18
Anda harus menjadikan raja bukit ini.
PyRulez

1
@Arnauld tidak, itu juga jawaban yang valid.
isaacg

Jawaban:


6

JavaScript (ES7),  354  348 byte

Mengambil input sebagai ([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)`)

Cobalah online!

Kurang golf dan diformat

Kode ini identik dengan versi golf, hanya tanpa mapalias dan eval()pembungkus agar mudah dibaca.

(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
}

Bagaimana?

Inisialisasi dan loop utama

0pushSEBUAH

A = a.push([, 0])

Kita akan memblokir makhluk tiruan ini alih-alih memblokir makhluk sama sekali. Ini memungkinkan beberapa penyederhanaan dalam kode.

SEBUAHDDN

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

SM.HaiHAI

Pada akhir setiap iterasi, kami menguji apakah M.HAI

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

Membangun pertahanan kita

l

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()

Mengoptimalkan serangan

Keputusan para penyerang tidak berkorelasi satu sama lain. Optimal global untuk sisi penyerang adalah jumlah dari optima lokal untuk setiap penyerang.

Kami beralih pada setiap penyerang kekuasaan PTsaya.

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

Untuk menemukan keputusan terbaik bagi penyerang, pertama-tama kita membuat daftar baru yang berasal l[saya]:

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

Setiap entri dalam daftar ini berisi ketangguhan pemblokir, diikuti oleh nilainya (kekuatan + ketangguhan). Saat membangun daftar ini, kami juga menghitungnyang merupakan jumlah dari kekuatan semua blocker. Kami akan menggunakannya nanti untuk menguji apakah penyerang dihancurkan. (Fakta bahwa jumlah menengah disimpan sebagai entri ke-3 untuk setiap pemblokir hanyalah trik golf untuk menghemat beberapa byte. Entri ke-3 ini tidak digunakan.)

Kami sekarang memanggil fungsi rekursif g dengan 2 parameter: daya Ppenyerang saat ini dan daftar yang ditentukan di atas. Fungsi ini mencoba semua cara yang mungkin untuk mengirim titik kerusakan di antara pemblokir.

(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)
)                         //

Memperbarui skor bek

Setelah setiap iterasi pada penyerang, kami memperbarui skor bek dengan:

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

Bagian kiri mengurangi skor penyerang jika itu bertahan. Bagian kanan mengurangi setengah ketangguhan penyerang jika serangan itu tidak diblokir sama sekali, atau menambahkan skor penyerang terbaik sebaliknya (yang merupakan yang terburuk dari perspektif sisi pertahanan).

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.