CSC 3101 – Algorithmique et langage de programmation

Portail informatique

Contrôle Final 1 – Année 2020/2021

Le barème est donné à titre indicatif
  • Durée du contrôle : 2h
  • Tous les documents (papier et électronique) sont autorisés
  • On vous demande de rendre vos devoirs via moodle à l'adresse https://moodle.imtbs-tsp.eu/mod/assign/view.php?id=54441. On vous demande de remettre soit une archive contenant au moins les fichiers Java, soit directement vos fichiers Java.
  • Vous devez être connecté pendant toute la durée de l'examen à votre salle BBB que vous pouvez trouver à cette adresse https://moodle.imtbs-tsp.eu/course/view.php?id=1568.
  • Si vous avez des questions, vous pouvez trouver des professeurs du module dans ce salon : https://webconf.imt.fr/frontend/gae-1ms-xpn-sy9. Avant de commencer à discuter de vive voix dans ce salon, pensez à prévenir le professeur qui surveille votre salle de façon à éviter qu'il vous soupçonne de fraude.
On vous rappelle que le contrôle est à réaliser seul et que le fait de tricher entraînera des sanctions.

Pendant ce contrôle, nous allons mettre en œuvre le jeu de société "Les Loups-garous de Thiercelieux". Le principe du jeu est assez simple. Chaque joueur possède un rôle. Les rôles séparent les joueurs en deux camps : le camp des loups-garous et le camp des villageois. Le but, pour les loups-garous, est de tuer tous les villageois pendant la nuit. Le but, pour les villageois, est de démasquer les loups-garous en journée. Pour cela, les villageois discutent et désignent un loup-garou avec un vote. Là où la vie des villageois se complique, c'est que les loups-garous, en journée, se comportent comme tous les villageois : ils participent aux discussions et votent comme les autres villageois. Heureusement, les villageois peuvent avoir dans leurs rangs des personnages spéciaux qui peuvent les aider :

  • La petite fille peut observer les loups-garous pendant la nuit. La difficulté, pour la petite fille, c'est que les loups-garous peuvent voir que la petite fille les observe et peuvent, à ce moment, la manger.
  • La voyante peut, au début de chaque nuit, regarder la carte d'un autre joueur. Elle peut donc savoir assez rapidement qui sont les loups-garous, mais elle doit rester discrète pour ne pas se faire dévorer par les loups-garous.

Il existe d'autres rôles dans le jeu, mais, pour un contrôle, construire les quatre rôles de base (loup-garou, villageois, petite fille et voyante) est largement suffisant. Pendant le contrôle, nous allons mettre en œuvre le moteur de jeu, mais, faute de temps, nous n'allons pas permettre aux joueurs d'effectuer leurs actions. Le but est donc uniquement de :

  • Définir les rôles,
  • Permettre à chaque rôle d'effectuer une action à un moment précis de la journée (par exemple, les loups-garous désignent une victime pendant la nuit, la voyante peut apprendre le rôle d'un joueur au début de la nuit avant que les loups-garous ne frappent etc.),
  • Exécuter un tour de jeu (une journée complète) en s'assurant que chaque rôle est bien capable d'exécuter son action au bon moment.

Modélisation d'un tour de jeu (4 points)

Un tour de jeu consiste en une journée. Une journée est séparée en quatre étapes distinctes :

  • Étape 0 : le village s'endort. Aucun joueur n'effectue d'action,
  • Étape 1 : la voyante se réveille et peut connaître le rôle de l'un des joueurs,
  • Étape 2 : les loups-garous se réveillent et désignent une victime. La victime est éliminée du jeu. Pendant que les loups-garous choisissent une victime, la petite fille peut tenter de les observer,
  • Étape 3 : tous les habitants du village se réveillent et, après avoir discuté ensemble, désignent un joueur à éliminer.

Mise en route (1 point)

Dans le package loupsgarous, créez une classe Engine représentant le moteur de jeu. Cette classe doit posséder une méthode main qui doit afficher :

