Ghost in the Cell - Feedback & Strategy

I finished on gold league 406/3508.

This was my first contest so I can’t really compare to previous one but it was really fun and addictive.

Basically I used two stances which were selected following some heuristics : Defensive and Offensive

On Defensive stance the AI was not supposed to attack and was balancing the cyborgs on each of my factories. Factories had an higher chance rate to increase their production (again heuristics depending on a computed threat level)

On Offensive stance the AI was supposed to focus on a single target, moving cyborgs following a path predetermined using Dijkstra algorithm. I’m saying supposed because the target selection was not working well…

For bombs, very simple, both bombs were launched on first turn : one on the starting factory and one on the most probable factory to be captured by the opponent.

Avoiding bomb was a challenge, I was using the origin and the min distance from this origin to one of my factories to determine the number of turn of “safety”. Past this moment I was doing some evasive maneuver, moving all my cyborgs from each of my factories to its closest factory (mine or not… :sweat: ).

If I had more time I would have adapt the behaviour to the map size (the AI was better on big maps), fix my target selection algorithm and clean my code (a real mess)

I hope the next challenge will be as good as this one :slight_smile:

2 Likes

So apparently I won. Here is my postmortem. Please tell me if there are mistakes or unclear/unexplained things. You can PM me in the general_fr or the forum.

27 Likes

Very nice write-up, @Agade. Congratulations.

  • danBhentschel
1 Like

@Agade

Congratulations on winning (by a significant margin) and on sharing your strategies! Well deserved win I was quite afraid of - sometimes simpler algorithm with superior domain knowledge/heuristics owns complex search algorithms. We need skill to recognize when to use which of those and you displayed it perfectly in this contest :slight_smile:

You used some interesting formulas for heuristics. Did you intuitively got them right from the start (excluding constants) or did you need to make a lot of trial and error to arrive at them?

Also, I think you made a slight mistake in a postmortem describing best paths:

path and the amount of intermediate factories is <= to every other path

I believe that should read

path and the amount of intermediate factories is >= to every other path

I think I killed the simulation (some 20 million turns per sec without adding more moves) and the rest of my code wasn’t able to capitalize much on that. It’s a very convoluted GA/EA that I’m not particularly proud of.

Congrats again!

btw. I totally agree with comment about hiding AIs. While I don’t really care if someone does it, it seems a bit funny and overly and unnecessary subversive. My view is that our main competitors here are ourselves and our ability to come up with ideas/solutions. Other players are here to provide a very tough and realistic measure of how good we did.

Besides this, I congratulate ‘him’ on the good result as well, without pointing too many fingers :slight_smile:

7 Likes

The <= has been fixed to >=, thank you. I came up with those formulas on my first version but the coefficients evolved with time because with changes and fixes the optimal values evolved. The value of paths through a factory was a later addition though.

It’s interesting that you got that high with an actual algorithm.

1 Like

I pasted it sometimes on the chat, but I’d like to show a replay that reflects you can go faster by jumping from factory to factory rather than a direct path:

https://www.codingame.com/replay/191079078

This is a speed test going from factory ID=3 to ID=4
You save 2 turns, and you have better chances to change your strategy mid-flight.

12 Likes

Hello everyone! 3rd place legend here for my first participation. It was a loooot of fun.

An especially big kudos to the CG dev team, this website and the bakend behind it are awesome.

Also, as many people already mentioned, I really liked the fact that different kind of algorithms/languages could be viable even at the top of the leaderboard (with a preference for heuristics, admittedly).


Metagame

I think it’s been given the name of ‘INC strategy’. My goal was to take control of my half of the map as fast as possible, upgrade it as fast as possible, then send every remaining units to the zero factory, and hope that at that point I’d have more bots than the opponent.
The idea behind this is that it’s the minimum required to win a game, so it probably requires the minimum amount of thinking to solve.

One-to-many moves

The first algorithm I used to decide my actions aimed to find the best moves for one factory with one or multiple targets.

It worked in three steps:

  1. Compute the ‘gain’ to send troops from any factory to any other factory.
  2. Bruteforce combinations of the previous moves to find the source factory with the best combinations of targets.
  3. if the best gain is zero or less, stop. Otherwise, send the troops and go back to step one.

The amount of troops to send is defined as the minimum amount of troops to guarantee ownership of the destination, and the gain is defined as the delta total cyborgs after 4 turns (4 is arbitrary).
.
This worked especially well in the first few turns.

Many-to-one moves

I later realized that combining the efforts of multiple factories on a single target was a must have.

I assigned to each factory a ‘quest’, that can be ‘upgrade’, ‘defend’, ‘attack’, ‘capture’ or None, and an amount of available cyborgs, which is the maximum it can send to complete quests without going in the negatives in the next 4 turns.

