Aller au contenu

Donjons et Dragons

Ce projet sert de pratique pour l'orienté objet, à savoir les classes, l'héritage, les interfaces et les classes abstraites. Nous allons donc simuler des personnages et des objets dans une partie de Donjons et Dragons et implémenter le tout en orienté objet.

Rien ne sert de tout planifier dès le début, des ajustements seront faits en cours de route.

Partie 1 - Classes

Débutons par implanter une classe pour les personnages. Les personnages devront avoir les attributs de base (force, dextérité, constitution, intelligence, sagesse et charisme), un nom, une classe, des points de vies et des points d'expérience.

Lorsqu'instancié, un personnage doit recevoir un nom et une classe. Une nouvelle instance peut également recevoir tous les attributs de base ou aucun. S'il n'en reçoit aucun, un nombre aléatoire entre 12 et 20 sera alors attribué.

Chaque personnage débute avec 40 points de vies.

À faire

  • Créer une classe Personnage
    • Permettre d'instancier avec un nom et une classe
    • Permettre d'instancier avec un nom, une classe et les 6 attributs de base
    • Remplacer la méthode toString() pour afficher de l'information pertinence à propos de l'instance
    • Ajouter des getters pour les statistiques, le nom et la classe
    • Créer une méthode int gagnerExperience(int xp) qui ajoute la quantité d'expérience, affiche un message et retourne la nouvelle quantité d'expérience
    • Créer une méthode int modifierVies(int vies) qui ajoute/supprime les points de vies, affiche un message et retourne la nouvelle quantité de points de vies. Attention, on peut gagner ou perdre des points de vies!
  • Tester le jeu
    • Créer deux personnages, chacun avec un constructeur différent, et les ajouter dans un ArrayList<Personnage>
    • Modifier les vies et les points d'expérience pour chacun des personnages
    • Afficher les personnages à l'aide d'une boucle for (Personnage p : personnages)

Solution

Solution
import java.util.ArrayList;

public class Jeu {

    static void main() {
        ArrayList<Personnage> personnages = new ArrayList<>();

        // Les personnages
        Personnage sorcier = new Personnage("Melchior", "Sorcier");
        Personnage guerrier = new Personnage("Therion", "Guerrier", 20, 19, 18, 10, 10, 12);
        personnages.add(sorcier);
        personnages.add(guerrier);

        // Faire des actions
        guerrier.gagnerExperience(5);
        sorcier.modifierVies(-3);
        guerrier.modifierVies(7);

        // Afficher les personnages
        for (Personnage p : personnages) {
            System.out.println(p);
        }
    }

}
import java.util.Random;

public class Personnage {

    // Nom et classe
    private String nom;
    private String classe;

    // Les statistiques
    private int force;
    private int dexterite;
    private int constitution;
    private int intelligence;
    private int sagesse;
    private int charisme;
    private int experience = 0;
    private int vies = 40;

    public Personnage(String nom, String classe, int force, int dexterite, int constitution, int intelligence, int sagesse, int charisme) {
        this.nom = nom;
        this.classe = classe;
        this.force = force;
        this.dexterite = dexterite;
        this.constitution = constitution;
        this.intelligence = intelligence;
        this.sagesse = sagesse;
        this.charisme = charisme;
    }

    public Personnage(String nom, String classe) {
        this.nom = nom;
        this.classe = classe;

        // Générer les statistiques de manière aléatoire
        Random random = new Random();
        this(nom, classe, random.nextInt(12, 20),random.nextInt(12, 20),random.nextInt(12, 20),random.nextInt(12, 20),random.nextInt(12, 20),random.nextInt(12, 20));
    }

    public int gagnerExperience(int xp) {
        System.out.printf("** %s gagne %d points d'expérience%n", this.nom, xp);
        this.experience += xp;
        return this.experience;
    }

    public int modifierVies(int vies) {
        if (vies > 0) {
            System.out.printf("** %s gagne %d points de vies%n", this.nom, vies);
        } else if (vies < 0) {
            System.out.printf("** %s perd %d points de vies%n", this.nom, vies*-1);
        }
        this.vies += vies;
        return this.vies;
    }

    public String getNom() {
        return nom;
    }

    public String getClasse() {
        return classe;
    }

    public int getForce() {
        return force;
    }

    public int getDexterite() {
        return dexterite;
    }

    public int getConstitution() {
        return constitution;
    }

    public int getIntelligence() {
        return intelligence;
    }

    public int getSagesse() {
        return sagesse;
    }

    public int getCharisme() {
        return charisme;
    }

