.damier-mini { display: grid; grid-template-columns: repeat(10, 1fr); width: min(100%, 340px); aspect-ratio: 1; border: 4px solid #4a2e17; border-radius: 4px; margin: 0 auto; } .damier-mini span { display: grid; place-items: center; font-family: var(--font-mono); font-size: 10px; } .damier-mini .c { background: #efdcb8; } .damier-mini .f { background: #8b5a33; color: #fff3df; } .damier-mini .f.ray { background: #5f8f3e; } .damier-mini .f.dame { background: #a87a06; font-weight: 700; }
dames-moteur.js · alpha-bêta · algorithme évolutionnaire · serveur Node en Server-Sent Events

Dames contre l'IA, le code

Comment la page applique les règles des dames internationales, comment l'IA choisit ses coups et apprend à mieux juger une position, et comment deux téléphones jouent ensemble à travers le serveur.

01Les règles

Ce sont les dames internationales, celles des clubs français : un damier de 10 × 10, 20 pions chacun sur les cases foncées, les blancs commencent.

  • Un pion avance d'une case en diagonale, mais prend vers l'avant comme vers l'arrière.
  • La prise est obligatoire, et majoritaire : parmi toutes les prises possibles, il faut jouer celle qui prend le plus de pièces.
  • Les pièces prises ne sont retirées qu'à la fin de la rafle, et on ne peut pas sauter deux fois la même.
  • Un pion devient dame s'il termine son coup sur la dernière rangée. La dame « vole » : elle se déplace et prend à n'importe quelle distance.
  • Celui qui ne peut plus jouer a perdu. La partie est nulle après 25 coups de dames de chaque côté sans prise ni mouvement de pion.

02Le damier en mémoire

Seules les 50 cases foncées comptent. Le damier est donc un simple tableau de 50 nombres : 0 case vide, 1 pion blanc, 2 dame blanche, -1 pion noir, -2 dame noire. Les cases sont numérotées comme sur un vrai damier de compétition (1 à 50), ce qui donne directement la notation officielle des coups : 32-28, 28x19.

Pour aller vite, on calcule une fois pour toutes les rayons : pour chaque case et chacune des 4 diagonales, la liste des cases rencontrées jusqu'au bord. Un pion n'en regarde que la première case, une dame les parcourt toutes.

Les 50 cases jouables numérotées. En vert : les quatre rayons de la case 28, ce qu'une dame posée là peut atteindre sur un damier vide.
dames-moteur.js · rayons
const RAYONS = Array.from({ length: 50 }, (_, s) => DIRS.map(([dr, dc]) => {
  const r = [];
  let l = LIGNE(s) + dr, c = COL(s) + dc;
  while (CASE(l, c) >= 0) { r.push(CASE(l, c)); l += dr; c += dc; }
  return r;
}));

03Trouver les coups

Le plus délicat est la rafle. Pour chaque pièce, une recherche en profondeur essaie toutes les suites de prises : depuis la case courante, dans chaque direction, peut-on sauter une pièce adverse pas encore prise et atterrir sur une case libre ? Si oui, on recommence depuis la case d'arrivée. Quand plus aucune prise n'est possible, la rafle est terminée et on la note avec le nombre de pièces prises.

Deux détails comptent. La case de départ est vidée pendant la recherche, car la pièce peut repasser par là. Les pièces déjà prises restent sur le damier jusqu'à la fin : une dame ne peut donc pas les traverser.

dames-moteur.js · prisesDe() (extrait : la dame)
let i = 0;
while (i < ray.length && b[ray[i]] === 0) i++;          // on glisse sur les cases vides
const a = ray[i];
if (b[a] * camp >= 0 || prises.includes(a)) continue;  // pas une pièce adverse, ou déjà prise
i++;
while (i < ray.length && b[ray[i]] === 0) {             // toutes les cases libres derrière
  dfs(ray[i], [...prises, a], [...chemin, ray[i]]);     // la rafle continue depuis chacune
  i++;
}

On garde ensuite seulement les rafles qui prennent le plus de pièces, toutes pièces confondues : c'est la règle majoritaire. S'il n'y a aucune prise, on liste les déplacements simples. La page montre alors seulement les pièces jouables, et sélectionne d'office la pièce quand il n'y en a qu'une.

04Juger une position

Pour comparer deux positions, l'IA compte 7 critères pour chaque camp et fait la différence, chaque critère multiplié par un poids. C'est ce jeu de 7 poids qui fait « le caractère » de l'IA, et c'est lui qu'elle apprend.

CritèreCe qu'on comptePoids de départ
PionNombre de pions1
DameNombre de dames3
Avancée des pionsRangées parcourues par chaque pion (0 à 1)0,1
Contrôle du centrePièces sur les 10 cases centrales0,1
Rangée du fond gardéePions restés sur sa première rangée (ils empêchent l'adversaire d'aller à dame)0,1
Pion protégéPions avec une pièce amie ou le bord juste derrière0,05
Pion bientôt damePions sur les 3 dernières rangées0,3

05Chercher le meilleur coup

L'IA imagine tous ses coups, toutes les réponses, ses réponses aux réponses, et ainsi de suite jusqu'à une certaine profondeur. Au bout de chaque branche, elle juge la position. Elle suppose que chaque camp choisit le meilleur coup pour lui (minimax), et coupe les branches qui ne peuvent plus changer le résultat (alpha-bêta), ce qui permet de voir bien plus loin pour le même calcul.

Une précaution propre aux dames : si la position au bout d'une branche contient une prise obligatoire, la juger maintenant serait trompeur (une pièce est sur le point de tomber). La recherche continue donc tant qu'il y a des prises, jusqu'à 6 demi-coups de plus.

dames-moteur.js · chercher() (extrait)
const nega = (q, prof, alpha, beta, ext) => {
  const liste = coups(q);
  if (!liste.length) return -1000 - prof;                 // plus de coup : perdu
  const forcee = liste[0].prises.length > 0;
  if (prof <= 0 && (!forcee || ext <= 0)) return evaluer(q, w);
  let best = -Infinity;
  for (const m of liste) {
    const v = -nega(jouer(q, m), ..., -beta, -alpha, ...);
    if (v > best) best = v;
    if (v > alpha) alpha = v;
    if (alpha >= beta) break;                            // coupure alpha-bêta
  }
  return best;
};
ForceProfondeurRemarque
Débutant1 demi-coupAvec une bonne part de hasard dans le choix.
Moyen3Bat le jeu au hasard 10 fois sur 10.
Fort5Environ 8 500 positions examinées au premier coup.
Expert7Environ 140 000 positions, 0,4 s au premier coup, plus en milieu de partie.

La recherche tourne dans un Web Worker : pendant que l'IA réfléchit, la page reste fluide.

06Apprendre par tournois

Ici, pas de réseau de neurones comme pour les pandas : ce qui s'apprend, ce sont les 7 poids. On utilise un algorithme évolutionnaire, le même principe que NEAT mais sur une liste de nombres au lieu d'un réseau.

  1. Une population de 12 jeux de poids (réglable), tous proches des poids de départ.
  2. Tournoi : chacun joue 4 parties (2 avec les blancs, 2 avec les noirs) contre des adversaires tirés au sort, à profondeur 2. Victoire 1 point, nulle ½. Les 2 premiers coups sont joués au hasard pour varier les parties.
  3. Sélection : les 3 meilleurs passent tels quels ; les autres places sont prises par des enfants de deux bons parents (chaque poids pris chez l'un ou l'autre), légèrement modifiés au hasard.
  4. Mesure : le meilleur de la génération joue 8 parties contre l'IA de départ. C'est la courbe affichée.

Les poids sont toujours remis à l'échelle pour qu'un pion vaille 1 : on lit directement « une dame vaut 3,6 pions ». Chaque génération est gardée, et vous pouvez choisir l'IA de n'importe laquelle comme adversaire.

Résultat mesuré sur 30 générations : l'IA entraînée marque 69 % des points contre l'IA de départ, au contrôle sur 40 parties. Elle a appris seule à garder sa rangée du fond (poids de 0,1 à 0,4-0,6) et à valoriser fortement les pions proches de la promotion (de 0,3 à plus de 1). La courbe génération par génération est bruitée : 8 parties, c'est peu pour départager deux IA proches.

07Jouer en ligne

Pour jouer à deux sur deux téléphones, un petit serveur Node (serveur/dames-serveur.js, sans aucune dépendance) tient les parties. Chaque partie est une salle avec un code de 5 caractères, sans 0, O, 1 ni I pour pouvoir le dicter.

  1. Le créateur reçoit un code et un jeton secret, gardé dans son navigateur : c'est lui qui réserve sa couleur, même s'il recharge la page.
  2. L'adversaire ouvre le lien dames.html?salle=CODE et reçoit l'autre place et son propre jeton.
  3. Chaque navigateur ouvre un flux (Server-Sent Events) : une connexion HTTP qui reste ouverte, par laquelle le serveur pousse l'état de la partie à chaque changement.
  4. Un coup part par une simple requête POST. Le serveur rejoue toute la partie, vérifie que c'est bien le tour de ce joueur et que le coup figure dans la liste des coups légaux, puis l'ajoute et prévient les deux joueurs.
serveur/dames-serveur.js · vérification d'un coup
if (p.trait !== c) return repondre(res, 409, { erreur: "Ce n'est pas à vous de jouer." });
if (m.index !== salle.coups.length) return repondre(res, 409, { erreur: "Coup déjà joué ou décalé" });
const legal = liste.find((x) => cle(x) === cle({ de: m.de, a: m.a, prises: m.prises || [] }));
if (!legal) return repondre(res, 422, { erreur: "Coup illégal." });
salle.coups.push(legal);
diffuser(salle);

Le serveur charge le même fichier de règles que la page (dames-moteur.js) : une page trafiquée ne peut pas faire accepter un coup interdit. Comme la partie est stockée sous forme de liste de coups, un joueur qui revient reçoit la liste et reconstruit la position.

Le flux passe par nginx avec la mise en tampon désactivée, sinon les coups arriveraient par paquets. Les salles vivent en mémoire et disparaissent après 48 heures sans activité.

RequêteRôle
POST /dames-api/sallesCréer une partie (couleur au choix)
POST …/CODE/rejoindrePrendre la place libre, ou retrouver la sienne avec son jeton
GET …/CODE/fluxRecevoir l'état de la partie en continu
POST …/CODE/coupJouer un coup (vérifié)
POST …/CODE/abandon, …/revancheAbandonner ; rejouer en échangeant les couleurs

08Les fichiers

FichierContenu
web/dames-moteur.jsRègles, jugement d'une position, recherche alpha-bêta, partie entre deux IA
web/dames.htmlDamier, modes de jeu, Web Worker (réflexion et entraînement), mode en ligne
serveur/dames-serveur.jsServeur des parties en ligne (service pm2 dames-reseau, port 3077)
terminal
pm2 start serveur/dames-serveur.js --name dames-reseau   # lancer le serveur des parties en ligne
pm2 restart dames-reseau                                  # après une modification (les parties en cours sont perdues)
./deploy.sh                                               # publier les pages sur rdv-app.fr