Devoir en groupe 1 : Le flocon de Von Koch Les tours de Hanoï Pierre Chatelain Matthieu Perrin 5 octobre 2009 1 Le flocon de Von Koch On souhaite réaliser une représentation du flocon de Von Koch en CamL. Après avoir défini cette fractale, nous présenterons l’algorithme et sa complexité, puis nous chercherons à améliorer la figure obtenue en supprimant les artefacts dus à certaines particularités d’OCamL. Enfin, nous proposerons quelques variantes de ce problème. 1.1 Définition Cette figure est une fractale définie à partir du motif de base suivant : F IG . 1 – motif de base À la génération 0, on avance simplement de la longueur distance. À la génération n > 0, on trace quatre morceaux de génération n − 1 en suivant ce motif. Pour obtenir le flocon complet, il suffit alors de tracer 3 fois le motif, en tournant à chaque fois d’un angle de 2π 3 . Un exemple est donné figure 2. 1 F IG . 2 – flocon de génération 5 2 1.2 Algorithme Si on se donne une fonction AVANCE telle que l’appel AVANCE(distance, angle) trace une ligne de longueur distance dans la direction angle à partir du point courant, la définition permet de donner directement l’algorithme 1. Algorithme 1 : dessin du motif MOTIF(generation, distance, angle) = if generation = 0 then AVANCE(distance, angle); else MOTIF(generation − 1, distance/3, angle); MOTIF(generation − 1, distance/3, angle + π/3); MOTIF(generation − 1, distance/3, angle − π/3); MOTIF(generation − 1, distance/3, angle); La complexité de cet algorithme se calcule donc récursivement de la manière suivante, en notant Cn le nombre de lignes à tracer pour un motif de génération n : C0 = 1 ∀n ≥ 0, Cn+1 = 4 ∗ Cn D’où Cn = 4n . 1.3 1.3.1 Quelques pièges à éviter Utilisation du type float Les points de la fenêtre graphique sont repérés par des couples d’entiers. Or, pour dessiner un flocon d’ordre relativement grand (n > 5), la définition de l’écran est insuffisante, et on doit dessiner des longueurs inférieures au pixel. Si la conversion f loat → int est effectuée au mauvais moment, le flocon devient donc rapidement un point... Une solution est d’utiliser des références sur des flottants pour repérer la position théorique du point courrant, et de n’utiliser des entiers que dans les fonctions graphiques. Le calcul se faisant entièrement sur des flottants, la déformation du flocon est locale, et il n’y pas de problème de raccord. 1.3.2 Problème d’arrondis En regardant à la loupe, nous avons observé une légère déformation locale : le flocon n’était pas parfaitement symétrique. Cela était dû au fait que la fonction int_of_float donne la partie entière d’un flottant, et non son arrondi. Il est donc nécessaire de créer une fonction arr : f loat → int qui, appliquée à un flottant x, renvoie l’entier le plus proche de x. 3 1.4 1.4.1 Quelques variantes... Flocons tordus En modifiant l’angle du motif de base, on obtient des formes diverses, plus lisses si l’angle diminue, et plus découpées si l’angle se rapproche de π2 : 1.4.2 F IG . 3 – angle = π 10 F IG . 4 – angle = π 4 F IG . 5 – angle = 3π 7 F IG . 6 – angle = 10π 21 Remplissage du flocon On peut également chercher à remplir le flocon. Pour cela, une possibilité est de remplir d’un bloc le triangle interne, puis de dessiner le flocon avec un algorithme similaire à l’algorithme 1, dans lequel on ajoute à chaque appel le remplissage du triangle apparaissant dans le motif. Plus concrètement, le motif de base devient : 4 F IG . 7 – motif plein Le résultat n’est pourtant pas totalement satisfaisant : on voit sur la figure 8 qu’il manque certains pixels. L’algorithme de remplissage reste donc encore à améliorer... F IG . 8 – flocon plein 5 2 Les tours de Hanoï 2.1 Résolution d’une partie à n disques Le jeu des tours de Hanoï consiste en un ensemble de trois tiges et n disques de tailles différentes. Au début de la partie, tous les disques sont rangés par ordre décroissant sur la première tige. Le but du jeu est de déplacer tous les disques dans le même ordre sur la dernière tige en n’en déplaçant qu’un seul à la fois et en ne posant jamais un grand disque sur un plus petit. On remarque que si l’on sait déplacer n disques d’une tige t1 à une tige t2 , on peut en déplacer n + 1 en déplaçant les n premiers disques de t1 à t3 puis le disque restant de t1 à t2 , et en redéplaçant les n plus petits disques de t3 à t2 . On obtient ainsi l’algorithme 2. Algorithme 2 : tours de Hanoï HANOI(nombre, origine, destination) = if nombre = 1 then DEPLACE(origine, destination); else HANOI(nombre − 1, origine, autretige); DEPLACE(origine, destination); HANOI(nombre − 1, autretige, destination); Soit Cn le nombre de transferts à effectuer pour déplacer n disques d’une tige à une autre par l’algorithme précédent. Cn vérifie : C1 = 1 Cn+1 = 2 ∗ Cn + 1 On en déduit que Cn = 2n − 1. En effet, notons un = 2n − 1, on a bien : u1 = 1 un+1 = un+1 = 2n+1 − 1 = 2 ∗ (2n − 1) + 1 = 2un − 1 2.2 Affichage graphique La première implémentation de l’algorithme donne une suite de tâches à exécuter pour résoudre une tour de n disques. Un affichage graphique des tours serait plus éloquante pour comprendre son fonctionnement. Pour cela, il faut garder en mémoire les disques de chacune des tiges. Nous avons défini le type tour par : type tour = { tiges : int list array; nb_disques : int; enleve : int };; où tiges est le tableau des disques présents sur les trois tiges, nb_disques code le nombre total de disques dans les tours, et enleve indique si l’un des disques –et lequel– est en cours de déplacement. 6 F IG . 9 – représentation des tours de Hanoï Les fonctions enleve_disque : tour−>int−>unit, pose_disque : tour−>int−>unit et permettent d’effectuer toutes les opérations de déplacement des disques d’une tige à une autre. est_complete : tour−>bool Enfin, la fonction affiche_tour : tour−>unit permet de dessiner une tour sur l’afficheur graphique de OCamL. Le même algorithme peut alors être implémenté en ralentissant l’affichage à chaque étape grace à une boucle for. 2.3 Un nouvel algorithme moins restrictif Le problème des tours de Hanoï étant issu d’un jeu, nous avons utilisé les fonctions décrites ci-dessus pour proposer un jeu interractif dans lequel il est possible de cliquer sur les tours pour enlever ou poser un disque. Nous utilisons pour cela la gestion d’évènements de la biblithèque graphique. Lorsqu’un joueur humain déplace les tours, beaucoup d’états différents peuvent être atteints par les trois tiges. Or il serait intéressant que l’ordinateur puisse terminer seul une partie dans laquelle le joueur humain s’est fourvoyé. Voici l’algorithme envisagé : Nous supposerons que tous les disques sont posés sur une tige. Sinon, il est possible de se ramener à ce cas en reposant le disque enlevé sur une tige où il est autorisé à aller, celle sur laquelle il était précédement posé par exemple. Le disque 1 étant le plus petit disque, il est posé au sommet d’une tige culminée par une suite de la forme 1, ..., n. Si n est le plus grand disque, alors on sait transporter tous les disques sur la dernière tige. Sinon, le disque n+1 est au sommet d’une autre tige. On peut y transporter les n premiers disques, et recommencer l’algorithme. A chaque étape, on transporte un nombre de disque strictement croissant. A une certaine étape, tous les disques sont donc sur la même tige et l’agorithgme s’arrête. On peut remarquer que l’algtorithme n’est pas optimal. Par exemple, pour la tour définie par { tiges = [| [n]; [1;...;(n−1)]; [ ] |]; nb_disques = n; enleve = 0 }, les (n-1) premiers disques sont transportés de la tige 1 à la tige 0, puis de la tige 0 à la tige 1, ce qui est bien sûr inutile. 7