I ended 4th in legend, just barely (less than 0,01) ahead of Illedan (still feel a little bit guilty). This was a very fun contest, but also very stressful. Sometimes you had no idea whether a newly submitted bot was better or worse than the one before. Ranks jumped around a lot. Here’s how I did it:
Precalculation
I precalculate a lot, namely:
- Pathing from every square to every other square, this is used for wanderer simulation, but also to compute other distances.I used BFS for this.
- Line-of-sight: to know for every square, whether it is in sight of another square. This is used for slasher logic.
- Neighbours: To have a list of walkable neighbours for every square on the map.
- Branch value: To know the total number of possible move combinations from every square. So for example, square [4,3] could have as a possible combination: left-up-right or down-left-up-up-right. I do this for 7 depth levels. This is used to calculate the maximum depth I am able to sim to from that particular square. Long hallways are more easily searched in depth than big floor-like areas.
- I also have all maps in memory as static string arrays to be able to recognize the map I am playing on.
Simulation
I started by copying the entire referee and translating it to C#. I threw out all the visuals, multiplayer code and the pathfinding. I tried to use what was left but found it too hard. I kept the code as a reference and started anew, writing my simulation bit by bit and using the translated code. In the end I had a good sim that only disregarded light. I even simmed spawns and such (not sure if it is worth it).
The search
Once I had a good simulation, I simulated 1 turn, with the enemy explorers as wait bots. I tried all possibilities and picked the one that gave me the most sanity. This got me around rank 100. Then I switched to using random actions for enemy explorers and that got me to around rank 50. I had a really hard time coming up with a way to simulate further than one turn. It was around this time I finally had a good idea about how to search in more depth and it is a weird one! I have no idea if this method already exists, so I will just call it SmitsiMax . Max, because it maximizes all explorers’ sanity and has no other evaluation than that.
It works like this:
All players have a current position. They each get their own tree to choose moves from. They choose a path in the tree and after depth x (variable, will explain later), a simulation will be run. The result of the simulation (sanity change) will be backpropagated along the tree and all passed nodes will have this score. This is done for each explorer separately. So each explorer has their own tree and their own scores (see pic). The only thing they have in common is the simulation
The explorers pick the children-nodes randomly the first 10 times they get to a parent-node. After that, the UCB1 formula is used to pick the best branches. The UCB1 formula has a value (average sanity change) and an exploration part (based on number of visits to that node, with an exploration parameter). The simulations will (hopefully) converge on good moves for every explorer if you have enough sims. To do this, you have to know how “deep” you can go. If you go too deep, the moves will not converge and you get nonsensical decisions. Moves are picked for the best average sanity loss over the course of the turns covered by the depth used. Every explorer is treated the same in this search and only movement is considered. The reason it works so well, is that the resulting moves are both good (minimized sanity loss) and realistic (enemies minimize sanity loss as well). Because there are few parameters, it is easy to use, but also not very flexible. I could not tweak some parameter to avoid my explorer splitting from the group.
Variable depth
Sims go faster when there are few minions around. I cull my sim, throwing away far away explorers and wanderers to keep the sim lean. Even so, there may be too many minions and explorers nearby to have a lot of depth. So I use my branch cache for the square I am on (is it a big floor or 1 square wide hallway) and the amount of entities, to choose the depth. The depth usually varies from 3 (very crowded), to 6, me alone in a hallway.
Plan
I plan when I am lower than 210 or so health and have at least another explorer around me, or when there are only 2 explorers left total. This is one of the few heuristics I have. I only do this when waiting is a good option.
Light
I check usefulness of light with a formula which uses as input: the list of wanderers, and the players closest to these wanderers. I calculate whether the light will switch the targets of the wanderers away from me to the other player and if it does, i use it. It is not perfect and could be better but it works. I only do this when waiting is a good option.
Yell
My yell code is pretty good I think. I use 4-8 sims per turn to see if yell is a good idea. A yell basically is like this:
Turn 1: I yell and stand still
Turn 2: I move one square to the side.
Since I don’t know which way is the correct way to go to to avoid the minions, i try all 4 sides that are walkable. So I sim 2 turns for each direction. If any one of those sides results in 0 damage for me and at least 25 damage per stunned explorer, it is a good yell. So that means 40 damage for 1 player, or 80 damage for 3 players (2x 40dmg + 1x unaffected). As long as the average is over 25, I yell. I do my yell code before I do anything else. So if I choose to yell, I don’t do any other sims that turn.
Map recognition and hard-coding
I tried my best to get some hard-coding in, but in the end I only really used it for those maps where you start far away from eachother. My bot had no overarching map strategy and only looked at the short-term situation (3-6 depth). Therefore it had trouble getting past a single wanderer to the other explorers. This was worst in Typhoon, so I just hardcoded a run to the other side, straight through a wanderer. This boosted my winrate for that map.
Thanks
to the creators for making this game. It was fun, challenging and (too) exciting.