IFT1015

Hiver 2008

Professeur: Pascal Vincent

TP1 : Labyrinthe Invisible

A remettre le vendredi 28 mars avant minuit. 

À faire en équipe de 2.


Principe du jeu

Le jeu de labyrinthe invisible est un jeu de mémoire qui fonctionne selon le principe suivant:

Au début du jeu, l'ordinateur crée un labyrinthe au hazard. Il choisit un point de départ pour votre personnage (représenté par @) au hazard dans l'extrème gauche du labyrinthe, et une porte de sortie du Labyrinthe, au hazard dans l'extrème droite du labyrinthe. 

Il vous affiche ce labyrinthe pendant quelques secondes à l'écran. Ex pour un petit labyrinthe de 5x5 cases:

+---------------------------------------+
| | | | |
| | | | |
| | | | |
| |
| | | | |
| @ | | | |
| | | | |
| ------- |
| |
| |
| |
| ------- ------- |
| | |
| | |
| | |
| ------- |
| |
| |
| |
+---------------------------------------+

Au bout de ces quelques secondes, tous les murets intérieurs du labyrinthe deviennent invisibles, ce qui donnera, pour notre exemple, l'affichage ci-dessous:

+---------------------------------------+
| |
| |
| |
| |
| |
| @ |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
+---------------------------------------+
Il vous reste 5 vies sur 5.

Quelle direction souhaitez-vous prendre?
(droite: d; gauche: g ou s; haut: h ou e; bas: b ou x)

En plus l'ordinateur affiche combien de vies il reste à votre personnage.

C'est alors à vous de guider votre personnage vers la sortie au travers du labyrinthe aux murs invisibles (et électriques), en choisissant de vous déplacer à chaque fois d'une case vers le haut, bas, droite ou gauche. 

A chaque fois que vous choisissez une direction qui vous amène à toucher un muret, le muret devient visible de façon permanente, mais vous êtes électrocutés, et vous perdez une vie.

Ainsi, dans notre exemple, si on choisissait d'aller à droite (d), on obtiendrait l'affichage suivant (notez que votre personnage n'a pas changé de case, puisque son mouvement a été bloqué par un muret. Notez aussi que le nombre de vies est passé à 4):

+---------------------------------------+
| |
| |
| |
| |
| | |
| @ | |
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
+---------------------------------------+
Il vous reste 4 vies sur 5.

Quelle direction souhaitez-vous prendre?
(droite: d; gauche: g ou s; haut: h ou e; bas: b ou x)

Si vous allez ensuite dans la direction bas vous obtiendrez l'affichage suivant:

+---------------------------------------+
| |
| |
| |
| |
| | |
| | |
| | |
| |
| |
| @ |
| |
| |
| |
| |
| |
| |
|
|
|
+---------------------------------------+
Il vous reste 4 vies sur 5.

Quelle direction souhaitez-vous prendre?
(droite: d; gauche: g ou s; haut: h ou e; bas: b ou x)

Si vous continuez avec bas, droite, droite, comme il n'y a aucun muret sur ce chemin, vous devriez voir l'affichage suivant:

+---------------------------------------+
| |
| |
| |
| |
| | |
| | |
| | |
| |
| |
| |
| |
| |
| |
| @ |
| |
| |
|
|
|
+---------------------------------------+
Il vous reste 4 vies sur 5.

Quelle direction souhaitez-vous prendre?
(droite: d; gauche: g ou s; haut: h ou e; bas: b ou x)

Si de ce point, vous essayez d'aller à droite puis vers le haut, comme il y a un muret à droite et aussi au dessus, vous perdrez 2 vies supplémentaires et votre plateau de jeu apparaîtra comme:


+---------------------------------------+
| |
| |
| |
| |
| | |
| | |
| | |
| |
| |
| |
| |
| ------- |
| | |
| @ | |
| | |
| |
|
|
|
+---------------------------------------+
Il vous reste 2 vies sur 5.

Quelle direction souhaitez-vous prendre?
(droite: d; gauche: g ou s; haut: h ou e; bas: b ou x)

Si votre personnage épuise toutes ses vies (si il reste 0 vies) alors vous avez perdu la partie, et l'ordinateur devra afficher le message suivant.

Vous avez perdu, vous avez épuisé vos 5 vies!

Si votre personnage parvient jusqu'à la sortie, l'ordinateur devra afficher:

Bravo, vous êtes parvenu jusqu'à la sortie en comettant seulement 3 erreurs.

Dans un cas comme dans l'autre cela termine la partie, et l'ordinateur devra vous demander si vous voulez une jouer nouvelle partie ou non.

Voulez-vous jouer une nouvelle partie?

A quoi vous pouvez répondre non (auquel cas le programme se termine) ou oui (auquel cas une nouvelle partie recommence avec un nouveau labyrinthe).

Paramètres du jeu

Vous appellerez votre programme Laby.java et il devra prendre 5 paramètres sur la ligne de commande qui sont respectivement:

  1. la hauteur du labyrinthe en nombre de cases
  2. la largeur du labyrinthe en nombre de cases
  3. la "densité" sous forme de probabilité de générer un muret (il s'agir d'un nombre entre 0 et 1: plus il est élevé, plus il y aura de murets, ainsi 0 génèrera un labyrinthe sans aucun muret intérieur, et 1 génèrera un labyrinthe avec tous les murets intérieurs voir exemple et explications plus bas).
  4. le nombre de secondes d'affichage du labyrinthe complet avant qu'il ne devienne invisible.
  5. le nombre de vies dont dispose votre personnage

Ainsi l'exemple ci-haut a été généré avec l'appel:

java Laby 5 5 0.30 7 5

Ces paramètres vous permettront de rendre le jeu plus intéressant, en contrôlant le niveau de difficulté, par exemple en créant un labyrinthe plus grand, plus ou moins dense, etc... A vous d'expérimenter des valeurs pour voir ce qui rend le jeu plus intéressant.

Si votre programme est appelé sans le bon nombre de paramètres, affichez un message d'erreur expliquant les paramètres nécessaires et suggérez à l'utilisateur comment démarrer le jeu avec des paramètres intéressants.


Nombre de paramètres incorrects.
Utilisation: java Laby <hauteur> <largeur> <densite> <duree visible> <nb vies>
Ex: java Laby 10 20 0.20 10 5

Format de l'affichage

Nous allons préciser ici le format de l'affichage. Comme nous venons de le voir, le labyrinthe a une certaine hauteur et une certaine largeur exprimée en nombre de cases ou de "murets". 

Les distances en largeur et en hauteur entre 2 murets sont données par deux constantes dans le programme (exprimées en nombre de caractères).

    public static final int LMURET = 8;
public static final int HMURET = 4;

Pour clarifier les choses, voici un exemple d'un labyrinthe de hauteur 3 et de largeur 5, avec tous les murets de dessinés (sauf l'ouverture de la porte de sortie), tel que pourrait le montrer l'appel à

