Twee weken geleden schreven we over De 100-million-row-challenge. Deelnemers van deze wedstrijd hadden als doel om zo snel mogelijk 100 miljoen rijen in (puur) PHP te verwerken. Inmiddels is de competitie afgerond, en zijn de winnaars bekend.
Dus, hoe snel kan je 100 miljoen (CSV-)rijen verwerken in PHP?
Een lange tijd leek het erop dat het antwoord op zo'n 2,5 seconden zou gaan uitkomen, maar na een dag of negen gezweefd te hebben rond dat getal, sprong één van de deelnemers opeens naar ~2,05 seconden. Dit was een onverwachte ontwikkeling, want de consensus op het Discord-kanaal was dat we eigenlijk al tegen de limiet zaten. Maar toch duurde het niet lang voordat ook een aantal andere deelnemers deze tijd begon te benaderen.
Uiteindelijk was dit de eindstand:

Performance van PHP
Honderd miljoen rijen in zo'n twee seconden tijd. Ik zou kunnen zeggen dat het me verbaast: de ongelooflijke performance die ze uit PHP, een taal die niet bepaald bekend staat om zijn snelheid, weten te halen. Maar eigenlijk verbaast het me niet.
Want dit soort prestaties zie je wel vaker terug, wanneer een groepje slimme mensen zich op een probleem
stort. Bij de oorspronkelijke One Billion Row Challenge, waar deze challenge op was gebaseerd, werden
bijvoorbeeld ook ongelooflijke tijden gehaald. Maar, zelfs in het algemeen, is dit een terugkerend
fenomeen. Er zijn zat voorbeelden te noemen: van ongelooflijke snelheden in HFT en oude games met graphics
die hun tijd ver vooruit waren tot het trainen van moderne LLM's met biljoenen parameters.

Voorbeelden van knappe prestaties door de jaren heen.
Waar slimme mensen zich verzamelen rondom een probleem, gebeuren vaak bijzondere dingen. En dat is dus wederom bij zo'n competitie niet anders. Over competities gesproken...
De volgende is alweer begonnen
Alhoewel ik het Discord-kanaal en de ontwikkelingen van deze challenge in de gaten hield, was ik zelf, na het behalen van een tijd waar ik tevreden mee was, uit zelfbescherming (lees hierover in ons vorige artikel) vrij snel gestopt met deelnemen. Maar ondertussen was de CodinGame Winter Challenge 2026 ook alweer gestart, en kon ik het toch niet laten om ook daar nog een leuk botje voor te schrijven.

Snakebyte
Twee tot drie keer per jaar organiseert CodinGame een challenge. Het thema van de challenge is iedere keer anders. Zo was er vorig jaar zomer een soort watergevecht, waarbij je agents, bewapend met waterkanonnen, zoveel mogelijk terrein en punten moest laten veroveren, en ging de lentechallenge over een klein bordspel met dobbelstenen op een 3x3-veld.
Dit jaar staat de challenge in het teken van SNAKEBYTE, een strategisch slangenspel waarin je een robot-slang aanstuurt die "powernodes" moet opeten en daarbij om obstakels en tegenstanders heen moet navigeren. Je bekijkt het speelveld vanaf de zijkant, en dat betekent dat er ook een hoogte-component is, waarbij je slang te maken heeft met zwaartekracht en op zijn eigen stukken/staart moet steunen - dit voegt dus een extra dimensie toe aan de pathfinding-logica. Voor iedere powernode die je slang opeet, wordt je slang één element langer, en kun je dus bijvoorbeeld hogere powernodes bereiken.

Dit is hoe het spel eruitziet. In deze match besturen beide spelers vier slangen. De gele 'powernodes' moeten worden opgegeten door je slangen, waardoor ze groeien. Wie op het einde (zodra alle powernodes op zijn, of zodra de tijd voorbij is) de meeste punten heeft, wint de wedstrijd.
Diepte
Het lijkt op het eerste gezicht een simpel spelletje. Maar het bijzondere aan zo'n challenge is hoe ongelooflijk je de diepte ermee in kunt gaan.
Het begint eigenlijk al met de navigatie. Omdat er een hoogte-/zwaartekrachtcomponent is, betekent het dat je pathfinding er rekening mee moet houden dat je je doel kunt bereiken, dus dat een bepaald deel van je slang nog in contact is met de grond. Om een gekozen plek te bereiken moet je route dus ook rekening houden met de positionering van de rest van je slang. Je kunt in theorie zelfs andere slangen gebruiken als platform om hogere hoogtes te bereiken. En omdat die posities voortdurend wijzigen, moet je constant je routeplanning bijwerken.
Dan wil je het liefst ook nog een slimme keuze maken tussen de aanwezige powernodes. Je kunt degene kiezen die het meest dichtbij is (en bereikbaar). Maar, je hebt vaak meerdere slangen, dus dat valt misschien slimmer te plannen. En dan is er ook nog je tegenstander - misschien kan je een route berekenen waardoor je zijn powernodes kunt afpakken.
Over je tegenstander gesproken: als het hoofd van je slang ergens tegenaan botst, verliest hij één deel (tenzij je slang nog maar een lengte van drie heeft, dan verdwijnt hij helemaal). Je kunt dus gaan nadenken over defensieve of offensieve manouevres. De mogelijkheden die je hebt verschillen weer per "map", de hoeveelheid slangen die je hebt, de lengte ervan, etc.

