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

Scoreboard

Several counters side by side for a match or a board game, with editable names and the leader highlighted. The right exercise to handle a list of objects rather than a single value.

HTMLCSSJS
What you'll learn
  • Describe the state with an array of objects
  • Recompute the whole display from the data
  • Find the leader and handle ties
Why this language

Pourquoi un tableau de score ? Au ping-pong, aux cartes ou au Mölkky, on finit toujours par compter sur un bout de papier. Ici, chaque joueur a son nom, son score, ses boutons.

Le vrai changement, c'est l'état : ce n'est plus un nombre, c'est un tableau d'objets. Ajouter un joueur, c'est ajouter un objet. Après chaque action, on redessine tout à partir de ce tableau, et le joueur en tête change de couleur.

Pourquoi en JavaScript simple ? Un tableau, une boucle qui crée les cartes, un calcul de maximum. C'est le premier pas vers ce que font React ou Vue.

How it works
  1. L'ÉTAT

    Un tableau d'objets : chaque joueur porte son nom et son score.

    const joueurs = [
      { nom: 'Rouge', score: 0 },
      { nom: 'Bleu', score: 0 },
    ];
  2. LE MENEUR

    S'il y a plusieurs joueurs au même score maximum, personne ne mène.

    function meneur() {
      const max = Math.max(...joueurs.map(j => j.score));
      const enTete = joueurs.filter(j => j.score === max);
      return enTete.length === 1 && max > 0 ? enTete[0] : null;
    }
  3. DESSINER

    On reconstruit la grille à partir des données, à chaque changement.

    function rendre() {
      const tete = meneur();
      const grille = document.getElementById('grille');
      grille.innerHTML = '';
      joueurs.forEach((j, i) => {
        const carte = document.createElement('div');
        carte.className = 'joueur' + (j === tete ? ' tete' : '');
        carte.innerHTML = '<input aria-label="Nom"><b></b><button data-d="-1">−</button><button data-d="1">+</button>';
        carte.querySelector('input').value = j.nom;      // value et textContent : pas d'injection de HTML
        carte.querySelector('b').textContent = j.score;
        carte.dataset.i = i;
        grille.appendChild(carte);
      });
    }
  4. UN SEUL ÉCOUTEUR

    document.getElementById('grille').addEventListener('click', e => {
      const b = e.target.closest('[data-d]');
      if (!b) return;
      const j = joueurs[b.closest('.joueur').dataset.i];
      j.score = Math.max(0, j.score + Number(b.dataset.d));
      rendre();
    });
  5. AJOUTER UN JOUEUR

    document.getElementById('ajout').addEventListener('click', () => {
      joueurs.push({ nom: 'Joueur ' + (joueurs.length + 1), score: 0 });
      rendre();
    });
Pitfalls

PIÈGE 1 : Mettre le nom dans innerHTML

Pitfall

Mauvais : '<b>' + j.nom + '</b>'. Un nom comme <img onerror=...> exécute du code.

Fix

Correct : créer la structure, puis remplir avec textContent ou value.

PIÈGE 2 : Perdre le focus à chaque clic

Pitfall

Mauvais : tout redessiner sans y penser. Au clavier, le focus disparaît après chaque point.

Fix

Correct : remettre le focus sur le bouton équivalent après le rendu, ou ne mettre à jour que la carte concernée.

PIÈGE 3 : Un meneur en cas d'égalité

Pitfall

Mauvais : prendre le premier joueur qui a le score maximum. À 3 partout, Rouge passe en tête sans raison.

Fix

Correct : compter combien de joueurs ont le maximum, et n'afficher un meneur que s'il est seul.

PERSONNALISER
  • Un score cible : la partie s'arrête quand un joueur atteint 21 ou 50.
  • Un historique des manches, avec le total de chacun.
  • Des couleurs par joueur, choisies dans la palette du site.
OPTIMISATION
  • Gardez la partie dans localStorage pour la reprendre après une coupure, comme le compteur qui se souvient.
  • Avec beaucoup de joueurs, ne redessinez que la carte modifiée au lieu de toute la grille.
VERS REACT

Le tableau vit dans un useState, et chaque modification crée un nouveau tableau avec map.

Chaque joueur devient un composant Carte, avec une key stable comme un id, pas l'index.

Le meneur se calcule pendant le rendu, sans état séparé.

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.