Spring Challenge 2022 - Feedbacks & Strategies

The topic where you can tell how you liked the challenge, & how you solved it.

As the challenge game will be released soon as a classic game on the platform, please don’t share your code publicly.

Global leaderboard: https://www.codingame.com/contests/spring-challenge-2022/leaderboard/global
After all matches are finished playing, we’ll rerun the entire Legend league. We’ll also do a check for cheaters. Then only, the ranking will be final.

Next event: not yet announced. Stay tuned.
=> https://www.codingame.com/events

Thanks a lot for participating!

16 Likes

Language: C++
League: Legend
Rank: around 150 before rerun

Strategy

My strategy is based arround cracking the seed of the game as soon as possible to predict the angle of each spawn, and predict when and where a mob is entering in the visible game area. This allow me to plan far in advance timing attacks that would otherwise be hard to setup.

Some examples (when it worked):

Scouting (collecting information)

I scout the 3 first waves, there is 2 new mobs and their symmetric per wave, so I collect information about 6 mobs. I am solely interested in their direction vector. If I spot an odd-id monster, I flip the direction to end up with 6 directions of even-id mobs. For instance:

6: (-361, 170)
8: (-386, 103)
10: (16, 399)
12: (-229, 327)
14: (-5, 399)
16: (-329, 226)

That’s all I need! For the technique to work, I don’t need them to be consecutive nor the first 6, but it simplifies a lot the implementation and avoid having to handle winds (that also draw numbers from the random generator and thus change it’s state). Since the last mob spawn at turn 11, it is impossible that a player collected enough mana and that a mob reached inside a base before this time, so wind cannot affect the first 3 waves. To make sure that I didn’t miss a mob, I generate 32 probes for each spawn pair at each wave, covering the angle range, and I assign my heroes to cover them all (once a probe is spotted, it is removed).

Cracking the seed (in 1 turn)

Note: I’m not going into the details of how the method works, but there is already an excellent 2 part video on the subject by Matthew Bolan (in the context of minecraft seedfinding) Minecraft Seedfinding Ep. 2 Pt.1 - A General Seedfinding Problem - YouTube Minecraft Seedfinding Ep. 2 Pt. 2 - Lattices and Linear Programming - YouTube)

Once I collected the 6 directions I can reverse the vector to find the approximate random angle:

double rvalue = (-std::atan2(dir.x, dir.y) + maxDirectionDelta) / (2.0 * maxDirectionDelta);

Based on my observations, I conservatively chose a confidence interval of ±0.001, and reversed Java’s Random nextDouble function to find min and max values for the internal seed every 2 state of the random (nextDouble calls next twice for the 26 higher and 27 lower bits, unfortunately, the lower bits and some of the higher bits are lost in the approximation):

long minLong = (long) std::ceil((rvalue - 0.001) * (1L<<53));
long maxLong = (long) std::ceil((rvalue + 0.001) * (1L<<53)) - 1;
mins[i] = (minLong >> 27) << 22;
maxs[i] = ((maxLong >> 27) << 22) | 0x3fffff;

Which for the example above gave me the values:

rvalues: [0.9318906091090353, 1.0003956303151396, 0.4846910347877601, 0.733358520016807, 0.5047863642993761, 0.8700910401509881]
seeds_min: [262022411321344, 281304859934720, 136146919096320, 206140596027392, 141803252613120, 244627378536448]
seeds_max: [262585366609919, 281867815223295, 136709874384895, 206703551315967, 142366207901695, 245190333825023]

Now, finding the seed is about finding a vector of 6 values inside this interval that when used in Java’s Random generator gives us the result we expect. A solution would be to try every possibility, but due to our missing bits, that would be 562955288575^6 combinations to test. Fortunately, we can reduce this by exploiting lattices, linear programming and a branch-and-bound search (see Mattew’s video). In the case of the challenge, 6 values constrains the search enough so that only 1 possible sequence of seed is searched. So one matrix multiplication later, we get the sequence of internal seeds:

seeds: [262309646766292, 281466933482926, 136375006762776, 206475916988306, 142282001333660, 244993047446454]

We can verify that it is indeed what we are looking for by comparing to the seed of the game in the ide (9135968950900845600). When setting a seed in Java’s random, only the lowest 48 bits are used and the value is xored with Java’s LCG multiplier:

(9135968950900845600 & ((1L << 48)-1)) ^ 0x5DEECE66D = 135647475894861

