As you may or may not have noticed I like playing Hearthstone. I'll spare you the details. One thing I've been wanting to do for a while now is to create a simulator where you can simulate certain conditions. One in particular is drawing odds. And that's the one I've made now :)
Tldr; github at https://github.com/pvdz/hs_draw_sim
, the sugar at https://pvdz.github.io/hs_draw_sim/src/web.html
This was built in from scratch about a day, give or take. I'd say about half the time was spent in the UI world. May have accidentally recreated parts of React :p
How it works
The way this works is you setup simple logic rules through the UI, you tell the simulator the relevant cards in your deck, up to how many cards to draw, how many runs to make, and which players to simulate and then you, well, run it.
The simulator will create a deck of cards from your input (padded with dummies) and shuffle them (yates/fish). First it draws 3 or 4 cards. It checks the win condition (in some cases you just want to know whether you get the card at all). It'll draw 3 or 4 cards, run the mulligan rules on them, and stop if they are met. Otherwise it'll run the mulligan rules and replace the card if the rules say so. After this check is done for all cards it checks the win condition again. If met it stops here.
Then it starts drawing cards. For each card drawn it checks the win condition. If it is met, it stops there. Oterwise it continues for as long as there are cards or for as long as it is supposed to draw, whichever comes first. Once the win condition (or end) is met, it increments a counter for that turn and continues with the next run. That's it.
Look I'm not a designer.
You can save/restore all the things which is put in local storage. When you hit the load button it'll load the preset by name in the dropdown and fill in the rest by those details. When you press the save button it'll collect all the details in the interface and store them by the given name as a preset. You can't really delete a preset unless you clear everything (or just hack it). You can override a preset.
The "stop after this many cards" means that if the engine has drawn this many cards and has not met the win condition it just stops and increments a "other" field.
I've added a "stop one card earlier on coin" option because sometimes you want to know the odds of the theoretical earliest moment you could cast some thing.
The number of simulations should be obvious. Note that this is per player to run for. In the UI about 100k seems to be a sweet spot. This does all depend on your rules of course. The engine could be much faster but for most simulations a 100k runs will tell you all you know to know, anyways. So I'm not too bothered by this. It's an artifact of the generalization of the logic system.
Player to simulate allows you to run only for either the starting or second player, or both.
The run button makes things go boom.
The logic was quite a bit of work. These are generalized components which work individually. It's kind of beautiful if it didn't look so ugly (PR's are welcome ;) ).
You can add and remove new rules. I think the logic should speak for itself. You can have groups of "and" and "or" rules and you can nest them. The top level is always an "and".
You can setup rules to match the current card, turn, player, and hand contents. You can also invert the logic of a group ("if hand does not contain foo").
There are two sets of rules, one is for the mulligan and should return positive if you want to mull the card. The other is to check for the win condition and should return positive if it was met.
The turn will be -1 after the first cards of the mulligan have drawn (the hand will already contain all the cards but the check is fired for each card individually). Turn 0 will be after the mulligan but before the first card was drawn. After that each turn really means the nth card draw.
Note that if you're looking for something bad, like bricking your Spiteful Summoner by drawing the only two activators too early, then you probably want to skip the first win condition check ("and not turn -1") since that's the only time when you can put the cards back into your deck.
The engine is really just a counter. It applies the rules in a fairly abstracted way. It doesn't really know about Hearthstone, beyond the special mulligan. So all it does is for each card check your win condition and add a notch once the win condition is met.
The percentages in the stats are not so much the probability, although with a high enough simulation count it ought to be, but are rather the percentage of total times that the win condition was found to be met at a certain step. This means that with 10 runs you could end up with 20% after mulligan where with 100k runs you'd only get 2%. So take it with a grain of salt.
That said, I do believe that these stats are fairly representative of your odds of certain situations. Findings seem to line up with what I found online (and those posts wildly vary as well, anyways). Since this is a simulation the only two sources of reliability is how proper the rules are applied and how proper the shuffling is.
The Hearthstone way of shuffling is currently not exposed. It's really a Schrodinger's cat story. When a card is shuffled back into the deck you don't know whether it shuffles that one card or the whole deck. At the same time that also means you don't have to know. Assuming there's at least some randomization going on it would be all the same from your perspective (hence the cat). Since there are currently no ways to inspect the current order of the deck you also can't tell anything about its shuffling mechanics. And so we don't.
Okay, that was fun. Back to work!
Hope it helps you :)