Skip to content
← 1001 Widgets
N° 011 · ●●○

Tic-tac-toe

Two-player tic-tac-toe, with the winning line highlighted and the score kept. The right exercise to learn to describe a game by its state.

HTMLJS
What you'll learn
  • Represent a board with an array of 9 cells
  • Test the 8 winning lines
  • Lock the board when the game is over
Why this language

Pourquoi un morpion ? Neuf cases, deux joueurs, huit façons de gagner. C'est le plus petit jeu complet qu'on puisse coder : un plateau, des tours, une condition de victoire, une fin de partie.

On représente le plateau par un tableau de neuf cases, et on teste les huit lignes gagnantes après chaque coup.

Pourquoi en JavaScript simple ? Toute la logique tient dans un tableau. C'est la base de la variante imbattable, qui ajoute une IA.

How it works
  1. LE HTML

    <div id="plateau"></div>
    <p id="statut" aria-live="polite"></p>
    <button id="rejouer">Rejouer</button>
  2. L'ÉTAT

    Neuf cases vides, le joueur qui commence, et la fin de partie.

    let cases = Array(9).fill(null);
    let joueur = 'X';
    let fini = false;
  3. LES LIGNES GAGNANTES

    Trois lignes, trois colonnes, deux diagonales.

    const LIGNES = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]];
    function gagnant() {
      for (const [a, b, c] of LIGNES) {
        if (cases[a] && cases[a] === cases[b] && cases[a] === cases[c]) return cases[a];
      }
      return cases.every(Boolean) ? 'nul' : null;
    }
  4. AFFICHER

    Neuf boutons, désactivés quand la case est prise ou la partie finie.

    function afficher() {
      document.getElementById('plateau').innerHTML = cases.map((v, i) =>
        `<button data-i="${i}" ${v || fini ? 'disabled' : ''} aria-label="Case ${i + 1}">${v || ''}</button>`
      ).join('');
      const g = gagnant();
      document.getElementById('statut').textContent =
        g === 'nul' ? 'Match nul.' : g ? `${g} gagne.` : `Au tour de ${joueur}.`;
    }
  5. JOUER

    document.getElementById('plateau').addEventListener('click', e => {
      const i = e.target.dataset.i;
      if (i === undefined || cases[i] || fini) return;
      cases[i] = joueur;
      fini = Boolean(gagnant());
      joueur = joueur === 'X' ? 'O' : 'X';
      afficher();
    });
    document.getElementById('rejouer').addEventListener('click', () => {
      cases = Array(9).fill(null);
      joueur = 'X';
      fini = false;
      afficher();
    });
    afficher();
Pitfalls

PIÈGE 1 : Tester la victoire à la main

Pitfall

Mauvais : une longue suite de if pour chaque ligne. Une faute de frappe, et une diagonale ne compte plus.

Fix

Correct : un tableau des huit lignes et une seule boucle.

PIÈGE 2 : Jouer après la fin

Pitfall

Mauvais : oublier de bloquer le plateau. On peut encore cliquer après la victoire.

Fix

Correct : une variable fini, et des boutons désactivés.

PIÈGE 3 : Des div cliquables

Pitfall

Mauvais : des <div> avec un onclick. Impossible de jouer au clavier.

Fix

Correct : des <button>, un aria-label par case, et le statut dans une zone aria-live.

PERSONNALISER
  • Surligner la ligne gagnante : renvoyez la ligne dans gagnant() et ajoutez une classe aux trois cases (c'est ce que fait la démo).
  • Un score qui survit à « Rejouer » : trois compteurs, X, O et nuls.
  • Vos symboles : remplacez X et O par deux kanji, ou deux emoji.
OPTIMISATION
  • Le joueur courant se déduit du nombre de cases remplies : une variable de moins à garder synchronisée.
  • Pour une IA, l'algorithme minimax explore toutes les suites possibles : c'est la variante « Morpion imbattable ».
VERS REACT

cases dans un useState, et le gagnant calculé pendant le rendu, jamais stocké.

Un historique des coups permet un bouton « Annuler » en une ligne.

Variants
Comments
0/1000

Loading comments…

Audience measurement

With your consent, this site measures its traffic with Google Analytics and the Meta pixel, via Google Tag Manager. Nothing is loaded until you choose, and you can change your mind at any time with the "Cookies" link at the bottom of the page.