ドミニオンのシミュレーション

概要

ドミニオンのコインを買う戦略をする際に、初手で礼拝堂を買うべきかどうかについて。
初手終了後10ターンの間「礼拝堂が出たら屋敷を捨てる。買えるなら銀貨や金貨を買う。」という戦略をとった場合に、デッキ1枚あたりのコイン価値がどうなるかを1万回シミュレーションした。結果、「礼拝堂+銀貨」は「銀貨+銀貨」、「礼拝堂+礼拝堂」、「銀貨+堀」のいずれよりも優れていることがわかった。

動機

タケルンバのドミニオン戦略 - その4 アクションカード(その1) - タケルンバ卿日記を読んで

コインをかき集めるというシンプルな戦法に礼拝堂がどれだけ効くかというと微妙。効果はないとは言えないし、2金ならどうせ銀貨を買えないので、とりあえず購入して屋敷を処分する考え方はアリなんだけど、屋敷3枚を都合良く処分できるかどうかは運次第だし、礼拝堂の1枚分、コインを引く可能性が下がるので、序盤にどれだけ有利になるんだろうかなあと。

これはプログラムを書けば簡単に実験できるな、と思ったのでやってみた。

手法

初手終了後10ターンの間、「礼拝堂が出たら屋敷を捨てる。買えるなら銀貨や金貨を買う。」という戦略をとった場合に、デッキ1枚あたりのコイン価値がどうなるかを1万回シミュレーションした。ソースコードは末尾。

結果

初手で礼拝堂と銀貨を買った場合、平均1.72、SD 0.1。銀貨だけを買った場合、平均1.62, SD 0.05。初手のコインが3-4に別れて銀貨を2枚買えた場合で平均1.68, SD 0.05。礼拝堂2枚を買った場合、平均1.60, SD 0.08。堀と銀貨を買った場合、平均1.59, SD 0.05、となった。つまり、「礼拝堂+銀貨」は「銀貨+銀貨」、「礼拝堂+礼拝堂」、「銀貨+堀」のいずれよりも優れている。なお、この実験では礼拝堂を屋敷を捨てるためだけに使っているが実戦では銅貨を捨てることに使ってもよいので「礼拝堂+銀貨」と「礼拝堂+礼拝堂」はより高い値になりうる。

なお+0.1の平均価値の向上と+3の勝利点を捨てることが投資として見合うかどうかは検討の余地がある。

ソースコード

from random import shuffle
from copy import copy

NUM_TRIAL = 10000

def draw(deck, used, n):
    if len(deck) < n:
        shuffle(used)
        deck.extend(used)
        used = []
    hand = deck[:n]
    deck = deck[n:]
    return deck, used, hand

def count_money(cards):
    return cards.count("1") + cards.count("2") * 2 + cards.count("3") * 3

def test(initial_deck):
    sum = 0
    sumsq = 0.0

    for triel in range(NUM_TRIAL):
        deck = copy(initial_deck)
        shuffle(deck)
        used = []
        for i in range(10):
            deck, used, hand = draw(deck, used, 5)
            if "C" in hand:
                while "E" in hand:
                    hand.remove("E")
            elif "M" in hand:
                deck, used, got = draw(deck, used, 2)
                hand += got

            money = count_money(hand)
            if money >= 6:
                used.append("3")
            elif money >= 3:
                used.append("2")

            used += hand

        deck += used
        money = count_money(deck)
        x = float(money) / len(deck)
        sum += x
        sumsq += x * x

    from math import sqrt
    print sum / NUM_TRIAL, sqrt(sumsq / NUM_TRIAL - (sum / NUM_TRIAL) ** 2)


print "a silver only"
test(list("1111111EEE2"))
print "silver and chapel"
test(list("1111111EEE2C"))
print "two silvers"
test(list("1111111EEE22"))
print "two chapels"
test(list("1111111EEECC"))
print "silver and moat"
test(list("1111111EEE2M"))