Magic: the Gathering Combat Golf

30

Magic: the Gathering es un juego de cartas coleccionables donde, entre otras cosas, los jugadores juegan cartas que representan criaturas, que luego pueden atacar al otro jugador o defenderse de los ataques del otro jugador bloqueando.

En este desafío de código de golf, su programa estará en el lugar de un jugador de Magic que decida cómo bloquear en combate.


Cada criatura tiene dos atributos relevantes: poder y resistencia. El poder de una criatura es la cantidad de daño que puede causar en un combate, y su resistencia es la cantidad de daño necesaria para destruirla. La potencia siempre es al menos 0, y la tenacidad siempre es al menos 1.

Durante el combate en Magic, el jugador cuyo turno es declara que algunas de sus criaturas están atacando al oponente. Luego, el otro jugador, conocido como el jugador defensor, puede asignar a sus criaturas como bloqueadores. Una criatura puede bloquear solo una criatura por combate, pero varias criaturas pueden bloquear a la misma criatura.

Después de que se declaran los bloqueadores, el jugador atacante decide, por cada criatura atacante que fue bloqueada, cómo distribuir el daño (igual a su poder) que esa criatura inflige a las criaturas que lo bloquean.

Luego, se inflige daño. Cada criatura hace daño igual a su poder. Las criaturas atacantes que fueron bloqueadas infligen daño como se describe arriba. Las criaturas atacantes desbloqueadas infligen daño al jugador defensor. Las criaturas bloqueadoras hacen daño a la criatura que bloquearon. Las criaturas que pertenecen al jugador defensor que no bloquearon no infligen daño. (No se requiere que las criaturas bloqueen).

Finalmente, cualquier criatura con daño igual o mayor que su resistencia es destruida y eliminada del campo de batalla. Cualquier cantidad de daño menor que la resistencia de una criatura no tiene efecto.


Aquí hay un ejemplo de este proceso:

Una criatura con poder P y resistencia T se representa como 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.

El jugador defensor tiene 3 objetivos en combate: destruir las criaturas del oponente, preservar las propias criaturas y recibir el menor daño posible. Además, las criaturas con más poder y resistencia son más valiosas.

Para combinarlos en una sola medida, diremos que la puntuación del jugador defensor de un combate es igual a la suma de los poderes y la resistencia de sus criaturas sobrevivientes, menos la suma de los poderes y la resistencia de las criaturas sobrevivientes de su oponente, menos uno La mitad de la cantidad de daño infligido al jugador defensor. El jugador defensor quiere maximizar este puntaje, mientras que el jugador atacante quiere minimizarlo.

En el combate que se muestra arriba, el puntaje fue:

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

Si el jugador defensor no hubiera bloqueado en absoluto en el combate descrito anteriormente, la puntuación habría sido

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

La elección óptima para el jugador defensor habría sido bloquear el 2/2con el 1/1y el 1/4, y bloquear el 3/3con el 0/1. Si lo hubieran hecho, solo 1/4y el 3/3habría sobrevivido, y no se habría causado ningún daño al jugador defensor, haciendo el puntaje

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

Su desafío es escribir un programa que genere la opción de bloqueo óptima para el jugador defensor. "Óptimo" significa la elección que maximiza la puntuación, dado que el oponente distribuye el daño de la manera que minimiza la puntuación, dada la forma en que bloqueaste.

Esta es una maximina: el puntaje máximo sobre las distribuciones de daño que minimiza el puntaje para cada combinación de bloqueo.


Entrada: La entrada consistirá en dos listas de 2 tuplas, donde cada 2 tuplas tiene la forma (potencia, resistencia). La primera lista contendrá los poderes y la resistencia de cada criatura atacante (las criaturas de tu oponente). La segunda lista contendrá los poderes y la resistencia de cada una de tus criaturas.

Las tuplas y las listas se pueden representar en cualquier formato conveniente, como:

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

Salida: La salida consistirá en una serie de 2 tuplas, en la forma (criatura bloqueadora, criatura bloqueada), es decir, una de tus criaturas seguida de una de sus criaturas. Se hará referencia a las criaturas por su índice en las listas de entrada. Los índices pueden ser 0 o 1 indexados. De nuevo, cualquier formato conveniente. Cualquier orden está bien. Por ejemplo, el escenario de bloqueo óptimo desde arriba, dada la entrada anterior, podría representarse como:

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

Ejemplos:

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

La entrada y salida puede ser a través de STDIN, STDOUT, CLA, entrada / retorno de funciones, etc. Se aplican lagunas estándar . Esto es code-golf: el código más corto en bytes gana.


Para aclarar las especificaciones y proporcionar ideas iniciales, este pastebin proporciona una solución de referencia en Python. La best_blockfunción es una solución de muestra para este desafío, y ejecutar el programa proporcionará entradas y salidas más detalladas.

isaacg
fuente
18
Deberías hacer de este rey de la colina.
PyRulez
1
@Arnauld no, esa también es una respuesta válida.
Isaac

Respuestas:

6

JavaScript (ES7),  354  348 bytes

Toma entrada como ([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)`)

Pruébalo en línea!

Menos golf y formateado

Este código es idéntico a la versión de golf, solo que sin los mapalias y la eval()envoltura para facilitar la lectura.

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

¿Cómo?

Inicialización y lazo principal

0pushA

A = a.push([, 0])

Vamos a bloquear a esta criatura ficticia en lugar de bloquear a ninguna criatura. Esto permite algunas simplificaciones en el código.

ADDN

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

SMoO

MO

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

Construyendo nuestra defensa

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

Optimizando el ataque

Las decisiones de los atacantes no están correlacionadas entre sí. El óptimo global para el lado atacante es la suma de los óptimos locales para cada atacante.

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

Actualizar el puntaje del defensor

Después de cada iteración en un atacante, actualizamos el puntaje del defensor con:

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

La parte izquierda resta el puntaje del atacante si ha sobrevivido. La parte derecha resta la mitad de la resistencia del atacante si el ataque no fue bloqueado en absoluto, o agrega la mejor puntuación de atacante de lo contrario (que es la peor desde la perspectiva del lado defensor).

Arnauld
fuente