Magic: The Gathering Combat Golf

30

Magic: the Gathering é um jogo de cartas colecionáveis ​​em que, entre outras coisas, os jogadores jogam cartas representando criaturas, que podem atacar o outro jogador ou se defender contra os ataques do outro jogador, bloqueando.

Neste desafio de código-golfe, seu programa estará no lugar de um jogador de Magic que decide como bloquear em combate.


Cada criatura tem dois atributos relevantes: Poder e resistência. O poder de uma criatura é a quantidade de dano que ela pode causar em um combate, e sua resistência é a quantidade de dano necessária para destruí-la. A energia é sempre pelo menos 0 e a resistência é sempre pelo menos 1.

Durante o combate em Magic, o jogador do turno declara que algumas de suas criaturas estão atacando o oponente. Então, o outro jogador, conhecido como defensor, pode atribuir suas criaturas como bloqueadores. Uma criatura pode bloquear apenas uma criatura por combate, mas várias criaturas podem bloquear a mesma criatura.

Depois que os bloqueadores são declarados, o jogador atacante decide, para cada criatura atacante que foi bloqueada, como distribuir o dano (igual ao seu poder) que aquela criatura causa às criaturas que o bloqueiam.

Então, o dano é causado. Cada criatura causa dano igual ao seu poder. As criaturas atacantes que foram bloqueadas causam dano conforme descrito acima. Criaturas atacantes desbloqueadas causam dano ao jogador defensor. As criaturas bloqueadoras causam dano à criatura que eles bloquearam. Criaturas pertencentes ao jogador defensor que não bloquearam não causam dano. (As criaturas não precisam bloquear.)

Finalmente, qualquer criatura que cause dano igual ou superior a sua resistência é destruída e removida do campo de batalha. Qualquer quantidade de dano menor que a resistência de uma criatura não tem efeito.


Aqui está um exemplo deste processo:

Uma criatura com poder P e resistência T é representada 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.

O jogador defensor tem 3 objetivos em combate: destruir as criaturas do oponente, preservar as próprias criaturas e receber o menor dano possível. Além disso, criaturas com mais poder e resistência são mais valiosas.

Para combiná-los em uma única medida, diremos que a pontuação do jogador defensor de um combate é igual à soma dos poderes e durezas de suas criaturas sobreviventes, menos a soma dos poderes e durezas das criaturas sobreviventes de seu oponente, menos um metade da quantidade de dano causado ao jogador defensor. O jogador defensor deseja maximizar essa pontuação, enquanto o jogador atacante deseja minimizá-la.

No combate mostrado acima, a pontuação foi:

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

Se o jogador defensor não tivesse bloqueado o combate descrito acima, a pontuação teria sido

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

A melhor opção para o jogador defensor seria bloquear o 2/2com o 1/1e 1/4oe bloquear o 3/3com o 0/1. Se eles tivessem feito isso, apenas 1/4o 3/3sobreviveria e nenhum dano teria sido causado ao jogador defensor, fazendo a pontuação

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

Seu desafio é escrever um programa que produza a melhor opção de bloqueio para o jogador defensor. "Ótimo" significa a escolha que maximiza a pontuação, dado que o oponente distribui dano da maneira que minimiza a pontuação, considerando como você bloqueou.

Esta é uma pontuação máxima: a pontuação máxima sobre as distribuições de dano que minimizam a pontuação de cada combinação de bloqueio.


Entrada: A entrada consiste em duas listas de 2 tuplas, em que cada 2 tuplas é da forma (Potência, Robustez). A primeira lista conterá os poderes e as resistências de cada criatura atacante (as criaturas do seu oponente). A segunda lista conterá os poderes e as durezas de cada uma de suas criaturas.

Tuplas e listas podem ser representadas em qualquer formato conveniente, como:

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

Saída: A saída consistirá em uma série de duas tuplas, na forma (criatura bloqueadora, criatura bloqueada) - ou seja, uma de suas criaturas seguida por uma de suas criaturas. As criaturas serão referenciadas por seu índice nas listas de entrada. Os índices podem ser 0 ou 1 indexados. Mais uma vez, qualquer formato conveniente. Qualquer pedido é bom. Por exemplo, o cenário ideal de bloqueio acima, considerando a entrada acima, pode ser representado como:

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

Exemplos:

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

A entrada e a saída podem ser via STDIN, STDOUT, CLA, entrada / retorno de função, etc. São aplicadas brechas padrão . Este é o código-golfe: o código mais curto em bytes vence.


Para esclarecer as especificações e fornecer idéias iniciais, este pastebin fornece uma solução de referência em Python. A best_blockfunção é uma solução de exemplo para esse desafio e a execução do programa fornecerá entradas e saídas mais detalhadas.

isaacg
fonte
18
Você deveria fazer deste rei da colina.
PyRulez
1
@ Arnauld não, isso também é uma resposta válida.
isaacg 09/02

Respostas:

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

Experimente online!

Menos jogado e formatado

Este código é idêntico à versão em golf, apenas sem os mapaliases e o eval()invólucro para facilitar a leitura.

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

Quão?

Inicialização e loop principal

A primeira coisa que fazemos é adicionar uma criatura manequim atacante com um poder indefinido e uma resistência de . O método retorna o novo número de criaturas atacantes, incluindo esta.0ApushA

A = a.push([, 0])

Vamos bloquear esta criatura fictícia em vez de não bloquear nenhuma criatura. Isso permite algumas simplificações no código.

O número de combinações para o jogador defensor pode ser expresso como , onde é o número de defensores. Usamos um loop for com o contador para iterar todas essas possibilidades.ADDNN

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

A pontuação da iteração atual e a pontuação máxima são armazenadas em e respectivamente. Da mesma forma, a solução atual ea melhor solução são armazenados em e .SMoO

MO

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

Construindo nossa defesa

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

Otimizando o ataque

As decisões dos atacantes não se correlacionam. O ideal global para o lado atacante é a soma das ótimas locais para cada invasor.

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

Atualizando a pontuação do defensor

Após cada iteração em um atacante, atualizamos a pontuação do defensor com:

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

A parte esquerda subtrai a pontuação do atacante, se ele sobreviveu. A parte direita subtrai metade da resistência do atacante, se o ataque não foi bloqueado, ou adiciona a melhor pontuação de atacante de outra forma (que é a pior da perspectiva do lado defensivo).

Arnauld
fonte