Aller au contenu principal

5) Combat tour par tour

Collections et combat tour par tour

Le jeu prend forme ! Dans cette séance, vous allez créer deux équipes, implémenter une boucle de combat tour par tour et trier les personnages par force grâce à un Comparator.

Objectifs de la séance

  • Utiliser ArrayList<Personnage> pour deux équipes
  • Créer un enum TypeAttaque pour catégoriser les attaques
  • Implémenter une boucle de combat while qui s'arrête quand une équipe est vaincue
  • Trier une liste de personnages par force avec Comparator

Notions théoriques

enum — type énuméré

Un enum (type énuméré) définit un ensemble fixe de constantes nommées. C'est plus sûr que d'utiliser des String ou des int pour représenter des catégories.

public enum TypeAttaque {
PHYSIQUE,
MAGIQUE,
SOIN
}

Utilisation :

TypeAttaque type = TypeAttaque.PHYSIQUE;

if (type == TypeAttaque.PHYSIQUE) {
System.out.println("Attaque physique !");
}
info

Contrairement aux String, les enums sont comparés avec == (pas besoin de .equals()). Ils sont aussi utilisables dans des switch, ce qui les rend très pratiques.

Vérifier si une équipe est encore en vie

Pour déterminer si une équipe a encore des combattants avec des PV positifs :

public static boolean equipeEnVie(ArrayList<Personnage> equipe) {
for (Personnage p : equipe) {
if (p.getPointsDeVie() > 0) {
return true;
}
}
return false;
}

Boucle de combat while

while (equipeEnVie(equipeA) && equipeEnVie(equipeB)) {
// tour de l'équipe A
for (Personnage attaquant : equipeA) {
if (attaquant.getPointsDeVie() > 0) {
Personnage cible = trouverCibleVivante(equipeB);
if (cible != null) {
attaquant.attaquer(cible);
}
}
}
// tour de l'équipe B (symétrique)
}

Trier avec Comparator

Comparator.comparingInt() crée un comparateur simple basé sur un entier :

import java.util.Comparator;

// Trier par force croissante
equipeA.sort(Comparator.comparingInt(Personnage::getForce));

// Trier par force décroissante
equipeA.sort(Comparator.comparingInt(Personnage::getForce).reversed());
info

Personnage::getForce est une référence de méthode. C'est une notation raccourcie pour p -> p.getForce(). Nous les étudierons en détail dans le cours Java Expert.

Exemple pratique

import java.util.ArrayList;
import java.util.Comparator;

public class Main {

public static void main(String[] args) {
ArrayList<Personnage> equipeA = new ArrayList<>();
equipeA.add(new Guerrier("Aragorn", 150, 30));
equipeA.add(new Mage("Gandalf", 100, 20, 50));

ArrayList<Personnage> equipeB = new ArrayList<>();
equipeB.add(new Guerrier("Orc Chef", 120, 25));
equipeB.add(new Guerrier("Orc Soldat", 80, 18));

System.out.println("=== Ordre de combat (par force) ===");
equipeA.sort(Comparator.comparingInt(Personnage::getForce).reversed());
for (Personnage p : equipeA) {
p.afficher();
}

System.out.println("\n=== Début du combat ===");
int tour = 1;
while (equipeEnVie(equipeA) && equipeEnVie(equipeB)) {
System.out.println("--- Tour " + tour + " ---");
jouerTour(equipeA, equipeB);
jouerTour(equipeB, equipeA);
tour++;
}

System.out.println("\n=== Résultat ===");
if (equipeEnVie(equipeA)) {
System.out.println("L'équipe A a gagné !");
} else {
System.out.println("L'équipe B a gagné !");
}
}

public static boolean equipeEnVie(ArrayList<Personnage> equipe) {
for (Personnage p : equipe) {
if (p.getPointsDeVie() > 0) {
return true;
}
}
return false;
}

public static void jouerTour(ArrayList<Personnage> attaquants, ArrayList<Personnage> defenseurs) {
for (Personnage attaquant : attaquants) {
if (attaquant.getPointsDeVie() > 0) {
Personnage cible = trouverCibleVivante(defenseurs);
if (cible != null) {
attaquant.attaquer(cible);
}
}
}
}

public static Personnage trouverCibleVivante(ArrayList<Personnage> equipe) {
for (Personnage p : equipe) {
if (p.getPointsDeVie() > 0) {
return p;
}
}
return null;
}
}

Test de mémorisation/compréhension


Comment compare-t-on deux valeurs d'un enum en Java ?


Que retourne `Comparator.comparingInt(Personnage::getForce).reversed()` ?


Quelle condition d'arrêt est correcte pour une boucle de combat ?


Pourquoi utiliser un enum plutôt qu'un String pour TypeAttaque ?


Que retourne la méthode trouverCibleVivante() si tous les personnages de l'équipe ont 0 PV ?


TP pour réfléchir et résoudre des problèmes

Vous allez implémenter le combat complet : deux équipes, boucle tour par tour, affichage du vainqueur.

Étape 1 — Créer l'enum TypeAttaque

Créez TypeAttaque.java avec les valeurs PHYSIQUE et MAGIQUE.


Bonne pratique - Un enum par fichier

En Java, chaque type public (classe, interface, enum) doit être dans son propre fichier. TypeAttaque.java contiendra uniquement l'enum. Cela facilite la navigation dans le projet.

Étape 2 — Implémenter la méthode equipeEnVie()

Créez une méthode statique equipeEnVie(ArrayList<Personnage> equipe) qui retourne true si au moins un personnage a des PV positifs.


Bonne pratique - Retourner tôt (early return)

Dès qu'on trouve un personnage vivant, on retourne true immédiatement sans continuer à parcourir la liste. C'est plus efficace et plus lisible que d'utiliser un booléen accumulateur.

Étape 3 — La boucle de combat principale

Écrivez la boucle while qui fait jouer alternativement les deux équipes jusqu'à ce que l'une soit vaincue.


Bonne pratique - Compter les tours

Gardez un compteur de tours. Cela permet de détecter les boucles infinies (combat qui ne se termine jamais) et d'afficher le déroulé du combat de façon lisible. En production, ajoutez une limite maximale de tours pour éviter les boucles infinies.

Étape 4 — Trier les équipes par force avant le combat

Avant de lancer le combat, triez equipeA par force décroissante pour que les plus puissants attaquent en premier.


Bonne pratique - Ne pas modifier l'ordre pendant le combat

Triez les équipes avant de lancer la boucle de combat, pas pendant. Modifier une liste pendant une boucle for-each provoque une ConcurrentModificationException. Si le tri dynamique est nécessaire, utilisez un index ou une copie de la liste.

📌 Une solution