Ajout du 13 décembre : schéma montrant comment aller au bloc suivant.
Ajout du 15 décembre : description de résultats attendus
pour les programmes fournis en exemple (fichier README.md dans l'archive)
Correction du 3 janvier : nom des actions out(nombre) et
out(char) dans la description des actions
Projet à rendre pour le 8/1/2024 à 23h59, aucun retard ne sera toléré.
Des soutenances pourront potentiellement être organisées ensuite.
Lire tout le sujet.
Un rendu de projet comprend :
Vous devez rendre
Toute tentative de fraude (plagiat, etc.) sera sanctionnée. Si plusieurs projets ont des sources trop similaires (y compris sur une partie du code uniquement), tous leurs auteurs se verront attribuer la note 0/20. En particulier, il faudra prendre soin de ne pas publier son travail sur un dépôt public (en tout cas pas avant la date de fin de rendu). On évitera également de demander (ou de donner) des conseils trop précis à ses camarades (y compris des promotions précédentes), ces conseils ayant pu être donnés à plusieurs personnes. Les rendus seront comparés deux à deux.
Procédure de dépôt
Vous devez enregistrer
votre archive tgz dans le dépôt dédié au cours PRIM11 (prim11-2023) en vous
connectant à http://exam.ensiie.fr. Ce dépôt sera ouvert
jusqu'au 8 janvier inclus.
Le but de ce projet est d'implémenter un interpréteur pour un langage de programmation dont les programmes sont des images.
Cornelis est un langage de programmation fortement inspiré du langage Piet, qui se définit comme un langage dont les programmes ressemblent à des tableaux d'art abstrait. (Cornelis est le second prénom de Pieter Mondriaan, plus connu sous son nom d'artiste Piet Mondrian.) En Cornelis comme en Piet, les programmes sont donnés sous la forme d'image en deux dimensions. Le contenu de l'image est parcouru, ce qui entraîne un certain nombre d'actions.
L'image est constituée d'une grille de pixels de composantes rouge/verte/bleue. Chaque pixel a un voisin à gauche, à droite, en haut et en bas. En particulier, l'image doit être considérée comme un tore : le voisin d'un pixel située sur la ligne tout en haut est le pixel de la même colonne situé tout en bas, et réciproquement ; de même, le voisin d'un pixel située sur la colonne tout à droite est le pixel de la même ligne situé tout à gauche, et réciproquement.
En Cornelis, il y a 18 couleurs codantes, que l'on peut donner par leur code HTML :
#FF8080 rouge clair |
#FFFF80 jaune clair |
#80FF80 vert clair |
#80FFFF cyan clair |
#8080FF bleu clair |
#FF80FF magenta clair |
#FF0000 rouge |
#FFFF00 jaune |
#00FF00 vert |
#00FFFF cyan |
#0000FF bleu |
#FF00FF magenta |
#800000 rouge foncé |
#808000 jaune foncé |
#008000 vert foncé |
#008080 cyan foncé |
#000080 bleu foncé |
#800080 magenta foncé |
Les autres couleurs seront réparties en deux catégories en fonction de leur luminance : celles dont la luminance est strictement inférieure à 128 seront bloquantes, les autres seront passantes. La luminance sera calculée à l'aide de la formule : 0.202 × rouge + 0.707 × vert + 0.071 × bleu.
On ne considérera pas les pixels individuellement mais pas regroupés par blocs de même couleur. Un bloc est un ensemble de pixels contigus de même couleur, délimités par des pixels d'une autre couleur. Attention, comme l'image est un tore, les blocs peuvent déborder des limites de l'image et continuer de l'autre côté.
Pour faire un traitement sur un bloc (par exemple pour calculer sa taille ou pour trouver son pixel le plus en haut à gauche), on pourra utiliser un algorithme de type remplissage, avec un tableau supplémentaire, initialement rempli de zéros, qui indiquera les pixels déjà traités et limitera les appels récursifs :
traitement(i : image, init : couleur, x : colonne, y : ligne, traité : tableau) traité[x,y] := 1 si init = i[x, y] ... traitement du pixel en x, y ... pour chacun des quatre voisins vx, vy de x,y si non traité[vx,vy] traitement(i, init, vx, vy, traité)
L'interpréteur du code comportera
direction | bord | pixel retenu |
---|---|---|
est | bâbord | frontière droite, le plus haut |
tribord | frontière droite, le plus bas | |
sud | bâbord | frontière basse, le plus à droite |
tribord | frontière basse, le plus à gauche | |
ouest | bâbord | frontière gauche, le plus bas |
tribord | frontière gauche, le plus haut | |
nord | bâbord | frontière haute, le plus à gauche |
tribord | frontière haute, le plus à droite |
Attention, puisque l'image est un tore, la limite de l'image n'est pas forcément une frontière pour le bloc car il peut continuer de l'autre côté s'il s'agit de la même couleur.
Si jamais un bloc n'a pas de frontière dans la direction donnée (parce que c'est un bandeau tout le long de l'image), alors le comportement est indéfini.
Une fois sur ce pixel, on se déplace à nouveau dans le sens de la direction.
Exemple (on est dans le bloc rouge entouré de pointillés) :
|
À chacun des pas, on va potentiellement agir sur la pile. L'action à effectuer dépend des différences de couleurs entre le bloc d'origine et celui où on est arrivé.
Si on est passé par des pixels de couleur passante, alors on ne fait rien, quelle que soit la couleur des blocs d'origine et de destination.
Sinon, on va considérer la différence entre les couleurs dans le cycle rouge -> jaune -> vert -> cyan -> bleu -> magenta -> rouge et la différence de luminosité dans le cycle clair -> normal -> foncé -> clair.
Différence de luminosité | |||
---|---|---|---|
Différence de couleur | Aucune | 1 plus foncé | 2 plus foncé |
Aucune | empile | dépile | |
1 cran | plus | moins | fois |
2 crans | divise | reste | non |
3 crans | plus grand | direction | bord |
4 crans | duplique | tourne | in(nombre) |
5 crans | in(char) | out(nombre) | out(char) |
Attention, clair est donc considéré comme une fois plus foncé que foncé.
2 5 1 2 3 4 5 6 7(le sommet est à gauche), on va tourner deux fois à la profondeur cinq le reste de la pile, ce qui donnera la pile
3 4 5 1 2 6 7
L'exécutable final prendra en premier argument le nom d'un fichier au format PPM qui contiendra l'image contenant le code. Les entrées et sorties se feront sur l'entrée et la sortie standard.
Si le programme final s'appelle prog
, et qu'on a un
fichier d'entrée nommé test.ppm
, il devrait être
possible d'entrer la commande
./prog test.ppmpour interpréter le code contenu dans
test.ppm
.
.ppm
).
D'autres exemples seront possiblement proposés d'ici la date de rendu.
Lors de la correction, votre programme sera testé avec des exemples qui n'auront pas été fournis à l'avance ; faire passer les exemples ne suffira donc pas, il faudra bien respecter la spécification.
Pour la récupération d'une entrée de l'utilisateur, plutôt que faire un scanf directement, il vaut parfois mieux récupérer une ligne en entier avec fgets puis utiliser sscanf dessus ; on peut utiliser la suite de commandes suivantes :
char buf[256]; . . . fgets(buf, 256, stdin); sscanf(buf, "format", ...);
Pour lire un seul caractère, on pourra
utiliser getc
, qui renvoie EOF
en cas
d'erreur ou en fin de fichier.
Il pourra être opportun d'utiliser certaines fonctions de la
bibliothèque standard comme memcpy
ou memset
. Se reporter aux pages de manuel pour leur
fonctionnement.
Vous devez avoir lu jusqu'ici avant de commencer.