Mini-projet collectif : "dessiner ma rue"¶
But :¶
On souhaite écrire un programme qui permet de générer « aléatoirement » le dessin d’une rue de 4 immeubles dans un notebook jupyter.
On utilisera pour cela le module ipycanvas
de Martin RENOU :
Si vous ne connaissez pas ce module, il vous faut donc préalablement le prendre en main en faisant, par exemple, les activités de ipycanvas-Le_BN_pour_dessiner.ipynb...
Contraintes :¶
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 ;
- D'une manière générale, tous les tracés de contour des formes sont de couleur noire et font 1 pixel d'épaisseur ;
- 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 aléatoirement ;
- Toutes les fenêtres sont identiques, de taille 30 pixels sur 30 pixels avec une vitre de couleur 'Azure' le jour ;
- Toutes les portes fenêtre ont un balcon et font une taille de 30 pixels en largeur et 50 pixels en hauteurs ;
- 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.
Exemples :¶
La série d'exemples suivants est basée sur :
- une couleur aléatoire pour les façades ;
- deux modèles de toits ;
- deux modèles de portes avec une couleur aléatoire ;
- deux modèles d'ouvertures pour les étages : fenêtre ou porte-fenêtre avec balcon ;
- trois éléments horizontalement pour chaque niveau.
Travail à faire :¶
Livrer un programme constitué de modules qui réponde au problème posé en utilisant le module ipycanvas
de Martin RENOU.
Vous utiliserez le plus de petites fonctions possibles :
Le Product Backlog, c'est à dire les modules à produire et leurs dépendances sont décrits dans le schéma ci-dessus.
Vous travaillerez collectivement en mode agile et en interdépendance à travers des importations de modules par équipes de 6 élèves + 1 professeur qui jouera le role de Product Owner.
Les équipes seront donc misent en concurrences pour ce projet.
Dans chaque équipe vous désignerez deux volontaires pour être Scrum Master.
Tout le monde participe activement au développement du code y compris le Product Owner en cas de besoin (mais pas trop parce qu'il n'a pas que ça à faire !;) ).
Ce projet sera donc aussi l'occasion de découvrir et d'expérimenter les rudiments d'une méthode agile que présente les vidéos suivantes...
%%HTML
<center>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/VpdFpZ_w5x8?start=30" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</center>
%%HTML
<center>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/-HV_MW5KgVk" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</center>
%%HTML
<center>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/WNYcSxbJvsc" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</center>
Le développement du projet va donc se décomposer en plusieurs sprints.
Le premier sprint, en fait le sprint zéro, sera collectif et piloter par le Product Owner afin que chacun comprenne le besoin du client et prenne en main l'environnement de travail (workflow) qu'il faudra appliquer pour la suite du développement. Le livrable attendu pour ce sprint est le module ma_rue.py
.
Les sprints suivants seront menés au sein de chaque équipe où les Scrums Master veilleront à animer les cérémonies de sprint, la répartition des tâches pour chaque User Story, qui seront ici les modules à produire en commençant par ceux qui ne dépendent pas des autres.
Ainsi le Sprint 1 devrait contenir quatre User Stories pour produire respectivement les modules trait.py
, rectangle.py
, couleur_aleatoire.py
et toit1
.
Pour vous aider dans votre tâche, pour chaque module :
- la spécification des fonctions est imposée et précisée dans leur docstring respective ;
- des tests unitaires sont fournis, ils doivent produire le même dessin que celui de l'image fournie pour le résultat attendu.
Il ne vous reste qu’à écrire le code en suivant le Workflow.
A vous de jouer ! C'est parti pour le Sprint..., rendez-vous au prochain Scrum...
Etres agile c'est avant tout une posture, qui au delà des aspects techniques de l'écriture du code, nous conduit à surveiller également la méthode de développement et l'organisation dans l'équipe avec le souci constant de la réussite collective du projet et de la satisfaction des clients, les Key Users.
# Dépendances
from machin import bidule, truc
# Définitions
bar = 'titi'
def foo() :
'''
Docstring de foo()
'''
pass
# Tests
foo() # appel à la fonction foo() pour un test
print(bar) # affichage en sortie d'une variable
Sans toutefois trop digresser, multiplier les tests pour éprouver le potentiel des fonctinnalités de votre module. Afin de démontrer, qu'en cas d'abandon du projet final pour une raison ou une autre, vous n'avez pas perdu votre temps car au moins le morceau de code de votre User Story pourrait être réemployé à d'autres fins.
Si les tests sont concluants alors on peut rassembler le code du module dans un fichier toto.py
tel que :
# Dépendances
# (coller ici les instructions de la cellule des dépendances)
from machin import bidule, truc
# Définitions
# (coller ci-dessous les instructions de la cellule de définitions)
bar = 'titi'
def foo() :
'''
Docstring de foo()
'''
pass
# Tests
if __name__ == '__main__': # ce bloc d'instructions sera exécuté si on fait un %run toto.py
#(coller ci-dessous les instructions de la cellule de tests)
# appel à la fonction foo() pour un test
foo()
# affichage en console d'une variable
print(bar)
On peut alors exécuter le code de test du programme toto.py
depuis une cellule du notebook avec l'instruction :
%run toto.py
Si cet ultime test est concluant, on peut alors partager le fichier toto.py
avec les autres membres de l'équipe (par mail, airdrop, ou beaucoup mieux avec GitHub...).
/!\ ne partager un module que si votre code à passer tous les tests
Les autres membres peuvent alors tout comme vous, importer dans leur notebook les dépendances validées afin de les utiliser dans la suite du développement des autres modules
from toto import bar,foo
Sinon on continue le développement dans le notebook sans faire d'import de dépendances externes tant qu'elles ne sont pas fonctionnelles...
# Dépendance
# Définition
# Création d'un canvas nommé rue de 800 pixels de large par 400 pixels de haut
# Définition d'une fonction d'affichage
def affiche(canvas) :
'''
Efface et affiche le canvas dans le notebook
'''
# Test
# Affichage du canvas rue pour un test
affiche(rue)
Le test d'affichage est concluant => On emballe ce code dans un fichier
ma_rue.py
et on le teste une dernière fois :
Validation du module :¶
%run ma_rue.py
Cet ultime test est aussi concluant => On partage le fichier
ma_rue.py
dans l'équipe pour importer ses fonctionnalités par la suite
# Dépendances
from ma_rue import rue, affiche
# Définitions
# Fonction trait()
def trait(x1,y1,x2,y2):
'''
dessine un trait entre les 2 points transmis en paramètres
Paramètres
x1, y1 : coordonnées du début du trait
x2, y2 : coordonnées de la fin du trait
'''
# Tests
affiche(rue)
trait(50, 25, rue.width/2, rue.height/2)
# Autres tests
for x in range (int(rue.width/2), rue.width + 1, 20) :
trait(x, 0, 3*rue.width/2 - x, rue.height)
Validation du module :¶
%run trait.py
# Dépendances
from ma_rue import rue, affiche
# Définitions
# Fonction rectangle()
def rectangle(x,y,w,h,c):
'''
Dessine un rectangle avec un contour noir et rempli de la couleur passée en paramètre
Paramètres
x, y : coordonnées du centre de la base de rectangle
w : largeur du rectangle
h : hauteur du rectangle
c : couleur du remplissage
'''
# Tests
affiche(rue)
rectangle(0, 50,200,100,'YellowGreen')
rectangle(800, 450,200,100,'plum')
rectangle(400, 250,200,100,'SkyBlue')
rectangle(400, 250,100,50,'salmon')
# Autres tests
Validation du module :¶
%run rectangle.py
# Dépendances
from ma_rue import rue, affiche
# Définitions
def couleur_aleatoire():
'''
Renvoie une couleur HTML valide
au format 'rgb(rouge, vert, bleu)'
où rouge, vert et bleu sont des entiers
compris entre 0 et 255 choisis aléatoirement.
'''
return
# Tests
affiche(rue)
couleur = couleur_aleatoire()
rue.fill_style = couleur
rue.fill_rect(0, 0, rue.width, rue.height)
rue.font = '48px Lucida Console'
rue.text_align = 'center'
rue.stroke_text(couleur, rue.width/2, rue.height/2)
# Autres tests
from time import sleep
affiche(rue)
for i in range(30) :
couleur = couleur_aleatoire()
rue.fill_style = couleur
rue.fill_rect(0, 0, rue.width, rue.height)
rue.font = '48px Lucida Console'
rue.text_align = 'center'
rue.stroke_text(couleur, rue.width/2, rue.height/2)
sleep(1)
Validation du module :¶
%run couleur_aleatoire.py
# Dépendances
from ma_rue import rue, affiche
# Définitions
# Fonction toit1()
def toit1(x, niveau):
'''
Dessine un triangle plein de couleur noir de 40 pixels de haut
et avec une base de 160 pixels
Paramètres :
x : abcisse du centre du toit
niveau : numero du niveau (0 pour les rdc, ...)
'''
y = rue.height - niveau * 60 # ordonnée de la base du toit
# Tests
affiche(rue)
toit1(rue.width/2, 0)
# Autres tests
for i in range(5) :
for j in range(1, 6) :
toit1(0 + 200 * i, j)
Validation du module :¶
%run toit1.py
from ma_rue import rue, affiche
from rectangle import rectangle
from couleur_aleatoire import couleur_aleatoire
from trait import trait
from toit1 import toit1
affiche(rue)
for i in range(5) :
rectangle(0+200*i, rue.height, 140, 60*(i+1), couleur_aleatoire())
toit1(0+200*i, i+1)
for j in range(i+1) :
trait(0+200*i-70, rue.height-60*j, 0+200*i+70, rue.height-60*j)
from ma_rue import rue, affiche
from rectangle import rectangle
from couleur_aleatoire import couleur_aleatoire
from trait import trait
from toit1 import toit1
affiche(rue)
# Les pieds
toit1(rue.width/4, 0)
toit1(3*rue.width/4, 0)
# Le cadre
rectangle(rue.width/2, rue.height-40, rue.width/2, rue.height -80, couleur_aleatoire())
# Un chapeau
toit1(rue.width/2, 6)
# Un carré de couleur 'snow' au centre du canvas
c = rue.width/3 # longueur du coté
# Coordonnées du milieu de la base du carré
cx = rue.width/2
cy = rue.height/2 + c/2
rectangle(cx, cy , c, c, 'snow')
# Figure de fils tendus dans le carré
n = 30 #nombre de fils
pas = c/n
for k in range(1, n+1) :
trait(cx-c/2, cy-c + k*pas, cx-c/2 + k*pas, cy)
for k in range(1, n+1) :
trait(cx-c/2 + k*pas, cy-c, cx+c/2, cy-c + k*pas)
for k in range(1, n+1) :
trait(cx+c/2, cy-c + k*pas , cx+c/2 - k*pas, cy)
for k in range(1, n+1) :
trait(cx-c/2, cy-c + k*pas , cx+c/2 - k*pas, cy-c)
Autres démonstrations de fin de sprint 1 :¶
from ma_rue import rue, affiche
from rectangle import rectangle
from couleur_aleatoire import couleur_aleatoire
from trait import trait
from toit1 import toit1
# A compléter par vous même pour implémenter vos propres idées de démonstration pour la Sprint Review...
Rétrospective du Sprint 1 :¶
Organiser une réunion de rétrospective du Sprint 1, relever la tête du guidon et résumer ci-dessous les points à retenir :
Nos points forts, ce qui à bien fonctionné :¶
- ¶
Nos points de faiblesses, ce qui à moins bien fonctionné :¶
- ¶
Nos points de blocages, ce qui n'a pas fonctionné :¶
- ¶
Nos axes d'améliorations envisagés pour la suite :¶
- ¶
Autres modules à développer (User strories restantes en Backlog) :¶
A charge pour vous de vous organiser dans chaque équipe afin de finaliser le développement du projet...
# Dépendances
from ma_rue import rue, affiche
from trait import trait
# Définitions
# Fonction sol()
def sol():
'''
Trace une ligne horizontale au niveau du sol de la rue
d'épaisseur 3 pixels et de longueur 760 pixels
centrée dans le canvas
'''
y_sol = rue.height-1 # ordonnée du sol de la rue
# Tests
affiche(rue)
sol()
# Autres tests
Validation du module :¶
%run sol.py
# Dépendances
from ma_rue import rue, affiche
from rectangle import rectangle
from couleur_aleatoire import couleur_aleatoire
from math import pi
from random import randint
# Définitions
# Fonction portes()
def portes(x,y):
'''
Dessine une porte de 50 pixels en largeur et 70 pixels en hauteur
La forme du haut de la porte est aléatoirement rectangulaire ou arrondi
La couleur pleine de remplissage est choisi aléatoirement parmi les couleurs HTML valides
Paramètres :
x est l'abcisse du milieu de la base de la porte
y est l'ordonnée du sol du niveau de la porte
'''
# Tests
affiche(rue)
for i in range(21) :
portes(0 + i * 40,rue.height)
# Autres tests
Validation du module :¶
%run portes.py
# Dépendances
from ma_rue import rue, affiche
from rectangle import rectangle
# Définitions
# Fonction
def facade(x, couleur, niveau):
'''
Dessine un rectangle de 60 pixels de haut et 140 pixels de large
Paramètres :
x : abcisse du centre de la façade
couleur : couleur de la façade fixée par l'immeuble
niveau : numéro du niveau (0 pour les rdc, ...)
'''
y = rue.height - niveau * 60 # ordonnée de la base de la facade
# Tests
from couleur_aleatoire import couleur_aleatoire
affiche(rue)
couleur = couleur_aleatoire()
for n in range(6) :
facade(rue.width/2, couleur, n)
# Autres tests
Validation du module :¶
%run facade.py
# Dépendances
from ma_rue import rue, affiche
from rectangle import rectangle
# Définitions
# Fonction
def fenetre(x,y):
'''
Dessine une fenêtre de taille 30 pixels sur 30 pixels
avec une vitre de couleur 'Azure' le jour
et un contour noir.
Paramètres :
x est l'abcisse du milieu de la base de la fenêtre
y est l'ordonnée du sol du niveau de la fenetre
'''
# Tests
affiche(rue)
fenetre(rue.width/2,rue.height)
# Autres tests
Validation du module :¶
%run fenetre.py
# Dépendances
from ma_rue import rue, affiche
from rectangle import rectangle
from trait import trait
# Définitions
# Fonction balcon()
def balcon(x,y):
'''
Dessine une porte fenêtre de largeur 30 pixels et 50 pixels en hauteur
avec une vitre de couleur 'Azure' le jour au contour noir,
devancé d'un balcon constitué de traits noirs d'épaisseur 3 pixels.
Paramètres :
x est l'abcisse du milieu de la base de la porte-fenetre
y est l'ordonnée du sol du niveau de la porte-fenetre
'''
# porte-fenetre
# balcon
# Tests
affiche(rue)
balcon(rue.width/2,rue.height)
# Autres tests
Validation du module :¶
%run balcon.py
# Dépendances
from ma_rue import rue, affiche
from facade import facade
from portes import portes
from fenetre import fenetre
# Définitions
# Fonction rdc()
def rdc(x, couleur):
'''
Dessine le rdc sur une facade au niveau do sol de la rue
avec une seule porte et 2 fenêtres placées aléatoirement.
Paramètres
x : abscisse du milieu de la base du RDC
couleur : couleur fixée par l'immeuble
'''
# Dessine la facade
# Choix d'une distribution
# dessiner une porte
# dessiner une fenetre
# Tests
from couleur_aleatoire import couleur_aleatoire
affiche(rue)
for i in range(7) :
rdc(i*160, couleur_aleatoire())
# Autres tests
Validation du module :¶
%run rdc.py
# Dépendances
from ma_rue import rue, affiche
from facade import facade
from fenetre import fenetre
from balcon import balcon
from random import randint
# Définitions
# Fonction etage()
def etage(x, couleur, niveau):
'''
Dessine sur une facade un étage avec 3 éléments choisis aléatoirement
parmi une fenêtre ou une porte fenêtre avec balcon.
Paramètres
x : abscisse du milieu de la base de l'étage
couleur : couleur fixée par l'immeuble
niveau : numéro de l'étage en partant de 0 pour le rdc
'''
y = rue.height - niveau * 60 # ordonnée de la base de l'etage
# Murs
# Eléments
# Tests
from couleur_aleatoire import couleur_aleatoire
affiche(rue)
couleur = couleur_aleatoire()
for n in range(6) :
etage(rue.width/2,couleur,n)
# Autres tests
Validation du module :¶
%run etage.py
# Dépendances
from ma_rue import rue, affiche
from trait import trait
# Définitions
# Fonction toit2()
def toit2(x, niveau):
'''
Paramètres :
x : abcisse du centre du toit
y_sol : ordonnée du sol du la rue
niveau : num du niveau (0 pour les rdc, ...)
'''
y = rue.height - niveau * 60 # ordonnée de la base du toit
# trait horizontal
# Tests
affiche(rue)
for n in range(6) :
toit2(rue.width/2, n)
# Autres tests
Validation du module :¶
%run toit2.py
# Dépendances
from ma_rue import rue, affiche
from toit1 import toit1
from toit2 import toit2
# Définitions
# Fonction toits()
def toits(x, niveau):
'''
Dessine aléatoirement un toit plat ou un toit en pointe
à l'ordonnée du niveau passé en paramètre
Paramètres
x : abscisse du centre de l'étage
y_sol: ordonnée du sol
niveau : numéro de l'étage en partant de 0 pour le rdc
'''
# Tests
affiche(rue)
for i in range(5) :
for j in range(6) :
toits(0 + 200 * i, j)
# Autres tests
Validation du module :¶
%run toits.py
# Dépendances
from ma_rue import rue, affiche
from couleur_aleatoire import couleur_aleatoire
from rdc import rdc
from etage import etage
from toits import toits
from random import randint
# Définitions
def immeuble(x):
'''
Dessine un immeuble selon les règles urbanistiques imposées
Paramètres
x : abscisse du milieu de la base de l'immeuble
'''
# Nombre d'étage (aléatoire)
#Couleur facade (aléatoire)
# Dessin du RDC
# Dessin des étages
# Dessin du toit
# Tests
affiche(rue)
immeuble(rue.width/3)
immeuble(2*rue.width/3)
# Autres tests
Validation du module :¶
%run immeuble.py
# Dépendances
from sol import sol
from immeuble import *
# Définitions
def rue_finale(canvas):
# Affichage de ma rue
affiche(canvas)
# Dessin des immeubles
# Dessin du sol de la rue
# Tests
rue_finale(rue)
# Autres tests
Validation du module :¶
%run rue_finale.py
Foncionnalités supplémentaires à envisager, la file d'attente en Product BackLog :¶
Mettons à l'épreuve notre agilité et voyons comment nous pourrions intégrer ces nouvelles fonctionnalités à notre projet...
Une voiture traverse ma rue de gauche à droite.
=> utiliser des calques avec MultiCanvas ?
Le code urbanistique évolue et impose une palette de couleurs données à appliquer dans ma ville. ( En fait, le maire a reçu des remarques des automobilistes circulant dans votre rue, ils veulent bien me permettre un peu de fantaisie de couleurs mais dans des limites acceptables par mes concitoyens...)
=> Chosir un type construit adapté parmi Liste, Distionnaire ou Tuple ?
- Peut-on produire un croquis façon dessin à main levé de ma rue ?
=> Avec le module RoughCanvas ?
Dans ma rue, il y a des immeubles qui ont des fenêtres de type oeil de boeuf, dans ce cas, toutes les fenêtres sont de ce type et alors les portes comme les portes fenêtres ont un haut arrondi.
Dans ma rue, il y a aussi des immeubles où toutes les fenêtres sont triangulaires, et dans ce cas les portes comme les portes fenêtres ont un haut triangulaire.
Mais que se passe-t'il dans ma rue lorsque la nuit tombe ? Les chats y sont-ils tous gris ??
Ressource :¶
Pour ipycanvas :
- https://blog.jupyter.org/ipycanvas-a-python-canvas-for-jupyter-bbb51e4777f7
- https://github.com/martinRenou/ipycanvas/
- https://ipycanvas.readthedocs.io/en/latest/?badge=latest
Pour Agile, Scrum, Design Thinking, ...
Références aux programmes :¶
Langages et programmation¶
Contenus | Capacités attendues | Commentaires |
---|---|---|
Constructions élémentaires. | Mettre en évidence un corpus de constructions élémentaires. | Séquences, affectation, conditionnelles, boucles bornées, boucles non bornées, appels de fonction. |
Spécification. | Prototyper une fonction. Décrire les préconditions sur les arguments. Décrire des postconditions sur les résultats. |
Des assertions peuvent être utilisées pour garantir des préconditions ou des postconditions. |
Mise au point de programmes. | Utiliser des jeux de tests. | L’importance de la qualité et du nombre des tests est mise en évidence. Le succès d’un jeu de tests ne garantit pas la correction d’un programme. |
Utilisation de bibliothèques. | Utiliser la documentation d’une bibliothèque. | Aucune connaissance exhaustive d’une bibliothèque particulière n’est exigible. |
Ce document est l'adaptation d'une proposition d'Adrien WILLM et du travail de Sébastien CHANTHERY au module ipycanvas
de Martin RENOU ingénieur logiciel scientifique chez QuantStack avec un peu d'agilité et un workflow de développement dans un environnement jupyter. Il est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International.
Pour toute question, suggestion ou commentaire : eric.madec@ecmorlaix.fr