python projet rue

Identification

Infoforall

36 - Projet Rue


Aujourd'hui, on dessine. Mais pas n'importe comment.

Au minimum :

Et si vous êtes ambitieux :

Image tirée du module de Sofian, Alexandre et Leo

Et vous voulez un peu plus de verdure par exemple :

Image tirée du module de Matt, Imrane, Samy et Yassin

Prérequis :

  • la création et l'utilisation des modules
  • un peu de Turle pour le programme 1
  • beaucoup de Python (fonctions, dictionnaires ou objets...) pour le programme 2
  • gestion des fichiers-texte, des strings et des tableaux pour le programme 3
  • les bonnes pratiques de programmation

Logiciel nécessaire pour l'activité : Python 3 : Thonny, IDLE ...

Evaluation ✎ : le projet (déroulé, documentation, revues de projet et état final)

1 - Idée générale

Voici l'idée général du projet :

  1. On aura d'abord un premier programme comportant des fonctions capables de dessiner des formes simples. Elles utiliseront le module Turtle à l'interne.
  2. Le programme dessiner.py dessine des formes simples en utilisant

    le module turtle

  3. Un deuxième programme devra être capable de dessiner une rue comportant plusieurs immeubles. Aucune ligne de Turtle à l'intérieur de ce programme, juste des appels aux fonctions du premier programme.
  4. Le programme rue.py dessine des immeubles en utilisant

    le module dessiner qui dessine des formes simples en utilisant

    le module turtle

  5. Votre troisième programme devra lire un fichier-texte contenant une description codifiée de la rue et devra la tracer en utilisant... le module rue.
  6. Le programme interpreteur.py lit un fichier-texte ma_rue.txt et l'interprète visuellement en utilisant

    le module rue qui dessine des immeubles en utilisant

    le module dessiner qui dessine des formes simples en utilisant

    le module turtle

    graph TD a(interpreteur) --> b(rue) a --> e(ma_rue.txt) b --> c(dessiner) c --> d(turtle)

    Exemple de texte interprétable et qui pourrait donner le dessin ci-dessus :

    rue 4 immeubles facade rouge - 2 étages - porte au milieu - toit pointu noir facade rouge - 1 étage - porte à gauche - toit pointu noir facade verte - 4 étages - porte à droite - toit pointu noir facade violet - 3 étages - porte au milieu - toit pointu noir
    rue 4 immeubles aléatoire

    Attention, le choix de la syntaxe est totalement libre. C'est une occasion unique de créer votre langage. Et attention, plus votre syntaxe sera rigide, plus votre interpreteur sera facile à réaliser !

graph TD a(interpreteur) --> b(rue) b --> c(dessiner) c --> d(turtle)

Mais d'ailleurs... comment dessine Turtle ? A qui fait-il appel ?

Les modules dont dépend votre programme programme est ce qu'on nomme ses dépendances. Il est important de connaître les versions des modules qu'on utilise pour faire fonctionner son programme car il est possible qu'une future version d'un module fortement en profondeur dans les dépendances fasse tomber tout l'édifice en cas de mise à jour contenant une non retro-compatibilité.

2 - Quelques précisions

Crédits : ce projet est une idée originale du site Ostralo.net.

Pour être certain de partir tous dans la même direction au début, et gagner un peu de temps de coordination, voici quelques choix imposés.

Dimensions imposées

On devra pouvoir créer la rue de façon aléatoire. Elle devra alors comporter au minimum 4 immeubles différents.

Chaque immeuble aura les caractéristiques suivantes :

  • Des immeubles d'une largeur de 140 pixels,
  • Chaque étage fait 80 pixels,
  • Entre 1 à 5 étages pour un immeuble
  • Chaque fenêtre fait 30 px sur 30 px
  • Une porte unique au rez-de-chaussée de 30 px sur 50px
  • Deux variations du toit
  • Plusieurs couleurs disponibles
  • Il est possible que certaines fenêtres soient des portes-fenêtres de 30 px sur 50 px

Format de livraison du projet

Pour simplifier les explications, je considère que votre projet comporte ces 4 fichiers, placés simplement dans le même répertoire. Bien entendu, vous êtes libres de créer un vrai package.

📁 activite_module

📄 dessiner.py

📄 rue.py

📄 interpreteur.py

📄 ma_rue.txt

Le projet que vous rendrez devra donc comporter au moins 4 fichiers, mais vous avez le droit d'en placer certains dans un répertoire pour en faire un package.

Structure du projet