java Laby 3 5 1 100 5

+---------------------------------------+
| | | | |
| | | | |
| | | | |
|------- ------- ------- ------- -------|
| | | | | |
| | | | | |
| | | | | |
|------- ------- ------- ------- -------|
| | | | | |
| @ | | | | |
| | | | | |
+---------------------------------------+

Ce tableau de jeu devrait être représenté par un tableau de char à 2 dimensions de hauteur*HMURET+1 par largeur*LMURET+1

Il vous est suggérer de définir une fonction creeTableauVide prenant en paramètre hauteur et largeur et qui crée un tel tableau, le remplit de caractères espace (' ') et le retourne.
Le tableau de jeu a un mur d'endeinte dessiné avec des caractères | et - et + (pour les 4 coins). Ce mur d'enceinte peut dans un premier temps être dessiné sans l'ouverture de la sortie à l'aide d'une fonction dessineMurdEnceinte qui prend en paramètre par ex. un tableau vide et le modifie en dessinant l'enceinte.

Il y a deux sortes de murets: les murets verticaux sont construits avec des "briques"  | et les horizontaux dessinés avec des "briques" -. Remarquez que les murets verticaux sont dessinés avec HMURET-1 briques et les horizontaux avec LMURET-1 briques, de sorte qu'il y a toujours un petit interstice (espace) entre deux murets consécutifs (ce qui permet de plus facilement voir la limite entre les cases).  On vous suggère d'écrire deux fonctions dessineMuretVertical et dessineMuretHorizontal avec comme paramètre le tableau que vous allez modifier, et la position i et j du muret (en nombre de murets plutôt que de caractères). De même vous pouvez écrire une fonction effaceMuretVertical qui servira à pratiquer l'ouverture à l'extrème droite du tableau. L'ouverture indiquée ci-dessus correpsond à la position de muret 0,5.
Le personnage est représenté par le caractère '@' et devra toujours être affiché au centre d'une case. Vous pouvez de même écrire une fonction affichePersonnage et effacePersonnage prenant en paramètre le tableau à modifier et la position du personnage (en cases). Ainsi le paersonage sur l'exemple ci-dessus est à la position 2,0.

Votre affichage devrait fonctionner même si on change les valeurs de HMURET et LMURET pour d'autres valeurs (paires supétieures ou égales à 2).

Ce que vous devez faire

