Morpion Invincible - 5/6 - Du terminal au navigateur
- 25 avr.
- 5 min de lecture
Lien vers le repo du projet : https://github.com/vohorgeez/Morpion-invincible
Notre IA est imbattable.
Elle explore l'arbre de jeu, élague les branches inutiles, choisit toujours le coup parfait. Techniquement, c'est propre.
Mais voilà le problème : personne ne veut jouer dans un terminal.
Ou disons : les gens peuvent jouer dans un terminal. Mais ils ne le feront pas. Pas spontanément. Pas avec plaisir. Et si personne ne joue, l'IA la plus brillante du monde n'est qu'un algo qui tourne dans le vide.
Cet article, c'est l'histoire du passage de ça :
PS C:\dev\Morpion-invincible> python cli.py
Qui commence ?
1 - Vous (O)
2 - IA (X)
Votre choix (1 ou 2, défaut = 1) : 1
| |
---------------
| |
---------------
| |
Veuillez entrer un entier entre 0 et 8, correspondant à la case que vous souhaitez investir.à ça :

-> Un vrai truc cliquable, dans un navigateur, qui donne envie de (re)jouer.
Nostalgie de la CLI
Commençons par être honnêtes : la CLI a de vraies qualités.
Elle est rapide à mettre en place. Pas de HTML, pas de CSS, pas d'événements DOM. On `print()`, on `input()`, c'est réglé. Pour tester un algo, c'est parfait - c'est d'ailleurs pour ça qu'on a commencé par là.
Elle est aussi directe. Quand on développe le moteur du jeu, on ne veut pas se battre avec des pixels. On veut voir les coups, les scores, les logs de debug. La CLI laisse voir l'intérieur de la machine.
Mais elle a un défaut fatal : elle demande un effort à l'utilisateur.
Lancer un script Python. Comprendre le format des cases numérotées (de 0 à 8 ?!) et ne pas se tromper d'index. Relire la grille affichée en ASCII. Ce n'est pas "difficile" - mais c'est pénible. Et la pénibilité, c'est exactement comme lire le mot "pénibilité" à haute voix, ça ne donne pas envie d'y aller.
La règle d'or de l'UX : chaque étape supplémentaire entre l'utilisateur et son objectif réduit les chances qu'il y arrive.
Un clic sur une case, c'est zéro friction. C'est intuitif, c'est fluide, on y pense naturellement sans même y avoir été invité explicitement... Une saisie clavier, qui plus est avec un format spécifique, c'est déjà une barrière.
Le choix technique : JS simple, pas React
Quand on parle d' "interface web", on pourrait avoir le réflexe de se la péter en ouvrant un terminal et de taper `npx create-react-app`. Ou Vite. Ou Next.js. Ou n'importe quel framework qui brille sur une ligne de CV.
J'ai choisi de ne pas le faire.
Pour une raison toute bête.
Le projet ne le justifie absolument pas.
React, c'est un outil pour gérer la complexité d'état dans des applications qui ont des dizaines de composants qui se parlent. Notre morpion : un plateau de 9 cases, un état de jeu, et un bouton "reset"...
Sortir React pour ça, c'est vouloir tuer un moustique avec une bombe H : ça fonctionne mais c'est peut-être un peu exagéré. L'efficacité ne doit pas occulter l'efficience.
Ô Sainte Simplicité
Revendiquons donc le choix de la simplicité comme une décision d'ingénieur qui se tient. Un fichier `index.html` pour le front, un `style.ss` parce qu'on n'est pas des bêtes, et un `script.js` pour la logique. C'est tout, et ça suffit.
Pas de build step. Pas de `node_modules` de 300 Mo. Pas de configuration webpack. Epargnons-nous ces vaines souffrances et contentons-nous d'apprécier les plaisirs simples du fichier unique qui s'ouvre dans un navigateur et qui fonctionne correctement.
Ce choix n'est pas de la paresse - vous l'aurez compris, c'est une question de proportionnalité. Choisir l'outil adapté au problème, c'est exactement ce que font les bons ingénieurs.
Une portabilité maximale
N'importe qui peut cloner le repo et ouvrir `index.html`. Sans installer quoi que ce soit. C'est important pour un projet portfolio : je confesse nourrir l'espoir qu'un recruteur puisse tester en 10 secondes, pas qu'il passe 20 minutes interminables à configurer un environnement.
Ce que l'UI change
La grille devient un objet, pas un simple affichage.
En CLI, la grille était affichée - c'est-à-dire imprimée, puis oubliée. A chaque coup, on réimprime tout.
En JS, la grille est un objet DOM vivant. Les cases existent en permanence. On leur attache des événements. On les met à jour. On leur change des classes CSS. Elles réagissent. Bref, ça vit, tout ça.
function initGrid() {
gridEl.innerHTML = "";
for (let i = 0; i < 9; i++) {
const btn = document.createElement("button");
btn.className = "cell";
btn.dataset.index = String(i);
btn.addEventListener("click", onCellClick);
gridEl.appendChild(btn);
}
}Cette petite fonction, c'est toute la différence entre un affichage et une interface.
L'état du jeu devient visible
En CLI, l'état vivait dans des variables Python. On le voyait via des `print()`.
En UI, l'état se lit dans la page : quelle case est jouée, quelle ligne est surlignée, quel message s'affiche. L'utilisateur perçoit l'état sans qu'on ait besoin de lui expliquer.
C'est ce qu'on appelle le feedback visuel - et c'est ce qui donne l'impression qu'un produit "fonctionne bien", même si la logique derrière est identique.
Le surlignage de victoire : le petit détail qui te prend par la main
const line = getWinningLine(board);
if (line) {
for (const idx of line) gridEl.children[idx].classList.add("win");
}Ajout purement cosmétique qui ne change rien à l'algorithme. L'IA gagne exactement de la même façon. Mais l'expérience monte d'un grade : l'issue de la partie saute aux yeux. La ligne s'illumine. C'est satisfaisant, même dans la défaite.
Les décisions UX
Bloquer le plateau après la fin de la partie
Rétrospectivement évident, mais facile à zapper : si on ne bloque pas les clics après un game over, l'utilisateur peut continuer de jouer sur un plateau terminé. C'est absurde et ça casse l'immersion.
function checkGameState() {
const w = getWinner(board);
if (w === null) return false;
endGame(w);
return true;
}
function onCellClick(e) {
// ...
if (checkGameState()) return;
aiTurn();
}Le bouton reset
Alors oui, l'utilisateur peut rafraîchir la page. Mais pourquoi ne pas proposer un reset propre tant qu'à faire ? Réinitialiser l'état JS, effacer les classes CSS, réafficher un plateau vide. L'utilisateur reste dans la page, et le contexte de ne casse pas.
L'affichage du tour et du résultat
"à vous de jouer" -> "L'IA réfléchit..." -> "Match nul !" - autant d'états que de messages différents. Ce n'est pas de la complexité : c'est de la clarté. L'utilisateur sait toujours où il en est.
La vraie différence
Coder le moteur de jeu, c'est résoudre un problème fermé : on a des règles, on implémente, on teste, c'est vrai ou faux.
Coder l'interface, c'est résoudre un problème ouvert : on ne sait jamais vraiment si c'est "assez bien". On peut toujours ajouter une animation, améliorer un message, affiner un layout.
Mais comme on dit : "Le mieux est l'ennemi du bien !", la vraie compétence, c'est de savoir s'arrêter.
Minimal viable != bâclé. Cela signifie : ce qui est là fonctionne bien. Ce qui n'est pas là n'était pas nécessaire.
Notre UI n'est pas spectaculaire. Elle est propre, fonctionnelle, compréhensible. Et elle laisse l'algorithme sous le feu des projecteurs, car c'est tout l'intérêt de ce projet, ne l'oublions pas.
Et maintenant ?
Le projet est jouable. L'IA est imbattable. Le code est lisible.
Dans le dernier article, on va voir ce qu'on aurait pu pousser encore plus loin : le move ordering, la transposition table, et un bilan honnête de ce qu'on a vraiment appris.





Commentaires