I thought it was a fun game and if it had been a 10 day contest it would mostly be ok. The biggest problem was the asymmetry. If most games are a draw because P1 mostly wins, then you need too many games to test if a change to your bot is a real improvement. Brutaltester helps but only to some extent. I ended 5th which is somewhat on the lucky end of the error margin, but I can't be sure of that.
I feel I mostly failed at draft. I don't know why. It seems it is something I could be good at, but I wasn't. I started with a formula with about 30 weights to fit a good manacurve, item/creature ratio and to pick good cards. I fiddled with it for days and only went down in rank (from top 3 to about 20). Then I was told that ClosetAI used an ordered draft with no formula, I stole his and was back to top 3. After that I changed it a bit and tried other things. One big thing I tried was this:
I kept track of cards in the 60 card-cardset and cards I already picked, then randomly finished the cardset about 500 times, randomly picked 3 choices for all remaining turns and finished my deck according to cardorder. I then calculated the chance to draw a 1, 2 or 3 cost creature-card in my first few turns. I set a probability I wanted to achieve and then did a binary search to find the best weight adjustments to 1,2,3 cost creatures to achieve this probability. This was a 500 line solution to my draft problem I was pretty proud of. It did exactly what I wanted and achieved the probability I was looking for.
Then I tested it in brutaltester and in the leaderboards and it did nothing. I would have been cool with the result, if it had been way worse, or way better, but it just did... nothing. Working on draft only only got worse from there. Every adjustment to draft failed for me and in the end I only had the ClosetAI draft with some common sense changes. Pretty frustrating. I think draft was the main weakness that separated me from the players above me in the leaderboard (and maybe some below me as well).
My search is what got me to nr 1 when legend opened and kept me in top 10 most of the time during the last 2 weeks when I made no real changes. I'm sure I have a good search. This is how it works:
I have a tree (parents, children etc.) that contains possible decisions you can make during a turn and also possible decisions the enemy can make (1 turn for both players). I have the nodes of the tree pregenerated so I dont have any garbage collection and save the decisions in an integer (like a bitboard). For example: Summon creature 1, 4 and 5 from my hand would be coded as binary 00110010. At most you can summon 8 creatures so you only need 8 bits.
I would start with the summon phase. All combinations of summons that fit on the board and mana pool will be considered. The order of summons does not matter and I am not considering duplicates. A full summon phase fits in 1 child node.
For green items I do the same thing only I space out my bits in groups of three. 3 bits can make 8 possibilities. Since there is a max of 6 creatures (+ not using = 7) this fits. There can be at max 8 green cards in your hand, so you need 3 * 8 = 24 bits. This also fits in an integer. So a full green item phase fits in 1 child node as well.
Attacking is way more complicated. A single attack node contains a creature number (0 to 5) and a target creature (0 to 5, bitshifted) . I don't combine multiple attacks in one node because it wouldn't fit.
Attacking is the best way to explode your solution space so you need ways to reduce the number of possibilities. My main trick is: Whenever the order does not matter, you enforce a specific order and disallow all others. The order I enforce is:
1) Items that remove abilities go first. Since they always remove ward (with 1 exception), there is no point in using another creature or item to remove ward first. I never allow these items to be used after other kinds of items or attacks on the current target.
2) Low attack items and creatures always go before higher attack items and creatures, except where breakthrough is involved. I allow breakthrough creatures as the last hit even if they have lower attack. Of course I do not allow them later than an even higher damage breakthrough creature. Whenever items or creatures are tied for attack, I let the higher instance id go first (or last, I don't remember and it doesn't matter as long as you are consistent). The main reason to let low attack creatures go first is because of breaking ward.
3) I attack the enemy creatures from left to right. Every time I arrive at a creature I can attack it, or skip it. If I attack it, I can attack it for as long as I want or until it is dead. If the creature is damaged, I must kill it, or stop attacking entirely (my bot is not allowed to damage, then move on to different target). The idea of this is that it makes no sense to attack multiple creatures and not finish them off. Ward is an exception to this. I can remove a ward, then move on to the next creature. I can of course also damage a single creature and not do anything else.
I would do all this twice. First for guard creatures (to remove all guards) and then for non-guard, if possible. I would always hit face with whatever hadn't attacked. All of the above unique possibilities lead to different nodes in the tree.
If my attack leads to more board space for summons, I would repeat the summon phase and allow a new green item phase (just for the new summons). I would also allow a new attack phase, if I summoned a charge creature and only if I did not already summon a non-charge creature in the first summon phase (because why summon a charge creature second if you can summon it first?). I would also give my bot the opportunity to use blue items that can be used on the enemy (at the end of turn).
Enemy turn attacks were resolved the same way (as above). The eval score was backpropagated. Enemy nodes would backpropagate the worst score and my nodes would backpropagate the best score. So every node in the tree always has the best score that is achievable on that path, for the player it belongs to. I also used a kind of alpha bèta pruning that made it so that I only needed a few more sims to do enemy attacks after solving my own turn. If my best path through my own part of the tree with the best enemy moves would still be better than my other paths, there would be no need to calculate enemy moves on the other paths (enemy moves can only reduce score). When I thought of this trick, my required simcount became roughly 20x smaller and calculation time mostly rounded down to 0 ms.
Then I figured, if I have so much calculation time left, why not try a greater depth? I did and it failed. I never found any other way to use my calculation time, unfortunately. Some part of me tells me I might have, if I had used another week for it, but I doubt it. At least my brutaltester series were fast and I guess I helped out the CG servers by keeping my turns short.
Evaluation of search
My eval is simple. I have a score for each ability and for offense and defense. Ward has synergy with offense, so I added that to my eval as well. I have a weird HP formula for me and the enemy that has a peak in it, meaning for some reason it seemed ok if my bot got more eval score for having less hp when over the peak. I guess maybe it has something to do with runes, because the peak was fitted around 28 hp (near rune 25 breakage). My effort at finding an alternate formula failed. I also evaluated card draw and I included runes and card death. The way my bot works is that it would try to get maximum score even if it found a win. So my games often finish with breakthrough damage (to kill 1 more creature), or to kill as many creatures as possible before finishing off the enemy. Somewhat amusing (and evil).
eulerscheZahl: For helping me set up his parameter fiddler and otherwise useful discussion.
Counterbalance: For helping me set up brutaltester (and for coding those new releases in the first place)
blasterpoard: For much useful discussion. He also pointed out the idea of a fixed cardorder (after which I stole one).
And of course (!) the creators of the game for making a beautiful addition to the CG arenas, because that is what it will be. It might not have been the best contest for balance reasons and because of randomness, but it is still pretty neat and will draw players I think.