We could advance this seed one step to get the first value of our sequence of seeds, but a cooler way is to go one step backward from seeds[0]. The inverse of a LCG is also an LCG, in case of Java, you can rewind a random using:

prev_seed = ((seed * 246154705703781L + 107048004364969L) & ((1L << 48) - 1));

Which gives us what we expect:

((262309646766292 * 246154705703781L + 107048004364969L) & ((1L << 48) - 1)) = 135647475894861

That’s it, now I can predict all spawns in the game.

A few additional notes:

Keeping the seed in sync

One caveat, is that the seed can be modified by the players, if they use wind from inside their base to push a monster outside. Since the order of mob processing is hard to predict (since it relies on the implementation of HashMap in the referee, see this PR Maintain order when processing push by wind by pikazlou · Pull Request #27 · CodinGame/SpringChallenge2022 · GitHub) I didnt bother to account for winds, and just resynchronize the seed by trying each n-skip possibilities when encountering a monster that has spawned in a weird direction. In practice, this kind of desynchronization happen very rarely (especially in higher leagues) and are quick to fix. I also later adapted my strategy to never wind defensively from inside to outside the base (I either wind before the mob enter, or make sure it stays inside).

If the wind processing order was deterministic, a simpler way to resynchronize would be to use the result (mob direciton) of my own winds. It could even be used to manipulate the outcome of the next spawns, by using wind at specific time. I didn’t bother going this way.

Farming

My farming strategy is very simple: going to the threatening mobs in priority and otherwise the closest, computing barycenters of closest mobs to maximize damage.

Attacking

I use 2 heroes and 2 main forms of attacks:

  • one wind then double wind with stacked heroes (30 mana, 2 turns, 6900units)
  • four winds with spaced heroes (40 mana, 4 turns, 9100units)

I didn’t used shields, and barely used redirects.

Feedback

This challenge was a lot of fun. As my ranking probably tell, reversing the seed isn’t that much of an advantage, especially against the higher leagues meta. I hope seed reversal will not get nerfed too much in the next challenges, nor become the new meta, but maybe make it like a puzzle inside the challenge that can give a small bonus to people who solves it.

56 Likes

BlitzProg - ~420th (~20th gold)

It’s not often that we see a challenge with such a broad range of tactics, and that was surprisingly pleasing. Going full defense, or going with 1, 2, even 3 attackers, I really can’t tell which was the best strategy, but can tell that I faced many opponents that had very different idea each with very clever tricks. Lots of fun, except maybe when climbing the ladderboard to get blown away by a counter to your own strategy :smiley:

There was also quite a lot of people this challenge, nice to see!

I picked the 2-wind attackers 1-defender strategy. The defender buys as much time as possible to let the attacker score 3 spidergoals.
The base idea consists of stacking two WINDS on a appropriately placed spider to blow it into the ennemy base from afar. I cannot defend for long against one or two attackers, so most of the time, it is about who’s going to have the other lose his 3 lives first.

Several upgrades were brought, such as attacking from the border of the map to make the most out of the fog and perform sneaky attacks, controling spiders to bring more sneaky candidates for goals, and making assists (or “pre-Wind” attacks) - that is, using a single Wind spell to have a spider conveniently placed for a double-WIND.
This, along with other fixes, got my attacks to be increasingly harder to counter.

Edit: that post above makes me feel tiny :smiling_face_with_tear: My PHP code has no prediction whatsoever and is completely procedural. But that rng reversal is very instructive, I’ve got to try this kind of tactic later on.

Earlier leagues were reached by simply targeting enemies closest to base (Bronze league), and added basic defensive formation to survive longer than the opponent. (Silver league)

I hope to see more challenges like this one! Thanks you for the contest :slight_smile:

12 Likes

At the time of writing, my place is still pending but my last stable ranking was ~top 100 silver.

I didn’t have as much time to work on this contest as I wanted, so many good ideas that I didn’t get to implement. Yet.

The biggest problem I faced was the fog of war. Most of my losses are just me not having enough wild mana, and that’s simply due to me not actually writing any hunting code and just waiting for spiders to come to me.

I added attacking very late into the contest, so it’s not the best (but it was good enough to get me up to where I am). Basically, just run to a hardcoded point near the enemy base while controlling spiders along the way if they’re not already headed that direction. There’s some extra code in there to use wind for groups of spiders and to put shields on some of them, as well as controlling opposing heros out if possible, but it ended up being a challenge to balance that with my mediocre defense.

All in all, not too bad for me considering I barely had time to work on it.

6 Likes

Nice game, thanks to CG and comunity.

Finished mid legend before rerun. I started with python heuristics and then switched to C++ performing MC search with limited set of moves. This set included 8 moves, move to the closest spider, wind, shield and control closest enemy or spider. I also used 1 attack 2 def strategy. MC simulations were performed separately for defence and offence, with some heuristic switches of behaviour on top. One thing I liked the most about this game is that any strategy could work. One could do anything he liked and still get to legend, I think thats why we see some completely different strategies at the top legend. I personally spent too much time debging sim and trying to make my search work in defence, adding limitless heuristic features and ending up rewriting defence to heuristics, leaving MC only for attack. I think instead I should’ve experiment more with the strategy since the best defence this contest was fast offence.

Anyway it was realy fun to see so many old familiar faces in chat and ladderboard, discussing strategies and just joking around. I really missed that feeling over the last year, so I hope CG won’t give up on us, and there will be at least some multiplayer contests.

The things that actually worked for me, giving noticable winrate boost in random order:

  • tracking spiders + symmetry constrains
  • calculating directions for pushes from spiders to base (opposing to player → base)
  • for two defenders use shield only when the second one is under control
  • walking around enemy base to intercept and push spiders, strating from turn one
17 Likes

My rank will end nearly #320 after recalculation in legend.
and #2 in India

First of all thank you to Codingame team for such a great, interesting contest for one more time and also i would like to congratulate them as SC22 became the biggest CG contest of all time till now.

My final bot approach was simple enough,

Defense
2 heors for defensing the base going to first two nearest spiders, in case any spider is shielded and in my territory of distance 5000 then both heros will target it cause you can’t wind them

The attacker
the strategy was simple enough just explore a bit by walking here and there and wind the spiders inside shield them if it looks good and also control the opponent heros if they are damaging spiders which are going to harm opponent

Lacking points
mana - I lack a lot in mana gathering due to which i lose many games,
vision - I also didn’t spread out my heros in a way such that i get a good vision which is necessary for attacker atleast
use of symmetry - As we know game is symmterical so can be used for monster tracking which i didn’t used

THESE LISTS OF LACKING POINTS WOULD HAVE BEEN LESS IF I DIDN’T HAD MY EXAMS :smiling_face_with_tear:

Further Scope Of improvement in my current bot
a - A better farming strategy
b - Attacker having a good vision of new spawning spiders or say use of symmetry included.
c - Detection of wind canons from opponent to prevent it.

FUNFACT - I started writing sim on 3rd day of contest but on 4th day i thought heuristic can be much better than average sim because i am bad at evaluation part of sim!

Thats all from my side!!
Thank you again to CG and the participants for making this contest a big one.

8 Likes

League: Legend

My bot tries to take advantage of symmetry in this game.
My bot locates

  • 1 hero at 8200 far from my base with 10 degree (Blocker)
  • 1 hero at 8200 far from my base with 65 degree (Blocker)
  • 1 bot at 8200 far from opponent base with 35 degree (Attacker)

Whenever we find a monster, we assume there is the other monster at the symmetric position.
The strategies are

  • Find/Guess a point where a monster firstly appear in the opponent’s base
  • The attacker goes to a place which is 1220 far from the point the monster would appear
  • Once the monster appeared, spell WIND and spell WIND again next turn

While the attacker is waiting for a monster, if opponents are close to the monster, I give SHIELD to them so that I don’t WIND them. Some bots don’t think of this case :laughing:

Since my bot hopes no opponents should be inside, my legend bot couldn’t win the bot I wrote in Bronze which always locates a guardian very close to the base. :laughing:

The most important thing I noticed is there are only 2 spawn points in the game.
All monsters should be born either at (X/2, -799) or (X/2-4000, 799) meaning if the monster’s vector pass through either one, we can practically assume the monster wasn’t moved before. It means the symmetric monster should exist. My blockers also use these symmetric information to find attacking monsters.

Lastly, thanks to CG for organising this fun event :smiley:

9 Likes

Language: Scala
League: Legend
Rank: around 131

I enjoyed a lot this challange, and learned a lot. Congrats to all! I ended around position 120 with scala.

I would like to talk about my implmentation for finding a optimal potition to farm the most enemies possibles in a turn.

First i search for a targed by some criteria. Once i got the target, instead of going directll to that position, i want to find a optimal position that i can reach the most enemies possible to attack them at the same time. I’m not an expert in maths, but i found my own solution and did a script to help me understand it better:

Objective: Find a middle point where the circle of the hero movements intersects with all the enemies in range of attack.
Constrains: I already filtered enemies that are overlaping and in range of hero. If there is multiple overlaping groups of enemies, i find the best group, but won’t talk about it now. Also if there is only 1 monster in range, i don’t apply this.

My exemple is 2 enemies(Red, attack range) and 1 hero(Green, movement range):

Step 1: Draw Movement circle, and enemies range of attack. As you can see visually it’s easy to identify where the optimal position can be, where all 3 circles intersect, so it can attack the 2 enemies:

Step 2: As i don’t know a lot of maths and geometry, i started working with squares. So i draw a square for each circle, and i find the intersecting rectangle of all of them.

Step 3: Find all intersection points of the circles. Get only the intersection points inside the overlaping square from step 2. With that points, i will draw all possible trianges with each intersection points and calculate it’s center point. Then i will find a center point that is in range of all eneies and hero.

There could be a case where some triangles can be outside the zone i want to be, specially when there are more than 2 enemies. So that’s why i check if the point is in the correct distances. A exemple of a wrong triangle:

I’m sure there is better ways to do it, and i would like to listen to them!

Edit: I also would like to mention that the overlaping squares are optional. I use it only to reduce the number of triengles i need to check for performance. With the first case where there is 2 enemies, with the overlap square i have 3 intersection points so there is only 1 possible triangle, but if i don’t do the squares overlap, there is 6 intersections, so there is 20 possible triangles to check. This can grow a lot. For example 3 enemies like in last case there is 6 points inside the overlap square so it’s 20 triangles, but including the outside intersections that’s 12 that means 220 possible triangles.

20 Likes

In your case, the solution is a basic centroid. It becomes more difficult if you have n candidates and want to maximize the ones you grab. The exact solution is a bit tricky but a simple greedy algorithm yields very good results:

  • For all your points, you compute the centroid
  • If the centroid is within range of all nodes, you got your winner
  • If it isn’t, you prune the node farthest away from the centroid, and loop

The idea is that the node farthest away from the centroid is also the most isolated node, and most probably not a good candidate.

9 Likes

Bottom Legend here.

This post is more or less for beginners…

I believe it was possible for me to reach top 150, but I haven’t allocate time for this contest at all.

My heuristic was purely about earning 150+ during at least 55 first turns and then sending mobs to opponent base trying to apply wind train (push once with 1 hero and then twice with 2 heroes). Nothing fancy, nothing to be proud of.

Guess where the spider is

Many juniors asked in chat, how do we know where the mobs are if we don’t have them as inputs due to fog. Since spawned spiders are mirrored, you can fully compute all info about spiders:

mirror(spider) = Spider(
    id = (id % 2 == 1)? spider.id - 1: spider.id + 1,
    x = MAP_WIDTH - spider.x,
    y = MAP_HEIGHT - spider.y,
    vx = -spider.vx,
    vy = -spider.vy,
    threatFor = (spider.threatFor == 1 ? 2 : ((spider.threatFor == 2) ? 1 : 0)
)

Spiders also are spawned only on top/bottom part of map, so to get complete info about all the spiders, you don’t have to do much work. You just have to cover as much of longer side as possible. That’s why you can see many good players immediately go to longer sides of the map.

How do I hit more then one spider?

This one probably earned me legend since most of my bot is weak garbage.
Already described by raxkin. I’ve simplified it (less accurate model) by going directly in between of 2 mobs, if I can reach it. It won’t work in every scenario, so sometimes I simply go for closest mob. Next image is unsuccessful scenario - I was able to hit 2 mobs, but didn’t do so. Anyway. It was enough to earn more than any bot in gold league.

Guess where the spider will be?

Quite funny how you hero is describing some kind of curve when trying to catch spider right? Well if you would know where the spider will be in 2-3-4-5 moves you should go straight there right?

So one move later mob will be at position [x + vx, y + vy] and its vx and vy might change if they reach BASE_RADIUS:

if (topBaseDistance < 5000) {
    mobVx = -(MOB_MAX_MOVE * mobX / topBaseDistance).toInt()
    mobVy = -(MOB_MAX_MOVE * mobY / topBaseDistance).toInt()
} else if (bottomBaseDistance < 5000) {
    mobVx = (MOB_MAX_MOVE * (MAP_WIDTH - mobX) / bottomBaseDistance).toInt()
    mobVy = (MOB_MAX_MOVE * (MAP_HEIGHT - mobY) / bottomBaseDistance).toInt()
}

Easy huh? Now you can reach the target destination in how many moves?
Simply: number_of_moves = distance(hero, mob.x) / HERO_MAX_MOVE

So you just move the mob n times and if n < number_of_moves then you can reach the mob at that distance (substract ATTACK_RANGE of course, if you want attack the mob)


(hit in 2 instead of 3 moves)

Memory (for beginners bronze/silver players):
Some folks asked about how do we keep track of entities, that disappeared in fog of war… Well this is kind of design thing. I guess you have simply read the game state from input and recreate your whole model that holds the Game State. Don’t do that if you need to remember something. Do UPDATE instead. Have single class where you send new values only to update it:

class GameState {
   updateGameState(read_values) {}
}

Now you can for example guess where the hidden mobs might be. My bot tends to defend against bots he had never seen, thanks to the update function, where I simply move all mobs I’m assuming are alive.

There are a lot of small things you can come up with, hope I’ve helped somebody to be at least slightly better

Minor tip:
If you don’t want to cast empty wind because of rounding errors, just make the “wind radius” smaller by 10 units :slight_smile: or do it properly

19 Likes

Not sure if i’m doing something wrong, but i test it with centroids and this is the results(blue dot):

So not sure if this works. For the centroids i applied this formula:
(x,y) = ((x1+x2+xn) / n, (y1+y2+yn) / n)

3 Likes

Don’t include the player, and retry a few time, each time remove the furthest mob until you hit all of them.

3 Likes

Language: Scala
League: Legend
Rank: 18

General

I’ve spent a lot of time watching replays to find (steal) an efficient strategy and detect the weaknesses of my bot.

As always, I’d recommend reading the instructions carefully (I did not and missed the action order : the heroes are moving first, then they are attacking monsters and finally monsters are moving)

Strategy

Early game, I adopted a defense-only strategy. As a lot of games were reaching the limit of 220 turns,
my idea was to push monsters outside my base to optimize for wild mana. It worked like a charm to reach the middle of the gold league

I then began to doubt the defense-only strategy. Looking at replays, I saw strategies with one, two or even three attackers.
I’ve also looked at the gold boss and its strategy looks quite simple :

  • It attacks from the beginning of the game with a single attacker (without waiting to reach a certain amount of mana)
  • It never uses spells CONTROL and SHIELD
  • It uses WIND in defense only in the case of an imminent danger on its base (probably to save mana, at the expense of wild mana).

So I decided to mimic the gold boss (two defenders and one attacker) in order to access the desired legend league.
One key thing to an efficient attack is to be able to use WIND two times in a row.
So your attacker needs to be moved at a right distance between the monster and the opponent’s base.

In order to climb the legend league, I have to refine the strategy a bit.

Defense

By order of priority

  • if no monsters in sight, both defenders move to fixed points
  • if only one monster is visible and inside the base, both defenders move to it
  • if only one monster is visible and outside the base, the closest defender moves on it while the other one moves to a fixed point
  • if 2+ monsters are visible, defenders move to the 2 monsters closest to the base

Attack

By order of priority

  • Shield monsters if turn count before reaching the base is above health/2
  • Push monsters that can be pushed
  • Move to the best position for next push
  • Push opponents that can be pushed
  • Control opponents if last push was recent
  • Follow out-of-sight monsters if last push was recent
  • Roam around

Testing (aka don’t guess, measure!)

Submissions were really slow, so I’ve tested as much as I could with brutaltester.
It is very useful to tune your magic numbers, with the risk of overfitting on your own strategy.
Basically, every new feature has been tested with brutaltester.

I’ve also used cgstats, mainly to find the opponents against whom my AI was failing.
I saw that I performed very poorly against two-attackers, but I didn’t find how to fix that.

Fun fact

Replays allow me to detect a weird behaviour when a monster is pushed out of the map while it is in a base.
It stays in place, and it can end with an infinite loop of WIND instructions that consumes all your mana.

To do

  • Leverage from the symmetry to fight the FOW
  • Keep the knowledge of seen monsters that go outside the FOW
  • Detect dead monsters (to avoid following them)
  • Compute the intersection point between a hero and a monster (I am anticipating only the next turn)

Conclusion

I did enjoy this challenge a lot, it was really much deeper than I thought at first sight.
My code is a mess but still manageable because the size and the complexity is small!

I’ve reached my best ranking ever. I’m so happy about that.
I will finally get my first CodingGame t-shirt !

20 Likes

Yes, but you still need to take into acount player movement range. In the case i exposed, i should not remove any mob, because all are in range of the hero. And finding the centroid of only the 2 mobs still is not inside the optimal zone.

3 Likes

Hello there,

I ended up 238th after the rerun in legend. I started my AI with a basic defensive strategy making my heroes to wind spiders out of the base so I could focus on farming wild mana (only way to win when no one is losing base’s HP). I limited to 1 hero per spider in order to split them allowing for more defensive winds options. This was enough to reach gold at the opening. I then waited until saturday to develop my offensive AI, based on 2 defensive heroes and 1 offensive one. The strategy I used is fully described by the 3 following functions:

    /**
     * Strategy (decreasing priority):
     *  - Choose defensive orders
     *  - Choose offensive orders
     */
    private static void computeOrders() {
        myAttackHero = myHeroes.iterator().next();
        Queue<Hero> heroesToCompute = new LinkedList<>(myHeroes.stream()
            .filter(hero -> hero != myAttackHero)
            .collect(Collectors.toList())
        );
        computeDefensiveOrders(heroesToCompute);
        if (myAttackHero != null) {
            computeOffensiveOrders(myAttackHero);
        }
    }

    /**
     * Strategy (decreasing priority):
     *  - Use wind spell (save the base or redirect spiders)
     *  - Use shield on a friendly hero in range that is currently controlled
     *  - Move toward near base targets
     *  - Move toward threat targets
     *  - Generate wild mana
     *  - Move to guard point
     */

    private static void computeDefensiveOrders(Queue<Hero> heroesToCompute) {
        computeDefensiveWindOrders(heroesToCompute);
        computeDefensiveShieldOrders(heroesToCompute);
        computeNearMyBaseTargetOrders(heroesToCompute);
        computeThreatTargetOrders(heroesToCompute);
        computeWildManaTargetOrders(heroesToCompute);
        computeDefensivePatrolOrders(heroesToCompute);
    }

    /**
     * Strategy (decreasing priority):
     *  - Use wind spell on spider if it can remove instantly 1 hp to enemy base
     *  - Use shield on spider if it guarantees the spider can reach the enemy base before the shield expires
     * and have enough hp to survive Constants.HERO_DAMAGE damage per turn until then
     *  - Use wind spell on spiders to move them toward the enemy base
     *  - Use control spell on his heroes to protect in base spiders
     *  - Move toward the closest spider near hisBase to assist it
     *  - Move toward the visible spiders to spell them. Score the possible positions to
     * maximise the number of spiders in wind range and minimise the damage dealt to them
     *  - Patrol near enemy base to detect close spiders
     */
    private static void computeOffensiveOrders(Hero hero) {
        computeKillerWindOrder(hero);
        computeKillerShieldOrder(hero);
        computeOffensiveWindOrder(hero);
        computeOffensiveControlOrder(hero);
        computeTrackNearHisBaseSpiderOrder(hero);
        computeOffensiveMoveToSpellOrder(hero);
        computeOffensivePatrolOrder(hero);
    }

About the wind spells in the defensive case : I kept the conditions I used to farm wild mana if there was no enemy hero threatening my base, but used another method otherwise. If there was an offensive hero, the goal was to prevent spiders to reach my base, therefore I only used wind to counter an enemy wind that would make me lose 1 HP or to prevent a spider to just walk in the base.

for each wind, I tested 16 different directions for the speed vector and used an evaluation function to pick the best one. When a hero had to move toward a spider, I simulated 16 * 16 different moves and an evaluation function in order to pick the best move too. Maximizing the damage dealt to spiders (in defense), minimizing the damage dealt and the distance to spiders for my offensive hero, maximizing the distance of the spiders to my base, minimizing the distance of the spiders to the enemy base…

3 Likes

League finish: Silver

Didn’t have much time for this one, happy to have finished around the top of silver given only a few hours put into it just before the contest closed. Nothing I can say about my bot strat that somebody else here hasn’t already described and implemented better. So here’s my general feedback and then some discussion of the solutions I saw:

Competition feedback:

  • After a year I was so happy to have a bot competition back, cannot overstate this :smiley: Even though I’m really busy atm I just had to at least take part for that reason.
  • Classic problem always faced during competitions: submissions get very slow during peak hours, especially at the end of the competition. This pretty much ends the competition 12hours early for some people, or at least restricts what they can do during peak times. I understand resources are limited and it’s a free competition. I know this isn’t a B2C business, but I’d happily directly contribute something by buying a T-shirt if you ever decide to sell some merch, or even pay for some premium ‘courses’ in AI programming/learn a new language.
  • The challenge was super well designed and accessible for newcomers. It’s possible to do well and progress up the leaderboard even with limited programming experience.
  • The complete rules weren’t overly complicated or restrictive. This made it really accessible and freed up the player to try almost any strategy with some success. Possible to progress very far even without utilising all available spells. Simple code worked really well.
  • Most of the nuance was quite situational in the game itself. This kinda discouraged trying to account for every scenario. Heuristics seemed to work very well and search was much less potent than in previous competitions. It’s nice to have a competition like this once in a while where crunching calculations as fast as possible isn’t so much a deciding factor (others may disagree).
  • There seems to be so much scope for bot improvement here. The successful strategies are so different, it is certainly not a ‘solved’ problem. I think that’s a sign of a well designed competition if it has longevity as a multi game in future.

Competition solution notes:

  • A metagame seemed to emerge in different leagues. It felt a lot like peeling back the natural (rather than forced via rules) layers of complexity to the game. Wood league seemed to me to be about mastering defense. Bronze introduced exploration/farming as a way to maximise wild-mana and break ties that emerged from defensive play. Silver introduced the idea of the attacker role to disrupt opponents. At the top end of silver lots more creative strategies emerged: canon, hoarding mana then rushing the opponent (lots of flavours of this, including fully defensive turtle before rush, or all heroes exploring to farm and maximise mana for early rush), interacting directly with opponent heroes, two attackers, three attackers, etc. From the looks of things it seems Gold/Legend followed a similar pattern with people broadly optimising the strategies I saw in Silver and handling more edge cases.
  • The only strat I found a bit problematic was the canon strat. It seems maybe a little too lethal for something so simple. It’s not so easy to anticipate or block; the best I could do was disrupt it a bit by letting my defenders control opponents in some scenarios, but a persistent canoneer would drain my mana like this very quickly and most of the time succeed anyway. Others maybe disagree with these thoughts, interested to hear.
9 Likes

Ended at Global 902nd. My strategy was a Defender - Farmer - Attacker strategy, where

  • Defender CONTROLs or WINDs mobs out of the defense zone, or attacks them if it feels safe enough
  • Farmer patrols around my base and simulates optimal movement for getting (wild) mana. It used to be ~4-6 turns of simulation and generating random trajectories in a ~25 ms time window, but I switched it to 1 turn temporarily and forgot to turn it back -_-. A GA would probably make more sense, but I felt that there wasn’t enough computation time to make it superior to blind random searching.
  • Attacker patrols around the opponent’s base and CONTROLs or WINDS mobs into the enemy and then SHIELDs them, while avoiding attacking any of these mobs.

However, this post is not about this bot.

After submitting the above bot, I came up with a cheesy strategy and cobbled together a proof of concept in half an hour before I had to leave. I guess this might be a version of the “cannon” strategy that DumbassDan just mentioned a few moments ago? Naturally it didn’t perform well, but I think with some optimizations it might have some value. Here’s the specifics of my version:

Phase 1:
All heroes patrol or stick to point outside the base to defend the base and gather MP as fast as possible.

Phase 2:
Once 90 MP is gathered, all heroes rush towards the opponent’s base while gathering onto the same pixel

Phase 3:
Patrol around the enemy base and look for mobs that are within 3x2200 + 300 = 6800 units of the opponent’s base. Cast 3 simultaneous wind spells and dunk the spider into the enemy goal xD.

I was worried that maybe the referee would allow spiders to be pushed out of bounds from outside the aggro radius yet going through the aggro circle, but it seems to have foreseen this possibility.

Obvious problems:

  • In phase 2, the heroes abandon defense and rush towards a point about 6800 units from the opponent’s base. This takes roughly 10 turns. In order for this to be a safe-ish strategy, there must be some logic for clearing out nearby mobs before entering phase 2, because mobs take 12 turns from the edge of the aggro circle to reach your base, and 2 turns is not enough time for phase 3 to dunk 3 points.
  • Knowing where spiders will be is critical. In my proof of principle, my heroes would reach the opponent’s base and waste many turns looking for a mob. I didn’t realize that you could extract the seed as detailed in the 1st post - that will be very helpful.
  • This strategy specifically counters full-defense strategies. If there is even one attacker on my side of the field, this strategy loses. Thus, there should be a backup strategy if it’s determined that the opponent is using an attacker (in my main strategy, I just count how many times I see an opposing hero near my base)
  • Keeping the heroes together means that a WIND spell won’t separate them. However, 2200 units is pretty far. Additionally, CONTROL will separate them. Thus, I wonder if casting SHIELD on themselves first would be better.
  • A lot of defense strategies that I saw were good at clearing mobs even 6800 units away from the base. It might be better to wait until mid-game to charge the enemy, which also means more MP saved up for unexpected situations.
4 Likes

It’s been at least 5 years since I coded professionally or participated in a contest, but for whatever reason I woke up this Saturday and thought ‘hey, there’s a CG contest going on, I wonder if I’ve still got it’.

The rust was obvious, but after a while I got my feet back under me and created something that wasn’t entirely embarrassing. With no time to build a simulation or debug an AI, I started with a spiderweb of ifs to see how the game worked and then scrapped it on Sunday morning for a heuristic bot using what I’d learned. The heuristic bot only got 3 submits before the end of the contest, but it made it to mid-Silver.

No groundbreaking strategies to share here; 1 attacker, 1 defender, and 1 floater. The only difference between the 3 was where they would go to explore when they got bored, plus a leash on the defender. I was somewhat proud of only using 1 magic number (answering the philosophical question ‘what is the value of a single life?’). Further enhancements would have included something to explore the map more intelligently, probably meshed with something to predict monster locations when I couldn’t see them.

I couldn’t believe how many people were playing. Never seen that many in a CG contest before, and I wish the servers could have kept up with them. All in all, it was a fun time.

8 Likes

123rd Legend, #2 in Canada

I had a blast (although my sleep suffered). Thanks CG for setting up this competition.

Wood to Gold - I went with a relatively mid-game type of approach. Early on focused on farming with my 2 defenders and 1 roamer on opponent side. Once a specific threshold was met (around spider health and number) I would control as many spiders as I could and roamer would shield them while dancing around the opponent base (to keep their health high). The dancing portion was probably the best part I had as I would calculate the optimum position where hero would not attack a monster on current turn and next turn (so that I could stand still and shield). It also looked cool :slight_smile:

This was enough to get me to top 50 gold but no matter how much I tweaked the settings I couldn’t get past Gold boss. I decided to move away from a mid-game approach and rewrote my roamer to focus on aggressive early game harassment. This was enough to get me promoted to legend.

Legend - matches were a lot faster and there was a lot of holes in my existing code. I ended up adding the following features which helped boost my rank:

  • Intercepting spiders that were approaching an enemy hero (either attacking or controlling if too far)
  • Simulating worst case scenario for base defense - taking current spiders close to base and simulating worst case scenarios with 1 defender. If defender can successfully defend no need to cast spells or bring in a 2nd defender. Although this was half-baked it worked well to prevent from over-helping so my 2nd defender can continue farming outside base. Also the better I made this the better results I had (obviously) but I found it difficult to do outside of calculating every possible scenario.
  • Tracking last known enemy hero locations - mostly used on offence to determine how many defenders were in base / nearby. If I knew there was only a single defender (and other was too far away based on last seen time and travelling straight to base) I would allow harasser to control chain them away from a spider who would walk in.
  • Double/triple pusher defense - seen a lot more in Legend. I had an approach that would try to intercept spiders as much as possible and if they couldn’t wind heroes away from the sweet spot. Was still a coin flip against most pushers, but had no chance against better coded ones.
  • Taking advantage of symmetry - instead of harasser patrolling enemy base back and forth it would take advantage of spiders seen close to my base and anticipate their arrival

All in all great contest and I’m hoping we get another one soon!

7 Likes

You don’t have any wind in defence?

1 Like