k Les bases de la programmation en Python

Les bases de la programmation en Python

II - Programmer en Python

5/Dessineravec turtle

Se repérer et tracer

NéanmoinsPour pouvoir se repérer dans la fenêtre, turtle met en place un repère à deux dimensions. Par défaut, celui-ci est centré dans la fenêtre. Ce repère nous permet de nous déplacer aisément de x sur l’axe des abscisses et de y sur l’axe des ordonnées. Le centre du repère, c’est-à-dire le point (x = 0, y = 0), est l’endroit où le curseur apparaît, sa maison en quelque sorte. Cela n’est pas bien compliqué à comprendre, il faut juste s’habituer à penser dans le plan. L’image suivante permet de mieux visualiser ce que nous venons de dire et comporte quelques exemples de points :

Nettoyer l'écran

NéanmoinsAu cours de notre utilisation de turtle, il est possible que nous ayons besoin de nettoyer l’écran. Pour cela, nous pouvons utiliser "clear" qui permet d’effacer ce que nous avons dessiné. En plus, nous pouvons aussi utiliser "reset" qui fait la même chose et réinitialise les valeurs du curseur à leurs valeurs par défaut (le crayon retourne à l’origine du repère, retrouve son orientation originale, sa largeur de trait par défaut, etc…). Ces deux fonctions ne modifient donc pas les configurations liées à la fenêtre, comme le titre ou la couleur de fond par exemple. Enfin, elles ne prennent aucun paramètre.

turtle.clear()
#Efface les dessins du crayon
turtle.reset()
#Fait de même et réinitialise le crayon

Avancer et reculer

Pour tracer, il faut se déplacer. Et pour déplacer le curseur, turtle nous offre plusieurs fonctions, comme forward et backward, respectivement pour avancer et reculer d’une distance que l’on passe en paramètre. Elles ont aussi chacune leur version abrégée, respectivement "fd" et "bk".

turtle.forward(turtle.window_width()/3)
#Avance d'un tiers de la largeur de la fenêtre
turtle.backward(turtle.window_width()/2)
#Recule de la moitié de la largeur de la fenêtre
turtle.bk(50) #Recule de 50px
turtle.fd(0) #Avance de 0px, donc n'avance pas

Se déplacer à des coordonnées données

Avec "goto", en lui fournissant une coordonnée x et une coordonnée y, nous pouvons nous rendre directement à un point (x, y) donné. De plus, nous pouvons aussi modifier uniquement la position en abscisse du curseur avec "setx" et la position en ordonnée avec "sety", en leur passant la nouvelle valeur. Enfin, puisque nous avons parlé du centre du repère, notons que la fonction "home" permet d'y retourner.

turtle.goto(100, 100)
#Position (100, 100)
turtle.setx(20)
#Position(20, 100)
turtle.sety(-80)
#Position(20, -80)
turtle.home()
#Position(0, 0 équivalent à turtle.goto(0, 0))

Lever ou baisser le crayon

Si nous ne pouvions pas nous déplacer dans la fenêtre sans laisser de trace, ce ne serait pas très amusant. Or, la fonction "up" nous permet de lever le crayon tandis que la fonction "down" nous permet de l’abaisser. Elles ne prennent aucun paramètre. Grâce à elles, nous pouvons choisir de tracer ou non :

turtle.up()
#Lève le crayon
turtle.forward(150)
#Avance de 150px sans tracer
turtle.down()
#Abaisse le crayon
turtle.backward(50)
#Recule de 50px en traçant

Changer la taille du traçage

Faire des traits, c’est bien, mais pouvoir choisir la taille, c’est encore mieux. En passant la nouvelle largeur de nos traits à pensize, nous pouvons le faire. En ne passant rien, la fonction nous renvoie la taille actuelle.

print(turtle.pensize())
#Affiche '1'
turtle.pensize(5.5)
#Modifie la largeur du traçage
print(turtle.pensize())
#Affiche '5.5'

