ブラウザ上で動作するドラフトビュアーを作った
MOのドラフトビュアー作りました
前にローカルで動作するドラフトビュアーをつくったんですが、 やっぱりオンライン上で動かないと面倒で使われませんでした。 オンラインのビュアーはDraft Converter - Imagesをよく使わして頂いてたのですが、 MO上のように、過去ピックが見えないとわかりづらいと思うんです。
なので、かわりのものを自分で作ってみました。 ぜひ以下のURLから飛んで一度試して見てください。
外観
特徴
- 過去のピックが一覧できる
灰色のゾーンが過去のピック
カードをクリックすると拡大する
ディスプレイが小さい場合に、1行に表示するカードの枚数が自動で少なくなる
使い方
- フォルダのマークをクリック
- MOのピック譜のデータを選択
- submitボタンをクリック
- Nextボタンで次のピックへ
今後の更新
まだ作ったばかりで、使い勝手が悪いと思います。
使いにくいところがあればコメント欄に書いていただけると嬉しいです。
今後の更新予定は
- カードの日本語対応
- サイトの見た目(センスある人助けて。。。)
- ピック譜の共有
を考えています。
ピックの共有は、ピック譜を読み込んだときに、 そのピック譜のドラフトが閲覧できるURLを生成して、 そのURLをみなさんのブログやtiwtterに貼ることで、 ピック譜の共有をできればいいなと思っています。
戦乱のゼンディカーのMOリリースから5日 ドラフト点数表を作ってみた ~環境初期のみんなのカード評価~
みんなが納得できるドラフト点数表の作成に向けて
一人のプロプレイヤーが考えたドラフト点数表よりもみんなの英知を集めた点数表のほうが優れているのではないだろうか? そのような考えのもと先日模擬的にKTKのドラフト点数表の作成した。 *1
今回は戦乱のゼンディカー(BFZ)のカードの評価を行った。自分のピック譜と協力いただいた方のピック譜を合わせて22の譜からデータを作成した。ご協力頂いた方ありがとうございます。
全アンコモン+コモン
アンコモンとコモンの点数は以下の表のようになった。
カード名 | 点 | 流れてきた回数 | マナコスト | レアリティ |
---|---|---|---|---|
Rolling Thunder | 26.6666666666667 | 1 | X R R | U |
Coastal Discovery | 53.3333333333333 | 2 | 3 U | U |
Grip of Desolation | 53.3333333333333 | 2 | 4 B B | U |
Drana's Emissary | 53.3333333333333 | 2 | 1 W B | U |
Ruination Guide | 53.3333333333333 | 2 | 2 U | U |
Brood Monitor | 53.3333333333333 | 2 | 4 G G | U |
Kor Bladewhirl | 106.666666666667 | 4 | 1 W | U |
Bane of Bala Ged | 106.666666666667 | 4 | 7 | U |
Touch of the Void | 121.2 | 12 | 2 R | C |
Sheer Drop | 131.3 | 13 | 2 W | C |
Gideon's Reproach | 131.3 | 13 | 1 W | C |
Windrider Patrol | 133.333333333333 | 5 | 3 U U | U |
Vile Aggregate | 133.333333333333 | 5 | 2 R | U |
Ulamog's Despoiler | 133.333333333333 | 5 | 6 | U |
Benthic Infiltrator | 151.5 | 15 | 2 U | C |
Stasis Snare | 160 | 6 | 1 W W | U |
Ulamog's Nullifier | 160 | 6 | 2 U B | U |
Turn Against | 160 | 6 | 4 R | U |
Adverse Conditions | 160 | 6 | 3 U | U |
Angel of Renewal | 160 | 6 | 5 W | U |
Retreat to Emeria | 160 | 6 | 3 W | U |
Kozilek's Channeler | 161.6 | 16 | 5 | C |
Complete Disregard | 181.8 | 18 | 2 B | C |
Carrier Thrall | 186.666666666667 | 7 | 1 B | U |
Hedron Archive | 186.666666666667 | 7 | 4 | U |
Roil's Retribution | 186.666666666667 | 7 | 3 W W | U |
Eldrazi Skyspawner | 191.9 | 19 | 2 U | C |
Deathless Behemoth | 213.333333333333 | 8 | 6 | U |
Transgress the Mind | 213.333333333333 | 8 | 1 B | U |
Mist Intruder | 222.2 | 22 | 1 U | C |
Smite the Monstrous | 222.2 | 22 | 3 W | C |
Courier Griffin | 232.3 | 23 | 3 W | C |
Kor Entanglers | 240 | 9 | 4 W | U |
Skitterskin | 240 | 9 | 3 B | U |
Tajuru Warcaller | 240 | 9 | 3 G G | U |
Kalastria Healer | 242.4 | 24 | 1 B | C |
Lifespring Druid | 242.4 | 24 | 2 G | C |
Clutch of Currents | 252.5 | 25 | U | C |
Snapping Gnarlid | 262.6 | 26 | 1 G | C |
Chasm Guide | 266.666666666667 | 10 | 3 R | U |
Pilgrim's Eye | 266.666666666667 | 10 | 3 | U |
Murasa Ranger | 266.666666666667 | 10 | 3 G | U |
Roil Spout | 266.666666666667 | 10 | 1 W U | U |
Retreat to Hagra | 266.666666666667 | 10 | 2 B | U |
Encircling Fissure | 266.666666666667 | 10 | 2 W | U |
Bloodbond Vampire | 266.666666666667 | 10 | 2 B B | U |
Hagra Sharpshooter | 266.666666666667 | 10 | 2 B | U |
Stonefury | 272.7 | 27 | 3 R R | C |
Dominator Drone | 282.8 | 28 | 2 B | C |
Nettle Drone | 282.8 | 28 | 2 R | C |
Evolving Wilds | 282.8 | 28 | C | |
Incubator Drone | 292.9 | 29 | 3 U | C |
Grove Rumbler | 293.333333333333 | 11 | 2 R G | U |
Horribly Awry | 293.333333333333 | 11 | 1 U | U |
Molten Nursery | 293.333333333333 | 11 | 2 R | U |
Blighted Cataract | 293.333333333333 | 11 | U | |
Dampening Pulse | 320 | 12 | 3 U | U |
Resolute Blademaster | 320 | 12 | 3 R W | U |
Zulaport Cutthroat | 320 | 12 | 1 B | U |
Tide Drifter | 320 | 12 | 1 U | U |
Catacomb Sifter | 320 | 12 | 1 B G | U |
Akoum Stonewaker | 320 | 12 | 1 R | U |
Makindi Sliderunner | 333.3 | 33 | 1 R | C |
Rising Miasma | 346.666666666667 | 13 | 3 B | U |
Expedition Envoy | 346.666666666667 | 13 | W | U |
Tunneling Geopede | 346.666666666667 | 13 | 2 R | U |
Serene Steward | 346.666666666667 | 13 | 1 W | U |
Malakir Familiar | 346.666666666667 | 13 | 2 B | U |
Blighted Fen | 346.666666666667 | 13 | U | |
Cryptic Cruiser | 346.666666666667 | 13 | 3 U | U |
Halimar Tidecaller | 346.666666666667 | 13 | 2 U | U |
Blighted Woodland | 346.666666666667 | 13 | U | |
Makindi Patrol | 353.5 | 35 | 2 W | C |
Sludge Crawler | 363.6 | 36 | B | C |
Vampiric Rites | 373.333333333333 | 14 | B | U |
Herald of Kozilek | 373.333333333333 | 14 | 1 U R | U |
Rot Shambler | 373.333333333333 | 14 | 1 G | U |
Murk Strider | 373.7 | 37 | 3 U | C |
Blisterpod | 383.8 | 38 | G | C |
Outnumber | 383.8 | 38 | R | C |
Valakut Predator | 383.8 | 38 | 2 R | C |
Culling Drone | 383.8 | 38 | 1 B | C |
Ondu Champion | 383.8 | 38 | 2 R R | C |
Salvage Drone | 393.9 | 39 | U | C |
Unnatural Aggression | 393.9 | 39 | 2 G | C |
Plated Crusher | 400 | 15 | 4 G G G | U |
Grovetender Druids | 400 | 15 | 2 G W | U |
Ondu Rising | 400 | 15 | 1 W | U |
Breaker of Armies | 400 | 15 | 8 | U |
Spell Shrivel | 404 | 40 | 2 U | C |
Ondu Greathorn | 404 | 40 | 3 W | C |
Valakut Invoker | 414.1 | 41 | 2 R | C |
Eyeless Watcher | 414.1 | 41 | 3 G | C |
Call the Scions | 414.1 | 41 | 2 G | C |
Ghostly Sentinel | 424.2 | 42 | 4 W | C |
Kozilek's Sentinel | 424.2 | 42 | 1 R | C |
Kor Castigator | 424.2 | 42 | 1 W | C |
Territorial Baloth | 424.2 | 42 | 4 G | C |
Skyrider Elf | 426.666666666667 | 16 | X G U | U |
Unified Front | 426.666666666667 | 16 | 3 W | U |
Oran-Rief Invoker | 434.3 | 43 | 1 G | C |
Silent Skimmer | 434.3 | 43 | 3 B | C |
Shadow Glider | 444.4 | 44 | 2 W | C |
Tajuru Stalwart | 444.4 | 44 | 2 G | C |
Titan's Presence | 453.333333333333 | 17 | 3 | U |
Ulamog's Reclaimer | 453.333333333333 | 17 | 4 U | U |
Retreat to Coralhelm | 453.333333333333 | 17 | 2 U | U |
Vestige of Emrakul | 464.6 | 46 | 3 R | C |
Lithomancer's Focus | 474.7 | 47 | W | C |
Kalastria Nightwatch | 474.7 | 47 | 4 B | C |
Rush of Ice | 474.7 | 47 | U | C |
Firemantle Mage | 480 | 18 | 2 R | U |
Crumble to Dust | 480 | 18 | 3 R | U |
Cloud Manta | 494.9 | 49 | 3 U | C |
Fertile Thicket | 505 | 50 | C | |
Anticipate | 505 | 50 | 1 U | C |
Tightening Coils | 505 | 50 | 1 U | C |
Mind Raker | 505 | 50 | 3 B | C |
Coralhelm Guide | 505 | 50 | 1 U | C |
Nirkana Assassin | 505 | 50 | 2 B | C |
Felidar Cub | 505 | 50 | 1 W | C |
Pathway Arrows | 506.666666666667 | 19 | 1 | U |
Retreat to Kazandu | 506.666666666667 | 19 | 2 G | U |
Processor Assault | 506.666666666667 | 19 | 1 R | U |
Voracious Null | 515.1 | 51 | 2 B | C |
Cliffside Lookout | 525.2 | 52 | W | C |
Eldrazi Devastator | 525.2 | 52 | 8 | C |
Spawning Bed | 533.333333333333 | 20 | U | |
Jaddi Offshoot | 533.333333333333 | 20 | G | U |
Blighted Gorge | 533.333333333333 | 20 | U | |
Demon's Grasp | 535.3 | 53 | 4 B | C |
Giant Mantis | 535.3 | 53 | 3 G | C |
Swell of Growth | 545.4 | 54 | 1 G | C |
Bone Splinters | 545.4 | 54 | B | C |
Tandem Tactics | 545.4 | 54 | 1 W | C |
Natural Connection | 555.5 | 55 | 2 G | C |
Oracle of Dust | 565.6 | 56 | 4 U | C |
Tajuru Beastmaster | 565.6 | 56 | 5 G | C |
Swarm Surge | 565.6 | 56 | 2 B | C |
Fortified Rampart | 575.7 | 57 | 1 W | C |
Sure Strike | 575.7 | 57 | 1 R | C |
Reclaiming Vines | 575.7 | 57 | 2 G G | C |
Belligerent Whiptail | 585.8 | 58 | 3 R | C |
Scythe Leopard | 586.666666666667 | 22 | G | U |
Slab Hammer | 586.666666666667 | 22 | 2 | U |
Forerunner of Slaughter | 586.666666666667 | 22 | B R | U |
Geyserfield Stalker | 595.9 | 59 | 4 B | C |
Hedron Blade | 606 | 60 | 1 | C |
Boiling Earth | 606 | 60 | 1 R | C |
Void Attendant | 613.333333333333 | 23 | 2 G | U |
Retreat to Valakut | 613.333333333333 | 23 | 2 R | U |
Stone Haven Medic | 636.3 | 63 | 1 W | C |
Wave-Wing Elemental | 656.5 | 65 | 5 U | C |
Scour from Existence | 656.5 | 65 | 7 | C |
Ruin Processor | 656.5 | 65 | 7 | C |
Goblin War Paint | 656.5 | 65 | 1 R | C |
Shatterskull Recruit | 666.6 | 66 | 3 R R | C |
Looming Spires | 666.6 | 66 | C | |
Mire's Malice | 666.6 | 66 | 3 B | C |
Reckless Cohort | 666.6 | 66 | 1 R | C |
Sylvan Scrying | 666.666666666667 | 25 | 1 G | U |
Earthen Arms | 676.7 | 67 | 1 G | C |
Inspired Charge | 676.7 | 67 | 2 W W | C |
Dutiful Return | 686.8 | 68 | 3 B | C |
Angelic Gift | 686.8 | 68 | 1 W | C |
Plummet | 686.8 | 68 | 1 G | C |
Seek the Wilds | 696.9 | 69 | 1 G | C |
Altar's Reap | 707 | 70 | 1 B | C |
Grave Birthing | 727.2 | 72 | 2 B | C |
Broodhunter Wurm | 737.3 | 73 | 3 G | C |
Brilliant Spectrum | 757.5 | 75 | 3 U | C |
Roilmage's Trick | 767.6 | 76 | 3 U | C |
Sandstone Bridge | 777.7 | 77 | C | |
Mortuary Mire | 777.7 | 77 | C | |
Lavastep Raider | 787.8 | 78 | R | C |
Dispel | 808 | 80 | U | C |
Volcanic Upheaval | 838.3 | 83 | 3 R | C |
Skyline Cascade | 868.6 | 86 | C | |
Blighted Steppe | 880 | 33 | U | |
Kitesail Scout | 919.1 | 91 | W | C |
Infuse with the Elements | 986.666666666667 | 37 | 3 G | U |
ピックアップ
まずは一番ピックされている上位6枚を見てみる。誤差の範囲だが《とどろく雷鳴/Rolling Thunder(BFZ)》は唯一1度しか流れてこなかったカードで、 最高点数がついている。だれが見ても強いとわかる納得の1枚である。
そのほかのカードで見ると、《ドラーナの使者/Drana's Emissary(BFZ)》は多色であり、2パック目3パック目では2色ともあっていなければほぼ流すカードでありながら この点数がつくことからカードパワーの高さが伺える。 片方の色があっていたらタッチしてでも使う人も多いのではないかと考えれる。
6マナの除去がこの位置に入るのは意外に感じる。この環境において覚醒土地・能力持ちの土地破壊に意味を見出しているか、環境の速度的に遅い除去でも強いと考えれているのだろうか。
ピックアップ コモン
コモンだけに目を向けると以下のカードが上位となっている。1番はいろんなデッキで使いやすいリムーブ付きの火力である《虚空の接触/Touch of the Void(BFZ)》であった。
7枚の内4枚は除去であった。白の除去は上位に何枚かある一方でクリーチャーは少なく、コントロールが組みやすくビートはアンコ以上がないと難しいことがわかる。
終わりに
プレイヤーとしてのレベルの問題であまりあれこれ各カードについて言及することはできないが、 私にとって意外に高い・低いと感じたカードはたくさんあったし、皆さんもそう感じられるカードがあったのではないかと思う。 このデータは22ピック分しかないので、確度が低いがより多くのデータを集められると細かい点数がわかる。 また、このカードが回っているときはこれも回りやすいといったような相関関係も見れないだろうかと考えている。 定期的に点数化することでメタの変遷なんかも見れたりもするだろう。
ユーザー名の部分等は消して頂いて問題ないので、もし興味がございましたらぜひメールにてピック譜を送って頂けるとうれしいです。 ご協力いただいた方、本当にありがとうございました。
mail: delver0234@yahoo.co.jp
みんなが納得できるドラフト点数表は作れるのだろうか ~ゼンディカーに向けて、いまさらKTK×3環境の点数表を作ってみた~
ドラフト点数表は信用していいのか?
ドラフトのピックを行う上で、ドラフト点数表を参考にする方は多い。LSVは新セット発売のたびに個人的なカード評価を点数化して記事にしているし、wikiにも誰が書いているのかはわからないがカードの評価が書かれている。
http://mtgwiki.com/wiki/%E3%83%89%E3%83%A9%E3%83%95%E3%83%88%E7%82%B9%E6%95%B0%E8%A1%A8
一方で、自分の過去のピックによって優先度は変わるのだからカードに点数つけて順番に取っていっても強いわけないから意味がないといった意見や、個人の感覚に依存した点数表は信頼できないといったことがよく言われる。確かにクリーチャーが取れてないからクリーチャーの点数を上げようなどということは多くのプレイヤーがやっていることであるし、デッキの完成をイメージしてピックしたほうがいい。アーキタイプドラフトの基本については行弘さんが記事を執筆されている。
とはいってもみんなドラフト点数表は使う
アーキタイプドラフトといっても、ファーストピックは一番点数の高いカードを取るケースが多い。また、~のカードが流れてきているからこの色・アーキタイプが空いているのだろうといった予想においても点数表を利用することになる。私のような初心者の助けにもなる点数表はなんだかんだいっても有用なものであると思う。2chなんかでも環境初期は○○より××のほうが強いなんていうレスはたくさんあり、なんだかんだでみんな点数表のことが大好きである。ただ、LSVのような強いプレイヤーであってもアドが取れるカードに高い点数をつけるやすいといったような好みが出るもので、ほかのプレイヤーに聞くと意見が分かれるものである。このような一人のアド厨の感覚に依存した点数表を信頼していいものだろうか?
もっといいドラフト点数表を作りたい ⇒ 集団知の利用
私が感覚でカードに点数をつけたとして、当然LSVより有用なものを作ることはできない。私一人では点数をつけれないのであれば皆の力を借りればいいのである。そこで以下のブログの記事を見てほしい。
「集団から得た意見は専門家1人で出した答えよりも正解に近づく」という考え方があるらしい。つまり一般のMTGプレイヤーの意見を統合すれば専門家(=LSV)よりも正解に近づくのだ。Wisdom Guidさんでこれと同じ考え方の点数表を作成されている。web上で2つカードを表示させてどっちとるかを閲覧者に選ばせてその結果を統計的にまとめられている。
おもしろい試みだと思うが、選択の本気度を考えるとMOのピック譜を利用すべきである。
ピック譜から点数表の作成
点数化の方法として流れてきたカードを元に作成する。基本的な考え方として流れてきたカードは取られたカードよりも点数が低いので、何度も流れてくる回数を数えて少ないほうから並べればカードの優先順位となる。ただし、レアリティが低いと流れてくる回数は当然少なくなるのでパックに封入される確率を割ることで補正をかける。タルキール覇王譚の場合、コモンは101枚収録されており、パックには10枚のコモンが入っているので101/10をかけ、アンコモンは80枚収録でパックに3枚なので80/3をかける。 私が最近の環境はあまりプレイできていないので、KTK×3の環境で私がプレイした30ゲーム分のデータを使って点数化してみた。レアはサンプルが少ないのでアンコモンとコモンだけを表にして下に示す。 一番点数が高いのは、鷹匠、切断、戦僧侶となった。その下にも休息地の見張り、道の求道者と続いており、単純に点数で取ると白から入ることが多くなることがわかる。また白の参入者が多くなるため、武器を手になど思ったより高い点数で取られているカードもある。多色も上位は白がらみが多い。逆に青意カードはジェスカイの長老が最高点で、レアがないと入りにくい色であるといえる。こうして並べてみると、停止の場より点数が高いカードが5枚もあるから、停止の場が4手目くらいに流れてきたからといって白が空いているとは限らない一方で、荒野の後継者が流れてきたら緑は空いているといった考え方ができる。
戦乱のゼンディカーに適用(したい)
実験的にKTK×3の環境で点数表を作ってみたが、同じ点数表を次の戦乱のゼンディカーの環境初期に作成すれば環境理解に役立つはずである。この点数表は30ゲーム分だが、アンコモンの順位をちゃんと決めるにはサンプル数が足りていない。もし興味を持って頂ける方がいれば、次環境でのMOでのピック譜をぜひメールで譲って頂けないでしょうか?まとめたものをアップしますので、よろしくお願いします。あと表が読みにくいのでそれの改善も考えていきます。
delver0234@yahoo.co.jp
点 | 流れてきた回数 | カード名 |
---|---|---|
53.3333333333 | 2 | Abzan Falconer |
53.3333333333 | 2 | Murderous Cut |
53.3333333333 | 2 | Abzan Battle Priest |
80 | 3 | Watcher of the Roost |
80 | 3 | Seeker of the Way |
80 | 3 | Bellowing Saddlebrute |
106.6666666667 | 4 | Sultai Flayer |
106.6666666667 | 4 | Arc Lightning |
133.3333333333 | 5 | Take Up Arms |
133.3333333333 | 5 | Nomad Outpost |
133.3333333333 | 5 | Heir of the Wilds |
133.3333333333 | 5 | Abzan Charm |
133.3333333333 | 5 | Mardu Heart-Piercer |
160 | 6 | Mardu Charm |
160 | 6 | Suspension Field |
160 | 6 | Ruthless Ripper |
186.6666666667 | 7 | Mer-Ek Nightblade |
186.6666666667 | 7 | Icefeather Aven |
186.6666666667 | 7 | Armament Corps |
186.6666666667 | 7 | Highspire Mantis |
186.6666666667 | 7 | Mystic Monastery |
186.6666666667 | 7 | Dead Drop |
186.6666666667 | 7 | Pine Walker |
202 | 20 | Ainok Bond-Kin |
213.3333333333 | 8 | Tuskguard Captain |
213.3333333333 | 8 | Bear's Companion |
213.3333333333 | 8 | Death Frenzy |
213.3333333333 | 8 | Opulent Palace |
232.3 | 23 | Debilitating Injury |
240 | 9 | War-Name Aspirant |
240 | 9 | Raiders' Spoils |
240 | 9 | Incremental Growth |
240 | 9 | Timely Hordemate |
252.5 | 25 | Alabaster Kirin |
252.5 | 25 | Mardu Hordechief |
262.6 | 26 | Sultai Scavenger |
266.6666666667 | 10 | Kin-Tree Invocation |
266.6666666667 | 10 | Chief of the Edge |
266.6666666667 | 10 | Chief of the Scale |
266.6666666667 | 10 | Jeskai Elder |
266.6666666667 | 10 | Jeskai Charm |
272.7 | 27 | Feat of Resistance |
272.7 | 27 | Arrow Storm |
292.9 | 29 | Force Away |
293.3333333333 | 11 | Frontier Bivouac |
293.3333333333 | 11 | Ride Down |
293.3333333333 | 11 | Sandsteppe Citadel |
303 | 30 | Jeskai Windscout |
320 | 12 | Hordeling Outburst |
320 | 12 | Burn Away |
320 | 12 | Witness of the Ages |
323.2 | 32 | Abzan Guide |
323.2 | 32 | Jungle Hollow |
333.3 | 33 | Kill Shot |
333.3 | 33 | Mystic of the Hidden Way |
343.4 | 34 | Savage Punch |
346.6666666667 | 13 | Master the Way |
346.6666666667 | 13 | Mardu Roughrider |
346.6666666667 | 13 | Scion of Glaciers |
363.6 | 36 | Mardu Skullhunter |
373.3333333333 | 14 | Sultai Charm |
373.3333333333 | 14 | Warden of the Eye |
373.3333333333 | 14 | Secret Plans |
373.3333333333 | 14 | Temur Charger |
373.7 | 37 | Tranquil Cove |
373.7 | 37 | Disowned Ancestor |
373.7 | 37 | Alpine Grizzly |
393.9 | 39 | Thornwood Falls |
393.9 | 39 | Woolly Loxodon |
393.9 | 39 | Wind-Scarred Crag |
393.9 | 39 | Longshot Squad |
400 | 15 | Riverwheel Aerialists |
400 | 15 | Sultai Soothsayer |
404 | 40 | Scoured Barrens |
404 | 40 | Bring Low |
414.1 | 41 | Bloodfell Caves |
426.6666666667 | 16 | Winterflame |
434.3 | 43 | Abomination of Gudul |
453.3333333333 | 17 | Waterwhirl |
453.3333333333 | 17 | Mistfire Weaver |
453.3333333333 | 17 | Kheru Bloodsucker |
464.6 | 46 | Jeskai Student |
464.6 | 46 | Mardu Hateblade |
474.7 | 47 | Ponyback Brigade |
474.7 | 47 | Swiftwater Cliffs |
480 | 18 | Monastery Swiftspear |
480 | 18 | Swarm of Bloodflies |
484.8 | 48 | Archers' Parapet |
484.8 | 48 | Hooting Mandrills |
505 | 50 | Summit Prowler |
505 | 50 | Throttle |
515.1 | 51 | Dragonscale Boon |
515.1 | 51 | Smite the Monstrous |
525.2 | 52 | Blossoming Sands |
525.2 | 52 | Crippling Chill |
533.3333333333 | 20 | Temur Charm |
533.3333333333 | 20 | Dragon's Eye Savants |
533.3333333333 | 20 | Become Immense |
533.3333333333 | 20 | Horde Ambusher |
535.3 | 53 | Mardu Warshrieker |
535.3 | 53 | Dismal Backwater |
545.4 | 54 | Treasure Cruise |
545.4 | 54 | Snowhorn Rider |
545.4 | 54 | Leaping Master |
565.6 | 56 | Bitter Revelation |
565.6 | 56 | Efreet Weaponmaster |
585.8 | 58 | Unyielding Krumar |
585.8 | 58 | Sage-Eye Harrier |
606 | 60 | Bloodfire Expert |
606 | 60 | Scaldkin |
616.1 | 61 | Rush of Battle |
616.1 | 61 | Rugged Highlands |
636.3 | 63 | Tormenting Voice |
636.3 | 63 | Sagu Archer |
640 | 24 | Roar of Challenge |
646.4 | 64 | Monastery Flock |
646.4 | 64 | Valley Dasher |
646.4 | 64 | Trumpet Blast |
646.4 | 64 | Krumar Bond-Kin |
646.4 | 64 | Awaken the Bear |
666.6 | 66 | Glacial Stalker |
666.6 | 66 | Highland Game |
666.6666666667 | 25 | Blinding Spray |
666.6666666667 | 25 | Goblinslide |
676.7 | 67 | Smoke Teller |
686.8 | 68 | Defiant Strike |
693.3333333333 | 26 | Windstorm |
693.3333333333 | 26 | Venerable Lammasu |
693.3333333333 | 26 | Dazzling Ramparts |
693.3333333333 | 26 | Despise |
707 | 70 | Whirlwind Adept |
707 | 70 | Weave Fate |
717.1 | 71 | Singing Bell Strike |
717.1 | 71 | Sidisi's Pet |
717.1 | 71 | Kin-Tree Warden |
746.6666666667 | 28 | Stubborn Denial |
747.4 | 74 | Rakshasa's Secret |
747.4 | 74 | Salt Road Patrol |
747.4 | 74 | Canyon Lurkers |
757.5 | 75 | Rite of the Serpent |
767.6 | 76 | Wetland Sambar |
777.7 | 77 | Barrage of Boulders |
797.9 | 79 | Embodiment of Spring |
797.9 | 79 | Cancel |
800 | 30 | Quiet Contemplation |
800 | 30 | Set Adrift |
800 | 30 | Gurmag Swiftwing |
808 | 80 | Tusked Colossodon |
818.1 | 81 | Ainok Tracker |
828.2 | 82 | Disdainful Stroke |
828.2 | 82 | War Behemoth |
828.2 | 82 | Shambling Attendants |
838.3 | 83 | Act of Treason |
838.3 | 83 | Sultai Banner |
848.4 | 84 | Scout the Borders |
868.6 | 86 | Kheru Dreadmaw |
868.6 | 86 | Molting Snakeskin |
880 | 33 | Dragon Grip |
880 | 33 | Cranial Archive |
888.8 | 88 | Feed the Clan |
888.8 | 88 | Temur Banner |
906.6666666667 | 34 | Tomb of the Spirit Dragon |
909 | 90 | Abzan Banner |
909 | 90 | Firehoof Cavalry |
919.1 | 91 | Lens of Clarity |
919.1 | 91 | Siegecraft |
959.5 | 95 | Dutiful Return |
960 | 36 | Briber's Purse |
969.6 | 96 | Naturalize |
969.6 | 96 | Erase |
969.6 | 96 | Rotting Mastodon |
969.6 | 96 | Bloodfire Mentor |
999.9 | 99 | Jeskai Banner |
999.9 | 99 | Mardu Banner |
1040 | 39 | Mardu Blazebringer |
1050.4 | 104 | Shatter |
1066.6666666667 | 40 | Brave the Sands |
1070.6 | 106 | Taigam's Scheming |
1093.3333333333 | 41 | Seek the Horizon |
1100.9 | 109 | Swift Kick |
1146.6666666667 | 43 | Heart-Piercer Bow |
論文の紹介 ~MTGのカード選択へのモンテカルロ探索の適応~
コメントで論文を紹介していただいたので早速読んでみた。
"Monte Carlo search applied to card selection in Magic: The Gathering"(和訳:MTGのカード選択へのモンテカルロ探索の適応) url: http://ieeexplore.ieee.org/xpl/articleDetails.jsp?arnumber=5286501
モンテカルロ法はmtgのAIに応用する場合においても有用であると主張する内容だった。私としても有用であろうというふうには思うんだけど、この内容がそれを裏付けるものになっているかは疑問に感じた。
論文の概要
この論文では「メインフェイズのクリーチャーのプレイをモンテカルロ法で選択すると、ランダムでの選択や貪欲法での選択より強かった。」と主張している。計算条件として、
- クリーチャーと土地だけの単色デッキ
- デッキ内容は公開情報
- マリガンについての記述なし(たぶんなし)
- アタックとブロックの戦術は著者が考えたルールに従う場合とランダムの場合の2通り
- 第2メインのカードプレイの戦術はルールに従う場合、ランダムの場合、モンテカルロ法に従う場合の3通り
プレイヤーの戦略はアタック戦略がランダムかルールに従うか、ブロック戦略がランダムかルールに従うか、カードプレイ戦略がランダムかルールに従うかモンテカルロかの組み合わせで作られ、計12通りの戦略を相互に戦わせて勝率を調べる。メインフェイズでの戦術のルールは以下のとおり。
- ランドはあったら出す
- クリーチャーは一番重たいものから貪欲法で出す
アタックとブロックの戦術のルールは紙面の都合で詳しくは書かないとしてある。(超重要事項なんですが、、、)一応アタックは無駄死にしないように、クリーチャーの数とPTの合計が損をしないように選択し、ブロックは返しのフルパンでのダメージが大きくなるように、受けるダメージを抑えて相手のクリーチャーを殺せるようにブロックすると書かれている。
結果と自分の考え
結果として、各プレイヤー間の200戦やったときの戦績表を載せる。RA_RB_RMといった表現は、RA=ランダムアタック、SA=ルールアタック、RB=ランダムブロック、SB=ルールブロック、RM=ランダムメインプレイ、SM=ルールメインプレイ、MC=モンテカルロメインプレイを表す。
著者はモンテカルロの優位性について述べているが、そんなことよりブロックのルールがいくらなんでも弱すぎる。rarbrmとrasbrmのプレイヤーの勝率を比べると、どのプレイヤーを相手にした場合でもルールでブロックするプレイヤーが大きく劣っている。さすがにランダムより弱いnoobブロックでは検証の意味がないのではと思う。 さらに最後にモンテカルロの探索回数と勝率の関係を示すグラフが2つ載せられている。これによるとnoobブロック戦術をお互いに採用したときはルールに従うカードプレイに対して、モンテカルロ法は80%の勝率があるとしているが、この異常に高い勝率はモンテカルロ法にのみnoobブロックが行われることを予想できるために生じているに過ぎない。そのためプレイ戦術自体の評価としては不適切である。 一方でランダムブロックを採用したときは、ほぼ50:50であり、2000回の探索を行った際にはモンテカルロ法がルールに従うプレイに対して負け越している。このデータからモンテカルロ法が優れているとするのは無理がある。むしろカードプレイはクリーチャー以外のスペルがない条件では貪欲法で決めるので十分であるとすら言える。
著者は私と同じような思考を辿って、論文が必要で無茶したのかなと思う。もともとはアタックブロックもモンテカルロでやりたかったのではないだろうか。 ブロックを適切に行うと、この条件では多くのゲームはライブラリアウトまで決着しない上に、ロングゲームになるので探索空間が広がりモンテカルロは弱くなる。そのためブロックルールを弱くせざる得なかったんだろう。私としては10体クリーチャーが並んだら引き分けにする等の処置をとるしかないと考えている。 結果を見る限り、クリーチャーしか入ってないデッキであれば貪欲法で十分そうである。アタックとブロックについて考察してる論文があるといいなと思うので、自分でも論文を探してみることにする。
MTGの簡単なデッキでのゲームの全探索を行うコードを書いてみた - プレイヤー2人は全知全能の完全情報ゲームとしても常識的な時間で探索が終わらなかった -
C#でMTGのゲームプレイの木探索を行うコードを書いてみたが意外と難しく、完成とは程遠いが意見等を頂けるとありがたいので途中経過を公開しようと思う。
複雑なスペルや特殊なデッキを考えるとキリがなく、できるだけ単純なゲームから考えていきたいし、ソフトの雛形作りの意味もあるので簡単にしたいので、まずはその基本であるお互いバニラクリーチャーと土地だけのデッキの場合のシミュレーションを行うことにする。さらに面倒を省くために、プレイヤーはお互いのデッキの内容を知っていて除去とかバットリとかは飛んでこないことを知っているとする。するとプレイヤーはマナ効率だけを考えて漫然とクリーチャーをプレイすることになる。
まずはお互いのプレイヤーがハンド・お互いの未来のドローを知っている状態(完全情報ゲーム)としてMin-Max法で全探索を行うコードを書いてみた。デッキはクリーチャーと土地のみでプレイヤーの選択肢はアタックとブロックのみで、カードのプレイは手なりに行うものとした。デッキ枚数は40枚とした。デッキのバランスをいじることで、適切なマナカーブや土地配分がわかるかと考えていたが、計算にかなり時間がかかるようで、3時間ほど動かしてみても1ゲームが終わらなかった。計算時間の見積もりもできてないし、無駄な探索が多いのも明らかである。
次は無駄な探索を削ること、計算の進捗状況を知ることをやっていこうと思う。プログラムに詳しい人からこうしたらコードが読みやすくなるよとか、早くなるよとか教えて頂けるとありがたいです。
動作の概要
**メイン関数
using System; using GameSimulator.探索; namespace GameSimulator { static class Program { [STAThread] static void Main() { Player player1 = new Player(6); //先手ハンド6で初期化 Player player2 = new Player(7);//後手ハンド7で初期化 SecondMainPhaseNode node = new SecondMainPhaseNode(); node.MyPlayer = player1; node.OppPlayer = player2; node.IsMaxPlayer = true; //先手をmaxプレイヤーと指定 var result = node.Eval(); //探索開始 Console.WriteLine(result.WinningRate); var x = Console.ReadLine(); } } }
評価
将来的には状況からの評価を行いたいが、今回は全探索を行うので、勝つ負けるの2値で扱っていて、WinningRagteにはMin()の値かMax()の値か0(引き分け)が入る
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace GameSimulator { class Evaluation { #region プロパティ public int WinningRate { get; set; } #endregion #region コンストラクタ public Evaluation() { } public Evaluation(Evaluation ev) { } #endregion #region Static public static Evaluation Min() { var val = new Evaluation(); val.WinningRate = int.MinValue; return val; } public static Evaluation Max() { var val = new Evaluation(); val.WinningRate = int.MaxValue; return val; } #endregion } }
カードをクラス化したもの
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Collections.ObjectModel; using System.Collections.Specialized; namespace GameSimulator { public enum CardType { None, Creature, Land }; public class Creature : Card { public Creature() : base(CardType.Creature) { } public Creature(int P, int T) : base(CardType.Creature) { Power = P; Toughness = T; } public Creature(int P, int T, int cost) : base(CardType.Creature, cost) { Power = P; Toughness = T; } private int _Power; private int _Toughness; public int Power { get { return _Power; } set { _Power = value; } } public int Toughness { get { return _Toughness; } set { _Toughness = value; } } public bool Taped { get; set; } public override string ToString() { return Power + "/" + Toughness; } } public class Land : Card { public Land() : base(CardType.Land) { } } public class Card { #region フィールド private CardType _Type; private int _Cost; #endregion #region プロパティ public int Cost { get { return _Cost; } private set { _Cost = value; } } public CardType Type { get { return _Type; } private set { _Type = value; } } #endregion #region コンストラクタ public Card() { Type = CardType.None; Cost = 0; } public Card(CardType type) { Type = type; } public Card(CardType type, int cost) { Type = type; Cost = cost; } #endregion public override string ToString() { return "Land"; } } }
**場・ハンド・ライフの情報保持
場のクリーチャーやハンドを管理する。カードのプレイはPlay(card)を通して行う。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Collections.ObjectModel; namespace GameSimulator { public class Player { #region フィールド private Dictionary<Creature, bool> _CreatureIsTaped = new Dictionary<Creature, bool>(); private List<Land> _Lands = new List<Land>(); private List<Card> _Deck = new List<Card>(); private List<Card> _Hand = new List<Card>(); private int _Life = 20; private Player _Opponent; #endregion #region プロパティ public int Life { get { return _Life; } set { _Life = value; } } public List<Card> Hand { get { return _Hand; } set { _Hand = value; } } public List<Card> Deck { get { return _Deck; } set { _Deck = value; } } public List<Land> Lands { get { return _Lands; } set { _Lands = value; } } public Dictionary<Creature, bool> CreatureIsTaped { get { return _CreatureIsTaped; } set { _CreatureIsTaped = value; } } public ReadOnlyCollection<Creature> GetCreatures() { return new ReadOnlyCollection<Creature>(_CreatureIsTaped.Select(x => x.Key).ToList()); } public IEnumerable<Creature> GetCreaturesIE() { return CreatureIsTaped.Select(x => x.Key); } public Player Opponent { get { return _Opponent; } set { _Opponent = value; } } #endregion #region コンストラクタ public Player() { Init(); } public Player(List<Creature> creatures, List<Card> deck, List<Card> hand, List<Land> land) { Init(); Deck = deck.ToList(); Hand = hand.ToList(); Lands = land.ToList(); } public void Init() { Init(7); } public void Init(int handNum) { for (int i = 0; i < 17; i++) { Deck.Add(new Land()); } for (int i = 0; i < 5; i++) { Deck.Add(new Creature(2, 2, 2)); } for (int i = 0; i < 6; i++) { Deck.Add(new Creature(3, 3, 3)); } for (int i = 0; i < 5; i++) { Deck.Add(new Creature(4, 4, 4)); } for (int i = 0; i < 5; i++) { Deck.Add(new Creature(5, 5, 5)); } for (int i = 0; i < 2; i++) { Deck.Add(new Creature(6, 6, 6)); } Deck = Deck.OrderBy(x => Guid.NewGuid()).ToList(); //シャッフル Hand = new List<Card>(); for (int i = 0; i < handNum; i++) { DrawACard(); } } public Player(Player pl) { this.CreatureIsTaped = new Dictionary<Creature, bool>(pl.CreatureIsTaped); this.Lands = new List<Land>(pl.Lands); this.Deck = new List<Card>(pl.Deck); this.Hand = new List<Card>(pl.Hand); } public Player(int handNum) { Init(handNum); } #endregion //カードを引く public void DrawACard() { if (Deck.Count != 0) { Hand.Add(Deck[0]); Deck.RemoveAt(0); } else { throw new Exception(); } } //カードをハンドから場に出す void Play(Card card) { if (card.Type == CardType.Creature) { PlayCreature((Creature)card); } else if (card.Type == CardType.Land) { PlayLand((Land)card); } } void PlayCreature(Creature card) { Hand.Remove(card); CreatureIsTaped.Add(card, false); } void PlayLand(Land land) { Hand.Remove(land); this.Lands.Add(land); } //出せる場合ランドをプレイする void PlayLandAuto() { var land = Hand.FirstOrDefault(x => x.Type == CardType.Land); if (land != null) { this.Play(land); } } //スペルプレイのアルゴリズム void PlaySpellAuto() { //プレイ可能なマナ数で初期化 int mana = Lands.Count; //プレイ可能なカードで最大コストのカードからプレイ var query = from x in Hand where (x.Cost <= mana) && (x.Type != CardType.Land) orderby -x.Cost select x; foreach (var x in query) { if (x.Cost <= mana) { Play(x); mana -= x.Cost; } } } public void SecondMainPlay() { UntapAll(); PlayLandAuto(); PlaySpellAuto(); } public void UntapAll() { var list = CreatureIsTaped.Select(x => x.Key).ToList(); foreach (var x in list) { CreatureIsTaped[x] = false; } } public void TapCreatures(List<Creature> cre) { foreach (var x in cre) { CreatureIsTaped[x] = true; } } #region プライベート int Pow(int x, int y) { int val = 1; for (int i = 0; i < y; i++) { val *= x; } return val; } #endregion } }
探索
Nodeクラスがが枝分かれして探索する。NodeクラスはメンバとしてList
NodeはAttakerNode, BlockerNode, MainPhaseNodeに継承され、AttakerNodeのNextListにはBlockerNodeが入り、BlockerNodeのNextListにはMainPhaseNodeが入り、MainPhaseNodeのNextListにはAttakerNodeが入り、これら3つを順にめぐる。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace GameSimulator.探索 { class Node { #region フィールド Evaluation _Evaluation = null; private Player _MyPlayer; private Player _OppPlayer; private List<Node> _NextList = null; private Node _Previous = null; #endregion #region プロパティ public Evaluation CurrentEvaluation { get { return _Evaluation; } set { _Evaluation = value; } } public Player MaxPlayer { get { if (IsMaxPlayer) { return _MyPlayer; } else { return _OppPlayer; } } } public Player MinPlayer { get { if (IsMaxPlayer) { return _OppPlayer; } else { return _MyPlayer; } } } public Player MyPlayer { get { return _MyPlayer; } set { _MyPlayer = value; } } public Player OppPlayer { get { return _OppPlayer; } set { _OppPlayer = value; } } public bool IsMaxPlayer { get; set; } public List<Node> NextList { get { return _NextList; } set { _NextList = value; } } public Node SelectedNode { get; set; } public Node Privious { get { return _Previous; } set { _Previous = value; } } #endregion #region コンストラクタ public Node() { } public Node(Node node) { this.CurrentEvaluation = new Evaluation(node.CurrentEvaluation); this.MyPlayer = new Player(node.MyPlayer); this.OppPlayer = new Player(node.OppPlayer); this.IsMaxPlayer = node.IsMaxPlayer; } #endregion #region 公開関数 public virtual Evaluation Eval() { if (NextList == null) { CreateNextNodeList(); } if (CurrentEvaluation == null) { if (NextList.Count == 0) { return CurrentEvaluation; } CurrentEvaluation = SelectNode().Eval(); } return CurrentEvaluation; } public virtual Node SelectNode() { if (NextList.Count == 0) { return null; } Node node = null; if (IsMaxPlayer) { int evl = int.MinValue; foreach (var x in NextList) { if (evl <= x.Eval().WinningRate) { evl = x.Eval().WinningRate; node = x; } } } else { int evl = int.MaxValue; foreach (var x in NextList) { if (evl >= x.Eval().WinningRate) { evl = x.Eval().WinningRate; node = x; } } } return node; } public virtual void CreateNextNodeList() { } #endregion } class SecondMainPhaseNode : Node { #region コンストラクタ public SecondMainPhaseNode() { } public SecondMainPhaseNode(Node node) : base(node) { } #endregion public override void CreateNextNodeList() { this.NextList = new List<Node>(); Player my = new Player(MyPlayer); Player opp = new Player(OppPlayer); my.DrawACard(); my.SecondMainPlay(); if(8 <my.CreatureIsTaped.Count && 8 < opp.CreatureIsTaped.Count) { CurrentEvaluation = new Evaluation(); CurrentEvaluation.WinningRate = 0; return; //クリーチャーの数がどちらも8超えたら引き分け } else if (my.Deck.Count < 10) { CurrentEvaluation = new Evaluation(); CurrentEvaluation.WinningRate = 0; // デッキ枚数が10きったら引き分け } AttakerNode node = new AttakerNode(); node.MyPlayer = opp; node.OppPlayer = my; node.IsMaxPlayer = !this.IsMaxPlayer; node.Privious = this; this.NextList.Add(node); } } class BlockerNode : Node { #region プロパティ public List<Creature> Attackers { get; set; } Dictionary<Node, Dictionary<Creature, Creature>> Blockers { get; set; } //keyブロッカー val アタッカーor null #endregion #region コンストラクタ public BlockerNode() : base() { } public BlockerNode(List<Creature> attackers) { Attackers = attackers; } #endregion public override void CreateNextNodeList() { var cre = MyPlayer.GetCreatures().Where(x => !x.Taped).ToList(); Dictionary<Creature, Creature> noBlock = new Dictionary<Creature, Creature>(); foreach (var x in cre) { noBlock.Add(x, null); } List<Dictionary<Creature, Creature>> blkList = new List<Dictionary<Creature, Creature>>(); blkList.Add(noBlock); foreach (var x in cre) { var add = new List<Dictionary<Creature, Creature>>(); foreach (var y in blkList) { foreach (var sel in Attackers) { var blk = new Dictionary<Creature, Creature>(y); blk[x] = sel; add.Add(blk); } } blkList.AddRange(add); } NextList = new List<Node>(); foreach (var x in blkList) { var val = SolveCombat(x); if (val != null) { NextList.Add(val); } else { if (IsMaxPlayer) { CurrentEvaluation = Evaluation.Min(); } else { CurrentEvaluation = Evaluation.Max(); } } } } //ダメージ割り振り解決 private SecondMainPhaseNode SolveCombat(Dictionary<Creature, Creature> block) { SecondMainPhaseNode newNode = new SecondMainPhaseNode(); newNode.MyPlayer = new Player(this.OppPlayer); newNode.OppPlayer = new Player(this.MyPlayer); newNode.IsMaxPlayer = !this.IsMaxPlayer; newNode.Privious = this; foreach (var atk in Attackers) { var blks = block.Where(x => x.Value == atk).Select(x => x.Key); if (blks == null) { newNode.OppPlayer.Life -= atk.Power; //ブロックされていなければダメージが入る if (newNode.OppPlayer.Life <= 0) { //死判定 return null; } } else { int rest = atk.Power; var b = blks.OrderBy(x => -x.Power); foreach (var blk in b) { //パワーの大きいブロッカーからダメージを割り振る if (blk.Power <= rest) { rest -= blk.Power; newNode.OppPlayer.CreatureIsTaped.Remove(blk); } } if (atk.Power <= blks.Sum(x => x.Power)) { OppPlayer.CreatureIsTaped.Remove(atk); // アタッカーの死判定 } } } return newNode; } } class AttakerNode : Node { public override void CreateNextNodeList() { var creatures = MyPlayer.GetCreatures(); //0 //1 //01 //11 //001 //101 //011 //111とふえていく List<List<Creature>> attacker = new List<List<Creature>>(); attacker.Add(new List<Creature>()); foreach (var x in creatures) { List<List<Creature>> add = new List<List<Creature>>(); foreach (var y in attacker) { var atk = y.ToList(); atk.Add(x); add.Add(atk); } attacker.AddRange(add); } NextList = new List<Node>(); foreach (var x in attacker) { BlockerNode node = new BlockerNode(x); node.MyPlayer = new Player( this.OppPlayer); node.OppPlayer = new Player(this.MyPlayer); node.OppPlayer.TapCreatures(x); node.IsMaxPlayer = !this.IsMaxPlayer; node.Privious = this; NextList.Add(node); } } } }
MTGの単純化 - 探索の無駄をなくす -
前回の探索プログラム
前回、木探索のプログラムを書いてはみたものの、時間がかかりすぎて終わらなかった。できるだけ無駄な探索を減らして効率的な探索に修正する必要がある。
単純化
プレイヤーが選択することはアタックとブロックである。すごく簡単にしたけど、これでもクリーチャーが5体ずつ並んでいる場合に、1体がアタックする場合のブロックの仕方は2^5通りある。2対の場合は3^5、3体の場合は4^5となり、1ターン中のアタックを選んでブロックを行うときのの場合の数は
36,232通りとなる。
この試行をライフが0になるまでランダムに繰り返す。こんなこと20ターンも繰り返せば終わるわけない。
条件の絞込み
探索を行う上で誤った試行は省きたい。
アタック・ブロックの最低ルール
- 相手のライフを0以下にできるならすべてのクリーチャーでアタックする。
- 自分のライフが0以下になるブロックは行わない。
- 2/2が2体ブロッカーに立っている場合に3/3がアタックして2/2と3/3の交換になるような損なアタックは行わない。
- 2/2を残して3/3でチャンプブロックのようなメリットのないチャンプは行わない。
アタック・ブロックのルールの数式化
これらをプログラムに落とせるように数式で表現する。
-
アタッカーがA(1) ~ A(n)までのn体いて、同様にブロッカーB(k){k=1,2,.....,m}がいるとする。このときAatk(1) <= Aatk(2)<=.... <=Aatk(n)とする。
のとき、すべてのクリーチャーでアタックする。
-
[1,n]の範囲から攻撃を通すクリーチャーを選んで整数の集合Sとして表記する。
を満たすブロックが合法手である。
- A(k){k=1,2,..n}でアタックを試みる。すべてのブロッカーB(k){k=1,2,...,m}の集合をSとする。Sを部分集合の集合X1,X2,....Xnに分割するとき、すべてのkに対して となる分割Xが存在する場合非合法である。
- アタッカーがA(1) ~ A(n)までのn体いて、同様にブロッカーB(k){k=1,2,.....,m}がいるとする。このときAatk(1) <= Aatk(2)<=.... <=Aatk(n)であり、Batk(1)<=Batk(2)<=... <=Batk(m)とする。A(p)をB(q)でブロックするときBatk(q) < Aatk(p)を満たす場合、「Batk(k) < Batk(q)」かつ「B(k)はブロックを行わないかダブルブロック以上を行う」を満たすkが存在する場合非合法である。
・さらに、 MTGのルールではないが、最善手を見つけたらそれ以上そのノードの探索は行わない。を付け足す。
終わりに
これで計算終わるのか?ひとまずコードにしてみようと思う。
不完全情報ゲーム例として大貧民のアルゴリズムについて調べた
今回は不完全情報ゲームについて調べる。不完全情報ゲームは各プレイヤーが相手の手札などの知らない情報が存在するゲームのことで、MTGは当然これに含まれる。不完全情報ゲームの基本的なAIの動作に関しては情報処理学会誌に以下のような記述がある。
ゲームの状態がわからないとモンテカルロ木探索などの探索的手法は適用できない。そこで可能なじょうたいからランダムに状態をサンプリングし、完全情報ゲームと同様にモンテカルロ木探索を実行することが行われている。これをモンテカルロサンプリングという。[情報処理学会誌より]
モンテカルロ木探索
まず、見えないハンドをランダムに割り当てる。これに対して、何度もランダムにゲームが終わるまでプレイしてみて各プレイの勝率を調べる。また別のハンドを割り当てて同じことを行う。これを繰り返して勝率の高いプレイを選択するというのがモンテカルロ木探索である。囲碁で初めに使用された手法であるが、ランダムにプレイしてみるタイプは無駄な探索が多くとても弱いらしい。囲碁なんて適当に打ったら端っことかにも石を置くだろうし当然といえば当然であるが。そこで、もっともらしい手の比率を上げてランダムにプレイするという手法がとられている。結局このもっともらしさとは何かを調べるのが大変なのであろう。
参考:http://homepage1.nifty.com/ta_ito/fit2008/muramatsu-fit.pdf
さらに、情報処理学会誌に書かれている内容に、ε-Greedy法というものあるが、これは初手はランダムでプレイして手が進むにつれてもっともらしい手を中心に打つようになるというものである。この手法は尤もらしさの評価により強く依存するようになっている。
次に考えること
MTGのシミュレーションにはこのモンテカルロ木探索を使用することになるだろう。探索を効率的に行うために、明らかに誤った手とはなにか、もっともらしい手とは何かといったことを調べる必要がある。まずは単純なデッキで考察し、ソフト上で動作させてみたい。