On impose une structure en couche : chaque module ne peut communiquer qu'avec les fonctions publiques du module juste en dessous ou juste au dessus.

graph TD A(interpreteur) <--> B(rue) B <--> C(formes) C <--> D(turtle) a(interpreteur) <--> b(rue) b <--> c(formes) c <--> d(turtle) b <--INTERDIT--> d a <--INTERDIT--> c a <--INTERDIT--> d

Structure des modules

De façon à rendre votre projet facilement utilisable, chaque module devra disposer d'une documentation des fonctions publiques en début de fichier.

De façon à rendre votre projet facilement modifiable, on vous demande de distinguer clairement AFFICHAGE et CALCUL. Pour cela, il faudra créer des fonctions qui ne réalisent que l'une des ces actions :

  • de l'affichage : elles ne devraient pas faire de calculs.
  • de la modification mémoire : elles ne devraient pas provoquer d'affichage mais uniquement faire des calculs ou mémoriser.
  • de la liaison entre affichage et mémoire : bien entendu, vous aurez parfois besoin de faire les deux. Dans ce cas, il faudra externaliser les tâches dans une autre fonction : votre fonction devra faire appel à une fonction qui fait le calcul et une fonction qui fait l'affichage. Elle ne doit pas faire tout le job toute seule. Une fonction, une tâche.

Gérer ces catégories dès la création est plus simple que de devoir tout retravailler une fois le programme fonctionnel.

3 - Fichiers fournis et travail à faire

Voici le squelette de dessiner.py, le programme 1 : celui qui devra contenir des fonctions permettant de dessiner des formes géométriques basiques : carré, rectangle, disque, cercle, triangle...

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
"""Ce fichier permet de dessiner des formes à l'aide des fonctions suivantes + triangle_equilateral(cote, info_feutre, coordonnees) + arc_de_cercle(rayon, angle, info_feutre, coordonnees) Exemples d'utilisation : >>> informations_feutre = {'écriture':'blue', 'fond':'#FF88FF', 'épaisseur':5} >>> triangle_equilateral(50, informations_feutre, (50,100)) >>> arc_de_cercle(75, 360, informations_feutre, (200,-200)) """ # Importation import turtle as trt import random as rd # Déclaration des fonctions privées def nouveau_stylo(ecriture, fond, largeur): """Renvoie la référence d'un stylo configuré :: param ecriture(str) :: la couleur d'écriture ('red', '#FF0000') :: param fond(str) :: la couleur de fond pour ce stylo :: param largeur(int) :: la largeur du trait :: return (Turtle) :: renvoie un objet de la classe Turtle """ feutre = trt.Turtle() feutre.color(ecriture) feutre.fillcolor(fond) feutre.pensize(largeur) feutre.speed(5) return feutre def deplacer(feutre, x, y): """Lève le feutre, déplace le feutre et abaisse le feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param x(int) :: coordonnée horizontale (abscisse) :: param y(int) :: coordonnée verticale (ordonnée) :: return (None) :: c'est une fonction sans retour .. effet de bord :: modifie l'état de feutre """ feutre.penup() # On lève la pointe feutre.goto(x, y) # On déplace le crayon feutre.pendown() # On abaisse la pointe def trace_triangle_equilateral(feutre, cote): """Trace un triangle (equilatéral) à l'aide du crayon feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param cote(int) :: la valeur en pixel des côtés :: return (None) :: fonction sans retour .. effet de bord :: modifie l'état de feutre """ feutre.begin_fill() for x in range(3): feutre.forward(cote) feutre.left(120) feutre.end_fill() feutre.hideturtle() def trace_arc(feutre, rayon, angle): """Trace un arc de cercle à l'aide du crayon feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param rayon(int) :: la valeur en pixel du rayon :: param angle(int) :: l'angle à tracer (360 pour un cercle) :: return (None) :: fonction sans retour .. effet de bord :: modifie l'état de feutre """ feutre.begin_fill() feutre.circle(rayon, angle) feutre.end_fill() feutre.hideturtle() # Déclarations des fonctions publiques def triangle_equilateral(cote, info_feutre, coordonnees): """Trace un triangle (equilatéral) à partir des info_feutre et aux bonnees coordonnées :: param cote(int) :: la valeur en pixel des côtés :: param info_feutre(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple (int,int) ) :: un tuple (x,y) """ ecriture = info_feutre['écriture'] fond = info_feutre['fond'] epaisseur = info_feutre['épaisseur'] x = coordonnees[0] # ou x,y = coordonnees (par désempaquetage) y = coordonnees[1] feutre = nouveau_stylo(ecriture, fond, epaisseur) deplacer(feutre, x, y) trace_triangle_equilateral(feutre, cote) return feutre def arc_de_cercle(rayon, angle, info_feutre, coordonnees): """Trace un arc de cercle à partir des info_feutre et aux bonnees coordonnées :: param rayon(int) :: la valeur en pixel du rayon :: param angle(int) :: la valeur en ° de l'angle :: param info_feutre(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple (int,int) ) :: un tuple (x,y) """ ecriture = info_feutre['écriture'] fond = info_feutre['fond'] epaisseur = info_feutre['épaisseur'] x = coordonnees[0] # ou x,y = coordonnees (par désempaquetage) y = coordonnees[1] feutre = nouveau_stylo(ecriture, fond, epaisseur) deplacer(feutre, x, y) trace_arc(feutre, rayon, angle) return feutre # Instructions du programme principal if __name__ == '__main__': informations_feutre = {'écriture':"blue", 'fond':'#FF88FF', 'épaisseur':5} triangle_equilateral(50, informations_feutre, (50,100)) arc_de_cercle(75, 360, informations_feutre, (200,-200))