Vous devez écrire un programme Laby.java qui correspond au jeu de Labyrinthe invisible tel que décrit ci-dessus en respectant les contraines suivantes:

Plus précisément ce que vous devez écrire:

La fonction jeuLabyrintheInvisible devra notamment:

Écrivez et utilisez au mieux des fonctions! La  plupart des fonctions ne devraient pas dépasser une dixaine de lignes. En règle générale, si une de vos fonctions fait plus d'une vingtaine de lignes, elle est probablement trop longue!  Regardez alors si vous ne pouvez pas en extraire certaines opérations et en faire d'autres fonctions plus élémentaires à appeler. Cela aidera à la modularité et à la lisibilité de votre code.

Indentez correctement et documentez votre code!

Une fois que vous aurez écrit le jeu de Labyrinthe invisible de base tel que décrit ci-dessus, on vous demande d'ajouter les fonctionalités suivantes, pour certaines touches de commandes entrées par l'utilisatuer qui ne sont pas des touches de direction:

Attention, ce dernier point (quand l'utilisateur presse 'o') est un petit problème d'intelligence artificielle pas facile. Nous vous recommandons de commencer par une intelligence simpliste puis de l'améliorer progressivement:

Une première stratégie simple pourrait se contenter de choisir au hazard la prochaine direction à prendre parmi toutes celles qui ne sont pas bloquées par un mur, et ceci tant que la sortie n'est pas atteinte. Affichez au fur et à mesure la position du personnage déplacé par l'intelligence artificielle de l'ordinateur, afin qu'on puisse le voir courir partout!

Une fois implémenté cette première stratégie simple, vous pouvez essayer de faire d'autres versions utilisant des stratégies possiblement plus efficaces...
Si vous avez implémenté plusieurs stratégies pour votre intelligence artificielle, proposez à l'utilisateur un choix de stratégie après qu'il a pressé 'o'.

Quelques indications

Indications diverses

Vous pouvez interrompre votre programme à tout moment avec la touche Ctrl C.

Concernant la saisie au clavier

Les fonctions de Keyboard attendent toujours que vous appuyez sur Entrée (ou Enter ou Return selon votre clavier) après avoir saisi le caractère indiquant la direction.

Concernant l'affichage du tableau de jeu

Ce n'est pas parce que vous modifiez le tableau de caractère qu'il s'affiche! Appelez régulièrement une fonction pour afficher votre tableau de jeu à l'écran (après chaque changement pertinent).

Aussi il vous est recommandé d'"effacer" l'écran avant tout nouvel affichage (ceci devrait permette au tableau de jeu de toujours apparaître au même endroit, plutôt qu'"en dessous" du précédent...). Pour ce faire, vous pouvez utiliser la fonction suivante:

    public static void effaceEcran()
{
char esc = (char)27;
System.out.print(esc+"[H"+esc+"[2J");
}

Concernant la création d'un labyrinthe au hazard

La seule méthode de Math que vous devez utiliser est Math.random() qui retourne un nombre aléatoire entre 0 et 1 tiré d'une loi uniforme.

Pour créer un labyrinthe au hazard, on vous suggère de considérer tour à tour tous les murets internes possibles, et de faire un tirage aléatoire d'un boolean pour voir si on devrait construire le muret ou pas. Le tirage aléatoire peut se faire en générant un nombre aléatoire entre 0 et 1 et en le comparant au paramètre densite passé sur la ligne de commande (qui est lui aussi entre 0 et 1 et représente la probabilité de construire un muret).

Il vous faut aussi choisir au hazard la position (indice de ligne) où placer le personnage, et la position (indice de ligne) où placer la sortie.

Notez que comme tout est construit au hazard, il se peut parfois (surtout si vous indiquez une densite élevée) qu'il n'y ait pas de possibilité pour votre personnage de sortir du labyrinthe.

Pour faire une pause

Pour faire effectuer une pause pendant un certain temps à votre programme (après avoir affiché le labyrinthe "visible"), vous pouvez utiliser la fonction suivante:

    public static void sleep(long millisecondes)
{
try {
Thread.sleep(millisecondes);
}
catch(InterruptedException e){
System.out.println("Sleep interrompu");
}
}

Comment détecter qu'un mouvement entraînerait une collision avec un muret (possiblement invisible...)

Suggestion: conservez 2 tableaux, un contenant le labyrinthe visible au complet, l'autre contenant le tableau de jeu courant (partiellement invisible). Ainsi vous pourrez aller voir dans le tableau ou tout est "visible" si il y a oui ou non un muret...
 

Remise

Vous faites la remise comme à l'habitude comme suit:
remise ift1015 tp1 Laby.java

BONNE CHANCE TOUT LE MONDE !