Projet de POO

Réalisation d’un jeu vidéo 2D : UBGarden.

UBGargen

Principes du jeu

Un hérisson est perdu et désespéré. Votre mission, si vous l’acceptez, est d’aller le secourir. Pour cela, vous devrez traverser plusieurs jardins, plus fantastiques les uns que les autres.

Chaque jardin est représenté par une carte (rectangulaire) composée de cellules. Des portes vous permettront de passer de jardin en jardin. Certaines portes seront fermées à clé et nécessiteront d’avoir une clé pour les ouvrir. Attention, des frelons vivent dans ces jardins et vous êtes allergique aux piqures. Le jardinier et les frelons peuvent se déplacer de cellule en cellule, dans un espace géométrique discret. Les arbres sont des éléments de décor infranchissables. Seuls les frelons peuvent traverser les bosquets de fleurs, le jardinier ne peut pas s’y aventurer. Chaque déplacement du jardinier consomme de l’énergie, en fonction du type de sol sur lequel il marche. Ce coût de déplacement est multiplié par diseaseLevel, initialisé à 1. Les frelons peuvent voler autant qu’ils veulent sans jamais se fatiguer. Le jardinier récupère vite et gagne un point d’énergie au bout de energyRecoverDuration secondes sans bouger, dans la limite de son niveau d’énergie initial gardenerEnergy. Le niveau d’énergie actuel du jardinier est affiché dans la barre d’état.

En cas de piqure d’un frelon, vous perdez 20 points d’énergie. Les frelons ne survivent pas aux piqures qu’ils vous infligent. Heureusement, vous pouvez ramasser des bombes insecticides qui tuent les frelons. Chaque bombe peut tuer un frelon. Le nombre de bombes insecticides restant est affiché dans la barre d’état. Lorsque le jardinier n’a plus d’énergie et se fait piquer, la partie est perdue. Les frelons n’aiment pas les courants d’air et ne peuvent pas passer sur les portes.

Le jardinier ramasse automatiquement les bonus qui se trouvent sur des cellules d’herbe quand il marche dessus. Les frelons n’interagissent pas avec les bonus sauf les bombes insecticides qui les tuent lorsqu’ils marchent dessus. Si le jardinier ramasse un trognon de pomme pourrie, il devient malade pendant diseaseDuration quelques secondes et son degré de fatigue diseaseLevel augmente de 1. Le degré de fatigue actuel du jardinier (diseaseLevel) est affiché dans la barre d’état. Si le jardinier ramasse des pommes, il gagne un surplus d’énergie de energyBoost et guérit instantanément (la valeur par défaut diseaseLevel vaut 1).

Image Description Effet
Herbe Le jardinier consomme 1 * diseaseLevel points d’énergie par déplacement
Terre Le jardinier consomme 2 * diseaseLevel points d’énergie par déplacement
Carottes Le jardinier consomme 3 * diseaseLevel points d’énergie par déplacement
Pomme empoisonnée Augmente diseaseLevel de 1 pendant une durée de diseaseDuration secondes
Pomme Réinitialise diseaseLevel à 1 et augmente la quantité d’énergie du jardinier de energyBoost
Bosquet de fleurs Infranchissable pour le jardinier, franchissable pour les frelons
Bombe insecticide Permet de tuer un frelon, le jardinier peut la ramasser. La bombe disparait au bout de 10 secondes.
Nid de frelons Génère un nouveau frelon et une bombe insecticide toutes les 10 secondes. La bombe est placée sur une case d’herbe vide (sans bonus)
Frelon Frelon pas sympa. Le frelon meurt s’il passe sur la bombe d’insecticide.
Jardinier Jardinier super cool
Hérisson Hérisson apeuré
Porte fermée  
Porte ouverte  
Clé Permets d’ouvrir une porte fermée lorsque le jardinier marche dessus. Le jardinier ne peut marcher sur une porte fermée que s’il possède une clé.

Prise en main

Nous vous fournissons une première ébauche du jeu. Le lancement du jeu fait apparaître une carte minimaliste (MapLevelDefaultStart), chargée statiquement en mémoire, dans laquelle le jardinier peut se déplacer dans toutes les directions, quelle que soit la nature des cellules. Les paramètres du jeu sont définis dans un fichier de propriétés. Les fichiers properties en Java permettent de facilement stocker des couples de clés/valeurs. Les valeurs par défauts sont les suivantes.

Paramètres   Valeur par défaut
hornetMoveFrequency   1 / s
gardenerEnergy   100
energyBoost   50
energyRecoverDuration   1s
diseaseDuration   5s

Travail à fournir

Archive du jeu de base : UBGarden-student-2024.zip

Premiers pas

Dans cette première version, les frelons sont immobiles. Nous utiliserons pour le moment la carte par défaut.

Donnons vie aux frelons

Gestion des mondes

Dans la version de base, le jeu ne dispose que d’un seul niveau codé en dur dans le code. Nous allons maintenant charger une configuration complète de jeu depuis un fichier. Vous trouverez un répertoire world à la racine du projet avec deux fichiers d’extension .properties représentant un monde avec 2 niveaux (avec ou sans compression des niveaux). Nous utilisons le codage LRE vu en TD avec ou sans compression en fonction du paramètre compression dans le fichier de configuration.

Bombes insecticides

Fin de partie

La partie est finie lorsque le jardinier arrive à secourir le hérisson ou lorsqu’il meurt.

La touche [ESCAPE] permet de quitter la partie à tout moment.

Double dispatch

Lorsque le jardinier marche sur un bonus, il faut pouvoir le ramasser. Imaginons que les classes Apple et PoisonedApple soit des sous-classes de la classe Bonus. On aimerait pouvoir écrire dans le code de la classe Gardener quelque chose comme ça :

class Gardener {
    void take(Apple apple) { ... }
    void take(PoisonedApple poisonedApple) { ... }

    void doMove(Direction direction) {
        Position nextPos = direction.nextPosition(getPosition());
        Decor next = game.world().getGrid().get(nextPos);
        setPosition(nextPos);
        if (next != null)
            take(next);
    }
}

Le problème est qu’en Java, on ne peut pas faire un dispatch de méthode dynamique en fonction du type d’un paramètre. Il est possible de faire un dispatch statique (surcharge) ou dynamique (redéfinition et polymorphisme). Pour résoudre le problème, nous allons faire ce qu’on appelle de l’inversion de dépendance.

class Gardener {
    // [...]
    void doMove(Direction direction) {
        // ... 
        if (next != null)
            next.takenBy(this);
    }
}

Plutôt que le jardinier ramasse un bonus, on va demander au bonus de se faire ramasser par le jardinier. Avec le dispatch dynamique, c’est la bonne méthode takenBy qui sera invoquée. Il faut ensuite invoquer dans chacune des classes Apple et PoisonedApple la bonne méthode de ramassage du jardinier. Il s’agit dans ce cas de dispatch statique.

public interface Takeable {
    default void takenBy(Gardener Gardener) {}
}

public abstract class Bonus implements Takeable {}

public class Apple extends Bonus { 
    public void takenBy(Gardener Gardener) {
        Gardener.take(this);
    }
}

public class PoisonedApple extends Bonus { 
    public void takenBy(Gardener Gardener) {
        Gardener.take(this);
    }
 }