Bot programming tutorialsMighty Professional
Build a Legend Bot · Measurement

Learning from the Leaderboard

From Gold up, the bots above yours know something yours doesn't, and after most contests several of their authors write it down. This page finds those write-ups, turns them into changes you can test, replays the games you played to see what a stronger bot does differently, and checks a change against the players you need to beat before you submit it.

Time~35 minLevelBeginnerPrereqsMeasuring Bot Strength for win rates with error bars and paired seeds.StackC++20 & Rust · browser demos
◂ Build a Legend BotPhase 4 · MeasurementNext · When to Use Machine Learning ▸
What you'll be able to do by the end

01Where the write-ups are

After most contests, several of the players above you write down what their bot did, what they tried and what failed. A write-up like that is a , and on CodinGame most of them start in the same place.

Each contest gets a forum thread, usually titled "<contest> - Feedback & Strategies", where players post their write-ups or links to them. On October 30, 2026 the Summer Challenge 2026 thread (the Back Track King contest) held 15 posts,[1] the Spring Challenge 2026 thread (Troll Farm) 35,[2] and the Spring Challenge 2022 thread 71.[3] Longer write-ups often live elsewhere and are linked from the thread: Pduhard's 3rd-place Back Track King post-mortem is a GitHub repository that holds only a README,[4] and JOiy's is a gist posted by tcordel.[5]

Two more places cover older and permanent games. For contests from 2014 to 2016, the CodinGame blog published the winners' strategies; the blog returned errors on October 30, 2026, and the Internet Archive keeps copies of the posts. And every permanent game has a discussion thread of its own, open for years: Mad Pod Racing's had passed 1,000 posts by October 2026.[6]

Write-ups change after they're posted. JOiy's gist was created on September 21, 2026, the day the contest ended, and last edited on October 10.[5] Note the date you read each one, next to its link.

02Turn a write-up into tests

A post-mortem describes one bot, built and measured under its author's conditions, plus the author's guess at what made it strong. Read every claim as a hypothesis about your bot, and turn it into one change with one test.

Each claim has three parts worth pulling apart: what the bot did, what the author measured and against what, and what they believe made the difference. The first is usually solid. The second tells you how far to trust the third.

Worked example

Pduhard, 3rd in Back Track King, put a small search on top of his network: "It builds a few candidate plans for the turn, scores them with the value head two turns ahead, and keeps the best one it had time to look at. Against the same network without it, that is a 0.839 win rate."[4]

What he did: a two-turn search over a few candidate plans. What he measured: local games against his own network without the search.

RamzaBeoulve, 4th, explains how that kind of measurement can mislead: "Locally, search versus vanilla looked like a +20 point improvement, but the first ladder results suggested otherwise: my vanilla model was actually better." His search was predicting its own network as the opponent.[1] The test for your bot: add the search, then measure it against bots other than its own network, and live.

Write-ups also disagree. Three Back Track King write-ups describe three different rewards for training: Indrill, 1st, used "terminal + delta_delta_score" with a score "assuming ink-all, every turn";[1] Pduhard used "the income gap of the turn", plus 30 to the winner and minus 30 to the loser at the end;[4] and JOiy, 13th, wrote that "The reward that produced every good bot is terminal-only".[5] When strong players disagree, only a test on your own bot can decide.

Claim (player, place in Back Track King)Condition to checkTest in your bot
"Half the games are against the network itself; the other half against old versions of it" (Pduhard, 3rd)[4]His trainer plays 512 games at onceAdd past versions to the training opponents; measure against fixed bots, never only the latest version
The network first copies his best rule bot, then trains (RamzaBeoulve, 4th)[1]A graph network, trained further by reinforcement learning after the copyStart your network as a copy of your own rule bot; compare against a random start
Promotion tested on maps never used in training (JOiy, 13th)[5]"Every submission after this change landed above the previous best baseline"Split your seeds into training and test ranges
"The average of hundreds of checkpoints is reliably stronger than any single one of them, by 10 to 15 points" (Pduhard)[4]Points of what, against whom: not statedAverage saved weights; test the average against the best single checkpoint

