Pogofish — l'expérience qui a entraîné l'humain
L'expérience devait entraîner une IA. C'est l'humain qu'elle a entraîné.
Cinq coups, le temps de sentir les règles.
Vous jouez les Blancs. Rouge répond par une règle fixe : il capture s'il peut, sinon il empile, et à défaut il joue le premier coup légal. Cinq de vos demi-coups suffisent ; après quoi l'article reprend son cours.
Pogo est entré dans ma vie par un ami qui en avait la boîte et m'en a expliqué les règles sur un coin de table un soir. Trois lignes, trois colonnes, douze pièces, ni dé ni carte cachée — les deux joueurs voient tout, comme aux échecs. Les règles s'apprennent en cinq minutes. Le jeu, lui, met plus longtemps à s'oublier.
Ce que je cherchais, ce mois-là, c'était une expérience d'apprentissage par renforcementRLApprentissage par renforcement — apprendre par récompenses et punitions, pas par étiquettes.. Le RL — la branche du machine learning où l'on apprend à un programme à jouer en le faisant jouer contre lui-même, des milliers ou des millions de fois, jusqu'à ce qu'une chose qui ressemble à un savoir émerge du bruit. C'est la méthode derrière les programmes qui ont battu les champions du monde au Go, aux échecs, à peu près à tout ce qu'on leur a posé sur la table — et c'est, notoirement, coûteux : les gros joueurs consomment de petits data centers pendant des semaines.
Je n'avais pas de petit data center. J'avais huit gigaoctets de RAM. Ce que je voulais, c'était un jeu assez petit pour que l'expérience tienne sur un laptop — entraîner, évaluer, et affronter le résultat, sur la même machine, dans un temps qui se compterait en jours plutôt qu'en mois.
Pogo, trois lignes sur trois colonnes, ressemblait beaucoup à ce jeu.
Je n'ai pas écrit la première ligne de code ce soir-là. J'ai ouvert un chat avec Claude — une IA générale que j'utilisais depuis plusieurs mois — pour planifier l'expérience.
Dans le chat, j'ai décrit ce que je voulais : un agent RL qui apprenne à jouer à Pogo en s'affrontant lui-même, sur mon laptop, en quelques jours. J'ai demandé à Claude comment structurer le projet.
Claude est revenu avec un plan en cinq phases. Phase un, un moteur de jeu en Python — un bac à sable où un agent RL peut interagir avec les règles. Phase deux, un solveur minimaxMinimaxUne recherche qui joue de façon optimale en supposant que l'adversaire fait de même. : un algorithme par force brute qui lit tous les coups possibles depuis la position courante, puis toutes les ripostes, puis les ripostes aux ripostes, jusqu'à la fin de la partie, et fait remonter les verdicts. Lancé une fois, en amont, il produit la carte complète de chaque position atteignable avec sa valeur exacte — la vérité terrain contre laquelle mesurer ce que le réseau apprendra ensuite. Phases trois et quatre, le RL lui-même : Q-learning tabulaire comme référence, puis deep RL par-dessus. Phase cinq, une page interactive où n'importe quel lecteur peut affronter le réseau entraîné.
Je ne connaissais pas le minimax avant cette conversation. Une fois la logique posée — résoudre d'abord, entraîner contre la solution — la chose tenait. Le plan faisait sens.
Restait une seule question pratique : est-ce que Pogo était assez petit pour que la phase de force brute se termine en un temps raisonnable sur un laptop de huit gigaoctets ?
Pogo, c'est trois sur trois, douze pièces, aucune capture — les pièces ne font que s'empiler. Combien de positions distinctes peut-il atteindre ? Avancez un nombre avant que la machine ne me donne le sien.
pas plus de ≈1 000 000. solveur facile — quelques heures, mono-thread.
L'estimation tenait. Un million de positions passées à la moulinette en un après-midi, ce n'était pas un obstacle — ça libérait le terrain pour la partie RL, plus intéressante. J'ai validé le plan et demandé à Claude d'écrire le moteur.
L'estimation allait s'avérer fausse d'un facteur cinquante.
Le moteur a tenu en deux jours. Claude l'a écrit ; j'ai lu chaque commit, posé des questions, lancé les fixtures de test livrées avec. Phase un, faite.
Le solveur minimax est venu ensuite. Claude l'a écrit en Python, en parallèle du moteur. Les deux outils standards de trente ans de recherche en arbres de jeu y étaient : l'élagage alpha-bêtaÉlagage alpha-bêtaUn raccourci qui saute les branches déjà prouvées moins bonnes qu'une précédente. (jeter une branche dès qu'on a prouvé qu'une autre est meilleure) et une table de transpositionTable de transpositionUne table de hachage des positions déjà évaluées, pour ne pas refaire le travail. (mémoriser la valeur de chaque position rencontrée pour ne pas la recalculer si on y revient par un autre chemin). Rien d'exotique. Sur le papier, le genre de solveur qui ratisse un million de positions entre midi et deux.
Je l'ai lancé, j'ai regardé les premières centaines de positions s'évaluer, et je suis parti faire autre chose. Je tenais Claude au courant. Jour un, table à huit cent mille entrées, croissance nominale. Jour deux, plus lente, plausible. Jour trois, la courbe ne ressemblait déjà plus à rien de connu. Jour cinq, elle montait presque à la verticale. Au matin du sixième, le système d'exploitation a tué le processus — plus de mémoire, trente-six gigaoctets résidents, quarante-neuf millions d'entrées dans la table, et pas une seule valeur remontée à la racine. Six jours, aucune réponse, aucun snapshot sur disque.
- d1 03:14:07inforecherche alpha-bêta prof=8 branchement≈12
- d1 11:40:22infoentrées TT : 412 008 taux : 38,4 %
- d2 07:12:55infoentrées TT : 8 344 207 taux : 41,1 %
- d3 14:03:18warncourbe de croissance n'est plus logarithmique
- d4 09:45:01infoentrées TT : 27 901 540 RAM : 22,4 Go
- d5 22:56:37warncroissance presque linéaire
- d6 08:11:19infoentrées TT : 49 000 000 RAM : 36,0 Go
- d6 08:11:21killtué (oom-killer) : pogofish-solver
J'ai eu de la chance, une fois. Le solveur tournait dans un bash à l'intérieur d'une session Claude Code, ce qui voulait dire que l'image mémoire du processus mort était encore accessible depuis la même session : à nous deux, Claude et moi avons sorti la table de valeurs de la RAM dans un fichier Pickle avant la fin de la session. Sur le papier, les six jours de calcul étaient sauvés.
Fort de cette récupération, j'ai demandé à Claude si on pouvait relancer — cette fois en dehors de la session, dans un terminal frais, avec plus de marge. Claude a dit oui.
Ce n'était pas vrai. Le second run est tombé de la même manière au sixième jour, sans Claude Code dessous pour récupérer la mémoire. Cette fois, rien à sauver. Douze jours de calcul, perdus. Un fichier de logs à peu près vide, un ventilateur silencieux depuis une semaine, et un diagnostic à faire.
Après le second échec, Claude et moi nous sommes installés devant le code source et avons compris ce qui s'était réellement passé.
La cause était petite. Pogo n'a pas de captures : les pièces ne quittent jamais le plateau, elles s'empilent. Une même position peut donc être atteinte non par une seule suite de coups, mais par plusieurs — y compris des suites qui la traversent en boucle. Deux joueurs prudents peuvent se renvoyer les mêmes trois pièces entre les mêmes trois cases indéfiniment. C'est aussi vrai pour un algorithme de recherche : rien dans un minimax classique ne l'empêche de faire mille fois le tour du même cycle, en enregistrant chaque tour comme un nouveau chemin.
Voilà ce qui avait avalé trente-six gigaoctets. Les quarante-neuf millions d'entrées que le solveur avait accumulées n'étaient pas quarante-neuf millions de positions distinctes — beaucoup étaient la même poignée de positions rangées sous des suites de coups différentes. La table de transposition ne pouvait rien : elle indexe les positions, pas les chemins, et rien ne disait à la route qui menait à une position stockée qu'elle-même tournait en rond.
Le correctif est connu. Quand la recherche rencontre une position qu'elle a déjà visitée plus haut dans la même ligne de jeu, on traite cette branche comme un nul et on rebrousse chemin. Deux lignes de code ajoutées, trois avec le commentaire. Claude les a écrites.
Pendant que nous étions dans le code, nous avons remarqué autre chose. La condition de défaite de Pogo — on perd quand plus aucune case du plateau n'a sa couleur au sommet, et donc quand on n'a plus de coup légal — laissait en théorie la place, à deux joueurs prudents, à une configuration stable et mutuellement inerte que personne n'avait envie de bouger. Dans une partie ordinaire ça n'arrive presque jamais. Pour une recherche qui essaie tout, ça arrivait sans cesse. Il fallait au jeu une manière plus nette de se terminer, non seulement pour l'algorithme mais pour que le jeu en soit un.
Douze jours de calcul. Deux lignes de code. Trois avec le commentaire.
La modification était petite. Une règle ajoutée : si la même position revient trois fois dans une partie, le joueur au trait perd. Une répétition une ou deux fois fait partie du jeu normal ; trois fois, c'est le signe que personne ne veut s'engager. Cela suffisait à donner au jeu une fin sans ambiguïté, même entre deux joueurs qui s'étaient tacitement mis d'accord pour ne rien faire.
Avec le bug du moteur de recherche corrigé et la règle ajoutée au niveau du jeu, Claude a relancé le solveur. Il a tourné cinquante-quatre minutes. Neuf millions huit cent mille positions distinctes vues, dont environ neuf cent soixante-quinze mille décisives — un camp ou l'autre y avait un gain forcé — et le reste, des nuls. Depuis la position de départ, les deux côtés jouant le meilleur coup possible, la partie est nulle.
Il faut bien, à la fin, que quelqu'un perde.
Phase deux du plan d'origine : faite. Mais le résultat arrive avec une note de bas de page. Pour garder la mémoire bornée, le solveur avait été plafonné à vingt demi-coups de recherche, et tout ce qui restait indécis à cette profondeur était classé nul. Pogo, on l'apprend par là, est plus profond que vingt demi-coups. Certains de ces « nuls » sont des positions dont la vraie valeur ne se règle qu'en regardant plus loin. La force brute mène Pogo jusqu'à vingt. Pas plus loin.
C'est, à la réflexion, le bon cadre pour l'expérience que je voulais dès le départ. Tout ce que la force brute peut prouver est maintenant prouvé. Le reste — les positions encore indécises à vingt demi-coups — est exactement le terrain où l'apprentissage par renforcement justifie sa place. Si un réseau, après assez d'auto-jeu, surpasse l'oracle partiel sur les positions profondes, c'est un résultat réel.
Les variantes que le projet a fini par tester sont de petites variations autour de cette règle de fin : nombres de répétitions différents, plafonds de coups différents, un départage de temps en temps. Le chapitre suivant en fait le tour, et le verdict trie celles qui valent la peine d'être jouées.
Si la règle était la variable, laquelle était la bonne ?
Pour choisir la bonne règle, il faut effectivement jouer chaque variante. J'ai fini par tester cinq petites variations autour de la règle de fin, et pour chacune j'ai construit trois adversaires à opposer entre eux.
Le premier adversaire pioche un coup légal au hasard. C'est un plancher : si un réseau entraîné ne le bat pas, c'est qu'il n'a rien appris.
Le deuxième est un réseau qui apprend en jouant contre lui-même, des milliers de fois, en glissant peu à peu ses préférences vers les coups qui ont fini par gagner et en s'éloignant de ceux qui ont fini par perdre. Le troisième est une version plus ambitieuse de la même idée — l'algorithme avec lequel DeepMind a battu les champions du monde au Go et aux échecs : à chaque tour, le réseau dresse une courte liste de suites plausibles et en regarde quelques-unes en avance avant de jouer. La combinaison est nettement meilleure que chaque pièce prise séparément.
Pour chaque variante, chaque niveau a affronté tous les autres — aléatoire contre le réseau entraîné, le réseau contre sa version plus ambitieuse, et ainsi de suite. Environ quinze mille parties par variante. Trois choses comptaient. Le taux de victoire du Blanc tombait-il entre 45 et 55 % — le jeu est-il équilibré ? Le joueur le plus fort battait-il le plus faible au moins 75 % du temps — la hiérarchie joue-t-elle son rôle ? Et quand il y avait des nuls, tombaient-ils entre joueurs de niveau comparable (mérités) ou entre joueurs inégaux (suspects) ?
Les cinq variantes testées sont ci-dessous. Le verdict suit.
Répéter une position deux fois → vous perdez.
La répétition triple met fin à la partie.
30 coups. La parité choisit le vainqueur.
29 coups. Le plus de tours l'emporte. Égalité = nul.
40 coups. Les nuls se méritent.
Deux tiennent les trois critères. Les trois autres, non.
Équilibre, hiérarchie des forces, nuls mérités : chaque variante passe ou ne passe pas, critère par critère. Les barres qui suivent résument un tournoi de plusieurs milliers de parties en trois chiffres ; les relevés partie par partie dorment dans le dépôt, mais l'histoire qu'ils racontent se voit de loin.
| variante | équilibre | hiérarchie | nuls | verdict |
|---|---|---|---|---|
★ Mort subite LC1-2 | 52.0% | 82.0% | 0.0% | tient |
★ Classique · 29 LC3-29 | 50.0% | 77.0% | 5.5% | tient |
Plafond souple long LC3-40 | 51.0% | 72.0% | 14.0% | réserve Les nuls s'installent. Jouable, mais sans relief. |
Triple répétition LC1-3 | 48.0% | 69.0% | 0.0% | réserve Équilibré sur le papier, défensif en pratique. |
Plafond strict · 30 LC2-30 | 58.0% | 61.0% | 0.0% | cède La parité du plafond tranche à la place des joueurs. |
Mort subite gagne par élégance. Répéter une position vous fait perdre, et le coût de l'attentisme est inscrit dans la règle elle-même plutôt que greffé par un compteur : chaque pose engage quelque chose. Le réseau l'a compris vite. Un AlphaZero entraîné bat un DQN entraîné 82 fois sur cent, et pas une partie n'a fini sur un nul. Un jeu qui tranche, ou qui continue.
LC1-2 · 52 % B · 82 % niveau · 0 % nuls
Classique · 29 gagne à l'oreille. Un budget de coups qu'on entend décompter, un départage propre (le plus de tours), et des nuls qui existent mais qui se paient : les joueurs forts en ont fait 5,5 % entre eux, jamais une paire déséquilibrée n'en a arraché un. C'est la règle que je choisirais pour apprendre le jeu à un enfant de dix ans — et, pour une règle, c'est difficile de recevoir un plus beau compliment.
LC3-29 · 50 % B · 77 % niveau · 5,5 % nuls
Des trois variantes qui n'ont pas survécu, l'échec le plus instructif était celle au plafond strict, sans départage. Sous cette règle, la partie est décidée en secret par la parité : celui qui doit jouer au coup de plafond perd, donc la couleur dont la parité tombe juste gagne, quelle que soit la qualité du jeu. Le réseau entraîné a mis un après-midi à le comprendre. Après quoi il a cessé de jouer au jeu, et a joué avec le compteur — un rappel utile : une règle où l'apprenant peut converger sur une astuce de comptage n'est plus, à proprement parler, une règle qui parle du plateau.
Le correctif a pris un après-midi ; la reprise, cinquante-quatre minutes. Les douze jours perdus n'auront été qu'à moi.
Face à un adversaire qui a appris le jeu réécrit sur un million de parties contre lui-même.
Choisissez une règle, choisissez un camp. Le réseau qui tourne dans votre navigateur est exactement celui qui a remporté le tournoi — exporté en ONNXONNXUn format de fichier de réseau de neurones que différents frameworks savent charger. (un format de fichier portable pour les réseaux entraînés), puis chargé côté navigateur, sans serveur dans la boucle. Chaque coup qu'il joue ici est calculé dans votre navigateur, sur votre appareil.
Au bout de 500 parties jouées contre lui-même, le réseau avait des idées bien arrêtées.
Les graphiques qui suivent viennent du même entraînement qui a remporté le tournoi — la variante à plafond souple, à 200 simulations par coup. Les chiffres sont empiriques, comptés sur les parties réellement jouées ; les diagrammes sont des positions tirées de l'enquête.
Là où le réseau regarde en premier.
Plus une case est saturée, plus le réseau y joue souvent au tout début de la partie. Les Blancs ne jouent presque jamais en dehors d'un petit ensemble de cases — la diagonale et le centre. La meilleure ouverture observée : a3 → b3 [×1] dans 72% des parties.
Le premier coup des Blancs, à peu de chose près, est écrit d'avance : a3 → b3 [×1], dans 72% des parties. (Le crochet indique la taille de la pile déplacée : à Pogo, on prend une, deux ou trois pièces sur une case où sa couleur est au sommet.)
Sur 500 parties qu'il a jouées contre lui-même, le réseau n'a essayé que 7 premiers coups distincts. Il sait la case qu'il veut atteindre.
Les captures culminent au demi-coup 6, puis s'espacent à mesure que la position se fige.
1353 parties sur 500 voient une capture se produire entre les demi-coups 6 et 10. Passé le coup 15, les captures se raréfient : le réseau règle ses échanges tôt, puis joue pour le tempo.
La confiance du réseau bascule brutalement sur de tout petits échanges.
Même partie, à douze demi-coups d'écart. À la première position, la tête valeur annonce +0.55 — Blanc gagne. Douze demi-coups plus tard, après une séquence d'échanges forcés, elle dit -0.47 — Rouge gagne. Pogo a ses falaises tactiques, et AlphaZero les voit venir.
Le premier joueur l'emporte par construction : 65% au Blanc, 31% au Rouge, 4% de nuls.
Face à lui-même, le réseau creuse un avantage de deux contre un en faveur du premier joueur. La variante Classique (LC3-29) atténue cet écart par la possibilité du nul, mais elle ne l'efface pas — l'ordre de jeu pèse, en définitive, plus lourd que toute subtilité stratégique.
Chaque terme du récit, en français clair.
La plupart de ces mots ne disaient rien à William au début du projet. Les définitions qui suivent sont celles qu'il aurait voulu qu'on lui tende à l'époque : brèves, précises, et qui ne tiennent jamais pour acquise la connaissance des dix termes voisins.
Glissez un terme — la constellation suit. Survol pour révéler les liens.
- Minimax: Un algorithme de recherche classique : on construit l'arbre des coups, on suppose que chaque camp choisit le coup qui lui est le plus favorable et le plus défavorable à l'autre, puis on remonte les valeurs depuis les feuilles. Optimal sur les jeux finis, mais il faut que chaque branche finisse par se terminer — ce que Pogo, dans sa forme d'origine, ne fait pas.
- Élagage alpha-bêta: Une amélioration de minimax. En explorant, on garde trace de la meilleure valeur déjà garantie à chaque joueur ; dès qu'une branche ne peut plus battre cette borne, on arrête. Sur un arbre bien ordonné, cela divise à peu près par deux l'exposant du coût de recherche.
- Table de transposition: Les algorithmes de recherche croisent la même position par des ordres de coups différents (les « transpositions »). Mettre en cache le résultat indexé sur la position elle-même transforme une explosion exponentielle en quelque chose de gérable — au prix de la mémoire, ce qui est exactement comment six jours se sont terminés en 36 Go et un OOM.
- RL: Apprentissage par renforcement. Une branche du machine learning où un agent choisit des actions, observe un signal de récompense, et ajuste sa stratégie pour obtenir plus de récompense avec le temps. Aucun professeur ne dit « ce coup est correct » ; le seul signal, ce sont les victoires et les défaites accumulées sur de nombreuses parties.
- Q-learning: L'algorithme RL d'entrée de gamme. On tient à jour une table de valeurs Q(état, action) estimant la récompense à long terme d'une action depuis un état, et on la met à jour avec l'équation de Bellman après chaque transition. Exact sur les petits problèmes, totalement impraticable sur les grands — la table devient plus grande que la RAM.
- DQN: Deep Q-Network. Le résultat de DeepMind en 2013 : remplacer la table Q par un réseau de neurones qui prend une position en entrée et sort une valeur pour chaque coup légal. Le réseau généralise, donc on n'est plus limité par la RAM. Ici, DQN fut le deuxième adversaire que j'ai entraîné — il bat largement le jeu aléatoire et perd contre AlphaZero.
- AlphaZero: Une recette d'entraînement. Un seul réseau de neurones sort à la fois une politique de coup et une valeur de position. L'agent joue contre lui-même des milliers de fois, chaque coup guidé par un Monte Carlo Tree Search qui consulte le réseau. L'entraînement n'utilise que les résultats de ces parties — pas d'exemples humains. C'est l'adversaire le plus fort de ce projet.
- MCTS: Monte Carlo Tree Search. Au lieu d'explorer exhaustivement, on simule de façon répétée des parties depuis la position courante, on fait pousser l'arbre vers les coups qui ont bien marché, et on renvoie l'action la plus visitée. Combiné avec un a priori par réseau de neurones, cela devient le cœur d'AlphaZero.
- PUCT: La formule qu'utilise MCTS pour choisir le prochain nœud à étendre. Elle ajoute un bonus aux coups peu visités que la politique juge bons, pour que l'arbre pousse vers les branches prometteuses mais sous-explorées. Les lettres signifient Polynomial Upper Confidence Trees.
- Self-play: Des données d'entraînement fabriquées en opposant le réseau courant à lui-même. Pas besoin d'humains. Chaque partie produit une trajectoire de triplets (état, politique, résultat) qui servent à pousser le réseau vers de meilleurs coups. La qualité des données s'améliore en même temps que le réseau — une boucle vertueuse.
- Gatekeeper: Une porte qui maintient l'entraînement honnête. Quand un nouveau réseau candidat est produit, il dispute une série contre le champion courant. S'il gagne au-delà d'un seuil (ici : 55 %), il devient le nouveau champion. Sinon on le jette. Empêche les coups de chance d'être pris pour du progrès.
- Réseau politique / valeur: Un réseau à la AlphaZero a deux têtes. La tête politique sort une distribution de probabilités sur les coups légaux ; la tête valeur sort un seul nombre estimant le résultat pour le joueur courant. Les deux sont entraînées conjointement sur les données de self-play.
- ONNX: Open Neural Network Exchange. Une sérialisation portable pour les modèles entraînés. Ici un script Python exporte le checkpoint PyTorch en ONNX, et le navigateur charge le même fichier via onnxruntime-web — le réseau entraîné sur mon iMac tourne tel quel sur votre téléphone.
- WASM: WebAssembly. Un format binaire portable pour du code qui tourne dans le navigateur. Le moteur Rust qui anime l'entraînement sur le serveur est compilé en WASM et livré à votre navigateur, c'est ainsi que votre adversaire utilise exactement le même générateur de coups que la boucle d'entraînement.
- Rust: Un langage de programmation conçu par Mozilla. Sûr mémoire sans ramasse-miettes, assez rapide pour les compilateurs et les moteurs de jeu, compile à la fois en binaires natifs et en WebAssembly. Ici il remplace une combinaison Python+TypeScript qui avait tendance à dériver entre entraînement et production.
- Distance de Manhattan: Pour deux cases en (x1,y1) et (x2,y2), la valeur |x1−x2| + |y1−y2|. C'est ainsi qu'un taxi mesurerait une distance sur une grille de rues. À Pogo, un coup d'une pièce franchit 1, un coup de deux pièces franchit 2, un coup de trois pièces franchit 1 ou 3.
- Équilibre paresseux: Pas un terme standard — mon raccourci pour un état de Pogo où les deux joueurs peuvent continuer à déplacer des pièces entre les trois mêmes cases sans jamais former de tour. Aucun ne perd, aucun ne gagne, et la partie, sous les règles naturelles, ne se termine pas.
- Toutes rondes: Chaque adversaire joue un nombre fixe de parties contre tous les autres, la moitié en Blanc et la moitié en Rouge. La matrice de taux de victoire qui en résulte indique non seulement qui est le plus fort, mais comment la variante de règle se comporte à chaque niveau — important parce qu'une règle qui marche pour les joueurs forts peut échouer pour les faibles, et l'inverse.
Voir la liste complète
- Minimax
- Un algorithme de recherche classique : on construit l'arbre des coups, on suppose que chaque camp choisit le coup qui lui est le plus favorable et le plus défavorable à l'autre, puis on remonte les valeurs depuis les feuilles. Optimal sur les jeux finis, mais il faut que chaque branche finisse par se terminer — ce que Pogo, dans sa forme d'origine, ne fait pas.
- Élagage alpha-bêta
- Une amélioration de minimax. En explorant, on garde trace de la meilleure valeur déjà garantie à chaque joueur ; dès qu'une branche ne peut plus battre cette borne, on arrête. Sur un arbre bien ordonné, cela divise à peu près par deux l'exposant du coût de recherche.
- Table de transposition
- Les algorithmes de recherche croisent la même position par des ordres de coups différents (les « transpositions »). Mettre en cache le résultat indexé sur la position elle-même transforme une explosion exponentielle en quelque chose de gérable — au prix de la mémoire, ce qui est exactement comment six jours se sont terminés en 36 Go et un OOM.
- RL
- Apprentissage par renforcement. Une branche du machine learning où un agent choisit des actions, observe un signal de récompense, et ajuste sa stratégie pour obtenir plus de récompense avec le temps. Aucun professeur ne dit « ce coup est correct » ; le seul signal, ce sont les victoires et les défaites accumulées sur de nombreuses parties.
- Q-learning
- L'algorithme RL d'entrée de gamme. On tient à jour une table de valeurs Q(état, action) estimant la récompense à long terme d'une action depuis un état, et on la met à jour avec l'équation de Bellman après chaque transition. Exact sur les petits problèmes, totalement impraticable sur les grands — la table devient plus grande que la RAM.
- DQN
- Deep Q-Network. Le résultat de DeepMind en 2013 : remplacer la table Q par un réseau de neurones qui prend une position en entrée et sort une valeur pour chaque coup légal. Le réseau généralise, donc on n'est plus limité par la RAM. Ici, DQN fut le deuxième adversaire que j'ai entraîné — il bat largement le jeu aléatoire et perd contre AlphaZero.
- AlphaZero
- Une recette d'entraînement. Un seul réseau de neurones sort à la fois une politique de coup et une valeur de position. L'agent joue contre lui-même des milliers de fois, chaque coup guidé par un Monte Carlo Tree Search qui consulte le réseau. L'entraînement n'utilise que les résultats de ces parties — pas d'exemples humains. C'est l'adversaire le plus fort de ce projet.
- MCTS
- Monte Carlo Tree Search. Au lieu d'explorer exhaustivement, on simule de façon répétée des parties depuis la position courante, on fait pousser l'arbre vers les coups qui ont bien marché, et on renvoie l'action la plus visitée. Combiné avec un a priori par réseau de neurones, cela devient le cœur d'AlphaZero.
- PUCT
- La formule qu'utilise MCTS pour choisir le prochain nœud à étendre. Elle ajoute un bonus aux coups peu visités que la politique juge bons, pour que l'arbre pousse vers les branches prometteuses mais sous-explorées. Les lettres signifient Polynomial Upper Confidence Trees.
- Self-play
- Des données d'entraînement fabriquées en opposant le réseau courant à lui-même. Pas besoin d'humains. Chaque partie produit une trajectoire de triplets (état, politique, résultat) qui servent à pousser le réseau vers de meilleurs coups. La qualité des données s'améliore en même temps que le réseau — une boucle vertueuse.
- Gatekeeper
- Une porte qui maintient l'entraînement honnête. Quand un nouveau réseau candidat est produit, il dispute une série contre le champion courant. S'il gagne au-delà d'un seuil (ici : 55 %), il devient le nouveau champion. Sinon on le jette. Empêche les coups de chance d'être pris pour du progrès.
- Réseau politique / valeur
- Un réseau à la AlphaZero a deux têtes. La tête politique sort une distribution de probabilités sur les coups légaux ; la tête valeur sort un seul nombre estimant le résultat pour le joueur courant. Les deux sont entraînées conjointement sur les données de self-play.
- Toutes rondes
- Chaque adversaire joue un nombre fixe de parties contre tous les autres, la moitié en Blanc et la moitié en Rouge. La matrice de taux de victoire qui en résulte indique non seulement qui est le plus fort, mais comment la variante de règle se comporte à chaque niveau — important parce qu'une règle qui marche pour les joueurs forts peut échouer pour les faibles, et l'inverse.
- ONNX
- Open Neural Network Exchange. Une sérialisation portable pour les modèles entraînés. Ici un script Python exporte le checkpoint PyTorch en ONNX, et le navigateur charge le même fichier via onnxruntime-web — le réseau entraîné sur mon iMac tourne tel quel sur votre téléphone.
- WASM
- WebAssembly. Un format binaire portable pour du code qui tourne dans le navigateur. Le moteur Rust qui anime l'entraînement sur le serveur est compilé en WASM et livré à votre navigateur, c'est ainsi que votre adversaire utilise exactement le même générateur de coups que la boucle d'entraînement.
- Rust
- Un langage de programmation conçu par Mozilla. Sûr mémoire sans ramasse-miettes, assez rapide pour les compilateurs et les moteurs de jeu, compile à la fois en binaires natifs et en WebAssembly. Ici il remplace une combinaison Python+TypeScript qui avait tendance à dériver entre entraînement et production.
- Distance de Manhattan
- Pour deux cases en (x1,y1) et (x2,y2), la valeur |x1−x2| + |y1−y2|. C'est ainsi qu'un taxi mesurerait une distance sur une grille de rues. À Pogo, un coup d'une pièce franchit 1, un coup de deux pièces franchit 2, un coup de trois pièces franchit 1 ou 3.
- Équilibre paresseux
- Pas un terme standard — mon raccourci pour un état de Pogo où les deux joueurs peuvent continuer à déplacer des pièces entre les trois mêmes cases sans jamais former de tour. Aucun ne perd, aucun ne gagne, et la partie, sous les règles naturelles, ne se termine pas.
Gardez assez du problème en tête pour savoir quand quelque chose cloche.
Une IA m'a dit, un jour, qu'un petit jeu de plateau avait moins d'un million de positions atteignables. Elle s'est trompée d'un facteur cinquante. Le premier run a stocké quarante-neuf millions d'entrées avant que le noyau ne le tue. Le second a sauté de la même manière, et tout ce qui restait avec. Douze jours de calcul en tout.
La réponse était plausible. Elle collait à ce que j'espérais entendre. Ni l'un ni l'autre n'a pris les dix minutes qu'il aurait fallu pour vérifier.
La leçon, ce n'est pas que les IA ne sont pas fiables. C'est qu'une erreur dite avec aisance sonne exactement comme une vérité dite avec aisance — et que la seule défense contre cette symétrie, c'est de garder dans votre tête une version du problème assez détaillée pour repérer vous-même, sans aide, quand une réponse est fausse d'un ordre de grandeur. Ce n'est pas une protection technique. C'est une discipline.
On ne peut pas déléguer la part du raisonnement qui vous dit si le raisonnement fonctionne.
La réécriture a eu lieu après le second échec. Le solveur Python a été repris en RustRustUn langage système qui élimine toute une classe de bugs mémoire à la compilation., avec détection de cycle dans la recherche et points de sauvegarde à chaque long calcul. Le même moteur alimente l'entraînement du réseau et l'onglet du navigateur dans lequel vous lisez, sans dérive possible entre ce que le réseau a appris et ce que vous pouvez affronter. La règle elle-même a été traitée comme variable d'expérience, non plus comme donnée. Ce qui en est sorti est un jeu plus petit, plus honnête : il se termine, le meilleur l'emporte, les nuls sont mérités.
Le code est ouvert. Le solveur, le pipeline d'entraînement, les variantes et leurs logs de tournoi, le réseau et le pont qui le fait tourner côté client — tout tient dans un seul espace de travail Rust qu'il vous suffit de cloner et de lancer. La meilleure manière de faire confiance à un calcul, c'est encore de le refaire soi-même.
Pogofish embarque un jeu en mode terminal — même moteur Rust que la version navigateur ci-dessus, interface curses, contrôles au clavier.
macOS et Linux. Touches : flèches pour naviguer, Entrée pour sélectionner, 1/2/3 pour la taille de pile, q pour quitter.
— William Revah, printemps 2026