Pour le moment, ce sont des fonctions plutôt basiques. Ce serait plus intéressant de pouvoir faire des figures, en assemblant les traits, et c’est ce que nous allons faire dans la section suivante !

Dessiner des figures simples

Jusqu’à présent, nous avons vu comment ouvrir une fenêtre et comment nous déplacer dans celle-ci. À présent, nous allons aller encore plus loin en dessinant nos premières figures.

Changer l'angle

Pour dessiner aisément, il nous manque tout de même quelque chose, et je pense que vous vous en êtes rendu compte : il faut que l’on puisse choisir l’inclinaison de notre trait, c’est-à-dire l’angle. En effet, jusqu’à présent nous avons été limités dans nos déplacements.

Or, turtle nous permet justement de faire varier la direction du curseur. Par défaut, lorsque l’on ouvre une fenêtre avec turtle, le crayon est orienté vers l’Est : l’angle est de 0 (ou 360). Pour jouer avec les angles, nous avons les fonctions right et left qui permettent de tourner respectivement vers la droite ou vers la gauche d’un angle passé. Parfois, il est plus simple d’utiliser setheading qui change directement l’angle avec la valeur passée. Vous pouvez aussi connaître la direction actuelle de votre crayon en utilisant la fonction heading qui ne prend aucun paramètre. Toutes ces explications sont illustrées avec l’image ci-dessous ainsi que par l’exemple qui la suit :

turtle.setup()
#Initialise la fenêtre
print(turtle.heading())
#Affiche 0.0 : le crayon pointe vers le point bleu : Est
turtle.left(90)
#Pointe vers le point jaune : Nord
turtle.right(270)
#Pointe vers le point vert : Ouest
turtle.setheading(0)
#Pointe de nouveau vers le point bleu
turtle.setheading(-90)
#Pointe à l'opposé du point jaune : Sud
print(turtle.heading()) #Affiche '270.0'

Concrètement, vous conviendrez que nous sommes désormais beaucoup plus libres. N’hésitez pas à essayer et à vous approprier ces notions, car elles seront vraiment utiles pour la suite. Voici un exemple d’utilisation de ce que l’on vient d’apprendre :

import turtle
LARGEUR, HAUTEUR = 640, 480
if __name__ == "__main__":
turtle.forward(LARGEUR/3) #Avance de d'un tiers de la largeur
turtle.left(80) #Tourne de 80° à gauche
turtle.up() #Lève le curseur
turtle.forward(HAUTEUR/4) #Avance d'un quart de la hauteur
turtle.down() #Baisse le curseur
turtle.right(180) #Tourne à 180 à droite
turtle.backward(HAUTEUR/4) #Recule d'un quart de la hauteur
turtle.pensize(3) #Change l'épaisseur du tracé
turtle.home() #Retourne à la maison
turtle.exitonclick() #Clique gauche pour fermer

Résultat exemple d'utilisation des angles.

Notons au passage que home réinitialise aussi l’orientation en plus de la position, c’est pourquoi nous voyons que le curseur a un angle de 0 à la fin puisque nous terminons le traitement par cela.

Pour terminer sur les angles, nous pouvons brièvement parler de la fonction towards qui prend en paramètre les coordonnées d’un point et nous retourne l’angle qu’il faudrait pour aller à ce point. Ainsi, ces deux morceaux de code donnent quasiment le même résultat (dans le second cas, l’angle final est celui d’avant l’appel à la fonction)

angle = turtle.towards(0, 90)
print(angle) #Affiche '90.0'
turtle.setheading(angle) #Angle : 90.0
turtle.forward(90) #Position : (0, 90); Angle : 90.0
turtle.goto(0, 90) #Position : (0, 90); Angle : 0.0

Dessiner des figures simples

Voilà, nous sommes désormais totalement capable de tracer nos propres figures grâce à ce que nous avons appris. Nous allons donc nous exercer en faisant quelques polygones. Triangle équilatérale, carré et octogone régulier seront nos invités. Avant que vous lisiez la suite, je vous encourage à essayer de dessiner par vous-mêmes ces figures.