A common question after reading the 3rd- and 4th-place write-ups is what to take from them for your own bot. Take one claim at a time, make the smallest change that tests it, and measure that change against fixed opponents on seeds you haven't tuned on (Measuring Bot Strength), then live, as section 06 shows. A change that only beats your own previous version hasn't shown anything yet.

From the author's lab

After the Back Track King contest the lab rebuilt its bot from the published write-ups and credited each idea to its source: self-play against a pool of past versions (Pduhard), averaged weights (Pduhard and putibuzu; the lab used a running average), a network that starts as a copy of the lab's own rule bot (RamzaBeoulve), test maps kept out of training (JOiy), route features and compressed weights (Indrill), 5-bit weights (putibuzu).[1][4][5] It adopted them together, so it can't say which ones carried the gain.[7]

The first network from the rebuild beat the lab's own contest bot in 500 of 512 games on 256 maps it had never played (October 14, 2026). The author reports it reached Legend on the permanent ladder after the contest.[7]

03Replay the games you played

Every ranked game your bot plays against the players above it is logged, and CodinGame's API serves the logs. They're free data about exactly the bots you need to beat.

A game's log, a battle log, holds the game's options, such as the map seed, then every turn's referee summary and the commands each player printed. It doesn't hold the input each bot received, and ranked games run without debug output, so the log holds no bot's stderr, yours included. Your own test games keep your stderr. You can always read the games you played in.[8]

game 610230745
options: seed=12
agents:
  0 Alice (me, agent_id -1)
  1 Boss 3 (agent_id 1234, rank 56)
frame 1 agent 0
  stdout: MOVE 1 2
frame 2 agent 1
  stdout: MOVE 3 4
…
result
  scores: 0=-1 1=1

The shape of a text log, abridged from the example in CodinGame's API documentation.[8] To see the positions behind those commands, rebuild the game: generate the start from the logged options with your simulator from Referees and Simulators, apply both players' logged commands turn by turn, and check that the game ends on the logged scores. A replay like that gives you every position of the game in your own data structures, ready for any statistic. A replay that ends on different scores means your simulator differs from the referee, or the game drew randomness the log doesn't show; set it aside and find out which.

Worked example

A Mad Pod Racing log gives the map in its options and each pod's target and thrust for every turn. Your simulator builds the same track, flies both teams' logged commands, and must end the race on the same turn with the same finishing order.

Once it does, you can ask the log questions it never answered directly: how far the opponent's blocker waits from each checkpoint, or how many turns your runner lost in each collision.

From the author's lab

The lab replayed 1,223 live Back Track King games through its bit-exact simulator by October 25, 2026: the ranking battles of seven submissions plus a screen of test games, each replay checked against the logged scores. In Mad Pod Racing, all 226 of its Legend ranking battles replayed exactly, 58,167 turns.[7]

04Fingerprint a style

A win rate tells you who is stronger. It doesn't tell you how. A fingerprint does: a few statistics of a bot's play, averaged over many replayed games and split by stage of the game, that tell its style apart from yours.

Pick quantities that capture decisions in this game: where the bot builds, how close it plays to the opponent, when it attacks. Compute them on every turn of the replayed games, average them by stage, and draw them next to the same numbers for your own bot. Splitting by stage matters because many styles look the same in the opening and part ways later.

Worked example

In a light-cycle game (two bikes that leave walls behind them, as in Tron), take the distance between the two heads, counted in cells along rows and columns, after each of a bot's moves. Over its first six moves in 50 games, calm bot A's average is 4.7 cells and aggressive bot B's is 4.55: no difference.

Over moves 7 to 12, A averages 6.96 and B 4.29. B is closing in, and the fingerprint shows it.

A bot's fingerprint

The games run when the widget scrolls into view.

