Pour ce premier projet de NSI d’art génératif, nous avons décidé de représenter une scène en perspective avec des torii, portails japonais traditionnels symboles majeurs de la culture du pays du soleil levant.
Qu’est-ce qu’un torii ?
Un torii est une porte traditionnelle japonaise, souvent en bois ou en pierre, qui marque l’entrée d’un sanctuaire shintoïste. Symboliquement, le torii représente la frontière entre le monde terrestre et le monde spirituel : en passant sous cette porte, on entre dans un espace sacré. Ce symbole est profondément lié à la spiritualité japonaise et au respect de la nature et des divinités dans cette religion.
Le Script
Pour ce projet nous avons décidé de découper chaque partie principale en une fonction distincte : le sol, le fond, les escaliers, la forme d’un torii et l’effet de perspective, nous permettant de travailler indépendamment sur chaque partie et de bien les séparer. A la fin chacune des fonctions est appelées dans un ordre méticuleusement choisi afin de donner le résultat souhaité.
Composition du script
A présent nous allons analyser le script étape par étape afin de mieux comprendre son cheminement.
from turtle import *
from random import randint
try:
from PIL import Image
pillow_installed = True
except:
print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :")
print("https://nsi.xyz/py2png")
pillow_installed = False
titre = "Fushimi Inari - construite avec turtle"
title(titre+" | Imene S. Marie GS")
setup(1280, 720)
speed(0)
hideturtle()
flash = True
if flash:
wn = Screen()
wn.tracer(0)
Pour ce faire nous utilisons le module Turtle ainsi que la fonction Randint du module Random. Le script inclut une vérification pour s’assurer que le module PIL est bien installé, essentiel pour exporter une image créer avec Turtle; Si PIL n’est pas trouvé, un message d’erreur apparaît avec un lien vers la page d’installation du module, sans interrompre l’exécution du script. Ensuite, nous définissons le titre de la fenêtre pour afficher le rendu final, ajustons sa taille
Commençons par la première fonction : le sol.
from turtle import *
from random import randint
def sol():
penup()
goto(-720, 0)
pendown()
color("#E8E9F3")
begin_fill()
fillcolor("#E8E9F3")
for _ in range(4):
forward(2000)
right(90)
end_fill()
penup()
Pour le sol afin de rester dans la sobriété et éviter la surcharge de couleur, nous avons opté pour un sol d’un gris clair uni remplit avec fill_rect rappelant des rochers. Le script consiste à créer un rectangle puis de le remplir de gris.
Cette fonction dessine des marches d’escalier en grande partie grâce a la fonction goto(), en commençant par une ligne longue en bas et en réduisant progressivement la longueur à chaque marche supérieure . Chaque marque a un point de départ et une longueur spécifiques, qui diminuent au fur et à mesure qu’on se rapproche du centre de l’image.
list1 = ["#308E30","#31572C", "#4F772D", "#90A955", "#ECF39E"]
def fond():
for i in range(9999):
penup()
goto(randint(-730,720), randint(-10,720))
pendown()
color(list1[i%5])
rcercle = randint(5,10)
begin_fill()
circle(rcercle)
end_fill()
penup()
Pour le fond, afin de représenter une forêt, la fonction fond() dessine plusieurs cercles remplis de couleurs aléatoires tirées de list1, en choisissant des positions et des tailles aléatoires pour chaque cercle. Elle utilise un grand nombre de répétitions (9999) pour couvrir la surface de l’écran.
def dessiner_rectangle(largeur, hauteur, couleur):
fillcolor(couleur)
begin_fill()
for _ in range(2):
forward(largeur)
left(90)
forward(hauteur)
left(90)
end_fill()
Pour rendre l’utilisation plus simple et rapide d’une des fonctions principales torii(), nous avons créer la fonction dessiner_rectangle() ayant pour but de seulement dessiner un rectangle de la couleur, largeur et hauteur souhaitées.
Ce qui nous amène à la fonction suivante torii() :
La fonction torii() dessine une structure de torii avec des rectangles, en utilisant des dimensions et des positions spécifiques pour chaque élément.
repeter = 8
def gperspective():
x = -115
y = 0
largeur = 25
hauteur = 50
for i in range(repeter):
torii(x, y, largeur, hauteur)
x -= 20 * (i + 1)
y -= 15 * (i + 1)
largeur *= 1.5
hauteur *= 1.5
La fonctiongperspective() crée une série de torii en perspective. En répétant et modifiant légèrement les coordonnées et dimensions des torii à chaque étape. On obtient un effet de perspective, où chaque torii semble être plus loin et plus grand que le précédent grâce aux étapes largeur*= 1.5 et hauteur *= 1.5 consistant à créer un torii 1.5 fois plus grand que le précédent.
repeter = 8
def dperspective():
x = 115
y = 0
largeur = 25
hauteur = 50
for i in range(repeter):
torii(x, y, largeur, hauteur)
x += +15 * (i + 1) ** 0.5
y += -30 * (i + 1) **0.5
largeur *= 1.5
hauteur *= 1.5
penup()
goto(-60, 0)
pendown()
La fonction dperspective() est similaire à gperspective(), mais elle crée une série de torii en perspective dans une direction différente. Plutôt que de déplacer chaque torii vers la gauche et le bas, cette fonction les déplace vers la droite et le bas.
Et enfin, nous avons appelées les différents fonctions dans un ordre défini afin d’obtenir le résultat attendu.
if flash:
wn.update()
image = getcanvas()
nom_du_fichier_sans_extension=titre+"_"+hex(randint(2**30+2**25,2**30+2**25+2**24-1))[2:]
image.postscript(file=nom_du_fichier_sans_extension+".ps", colormode='color')
try:
psimage = Image.open(nom_du_fichier_sans_extension+".ps")
psimage.load(scale=2)
psimage_resized = psimage.resize((1280, 720))
psimage.save(nom_du_fichier_sans_extension+".png")
print(nom_du_fichier_sans_extension+".png", psimage.size, "sauvegardé dans le dossier")
except:
if not pillow_installed:
print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :")
print("https://nsi.xyz/py2png")
else:
print("Oops! - 'ghostscript' not installed- RTFM :")
print("https://nsi.xyz/py2png")
exitonclick()
Cette toute dernière parti du script n’a aucun impacte sur le résultat final, c’est pour exporter l’image finale au format .png. En effet, le script comprend une suite de commandes permettant de sauvegarder une image générée par turtle en .png. D’abord, l’image est créée au format .ps puis elle est convertie en .png avec un nom aléatoire (cela permet d’éviter de remplacer les anciennes images avec un nom similaire).
Bill Cipher est un personnage fictif de la série animée « Gravity Falls ». C’est un démon triangulaire jaune avec un chapeau haut de forme, connu pour ses pouvoirs surnaturels et son rôle antagoniste. Il est souvent représenté comme une figure maléfique et manipulatrices. Comme nous aimons tout les deux ce personnage, nous avons voulu le représenter en python. Voici ci dessous l’image d’origine, tiré du cartoon, que nous avons voulu reproduire en python.
Structure globale du script
Pour que ce soit plus clair à expliquer et pour nous même, nous avons d’abord mis toutes les fonctions, puis les avons exécutés une par une dans l’ordre nécessaire au bon fonctionnement du script. Dans l’ordre, nous avons les fonctions : -cercle_centre(x) : trace un cercle qui a pour centre le point 0, 0 et un rayon de taille x pixels. -lignes1(inner_radius, outer_radius, num_segments) : trace un certain nombre de lignes entre un rayon extérieur et intérieur d’un cercle, partant de 0, 0 et ayant pour rayon inner_radius et outer_radius. Entre chaque lignes, rajoute un symbole d’UTF 8 pour remplacer les symboles présents dans le dessin originel. -lignes2(inner_radius, outer_radius, num_segments) : Même principe, mais sans les symboles en UTF 8. -Bill(1) : Fais l’image de Bill Cipher, au centre de l’image. La variable 1 permet de choisir la taille du triangle principale de bill, cette fonction permet simplement de construire Bill à un endroit de l’image.
Les fonctions du script expliquées en détail : cercle_centre
Ici, la fonction déplace la tortue x pixel en dessous du point 0, 0 (la deuxième coordonné représente l’ordonné). On est donc à une distance de rayon du centre de l’image, ce qui fait qu’on a un cercle avec un centre 0, 0.
Les fonctions du script expliquées en détail : lignes1
def lignes1(rayon_intérieur, rayon_extérieur, num_segments):
angle = 360 / num_segments
symboles = ["☣", "☯", "Ω", "♔", "☀", "☢", "∞", "⚛", "✴", "♧"]
for i in range(num_segments):
penup()
goto(0, 0)
setheading(i * angle)
forward(rayon_intérieur)
pendown()
forward(rayon_extérieur - rayon_intérieur)
penup()
goto(0, 0)
# Dessiner le symbole
setheading(i * angle + angle / 2)
# Adjust the position to move lower symbols down
if i >= 5: # For example, if you want to move the last 5 symbols down
forward((rayon_intérieur + rayon_extérieur) / 2 - -20) # Move down more
else:
forward((rayon_intérieur + rayon_extérieur) / 2 - 20) # Move down less
write(symboles[i], align="center", font=("Arial", 30,))
goto(0, 0)
Les paramètres de la fonction sont le rayon intérieur, le rayon extérieur du cercle et le nombre de segments que l’on veut. angle = 360 / num_segments : cette ligne calcule l’angle entre chaque segment en degrés, en divisant le cercle complet (360 degrés) par le nombre de segments désiré. On a rangé les symboles dans une liste, cela permet d’en utiliser un différent à chaque fois grâce à la variable i. Dans le for i in range(num_segments), on commence par multiplier l’étape de la boucle à laquelle on est (i) par l’angle entre chaque segments. On relève le stylo, on avance jusqu’au rayon intérieur, on rabaisse le stylo, et enfin on avance de la différence entre le rayon extérieur et intérieur (ce qui correspond à la distance qu’il reste à parcourir). Le setheading permet de pointer la tortue vers le centre de la case, angle/2 étant le milieu de la case. Pour les symboles de 0 à 5, les symboles supérieurs, le stylo descend légèrement avant de placer le symboles (elle est tourné vers le bas grâce au setheading). Pour les symboles de 0 à 4, ils sont placés plus haut. Enfin, on écris le symbole qui correspond à l’étape à laquelle on est (stockée dans i). On aligne les symboles avec la tortue, puis on défini la police.
lignes1(370, 440, 10)
Les fonctions du script expliquées en détail : lignes2
def lignes2(rayon_extérieur, num_segments):
angle = 360 / num_segments
for i in range(num_segments):
penup()
goto(0, 0)
setheading(i * angle)
distance = rayon_extérieur
while distance > 0:
distance_mouvement = min(25, distance)
forward(distance_mouvement)
distance -= distance_mouvement
if random.random() < 0.5:
penup()
else:
pendown()
penup()
goto(0, 0)
Le premier paragraphe fait la même chose que dans lignes1. Dans le second paragraphe, on commence à définir la distance à parcourir, qui est égale au rayon extérieur (puisque l’on part du point 0, 0 le rayon intérieur est inutile). Tant qu’il est plus grand que 0, on avance de la plus petite valeur entre 25 et distance (qui est défini par distance_mouvement). Ensuite, on fait la différence entre distance et distance_mouvement. Cela permet d’avancer de 25 pixel à chaque fois, sauf à la fin où l’on avance de pile le nombre de pixel avant d’atteindre la limite. Tout les 25 pixels avancés, le script baisse ou lève aléatoirement le stylo, ce qui donne une impression d’irrégularité aux lignes (elles peuvent faire 0, 25, 50, 75, etc… pixels avant que le stylo ne se lève ou ne se baisse). Enfin, on repart du point 0, 0 et on recommence avec un angle différent.
pensize(1), lignes2(360, 100)
Les fonctions du script expliquées en détail : lignes3
def lignes3(rayon_intérieur, rayon_extérieur, num_segments):
angle = 360 / num_segments
for i in range(num_segments):
penup()
goto(0, 0)
setheading(i * angle)
forward(rayon_intérieur)
pendown()
forward(rayon_extérieur - rayon_intérieur)
penup()
goto(0, 0)
C’est la même chose que dans lignes1, mais sans les symboles.
lignes3(450, 1200, 300)
Les fonctions du script expliquées en détail : bill
y = 1
def bill(size_bill):
global y
pensize(4)
goto(-200, -150) # Partie 1
setheading(0)
# Set the fill color
fillcolor("#f4e4c2") # Change "blue" to any color you want
begin_fill() # Start filling the shape
pendown()
for i in range(3):
forward(400 * size_bill)
left(120)
end_fill() # End filling the shape
penup()
# Partie 2
goto(0, 195), setheading(0), pensize(6), forward(-60), pendown(), forward(120), penup(), goto(-20, 195), setheading(90), pendown()
for i in range(40): # Partie 3
forward(150)
y = y + 1
goto(-20 + y, 195)
penup(), goto(-125, -20), setheading(0), pensize(4), pendown(), forward(250), penup(), goto(-160, -80), pendown(), forward(320), penup()
goto(-185, -130), pendown(), forward(370), penup(), setheading(270), goto(-70, -20), pendown(), forward(60), penup(), goto(70, -20), pendown()
forward(60), penup(), goto(-110, -80), pendown(), forward(50), penup(), goto(110, -80), pendown(), forward(50), penup(), goto(0, -80)
pendown(), forward(50), penup(), goto(-195, -150), setheading(0), pendown(), pensize(1)
for i in range(95):
b = randint(3, 5)
forward(b), left(90), forward(b**2), right(180), forward(b**2), setheading(0)
forward(4), left(90), forward(4**2), penup(), pensize(4), goto(-45,-113), pendown()
fillcolor("black")
begin_fill()
right(60)
for i in range(3):
forward(65)
left(120)
end_fill()
penup()
goto(45,-48)
setheading(270)
pendown()
fillcolor("black")
begin_fill()
right(60)
for i in range(3):
forward(65)
left(120)
end_fill()
penup()
goto(-65,60)
setheading(0)
pendown()
right(60)
for i in range(45):
left(2.6)
forward(3.5)
setheading(180)
right(60)
for i in range(45):
left(2.6)
forward(3.5)
penup()
goto(0,35)
setheading(90)
right(35)
pendown()
for i in range(28):
forward(2)
left(2.5)
setheading(90)
right(215)
for i in range(28):
forward(2)
left(2.5)
penup(), goto(0,23), setheading(270), pendown(), forward(5), penup(), goto(-35,32), setheading(225), pendown(), forward(13), penup()
goto(39,33), setheading(300), pendown(), forward(12), penup(), goto(0,97), setheading(90), pendown(), forward(13), penup(), goto(-30,92)
setheading(110), pendown(), forward(17), penup(), goto(30,92), setheading(70), pendown(), forward(17), penup(), goto(-95,-150)
setheading(265), pensize(6), pendown()
forward(90)
for i in range(20):
forward(2)
left(3)
forward(50)
setheading(260)
p=8
for i in range(10):
pensize(p)
forward(3)
p = p - 0.3
penup()
goto(95,-150)
setheading(275)
pensize(6)
pendown()
forward(90)
for i in range(20):
forward(2)
right(3)
forward(50)
setheading(280)
p=8
for i in range(10):
pensize(p)
forward(3)
p = p - 0.3
penup()
goto(-133,-33)
setheading(225)
pendown()
for i in range(40):
forward(3)
right(2)
setheading(45)
pos_hand_l=pos()
pensize(7)
for i in range(28):#oe
forward(1)
left(3)#oe
penup()
goto(pos_hand_l)
setheading(210)
pendown()
for i in range(40):
forward(1)
right(2)
penup()
goto(pos_hand_l)
setheading(265)
pendown()
for i in range(30):
forward(1)
right(3)
penup()
goto(pos_hand_l)
setheading(300)
pendown()
for i in range(25):
forward(1)
right(3)
penup()
goto(133,-33)
setheading(315)
pensize(p)
pendown()
for i in range(40):
forward(3)
left(2)
setheading(135)
pos_hand_l=pos()
pensize(7)
for i in range(28): #oe
forward(1)
right(3)#oe
penup()
goto(pos_hand_l)
setheading(330)
pendown()
for i in range(40):
forward(1)
left(2)
penup()
goto(pos_hand_l)
setheading(275)
pendown()
for i in range(30):
forward(1)
left(3)
penup()
goto(pos_hand_l)
setheading(240)
pendown()
for i in range(25):
forward(1)
left(3)
Pour créer Bill, nous avons commencé par le triangle principal, que nous avons rempli pour superposer la ligne 2. Ensuite, nous avons dessiné le chapeau en augmentant le pensize() afin de reproduire l’effet de l’image de référence. Pour le sommet du chapeau, nous avons utilisé une boucle où un trait se forme, puis la tortue se déplace d’un pixel pour refaire un trait, ce qui permet d’optimiser le script et de personnaliser sa taille.
Ensuite, nous avons recréé les briques sur Bill par essais et erreurs, en testant différentes coordonnées et distances pour reproduire fidèlement la texture des briques. Pour dessiner les traits en bas de Bill, nous avons fait avancer la tortue d’une distance aléatoire entre 3 et 5, puis elle monte verticalement de cette même distance au carré, créant ainsi l’effet de l’image originale tout en facilitant la personnalisation du script.
Pour le nœud papillon, nous avons créé deux triangles remplis qui s’emboîtent pour reproduire l’effet du Bill original. Concernant l’œil, nous avons pris le temps de déterminer l’angle et le nombre exact de répétitions nécessaires pour former la forme circulaire de l’œil et de l’iris avec précision. Pour les cils, nous avons simplement ajusté les coordonnées pour obtenir les bonnes valeurs.
La jambe est un trait avec un pensize() plus large, et pour le genou, nous avons utilisé une répétition de forward() et de left(). Le pied a été réalisé avec une boucle où le pensize() diminue progressivement à mesure que la tortue avance, imitant ainsi l’effet de l’image d’origine.
Enfin, les bras ont été construits avec une simple boucle de forward() et de right(). Les mains ont été créées avec des boucles contenant différentes valeurs pour reproduire fidèlement les doigts de Bill. La fonction pos() a été utilisée pour revenir facilement au même point de départ pour chaque doigt, soit le milieu de la main.
Le mot-clé global dans le code Python permet de déclarer que la variable y utilisée dans la fonction bill fait référence à la variable globale y définie en dehors de la fonction. Cela signifie que toute modification apportée à y à l’intérieur de la fonction affectera la variable y dans l’espace de noms global.
Dans le cadre du projet Art génératif qui consiste a générer une image en pythona l’aide du module turtle, j’ai réalisé ma voiture favorite, la Subaru Impreza dans un thème néon. Je vous explique dans cet article la rélisation de ce projet
La légendaire Subaru Impreza
L’Impreza est une compacte du constructeur automobile japonais Subaru, apparue en 1992. Icône de la marque grâce à ses succès en championnat du monde des rallyes (trois titres pilotes et trois titres constructeurs), elle en est à sa cinquième génération depuis 2016.Avec un moteur d’une cylindrée de 2 litres et 211 chevaux (94/97) et 217 (98/00) pour la version européenne de première génération sous l’appellation GT, les premières Impreza qui deviendront les WRX peuvent afficher des caractéristiques allant jusqu’à 320 chevaux dans leur version STI. Depuis 2015, La Subaru WRX est devenu un modèle indépendant de la Impreza et suit son propre développement. De ce fait, le nom Impreza disparait peu à peu des références sportives remplacé par la WRX et à moindre mesure par la Subaru BRZ.
Avant meme de realiser
Appel et verification des modules
from turtle import *
from random import randint
try:
from PIL import Image
pillow_installed = True
except:
print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :")
print("https://nsi.xyz/py2png")
pillow_installed = False
titre = "Bastien.S"
title(titre+" | Subaru Impreza WRC")
setup(1920,1080)
colormode(255)
speed(0)
hideturtle()
flash = True
if flash:
wn = Screen()
wn.tracer(0)
Ici on appelle les fonctions turtle et random ou plutot uniqument randint. On verifie ensuite on verifie si l’utilisateur a bien PIL d’installer s’il ne l’a pas un code d’erreur s’affichera ainsi qu’un lien pour installer le module. On definie le titre de la fenetre ainsi que sa taille,le type de couleurs utiliséeds (ici R,G,B), on cache la tortue, et randons le script instantané.
Un script en calques
Comme pour tout dessin numérique nous avons besoins de differents calques. Un calque est une couche transparente sur laquelle des éléments graphiques peuvent être placés et manipulés indépendamment les uns des autres. Les differentes fonctions représentent nos calques voici les notres :
background()
batiments()
road()
subaru()
logo()
Background()
def background():
r = 20
g = 20
b = 20
hauteur = 540
x = 0
for i in range(50):
pensize(10),color(r,g,b),penup(),goto(-960,hauteur),pendown(),forward(1920)
hauteur = hauteur - 8
b = b + 1
r = r + 1
goto(-960,hauteur)
for i in range(37):
pensize(10),color(r,g,b),penup(),goto(-960,hauteur),pendown(),forward(1920)
hauteur = hauteur - 10
g = g + 2
r = r - 1
goto(-960,hauteur)
for i in range(90):
pensize(7),color(35,35,45),penup(),goto(-960,hauteur + 1),pendown(),forward(1950)
hauteur = hauteur - 7
Nous avons décomposer fond en 3 parties distinctes: degradé noir vers rose puis rose vers vert pour le ciel puis du gris foncé pour la route.
Pour creer nos degradés nous plaçons notre tortue tout en haut a gauche de notre fenetre puis nous la faisons avancer jusqu’a l’extremité droite. Ensuite on retourne a gauche et on va a la ligne du dessous en diminuant la valeur (hauteur), tout ca grace a la fonction goto. On modifie la couleur en augmantant ou diminuant les valeur (R,G,B) et on recommence. Pour creer la route on fait la meme chose mais sans changer la couleur
Pour creer ses batiments sombre rien de plus simple et d’aussi long. Nous avons utilisé les fonctions bengin_fill et end_fill, avec differéntes teintes de gris pour créer du relief
Pour realiser les bandes nous avons creer des listes contenants les differents parametres de celles ci (longueurs, angle, etc)
subaru()
def subaru():
color(0,150,250)
pensize(7)
for i in range(2):
penup()
goto(-330,-200)
pendown()
#roue arriere
left(106),forward(95),left(60),forward(30),left(50),forward(30),backward(30),right(50),backward(30),right(60),forward(25),left(85),forward(40),left(65),forward(25),left(2),forward(30),left(10),forward(45),left(24),forward(40),left(90),forward(35),left(62),forward(60),left(45),forward(35),left(90),forward(37),left(37),forward(40)
#arriere voiture
right(155),forward(75),right(55),forward(20),left(45),forward(45),right(45),forward(50),right(25),forward(40)
#aileron
left(160),forward(30),right(80),forward(30),right(100),forward(60),backward(60),left(100),forward(8),right(100),forward(60),backward(60),left(80),forward(5),left(10),left(90),forward(20),left(90),forward(50),left(90),forward(35),left(20)
#plafond
forward(20),left(30),forward(80),right(22),forward(65),right(24),forward(85),right(9),forward(185),right(17),forward(70),backward(20)
#parebrise
right(165),forward(70),left(5),forward(145),left(112),forward(95)
#capot
left(50),forward(140),right(15),forward(35),left(5)
#phare
right(55),forward(32),backward(32),right(85),forward(35),left(90),forward(25),left(80),forward(120),left(110),forward(25),left(65),forward(85),right(10)
#capot
left(155),backward(35),left(20),backward(140),left(18),forward(150),left(35),backward(10),forward(20),right(35),forward(110),right(50),forward(10),backward(5),left(50),forward(15),right(40),backward(110) ,forward(110),left(23),forward(130),right(20),forward(50),right(35)
#phare droit
forward(20),right(10),forward(10),right(100),forward(60),right(100),forward(20),right(75),forward(50),penup(),goto(-330,-200),right(2),pendown()
#roue arriere
left(106),forward(95),left(60),forward(30)
#dessus du peneu
left(50),forward(30),backward(30),right(50),backward(30),right(60),forward(25),left(85),forward(40),left(65),forward(25)
#jante
left(2),forward(30),left(10),forward(45),left(24),forward(40),left(90),forward(35),left(62),forward(60),left(45),forward(35),left(90),forward(37),left(37),forward(40),left(37),forward(70),left(65),forward(55),left(60),forward(70)
#bas de caisse
right(58),backward(25),forward(190)
#roue avant
left(86),backward(45),right(78),forward(33)
#jante
penup(),left(85),forward(90),left(170),pendown(),left(10),forward(45),left(24),forward(40),left(90),forward(35),left(62),forward(60),left(45),forward(35),left(90),forward(37),penup(),left(48),forward(84),pendown(),left(96),backward(33),left(78),forward(112)
#portiere
left(10),forward(50),right(20),forward(55),left(35),forward(95),left(60),forward(75),left(35),forward(85),left(30),forward(70),left(124),forward(220),backward(140),left(67),forward(100),backward(180),right(140),forward(80),left(73),forward(140)
#roue avant
left(86),forward(20),right(22),forward(56),right(67),forward(50),right(67),forward(6),right(94),forward(47),left(47),forward(40),left(26),forward(62),left(27),forward(42),left(64),forward(71),left(60),forward(50),left(125),forward(15),right(77),forward(115)
#parechock
backward(20),right(108),forward(215),left(120),forward(40),right(120),forward(140),right(115),forward(38),left(115),backward(110),forward(190)
#aeration parchock
backward(45),penup(),right(90),forward(7),pendown(),right(90),forward(190),left(120),forward(10),right(120),penup(),forward(40),pendown(),forward(60),left(95),forward(20),left(85),forward(50),left(55),forward(25),right(55)
penup(),forward(35),pendown(),right(60),forward(50),left(60),forward(130),left(60),forward(60),left(30),penup(),forward(7),pendown(),right(90),forward(45)
#parechock
right(90),backward(10),forward(42),right(90),forward(10),right(60),forward(20),right(120),forward(20),right(90),forward(17),right(90),forward(5),left(85),forward(50),right(86),forward(30),forward(160),left(5),forward(170),backward(170),right(5),backward(160),left(86),forward(40),right(25),forward(20),right(60),forward(57),right(50),forward(20),right(30),forward(40),backward(40),left(80),forward(30),right(85),forward(40),backward(25),left(85),forward(210),right(180),color(255,255,255),pensize(2)
Nous avons dit que les batiments etaient longs? Et bien ce n’est rien a coté de cette subaru. Cette subaru a été bètement dessiné
logo()
def logo():
pensize(4)
def etoile_logo_main():
left(10),forward(60),left(60),forward(40),right(140),forward(40),left(60),forward(60),right(160),forward(60),left(60),forward(40),right(140),forward(40),left(60),forward(60),right(160),forward(60),right(10)
def etoile_logo():
left(10),forward(30),left(60),forward(20),right(140),forward(20),left(60),forward(30),right(160),forward(30),left(60),forward(20),right(140),forward(20),left(60),forward(30),right(160),forward(30),right(10)
color(255,255,0)
for i in range(2):
xetoile = [740,760,680,625,710]
nbx = len(xetoile)
yetoile = [400,350,350,305,310]
nby = len(yetoile)
for i in range(5):
penup(),goto (xetoile[i%nbx],yetoile[i%nby]),pendown(),etoile_logo(),penup(),goto (560,380),pendown(),etoile_logo_main()
pensize(2),color(255,255,255)
Afin de réaliser le logo Subaru nous avons créé deux fonction qui sont aussi simple que la subaru etoile_logo_main() qui correspond a la plus grande étoile et etoile_logo() a la plus petite. Avec goto() on a placer les etoiles a différentes coordonées.
Fin de script
if flash:
wn.update()
image = getcanvas()
nom_du_fichier_sans_extension=titre+"_"+hex(randint(2**30+2**25,2**30+2**25+2**24-1))[2:]
image.postscript(file=nom_du_fichier_sans_extension+".ps", colormode='color')
try:
psimage = Image.open(nom_du_fichier_sans_extension+".ps")
psimage.load(scale=2)
psimage_resized = psimage.resize((1280, 720))
psimage.save(nom_du_fichier_sans_extension+".png")
print(nom_du_fichier_sans_extension+".png", psimage.size, "sauvegardé dans le dossier")
except:
if not pillow_installed:
print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :")
print("https://nsi.xyz/py2png")
else:
print("Oops! - 'ghostscript' not installed- RTFM :")
print("https://nsi.xyz/py2png")
exitonclick()
Ce script permet generer l’image final en .ps puis a la convertir en .png avec un nom aléatoir evitant que l’image précdédemment généré soit écrasé.
Script complet
from turtle import *
from random import randint
try:
from PIL import Image
pillow_installed = True
except:
print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :")
print("https://nsi.xyz/py2png")
pillow_installed = False
titre = "Bastien.S"
title(titre+" | Subaru Impreza WRC")
setup(1920,1080)
speed(0)
hideturtle()
flash = True
if flash:
wn = Screen()
wn.tracer(0)
# ============== MON SCRIPT PYTHON ==============
colormode(255)
def logo():
pensize(4)
def etoile_logo_main():
left(10),forward(60),left(60),forward(40),right(140),forward(40),left(60),forward(60),right(160),forward(60),left(60),forward(40),right(140),forward(40),left(60),forward(60),right(160),forward(60),right(10)
def etoile_logo():
left(10),forward(30),left(60),forward(20),right(140),forward(20),left(60),forward(30),right(160),forward(30),left(60),forward(20),right(140),forward(20),left(60),forward(30),right(160),forward(30),right(10)
color(255,255,0)
for i in range(2):
xetoile = [740,760,680,625,710]
nbx = len(xetoile)
yetoile = [400,350,350,305,310]
nby = len(yetoile)
for i in range(5):
penup(),goto (xetoile[i%nbx],yetoile[i%nby]),pendown(),etoile_logo(),penup(),goto (560,380),pendown(),etoile_logo_main()
pensize(2),color(255,255,255)
def background():
r = 30
g = 30
b = 30
y = 540
x = 0
for i in range(50):
pensize(10),color(r,g,b),penup(),goto(-960,y),pendown(),forward(1920)
y = y - 8
b = b + 1
r = r + 1
goto(-960,y)
for i in range(37):
pensize(10),color(r,g,b),penup(),goto(-960,y),pendown(),forward(1920)
y = y - 10
g = g + 2
r = r - 1
goto(-960,y)
for i in range(90):
pensize(7),color(35,35,45),penup(),goto(-960,y + 1),pendown(),forward(1950)
y = y - 7
def batiments():
pensize(2)
penup()
goto(-960,-226)
pendown()
forward(100)
color(20,20,20),forward(360),begin_fill(),left(90),forward(640),right(100),forward(100),right(80),forward(620),end_fill(),left(90),backward(80),begin_fill(),left(90),forward(650),left(90),forward(120),left(90)
forward(40),right(70),forward(60),left(70),forward(592),end_fill(),backward(2),left(90),begin_fill(),left(90),forward(640),right(100),forward(100),right(80),forward(624),end_fill(),left(90),backward(260),color(30,30,30)
begin_fill(),left(90),forward(640),left(100),forward(240),left(80),forward(599),end_fill(),left(90),forward(240),begin_fill(),left(90),forward(640),right(100),forward(240),right(80),forward(599),end_fill()
left(90),forward(350),begin_fill(),left(90),forward(640),left(100),forward(240),left(80),forward(599),end_fill(),left(90),forward(240),begin_fill(),left(90),forward(640),right(100),forward(100),right(80),forward(620)
end_fill(),color(20,20,20),begin_fill(),left(90),forward(1),left(90),forward(350),right(45),forward(80),right(95),forward(60),left(50),forward(35),left(25),forward(50),right(115),forward(383),end_fill(),begin_fill()
left(180),color(25,25,25),forward(430),right(100),forward(80),right(80),forward(415),end_fill(),left(90),begin_fill(),color(15,15,15),forward(3),left(90),forward(250),right(90),forward(80),left(45),forward(20),right(45)
forward(15),left(90),forward(60),right(90),forward(100),right(90),forward(325),end_fill(),left(90),backward(20),color(25,25,25),begin_fill(),left(90),forward(230),right(95),forward(100),left(95),forward(110),right(80)
forward(100),right(55),forward(10),right(45),forward(342),end_fill(),begin_fill(),left(90),backward(15),left(90),color(10,15,15),forward(300),right(92),forward(120),right(88),forward(20),left(90),forward(20),right(90)
forward(275),end_fill(),color(25,25,25)
left(180),begin_fill(),forward(275),right(90),left(80),forward(20),right(65)
forward(200),right(15),forward(100),right(90),forward(360),end_fill(),left(90)
t = -200
u = -330
def subaru():
color(0,150,250)
pensize(7)
for i in range(2):
penup()
goto(u,t)
pendown()
#roue arriere
left(106),forward(95),left(60),forward(30),left(50),forward(30),backward(30),right(50),backward(30),right(60),forward(25),left(85),forward(40),left(65),forward(25),left(2),forward(30),left(10),forward(45),left(24),forward(40),left(90),forward(35),left(62),forward(60),left(45),forward(35),left(90),forward(37),left(37),forward(40)
#arriere voiture
right(155),forward(75),right(55),forward(20),left(45),forward(45),right(45),forward(50),right(25),forward(40)
#aileron
left(160),forward(30),right(80),forward(30),right(100),forward(60),backward(60),left(100),forward(8),right(100),forward(60),backward(60),left(80),forward(5),left(10),left(90),forward(20),left(90),forward(50),left(90),forward(35),left(20)
#plafond
forward(20),left(30),forward(80),right(22),forward(65),right(24),forward(85),right(9),forward(185),right(17),forward(70),backward(20)
#parebrise
right(165),forward(70),left(5),forward(145),left(112),forward(95)
#capot
left(50),forward(140),right(15),forward(35),left(5)
#phare
right(55),forward(32),backward(32),right(85),forward(35),left(90),forward(25),left(80),forward(120),left(110),forward(25),left(65),forward(85),right(10)
#capot
left(155),backward(35),left(20),backward(140),left(18),forward(150),left(35),backward(10),forward(20),right(35),forward(110),right(50),forward(10),backward(5),left(50),forward(15),right(40),backward(110) ,forward(110),left(23),forward(130),right(20),forward(50),right(35)
#phare droit
forward(20),right(10),forward(10),right(100),forward(60),right(100),forward(20),right(75),forward(50),penup(),goto(u,t),right(2),pendown()
#roue arriere
left(106),forward(95),left(60),forward(30)
#dessus du peneu
left(50),forward(30),backward(30),right(50),backward(30),right(60),forward(25),left(85),forward(40),left(65),forward(25)
#jante
left(2),forward(30),left(10),forward(45),left(24),forward(40),left(90),forward(35),left(62),forward(60),left(45),forward(35),left(90),forward(37),left(37),forward(40),left(37),forward(70),left(65),forward(55),left(60),forward(70)
#bas de caisse
right(58),backward(25),forward(190)
#roue avant
left(86),backward(45),right(78),forward(33)
#jante
penup(),left(85),forward(90),left(170),pendown(),left(10),forward(45),left(24),forward(40),left(90),forward(35),left(62),forward(60),left(45),forward(35),left(90),forward(37),penup(),left(48),forward(84),pendown(),left(96),backward(33),left(78),forward(112)
#portiere
left(10),forward(50),right(20),forward(55),left(35),forward(95),left(60),forward(75),left(35),forward(85),left(30),forward(70),left(124),forward(220),backward(140),left(67),forward(100),backward(180),right(140),forward(80),left(73),forward(140)
#roue avant
left(86),forward(20),right(22),forward(56),right(67),forward(50),right(67),forward(6),right(94),forward(47),left(47),forward(40),left(26),forward(62),left(27),forward(42),left(64),forward(71),left(60),forward(50),left(125),forward(15),right(77),forward(115)
#parechock
backward(20),right(108),forward(215),left(120),forward(40),right(120),forward(140),right(115),forward(38),left(115),backward(110),forward(190)
#aeration parchock
backward(45),penup(),right(90),forward(7),pendown(),right(90),forward(190),left(120),forward(10),right(120),penup(),forward(40),pendown(),forward(60),left(95),forward(20),left(85),forward(50),left(55),forward(25),right(55)
penup(),forward(35),pendown(),right(60),forward(50),left(60),forward(130),left(60),forward(60),left(30),penup(),forward(7),pendown(),right(90),forward(45)
#parechock
right(90),backward(10),forward(42),right(90),forward(10),right(60),forward(20),right(120),forward(20),right(90),forward(17),right(90),forward(5),left(85),forward(50),right(86),forward(30),forward(160),left(5),forward(170),backward(170),right(5),backward(160),left(86),forward(40),right(25),forward(20),right(60),forward(57),right(50),forward(20),right(30),forward(40),backward(40),left(80),forward(30),right(85),forward(40),backward(25),left(85),forward(210),right(180),color(255,255,255),pensize(2)
def road():
color(100,100,100),pensize(7)
for i in range(2):
liste_longueur_bandes1 = [2,10,6,10,60,150,]
liste_longueur_bandes2 = [1,4,7,16,75,220]
liste_longueur_bandes3 = [2,10,10,12,100,200]
liste_longueur_bandes4 = [1,4,8,15,25,97]
liste_longueur_distance_bandes = (1,3,6,9,20,40)
liste_angle_gauche = [15,20,25,30,43,85]
liste_angle1 = [90,50,45,35,45,85]
liste_angle2 = [90,135,135,155,155,133]
liste_angle3 = [90,70,40,20,25,45]
liste_angle4 = [90,105,140,150,135,97]
nblb1 = len(liste_longueur_bandes1)
nblb2 = len(liste_longueur_bandes2)
nblb3 = len(liste_longueur_bandes3)
nblb4 = len(liste_longueur_bandes4)
nbldb = len(liste_longueur_distance_bandes)
nba = len(liste_angle_gauche)
nba1 = len(liste_angle1)
nba2 = len(liste_angle2)
nba3 = len(liste_angle3)
nba4 = len(liste_angle4)
penup(),goto (-540,-226),pendown()
for i in range(6):
right(liste_angle_gauche[i%nba])
forward(liste_longueur_bandes1[i%nblb1])
left(liste_angle1[i%nba1])
forward(liste_longueur_bandes2[i%nblb2])
left(liste_angle2[i%nba2])
forward(liste_longueur_bandes3[i%nblb3])
left(liste_angle3[i%nba3])
forward(liste_longueur_bandes4[i%nblb4])
left(liste_angle4[i%nba4])
forward(liste_longueur_bandes1[i%nblb1])
penup()
forward(liste_longueur_distance_bandes[i%nbldb])
pendown()
left(liste_angle_gauche[i%nba])
color(255,255,255)
pensize(2)
#calque ===================================
background()
batiments()
road()
subaru()
logo()
#GENERER DES IMAGES AUTOMATIQUEMENT ==============
if flash:
wn.update()
image = getcanvas()
nom_du_fichier_sans_extension=titre+"_"+hex(randint(2**30+2**25,2**30+2**25+2**24-1))[2:]
image.postscript(file=nom_du_fichier_sans_extension+".ps", colormode='color')
try:
psimage = Image.open(nom_du_fichier_sans_extension+".ps")
psimage.load(scale=2)
psimage_resized = psimage.resize((1280, 720))
psimage.save(nom_du_fichier_sans_extension+".png")
print(nom_du_fichier_sans_extension+".png", psimage.size, "sauvegardé dans le dossier")
except:
if not pillow_installed:
print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :")
print("https://nsi.xyz/py2png")
else:
print("Oops! - 'ghostscript' not installed- RTFM :")
print("https://nsi.xyz/py2png")
exitonclick()
Minecraft est un jeu vidéo de type aventure « bac a sable » (construction complètement libre) développé par le Suédois Markus Persson, alias Notch, puis par la société Mojang Studios. Il s’agit d’un univers composé de voxels et généré de façon procédurale, qui intègre un système d’artisanat axé sur l’exploitation puis la transformation de ressources naturelles. Cet article montre la conception graphique à travers l’exemple de l’univers du jeu.
Le Projet
Comme cité dans l’introduction, cet article a pour but de montrer en détails la réalisation de notre conception graphique de l’univers de Minecraft. Pour ce projet, nous avons donc utilisé le module turtle, ainsi que le module random. Le script permettant d’exporter l’image générée a aussi été utilisé.
Le Code, en détails
Le code, dans son ensemble, est organisé avec chaque élément découpé et distinguable :
Tout d’abord, commençons par importation des librairies, ainsi que l’appel des fonctions :
from turtle import *
from random import randint
try:
from PIL import Image
pillow_installed = True
except:
print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :")
print("https://nsi.xyz/py2png")
pillow_installed = False
titre = "Perspective - A Day In Minecraft"
title(titre+" | Au lycée, la meilleure spécialité, c'est la spé NSI")
setup(1280, 720)
speed(0)
hideturtle()
flash = True
if flash:
wn = Screen()
wn.tracer(0)
On ajoute donc les librairies turtle, ainsi que random, puis, une partie du script qui permet d’« exporter une image générée par turtle » est utilisée afin de vérifier que l’utilisateur a bien installé le module PIL (PILLOW), sinon un message d’erreur s’affichera et lui donnera un lien pour installer le module. Après que la vérification soit terminée, on met en place la partie du script qui permet de nommer la fenêtre qui va s’afficher avec le rendu ainsi que sa taille. Enfin on ajoute le type de couleurs utilisées (RGB par exemple), la vitesse de la tortue, et on dissimule cette dernière.
Apres cette étape, nous pouvons enfin commencer a coder avec turtle.
pour la première étape, nous avons le fond :
import turtle
colormode(255)
def ciel():
penup()
# Valeurs initiales pour un bleu plus foncé
rciel = 30 # Valeur de rouge
gciel = 144 # Valeur de vert
bciel = 255 # Valeur de bleu
hauteur = -360
goto(-640, -358)
pendown()
# Durée totale pour le dégradé
total_steps = 720
while hauteur <= 360:
# Premier demi dégradé: Du bleu foncé au blanc
if hauteur <= 0:
r_incr = (255 - rciel) / (total_steps / 2) # Incrément de rouge
g_incr = (255 - gciel) / (total_steps / 2) # Incrément de vert
b_incr = (255 - bciel) / (total_steps / 2) # Incrément de bleu
else: # Deuxième demi dégradé: Du blanc au bleu clair
r_incr = (173 - 255) / (total_steps / 2) # Incrément de rouge vers bleu clair
g_incr = (216 - 255) / (total_steps / 2) # Incrément de vert vers bleu clair
b_incr = (230 - 255) / (total_steps / 2) # Incrément de bleu vers bleu clair
pencolor(round(rciel), round(gciel), round(bciel))
forward(1280)
hauteur += 1
goto(-640, hauteur)
# Incrémenter les valeurs de couleur
rciel += r_incr
gciel += g_incr
bciel += b_incr
Tout d’abord, le code commence configurer le mode de couleur pour utiliser le modèle RGB, où les valeurs des couleurs rouge, vert et bleu peuvent varier de 0 à 255. Une fonction nommée ciel est définie pour encapsuler tout le code de dessin. À l’intérieur de cette fonction, le stylo est levé au début, ce qui signifie que la tortue ne dessine pas lorsque elle se déplace à sa position initiale. Des variables sont initialisées pour représenter les valeurs de couleur du dégradé, avec une première couleur qui est un bleu plutôt clair. La tortue est ensuite déplacée à une position spécifique sur l’écran sans dessiner. Le code définit aussi une variable pour le nombre total d’étapes du dégradé, fixé à 720, ce qui représente le nombre d’itérations dans la boucle qui va dessiner le dégradé. Ensuite, une boucle commence et continuera tant que la hauteur de la tortue est inférieure ou égale à 360. Cette boucle est essentielle pour dessiner le dégradé de couleur. À chaque itération, il y a une condition qui vérifie si la tortue est dans la première moitié du dégradé (jusqu’à une hauteur de 0). Si c’est le cas, des incréments pour chaque composante de couleur sont calculés afin de passer du bleu foncé au blanc. Si la tortue est dans la seconde moitié du dégradé (au-dessus de 0), le code calcule de nouveaux incréments pour faire la transition du blanc vers une nuance de bleu clair. Dans chaque itération, la couleur actuelle du stylo est définie en fonction des valeurs calculées, puis la tortue se déplace en avant pour dessiner une ligne horizontale. Ensuite, la hauteur est augmentée pour passer à la ligne suivante, et la tortue est déplacée vers la nouvelle position. Enfin, à chaque étape de la boucle, les valeurs de couleur sont mises à jour en ajoutant les incréments précédemment calculés, permettant au dégradé de progresser progressivement à travers l’écran. Le résultat est un effet de ciel qui commence en bleu clair en haut, devient blanc au milieu, puis continue vers une couleur bleu clair en bas, recréant ainsi un effet visuel similaire à celui du ciel dans le jeu Minecraft.
Ensuite, la fonction créant les nuages est ajoutée :
def carre(x, y, size):
# Dessine un carré de taille donnée aux coordonnées spécifiées
penup()
goto(x, y)
pendown()
color("white")
begin_fill()
for _ in range(4):
forward(size)
right(90)
end_fill()
def unnuage(x, y, size, grid_width, grid_height):
# Dessine un nuage carré et homogène composé d'une grille de carrés
for row in range(grid_height):
for col in range(grid_width):
# Positionne chaque carré dans une disposition en grille
square_x = x + col * size
square_y = y - row * size
carre(square_x, square_y, size)
def lesnuages():
# Place des nuages carrés et homogènes avec de grandes distances entre eux
cloud_positions = [
(-500, 250), (-200, 300), (100, 310),
(400, 310), (0, 180)]
for x, y in cloud_positions:
unnuage(x, y, size=20, grid_width=10, grid_height=5) # Taille et dimensions de chaque nuage
# Appel des fonctions pour dessiner le ciel et les nuages
lesnuages()
Le code utilise la bibliothèque turtle de Python pour dessiner des nuages sous forme de carrés. La fonction carre(x, y, size) dessine un carré d’une taille spécifiée aux coordonnées (x, y). Elle commence par lever le stylo pour ne pas dessiner en se déplaçant, puis elle se positionne au point donné, abaisse le stylo, et définit la couleur de remplissage à blanc. Ensuite, elle dessine le carré en avançant et en tournant à droite à chaque coin, avant de terminer le remplissage. La fonction unnuage(x, y, size, grid_width, grid_height) crée un nuage en formant une grille de carrés. Elle utilise des boucles imbriquées pour parcourir les lignes et les colonnes de la grille, calculant les coordonnées de chaque carré en fonction de la taille spécifiée. Chaque carré est dessiné en appelant la fonction carre. Enfin, la fonction lesnuages() détermine où les nuages doivent être placés sur l’écran. Elle utilise une liste de positions prédéfinies pour placer plusieurs nuages avec un certain espacement entre eux. En appelant ces fonctions, le code dessine une série de nuages carrés et homogènes sur l’écran.
Pour la prochaine étape, nous coderons le soleil :
def draw_square(x, y, size,color):
# Fonction pour dessiner un carré de couleur donnée aux coordonnées spécifiées
penup()
goto(x, y)
pendown()
turtle.color(color)
begin_fill()
for _ in range(4):
forward(size)
right(90)
end_fill()
def soleil():
# Centre du soleil (carré jaune)
sun_size = 125 # Taille du carré central du soleil
sun_x = -sun_size / 2 # Position X pour centrer le soleil
sun_y = 250 # Position Y pour placer le soleil en haut de l'écran
draw_square(sun_x, sun_y, sun_size, 'yellow')
# Dessiner le soleil
soleil()
Ce code utilise également la bibliothèque turtle pour dessiner un soleil sous forme de carré coloré. La fonction draw_square(x, y, size, color) permet de dessiner un carré aux coordonnées (x, y) avec une taille spécifiée et une couleur donnée. Elle commence par lever le stylo pour éviter de dessiner en se déplaçant, puis elle se positionne à la coordonnée indiquée. Après avoir abaissé le stylo, elle définit la couleur du carré en utilisant turtle.color(color). Ensuite, elle commence à remplir la forme et dessine le carré en avançant et en tournant à droite de 90 degrés à chaque coin. Une fois le carré terminé, elle termine le remplissage avec end_fill(). La fonction soleil() est responsable du dessin du soleil, elle définit d’abord la taille du carré central (sun_size) à 125 pixels et calcule les coordonnées pour centrer le carré en soustrayant la moitié de sa taille de la position X (sun_x). La position Y (sun_y) est fixée à 250 pour placer le soleil en haut de l’écran. Elle appelle ensuite la fonction draw_square pour dessiner le carré représentant le soleil, en le remplissant de couleur jaune. Enfin, l’appel à soleil() exécute cette fonction, ce qui dessine un grand carré jaune en haut de l’écran, représentant le soleil.
Puis, nous voyons le script permettant de de dessiner les blocs d’herbe :
def herbe():
# Configuration de l'herbe pixelisée
penup()
grass_height = 360 # Moitié de la hauteur de l'écran (720 / 2)
pixel_size = 20 # Taille de chaque pixel (carré)
# Dessiner des "pixels" de différentes nuances de vert dans la moitié inférieure
for y in range(-360, 0, pixel_size):
for x in range(-640, 640, pixel_size):
# Choisir une nuance de vert aléatoire pour chaque carré
green_value = random.randint(200, 255) # Valeur de vert entre 100 et 255
color = "#00%02x00" % green_value # Couleur hexadécimale pour une teinte de vert
goto(x, y)
turtle.color(color)
begin_fill()
for _ in range(4):
forward(pixel_size)
right(90)
end_fill()
# Dessin de l'herbe pixelisée, de la végétation et des fleurs
herbe()
La fonction herbe() est responsable du dessin de l’herbe pixelisée. Elle commence par lever le stylo avec penup() pour éviter de dessiner en se déplaçant. Ensuite, elle définit la hauteur de l’herbe en spécifiant grass_height à 360, ce qui correspond à la moitié de la hauteur de l’écran (720 pixels). La variable pixel_size est définie à 20, ce qui détermine la taille de chaque « pixel » (carré) d’herbe. Le code utilise deux boucles imbriquées pour parcourir les coordonnées de la zone où l’herbe sera dessinée. La première boucle itère sur les coordonnées Y de -360 à 0 (la moitié inférieure de l’écran), et la seconde boucle itère sur les coordonnées X de -640 à 640, couvrant ainsi toute la largeur de l’écran. À l’intérieur de la boucle, une valeur de vert aléatoire est choisie pour chaque carré en utilisant random.randint(200, 255). Cela génère une nuance de vert qui sera plus claire, puisque les valeurs hexadécimales de vert vont de 200 à 255. La couleur hexadécimale est ensuite formatée en utilisant color = "#00%02x00" % green_value, ce qui crée une chaîne de caractères représentant une couleur verte dans le format hexadécimal (ex. #00CC00). Pour chaque pixel d’herbe, le curseur se déplace aux coordonnées (x, y) correspondantes, et la couleur est définie. Ensuite, le remplissage commence avec begin_fill(), et un carré est dessiné en avançant de pixel_size et en tournant à droite de 90 degrés à chaque coin. Une fois le carré terminé, end_fill() termine le remplissage de ce pixel.
Il ne reste plus que deux étapes, parmi celles-ci, on ajoute au code un script permettant de dessiner un arbre.
# Fonction pour dessiner un carré pour les blocs
def draw_block(t, size, color):
t.begin_fill()
t.color(color)
for _ in range(4):
t.forward(size)
t.left(90)
t.end_fill()
# Fonction pour dessiner un arbre style Minecraft
def draw_minecraft_tree(x, y):
block_size = 65 # Taille de chaque bloc pour un effet fidèle
# Tronc de l'arbre en 4 blocs verticaux
trunk = turtle.Turtle()
trunk.hideturtle()
trunk.speed("fastest")
trunk.penup()
trunk.goto(x, y)
trunk.pendown()
for i in range(4): # Tronc de 4 blocs de hauteur
draw_block(trunk, block_size, "saddle brown")
trunk.penup()
trunk.goto(x, y + (i + 1) * block_size)
trunk.pendown()
# Feuillage de l'arbre en blocs
foliage = turtle.Turtle()
foliage.hideturtle()
foliage.speed("fastest")
foliage.penup()
# Position des blocs de feuillage par rapport au tronc
foliage_positions = [
(-1, 4), (0, 4), (1, 4), # Rangée supérieure
(-1, 3), (0, 3), (1, 3), # Rangée au milieu
(-2, 3), (2, 3), # Côtés de la rangée au milieu
(-1, 2), (1, 2), (0, 2) # Rangée inférieure
]
for pos in foliage_positions:
foliage.goto(x + pos[0] * block_size, y + pos[1] * block_size)
foliage.pendown()
draw_block(foliage, block_size, "forest green")
foliage.penup()
# Dessiner l'arbre style Minecraft à gauche
draw_minecraft_tree(-500, -100)
draw_minecraft_tree(150, -100)
draw_minecraft_tree(600, -200)
La fonction draw_block(t, size, color) dessine un carré de couleur spécifiée avec une taille donnée. Elle commence le remplissage, définit la couleur, puis dessine les quatre côtés du bloc en tournant à gauche de 90 degrés après chaque côté, et termine le remplissage une fois le bloc dessiné. La fonction draw_minecraft_tree(x, y) est responsable du dessin d’un arbre aux coordonnées (x, y). Elle fixe la taille de chaque bloc à 65 pixels et crée un Turtle pour dessiner le tronc. Ce Turtle se positionne aux coordonnées données et dessine quatre blocs verticaux pour former le tronc en utilisant une couleur marron. Après avoir dessiné chaque bloc, le Turtle se déplace vers le haut pour la prochaine position. Un second Turtle, destiné au feuillage, est également créé. Il est configuré pour dessiner des blocs de feuillage verts autour du tronc, avec des positions définies dans une liste pour donner une forme arrondie. Pour chaque position de feuillage, le Turtle se déplace aux coordonnées calculées et dessine un bloc de couleur verte. Enfin, la fonction draw_minecraft_tree est appelée trois fois avec différentes coordonnées pour dessiner trois arbres à des endroits variés de l’écran. Ce code crée ainsi une représentation d’arbres en blocs de style Minecraft, composés de carrés pour le tronc et le feuillage.
Enfin, pour donner la touche finale qui va donner vie a notre conception graphique sur le thème de Minecraft, on ajoute un dernier script qui permet de dessiner un Creeper :
# Fonction pour dessiner un sous-bloc "pixel"
def draw_pixel(t, size, color):
t.begin_fill()
t.color(color)
for _ in range(4):
t.forward(size)
t.left(90)
t.end_fill()
# Fonction pour dessiner un bloc pixelisé avec un mélange de couleurs
def draw_pixelated_block(t, x, y, block_size, pixel_size, colors):
t.penup()
for row in range(block_size // pixel_size):
for col in range(block_size // pixel_size):
t.goto(x + col * pixel_size, y - row * pixel_size)
t.pendown()
color = colors[(row + col + (row % 2)) % len(colors)]
draw_pixel(t, pixel_size, color)
t.penup()
# Fonction pour dessiner un visage Creeper précis
def draw_creeper_face(t, x, y, pixel_size):
t.penup()
# Yeux (4 pixels chacun)
t.goto(x + pixel_size, y - 1 * pixel_size)
draw_pixel(t, pixel_size, "black")
t.goto(x + 2 * pixel_size, y - 1 * pixel_size)
draw_pixel(t, pixel_size, "black")
t.goto(x + pixel_size, y - 2 * pixel_size)
draw_pixel(t, pixel_size, "black")
t.goto(x + 2 * pixel_size, y - 2 * pixel_size)
draw_pixel(t, pixel_size, "black")
t.goto(x + 4 * pixel_size, y - 1 * pixel_size)
draw_pixel(t, pixel_size, "black")
t.goto(x + 5 * pixel_size, y - 1 * pixel_size)
draw_pixel(t, pixel_size, "black")
t.goto(x + 4 * pixel_size, y - 2 * pixel_size)
draw_pixel(t, pixel_size, "black")
t.goto(x + 5 * pixel_size, y - 2 * pixel_size)
draw_pixel(t, pixel_size, "black")
# Bouche (3x2 pixels pour la rangée supérieure, 2x1 pixels pour la rangée inférieure)
t.goto(x + 15, y - 4 * pixel_size)
for _ in range(4):
draw_pixel(t, pixel_size, "black")
t.forward(pixel_size)
t.goto(x + 25, y - 3 * pixel_size)
for _ in range(2):
draw_pixel(t, pixel_size, "black")
t.forward(pixel_size)
t.goto(x + 15, y - 5 * pixel_size)
for _ in range(4):
draw_pixel(t, pixel_size, "black")
t.forward(pixel_size)
t.goto(x + 45, y - 6 * pixel_size)
for _ in range(1):
draw_pixel(t, pixel_size, "black")
t.forward(pixel_size)
t.goto(x + 15, y - 6 * pixel_size)
for _ in range(1):
draw_pixel(t, pixel_size, "black")
t.forward(pixel_size)
# Fonction pour dessiner un Creeper Minecraft pixelisé complet
def draw_pixelated_creeper(x, y):
block_size = 70 # Taille de chaque section principale du Creeper
pixel_size = 10 # Taille de chaque pixel
# Couleurs pour le visage et le corps du Creeper
face_colors = ["#2E8B57", "#228B22", "#32CD32", "#006400"]
body_colors = ["#228B22", "#006400", "#32CD32"]
creeper = turtle.Turtle()
creeper.hideturtle()
creeper.speed("fastest")
# Tête du Creeper avec visage
draw_pixelated_block(creeper, x, y, block_size, pixel_size, face_colors)
draw_creeper_face(creeper, x, y, pixel_size)
# Corps du Creeper (2 blocs de hauteur sous la tête)
for i in range(2):
draw_pixelated_block(creeper, x, y - (i + 1) * block_size, block_size, pixel_size, body_colors)
# Pieds du Creeper (2 blocs de largeur sous le corps)
for i in range(2):
draw_pixelated_block(creeper, x - block_size // 2, y - (i + 3) * block_size, block_size // 2, pixel_size, body_colors)
draw_pixelated_block(creeper, x + block_size // 2, y - (i + 3) * block_size, block_size // 2, pixel_size, body_colors)
# Dessiner le Creeper pixelisé à droite
draw_pixelated_creeper(450, 50)
Et voila ! Nous voici avec une image qui représente (plus ou moins 😅) l’univers du jeu vidéo Minecraft, et cela, avec turtle et random !
Les difficultés du projet
Durant ce projet, nous avons eus quelques difficultés a savoir lors de la correction du code ou encore lorsqu’il fallait positionner certains éléments. Nous avons, bien que très peu, utilisés l’AI Chat GPT pour nous guider dans la correction ou encore quelques conceptions de script.
Pour ce premier projet de NSI, nous avons décider de prendre comme thème une image de la Patrouille de France, vous pourrez retrouver ici les démarches par lesquelles nous sommes passées.
Notre vidéo :
Le Fond :
Tout d’abord pour obtenir notre image nous avons créer un fond dégradé bleu :
n = 350
s = (0, 0, 139)
e = (173, 216, 230)
w, h = 1280, 720
def degrade(st, end, step, t_step):
return st + (end - st) * step / t_step
Pour obtenir ce résultat nous avons donc créer les paramètres de notre dégradé, il y en a 4 : le nombre de ligne que va faire la tortue (n) ; la couleur de départ (s) ; la couleur de fin (e) ; La longueur et la largeur (w, h) . Afin que la tortue change petit à petit de couleur, on créait la fonction degrade, qui va donc utiliser 3 paramètres : le nombre de ligne que va faire la tortue ; la couleur de départ ; la couleur de fin et effectuer une opération mathématique.’
for i in range(n):
r=int(degrade(s[0], e[0], i, n))
g=int(degrade(s[1], e[1], i, n))
b=int(degrade(s[2], e[2], i, n))
screen.colormode(255)
pen.color(r, g, b)
Ici, on calcule la nuance de bleu qui va être utilisé à chaque ligne du dégradé.
screen.colormode(255)
pen.color(r, g, b)
screen.colormode(255) : Cette fonction définit le mode de couleur de l’écran sur une échelle de 0 à 255. Par défaut, Turtle utilise une échelle de couleur de 0 à 1 (où chaque couleur est une valeur décimale entre 0 et 1).
pen.color(r, g, b) : Cette fonction définit la couleur du stylo en utilisant trois valeurs pour le rouge (r), le vert (g), et le bleu (b). Ces valeurs doivent être comprises entre 0 et 255.
pen.goto(-w/2, h/2 - i * (h / n))
pen.begin_fill()
for _ in range(2):
pen.forward(w)
pen.right(90)
pen.forward(h / n)
pen.right(90)
pen.end_fill()
Ici, on délimite le rectangle dans lequel le dégradé doit être créer.
Les avions :
Ensuite nous avons créer une fonction avion qui créer nos avions :
Tout d’abord, nous avons créé une fonction avion. Dans cette partie du code, nous dessinons la silhouette de l’avion à l’aide de goto(), nous lui donnons une couleur avec color(), puis nous la remplissons avec begin_fill() et end_fill().
Pour créer les traîners, nous avons simplement créé une fonction de base où nous avons changé la couleur.
Les problèmes rencontrés :
Lorsque nous avons voulu générer nos avions avec les traînées à l’arrière nous avons obtenue l’image suivante :
Le problème venait du fait que nous avions dessiner les avions avant de dessiner les traînées. Donc les traînées sont apparues par dessus les avions.
Nos sources :
Afin de réaliser le code nous avons utiliser le site python.org, ainsi que nos connaissances. Nous avons aussi pris le temps de regarder les travaux des années précédant de la catégories art.
Dans le cadre de notre projet d’art génératif en Première NSI, nous avons décidé de concevoir un paysage en perspective qui représente Paris pendant les Jeux Olympiques de 2024. Notre objectif est de mêler des éléments architecturaux emblématiques de la ville avec l’ambiance olympique qui y règne.
Cet été 2024 à Paris a eu lieu les JO. Les JO sont des événements sportifs internationaux majeurs, regroupant les sports d’été ou d’hiver, auxquels des milliers d’athlètes participent à travers différentes compétitions tous les quatre ans, pour chaque olympiade moderne.
Pour plus d’informations, nous vous conseillons cette vidéo explicative de l’origine des JO.
Le Projet
Revenons au projet. Nous avons choisi de créer une image des Champs Elysées pendant la période des JO 2024. Pour cela, nous utilisons les modules turtle et random. Le module turtle permet de dessiner l’image, tandis que le module random génère aléatoirement la taille et la position des étoiles dans le ciel. Nous avons également intégré un script pour exporter l’image générée par turtle en format .png, que vous pouvez retrouver ici.
Structure du script
Nous avons structuré le script en séparant chaque élément de l’image (fond dégradé, étoiles, route, trottoirs, arc de triomphe, anneaux olympiques, bâtiments, arbres) dans des fonctions distinctes. Ensuite, nous avons appelé ces fonctions dans un ordre spécifique, permettant ainsi à chaque élément de se superposer correctement dans son propre plan.
Analyse du script
Nous allons donc analyser notre script.
On commence d’abord par l’appel des modules utilisés et la mise en place turtle.
from turtle import *
from random import randint
# Vérification des modules importés
try:
from PIL import Image
pillow_installed = True
except:
print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :")
print("https://nsi.xyz/py2png")
pillow_installed = False
# Définir le titre de la fenêtre de turtle
titre = "Perspective Paris 2024 - construite avec turtle"
title(titre+" | Au lycée, la meilleure spécialité, c'est la spé NSI")
colormode(255) # Permet l'utilisation de couleurs rgb
setup(1280, 720) # Définir la taille de la fenêtre en 720p
speed(0) # La tortue va à la vitesse du lièvre
hideturtle() # La tortue active sa cape d'invisibilité
flash = True # False par défaut, on peut mettre True sinon, ou mieux 0x2A
if flash:
wn = Screen()
wn.tracer(0)
On utilise les modules turtle et random, en se concentrant uniquement sur la fonction randint de random. Le script inclut également une partie pour exporter une image générée par turtle. Cela permet de vérifier si l’utilisateur a installé le module PIL ; si ce n’est pas le cas, un message d’erreur s’affiche avec un lien pour l’installer, sans interrompre l’exécution du script. Ensuite, on définit le titre de la fenêtre qui affichera le rendu ainsi que sa taille. On configure également le type de couleurs utilisées (R,G,B), la vitesse de la tortue et on cache la tortue pour une présentation plus esthétique.
Puis, voici notre première fonction : le fond dégradé sur la partie haute de l’image.
def fond():
red, green, blue = 0, 0, 100
penup()
hauteur = 0
penup(), goto(-641, hauteur)
while hauteur != 360:
pendown()
pencolor(round(red), round(green), round(blue))
forward(1280)
hauteur += 1
goto(-640, hauteur)
blue = blue - float(100 / 360)
Pour le fond, nous avons donc choisi d’utiliser un dégradé. Le script fait avancer la tortue sur une ligne d’un pixel de large et à la fin de chaque ligne, la tortue est envoyée à la ligne suivante grâce à un goto. À chaque déplacement, on ajuste progressivement les valeurs des couleurs, de façon arrondie (dans notre cas, on ajuste seulement la valeur du bleu) en fonction de la différence entre la couleur de début et celle de fin du dégradé.
Ensuite, vient la deuxième fonction, celle des étoiles dans le ciel.
def etoiles (x_max1, x_max2, y_max1, y_max2, r_max1, r_max2, nb_etoiles):
for i in range(nb_etoiles):
x, y, r = randint(x_max1,x_max2), randint(y_max1,y_max2), randint(r_max1,r_max2)
pencolor("#FFFFFF"), begin_fill()
penup(), goto(x, y), pendown()
circle (r)
fillcolor("#FFFFFF"), end_fill()
Pour les étoiles, leur position dans le ciel et leur taille, variant entre 1 et 3 pixels de rayon, sont déterminées aléatoirement avec la fonction randint du module random. Ensuite, un cercle est tracé à chaque position générée avec la taille correspondante. Ce processus est répété 100 fois dans une boucle for i in range, afin de créer 100 étoiles au total.
Vient ensuite la fonction pour dessiner la route et les trottoirs (le script suivant n’est qu’une partie du code complet, long et répétitif, il n’est donc pas nécessaire de commenter la suite).
Pour la route et les trottoirs, nous commençons par tracer la base de la route en perspective en utilisant la fonction goto, puis nous ajoutons les marquages au sol. La ligne centrale entre les deux voies est progressivement affinée pour renforcer l’effet de perspective. Ensuite, les trottoirs de chaque côté sont tracés avec goto, en appliquant les mêmes proportions pour respecter la perspective. Enfin, une fonction finale regroupe toutes les fonctions constituant notre route et nos trottoirs.
Nous poursuivrons ensuite avec le codage de l’Arc de Triomphe (le script est encore long, il ne sera donc pas intégral ici).
def pilier_gauche(x, y, larg, long, col):
pencolor(col)
begin_fill(), fillcolor(col)
penup(), goto(x - larg,y), pendown()
right(70)
for i in range(2):
forward(larg), left(90), forward(long - 40), left(90)
end_fill()
def atique_arc_de_triomphe(x, y, larg, long, col):
pencolor(col)
penup(), goto(x + larg, y + long - 40), left(90), pendown()
begin_fill()
circle((1 / 2 * larg), 180)
for i in range(2):
right(90), forward(larg)
right(90), forward(3 / 2 * (long))
for i in range(2):
right(90), forward(larg)
fillcolor(col), end_fill()
def rajout_atique(x, y, larg, long, col):
pencolor(col)
penup(), goto(x + (long), y + ((long - 40) + larg)), pendown()
begin_fill(), fillcolor(col)
right(90)
for i in range(2):
forward(larg - (1 / 2 * larg)), left(90), forward((long) + larg), left(90)
end_fill()
def details_arc_de_triomphe(x, y, larg, long, col_detail):
penup(), goto(x + (long), y + (long - 40 + larg)), pendown()
pensize(5), pencolor(col_detail)
left(90), forward(long + larg)
def arc_de_triomphe(x, y, larg, long, col):
pilier_gauche(x, y, larg, long, col)
pilier_droite(x, y, larg, long, col)
atique_arc_de_triomphe(x, y, larg, long, col)
rajout_atique(x, y, larg, long, col)
details_arc_de_triomphe(x, y, larg, long, "#9C7F6E")
Pour l’Arc de Triomphe, nous avons choisi de le coder étape par étape : d’abord les piliers, puis l’attique, et enfin les détails. Les piliers sont représentés par de simples rectangles colorés, en utilisant les commandes begin_fill, fillcolor, et end_fill. Pour l’attique, nous avons dessiné un arc de cercle avec circle(rayon, 180) pour représenter la courbure. Enfin, toutes les fonctions constituant l’Arc de Triomphe sont appelées dans une fonction unique pour générer l’ensemble du monument.
Passons maintenant au code pour générer les anneaux olympiques.
Nous avons commencé par définir une liste des couleurs pour chaque anneau. Ensuite, les anneaux de la rangée supérieure ont été dessinés, suivis de ceux de la rangée inférieure, en utilisant la fonction circle. Enfin, nous avons regroupé le tout dans une fonction unique qui positionne les anneaux correctement.
Nous poursuivrons ensuite avec le script dédié à la lune et à ses cratères (le code des cratères étant répétitif, il ne sera pas présenté dans sa totalité).
def cratere1 (x, y, rayon_lune, col):
x_crat1 = x
y_crat1 = y + rayon_lune + (1 / 4 * rayon_lune)
taille_crat1 = 1 / 3 * rayon_lune
penup(), goto(x,y), goto(x_crat1,y_crat1), pendown()
begin_fill(), pencolor(col)
circle(taille_crat1)
fillcolor(col), end_fill()
def crateres(x, y, rayon_lune, col):
cratere1 (x, y, rayon_lune, col)
cratere2 (x, y, rayon_lune, col)
cratere3 (x, y, rayon_lune, col)
cratere4 (x, y, rayon_lune, col)
cratere5 (x, y, rayon_lune, col)
def lune(x, y, rayon_lune, col_lune):
left(180)
begin_fill()
penup(), goto(x,y), pendown()
pencolor(col_lune), circle(rayon_lune), fillcolor(col_lune), end_fill()
crateres(x, y, rayon_lune, "#DEDCDA")
A propos de la lune et de ses cratères, premièrement, chaque fonction (cratere1 à cratere5) dessine un cratère à des positions spécifiques autour de la lune, en utilisant le rayon de la lune pour ajuster la taille et la position. Deuxièmement, la fonction crateres regroupe tous les cratères en une seule fonction. Et au final, la fonction lune dessine la lune en tant que cercle rempli, puis appelle la fonction crateres pour ajouter les cratères à la surface de la lune.
Nous allons maintenant aborder le codage des bâtiments.
Pour les bâtiments, les fonctions dessinent des structures à droite et à gauche, en utilisant des coordonnées spécifiées, une couleur de remplissage, une couleur de contour et une hauteur. Chaque fonction remplit le bâtiment et trace un toit incliné, créant ainsi un effet de perspective qui enrichit visuellement l’image.
Nous allons maintenant découvrir le script qui permet de dessiner des arbres le long des trottoirs.
def tronc(x, y, larg_tronc, col_tronc, long_tronc):
begin_fill()
penup(), goto(x,y), pendown()
pencolor(col_tronc)
for i in range(2):
forward(larg_tronc), left(90), forward(long_tronc), left(90)
fillcolor(col_tronc), end_fill()
def feuilles(x, y, col_feuilles, ray_feuilles, larg_tronc, long_tronc, col_tronc):
begin_fill()
penup(), goto(x + (1 / 2 * larg_tronc),y + long_tronc), pendown()
pencolor(col_feuilles)
for i in range(5):
begin_fill()
circle(ray_feuilles), left(72), forward(10)
fillcolor(col_feuilles), end_fill()
def arbre(x, y, col_feuilles, ray_feuilles, larg_tronc, long_tronc, col_tronc):
tronc(x, y, larg_tronc, col_tronc, long_tronc)
feuilles(x, y, col_feuilles, ray_feuilles, larg_tronc, long_tronc, col_tronc)
# On place les arbres sur le trottoir
def arbres():
x = -275
y = -25
ray_feuilles = 20
larg_tronc = 22
long_tronc = 80
right(195)
for i in range(14):
arbre(x, y, "#009B1A", ray_feuilles, larg_tronc, long_tronc, "#7F2C00")
x -= 25
y -= 25
ray_feuilles += float(2.5)
larg_tronc += float(2.5)
long_tronc += 10
x = 258
y = -25
ray_feuilles = 20
larg_tronc = 22
long_tronc = 80
for i in range(14):
arbre(x, y, "#009B1A", ray_feuilles, larg_tronc, long_tronc, "#7F2C00")
x += 22
y -= 25
ray_feuilles += float(2.5)
larg_tronc += float(2.5)
long_tronc += 10
Concernant les arbres, tout d’abord, la fonction tronc dessine le tronc d’un arbre en utilisant des coordonnées spécifiques, une largeur, une couleur et une longueur. Elle utilise une boucle pour créer un rectangle rempli représentant le tronc. Après, la fonction feuille dessine les feuilles de l’arbre. Elle positionne le curseur au-dessus du tronc et utilise une boucle pour créer plusieurs cercles qui représentent les feuilles, disposés en étoile autour de la partie supérieure du tronc. Suite à quoi la fonction arbre combine les fonctions tronc et feuilles pour dessiner un arbre complet à des coordonnées données. Pour couronner le tout, la fonction arbres place plusieurs arbres le long des trottoirs. Elle commence à des coordonnées spécifiques et utilise une boucle pour dessiner 14 arbres de chaque côté. La position, la taille des feuilles, la largeur et la longueur du tronc augmentent progressivement pour créer un effet de perspective, tandis que les arbres se déplacent vers le bas et de manière symétrique des deux côtés.
Enfin, nous appelons chaque fonction dans un ordre spécifique pour produire l’image finale.
En se basant sur une photo du magnifique virage Auteuil, emblématique du Parc des Princes, stade du Paris-Saint-Germain, nous avons reproduit sous un angle de vue différent cette image. Cette reproduction met en avant la disposition du stade, du virage, mais surtout les couleurs emblématiques de ce club. Notre image possède une perspective appuyant bien sur les aspects clés de l’image cités auparavant.
Pour le fond, nous avons appelé deux fonctions rectangle chacune ayant la même hauteur et la même largeur pour respecter les proportionnalités et les avons choisies rouge et bleu en référence aux couleurs du club.
rectangle modifiable
# rectangle modifiable
def rectangle(x,y,z,g,a,b):
def longueur(a):
a == 0 + a
forward(a)
def largeur(b):
b == 0 + b
forward(b)
pensize(z)
speed(100)
# Remplissage selon la couleur donnée
penup()
begin_fill()
color(g)
goto (x,y), pendown(),
for i in range(2):
longueur(a), right(90), largeur(b), right (90)
end_fill()
penup()
rectangle(-858,538,1,"red",858,1076)
rectangle(858,538,1,"blue",-858,1076)
Pour commencer à dessiner une perspective, nous avons réalisé une pelouse composée de formes géométriques différentes en prenant pour exemple notre image référence. Les teintes sont différentes en fonction des zones sur la pelouse pour réaliser les sens de tonte et se rapprocher ainsi au maximum d’un vrai stade.
Toujours en appelant la fonction rectangle utilisée au tout début, nous avons réalisé un panneau publicitaire de couleur grise, comme on le voit dans les stades.
Panneau publicitaire
rectangle(-858,-377,1,"grey",1716,37)
Pour commencer la perspective des gradins, nous avons tracé des courbes horizontales qui descendent en se rapprochant du centre.
Perspective des tribunes avec traits horizontaux gris
penup(), goto(-858,40), pendown()
penup(), goto(-858,130), pendown()
def trait_coupe():
for x in range(-858,864):
y = -90+(x / 300) ** 4
goto(x,y)
pendown()
trait_coupe()
penup(), goto(-858,190), pendown()
def trait_coupe1():
for x in range(-858,864):
y = -30+(x / 300) ** 4
goto(x,y)
pendown()
trait_coupe1()
penup(), goto(-858,250), pendown()
def trait_coupe2():
for x in range(-858,864):
y = 30+(x / 300) ** 4
goto(x,y)
pendown()
trait_coupe2()
penup(), goto(-858,310), pendown()
def trait_coupe3():
for x in range(-858,864):
y = 90+(x / 300) ** 4
goto(x,y)
pendown()
trait_coupe3()
penup(), goto(-858,370), pendown()
def trait_coupe4():
for x in range(-858,864):
y = 150+(x / 300) ** 4
goto(x,y)
pendown()
trait_coupe4()
penup(), goto(-858,430), pendown()
def trait_coupe5():
for x in range(-858,864):
y = 210+(x / 300) ** 4
goto(x,y)
pendown()
trait_coupe5()
penup(), goto(-858,490), pendown()
def trait_coupe6():
for x in range(-858,864):
y = 270+(x / 300) ** 4
goto(x,y)
pendown()
trait_coupe6()
penup(), goto(-858,550), pendown()
def trait_coupe7():
for x in range(-858,864):
y = 330+(x / 300) ** 4
goto(x,y)
pendown()
trait_coupe7()
penup(), goto(-858,70), pendown()
def trait_coupe11():
for x in range(-858,864):
y = -150+(x / 300) ** 4
goto(x,y)
pendown()
trait_coupe11()
penup(), goto(-858,10), pendown()
def trait_coupe12():
for x in range(-858,864):
y = -210+(x / 300) ** 4
goto(x,y)
pendown()
trait_coupe12()
# Zone grise au dessus des gradins
def zone_grise():
pensize(1)
color("grey")
penup(), goto(-858,438), begin_fill(), pendown(), trait_coupe7(), goto(858,476), goto(-858,476), goto(-858,438), end_fill(), penup()
zone_grise()
# Zone grise en bas des gradins
def zone_grise_1():
pensize(1)
color("grey")
penup(), goto(-858,10), begin_fill(), pendown(), trait_coupe12(), goto(858,-377), goto(-858,-377), goto(-858,10), end_fill(), penup()
zone_grise_1()
Pour approfondir cette perspective, nous avons décidé de compléter la zone sous la banderole et au dessus de l’herbe avec du gris foncé.
Remplissage avec du gris foncé
# Zone grise au dessus des gradins
def zone_grise():
pensize(1)
color("grey")
penup(), goto(-858,438), begin_fill(), pendown(), trait_coupe7(), goto(858,476), goto(-858,476), goto(-858,438), end_fill(), penup()
zone_grise()
# Zone grise en bas des gradins
def zone_grise_1():
pensize(1)
color("grey")
penup(), goto(-858,10), begin_fill(), pendown(), trait_coupe12(), goto(858,-377), goto(-858,-377), goto(-858,10), end_fill(), penup()
zone_grise_1()
Pour donner une impression de hauteur en plus de la perspective dans la largeur, nous avons dessiné des traits verticaux qui font se rétrécir les cases au fur et à mesure que l’on se déplace sur les côtés de l’image.
Pour la cage, nous avons réalisé une fonction cage contenant une fonction pour les filets, une pour les montants et une pour les poteaux à côté. Tous sont reliés par des traits de contour blancs qui donnent cette impression de perspective. Un trait de contour noir se trouve en bas pour séparer la pelouse du reste.
Pour former la banderole bleu foncée, nous avons fait appel à la fonction rectangle et pour tracer les lettres, nous avons utilisé des rectangles, des cercles et une fonction trapèze spécialement conçue pour la queue du « R »
Nous avions donc comme premier projet de première de réaliser une image qui se génère grâce à code python que nous devions créer sur une durée de un mois. Nous avons donc choisi de générer un F-16, F-22 et F-35 pour ce projet. Nous devions utiliser le module turtle pour l’image.
Origines de ces avions
Le F-16 est un avion de combat à rôles multiples de l’armée américaine créé dans les années 70. Le F-22 est un avion de chasse furtif de l’armée américaine créé dans les années 90. Le F-35 est un avion de combat à rôles multiples de l’armée américaine créé dans les années 2000.
Organisation
Pour réussir à faire le script nous avons dû séparer les différentes parties du code pour s’y repérer. Cela nous a donc permis de faire des appels de fonctions dans le code (rectangle, triangle, étoile, trapèze). Faire appel à des fonctions permet de réduire la taille du code, cela été utile étant donné la limite de 404 lignes.
Le Script
Nous allons commencer par le début du code
from turtle import *
from random import randint
try:
from PIL import Image
pillow_installed = True
except:
print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :")
print("https://nsi.xyz/py2png")
pillow_installed = False
titre = "Avions de l'armée américaine - F16 F22 F35"
title(titre+" | Au lycée, la meilleure spécialité, c'est la spé NSI")
setup(1280, 720)
speed(0)
colormode(255)
hideturtle()
flash = True
if flash:
wn = Screen()
wn.tracer(0)
Il permet d’importer les modules nécessaires et initialiser la plupart du code (comme la couleur ou pillow). « flash » est une nouveauté, il permet de générer instantanément l’image.
LA SAUVEGARDE DES IMAGES
Pour sauvegarder l’image on nous a donné ce code ci-dessous :
image = getcanvas()
nom_du_fichier_sans_extension=titre+"_"+hex(randint(2**30+2**25,2**30+2**25+2**24-1))[2:]
# Génère un fichier .ps
image.postscript(file=nom_du_fichier_sans_extension+".ps", colormode='color')
# Ouvre le fichier .ps et génère le fichier .png
try:
psimage = Image.open(nom_du_fichier_sans_extension+".ps")
psimage.load(scale=2)
psimage_resized = psimage.resize((1280, 720))
psimage.save(nom_du_fichier_sans_extension+".png")
print(nom_du_fichier_sans_extension+".png", psimage.size, "sauvegardé dans le dossier")
except:
if not pillow_installed:
print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :")
print("https://nsi.xyz/py2png")
else:
print("Oops! - 'ghostscript' not installed- RTFM :")
print("https://nsi.xyz/py2png")
Or nous avions besoin d’enregistrer plusieurs images pour montrer les différentes étapes. Nous avons donc transformé ce code en une fonction nommée save() qui permet de sauvegarder l’image à n’importe quel moment dans le code.
LE FOND
La première fonction « drapeau » est le fond de l’image
def drapeau():
ligne(-640,640,13)
rectangle(-640,-24,640,-385,0,(35,45,100))
pos_y = 325
for j in range(1,6):
for i in range(1,7):
étoile(-700+106*i,pos_y,30)
pos_y = pos_y-70
pos_y = 290
for j in range(1,5):
for i in range(1,6):
étoile(-647+106*i,pos_y,30)
pos_y = pos_y-70
Cette fonction fait elle-même appel à d’autres fonctions. Commençons par la première fonction : « ligne »
def ligne(x,y,repeat):
for i in range(repeat):
rectangle(x,y,1280,-720/13,0,(150,20,30))
y = y-720/13
rectangle(x,y,1280,-720/13,0,(255,255,255))
y = y-720/13
La fonction ligne permet de faire toutes les lignes du drapeau soit 13 lignes ou 7 rouges et 6 blanches. Et à chaque ligne il descend de la résolution de l’image divisée par 13 (car il y a 13 lignes)
Ensuite nous avons la création du rectangle bleu en haut à gauche du drapeau.
rectangle(-640,-24,640,-385,0,(35,45,100))
Ensuite il y a la fonction « étoile »
def étoile(x,y,L):
setup(x,y,(255,255,255))
pensize(5)
for i in range(5):
forward(L)
right(144)
pensize(1)
end_fill()
La fonction « étoile » utilise « setup » qui est une fonction qui permet de faire le début de toutes les formes et qui est donc utilisée dans la plupart des fonctions. Cette fonction permet de créer une étoile et avec notre boucle « for » utilisée dans la fonction drapeau on peut créer ainsi les 50 étoiles.
PREMIER AVION : F-16
Le premier avion se situera à gauche de l’image, nous avons donc fait le plus ancien en premier : le F-16
Avant même d’avoir commencé à coder, on a refait les avions avec des formes géométriques pour savoir quelles formes créer et où les mettre. Voici comment nous avions découpé le F-16 avec plusieurs formes géométriques (des triangles et des rectangles).
# ======================== F-16 ========================
def F16_plane(x,y):
triangle_iso(-50 + x, -90 + y, 100, 100, (100, 100, 100))
triangle_iso(-100 + x, -20 + y, 200, 100, (80, 80, 80))
triangle_iso(-25 + x, 60 + y, 50, 180, (100, 100, 100))
rectangle(-100 + x, -20 + y, 200, 20, 0, (70, 70, 70))
rectangle(-10 + x, -110 + y, 20, -250, 0, (100, 100, 100))
rectangle(-20 + x, -70 + y, 40, -130, 0, (100, 100, 100))
for i in range(0, 66, 65):
rectangle(-50 + i + x, -110 + y, 35, -20, 0, (80, 80, 80))
for i in range(0, 203, 202):
rectangle(-102 + i + x, -42 + y, 2, -55, 0, (70, 70, 70))
# ======================== COCKPIT F-16 ========================
rectangle(-10 + x, 130 + y, 20, 50, 0, (10, 10, 10))
triangle_iso(-10 + x, 80 + y, 20, -15, (10, 10, 10))
cercle(10, 0 + x, 130 + y, (10, 10, 10))
Nous avons donc utilisé ces formes là pour créer l’avion puis ensuite fait un cockpit en noir. On a joué avec l’obscurité des couleurs pour créer un effet de profondeur.
DEUXIEME AVION : F-22
Le deuxième avion se situera au milieu de l’image, ce sera donc le F-22
Tout comme le F-16 nous avons d’abord refait le F-22 avec des formes géométriques. Voici comment nous avons refait le F-22 avec les formes géométriques (des triangles et des rectangles).
La forme finale n’est pas exactement la même car nous avons décidé de modifier certaines choses pour que la forme soit plus ressemblante.
Pour éviter de créer une fonction losange nous avons mis l’orientation de base de la fonction rectangle donc en mettant l’orientation à 45° ou -45° cela nous a permis d’avoir des losanges.
TROISIEME AVION : F-35
Le troisième avion se situera à droite de l’image, ce sera donc le plus récent : le F-35
Tout comme les deux autres avions nous avons d’abord refait le F-35 avec des formes géométriques. Voici le schéma du F-35 fait avec des formes géométriques (triangles, rectangles et trapèzes). Cet avion est celui que nous avons dû le plus modifier
Le F-35 fut le plus compliqué à faire étant donné la fonction trapèze très complexe à utiliser mais après un moment nous avons réussit à le finir.
LE NOM DES AVIONS
Maintenant que nous avons les différents avions nous avons décidé de mettre leur nom sous chacun d’eux. Pour cela, il nous fallait générer les caractères : F – 1 2 3 5 6
Donc nous avons créé 7 fonctions pour les 7 caractères différents.
Linkin Park est un groupe de rock et de métal américain formé en 1996. Il est actuellement composé de Emily Armstrong au chant à la suite du décès de Chester Bennington, Colin Brittain à la batterie à la suite du départ de Rob Bourdon, Mike Shinoda au chant(rap), à la guitare rythmique et au clavier, Brad Delson à la guitare solo, Dave Farrell à la guitare basse et Joe Hahn aux platines, effets et mixages.
Le fond
C’est un dégradé allant du blanc au noir grâce à une boucle for qui dessine les traits de couleurs au fur et à mesure. J’ai choisis ces teintes car le symbole et le nom du groupe sont habituellement représentés en noir avec un fond blanc ou inversement.
Le logo du groupe Linkin Park a beaucoup changé au cours du temps et cette forme est sûrement ma préférée. Chaque angle de l’hexagone est censé symboliser un des membres de la bande (six au total). Pour le codage du logo ; il est composé d’un goto(x,y) pour définir où la figure commencera. Ensuite, la fonction circle() me servira deux fois pour les deux arc de cercle et enfin je ferais tous les angles à l’aide des fonction setheading() pour définir l’angle de direction du trait et forward() pour définir la longueur de ce trait.
Pour écrire « Linkin » j’ai d’abord définit le i avec la fonction def pour le réutiliser dans d’autre lettres comme la barre du L, du N, du K et les deux barres du dernier N. J’ai ensuite utilisé les fonctions goto(x,y) pour commencer de nouvelles lettres aux coordonnées voulues, setheading() et forward() comme pour le logo. Mais j’ai aussi commencé à appliquer les fonction begin_fill() et end_fill() qui permette de remplir une portion de dessin de la couleur définit par fillcolor(r,g,b) tant que tous les pointes sont reliés.
Pour l’écriture de « Park », j’ai procédé de la même manière qu’avec « Linkin » : j’ai crée une fonction avec def qui a la même forme que la fonction i mais avec des mesures plus grandes et qui s’appelle donc I(i majuscule). J’ai réutilisé les fonctions circle() pour les boucles du P et du R ; c’est d’ailleurs le même bout de code grâce aux fonctions setheading() et forward() (on aurait été de changer toutes les données avec des goto(x,y). Le K, lui, est le même que le petit k mais avec des mesures plus grandes. Pour l’intérieur du A j’ai fais un triangles avec les fonctions setheading() et forward() puis j’ai essayé de le remplis avec begin_fill() et end_fill() de la même couleur que le fond.
Pour notre premier projet de la spé NSI dans le lycée Louis Pasteur, on nous à demandé de réaliser une image déssinée en Python. Nous avions libre choix de réaliser le dessin que l’on desirait, on à donc décidé de réaliser un sujet qui nous passione – le Pixel Art. Seulement, déssiner un pixel art en python demande beaucoup de travail et de réflexion.
Pour valoriser encore plus notre travail, nous avons décidé de rendre cette image animée.
C’est pour cela que le prof nous à suggeré d’utiliser les fonctions, et dans la suite de cet article, nous vous présenterons les techniques qui nous ont permi d’optimiser et de réaliser notre animation représentant un Pixel Art d’une nuie étoilée.
Schéma Original
Tout d’abord, Léandre à commencé par schématiser notre idée sur un logiciel de déssin pixelart – Aseprite.
Nous avons décidé de transcrire seulement les rideaux et la fenêtre en python, sinon le travail était trop manuel, long, et ne demandait pas tant de réfléxion.
Reconstitution des rideaux en Python
Commençons par le commencement :
Nous avons défini les constantes suivantes, qui vont nous aider plus tard.
Puis, nous avons transcrit manuellement le schéma en python à l’aide de boucles for, et d’une fonction spéciale, conçue par Clovis, nommée draw_pixel_symmetric(couleur, x, y).
Pourquoi symmetric? Car cela nous permet de réaliser 2 rideaux en même temps, symétrique à l’axe vertical, en 2 fois moinsde lignes !
D’ailleurs, fun fact. Nous avions pris plus d’une heure à trouver comment réaliser cette symétrie. Clovis était parti dans l’écriture d’un algorithme de dessin de pixels sous forme de dictionnaires {"y = 0": ["Couleur du pixel en x = 0", "Couleur du pixel en x = 1", "etc.."], "y 1":["..."]} avant de trouver une solution plus simple, efficace qui occupe un peu moins de 10 lignes.
def draw_pixel_symmetric(couleur, x, y):
# OG PIXEL
penup()
goto(x, y)
pendown()
color(couleur)
begin_fill()
for _ in range(4):
forward(PIXEL_SIZE)
left(90)
end_fill()
penup()
# SYMMETRIQUE PIXEL
goto(-x, y)
pendown()
begin_fill()
for _ in range(4):
forward(PIXEL_SIZE)
left(90)
end_fill()
penup()
C’est évident – au lieu d’avoir un seul pixel en x, y, nous l’avons dupliqué et placé le doublon en -x, y.
Seulement, si c’était si simple.. Nous avons crée la fonction rideaux() où se focalise la transcription manuelle. Merci à Léandre d’avoir transcrit l’entièreté du rideau (2h de travail : 1400 lignes de code).
def rideaux():
x = -SCREEN_WIDTH//2
y = -SCREEN_HEIGHT//2 + PIXEL_SIZE
for _ in range(2):
draw_pixel_symmetric('#9e1b1b',x,y)
x = x + PIXEL_SIZE
x = -SCREEN_WIDTH//2
y = -SCREEN_HEIGHT//2 + PIXEL_SIZE * 2
draw_pixel_symmetric('#9e2828',x,y)
x = x + PIXEL_SIZE
draw_pixel_symmetric('#ac3232',x,y)
x = x + PIXEL_SIZE
# ... Et la suite dont on vous fait part d'abstraction.
rideaux() sans système de symétrie
rideaux() avec draw_pixel_symmetric(). Et voici donc le résultat final. Pas mal non ? Ce n’est seulement que la première étape.
Système de gradient
Le système de gradient, développé par Clovis, prend 5 arguments :
colors (liste ["couleur 1", "couleur 2"])
x_start
y_start
x_end
y_end
Sans oublier une fonction qui converti l’héxadécimal en décimal, pour pouvoir manipuler des couleurs RGB, et assurer une transition des couleurs de manière fluide.
def hex_to_rgb(hex_color):
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
def draw_gradient(colors, x_start, y_start, x_end, y_end):
penup()
setheading(0) # tortue regarde à l'est
current_y = y_start
total_steps = y_end - y_start
start_rgb = hex_to_rgb(colors[0])
end_rgb = hex_to_rgb(colors[1])
while current_y <= y_end:
proportion = (current_y - y_start) / total_steps
interpolated_rgb = tuple(
int(start + (end - start) * proportion)
for start, end in zip(start_rgb, end_rgb)
)
pencolor(interpolated_rgb)
penup()
goto(x_start, current_y)
pendown()
forward(x_end - x_start)
current_y += 1
Expliquons la fonction draw_gradient()plus en détail :
Celle-ci effectue une boucle while qui s’arrête lorsque current_y dépasse ou est égal aux coordonnées y de destination (y_end).
Pour calculer la valeur RGB correspondante à la position de current_y, nous effectuons une boucle for qui consiste à construire un tuple contenant les valeurs rouge, vert, et bleu en fonction de start_rgb, end_rgb et la valeur qui change selon current_y : proportion.
Résultat avec les valeurs hexadécimales ["#4a4c9b", "#050758"].
Appel de la fonction complète : draw_gradient(["#4a4c9b", "#050758"], -SCREEN_WIDTH//2, -SCREEN_HEIGHT//2, SCREEN_WIDTH//2, SCREEN_HEIGHT//2)
Les étoiles
Pour pimenter notre gradient, nous avons placé des étoiles à des emplacements aléatoires.
Voici l’algorithme que nous avions développé.
def initialize_positions(count, screen_width, screen_height):
# liste avec des tuples dedans PRCQ ON ADORE LES TUPLES !!!!! TOUCHE PAS A MON TUPLE !!!
positions = []
for _ in range(count):
positions.append((randint(-screen_width // 2, screen_width // 2), randint(-screen_height // 2, screen_height // 2)))
return positions
def draw_stars(stars):
for (x, y) in stars:
draw_pixel(2, x, y, '#ffffff')
stars = initialize_positions(STAR_COUNT, SCREEN_WIDTH, SCREEN_HEIGHT)
draw_stars(stars)
Pour générer les positions aléatoires, on à décidé de créer une fonction initialize_positions(count, screen_width, screen_height) qui prend en compte les arguments correspondant au montant désiré, la largeur et la hauteur de l’écran. Cette fonction renvoie une liste [] remplie de tuples (x, y) contenant les positions aléatoires en x et y. Créer cette fonction nous permettra d’optimiser encore plus notre système de flocons dans la prochaine section de cet article.
Puis, une fonction draw_starts(stars) à été réalisée pour looper dans chaques tuples contenus dans la liste stars et placer un pixel de taille 2 à l’emplacement correspondant.
Gradient + Etoiles
Gradient + Etoiles + Rideaux
Les compléments des rideaux
Pour ajouter un peu plus de détails, nous avons décidé de rajouter un mur en bas de l’image et un cadre de fenêtre.
De plus, nous avons détaillé les couleurs en rajoutant un système de randomization pour altérner les couleurs marron (claires & foncées). Pour faire cela, il fallait inventer un système pour que chaques pixels ai une variation de marron propre à celui-ci. (Vu qu’il y a une animation plus tard, faire colour_variations_marron[randint(0, 1)] changerait la couleur du pixel à chaques générations).
colour_variations_marron = []
# initialisation des variations de couleurs une seule fois
colour_variations_marron = tuple(
tuple("#63352d" if randint(0, 1) == 0 else "#5e3129" for _ in range(SCREEN_HEIGHT))
for _ in range(SCREEN_WIDTH)
)
def supplement_rideaux():
x = -SCREEN_WIDTH//2
y = SCREEN_HEIGHT//2
# Barre horizontale
for _ in range(44):
draw_pixel_symmetric('#4e2b25',x,PIXEL_SIZE * 2.2)
x = x + PIXEL_SIZE
x = 0 - PIXEL_SIZE * 12
y = SCREEN_HEIGHT//2
# Double Barre Verticale
for _ in range(45):
color = colour_variations_marron[round(x)][round(y)]
draw_pixel_symmetric(color, x, y)
draw_pixel_symmetric(color, x - PIXEL_SIZE // 1.5, y)
y -= PIXEL_SIZE
x = -SCREEN_WIDTH//2
y = -SCREEN_HEIGHT//2 + PIXEL_SIZE * 8
# Barre horizontale au dessus du mur
for _ in range(43):
draw_pixel_symmetric('#4e2b25',x,y)
x = x + PIXEL_SIZE
x = -SCREEN_WIDTH//2
y = -SCREEN_HEIGHT//2
# Mur en bas
for _ in range(7):
for _ in range(22):
for _ in range(3):
color = colour_variations_marron[round(x)][round(y)]
draw_pixel(PIXEL_SIZE,x,y,color)
x = x + PIXEL_SIZE
draw_pixel(PIXEL_SIZE,x,y,'#8f563b')
x = x + PIXEL_SIZE
x = -SCREEN_WIDTH//2
y += PIXEL_SIZE
Et voici le résultat !
Les flocons animés
Cette dernière partie était la plus intéressante à faire. Beaucoup de travail et de recherches mais pour un résultat surprenant.
Voici la fonction draw_snowflake() qui déssine un flocon en x, y. Le choix d’une couleur est possible grâce à l’argument couleur. Et la taille de celui-ci est désirable grâce à l’argument size_ratio. Étonnement, Clovis était celui qui à désinné ce flocon, et à trouvé cela plus compliqué comparé aux autres fonctions développées dans tout le projet (ça à pris du temps et de l’imagination).
def draw_snowflake(size_ratio, og_x, og_y, couleur):
color(couleur)
x, y = og_x, og_y
ps = 7 * size_ratio
# pixel(ps, og_x, og_y)
goto(og_x - 8*ps, og_y - 8*ps)
x, y = pos()
left(90)
pendown()
for _ in range(16):
draw_pixel(ps, x, y)
x, y = x + ps, y + ps
goto(x, y)
goto(og_x - 8*ps, og_y + 7*ps)
x, y = pos()
for _ in range(16):
draw_pixel(ps, x, y)
x, y = x + ps, y - ps
goto(x, y)
goto(og_x - ps, og_y + ps * 8)
x, y = pos()
og_x2, og_y2 = x, y
for p in range(18):
goto(og_x2, og_y2 - (p * ps))
x, y = pos()
for i in range(2):
draw_pixel(ps, x + (i * ps), y)
x, y = pos()
goto(og_x - 8 * ps, og_y- ps)
x, y = pos()
og_x2, og_y2 = x, y
for p in range(16):
goto(og_x2 + (p * ps), og_y2)
x, y = pos()
for i in range(2):
draw_pixel(ps, x , y+ (i * ps))
x, y = pos()
penup()
‘Quel banger‘ – Léandre quand Clovis à montré son flocon pour la premère fois
Ensuite, passons au système de mouvement des flocons.
def update_snowflake_positions(current_positions):
updated_positions = []
for (x, y) in current_positions:
new_y = y + SNOWFLAKE_MOVE_DOWN
# snowflake pos reset if goes too down
if new_y < -SCREEN_HEIGHT // 2:
new_y = SCREEN_HEIGHT // 2
new_x = randint(-SCREEN_WIDTH // 2, SCREEN_WIDTH // 2)
updated_positions.append((new_x, new_y))
else:
updated_positions.append((x, new_y))
return updated_positions
snowflakes = initialize_positions(SNOWFLAKE_COUNT, SCREEN_WIDTH, SCREEN_HEIGHT)
Pour commencer, la fonction update_snowflake_positions(current_positions) prend en compte la liste [(x, y), (x, y)] contenant les tuples de positions x, y de chaques flocons. (réf. initialize_positions(count, screen_width, screen_height)).
Puis initialise une nouvelle liste updated_positions qui contient une nouvelle version de chaques tuples (x, y), cette fois-ci avec une valeur y différée selon la constante SNOWFLAKE_MOVE_DOWN(new_y = y + SNOWFLAKE_MOVE_DOWN).
En plus, nous avons ajouté un système qui replace le flocon en haut de l’écran si les coordonnées y de celui-ci dépasse le point le plus bas de l’écran.
Point le plus haut de l’écran : SCREEN_HEIGHT // 2
Point le plus bas de l’écran : -SCREEN_HEIGHT // 2
La valeur SCREEN_HEIGHT est divisée par 2, car le module turtle fait en sorte que la position (0, 0) correspond au centre absolu de l’écran.
Enfin, voici la boucle finale permettant une animation.
for generation in range(1, TOTAL_GENERATIONS + 1): # main loop
clear()
snowflakes = update_snowflake_positions(snowflakes)
draw_snowflakes(snowflakes)
update()
print(f"Generation n'{generation} dessinée :D")
time.sleep(WAIT_TIME_MS / 1000) # Converti millisecondes en seconds
Explication des termes :
clear() permet d’effacer complètement tous les éléments de la fenêtre turtle.
update() permet de mettre à jour la fenêtre, utile pour réaliser des animations.
time.sleep(WAIT_TIME_MS / 1000) permet un temps d’attente entre chaques image exposée. Ici la constante WAIT_TIME_MS est de 100, et la fonction time.sleep() ne prend seulement comme argument des secondes. Il faut donc convertir les millisecondes en secondes, signifiant une division par 1000.
Si TOTAL_GENERATIONS = 1, alors turtle nous renverra une seule image, et non un animé.
Autrement, si TOTAL_GENERATIONS est au dessus de 1, on obtient une animation. Voici un exemple avec 100 :
Pas mal non ?
Sources
Pour réaliser toutes ces fonctions, nous avons utilisé nos propres connaissances, logique, et w3schoolsqui nous à beaucoup servi pour comprendre certaines opérations et fonctions integrées dans python (comme la fonction zip).
Image et animation finale
Télécharger le projet
Remarque des enseignants : les élèves n’ont pas respecté le nombre maximal de lignes imposé (404) ni la charte de nommage des variables. Cela pose de nombreux problèmes. L’article est néanmoins publié.