---- démarrage du jeu ----
Dans la suite de l'exercice, vous devez créer toutes vos classes dans ce package loupsgarous.

La méthode void play() (1,5 point)

Ajoutez un constructeur vide à Engine et une méthode d'instance vide void play() à la classe Engine. Nous compléterons ces méthodes plus tard dans l'exercice. Dans la méthode main, allouez une instance de Engine et appelez la méthode play de cette instance.

Les étapes d'une journée (1 point)

Dans la méthode play() créez un tableau de références vers les quatre chaînes de caractères suivantes (pensez à copier/coller les phrases) :

  • "Le village s'endort",
  • "La voyante se réveille et désigne un joueur dont elle veut découvrir le rôle",
  • "Les loups-garous se réveillent, se reconnaissent et désignent une nouvelle victime",
  • "C'est le matin, le village se réveille et ouvre les yeux"

Ce tableau permet de savoir à quelle étape de la journée nous sommes.

Exécution d'une journée (0,5 point)

Dans la méthode play, ajoutez une boucle permettant d'afficher l'étape actuelle et la chaîne de caractères associée se trouvant dans le tableau créé à la question précédente. Si votre programme est correct, il devrait afficher :

---- démarrage du jeu ---- Étape 0 : Le village s'endort Étape 1 : La voyante se réveille et désigne un joueur dont elle veut découvrir le rôle Étape 2 : Les loups-garous se réveillent, se reconnaissent et désignent une nouvelle victime Étape 3 : C'est le matin, le village se réveille et ouvre les yeux

Les joueurs et les rôles (8 points)

À cet exercice, nous définissions les rôles. Pour cela, nous allons profiter du fait que Java est un langage orienté objet et offre un mécanisme d'héritage.

La classe Role (2 point)

Mettez en œuvre une classe Role. Cette classe permet de factoriser le code commun à chacun des rôles. Elle sera donc spécialisée par héritage pour chacun des quatre rôles dans les questions suivantes. Pour le moment, cette classe doit posséder :

  • Un champ engine de type Engine permettant de retrouver le moteur de jeu,
  • Un constructeur prenant en paramètre le moteur de jeu et initialisant le champ engine,
  • Une méthode void execute(int i, String player). Cette méthode sera appelée pour chaque joueur à chaque étape de la journée. Le paramètre i indique l'étape de la journée et le paramètre player donne le nom du joueur jouant le rôle de this.

À cette question, la méthode execute doit simplement afficher " PLAYER joue l'étape I", où PLAYER doit être remplacé par le paramètre player et I doit être remplacé par le paramètre i.

Les quatre classes modélisant les rôles (1 point)

Nous pouvons maintenant mettre en œuvre les quatre classes modélisant les quatre rôles. Créez les quatre classes FortuneTeller (voyante), LittleGirl (petite fille), WereWolf (loup-garou) et Peasant (villageois - il s'avère qu'un villagoeois est un paysan dans les pays anglophones). Chacune de ces classes doit hériter de Role et doit posséder un constructeur prenant en argument une référence vers un objet de type Engine utilisé pour invoquer le constructeur parent.

Modélisation des joueurs (2 points)

Nous pouvons maintenant modéliser les joueurs. Pour modéliser les joueurs, nous allons utiliser une table de hachage (java.util.HashMap<K, V>) nommée roles associant des noms de joueurs (des chaînes de caractères) et des rôles.

Dans la classe Engine, ajoutez le champ roles. Dans le constructeur de Engine, initialisez ce champ, et copiez/collez le code suivant permettant d'associer quelques joueurs avec quelques rôles :

roles.put("Bulbizarre", new Peasant(this)); roles.put("Pikachu", new Peasant(this)); roles.put("Carapuce", new Peasant(this)); roles.put("Pixel", new FortuneTeller(this)); roles.put("Tyrion", new WereWolf(this)); roles.put("Cersei", new WereWolf(this)); roles.put("Grogu", new LittleGirl(this));

Exécution des actions associées aux rôles (3 points)