Each bot here has a hidden aggression: the chance that on a given turn it heads for the opponent instead of taking its best territory move. Calm bot A's is 0.1 and aggressive bot B's is 0.9. The charts on the right are their fingerprints, averaged over 50 seeded light-cycle games each against the flood-fill bot and split by the bot's own move number: how far its head stays from the opponent's, what share of the board it holds, and how many sides of its head, besides the one it came from, touch a wall or a trail. Press Play to watch one game of the unknown bot, and drag its aggression: its purple lines move between A's and B's, and the readout names the closer fingerprint. In this demo the unknown bot matches A up to 0.3 and B from 0.4, where the two distances are nearly equal. The first six moves look alike for every bot; over moves 7 to 18, B stays closer to its opponent, holds less of the board, and runs along walls and trails more often.

The aggressive bot is also the weaker one here: against the flood-fill bot it scores 27 points from 50 games where the calm bot scores 46. A fingerprint tells you what a bot does. Whether that's why it wins is a separate question, and the answer comes from a test: give your bot the trait and measure it.

From the author's lab

In Back Track King, players spend paint each turn laying rail track between towns and score a point at the end of every turn for each of their tracks on an active connection between towns. Each turn they can also disrupt one region; a region disrupted four times is inked out and its tracks are washed away, and regions with a town can't be disrupted.[9]

Replaying its live games against Indrill, the contest winner, the lab compared his bot's fingerprint with its own. In turns 0 to 4, 63% of Indrill's tracks were in town regions, out of reach of disruption, against 52% of the lab's; only 5% were on an active connection, against 17%. In turns 0 to 9 Indrill's disruptions hit regions holding the lab's scoring tracks 28.5% of the time; the lab's hit Indrill's 10.2% of the time. By turns 20 to 29 Indrill had about 8.5 more scoring track-and-connection pairs per turn, which is where its extra income came from.[7]

05Find where games are decided

Games against a stronger player are often decided by one stretch of play. Splitting your logged games by one early event can show which.

Pick an event the replay shows plainly: who connected first, who passed the first checkpoint, who landed the first hit. Split the games by which side got it, and count wins on each side. When one side of the split almost never wins, the event is close to deciding the game, and your next change should aim at it.

From the author's lab

In Spring Challenge 2022, each player's three heroes defend a base that is destroyed after 3 points of damage, taken whenever a monster reaches it, and spells let a player push monsters toward the opponent's base and shield them.[10] On the game's permanent ladder in October 2026, the lab analyzed the logs of 132 live games between its search bot (rules plus a 12-turn look-ahead, no network) and five of the top Gold players. In the games played by its latest version, when it damaged the opponent's base first it won 19 of 31; when the opponent hit first, it won 0 of 48.[7]

That pointed every following change at the race to the first hit. None of those changes passed its panel (section 06), and the search bot stalled high in Gold, below the boss. A self-play network trained later was promoted to Legend on October 30, 2026, after finishing 8.03 points above the Gold boss.[7]

A split like this shows a correlation. The first hit may mark a stronger opponent as much as it causes the loss, and a split you chose after looking at the games needs fresh games to confirm it. Treat it as a pointer to where a change might help, then test the change.

06Test against the players above you

Local tests pit your candidate against bots you have. The ladder ranks it against bots you don't. Before you submit, play both versions against the players you need to beat, on the same seeds, in test games.

The API lets you run test games against the players and bosses of your league room and choose the game options, such as the seed; the usual limits are 10 test games a minute and 600 a day, and they depend on the challenge.[8] That's enough for a panel: a fixed list of entries, each one opponent and one seed, which the incumbent and the candidate both play once. A 40-entry panel costs 80 test games.

Pairing the games makes the panel sharper. Some entries are hard for both versions, some easy for both; comparing each entry with itself cancels that, and leaves the difference between the versions. Write the panel down before playing a single game: the entries, what counts as better on an entry, the test and the pass mark. That's pre-registration, and it stops the result from steering the test.

The test that fits a paired panel is the sign test. Count the entries where the candidate did better and the ones where it did worse, and drop the ties, which say nothing either way. If the candidate were no better than the incumbent, every decided entry would be a fair coin flip.

