Codingame Spring Challenge 2022

Blood for the blood god

You can find the challenge’s page here

Rules

The challenge pits two players against each other on a rectangular map. They each get 1 base with 3 hit points (on opposed corners) and 3 heroes who can move, attack and use spells. The twist is that the heroes can only attack monsters (who regularly and symmetrically spawn from the edge of the map), not other heroes nor enemy base. Doing so generates mana, which is used to cast spells. Monsters within a certain radius of a player’s base will be attracted to it, and if they are not killed before reaching it they do 1 hit point of damage. A player whose base drops to 0 hit points obviously loses the game.

Players play through a program they write beforehand, with no input nor command during the game. The programming language was free of choice. The program received data each turn describing the state of the battlefield and must output 3 lines, each one describing an hero’s action for the turn.

The challenge worked by leagues (namely Wood 2, Wood 1, Bronze, Silver, Gold and Legend) where you had to defeat a NPC Boss and a fixed amount of players to rank up. On Legends there was no Boss and players where ranked with an ELO-like system.

Rankings

I ranked 264th out of 7695 players, thus reaching the middle of the pack in the Legends League.

My school, 42, ranked 2nd out of 303 registered. We were beaten by the University of Wroclaw, who proved to be formidable opponents throughout the challenge.

Overall I’m pretty happy with the results for my first codingame challenge ever !

Code and strategy

Generalities

I used Python, of course. The timeout given was very generous so there was no need to use a more optimised language, unless you were going for some search tree shenanigans.

Quick explanation of spells :

  • SHIELD would render the target immune to spells for 12 rounds
  • WIND would push all entities around the caster towards a single point
  • CONTROL would force the target to move towards a single point for 1 round (if the target was a monster and that monster wasn’t already targeting a base, it would then continue in the given direction)

A meta quickly emerged : 1 hero would patrol the base and defend it for the whole game. The others 2 would roam the map, killing every monster in sight to optimize mana gain. After a while (or a certain amount of mana was reached), they would both go towards an edge of the enemy base and stand there. When a monster entered their reach, they would both spam WIND towards the enemy base to push it to the center so fast the enemy heroes couldn’t react. A proper setup could yeet a monster very far from the base to its center in just 2 turns.

There were a lot of refinements to this : some people played with 3 defenders, trying to disrupt the so-dubbed “wind cannon” setup while waiting for the lone defender to be overwhelmed by monsters. Some others went for 3 attackers, spamming CONTROL on the defender while pushing monsters in a reckless abandon trying to win faster than the other guy. This led to very interesting strategies : some people shielded monsters in their own base to prevent them from being WINDed in and get enough time to kill them by attacking normally. Pretty much everyone coded a routine to wind away large packs of monsters going towards the base, either as a last resort or to prevent a group WIND in the following turns. It was like a giant game of rock paper scissors.

My take on it

The challenge is still available as a “bot battle” minigame so I won’t disclose any code here but I’ll speak about strategy.

Speaking of rock paper scissors, I didn’t expect the wind cannon strategy to be so dominant so I went for a more adaptative bot. I would define a number of “attackers” and “defenders” with some variables to remember the game state of previous turns (things like probable strategy of the enemy, how much defenders he has, how much he tries to CONTROL my heroes…). Based on that info, I would assign the attacker role to some of my heroes (theoretically I could end up with no attacker or no defender but I don’t think such a case ever arose) and the defender role to the rest of them.

They then each had a generic routine (one for defense, one for offense). The principle was the same : for a given hero, get all of his possible actions, weight them against the game state and execute the best scoring course of action.

The defender routine had only one goal : keeping monsters away from the base. If no monster was found, it would patrol the edge of the base, trying to farm mana without getting too far. I had a lot of specific little tweaks for defense : if one or more attacker is nearby and the defender is alone (or far from the other), weight very heavily the self-shield because a single CONTROL could spell (get it ?) disaster. I would WIND out packs of monsters or enemy getting 1) too close to the base or 2) dangerously close to each other, as that was probably a setup for a wind cannon. If I had enough mana, I also would CONTROl far away monsters that would at one point come in range of the base and divert them to either the center of the enemy base or my own wind cannon starting point. The weights didn’t change much with the number of defenders, outside of self-shields and patrol routes.

The attacker routine on the other hand had a lot of different parameters.

  • The “1 attacker” version was intended to counter overaggressive bots and it would focus on controling the most monsters into the enemy base to get a quick win (it would also spam-control the lone defender (if any) away to let monsters do the job).
  • The “3 attackers” version wasn’t ever chosen by my code so it didn’t see much use.
  • The “2 attackers” version implemented the infamous wind cannon with some tweaks. I would first patrol the map for a given number of rounds (or until I spotted the enemy making a move) trying to maximize mana farming by putting my heroes where they could hit the most monsters at once. When the attack phase was triggered, they would move into a position close to the edge of the enemy base, controling every monster in their path. Not towards the enemy base but right behind the chosen attack spot, so that when they arrive into position, there would be a nice chunk of monsters already waiting to be yeeted into the base. Other improvements include calculating 2 turns in advance for a quick setup, moving into position when possible rather than spending 10 mana winding a monster to the right place, going back to farming mode when mana was scarce and an utility to self-shield given the defender(s)’ history of controling or winding me out.

Overall, I really loved trying to do a “smart” bot and not just hardcode a wind cannon but the tactic was SO effective that I had to weight it heavily and my bot would end up doing the same thing 95% of the time. I also wanted to use some linreg to automatically change and determine weights but I lacked the Codingame API to do so, having to change the code by hand on every iteration wasn’t feasible.

It was a lot of fun and many memorable battles (and bugs) were had.

Built with Hugo - Theme Stack