IN3R11-2: Sujet du TP2-C
ESIEE-PARIS, Denis BUREAU et Emilie CHARRIER, nov 2008.
1 - Les objectifs
Gestion des constantes, pré-processeur, création de nouveaux types
(énumérations, structures, et tableaux)
pour commencer le "projet" Sokoban.
2 "Projet" Sokoban
Les objectifs de ce mini-projet sont de mettre en oeuvre les notions
vues en cours. Plutôt que des exercices de TP indépendants,
il s'agira de réaliser un programme permettant de jouer à Sokoban. Vous pouvez tester le jeu en récupérant l'exécutable sokoban1.x. Après avoir récupéré le fichier, placez vous dans le répertoire du fichier téléchargé, tapez la commande linux chmod 764 sokoban1.x (pour garantir que vous avez les droits d'exécution sur le fichier) puis exécutez le avec la commande ./sokoban1.x.
Dans ce jeu, vous êtes un manutentionnaire dans un entrepôt qui
doit ranger des caisses à leur place; le problème est que les caisses
sont tellement lourdes que vous ne pouvez que les poussez, et une seule à
la fois.
Les directives des sections suivantes ne vous amènent pas
forcément aux meilleures solutions possibles, mais à des
solutions raisonnables permettant d'utiliser les différentes
notions vues en cours.
Veuillez lire tout le sujet avant de relire chaque partie
pour la réaliser, et n'hésitez pas à demander à un intervenant
si vous n'êtes pas sûr de ce qui est demandé. Pendant ce TP (et les suivants) vous devrez respecter l'ordre d'écriture des programmes vu en cours.
Tout ne sera pas terminé aujourd'hui; ce projet
continuera donc lors des prochains TP, mais des notions nouvelles
vues en cours devront alors être incorporées.
Il est fortement conseillé d'essayer de terminer chaque TP
en travail personnel avant le TP suivant.
3 Description du programme souhaité
3.1 Plan de jeu / affichage
Grille initiale = {
"########################", ######################## ########################
"#.........#......#.....#", #.........#......#.....# #.........#......#.....#
"##.....O###............#", ##......###............# ##.....O###............#
"###.............O#.....#", ###..............#.....# ###.............O#.....#
"####.............#O....#", ####.............#.....# ####.............#O....#
"##################.....#", ##################.....# ##################.....#
"####.............#.....#", ####.............#.....# ####.............#.....#
"###...............O..###", ###..................### ###...............O..###
"##...........#########o#", ##...........#########o# ##...........#########o#
"#....................oo#", #....................oo# #....................oo#
"#S....................o#", #.....................o# #S....................o#
"########################" ######################## ########################
};
1) Declaration en C 2) Grille initiale cachee 3) Grille de jeu (à afficher)
Remarques :
- La grille 1 est la façon dont le programmeur du jeu
spécifie le plan de départ.
# -> mur
S -> Sokoban, le personnage
O -> une caisse
o -> une cible (ou doit être rangée une caisse)
. -> une case vide
- La grille 2 reste cachée dans le programme et ne stocke que
ce qui est immobile (c'est-à-dire tout sauf Sokoban et les caisses,
qui sont remplacés par des cases vides).
Elle est produite automatiquement dès le début du jeu
à partir de la grille 1, et ne change plus jamais.
- La grille 3 est celle qui est affichée et qui sert à la
logique du jeu.
Elle est produite automatiquement dès le début du jeu
à partir des grilles 1 et 2, et est mise à jour
à chaque tour de jeu.
3.2 Commandes à traiter
- On saisira une ligne pour permettre au joueur d'entrer d'un seul coup
de 1 à 40 commandes d'1 caractère.
Au point 4.8 ci-dessous, il faudra utiliser la procédure
fgets
( commande, MAXCH, stdin ); où
commande doit être un tableau d'au moins MAXCH+1
caractères et stdin représente le clavier.
- q: pour Quitter (sort du programme)
- a: pour Aide (affiche ce message d'aide)
- h: pour aller en Haut
- b: pour aller en Bas
- g: pour aller à Gauche
- d: pour aller à Droite
4 Réalisation du programme en C
4.1 Constantes
Déclarez 3 constantes :
LARGEUR (24 dans notre exemple) et
HAUTEUR (12 dans notre exemple) destinées à servir de taille de tableau
et MAXCH (40 dans notre exemple) représentant la longueur maximale des commandes.
4.2 Nouveaux types
- boolean :
pour pouvoir manipuler aisément des valeurs booléennes ;
à définir par énumération en essayant de mimer le
type primitif java du même nom.
- Symbole :
pour pouvoir désigner les différents caractères
qui peuvent apparaître sur la grille :
MUR, CAISSE, CIBLE, SOKOBAN, VIDE
(le typage faible du C permet de mélanger ainsi des
caractères - ou plutôt leur code ascii - avec des
entiers - d'une énumération -).
- Commande :
pour pouvoir désigner les différents caractères
qui serviront de commande pour jouer
HAUT, BAS, GAUCHE, DROITE, AIDE, QUITTER
(toujours le typage faible du C ... )
- Ligne :
pour pouvoir stocker tous les caractères d'une ligne de la grille
et pouvoir considérer cette ligne comme une chaîne de
caractères.
- Grille :
pour pouvoir stocker toutes les lignes de la grille.
- Position :
pour pouvoir stocker la ligne et la colonne où se trouve
le personnage Sokoban.
Faites vérifier par un intervenant vos définitions de type.
4.3 Première itération
- Écrivez la procédure afficheGrille() qui affiche la grille
passée en paramètre précédée d'une ligne blanche.
- Écrivez la fonction main(), pour l'instant sans tenir compte
des arguments de la ligne de commande, mais en déclarant TOUS
les prototypes avant le main() .
- Déclarez-y la grille initiale comme indiqué au 3.1
puis appelez afficheGrille() .
- Testez votre programme le plus possible entre chaque itération.
4.4 Deuxième itération
- Écrivez la fonction getSokoban() qui retourne la Position
du Sokoban dans la grille passée en paramètre, ou {0,0}
s'il n'est pas trouvé.
- Ajoutez la procédure error() qui affiche un message d'erreur
en fonction de l'entier passé en paramètre puis qui termine le
programme en retournant ce code d'erreur.
Pour l'instant, on ne traite que le cas 1 : "Pas de sokoban !".
Nous utilisons la procédure exit() pour terminer le programme
(le code d'erreur est passé en paramètre).
void error(int pCode)
{
switch (pCode) {
case 1 : printf("pas de Sokoban!\n"); break;
default: printf("code erreur inconnu!\n");
}
exit(code);
}/*error*/
- Modifiez le main() pour qu'avant d'afficher la grille,
il calcule (et affiche pour debug) la position du Sokoban ;
s'il n'y en a pas, arrêtez le programme avec les bons
message et code d'erreur prévus ci-dessus.
4.5 Troisième itération
- Écrivez la procédure nouveauJeu(). Cette procédure recopie la grille
initiale passée en premier paramètre dans une nouvelle grille de jeu
passée en deuxième paramètre. Puis, la procédure remplace dans la grille
initiale les CAISSE et SOKOBAN par des VIDE ;
il ne restera donc plus que des choses immobiles dans la grille initiale.
- Modifiez le main() pour qu'au lieu d'afficher la grille initiale,
il crée une nouvelle grille de jeu et l'affiche. La grille initiale ne sera plus jamais affichée. Elle restera cachée et servira en 4.9 et 4.10.
4.6 Quatrième itération
- Écrivez la fonction booléenne verif() qui vérifie
si chaque ligne de la grille passée en paramètre est une
chaîne de caractères dont la longueur est bien égale
à la LARGEUR déclarée de la grille.
Dans le cas contraire, affichez un message approprié.
- Enrichissez la procédure error() avec le cas 2 :
"Grille initiale incorrecte !".
- Modifiez la procédure nouveauJeu() pour qu'elle commence par
cette vérification;
si elle échoue, arrêtez le programme avec les bons
message et code d'erreur prévus ci-dessus.
4.7 Cinquième itération
- Écrivez la fonction entière compte() qui compte
dans la grille passée en premier paramètre
le nombre de fois qu'apparaît le caractère passé
en second paramètre.
- Enrichissez la fonction booléenne verif() pour qu'elle vérifie
ensuite d'une part s'il y a au moins une CAISSE, et d'autre part
s'il y a autant de CAISSE que de CIBLE.
Dans le cas contraire, affichez un message approprié.
4.8 Sixième itération
- Jusqu'à présent, nous nous sommes attachés à construire et
vérifier la grille de jeu de départ.
Nous allons maintenant rendre le jeu interactif en permettant de
saisir des commandes.
- Écrivez la procédure aide() qui se contentera d'afficher
la légende des différents symboles et les commandes disponibles.
- Modifiez le main() pour qu'après la création de la
nouvelle grille de jeu, il répète le traitement
suivant tant qu'un booléen encore est vrai :
- afficher la grille
- afficher un prompt (caractère d'invite), par exemple "> "
- saisir la commande (voir détails ci-dessous)
- traiter la commande ; on se contentera pour l'instant des
commandes AIDE et QUITTER ; les autres seront ignorées
- La saisie : l'idée est de lire une ligne complète
et d'en extraire un caractère à chaque tour de boucle qui
sera interprété comme une commande.
Il faut bien sûr relire une ligne à chaque fois que
l'on arrive sur le caractère '\n' . Il faudra utiliser la procédure
fgets
( commande, MAXCH, stdin ); où
commande doit être un tableau d'au moins MAXCH+1
caractères et stdin représente le clavier.
4.9 Septième itération
- Écrivez la fonction pas() qui retourne la Position
quand on fait un pas dans la direction indiquée par la commande
passée en second paramètre à partir de la Position
passée en premier paramètre.
Afficher un message d'erreur approprié si la commande ne
représente pas une des 4 directions.
- Écrivez la fonction booléenne possible() qui vérifie
que la Position passée en second paramètre est une
destination possible (pour Sokoban ou pour une caisse)
dans la grille passée en premier paramètre (une destination est possible si elle correspond à une cible ou à du vide).
- Écrivez la procédure deplace() à 4 paramètres :
la Grille de jeu, la Position actuelle de ce qu'il
faut déplacer (sokoban ou une caisse) qu'on pourra appeler positionDeDepart, sa nouvelle Position qu'on pourra appeler positionDestination,
et la Grille initiale. Cette procédure remplace, dans la grille de jeu passée en premier paramètre, le symbole à la positionDestination par le symbole de positionDeDepart. Puis, elle remplace le symbole de positionDeDepart par VIDE ou CIBLE (cette information se trouve dans la Grille initiale passée en dernier paramètre).
- Écrivez la fonction joue() à 4 paramètres :
la Grille de jeu, la Commande (direction) à traiter,
la Position actuelle de Sokoban, et la Grille initiale.
Cette fonction retournera la nouvelle Position de Sokoban. Pour cela :
- Calculez la future position du joueur (en utilisant pas())
- Vérifiez que cette future position est une destination possible (en utilisant possible())
+ Si OUI, déplacer le joueur (en utilisant deplace())
+ Sinon :
Si c'est à cause d'un mur, le déplacement est impossible. Affichez un message appropprié.
Si c'est à cause d'une caisse, essayer de déplacer la caisse pour pouvoir déplacer le joueur.
- Enrichissez le main() pour qu'il traite aussi les 4 commandes
de direction, c'est-à-dire qu'il joue !
4.10 Huitième itération
Les TP suivants sont faisables sans avoir développé cette dernière itération. Cependant, il est conseillé de faire ce dernier exercice.
- Écrivez la fonction booléenne gagne() à 3 paramètres :
la Grille de jeu, la Position actuelle de Sokoban,
et la Grille initiale.
Un moyen de détecter que le joueur a gagné est lorsqu'il n'y a plus
de CIBLE dans la grille de jeu et que Sokoban n'occupe pas
la position d'une CIBLE dans la grille initiale.
- Enrichissez le main() pour qu'il détecte quand le joueur a gagné.
Dans ce cas, il doit afficher un message de félicitations en indiquant
le nombre de coups joués, puis terminer la boucle.