Worked example

A panel of 8 entries, scored by final margin: the candidate is better on 5, worse on 2, and one entry is an exact tie. Drop the tie and 7 entries are left.

Of the 27 = 128 equally likely ways 7 coin flips can land, 21 give exactly 5 heads, 7 give 6 and 1 gives 7: 29 in all. So p = 29 / 128 ≈ 0.23. A no-better candidate would look this good almost one time in four, so this panel shows nothing yet.

The general formula adds up the same coin-flip chances for any panel. It answers the one question a panel asks: could chance alone have produced a result this good?

p = Σk=bn C(n, k) / 2n,   with b better, w worse, n = b + w

In words: the p-value is the chance that n fair coin flips give at least b heads. A small p says a candidate that's no better would rarely come out ahead this often; the usual pass mark is p below 0.05.

A paired live panel

The panels run when the widget scrolls into view.

The games here come from a model set by the sliders: each entry (a seed and an opponent) has its own difficulty, the candidate wins the given number of percentage points more often on average, and the per-game noise sets how much one game varies on top of the entry's difficulty. Press Play to draw a new panel four times a second; each tile shows one entry's two results, outlined where they differ. The paired sign test uses only those differing entries and asks how often the candidate came out ahead; the unpaired test compares the two win counts as if the games were unrelated. At the defaults (+15 points, noise 0.5, 40 entries) the sign test finds the gain in 51% of 2,000 simulated panels and the unpaired test in 33%. Set the noise to 0 and the gap widens to 73% against 26%. At noise 2, when single games are close to coin flips, pairing stops helping: 35% against 38%.

A sign test needs lopsided panels. With 40 decided entries the candidate must be better on at least 26 to pass at 0.05, with 60 on at least 37, and a panel of 4 decided entries can never pass, because even a clean sweep has p = 1/16. With no real gain at all, the widget's sign test passes about 2% of 40-entry panels, below the 5% the pass mark allows, because a count of coin flips can't land exactly on 0.05.

From the author's lab

In Game of Drones the lab registered a panel at 10:20 on October 30, 2026, before playing any of it: 60 entries, 20 seeds against the Gold boss, 20 one-on-one games against 20 different top-30 Gold players, and 20 games with three or four players. On each entry a candidate counted as better if its final margin, or its place, beat the live bot's; a candidate would be submitted only if the one-sided sign test gave p < 0.05.[7]

A candidate with an opponent model learned from 501 logged games came out better on 39 entries and worse on 21, p = 0.014. Submitted to the game's permanent ladder, it moved up in Gold but stayed below the boss. In Spring Challenge 2022, four rounds of changes to the search bot failed their live tests and were never submitted.[7]

07A sign test in code

These complete programs score a panel entry by entry and compute the exact one-sided sign test. The p-value is summed in logarithms because 2−n underflows for large panels; a second, whole-number version counts the coin-flip sequences exactly and checks the first on every panel of up to 60 decided entries. The tests reproduce the worked example (29/128), the Game of Drones panel (39 better and 21 worse give 0.0137) and the edge cases, and print how many better entries each panel size needs to pass.

An exact sign test for a paired panel, complete programs
// Score a paired live panel and test it with an exact one-sided sign test.
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <vector>

// One entry of a panel: one opponent and one seed, played once by each version of the bot.
// A result is whatever the panel's rule compares: a win (1) or loss (0), a score margin, a place.
struct Entry { double incumbentResult, candidateResult; };
struct Tally { int better = 0, worse = 0, ties = 0; };

Tally tally(const std::vector<Entry>& panel) {
    Tally counts;
    for (const Entry& entry : panel) {
        if (entry.candidateResult > entry.incumbentResult) ++counts.better;
        else if (entry.candidateResult < entry.incumbentResult) ++counts.worse;
        else ++counts.ties;   // a tie says nothing about which version is better
    }
    return counts;
}

