Construire un jeu de dominos en Java : modélisation et mise en page

Coder un jeu de dominos est un excellent exercice de modélisation objet. Les règles sont simples, mais la représentation interne et l'affichage soulèvent des questions intéressantes d'architecture. Ce guide propose une modélisation claire en Java, avec code complet pour une partie à deux joueurs.

Les règles en bref

  • Un jeu complet contient 28 tuiles, chacune portant deux chiffres entre 0 et 6.
  • Chaque joueur pioche 7 tuiles ; les 14 restantes forment la pioche.
  • À tour de rôle, chaque joueur pose une tuile dont l'une des extrémités correspond à celle d'un bord du plateau.
  • Si un joueur ne peut pas jouer, il pioche. S'il ne reste rien dans la pioche, il passe.
  • Le premier à poser toutes ses tuiles gagne.

La classe Domino

Une tuile est représentée par deux entiers. Elle doit être comparable, retournable et afficheable :

public record Domino(int gauche, int droite) {
    public Domino {
        if (gauche < 0 || gauche > 6 || droite < 0 || droite > 6) {
            throw new IllegalArgumentException("Valeurs hors [0,6]");
        }
    }

    public boolean estDouble() {
        return gauche == droite;
    }

    public Domino retourner() {
        return new Domino(droite, gauche);
    }

    public boolean peutSeLierA(int valeur) {
        return gauche == valeur || droite == valeur;
    }

    @Override
    public String toString() {
        return "[" + gauche + "|" + droite + "]";
    }
}

Le record de Java 16 donne equals, hashCode et toString gratuitement.

Générer le jeu complet

import java.util.*;

public class Jeu {
    public static List<Domino> creerJeuComplet() {
        List<Domino> tuiles = new ArrayList<>();
        for (int i = 0; i <= 6; i++) {
            for (int j = i; j <= 6; j++) {
                tuiles.add(new Domino(i, j));
            }
        }
        return tuiles; // 28 tuiles
    }

    public static List<Domino> melanger(List<Domino> jeu) {
        List<Domino> copie = new ArrayList<>(jeu);
        Collections.shuffle(copie);
        return copie;
    }
}

La classe Plateau

Le plateau tient les tuiles déjà posées dans l'ordre, en exposant les deux valeurs d'extrémité. On utilise une Deque pour pouvoir ajouter à gauche comme à droite en O(1) :

public class Plateau {
    private final Deque<Domino> tuiles = new ArrayDeque<>();

    public boolean estVide() { return tuiles.isEmpty(); }

    public int extremiteGauche() {
        return tuiles.peekFirst().gauche();
    }

    public int extremiteDroite() {
        return tuiles.peekLast().droite();
    }

    public boolean poser(Domino d) {
        if (estVide()) { tuiles.addFirst(d); return true; }
        int g = extremiteGauche();
        int dr = extremiteDroite();

        if (d.droite() == g)       { tuiles.addFirst(d); return true; }
        if (d.gauche() == g)       { tuiles.addFirst(d.retourner()); return true; }
        if (d.gauche() == dr)      { tuiles.addLast(d); return true; }
        if (d.droite() == dr)      { tuiles.addLast(d.retourner()); return true; }
        return false;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (Domino d : tuiles) sb.append(d);
        return sb.toString();
    }
}

La classe Joueur

public class Joueur {
    private final String nom;
    private final List<Domino> main = new ArrayList<>();

    public Joueur(String nom) { this.nom = nom; }
    public String nom() { return nom; }
    public List<Domino> main() { return List.copyOf(main); }
    public boolean aGagne() { return main.isEmpty(); }
    public void prendre(Domino d) { main.add(d); }

    public Domino jouerAutomatique(Plateau p) {
        if (p.estVide()) {
            Domino choisi = main.remove(0);
            return choisi;
        }
        int g = p.extremiteGauche(), dr = p.extremiteDroite();
        for (Iterator<Domino> it = main.iterator(); it.hasNext(); ) {
            Domino d = it.next();
            if (d.peutSeLierA(g) || d.peutSeLierA(dr)) {
                it.remove();
                return d;
            }
        }
        return null; // doit piocher
    }
}

La boucle de jeu

public class Partie {
    public static void jouer(Joueur a, Joueur b) {
        List<Domino> pioche = new ArrayList<>(Jeu.melanger(Jeu.creerJeuComplet()));
        for (int i = 0; i < 7; i++) { a.prendre(pioche.remove(0)); b.prendre(pioche.remove(0)); }

        Plateau plateau = new Plateau();
        Joueur[] tour = { a, b };
        int i = 0, passes = 0;

        while (passes < 2) {
            Joueur courant = tour[i % 2];
            Domino d = courant.jouerAutomatique(plateau);
            if (d != null && plateau.poser(d)) {
                System.out.printf("%s pose %s → %s%n", courant.nom(), d, plateau);
                passes = 0;
                if (courant.aGagne()) {
                    System.out.println(courant.nom() + " gagne !");
                    return;
                }
            } else if (!pioche.isEmpty()) {
                courant.prendre(pioche.remove(0));
                System.out.println(courant.nom() + " pioche");
            } else {
                passes++;
                System.out.println(courant.nom() + " passe");
            }
            i++;
        }
        System.out.println("Partie bloquée, match nul.");
    }

    public static void main(String[] args) {
        jouer(new Joueur("Alice"), new Joueur("Bob"));
    }
}

Mise en page ASCII du plateau

L'affichage [3|5][5|2][2|4] est lisible mais basique. Pour quelque chose de plus proche d'une table de dominos, vous pouvez remplacer les chiffres par leur représentation en points (1 = ⚀, 2 = ⚁, 3 = ⚂, 4 = ⚃, 5 = ⚄, 6 = ⚅) :

private static final String[] FACES = { " ", "⚀", "⚁", "⚂", "⚃", "⚄", "⚅" };

private String formaterDomino(Domino d) {
    return "[" + FACES[d.gauche()] + "|" + FACES[d.droite()] + "]";
}

Améliorations possibles

  • IA plus intelligente : préférer les tuiles à forte valeur faciale pour minimiser les points résiduels en cas de blocage.
  • Affichage graphique : Swing ou JavaFX peuvent dessiner les dominos en 2D.
  • Multi-joueurs : adapter la distribution initiale (pour 3 ou 4 joueurs, on tire 6 ou 5 tuiles).
  • Sauvegarde / chargement : sérialisation JSON avec Jackson.

La force de cette modélisation tient à la séparation claire des responsabilités : Domino représente une donnée immuable, Plateau gère l'état courant, Joueur encapsule la main et la stratégie, et Partie orchestre le tout. Chaque classe tient en moins de 50 lignes et reste testable unitairement.