Premiere NSI / Projets

Projet : Dessine ta rue 🎨

Dernière mise à jour le : 01/07/2024

Dessine ta rue 🎨

Présentation

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 :

une rue d'immeuble

Mais libre à vous d'aller plus loin :

une rue d'immeuble

Image tirée du module de Sofian, Alexandre et Leo.
Source : infoforall.fr

une rue d'immeuble

Image tirée du module de Matt, Imrane, Samy et Yassin
Source : infoforall.fr

Cahier des charges

Vous utiliserez le module turtle pour générer ces dessins.

Les contraintes urbanistiques sont les suivantes :

  • Les immeubles ont au minimum un rez-de-chaussée et au maximum 4 étages (5 niveaux) ;
  • Les immeubles ont une largeur de 140 pixels ;
  • Les immeubles ont une couleur unique pour toute la façade ;
  • Chaque niveau (rez-de-chaussée ou étage) a une façade de hauteur 60 pixels ;
  • Les rez-de-chaussée n'ont qu'une seule porte et 2 fenêtres placées ;
  • Toutes les fenêtres sont identiques, de taille 30 pixels sur 30 pixels ;
  • Certaines fenêtres dans les étages peuvent être des portes-fenêtres qui ont un balcon et font une taille de 30 pixels en largeur et 50 pixels en hauteur ;
  • Le toit peut avoir 2 formes, plat ou triangulaire :
    • Si le toit est plat : il fait une épaisseur de 10 pixels,
    • Si le toit est triangulaire, il fait une hauteur de 40 pixels pour une base de 160 pixels.

Tout le reste est libre et peut donc être personnalisé.

Travail à faire

Étape 1 : Décomposition du travail en modules et fonctions

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.

✏️ À faire

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.

une rue d'immeuble

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 :

Fichier 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    

Fichier 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

Fichier 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

Fichier 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 à :

un immeuble

Fichier 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

Fichier 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 à :

un immeuble

Fichiers à télécharger

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.

Étape 2 : Écriture des fonctions

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 :

  • Commencez par écrire les fonctions en commençant par celle du bas puis en remontant progressivement.
    • Assurez-vous que chaque fonction est correcte avant de passer à la suivante ; en particulier, il faut les tester au fur et à mesure et s'assurer que le cahier des charges doit être respecté.
    • Faites des schémas sur papier avec les coordonnées et les dimensions pour bien écrire le code.
  • Répartissez-vous les tâches de manière efficace et intelligente.
  • Si une fonction dépend d'une ou plusieurs autres, rien n'empêche de l'écrire, mais elle ne pourra être testée qu'une fois ces dépendances écrites : la réussite des uns dépend donc de celle des autres !
  • Commencez par faire une version fonctionnelle basique de chaque fonction avant de faire les améliorations.
  • Pour l'écriture d'un module, il faudra correctement importer les fonctions nécessaires qui fonction partie d'autres modules.

Structure des modules

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 conditionnelle

if __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 avec import 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.

✏️ À faire

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().

Aide pour l'utilisation de 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.

Les basiques

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

Gestion des couleurs

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

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é.

Ne pas voir le tracé

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 Licence Creative Commons