// The chance of at least `better` heads in better + worse fair coin flips: how often a candidate that
// is no better than the incumbent would come out ahead this often.
double signTestPValue(int better, int worse) {
    const int decided = better + worse;
    double logProbability = -decided * std::log(2.0);   // P(0 heads) = 1 / 2^decided, kept as a logarithm
    double total = 0;                                    // because 2^-decided underflows on big panels
    for (int heads = 0; heads <= decided; ++heads) {
        if (heads >= better) total += std::exp(logProbability);
        if (heads < decided)   // P(heads + 1) = P(heads) * (decided - heads) / (heads + 1)
            logProbability += std::log(double(decided - heads)) - std::log(double(heads + 1));
    }
    return std::min(1.0, total);
}

// The same tail counted exactly in whole numbers, for checking: fine up to 62 decided entries.
double exactPValue(int better, int worse) {
    const int decided = better + worse;
    std::uint64_t binomial = 1, atLeast = 0;   // binomial = C(decided, heads)
    for (int heads = 0; heads <= decided; ++heads) {
        if (heads >= better) atLeast += binomial;
        binomial = binomial * std::uint64_t(decided - heads) / std::uint64_t(heads + 1);
    }
    return double(atLeast) / std::ldexp(1.0, decided);
}

// The fewest wins out of `decided` entries that pass at the given level.
int winsNeeded(int decided, double level) {
    for (int better = 0; better <= decided; ++better)
        if (signTestPValue(better, decided - better) < level) return better;
    return -1;   // too few entries to pass at all
}

int main() {
    // A panel of 8 entries scored by margin: better on 5, worse on 2, one exact tie.
    const std::vector<Entry> panel{{-82, -35}, {12, 40}, {-150, -151}, {30, 30}, {5, 61}, {-40, -12}, {88, 20}, {-9, 3}};
    const Tally counts = tally(panel);
    assert(counts.better == 5 && counts.worse == 2 && counts.ties == 1);
    assert(std::abs(signTestPValue(counts.better, counts.worse) - 29.0 / 128.0) < 1e-12);   // 29 of 2^7 sequences

    // The two Game of Drones panels from the text.
    const double drones60 = signTestPValue(39, 21), drones40 = signTestPValue(29, 11);
    assert(std::abs(drones60 - 0.013670066934) < 1e-11);
    assert(std::abs(drones40 - 0.003213288048) < 1e-11);
    std::printf("39 better, 21 worse: p = %.4f\n29 better, 11 worse: p = %.4f\n", drones60, drones40);

    // Edge cases: no decided entries, a clean sweep, a split, a loss on every entry.
    assert(signTestPValue(0, 0) == 1.0);
    assert(std::abs(signTestPValue(5, 0) - 1.0 / 32) < 1e-15);
    assert(std::abs(signTestPValue(3, 3) - 42.0 / 64) < 1e-12);
    assert(std::abs(signTestPValue(0, 5) - 1.0) < 1e-12);

    // The logarithm form agrees with exact counting on every panel of up to 60 decided entries.
    double largestGap = 0;
    for (int decided = 0; decided <= 60; ++decided)
        for (int better = 0; better <= decided; ++better)
            largestGap = std::max(largestGap, std::abs(signTestPValue(better, decided - better) - exactPValue(better, decided - better)));
    assert(largestGap < 1e-12);

    // How lopsided a panel must be to pass at p < .05.
    assert(winsNeeded(4, 0.05) == -1 && winsNeeded(5, 0.05) == 5);   // 4 entries can never pass
    for (int decided : {10, 20, 40, 60})
        std::printf("%2d decided entries: the candidate must be better on at least %d\n", decided, winsNeeded(decided, 0.05));
}
// Score a paired live panel and test it with an exact one-sided sign test.

// One entry of a panel: one opponent and one seed, played once by each version of the bot.
// A result is whatever the panel's rule compares: a win (1) or loss (0), a score margin, a place.
struct Entry { incumbent_result: f64, candidate_result: f64 }
#[derive(Default)]
struct Tally { better: u32, worse: u32, ties: u32 }