Le squelette de rue.py, le programme qui devra contenir les fonctions permettant de dessiner des immeubles. Attention, beaucoup de choses à rajouter dans celui-ci. Pour l'instant, il trace notamment les 4 "immeubles" aux mauvais endroits et avec une mauvaise forme.

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
"""Ce fichier permet de dessiner une rue à l'aide des fonctions suivantes : + dessiner_rue_aleatoire() + dessiner_rue_decrite(rue:dict) """ # Importation from dessiner import triangle_equilateral # Constantes LARGEUR_IMMEUBLE = 50 # Fonction privées def immeuble_aleatoire(numero:int) -> dict: informations = {} informations['couleur_facade'] = couleur_aleatoire() informations['numero'] = numero return informations def couleur_aleatoire() -> str: return "red" def coordonnees_facade(immeuble:dict) -> tuple: x_gauche = 30 * immeuble['numero'] y_bas = 30 * immeuble['numero'] return (x_gauche, y_bas) def dessiner_facade(immeuble:dict) -> None: # Traduction des données de rue vers dessiner crayon = {} crayon['écriture'] = "blue" crayon['fond'] = immeuble['couleur_facade'] crayon['épaisseur'] = 5 * immeuble['numero'] x, y = coordonnees_facade(immeuble) # désempaquatage du couple cote = LARGEUR_IMMEUBLE # Demande d'affichage triangle_equilateral(cote, crayon, (x,y)) def dessiner_porte(immeuble:dict) -> None: # Traduction des données de rue vers dessiner crayon = {} pass def dessiner_immeuble(immeuble:dict) -> None: dessiner_facade(immeuble) dessiner_porte(immeuble) # à compléter avec d'autres fonctions pour le reste : toit, fenêtres... # Fonction publiques def dessiner_rue_aleatoire() -> None: for n in range(4): informations_immeuble = immeuble_aleatoire(n) dessiner_immeuble(informations_immeuble) def dessiner_rue_decrite(rue:'?') -> None: pass # Programme principal if __name__ == '__main__': dessiner_rue_aleatoire()

L'intérêt du dictionnaire est évident ici : on pourra envoyer autant de caractéristiques qu'on veut sur chaque immeuble. Il en manque bien entendu. A vous de les rajouter.

