I'm just getting started in the area of simulation so I wanted to start with something simple. I'm mainly interested in using simulation to help answer tactical questions rather than strategic questions. So, rather than, does strategy X beat strategy Y, I'd like to know what is the optimal way to play strategy X. In the Workshop/Gardens strategy, for example, how many Workshops do you buy before you start getting gardens. As a new player, this is not obvious to me. I would have guessed about 4. It turns out that Workshop/Gardens is fastest when you buy
all the Workshops first (see
this thread for some discussion). But that's only true when you're basically playing solitaire. I wanted to know if you could still beat BM strategies if they started contesting you on Gardens.
First the W/G bot
# A Workshop/Gardens rush strategy
#
# Buys only workshops until all are purchased. Then switches to gaining and buying Gardens.
# Will switch early if it sees the opponent buy a Gardens.
{
name: 'Workshop Gardens'
requires: ['Workshop', 'Gardens']
gainPriority: (state, my) -> [
"Gardens" if my.countInDeck("Workshop") >= 10 \
or state.countInSupply("Gardens") < 8
"Workshop"
"Duchy"
"Estate"
"Silver"
"Copper"
]
}
I started with the DoubleJack bot. In a match with no interaction, Workshop/Gardens is a ~60/40 favorite. Then I tried making the DJ bot buy Gardens, but only after it sees the W/G player buy them. If it starts buying them too early, its deck gets diluted and can't keep up with the W/G deck. If it waits til the W/G player starts going after them, the DJ deck has had time to develop and can basically split the Gardens without slowing down too much (Note: it's still better to buy Province with 8 rather than a Gardens). DJ becomes a ~80/20 favorite.
# Buys two Jacks of All Trades and otherwise plays a version of Big Money.
#
# If it sees Gardens being purchased, it starts to buy them over Silver and Duchies
{
name: 'DoubleJack'
requires: ["Jack of All Trades"]
gainPriority: (state, my) -> [
"Province" if my.getTotalMoney() > 15
"Gardens" if state.countInSupply("Gardens") < 8
"Duchy" if state.gainsToEndGame() <= 5
"Estate" if state.gainsToEndGame() <= 2
"Gold"
"Jack of All Trades" if my.countInDeck("Jack of All Trades") < 2
"Silver"
]
}
So the Gardens player has to make some decisions. They know the optimal solitaire strategy is to buy all 10 Workshops first, but if the DJ player is smart, they can contest the Gardens late in the game and crush them. So it's probably better to force them to buy Gardens earlier, before their deck is full of treasure. If W/G only buys 2 Workshops before going for Gardens, they can close the gap, but DJ is still favored ~58/42 (In this case, DJ starts buying Gardens as soon as the W/G player does). But if DJ simply ignores the sub-optimal play of greening after just 2 Workshops, and doesn't buy
any Gardens, they crush ~85/16. So the W/G player is in a catch 22. If they wait til their deck is full of Workshops, the DJ player can split the Gardens without a problem, but if they green too early the DJ player can win by simply having a stronger deck. I think this is cool.
I'm sure a lot of folks are way ahead of this but it was a good exercise for me and hopefully it put some data behind peoples' intuition. I'll probably expand this to some other Gardens and BM strategies as well, to see if the same tactics can be successful. I should also add another condition to the Garden buying in the DJ bot that delays buying until later in the game. Not sure what game state attribute to look at, though.
Some simulator questions. In Dominiator, is there a way to query the number of cards in an opponent's deck? So instead of my.countInDeck("Gardens") I could use something like opp.countInDeck("Gardens"). Couldn't find anything in the coffee code.