fn tally(panel: &[Entry]) -> Tally {
    let mut counts = Tally::default();
    for entry in panel {
        if entry.candidate_result > entry.incumbent_result { counts.better += 1; }
        else if entry.candidate_result < entry.incumbent_result { counts.worse += 1; }
        else { counts.ties += 1; } // a tie says nothing about which version is better
    }
    counts
}

// The chance of at least `better` heads in better + worse fair coin flips: how often a candidate that
// is no better than the incumbent would come out ahead this often.
fn sign_test_p_value(better: u32, worse: u32) -> f64 {
    let decided = better + worse;
    let mut log_probability = -(decided as f64) * 2f64.ln(); // P(0 heads) = 1 / 2^decided, kept as a logarithm
    let mut total = 0.0; // because 2^-decided underflows on big panels
    for heads in 0..=decided {
        if heads >= better { total += log_probability.exp(); }
        if heads < decided { // P(heads + 1) = P(heads) * (decided - heads) / (heads + 1)
            log_probability += ((decided - heads) as f64).ln() - ((heads + 1) as f64).ln();
        }
    }
    total.min(1.0)
}

// The same tail counted exactly in whole numbers, for checking: fine up to 62 decided entries.
fn exact_p_value(better: u32, worse: u32) -> f64 {
    let decided = better + worse;
    let (mut binomial, mut at_least) = (1u64, 0u64); // binomial = C(decided, heads)
    for heads in 0..=decided {
        if heads >= better { at_least += binomial; }
        binomial = binomial * (decided - heads) as u64 / (heads + 1) as u64;
    }
    at_least as f64 / 2f64.powi(decided as i32)
}

// The fewest wins out of `decided` entries that pass at the given level.
fn wins_needed(decided: u32, level: f64) -> Option<u32> {
    (0..=decided).find(|&better| sign_test_p_value(better, decided - better) < level) // None: too few entries to pass at all
}

fn main() {
    // A panel of 8 entries scored by margin: better on 5, worse on 2, one exact tie.
    let panel: Vec<Entry> = [(-82.0, -35.0), (12.0, 40.0), (-150.0, -151.0), (30.0, 30.0), (5.0, 61.0), (-40.0, -12.0), (88.0, 20.0), (-9.0, 3.0)]
        .iter().map(|&(incumbent_result, candidate_result)| Entry { incumbent_result, candidate_result }).collect();
    let counts = tally(&panel);
    assert!(counts.better == 5 && counts.worse == 2 && counts.ties == 1);
    assert!((sign_test_p_value(counts.better, counts.worse) - 29.0 / 128.0).abs() < 1e-12); // 29 of 2^7 sequences

    // The two Game of Drones panels from the text.
    let (drones60, drones40) = (sign_test_p_value(39, 21), sign_test_p_value(29, 11));
    assert!((drones60 - 0.013670066934).abs() < 1e-11);
    assert!((drones40 - 0.003213288048).abs() < 1e-11);
    println!("39 better, 21 worse: p = {:.4}\n29 better, 11 worse: p = {:.4}", drones60, drones40);

    // Edge cases: no decided entries, a clean sweep, a split, a loss on every entry.
    assert!(sign_test_p_value(0, 0) == 1.0);
    assert!((sign_test_p_value(5, 0) - 1.0 / 32.0).abs() < 1e-15);
    assert!((sign_test_p_value(3, 3) - 42.0 / 64.0).abs() < 1e-12);
    assert!((sign_test_p_value(0, 5) - 1.0).abs() < 1e-12);

    // The logarithm form agrees with exact counting on every panel of up to 60 decided entries.
    let mut largest_gap: f64 = 0.0;
    for decided in 0..=60 {
        for better in 0..=decided {
            largest_gap = largest_gap.max((sign_test_p_value(better, decided - better) - exact_p_value(better, decided - better)).abs());
        }
    }
    assert!(largest_gap < 1e-12);

    // How lopsided a panel must be to pass at p < .05.
    assert!(wins_needed(4, 0.05).is_none() && wins_needed(5, 0.05) == Some(5)); // 4 entries can never pass
    for decided in [10, 20, 40, 60] {
        println!("{:2} decided entries: the candidate must be better on at least {}", decided, wins_needed(decided, 0.05).unwrap());
    }
}