Voici une proposition d'organisation pour le début du projet mais attention, le déroulé n'est pas vraiment linéaire : à vous de vous repartir le travail :


  1. Etape 1 : s'approprier dessiner.py (programme incomplet pour le moment) :
    • Parvenir individuellement à tracer les triangles et les cercles où vous voulez

  2. Etape 2 : s'approprier rue.py (programme non fonctionnel pour le moment) 
    • Comprendre où se déterminent les caractéristiques de l'immeuble
    • Comprendre pourquoi le programme affiche juste des triangles pour le moment
    • Lui faire tracer au moins des rectangles.

  3. Etape 3 : travail en commun 
    • Réfléchir ensemble aux formes générales qui vont être utiles pour faire des dessins quelconques avec dessiner.
    • Définir ensemble les prototypes de des fonctions supplémentaires de dessiner: noms, paramètres nécessaires, fausse réponse temporaire...
    • Réfléchir ensemble aux caractéristiques essentielles à connaître imérativement sur un immeuble pour parvenir à le dessiner correctement.
    • Définir ensemble les prototypes de des fonctions supplémentaires de rue: noms, paramètres nécessaires, fausse réponse temporaire...

  4. Etape 4 : chacun travaille dans son coin 
    • dessiner: L'un d'entre vous peut travailler sur les nouvelles formes, notamment une fonction qui traçe un triangle isocèle en utilisant la méthode goto.
    • rue: L'un d'entre vous peut alors modifier la création des caractéristiques pour que la hauteur des immeubles soient aléatoires.
    • rue: L'un d'entre vous peut alors modifier la création des caractéristiques pour que les couleurs des facades soient aléatoires.
    • rue: L'un d'entre vous peut modifier le programme pour que les immeubles s'affichent au bon endroit.
    • rue: Une fois que rue et dessiner ont bien avancé, l'un d'entre vous peut travailler sur la fonction dessiner_rue_decrite() : que voulez-vous recevoir en entrée comme description de la rue permettant de décoder les informations non aléatoire de chaque immeuble ? Un string ? Un dictionnaire ? Faire quelques tests et tenter de tracer des immeubles non aléatoires.
    • interpreteur: Une fois que dessiner_rue_decrite() semble fonctionner, l'un d'entre vous peut voir comment lire un fichier texte depuis Python et réaliser une fonction qui transforme le texte lu en ce qu'attend la fonction dessiner_rue_decrite().
    • ...

    Comment réussir ce projet ?

    • Avoir rapidement un prototype de base qui trace quelque chose qui ressemble vaguement à ce que voudrez au final.
    • Sauvegarder les différentes versions de vos programmes (pour cela, nous verrons que git est bien pratique)
    • Documenter les fonctions au fur et à mesure de leurs réalisations : vos collègues ne peuvent pas lire vos pensées. Dès qu'une fonction est finie, il faut la documenter et fournir des exemples d'utilisation. Encore mieux : la documentation devrait être faite AVANT d'écrire les instructions de la fonction.
    • Ne JAMAIS passer à une nouvelle tâche tant que vous n'avez pas testé que votre code ne provoque pas d'erreur. On lance le programme dès qu'on tape quelques lignes, pas au bout de 100 lignes...

    Une fois le prototype réalisé, il suffira de rajouter les options à travers de nouvelles fonctions ?:

    • une fonction pour tracer une porte ?
    • une fonction pour tracer une fenêtre ?
    • une fonction pour tracer les fenêtres ?
    • une fonction pour tracer le toit ?
    • une fonction pour tracer une antenne ou un pigeon ?
    • ...

    Vous êtes plusieurs, il faudra vous répartir les fonctions à réaliser. Sinon, c'est une perte de temps. Pensez à utiliser les quelques techniques de coopération que nous avons abordé en cours.

4 - Notation

Description des 4 critères de réussite :

  1. Réalisation pratique du projet, notamment :
    • Toutes les fonctionnalités sont présentes
    • Respect scrupuleux du cahier des charges
    • Aucun code magique en provenance d'internet ou d'IA générative
  2. Bonnes pratiques de programmation, notamment :
    • Noms explicites
    • Utilisation limitée et raisonnée des variables globales
    • Fonctions courtes et décomposition en sous-étapes
    • Séparation entre calculs et affichages.
    • Présence de jeux de tests pour les fonctions gérant les données
  3. Communication (à l'écrit et à l'oral), notamment
    • Documentation des fonctions publiques des modules
    • Documentation des fonctions
    • Commentaires sur les points délicats dans les fonctions
    • Qualité de l'interaction avec l'enseignant
    • Qualité de l'interaction avec les autres membres de l'équipe
  4. Qualité de la revue de projet, notamment :
    • Régularité des informations notées
    • Description de qui a fait quoi (vous pouvez le noter dans la documentation de la fonction)
    • Prototypes des fonctions dans la revue de projet, avant la réalisation pratique.

On attribue une note de 0 à 5 aux critères.

  • 5 si Bien
  • 4 si Assez bien
  • 3 si Moyen
  • 2 si Léger
  • 1 si Insuffisant
  • 0 si Rien

On fait la somme des critères.

Attention : aucun critère ne peut avoir plus que (le plus petit + 2).

  • Réalisation : Bien (5/5)
  • Bonnes pratiques : Assez bien (4/5)
  • Communication : Bien (5/5)
  • Revue de projet : Léger (2/5)

Tous les critères sont limités à 4 à cause de la revue de projet a une note de 2.