Dans la méthode play, à chaque étape de la journée (c'est-à-dire à chaque tour de la boucle qui itère sur les messages affichés pendant la journée), appelez la méthode execute associée à chaque joueur. Pour cela, vous devez savoir que la classe HashMap<K, V> possède :

  • une méthode Collection<K> keySet() renvoyant l'ensemble des clés sous la forme d'une collection,
  • une méthode V get(K key) renvoyant la valeur associée à la clé key.

Si votre programme est correct, il devrait générer l'affichage suivant (l'ordre d'apparition des joueurs n'est pas significatif) :

---- démarrage du jeu ---- Étape 0 : Le village s'endort Pikachu joue l'étape 0 Bulbizar joue l'étape 0 Carapuce joue l'étape 0 Grogu joue l'étape 0 Pixel joue l'étape 0 Tyrion joue l'étape 0 Cersei joue l'étape 0 Étape 1 : La voyante se réveille et désigne un joueur dont elle veut découvrir le rôle Pikachu joue l'étape 1 Bulbizar joue l'étape 1 Carapuce joue l'étape 1 Grogu joue l'étape 1 Pixel joue l'étape 1 Tyrion joue l'étape 1 Cersei joue l'étape 1 Étape 2 : Les loups-garous se réveillent, se reconnaissent et désignent une nouvelle victime Pikachu joue l'étape 2 Bulbizar joue l'étape 2 Carapuce joue l'étape 2 Grogu joue l'étape 2 Pixel joue l'étape 2 Tyrion joue l'étape 2 Cersei joue l'étape 2 Étape 3 : C'est le matin, le village se réveille et ouvre les yeux Pikachu joue l'étape 3 Bulbizar joue l'étape 3 Carapuce joue l'étape 3 Grogu joue l'étape 3 Pixel joue l'étape 3 Tyrion joue l'étape 3 Cersei joue l'étape 3

Les comportements associés aux rôles (8 points)

Maintenant que nous avons défini les joueurs et les rôles, nous pouvons associer des comportements aux rôles. Un comportement représente une action possible à une étape donnée. Pour illustrer, à la fin de l'exercice, le comportement PeasantBehavior permettra à tous les rôles de se comporter comme un villageois à l'étape 3, et le comportement LittleGirlBehavior permettra à la petite fille d'observer les loups-garous à l'étape 2.

L'interface Behavior (0,5 point)

Fondamentalement, un comportement permet à un joueur d'exécuter une action à une étape donnée. C'est donc un objet mettant en œuvre une interface Behavior avec une unique méthode void execute(Engine engine, String player). Cette méthode permet au joueur player d'exécuter l'action associée au comportement à une étape donnée de la journée. Le paramètre engine permet au joueur de retrouver les autres joueurs participants de façon à pouvoir interagir avec eux (que ce soit pour les éliminer ou discuter).

Donnez le code de l'interface Behavior.

Les comportements (1 point)

Écrivez les classes mettant en œuvre les différents comportements possibles. En détail :

  • Pour la classe FortuneTellerBehavior, vous devez afficher " PLAYER regarde la carte de l'un des joueurs",
  • Pour la classe LittleGirlBehavior, vous devez afficher " PLAYER observe les loups garous discrètement",
  • Pour la classe PeasantBehavior, vous devez afficher " PLAYER discute avec les autres joueurs pour désigner un loup garou",
  • Pour la classe WereWolfBehavior, vous devez afficher " PLAYER désigne une victime".

Pour chacun des affichages, vous devez, bien sûr, remplacer PLAYER par le nom d'un joueur.

La liste des comportements (1 point)

Nous pouvons maintenant commencer à associer des comportements aux rôles. Pour cela, nous commençons par ajouter un champ nommé behaviors de type java.util.List<E> à la classe Role. L'interface java.util.List<E> représente une collection dans laquelle les éléments sont indexés par un numéro. Cette interface généralise donc la notion de tableau. Elle hérite de java.util.Collection<E> et ajoute les méthodes suivantes qui vont nous servir dans la suite :

  • void set(int index, E e) : remplace l'élément à la position index par e. Si la liste est trop petite, la méthode lève une exception IndexOutOfBoundsException.
  • E get(int index) : renvoie l'élément à la position index et lève une exception IndexOutOfBoundsException si la liste est trop petite.