Hoe langer je over deze challenge nadenkt, hoe meer factoren er mee blijken te spelen. En over nadenken gesproken: je code krijgt steeds vijftig milliseconden de tijd om dit te doen.
Performance
Het spel is verdeeld in beurten. Iedere beurt mag je iedere slang een commando geven, namelijk de richting waarin de slang probeert te bewegen. Je code heeft per beurt 50 milliseconden rekentijd hiervoor beschikbaar. Er komt dus een optimalisatieoverweging bij kijken.
Een van de eerste dingen die ik deed, nadat ik mijn navigatie-algoritmes aan de praat had gekregen, was code toevoegen waarmee ik kon zien hoeveel denktijd ik benutte per beurt. En dat was wel verontrustend: ik zat vrijwel direct al op dertig tot tachtig procent van mijn budget! Maar ja, PHP staat er natuurlijk met een reden om bekend dat het niet zo'n snelle taal is. En ik was ook wel aardig wat debug-info aan het printen - misschien had dat nog een beetje impact?
Later kwam ik erachter dat in de CodinGame game-interface je gebruikte tijd per beurt ook getoond werd. Alleen daar werd steeds getoond dat mijn code minder dan een milliseconde nodig had.

Mijn match tegen monkey.
Op deze afbeelding zie je links bovenaan, onder mijn naam, de 'denktijd' staan. Maar die stond dus op 0ms. Terwijl mijn code beweerde dat ik hier 17ms had gespendeerd. Zo'n groot verschil zou natuurlijk nooit kunnen kloppen. Ik vond het al gek, omdat mijn lokale unittests vrijwel onmeetbaar snel waren. Dus ik ging toch maar zoeken: en toen kwam ik erachter dat ik een foutje had gemaakt.
Ik begon namelijk met het meten van de tijd voordat de beurt 'officieel' begon. Je begint met het vragen aan de CodinGame-server om de informatie over wat er gebeurd is in de vorige beurt. Je stelt deze vraag aan hun server, en daarna moet je wachten tot de server reageert. Maar ik had op dat moment mijn 'stopwatch' dus al aangezet! Het is dus alsof de tandarts je factureert voor de tijd in de stoel, maar ook het uur dat je vooraf in de wachtkamer hebt gewacht!

Dit doet mijn eigen tandarts niet hoor, die is juist uitzonderlijk lief!
Verslavend
Net als met de 100-million-row-challenge, bevatten dit soort 'puzzels' veel interessante en verslavende elementen. Iedere wedstrijd speelt zich weer af op een 'unieke map', en iedere map brengt zijn eigen uitdagingen met zich mee. Er zit heel veel variatie in: je kunt soms een heel kleine, bijna claustrofobische, map krijgen, waarbij je heel goed om je tegenstander heen moet kunnen navigeren, en soms krijg je een hele grote map, waar de lange termijn van je geplande route opeens heel belangrijk wordt. Op sommige maps hebben je slangen alle vrijheid, maar er zitten er ook tussen waarbij (alle of enkele van) je slangen 'gevangen' zitten in afgesloten ruimtes met een tegenstander.
Daarnaast is er een league-mechanic, waarbij je in hogere leagues komt (brons, zilver, goud, etc.), naarmate je hoger komt in de rankings. In eerdere challenges was er soms ook een wood-league, een soort 'simpelere' versie van het spel, waarbij later in brons extra spelelementen of regels erbij kwamen. Bij deze competitie was dat niet het geval, want we begonnen meteen in brons. Alhoewel de hogere leagues dus geen nieuwe spelelementen introduceren, zijn er over het algemeen wel grotere maps. Dit maakt de pathfinding namelijk een stuk uitdagender.
In je ontwikkelomgeving is het mogelijk om willekeurige maps te genereren, waarbij je je eigen tegenstanders (uit het leaderboard) kunt kiezen. Maar je hebt ook de mogelijkheid om daar je gespeelde matches opnieuw in te laden. Je kunt dus bijvoorbeeld je verloren matches inladen, en code proberen te schrijven waarmee je wel zou winnen. Het is heel leuk en verslavend om steeds weer nieuwe maps en matches te analyseren, en je bot te verbeteren op basis van de steeds weer nieuwe situaties en uitdagingen, en maar te blijven schaven aan je oplossing. Er is altijd wel weer een nieuw idee om uit te proberen.

Yep, daar gaan we weer.
Conclusie
Net als met de vorige competitie blijven de ideeën natuurlijk binnenstromen. Het is eindeloos verslavend, en daarom voor mij belangrijk om een moment te kiezen waarop ik stop. Ik had ongeveer plek 240 bereikt in de silver league op de dag dat die league opende, en merkte dat ik plannen en strategieën begon te bedenken die een vrijwel volledige rebuild vereisten. Het was dus een heel goed punt om daar te stoppen, om te voorkomen dat het mijn hele weekend zou gaan consumeren.
Vanaf dat moment gaan de andere bots je snel weer inhalen, want die gaan nog wel door met het verbeteren, en zakt je positie vrij snel. Toch ben ik erg tevreden met wat ik heb bereikt: ik had een aantal ideeën die ik heb kunnen uitproberen, heb dingen geleerd, plezier gehad, en een lijst met notities gemaakt met verbeteringen van mijn (algemene) aanpak voor de volgende challenge. Ik heb er nu al zin in!