What I implemented looks like that:

  1. For each quest, loop over factories, starting with the closest from the quest, and ending with the furthest away from it.
  2. For each of those factories, add all available cyborgs to the quest pool.
  3. If the quest pool is big enough to complete the quest, stop here and estimate the gain of doing the quest (still a delta cyborgs after 4 turns).

Then, find the quest with the better gain, launch the orders, and repeat until all quests are done or none of the remaining quests can be completed.

This made my micro management to upgrade/capture factories a lot more efficient, and allowed me to capture/defend positions more often.


Opponent’s turn

I first assume that the opponent is going to do all the actions cited above (one-to-many and many-to-one). The first goal is to make his amount of available cyborgs more realistic, since he has to defend/capture factories in his territory. The second goal is to predict obvious attacks, in which case I can sometimes counter-attack at the same turn and base-swap.

Then, I assume that any of his bots remaining that can reach one of my factory in 3 turns or less will either do so or stay where it is. Which means they end up duplicated in the game state.


My turn

Once the opponent’s turn simulation is done, it’s my turn to apply the one-to-many and many-to-one algorithms.

Then, after turn 4 (I like 4, it’s 2*2), I start sending all available cyborg to the factory zero (unless my side of the map is under attack in which case instead of sending to zero, I send to anything that’s being attacked in my side).


The ‘INC’ dilemna

The problem is: if you’re last to inc, you loose. But if you inc to fast, you get swarmed.

I don’t really have a solution to that, I think machine learning could be a good approach, but I didn’t set up an offline simulator to generate datasets.

What I did is something like: if my production is at least two more than the opponent, I don’t inc. Which worked ‘ok’, but it’s really not satisfying.


Pathfinding

I bruteforced my way through all the combinations of paths that were at least as fast as the direct traject, and made the approximation that they all took the exact same amount of time (it was too complex to deal with variable travel times).

My rule to prioritize paths was:

  1. The one for which the first node is the closest first.
  2. Only pass through nodes that are owned or have zero cyborgs in them

I also had a second pathfinding function that I used to send troops to the zero factory, that would sacrifice travel time in order to pass by a lot of other nodes and eventually change targe mid-way.


Bombs

Nothing noteworthy here. I targeted factories with a high production, and sent both bombs very early.

I assumed my opponent would target my factories with the highest production, and evacuated before impact.


Hacks

Here is a non exhaustive list of hacks I added in my logic that made the whole thing work better:

  • Never inc factory zero before the very late game, do it ONLY IF I would lose without incin’ it
  • Never inc if I have two or more prod than the other player
  • Never try to attack or capture a factory that is closer to an enemy than to me, that wouldn’t end well.

Python2 specific tips

The first version of the code I submitted only did the one-to-many logic, and it had the tendency to take more than 50ms on big maps… I then red a few articles on the subject, re-wrote everything and made the whole thing run in less than 1ms.

I’m far from being an expert on the subject, but I’d like to share some of the things I learned:

  • while loops are very cheap.
  • the dot (foo.bar) is expensive, it’s faster to use arrays to store data, and local references for functions and methods.
  • memory allocation, despite being very easy to do in python, still takes a lot of time. It’s faster to allocate memory only once.
  • you can’t can’t inline in python, so functions called in loops can get very expensive. sometimes it’s better to write all the code directly in the loop.
  • Not computing the same thing twice is a good way to gain time.

Conclusion

It was a blast, thanks to everyone who organized, as well as everyone on the chats.
You can count me in for the next one for sure :smiley:

18 Likes

Rank 25 Legend, Rank 1 PHP here

Thanks to CG and all of you coders, this was my first contest and I had lots of fun!
Reaching an seemingly unbreakable wall, coming up with something to get through it and then seeing the next wall in front of you while trying to fend of the people behind you. Lying in bed and planning your next steps for the following day. Dreaming about cyborgs and factories. I loved every bit of it.

First of, why PHP?
It’s the only language where I don’t need Google to create a new array, or so I thought…
My code is a total mess. I needed half the time just to get rid of all the bugs. Would not recommend.
But in the end it all worked out somehow. I’m still baffled.

Strategy
I’m totally impressed with what you guys came up with. I have no pathfinding, no calculations of invest -> return and so on. My whole code works with constants I tweaked over the time to get the best results and if statements that reach from London to Paris.

Bombs
Pretty simple: bomb enemy factories with 3 production or when close at least 2 production.

Production
Also simple: Only increase production, if your production is the same or lower than your enemies.

Attacking & Defending
This is the same in my code. I go through my factories and check the surrounding ones. If the potential target factory is not in my hands in $distance turns, I send just as much cyborgs to get/keep control. Potential targets are sorted by their production.

