Magie: der Gathering Combat Golf

30

Magic: the Gathering ist ein Sammelkartenspiel, bei dem Spieler unter anderem Karten spielen, die Kreaturen darstellen, die dann den anderen Spieler angreifen oder die Angriffe des anderen Spielers durch Blockieren abwehren können.

Bei dieser Code-Golf-Herausforderung wird Ihr Programm an die Stelle eines Magic-Spielers treten, der entscheidet, wie der Kampf geblockt wird.


Jede Kreatur hat zwei relevante Attribute: Kraft und Zähigkeit. Die Kraft einer Kreatur ist die Menge an Schaden, die sie in einem Kampf verursachen kann, und ihre Zähigkeit ist die Menge an Schaden, die erforderlich ist, um sie zu zerstören. Die Leistung ist immer mindestens 0 und die Zähigkeit ist immer mindestens 1.

Während des Kampfes in Magic erklärt der Spieler, der an der Reihe ist, dass einige seiner Kreaturen den Gegner angreifen. Dann können die anderen Spieler, die als verteidigender Spieler bekannt sind, ihre Kreaturen als Blocker zuweisen. Eine Kreatur kann nur eine Kreatur pro Kampf blocken, aber mehrere Kreaturen können alle dieselbe Kreatur blocken.

Nachdem die Blocker deklariert wurden, entscheidet der angreifende Spieler für jede blockierte angreifende Kreatur, wie der von dieser Kreatur verursachte Schaden (entsprechend ihrer Stärke) auf die blockierten Kreaturen verteilt wird.

Dann wird Schaden zugefügt. Jede Kreatur verursacht Schaden in Höhe ihrer Stärke. Angreifende Kreaturen, die geblockt wurden, verursachen den oben beschriebenen Schaden. Nicht blockierte angreifende Kreaturen fügen dem verteidigenden Spieler Schaden zu. Blockende Kreaturen fügen der geblockten Kreatur Schaden zu. Kreaturen des verteidigenden Spielers, die nicht geblockt haben, verursachen keinen Schaden. (Kreaturen müssen nicht blocken.)

Schließlich wird jede Kreatur, die Schaden gleich oder größer als ihre Zähigkeit verursacht, zerstört und vom Schlachtfeld entfernt. Jede Menge an Schaden, die geringer ist als die Zähigkeit einer Kreatur, hat keine Auswirkung.


Hier ist ein Beispiel für diesen Prozess:

Eine Kreatur mit Kraft P und Zähigkeit T wird als dargestellt 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.

Der verteidigende Spieler hat 3 Ziele im Kampf: Zerstöre die Kreaturen des Gegners, bewahre die eigenen Kreaturen und füge so wenig Schaden wie möglich zu. Darüber hinaus sind Kreaturen mit mehr Kraft und Zähigkeit wertvoller.

Um diese zu einem einzigen Maß zu kombinieren, werden wir sagen, dass die Punktzahl des verteidigenden Spielers aus einem Kampf gleich der Summe der Kräfte und Härten seiner überlebenden Kreaturen abzüglich der Summe der Kräfte und Härten seiner überlebenden Kreaturen minus eins ist die Hälfte des dem verteidigenden Spieler zugefügten Schadens. Der verteidigende Spieler möchte diese Punktzahl maximieren, während der angreifende Spieler sie minimieren möchte.

In dem oben gezeigten Kampf war die Punktzahl:

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

Wenn der verteidigende Spieler in dem oben beschriebenen Kampf überhaupt nicht geblockt hätte, wäre die Punktzahl gewesen

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

Die optimale Wahl für den verteidigenden Spieler wäre gewesen, das 2/2mit 1/1und 1/4und das 3/3mit zu blockieren 0/1. Wenn sie dies getan hätten, hätten nur 1/4die 3/3überlebt, und dem verteidigenden Spieler wäre kein Schaden zugefügt worden, der das Tor erzielt hätte

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

Ihre Herausforderung besteht darin, ein Programm zu schreiben, das die optimale Blockierungsauswahl für den verteidigenden Spieler ausgibt. "Optimal" bedeutet die Wahl, die die Punktzahl maximiert, vorausgesetzt, der Gegner verteilt den Schaden so, dass die Punktzahl minimiert wird, vorausgesetzt, wie Sie geblockt haben.

Dies ist ein Maximum: Die maximale Punktzahl über die Schadensverteilungen, die die Punktzahl für jede Blockkombination minimiert.


Eingabe: Die Eingabe besteht aus zwei Listen mit 2 Tupeln, wobei jedes 2-Tupel die Form (Potenz, Zähigkeit) hat. Die erste Liste enthält die Kräfte und Härten jeder angreifenden Kreatur (der Kreaturen deines Gegners). Die zweite Liste enthält die Kräfte und Härten jeder deiner Kreaturen.

Tupel und Listen können in jedem geeigneten Format dargestellt werden, beispielsweise:

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

Ausgabe: Die Ausgabe besteht aus einer Reihe von 2 Tupeln in der Form (blockierende Kreatur, blockierte Kreatur), dh einer deiner Kreaturen, gefolgt von einer ihrer Kreaturen. Kreaturen werden durch ihren Index in den Eingabelisten referenziert. Indizes können 0 oder 1 indiziert sein. Auch hier ein beliebiges praktisches Format. Jede Bestellung ist in Ordnung. Beispielsweise könnte das optimale Blockierungsszenario von oben bei der obigen Eingabe wie folgt dargestellt werden:

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

Beispiele:

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

Die Ein- und Ausgabe kann über STDIN, STDOUT, CLA, Funktionseingabe / -rückgabe usw. erfolgen. Es gelten Standardlücken . Das ist Code-Golf: Der kürzeste Code in Bytes gewinnt.


Um die Spezifikation zu verdeutlichen und erste Ideen zu liefern, bietet dieser Pastebin eine Referenzlösung in Python. Die best_blockFunktion ist eine Musterlösung für diese Herausforderung, und das Ausführen des Programms bietet ausführlichere Ein- und Ausgaben.

isaacg
quelle
18
Du solltest diesen König des Hügels machen.
PyRulez
1
@Arnauld nein, das ist auch eine gültige Antwort.
Isaacg

Antworten:

6

JavaScript (ES7),  354  348 Byte

Übernimmt die Eingabe als ([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)`)

Probieren Sie es online!

Weniger golfen und formatiert

Dieser Code ist identisch mit der Golf-Version, nur ohne die mapAliase und die eval()Umhüllung für die Lesbarkeit.

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

Wie?

Initialisierung und Hauptschleife

0pushA

A = a.push([, 0])

Wir werden diese Dummy-Kreatur blockieren, anstatt überhaupt keine Kreatur zu blockieren. Dies ermöglicht einige Vereinfachungen im Code.

ADDN

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

SMoO

MO

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

Unsere Verteidigung aufbauen

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

Angriff optimieren

Die Entscheidungen der Angreifer sind nicht miteinander korreliert. Das globale Optimum für die angreifende Seite ist die Summe der lokalen Optima für jeden Angreifer.

PTi

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

l[i]

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

n

gP

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

Aktualisierung der Verteidigerwertung

Nach jeder Wiederholung eines Angreifers aktualisieren wir die Verteidigerwertung mit:

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

Der linke Teil subtrahiert die Punktzahl des Angreifers, wenn er überlebt hat. Der rechte Teil subtrahiert die Hälfte der Zähigkeit des Angreifers, wenn der Angriff überhaupt nicht geblockt wurde, oder fügt den besten Angreiferwert hinzu (der aus Sicht der verteidigenden Seite der schlechteste ist).

Arnauld
quelle