    @Override
    public String toString() {
        return String.format("%s (%s - %d xp) : %d vies",
                this.nom, this.classe, this.experience, this.vies);
    }

}
** Therion gagne 5 points d'expérience
** Melchior perd 3 points de vies
** Therion gagne 7 points de vies
Melchior (Sorcier - 0 xp) : 37 vies
Therion (Guerrier - 5 xp) : 47 vies

Partie 2 - Héritage

Étant donné que Donjons et Dragons possède une quantité définie de classe de personnages, il pourrait être intéressant de les représenter sous forme d'objet qui hériterons de la classe Personnage.

  • Le sorcier est un personnage. En plus des actions d'un personnage, il pourra également lancer un sort.
  • L'archer est aussi un personnage. En plus des actions d'un personnage, il pourra également tirer des flèches.
  • Finalement, le guerrier, en plus des actions d'un personnage, gagne 2 fois plus d'expérience que les autres.

À faire

  • Créer les nouvelles classes
    • Créer une classe Sorcier et ajouter la méthode void lancerSort(String sort) qui affiche un message.
    • Créer une classe Archer et ajouter la méthode void tirerFleches() qui affiche un message.
    • Créer une classe Guerrier et remplacer la méthode int gagnerExperience(int xp) pour ajuster son gain d'expérience.
  • Tester les nouvelles classes
    • Créer une instance pour le sorcier, l'archer et le guerrier et les ajouter dans le ArrayList<Personnage>.
    • Tester les méthodes lancerSort() et tirerFleches()
    • Faire gagner 5 points d'expérience à tous à l'aide d'une boucle for

Solution

Solution

Aucun changement

public class Sorcier extends Personnage {

    public Sorcier(String nom, int force, int dexterite, int constitution, int intelligence, int sagesse, int charisme) {
        super(nom, "Sorcier", force, dexterite, constitution, intelligence, sagesse, charisme);
    }

    public Sorcier(String nom) {
        super(nom, "Sorcier");
    }

    public void lancerSort(String sort) {
        System.out.printf("** Le sorcier %s lance le sort %s%n", this.getNom(), sort);
    }
}
public class Archer extends Personnage {
    public Archer(String nom, int force, int dexterite, int constitution, int intelligence, int sagesse, int charisme) {
        super(nom, "Archer", force, dexterite, constitution, intelligence, sagesse, charisme);
    }

    public Archer(String nom) {
        super(nom, "Archer");
    }

    public void tirerFleche() {
        System.out.printf("** L'archer %s tire une flèche%n", this.getNom());
    }
}
public class Guerrier extends Personnage {
    public Guerrier(String nom, int force, int dexterite, int constitution, int intelligence, int sagesse, int charisme) {
        super(nom, "Guerrier", force, dexterite, constitution, intelligence, sagesse, charisme);
    }

    public Guerrier(String nom) {
        super(nom, "Guerrier");
    }

    @Override
    public int gagnerExperience(int xp) {
        return super.gagnerExperience(xp * 2);
    }
}
import java.util.ArrayList;

public class Jeu {

    static void main() {
        ArrayList<Personnage> personnages = new ArrayList<>();

        // Les personnages
        Sorcier sorcier = new Sorcier("Melchior");
        Archer archer = new Archer("Kellan");
        Guerrier guerrier = new Guerrier("Therion");
        personnages.add(sorcier);
        personnages.add(archer);
        personnages.add(guerrier);

        // Faire des actions
        sorcier.lancerSort("Malédiction");
        archer.tirerFleche();

        // Gagner l'expérience
        for (Personnage p : personnages) {
            p.gagnerExperience(5);
        }

        // Afficher les personnages
        for (Personnage p : personnages) {
            System.out.println(p);
        }
    }

}
** Le sorcier Melchior lance le sort Malédiction
** L'archer Kellan tire une flèche
** Melchior gagne 5 points d'expérience
** Kellan gagne 5 points d'expérience
** Therion gagne 10 points d'expérience
Melchior (Sorcier - 5 xp) : 40 vies
Kellan (Archer - 5 xp) : 40 vies
Therion (Guerrier - 10 xp) : 40 vies

Partie 3 - Interfaces

Maintenant, nous devons ajouter des objets à détruire : une boîte. Une boîte n'est pas un personnage mais aura également des points de vies et pourra être détruite, ce que les personnages et la boîte ont en commun. Une interface sera donc un ajout idéal pour les points de vies. Finalement, on pourra ajuster la classe Personnage pour ajuster le tout et tester notre jeu.