After Sending out my attackers/defenders I see how much troops are left and send them either to the next factory closer to the frontline or just to the closest opponent.

My biggest aha moments
I had several of these. Coding and lack of sleep make some dangerous combination sometimes. While the most of them were due to my horrible understanding of PHP or some stupid missing or wrong characters there were some really great ones understanding the game better.

The first one would be, if I send out troops or bombs, they start moving a turn later. I forgot to include the +1 to $distance (got me around 50 ranks in legend). This also fixed my problem of suiciding squads by my own bombs.

The other was the turns the factory is destroyed after a bomb. The turn the bomb is hitting the factory is counting towards the destroyed factory timer. I still don’t fully understand this, but my calculations were suddenly correct, after I set $destroyed - 1 right at the start of every round.

Last remarks
No matter what I tried, the top15 were unreachable. I got in the top10 for around 30 minutes and then never again close to that. This was just another league for me. Again kudos to you and all other coders that made this contest so interesting! We will see each other no later than next contest.

9 Likes

What was the trigger for switching between offense and defense in your strategy? I tried playing this card but it did not provide stable results for me, so I cut if off eventually.

I was using these simple rules to switch on full defense :

neutral production < 20% of both players production: so there was not that much production point to grab
my total cyborgs > 175% opponent cyborg: I was outnumbering the opponent on army size
my production > 175% opponent production: I was outnumbering the opponent on production rate

I admit quite naive and simple but seemed to work

I see.

I was trying to see whether I control more factories hence have better potential for increase.
That apparently was too simplistic and did not work well.

Thank you.

1 Like

At the top, at every contest, there are players who submit inferior version of their code to the online arena and only release their real code at the end. You might call it competitive behavior, but I call it anti-competitive behavior. If everyone does it, there is no more competition. I am happy that I won without such tactics. In the arena you could always find my latest version.

One of the first thoughts which came to my mind on Monday morning was “I’m really glad that Agade won”, because you didn’t hide your bot! I fully agree to your statement.

3 Likes

Ok I’m late to the feedback party, but here’s mine (in french), #39.

For those who don’t read french or who are lazy, maybe the only interesting thing on top of what was already shared in this thread is that I implemented a kind of “micro-management”, so to decide on its actions, a factory only considers its neighbours.
Neighbours of a factory A being all factories B for which there is no factory C such as d(AC) + d(CB) <= d(AB)
If all neighbours are mine, I’ll either “progress” towards the ennemy or increase, and if not, defend and/or attack.
And magically this gave a coherent global behaviour, even if some lacks of cooperation between my factories can be seen here and there.
Between Friday 22h and Saturday 17h, it allowed me to go from low Bronze straight to top 15, to reach a final 39 after an uninspired Sunday :slight_smile:

5 Likes

There is something that’s not really clear in your post-mortem though.
Would you recommend a raclette between wood2 and 1 for every player ? How significant was that in your victory ?
I’m interested in having one in-between every league promotion but … maybe that’s a bit overkill (especially for the wooden leagues).

3 Likes

This was a very interesting read, thanks for sharing. I’m surprised that a “local” strategy could work so well. Congratulations!

1 Like

I always recommend raclette. Anytime, anywhere. A league promotion is certainly a good occasion.

2 Likes

Hi, I’m late too, but here was my strategy (#32) :

I first calculate for each of my factories what will append in the next turns by adding my arriving troops and my production and subtracting the enemy troops. I also consider the possible number of troops that a close enemy factory can send.

Defense: for each of my factories which can be captured, I calculate how many troops I need to keep for the next turns. If this isn’t enough, I search for one of my factories able to send troops who will arrive before it’s too late.
I have now the real number of troops I can use for other tasks :

Increase: If a factory doesn’t have any neutral to capture, it will go for an increase if the factory is considered as safe (if it has several available troops for each turn) and if my nearby factories are also safe. By doing so I won’t increase a factory which can be easily retaken by the enemy.

Move: For each of my factories I target the nearest enemy or neutral factory with a prod > 0. For the neutrals I send just the needed number, for enemies I send all my remaining troops and I repeat until I have no troop left. If I can find a factory between my factory and the targeted factory, I go first to this middle factory. I’m also careful about the neutral factories which are at equal distance between me and the enemy because I don’t want to kill all the neutral troops for the enemy !

Bomb: I target the closest factories with the best production that the enemy has or is going to capture.

As I coded the INC very late they weren’t very well implemented because my AI lost a lot of aggressiveness and because I suffered from not having a bomb prediction. In fact the code I submitted after the contest without any increase seems even a bit better !

2 Likes