Commençons avec le triangle équilatéral. Pour rappel, un triangle équilatéral est un triangle dont les côtés ont la même longueur ce qui implique que chaque angle a une valeur de 60° (par définition, la somme des angles d’un triangle vaut 180°). Une fois que l’on a cela en tête, nous pouvons implémenter une solution explicite :

#Un exemple de triangle équilatéral
longueur_cote = 200
turtle.forward(longueur_cote) #1er côté
turtle.left(360/3) #Angle
turtle.forward(longueur_cote) #2ème côté
turtle.left(360/3) #Angle
turtle.forward(longueur_cote) #3ème côté

Le carré et l’octogone, nous appliquerons le même principe. Pour le carré, nous avons quatre côtés de même longueur ainsi que quatre angle de 90°. Voici une solution :

#Un exemple de carré
longueur_cote = 200
for i in range(4):
turtle.forward(longueur_cote) #Côté
turtle.left(90) #Angle

L’octogone a quant à lui 8 côtés et des angles de 45° (360° divisé par 8 côtés). Une solution est :

#Un exemple d'octogone
longueur_cote = 100
for i in range(8):
turtle.forward(longueur_cote) #Côté
turtle.left(360/8) #Angle

Résultats pour ces résultats

Voilà, pour pratiquer, vous pouvez essayer de dessiner des figures plus compliquées voire de généraliser au cas d’un polygone, ou encore de dessiner à d’autres endroits que depuis le centre du repère. Il est temps de parler d’une dernière figure dont nous n’avons pas encore parlée : le cercle.

Utiliser les cercles

Pour les cercles, nous pouvons éviter de réinventer la roue puisqu’il existe une fonction déjà toute prête : circle. Au minimum, nous devons passer à celle-ci le rayon de notre cercle. De plus, nous pouvons aussi lui passer un angle (extent) qui permet de tracer uniquement une partie du cercle, ainsi qu’un nombre (steps) qui correspond au nombre d’étapes pour tracer. L’orientation du crayon a des conséquences sur la manière dont le cercle sera tracé. Pour mieux comprendre, voyons ce que ça peut donner :

turtle.circle(120) #Trace un cercle de rayon 120px
turtle.circle(70, 180) #Trace un demi-cercle de rayon 70px
turtle.circle(90, steps = 8) #Trace un octogone de longueur 90px
turtle.circle(40, 180, 4) #Trace la moitié d'un octogone de longueur 40px

Voici un exemple de code pour afficher cinq cercles du plus petit au plus grand, avec pour centre l’origine du repère :

#!/usr/bin/env python3
import turtle
if __name__ == "__main__":
rayon, ecart = 50, 20
for i in range(5):
turtle.up()
turtle.goto(0, -rayon)
turtle.down()
turtle.circle(rayon)
rayon += ecart #Augmente la valeur de rayon
turtle.up()
turtle.home()
turtle.exitonclick()

Résultat de l'exemple des cercles

Voilà, nous savons désormais dessiner des figures simples. Dans la section suivante, nous allons complexifier nos figures.

Dessiner des choses plus complexes

Introduction

Grâce à ce que nous venons d’apprendre, nous pouvons désormais réaliser des figures plus complexes, comme le montre l’image suivante par exemple. Pour réaliser cela, j’ai utilisé deux fonctions que nous n’avons pas encore vues. Tout d’abord, la fonction position, qui ne prend aucun paramètre et retourne, comme son nom l’indique, la position du crayon. Ensuite, j’ai aussi fait appel à la fonction distance qui prend en paramètre les coordonnées x et y d’un point et qui retourne la distance entre le curseur et ce point. De cette manière, j’ai pu connaître facilement la distance entre le centre du dessin, le point (0, 0) et le coin des petits carrés par lesquels je souhaitais faire passer le cercle : c’est-à-dire le rayon. Remarquez que le dessin a pour centre le centre du repère.