À faire

  • Créer une classe IDestructible qui forcera l'implémentation de deux méthodes :
    • int getPointsDeVies(); --> Obtenir les points de vies de l'objet
    • int modifierVies(int vies); --> Modifier les points de vies de l'objet
  • Créer une classe Boite qui implémente l'interface, lui attribuer 10 points de vie et coder la modification de points de vies
  • Ajouter tous les personnages et la boîte dans un nouvel ArrayList<IDestructible>
  • Ajuster la classe Personnage pour implémenter l'interface.
  • Étant donné que les personnages ont (habituellement) des équipements, ajuster la méthode modifierVies() afin de perdre 1 point de vie de moins en tout temps
  • Ajuster la classe Guerrier afin qu'il perde 2 points de vie de moins en tout temps
  • Tester le jeu en ajoutant les actions suivantes :
    • Un malus fait perdre 3 points de vie à tout ce qui est destructible sur le plateau de jeu. Effectuer l'action à l'aide d'une boucle for (IDestructible destructible : destructibles)
    • Un sort lancé fait perdre 3 points de vie à tout ce qui est destructible et qui n'est pas un personnage. Effectuer également à l'aide d'une boucle, d'une condition et de l'instruction instanceof

Solution

Solution
public interface IDestructible {

    public int getPointsDeVies();
    public int modifierVies(int vies);

}
public class Boite implements IDestructible {
    private int vies = 10;

    @Override
    public int getPointsDeVies() {
        return this.vies;
    }

    @Override
    public int modifierVies(int vies) {
        this.vies += vies;
        if (this.vies <= 0) {
            System.out.println("** La boite est détruite");
        } else {
            System.out.printf("** La boite a encore %d points de vies%n", this.vies);
        }
        return 0;
    }
}
import java.util.Random;

public class Personnage implements IDestructible {
    ...

    @Override
    public int getPointsDeVies() {
        return this.vies;
    }

    @Override
    public int modifierVies(int vies) {
        if (vies > 0) {
            System.out.printf("** %s gagne %d points de vies%n", this.nom, vies);
        } else if (vies < 0) {
            // Perdre un point de vie de moins
            vies = vies + 1;
            System.out.printf("** %s perd %d points de vies%n", this.nom, vies*-1);
        }
        this.vies += vies;
        return this.vies;
    }

    ...
}
public class Guerrier extends Personnage {
    ...

    @Override
    public int modifierVies(int vies) {
        if (vies < 0) {
            // Perdre 1 point de vie de moins
            return super.modifierVies(vies + 1);
        }
        return super.modifierVies(vies);
    }

    ...
}
import java.util.ArrayList;

public class Jeu {

    static void main() {
        ArrayList<Personnage> personnages = new ArrayList<>();
        ArrayList<IDestructible> destructibles = new ArrayList<>();

        // Les personnages
        Sorcier sorcier = new Sorcier("Melchior");
        Archer archer = new Archer("Kellan");
        Guerrier guerrier = new Guerrier("Therion");
        personnages.add(sorcier);
        personnages.add(archer);
        personnages.add(guerrier);
        destructibles.addAll(personnages);

        // Une boite
        Boite boite = new Boite();
        destructibles.add(boite);

        // Faire des actions
        sorcier.lancerSort("Malédiction");
        archer.tirerFleche();

        // Gagner l'expérience
        for (Personnage p : personnages) {
            p.gagnerExperience(5);
        }

        // Un malus inflige 3 dégâts à tous
        System.out.println("MALUS");
        for (IDestructible destructible : destructibles) {
            destructible.modifierVies(-3);
        }

        // Un sort de feu
        System.out.println("SORT DE FEU");
        for (IDestructible destructible : destructibles) {
            if (!(destructible instanceof Personnage)) {
                destructible.modifierVies(-10);
            }
        }

        // Afficher les personnages
        for (Personnage p : personnages) {
            System.out.println(p);
        }
    }

}
** Le sorcier Melchior lance le sort Malédiction
** L'archer Kellan tire une flèche
** Melchior gagne 5 points d'expérience
** Kellan gagne 5 points d'expérience
** Therion gagne 10 points d'expérience
MALUS
** Melchior perd 2 points de vies
** Kellan perd 2 points de vies
** Therion perd 1 points de vies
** La boite a encore 7 points de vies
SORT DE FEU
** La boite est détruite
Melchior (Sorcier - 5 xp) : 38 vies
Kellan (Archer - 5 xp) : 38 vies
Therion (Guerrier - 10 xp) : 39 vies

