Hey everyone!
I reached 225th (65th in gold) but I still want to share my experience!
Humble beginnings
As I often do with contests, I started using JavaScript so I could quickly prototype something and get a feel for the rules.
This worked rather well and I simply did the most naive approach: if a bomb touches at least 2 enemies, throw it (not considering my agents or enemy movements). If not, try to shoot and aim for the highest value of dmg / (100 - wetness) which would prefer targets that are closer to death. Finally, my movement would simply move me closer to the nearest enemy.
Quite the simplistic logic but it was enough to get me started and understand the rules.
Then I decided it was time to switch from JS to Rust.
Fighting the urge to search
Having switch my JS algorithm to Rust, I was obviously getting similar results.
I hesitated a lot about the approach: refine the heuristics or move to a search algorithm like MCTS.
I used MCTS exactly once in the past, for Ultimate Tic-Tac-Toe.
I didn’t feel very confident about applying it to this contest because of the simultaneity of the game. So I decided to instead focus on smarter heuristics.
I started with the obvious one: consider moving BEFORE throwing/shooting. My heuristic was so simple I could see that my opponents would almost always shoot/throw one turn before my agents.
This wasn’t difficult in practice: find every cell I can move to and then use my throw/shoot heuristics to compute a score. Move to the most promising cell and perform the most promising action.
This resulted in a big performance improvement, as one would expect.
I started working on a full simulation of the referee, just in case I decided to perform a search in the end.
It took more time than I’d hoped but was interesting to do. (Thanks a lot to Codingame for providing the referee’s code, that’s very useful for simulations and self-play)
Despite my decision not to use a search, I spent about 4 hours trying to make a crude version work every evening before going to sleep thinking “That’s stupid, I should focus on heuristics, my search don’t yield any result whatsoever”.
Another heuristics improvement I made was the handling of the sniper agent. I decided (through several hardcoded if statements) that the sniper would play like a sniper and find a nicely positioned cover to hide behind until there were less than 3 agents alive on my side.
This had a very positive impact on my scoring. The sniper would deal damage while being shielded from it and a little out of reach for bombs. And having an agent stay at low wetness greatly helped with scoring as well.
Using a search anyway
After reaching the gold league, I noticed I had some nice arrays containing potential actions from my heuristics. Moving East and throwing a bomb at A had the same score as moving North and throwing a bomb at B.
Up until now, I arbitrarily selected the first of the list but I saw the opportunity for a smaller scale search.
After wondering about MCTS one last time, I decided it was the time to try a Beam Search for the first time of my life.
The search would be very limited. My previous attempts naively selected all moves and all possible actions which obviously resulted in a gigantic search space I had no hopes of traversing. This time, though, I would drastically reduce scope:
- If an agent can shoot a bomb, its only possible actions are all throws/moves that result in the max bomb score.
- If an agent can shoot, its only possible actions are all shots/moves that result in the max shot score.
- If an agent can do neither of those, its actions are the 5 possible movements.
Moreover, the enemies would not use a search but instead my previous heuristics. This is certainly not ideal since I would effectively be fighting a weaker version of myself but at least I had an opponent to simulate.
My beam search was ready. I arbitrarily chose a width of 5 with a depth of 5 to get started. Timeout.
Haha my bad, I overestimated the numbers. Width of 5 with depth of 2. Timeout.
Okay now it’s getting ridiculous. Width of 3 with depth of 2. Hey it works! I beat the previous version in 2% of the self matches.
Not surprising, a search is awful if you can’t consider enough situations. After investigating a little more, I noticed that 91% of the execution time was spent in the A* pathfinding. I had asked Claude AI to convert the official pathfinding to Rust. It worked fine when I didn’t care about performance but it was awful when playing a lot of games.
I quickly threw it out and replaced it with a naive BFS that performed no heap allocations at all. Didn’t even show in the profiler anymore.
At this point, I noticed I was able to use a beam width of 20 with a depth of 25. I also made it smarter and it would stop if a time deadline was reached. (So in practice I don’t reach the depth of 25 very often but it goes as deep as it can during the alloted time)
And that’s where I stopped! I’m rather proud since it’s my first time reaching the gold league during a contest. I hoped to reach legend but next time I’ll try to work on the contest during its whole duration instead of only the last week-end…
A word about LLMs
These past few years, I’ve often used LLMs as interactive rubber ducks.
I would give them a prompt like “I’m working on X. I’m gonna dump my brain and you will ask me questions to challenge my approach”. It would then ask me a lot of questions about the topic which would force me to think deeper about it.
It’s an extremely useful approach, I recommend you to try it sometimes.
I’ve also tried a few times to have the LLM write code for me.
My general conclusion is that they are way too stupid to write proper complex/deep code.
However, they can be extremely useful for one-shot, self-contained code.
Concrete examples: a Python script that would perform some specific operations on AWS. A set of unit tests for a function you give it. Or even a small benchmark framework to measure the performance of a function.
Basically, the LLM should not be considered a brain but a pair of hands. The human should perform the thinking but the LLM can execute the plan. We simply need to be very precise and tell it exactly what we want AND how we want it.
Though I’ll admit I’m a little bit salty that Whatever managed to make a bot that reached Legend using Gemini. (Discord message)
The wood leagues
I have moved my feedback about the Wood leagues to a dedicated forum topic.
Thank you
Alright, that’s it for my small post-mortem / feedback. I’m sorry I couldn’t write more, I’m a lazy guy.
Thanks a lot to the Codingame team for these contests and for the platform in general. It’s pure joy for someone like me.
Thanks a lot to the community for the chat/help on Discord and for the post-mortems I really enjoy reading.
See you all in the next one!
Telokis