Un dessin un peu plus complexe.

Concernant le code, nous commençons par tracer le grand carré, puis les quatre petits, et nous terminons par le cercle. L’ensemble a pour centre le point d’origine du repère. Pour tracer chaque carré, nous nous plaçons à son coin bas gauche. Les commentaires vous aideront à comprendre. Si vous avez du mal, n’hésitez pas à prendre une feuille de papier et un crayon pour vous aider à visualiser :

#!/usr/bin/env python3
import turtle
def deplacer_sans_tracer(x, y = None):
#Fonction pour se déplacer à un point sans tracer
turtle.up()
if (isinstance(x, tuple) or isinstance(x, list)) and len(x) == 2:
turtle.goto(x)
else:
turtle.goto(x, y)
turtle.down()
def triangle_dans_carre(long_carre):
#Fonction pour tracer un triangle à l'intérieur du carré
#On prend la position du curseur qui est sur le coin bas gauche
pos_coin_bg = turtle.position()
#On trace les deux traits restants, la base étant déjà faite
turtle.goto(pos_coin_bg[0]+long_carre/2, pos_coin_bg[1]+long_carre)
turtle.goto(pos_coin_bg[0]+long_carre, pos_coin_bg[1])
def carre_avec_triangle(longueur):
#Fonction pour tracer un carré avec un triangle à l'intérieur
for i in range(4):
turtle.forward(longueur)
turtle.left(90)
triangle_dans_carre(longueur)
if __name__ == "__main__":
#On initialise les longueurs du grand carré et des petits carrés
longueur_1, longueur_2 = 150, 75
#On se positionne au coin bas gauche de notre futur grand carré
deplacer_sans_tracer(-longueur_1/2, -longueur_1/2)
#On le dessine
carre_avec_triangle(longueur_1)
#On prépare les valeurs des coin bas gauche des petits carrés
coins = [(longueur_1/2, longueur_1/2),
(-longueur_1/2-longueur_2, longueur_1/2),
(-longueur_1/2-longueur_2, -longueur_1/2-longueur_2),
(longueur_1/2, -longueur_1/2-longueur_2)]
#On dessine notre quatre petits carrés
for coin in coins:
deplacer_sans_tracer(coin)
carre_avec_triangle(longueur_2)
#On retourne au centre de notre dessin
deplacer_sans_tracer(0, 0)
#On prend la distance entre le centre et le coin par lequel le cercle passera
rayon = turtle.distance(longueur_1/2+longueur_2, longueur_1/2+longueur_2)
#On se déplace et on trace notre cercle
deplacer_sans_tracer(0, -rayon)
turtle.circle(rayon)
#On retourne à la maison, et on prend un angle de 90°
deplacer_sans_tracer(0, 0)
turtle.left(90)
turtle.exitonclick()

Les points

La fonction "dot" nous permet d’afficher un point dans le canvas. Pour cela, nous pouvons passer en paramètre le diamètre du point, et nous pouvons aussi, si le cœur nous en dit, passer une couleur. Si nous ne lui passons rien, le point aura un diamètre par défaut et la couleur de traçage du curseur (noir par défaut). Nous étudierons le coloriage plus en détails dans la partie suivante.


turtle.dot(100, 'red') #Imprime un point rouge d'un diamètre de 100px
turtle.dot(50, 'yellow') #Imprime un point jaune d'un diamètre de 50px
turtle.dot(25) #Imprime un point noir d'un diamètre de 25px

Remarquons que si nous avions imprimé les points dans l’ordre inverse, nous n’aurions vu que le rouge puisque celui aurait masqué le jaune qui lui-même aurait masqué le noir. Nous pouvons aussi noter qu’un point sera tracé même si le crayon est levé.

Puisque comme d’habitude, rien ne nous empêche de jouer avec ce que nous apprenons, voici un programme qui imprime dix points de plus petit en plus grand en allant de gauche à droite et qui ont une couleur différente :

