マジック:ザギャザリングコンバットゴルフ


30

マジック:ザギャザリングはトレーディングカードゲームで、特にプレイヤーはクリーチャーを表すカードをプレイします。クリーチャーは他のプレイヤーを攻撃したり、ブロックして他のプレイヤーの攻撃から防御したりできます。

このコードゴルフチャレンジでは、戦闘でブロックする方法を決定するマジックプレーヤーの代わりにプログラムを作成します。


各クリーチャーには、パワーとタフネスという2つの関連する属性があります。クリーチャーのパワーは、戦闘で与えることができるダメージの量であり、そのタフネスはそれを破壊するのに必要なダメージの量です。パワーは常に少なくとも0で、タフネスは常に少なくとも1です。

マジックでの戦闘中、自分の順番が変わるプレイヤーは、自分のクリーチャーの一部が対戦相手を攻撃していると宣言します。次に、防御プレイヤーとして知られる他のプレイヤーは、クリーチャーをブロッカーとして割り当てることができます。クリーチャーは戦闘ごとに1つのクリーチャーのみをブロックできますが、複数のクリーチャーはすべて同じクリーチャーをブロックできます。

ブロッカーが宣言された後、攻撃プレイヤーは、ブロックされた攻撃クリーチャーごとに、そのクリーチャーがそれをブロックしているクリーチャーに与えるダメージ(そのパワーに等しい)をどのように分配するかを決定します。

その後、ダメージが与えられます。各クリーチャーは、その力に等しいダメージを与えます。ブロックされた攻撃クリーチャーは、上記のようにダメージを与えます。ブロックされていない攻撃クリーチャーは、防御プレイヤーにダメージを与えます。ブロックしているクリーチャーは、ブロックしたクリーチャーにダメージを与えます。ブロックしなかった防御プレイヤーに属するクリーチャーはダメージを与えません。(ブロックするのにクリーチャーは必要ありません。)

最後に、そのタフネス以上のダメージを与えられたクリーチャーは破壊され、戦場から取り除かれます。クリーチャーのタフネス未満のダメージは効果がありません。


このプロセスの例を次に示します。

パワー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つの目標があります:相手のクリーチャーを破壊し、自分のクリーチャーを保存し、できるだけダメージを与えない。さらに、より多くのパワーとタフネスを持つクリーチャーはより価値があります。

これらを1つの尺度にまとめるために、戦闘からの防御プレイヤーのスコアは、生き残ったクリーチャーのパワーとタフネスの合計から、相手の生き残ったクリーチャーのパワーとタフネスの合計から1を引いたものに等しいと言います。防御側プレイヤーに与えられたダメージの半分。防御側のプレーヤーはこのスコアを最大化したいのに対し、攻撃側のプレーヤーはこのスコアを最小化したいのです。

上記の戦闘では、スコアは次のとおりでした。

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/21/11/4し、ブロックするよう3/30/1。彼らはそうしていた場合は、のみ1/43/3生き残っているだろう、と何の損傷が得点を作る、防御プレイヤーに配られていないでしょう

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

あなたの挑戦は、防御側のプレイヤーに最適なブロックの選択を出力するプログラムを書くことです。「最適」とは、ブロック方法に応じて、対戦相手がスコアを最小化する方法でダメージを分配することを考えると、スコアを最大化する選択肢を意味します。

これは最大値です。各ブロックの組み合わせのスコアを最小化するダメージ分布の最大スコア。


入力:入力は2タプルの2つのリストで構成され、各2タプルは(Power、Toughness)の形式です。最初のリストには、攻撃している各クリーチャー(対戦相手のクリーチャー)のパワーとタフネスが含まれます。2番目のリストには、各クリーチャーのパワーとタフネスが含まれます。

タプルとリストは、次のような便利な形式で表すことができます。

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

出力:出力は、フォーム(ブロッキングクリーチャー、ブロッククリーチャー)の一連の2タプルで構成されます。つまり、自分のクリーチャーの1つとその後にクリーチャーの1つが続きます。クリーチャーは、入力リスト内のインデックスによって参照されます。インデックスには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、関数の入力/戻りなどがあります。標準の抜け穴が適用されます。これはコードゴルフです。バイト単位の最短コードが優先されます。


仕様を明確にし、最初のアイデアを提供するために、このpastebinはPythonの参照ソリューションを提供します。best_blockこの関数は、この課題に対する解決策のサンプルで、プログラムを実行すると、より詳細な入力と出力を提供します。


18
この丘の王を作るべきです。
PyRulez

1
@Arnauldいや、それも有効な答えです。
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
}

どうやって?

初期化とメインループ

0pushA

A = a.push([, 0])

まったくクリーチャーをブロックするのではなく、このダミークリーチャーをブロックします。これにより、コードを簡素化できます。

ADDN

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

SMoO

MO

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

防衛を構築する

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

攻撃の最適化

攻撃者の決定は相互に無関係です。攻撃側のグローバルな最適値は、各攻撃者のローカル最適値の合計です。

PT

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

l[]

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

ディフェンダースコアの更新

攻撃者の各反復の後、次のように防御者のスコアを更新します。

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

左の部分は、攻撃者が生き残っている場合、攻撃者のスコアを引きます。右側の部分は、攻撃がまったくブロックされていない場合は攻撃者のタフネスの半分を差し引き、それ以外の場合は最高の攻撃者スコアを追加します(防御側の観点からは最悪のスコアです)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.