Both programs print the same lines: p = 0.0137 and 0.0032 for the two Game of Drones panels, then the wins needed out of 10, 20, 40 and 60 decided entries (9, 15, 26 and 37).

08Borrow ideas, write your own code

A post-mortem gives you ideas. Every line of your bot should still be yours.

  1. Read the prose. Write-ups, diagrams and the occasional few lines of pseudocode are where the ideas are.
  2. Don't open another player's bot. No source, weights or training code, even when it's public. What you build stays your own work, and you never end up translating someone else's program.
  3. Credit each idea. Name the player by their CodinGame handle and link their write-up, wherever you describe the idea.
  4. Keep a provenance record. List the sources you read, what you took from each, and what you built, while you build it.

A common question about a bot rebuilt from post-mortems is whether it copied someone's code. Built by these rules, it can't have: the write-ups supply the ideas and you write the implementation. The provenance record is how you show it.

From the author's lab

The lab's provenance record for its Back Track King rebuild lists three sources: the contest's forum thread, JOiy's gist ("architecture sketch and two 3-line pseudocode snippets, no bot code") and Pduhard's repository ("only README.md (write-up + diagram), no bot code"). It credits each borrowed idea to its player and states: "No other player's bot source, weights or training code was downloaded, read or used." The only third-party code in the project was test equipment: the official referee, a command-line fork of it, and a match runner, none of them part of the submitted bot.[7]

09When nobody wrote it down

Some games have no recent write-ups. Game of Drones ran as a contest in 2014 and lives on as a permanent game; its forum thread is a general discussion, 12 posts long on October 30, 2026.[11]

A common question when a game has no post-mortem is where else to look. Start with the places in section 01, under any name the game had before; for Game of Drones, the 2014 CodinGame blog carries its top three's strategies, readable through the Internet Archive.[12][13] Then study the games themselves. The boss is always there to test against, and its games show up in your battles. The players above you show up there too: replay those games, fingerprint them, and fit a model of how they move.

Old write-ups still carry ideas. In Game of Drones, each player directs 3 to 11 drones. A player takes a zone by bringing more drones into it than its owner has there, keeps it until someone does the same, and scores a point for every zone it holds at the end of each of the 200 rounds.[14] god, 2nd in 2014, parked his spare drones in four-player games at the center of the three zones on one edge of the map, "the most isolated group of 3"; Gangrene, 3rd, made one zone at an end of the map his priority in three- and four-player games, to avoid being caught between two teams.[12][13]

From the author's lab

For Game of Drones the lab worked from its own logged games. It found the boss deterministic (the same code on the same seed scored the same), built a replica of it that agreed on only 22.6% of one-on-one turns, and dropped it. Fingerprints showed that winning players kept about 36% of their drone-turns inside their own zones, against 23% for the lab's bot. A model of opponent moves fitted to 501 logged games drove the candidate that passed the panel in section 06.[7]

Then the 2014 edge-territory idea, turned into one change for three- and four-player games, passed a 40-entry panel, better on 29 and worse on 11 (p = 0.003). On October 30, 2026 that bot still finished below the Gold boss, so it wasn't promoted.[7]

10Where studying goes wrong

A write-up is evidence about someone else's bot

Every number in a post-mortem was measured on the author's bot, against the author's opponents, with the author's simulator. Re-measure it on yours before you build on it.

Reading only the winner. The 1st-place write-up describes one route to the top. In Back Track King the 3rd, 4th and 13th places described three more, with different rewards and searches.

Copying a number without its condition. A 0.839 win rate against your own network, 10 to 15 points of unstated units: keep the condition next to the number, or drop the number.

Mining replays with a simulator that isn't exact. Statistics from a replay that drifted from the real game describe a game nobody played. Check every replay's final scores against the log.