Partie 4 - Classe abstraite

Finalement, nous allons ajouter des ennemis avec une classe abstraite (abstract). Cette classe sera incomplète et il faudra la compléter à chaque nouvelle implémentation d'un ennemi. Comme un ennemi est aussi destructible, il devra implémenter l'interface IDestructible.

Un ennemi possède un nom et des points de vies. Il pourra aussi attaquer un personnage grace à une valeur d'attaque, à spécifier.

À faire

  • Créer une classe abstraite Ennemi qui implémente IDestructible.
  • Remplacer la méthode toString() pour un message efficace.
  • Créer le constructeur qui accepte un nom et des points de vies de départ.
  • Créer une méthode abstraite int getValeurAttaque(), qui devra être implémentée par les classes enfants
  • Créer une méthode void attaquer(Personnage p) qui attaque le personnage p en utilisant la méthode getValeurAttaque()
  • Créer une classe Goblin qui hérite de la classe Ennemi. De plus, il devra remplacer certaines méthodes :
    • getValeurAttaque() : Le goblin possède 10 points de vies et effectue 2 dégats
    • modifierVies() : Le goblin subit 2 fois les dégats habituels
  • Modifier le jeu pour :
    • Créer un goblin et l'ajouter dans la liste destructibles
    • Créer un nouvel ennemi "Mort-Vivant" directement dans le jeu (Ennemi mortVivant = new Ennemi(...)) et implémenter la méthode abstraite. Le mort-vivant possède 7 points de vies et attaque de 2 fois ses points de vies
    • Tous les ennemis doivent attaquer l'archer. Utiliser une boucle for et vérifier pour l'instance Ennemi.

Solution

Solution
public abstract class Ennemi implements IDestructible {
    private String nom;
    protected int vies;

    public abstract int getValeurAttaque();

    public Ennemi(String nom, int vies) {
        this.nom = nom;
        this.vies = vies;
    }

    @Override
    public int getPointsDeVies() {
        return this.vies;
    }

    @Override
    public int modifierVies(int vies) {
        System.out.printf("** L'ennemi %s perd %d vies%n", this.nom, -vies);
        return this.vies += vies;
    }

    public void attaquer(Personnage p) {
        System.out.printf("** %s attaque %s de %d%n", this.nom, p.getNom(), this.getValeurAttaque());
    }

    @Override
    public String toString() {
        return "Ennemi (%s) : %d".formatted(this.nom, this.getPointsDeVies());
    }
}
public class Goblin extends Ennemi {

    public Goblin() {
        super("Goblin", 10);
    }

    @Override
    public int getValeurAttaque() {
        return 2;
    }

    @Override
    public int modifierVies(int vies) {
        int doubleVies = vies * 2;
        System.out.printf("** Le goblin perd le double : %d%n", -doubleVies);
        return this.vies += -doubleVies;
    }
}
import java.util.ArrayList;

public class Jeu {

    static void main() {

        ...

        // Une boite
        Boite boite = new Boite();
        destructibles.add(boite);

        // Les ennemis
        Goblin goblin = new Goblin();
        Ennemi mortVivant = new Ennemi("Mort-Vivant", 7) {
            @Override
            public int getValeurAttaque() {
                return 2 * this.getPointsDeVies();
            }
        };
        destructibles.add(goblin);
        destructibles.add(mortVivant);

        // Les ennemis attaquent l'archer
        for (IDestructible destructible : destructibles) {
            if (destructible instanceof Ennemi) {
                ((Ennemi) destructible).attaquer(archer);
            }
        }

        ...

}
** Le goblin fait 2 dégats à Kellan
** Kellan perd 1 points de vies
** Mort-Vivant attaque Kellan de 14
** Le sorcier Melchior lance le sort Malédiction
** L'archer Kellan tire une flèche
** Melchior gagne 5 points d'expérience
** Kellan gagne 5 points d'expérience
** Therion gagne 10 points d'expérience
MALUS
** Melchior perd 2 points de vies
** Kellan perd 2 points de vies
** Therion perd 1 points de vies
** La boite a encore 7 points de vies
** Le goblin perd le double : 6
** L'ennemi Mort-Vivant perd 3 vies
SORT DE FEU
** La boite est détruite
** Le goblin perd le double : 20
** L'ennemi Mort-Vivant perd 10 vies
Melchior (Sorcier - 5 xp) : 38 vies
Kellan (Archer - 5 xp) : 37 vies
Therion (Guerrier - 10 xp) : 39 vies