Objectif : Écrire un programme permettant de dessiner des rues d'immeubles, aléatoirement ou non.
Vous allez réinvestir l'utilisation de modules et la notion de modularité (découpage en différents modules) ainsi que la manipulation de dictionnaires.
Ce projet est une adaptation d'une activité proposée initialement par Adrien Willm (sur la liste de diffusion NSI) puis transposée par Sébastien Chanthery en un projet de groupe dont voici l'énoncé original. Merci à eux 🙏 !
Il faut parvenir à générer des images comme suit :
Mais libre à vous d'aller plus loin :
Image tirée du module de Sofian, Alexandre et Leo.
Source : infoforall.fr
Image tirée du module de Matt, Imrane, Samy et Yassin
Source : infoforall.fr
Vous utiliserez le module turtle
pour générer ces dessins.
Les contraintes urbanistiques sont les suivantes :
Tout le reste est libre et peut donc être personnalisé.
Vous travaillerez collectivement et en interdépendance à travers des importations de module (la réussite du projet dépendra donc de la réussite de chacun et chacune).
Pour vous aider, toutes les fonctions à écrire ont été recensées et décrites ci-après.
Votre projet se décompose en la création de 6 modules Python :
base.py
qui contiendra les fonctions de base utiles aux autres modules :
trait()
rectangle()
couleur_aleatoire()
facade.py
qui permet de construire une façade d'un immeuble grâce aux fonctions :
porte()
fenetre()
fenetre_balcon()
rdc()
etage()
toit.py
pour construire un toit selon deux modèles grâce aux fonctions :
dessiner_toit()
immeuble.py
pour construire un immeuble grâce à la fonction :
dessiner_immeuble()
sol.py
pour construire le sol d'une rue grâce à la fonction :
dessiner_sol()
rue.py
pour construire une rue d'immeubles et d'un sol grâce à la fonction :
dessiner_rue()
Le rôle de toutes ces fonctions est donné plus bas via leur chaîne de documentation.
Avant de vous lancer tête baissée, un peu de réflexion s'impose !
En utilisant la description des différentes fonctions données ci-dessous, complétez le diagramme de dépendances suivant avec les noms des différentes fonctions à écrire.
Diagramme de dépendances des fonctions.
Une flèche indique que l'on va utiliser une fonction pour en écrire une autre : par exemple, la fonction fenetre()
du module facade.py
s'écrira en utilisant la fonction rectangle()
du module base.py
.
Voici les différentes fonctions du projet et leur chaîne de documentation :
base.py
def trait(x1, y1, x2, y2, couleur, epaisseur):
"""
Trace un trait entre deux points.
Paramètres :
x1, y1 : coordonnées du premier point (int x int)
x2, y2 : coordonnées du second point (int x int)
couleur : couleur du trait (tuple de la forme (r, g, b))
epaisseur : épaisseur du trait (int)
"""
pass
def rectangle(x, y, largeur, hauteur, couleur):
"""
Trace un rectangle (dont les côtés sont parallèles aux axes du repère).
Paramètres :
x, y : coordonnées du coin inférieur gauche (int x int)
largeur, hauteur : largeur et hauteur du rectangle (int x int)
couleur : couleur du rectangle (tuple de la forme (r, g, b))
"""
pass
def couleur_aleatoire():
"""
Renvoie une couleur aléatoire.
Paramètres :
aucun
Retour :
(r, g, b) : un tuple des trois entiers r, g et b compris entre 0 et 255.
"""
pass
facade.py
def porte(x, y, couleur):
"""
Trace une porte.
Paramètres :
x, y : coordonnées du coin inférieur gauche
couleur : un tuple (r, g, b) de trois entiers compris entre 0 et 255
"""
pass
def fenetre(x, y):
"""
Trace une fenêtre.
Paramètres :
x, y : coordonnées du coin inférieur gauche (int x int)
"""
pass
def fenetre_balcon(x, y):
"""
Trace une porte-fenêtre avec un balcon.
Paramètres :
x, y : coordonnées du coin inférieur gauche (int x int)
"""
pass
def rdc(x, y_sol, couleur, c_porte):
"""
Trace le rez-de-chaussée d'un immeuble. La porte est positionnée aléatoirement.
Paramètres :
x : abscisse du coin inférieur gauche (int)
y_sol : ordonnée du sol de la rue (int)
couleur : couleur de la façade (tuple (r, g, b))
c_porte : couleur de la porte (tuple (r, g, b))
"""
pass
def etage(x, y_sol, couleur, niveau):
"""
Trace un étage d'un immeuble. Les fenêtres et portes-fenêtres avec balcon
sont positionnées aléatoirement.
Paramètres :
x : abscisse du coin inférieur gauche (int)
y_sol : ordonnée du sol de la rue (int)
couleur : couleur de la façade (tuple (r, g, b))
niveau : numéro de l'étage (int) (0: RDC, 1: étage n°1, etc.)
"""
pass
toit.py
def dessiner_toit(x, y_sol, niveau, type_toit):
"""
Trace un toit plat ou triangulaire selon la valeur du paramètre `type_toit`.
Paramètres :
x : abscisse du coin inférieur gauche (int)
y_sol : ordonnée du sol de la rue (int)
niveau : numéro du niveau (int) (0 pour les rdc, ...)
type_toit : chaîne de caractères qui vaut "plat" ou "triangulaire" (str)
"""
pass
immeuble.py
def dessiner_immeuble(immeuble):
"""
Dessiner un immeuble.
Paramètres :
------------
immeuble : dictionnaire représentant un immeuble (dict)
Exemple :
immeuble = {
'coord': (0, 0),
'nb_niveaux': 3,
'c_facade': (232, 103, 103),
'toit': 'plat'
}
Les clés du dictionnaire `immeuble` sont :
'coord' : coordonnées du coin inférieur gauche
'nb_niv' : nombre de niveaux
'couleur' : couleur de la façade
'toit': nature du toit
"""
pass
Par exemple, si
immeuble = {
'coord': (0, 0),
'nb_niveaux': 5,
'couleur_facade': (0, 153, 161),
'toit': 'plat'
}
alors dessiner_immeuble(immeuble)
doit générer une image similaire à :
sol.py
def dessiner_sol(y_sol):
"""
Trace le sol d'une rue.
Paramètres :
y_sol : ordonnée du sol de la rue
"""
pass
rue.py
def dessiner_rue(rue, y_sol):
"""
Trace toute la rue (sol et immeubles).
Paramètres :
rue : un tableau de dictionnaires représentant chacun un immeuble (list)
Exemple :
rue = [
{'coord': (-400, 0), 'nb_niv': 3, 'c_facade': couleur_aleatoire(), 'toit': 'plat'},
{'coord': (-250, 0), 'nb_niveaux': 2, 'c_facade': couleur_aleatoire(), 'toit': 'triangulaire'},
{'coord': (-100, 0), 'nb_niveaux': 5, 'c_facade': couleur_aleatoire(), 'toit': 'plat'},
{'coord': (50, 0), 'nb_niveaux': 4, 'c_facade': couleur_aleatoire(), 'toit': 'triangulaire'},
{'coord': (200, 0), 'nb_niveaux': 3, 'c_facade': couleur_aleatoire(), 'toit': 'triangulaire'}
]
"""
pass
Par exemple, si
rue = [
{'coord': (-400, 0), 'nb_niveaux': 3, 'couleur_facade': (255, 255, 0), 'toit': 'plat'},
{'coord': (-250, 0), 'nb_niveaux': 2, 'couleur_facade': (255, 0, 0), 'toit': 'triangulaire'},
{'coord': (-100, 0), 'nb_niveaux': 5, 'couleur_facade': (255, 0, 255), 'toit': 'plat'},
{'coord': (50, 0), 'nb_niveaux': 4, 'couleur_facade': (150, 150, 150), 'toit': 'triangulaire'},
{'coord': (200, 0), 'nb_niveaux': 3, 'couleur_facade': (0, 153, 161), 'toit': 'triangulaire'}
]
alors dessiner_rue(rue)
doit générer une image similaire à :
Pour vous faire gagner du temps, les différents fichiers sont déjà créés dans l'archive à télécharger en cliquant sur le lien suivant : dessine-ta-rue.zip
En particulier, le fichier base.py
est déjà en partie écrit (il y a des choses à modifier, d'autres à ajouter bien entendu) et sa structure pourra être reprise pour écrire les autres modules.
Il ne reste plus qu'à écrire les différentes fonctions. Mais là aussi, il faut de la rigueur et de la discipline. Voici quelques conseils :
Chacun des fichiers à écrire se terminera par les instructions du programme principal qui permettront de tester les fonctions du module.
Comme vous pouvez le voir dans le fichier de départ base.py
, cela prendra la forme suivante :
# PROGRAMME PRINCIPAL
if __name__ == '__main__':
# tous vos essais ici pour tester les fonctions
Dans
base.py
, comme dans les autres modules, les essais/tests sont à écrire dans l'instruction conditionnelleif __name__ == '__main__:'
dont la condition
__name__ == '__main__'
n'est vraie que si le fichier lui-même est exécuté, mais pas si on importe ce module avecimport base
. Cela permet de ne pas lancer les tests lorsque ce module est importé par ailleurs, c'est une pratique très courante même si elle n'est pas explicitement au programme.
Dans un premier temps
Écrivez les différentes fonctions des différents modules comme précisé dans les spécifications.
Dans un second temps
Faites en sorte que les immeubles d'une rue soit générés aléatoirement (sur le nombre de niveaux, la couleur, le toit, etc.). Pour cela vous créerez une fonction dessiner_rue_aleatoire()
.
Aide : Il suffit par exemple de générer une liste d'immeubles aléatoirement et d'utiliser la fonction
dessiner_rue()
.
turtle
La documentation officielle de turtle
en français est disponible à l'adresse : https://docs.python.org/fr/3.8/library/turtle.html. N'hésitez pas à vous y référer si vous avez besoin d'autres fonctionnalités que celles données ci-dessous.
Vous importerez turtle
partout où cela est nécessaire avec l'instruction :
from turtle import *
Les fonctions utiles de ce module pour le projet sont rappelées ci-dessous :
setup(LARGEUR, HAUTEUR) # taille de fenêtre égale à LARGEUR x HAUTEUR (en pixels)
forward(nb_pixels) # avance de nb_pixels
backward(nb_pixels) # recule de nb_pixels
goto(x, y) # se déplace aux coordonnées x, y
left(nb_degres) # tourne de nb_degres dans le sens antihoraire
right(nb_degres) # tourne de nb_degres dans le sens horaire
setheading(angle) # définit l'angle de la tortue (0: Est, 90: Nord, etc.)
penup() # lève le stylo
pendown() # abaisse le stylo
pensize(largeur) # largeur du trait : 1 au minimum
speed(valeur) # de 1 (lent) à 10 (rapide) et 0 le plus rapide
hideturtle() # cache la tortue
Vous exécuterez colormode(255)
dans les modules nécessitant des colorier des éléments : cela permettra d'utiliser des couleurs RVB sous la forme de triplets (r, v, b)
dont les valeurs r
, v
et b
sont des entiers compris entre 0 et 255.
colormode(255) # pour pouvoir utiliser des couleurs au format (r, v, b)
NOIR = (0, 0, 0) # permet de nommer une couleur pour l' utiliser par la suite
ROUGE = (255, 0, 0)
color(NOIR) # pour définir la couleur du trait (color((0, 0, 0)) fonctionne aussi)
fillcolor(ROUGE) # pour définir la couleur du remplissage
Une fois que la couleur de remplissage est définie avec la fonction fillcolor()
, il suffit d'appeler begin_fill()
, de tracer la forme à remplir, puis d'appeler end_fill()
et turtle
remplira la forme avec la couleur de remplissage. Par exemple :
# Permet de construire un carré de contour noir et rempli avec la couleur rouge
NOIR = (0, 0, 0)
ROUGE = (255, 0, 0)
color(NOIR)
fillcolor(ROUGE)
pendown()
begin_fill()
for _ in range(4):
forward(10)
left(90)
end_fill()
penup()
Un exemple dans le fichier
base.py
est proposé.
Parfois le tracé prend un peu de temps, même à vitesse maximale ! Pour voir le rendu final directement, vous pouvez utiliser l'astuce suivante :
fenetre = Screen()
fenetre.tracer(0) # permet de désactiver le tracé à l'écran
# vos instructions de tracé ici
fenetre.update() # pour voir le rendu final directement
Décommentez ces lignes dans le fichier
base.py
pour comprendre.
Références : La rédaction de ce projet a été inspirée des activités suivantes :
Germain BECKER, Lycée Emmanuel Mounier, ANGERS