Outre ces deux méthodes, comme java.util.List<E> hérite de java.util.Collection<E>, une liste offre aussi les méthodes suivantes qui vont nous servir dans la suite  :

  • void add(E e) : ajoute l'élément e à la fin de la liste après avoir étendu la liste,
  • int size() : renvoie la taille de la liste.

Ajoutez le champ behaviors à Role et initialisez ce champ dans le constructeur. Il faut savoir que ArrayList met en œuvre l'interface List.

Association d'un comportement à un rôle (2 points)

On peut maintenant associer des comportements à des rôles. Ajoutez à la classe Role une méthode attach(int step, Behavior b) permettant de placer à la position step de behaviors le comportement b. Pensez que vous pouvez étendre la taille de la liste en ajoutant (méthode add) des références null.

Création des comportements (1 point)

On va maintenant utiliser attach pour attacher les comportements aux rôles. En détails, il faut :

  • Dans le constructeur de Peasant, associer le comportement PeasantBehavior à l'étape 3,
  • Dans le constructeur de WereWolf, associer le comportement WereWolfBehavior à l'étape 2 et le comportement PeasantBehavior à l'étape 3,
  • Dans le constructeur de LittleGirl, associer le comportement LittleGirlBehavior à l'étape 2 et le comportement PeasantBehavior à l'étape 3,
  • Dans le constructeur de FortuneTeller, associer le comportement FortuneTellerBehavior à l'étape 1 et le comportement PeasantBehavior à l'étape 3.

Finalisation (2,5 points)

Modifiez la méthode execute de Role de façon à exécuter les comportements adéquats. Si votre programme est correct, vous devriez obtenir la sortie suivante (l'ordre d'exécution des personnages à chaque étape n'est pas significative) :

---- démarrage du jeu ---- Étape 0 : Le village s'endort Pikachu joue l'étape 0 Bulbizar joue l'étape 0 Carapuce joue l'étape 0 Grogu joue l'étape 0 Pixel joue l'étape 0 Tyrion joue l'étape 0 Cersei joue l'étape 0 Étape 1 : La voyante se réveille et désigne un joueur dont elle veut découvrir le rôle Pikachu joue l'étape 1 Bulbizar joue l'étape 1 Carapuce joue l'étape 1 Grogu joue l'étape 1 Pixel joue l'étape 1 Pixel regarde la carte de l'un des joueurs Tyrion joue l'étape 1 Cersei joue l'étape 1 Étape 2 : Les loups-garous se réveillent, se reconnaissent et désignent une nouvelle victime Pikachu joue l'étape 2 Bulbizar joue l'étape 2 Carapuce joue l'étape 2 Grogu joue l'étape 2 Grogu observe les loups garous discrètement Pixel joue l'étape 2 Tyrion joue l'étape 2 Tyrion désigne une victime Cersei joue l'étape 2 Cersei désigne une victime Étape 3 : C'est le matin, le village se réveille et ouvre les yeux Pikachu joue l'étape 3 Pikachu discute avec les autres joueurs pour désigner un loup garou Bulbizar joue l'étape 3 Bulbizar discute avec les autres joueurs pour désigner un loup garou Carapuce joue l'étape 3 Carapuce discute avec les autres joueurs pour désigner un loup garou Grogu joue l'étape 3 Grogu discute avec les autres joueurs pour désigner un loup garou Pixel joue l'étape 3 Pixel discute avec les autres joueurs pour désigner un loup garou Tyrion joue l'étape 3 Tyrion discute avec les autres joueurs pour désigner un loup garou Cersei joue l'étape 3 Cersei discute avec les autres joueurs pour désigner un loup garou