Choosing the test after seeing the results. Stopping a panel when it looks good, or picking the entries where the candidate shines, makes any change pass. Register the panel first.

Judging a style from a handful of games. A fingerprint is an average, and from a few games it mostly shows which way those games happened to go. The widget averages 50 games per bot.

11What's next

Several write-ups on this page describe networks trained by self-play. Before spending a week on one, When to Use Machine Learning asks what learning would actually change in your bot, what it costs, and how often it was the method that won.

12Sources

Forum counts and placements are as of October 30, 2026. Widget readouts are measured in the page on the stated seeds; the panel widget's games come from the model its caption describes.

  1. CodinGame forum, 2026. Summer Challenge 2026 - Feedback & Strategies, thread 208574. Indrill's 1st-place posts (#5 and #8): his reward, route features and compressed weights; putibuzu's post (#4): averaged weight snapshots and 5-bit weights; RamzaBeoulve's 4th-place write-up (#10): the start from a copy of his rule bot and the search that overrated itself locally.
  2. CodinGame forum, 2026. Spring Challenge 2026 (Troll Farm) - Feedback & Strategies, thread 208241. A contest thread of 35 posts, including delineate's 1st-place write-up.
  3. CodinGame forum, 2022. Spring Challenge 2022 - Feedbacks & Strategies, thread 195736. A contest thread of 71 posts, including bowwowforeach's 1st-place write-up.
  4. Pduhard, 2026. back-track-king-postmortem, README. 3rd in Back Track King: the plan search and its 0.839 win rate, the income-gap reward, half of the games against past versions, and checkpoint averaging.
  5. JOiy, 2026. JOiy's bot: Summer challenge 2026, GitHub gist posted by tcordel (created 2026-09-21, last edited 2026-10-10). The terminal-only reward and the held-out test maps.
  6. CodinGame forum. Mad Pod Racing - Puzzle Discussion, thread 1833, open since 2016. A permanent game's discussion thread.
  7. Author's lab records, 2026 (private repository). docs/provenance.md and docs/nn-training.md (the Back Track King rebuild and its credits); experiments/nn-final-20261014.json (500 of 512); experiments/live-submissions/summer-challenge-2026-back-track-king/opponents-20261025.json (1,223 replayed games and the Indrill fingerprint); games/madpodracing/README.md (226 Legend battles replayed); prompts/results/spring2022-legend-20261029.json (round 4, the first-hit split) and experiments/live-submissions/spring-challenge-2022/rankings.jsonl; prompts/results/gameofdrones-panel-r4.json and gameofdrones-legend-20261029.json, docs/postmortem-lessons.md and experiments/live-submissions/game-of-drones/rankings.jsonl (the Game of Drones panels and results).
  8. CodinGame, 2026. CodinGame API documentation (reference and guide, retrieved 2026-10-30). Game logs and their fields, no stderr in ranked battles, test games against league opponents with chosen game options, and the typical rate limits.
  9. CodinGame and CGjupoulton, 2026. Back Track King statement, referee repository at commit 23b2a02. Scoring per track on active connections, disruption, inking, and town regions that can't be disrupted.
  10. CodinGame, 2022. Spring Challenge 2022 statement, referee repository. Three heroes per player, a base destroyed by 3 points of damage, and the WIND, SHIELD and CONTROL spells.
  11. CodinGame forum. Game Of Drones multiplayer challenge discussion, thread 50 (12 posts on 2026-10-30).
  12. god, 2014. Game of Drones AI battle: god's strategy (2nd place), CodinGame blog, March 29, 2014 (Internet Archive copy). Spare drones parked, in four-player games, at the center of the most isolated group of three edge zones.
  13. Gangrene, 2014. Game of Drones AI battle: Gangrene's strategy (3rd place), CodinGame blog, March 29, 2014 (Internet Archive copy). A priority zone at an end of the map in three- and four-player games.
  14. CodinGame. Game of Drones, statement (retrieved 2026-10-30). 3 to 11 drones per player, zones held by numbers, a point per zone per round, 200 rounds.