#!/usr/bin/env python3
import turtle
from random import randint
COULEURS = ['black', 'grey', 'brown', 'orange', 'pink', 'purple', 'red', 'blue', 'yellow', 'green']
if __name__ == "__main__":
turtle.setup(650, 100)
diametre = 15
turtle.up(); turtle.setx(-turtle.window_width()/2+2*diametre);
turtle.down()
#Pour le nombre de couleurs disponibles
for i in range(len(COULEURS)):
#On choisit un couleur aléatoirement
index_choisi = randint(0, len(COULEURS)-1)
#On imprime un point de cette couleur
turtle.dot(diametre, COULEURS[index_choisi])
#On supprime la couleur choisie pour éviter de la rechoisir
del COULEURS[index_choisi]
#On met à jour le diamètre et on se déplace pour le prochain point
diametre += 5; turtle.up(); turtle.fd(1.5*diametre);
turtle.down()
turtle.exitonclick()

Résultat de l'exemple des points.
Résultat exemple de points.

Les points, ce n’est pas tout ! Nous pouvons aussi, de la même manière, imprimer la forme du curseur avec stamp. Cette fonction ne prend aucun paramètre et retourne l’identifiant du tampon. Ce dernier nous sert à supprimer le tampon de la fenêtre en le passant à clearstamp. Nous pouvons aussi supprimer plusieurs tampons de l’écran en fournissant un nombre à clearstamps, voire tous en ne lui passant aucune valeur ou None. Voici ci-dessous un exemple d’utilisation illustré :

id_tampons = []
#L'opération suivante est répétée à maintes reprises tout en se déplaçant
id_tampons.append(turtle.stamp()) #Tamponne et on enregistre l'identifiant
turtle.clearstamp(id_tampons[14]) #Supprime le 15ème tampon
turtle.clearstamps(9) #Supprime les 9 premiers tampons
turtle.clearstamps(-10) #Supprime les 10 derniers tampons
turtle.clearstamps() #Supprime tous les tampons restants

Résultat exemple tampon aux différentes étapes.
Un dessin plus complexe (2)

Voici un autre exemple un peu plus complexe. Ici, nous dessinons un ciel étoilé. Pour ce faire, nous avons codé une fonction etoile qui se charge de tracer une étoile d’une longueur donnée, et nous allons nous en servir dans la boucle principale, tout en veillant à ce que l’étoile ne soit pas dessinée hors de notre fenêtre. Nous pourrions améliorer le code pour éviter qu’une étoile soit tracée par dessus une autre par exemple.

#!/usr/bin/env python3
import turtle
from random import randint
LARGEUR, HAUTEUR = 640, 480
LONGUEUR_MIN, LONGUEUR_MAX = 5, 20
def deplacer_sans_tracer(x, y = None):
#Fonction pour se déplacer à un point sans tracer
turtle.up()
if (isinstance(x, tuple) or isinstance(x, list)) and len(x) == 2:
turtle.goto(x)
else:
turtle.goto(x, y)
turtle.down()
def etoile(longueur):
#Fonction pour dessiner une étoile
turtle.setheading(180-2*72)
for i in range(5):
turtle.forward(longueur)
turtle.left(180-180/5)
if __name__ == "__main__":
turtle.setup(LARGEUR, HAUTEUR)
turtle.speed(0) #Met la vitesse de traçage la plus rapide
nb_etoiles, longueur_etoile = 20, 0
for i in range(nb_etoiles):
longueur_etoile = randint(LONGUEUR_MIN, LONGUEUR_MAX)
deplacer_sans_tracer(randint(-LARGEUR//2+LONGUEUR_MAX//2, LARGEUR//2-LONGUEUR_MAX),
randint(-HAUTEUR//2+LONGUEUR_MAX//2, HAUTEUR//2-LONGUEUR_MAX))
etoile(longueur_etoile)
deplacer_sans_tracer(0, 0)
turtle.exitonclick()

Un ciel étoilé