Note : 4 + 3 + 4 + 2 = 13/20.


  • Réalisation : Très bien (5/5)
  • Bonnes pratiques : Moyen (3/5)
  • Communication : Très bien (5/5)
  • Revue de projet : Bien (4/5)

Pas de limitation car le plus bas est 3 (moyen).

Note : 5 + 3 + 5 + 4 soit 17/20.

5 - FAQ

Et ça fonctionne comment Git du site ?

Le git du site est facile à utiliser :

  • Chacun d'entre vous s'y inscrit et crée un compte.
  • L'un d'entre vous crée un nouveau projet en cliquant sur le + en haut à droite
    1. Choisissez un nom qui permettra de ne pas le confondre avec les autres
    2. Cliquez sur Visibilité : Rendre le dépot privé de façon à ne pas permettre à tous de le voir
    3. Cliquez sur LISEZMOI : Initialiser le dépot de façon à le configurer par défaut et vous permettre d'interagir avec lui avec l'interface Web
    4. Cliquez en bas sur CREER LE DEPOT
  • Celui qui a créé le compte doit maintenant permettre aux autres membres du groupe de rejoindre le projet :
    1. Cliquez sur l'icône Paramètres du dépot (une clé)
    2. Sélectionnez l'onglet Collaborateurs
    3. Invitez les membres du groupe en utilisant leurs noms sur le Git.
    4. Donnez à vos membres les droits qui leur permettront de lire et modifier les fichiers !
  • A partir de ce moment, vous pouvez en groupe utiliser l'interface graphique pour :
    1. Créer un nouveau fichier dans le dépot avec le bouton Nouveau Fichier
    2. Téléverser un fichier de votre ordinateur vers le dépot avec le bouton Téléverser
    3. Télécharger vers votre ordinateur l'ensemble du dépot (avec la flèche de téléchargement sur la même ligne que les deux boutons précédents)
    4. Modifier directement un fichier depuis l'interface en cliquant sur le fichier puis l'icône crayon
  • Optionnel mais encore mieux : vous pouvez créer une copie du projet MASTER actuel et en faire une déviation (nommée une branche) : comme cela, vous pourrez faire ce que vous voulez sans endommager les fichiers-source. Une fois que vos modifications sont validées et fonctionnent, il vous suffira de les fusionner avec la branche MASTER.
    1. Cliquez sur le bouton BRANCHE pour rentrer dans le menu des branches (il est entre COMMIT et TAG). Il suffit de cliquer sur l'icone en forme de Y sur la branche MASTER pour créer votre propre branche.
    2. Sous le bouton COMMIT, vous pouvez maintenant basculer entre la branche MASTER et votre propre branche.
    3. Modifer, créer à l'envie vos fichiers
    4. Lorsque vous voulez les ramener dans la branche MASTER, il suffit de faire une demande de fusion en cliquant sur "Nouvelle demande de fusion". Il vous faut alors signaler que vous voulez amener vers MASTER les différences qu'il va trouver sur votre branche
      • Visualisez les différences entre les fichiers.
      • Appuyez sur Nouvelle Demande de Fusion
      • Donner un nom qui permettra d'identifier votre demande et éventuellement un descriptif.
      • Validez la modification sur vous êtes administrateur du dépot et attendez la validation d'un administrateur sinon.

En ligne de code, c'est possible aussi ?

Oui, c'est même bien plus pratique qu'avec l'interface graphique.

Le processus est le suivant :

  1. On clone le projet sur son propre ordinateur (en tapant git clone et en donnant l'URL du dépot)
  2. Avant de travailler, on met à jour sa version locale en tirant vers notre PC la dernière version disponible de la branche MASTER par exemple (avec git pull)
  3. On rajoute ensuite un par un les fichiers ou répertoires qu'on a modifié et qu'on voudra faire remonter vers le dépot central (avec git add). Cela ne les envoie pas, on place juste les modifications dans une mémoire. Les modifications sont justes préparées sagemment.
  4. Une fois que tous les fichiers et répertoires ont été correctement modifiées pour la tâche voulue et additionner aux modifications, on peut alors créer une demande de remontée (avec git commit) en donnant un nom à cet ensemble de modifications. Attention, encore une fois, ça ne remonte pas encore vers le dépot. Add + Commit permettent simplement de créer cette demande.
  5. On demande alors de "pousser" réellement demande de remontée vers le dépot avec git push : cette fois, votre Git va discuter avec le Git du dépot et proposer votre demande aux administrateurs du dépot.

Activité publiée le 06 06 2022
Dernière modification : 07 09 2022
Auteur : ows. h.