{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Utilité d'une interface graphique" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Le module **Tkinter** importable par la commande :\n", "\n", "$from $ $tkinter$ $import$ $*$\n", "\n", "permet de construire une interface graphique. Plus précisément, on pourra grâce à cet outil :\n", "\n", "- implémenter un programme Python et faire une sortie graphique, un peu comme sous Matplotlib mais avec des fonctionnalités un peu plus interactives\n", "\n", "- on pourra interagir sur les données du programme via cette interface. Il sera par exemple possible, grâce à un click de souris de pointer un pixel afin d'influer sur l'algorithme en lui-même. On pourra mettre en place des boutons d'actions (appelés **widgets**) pour effectuer des procédures ou des fonctions par un seul clic." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Principales fonctions du module Tkinter" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## La fenêtre" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "La syntaxe $fenetre= Tk()$\n", "permet de définie une fenêtre appelée $fenetre$ liée à l'interface graphique. \n", "\n", "Pour afficher la fenêtre, on utilise la syntaxe :\n", "\n", "$fenetre.mainloop()$,\n", "\n", "la méthode $.mainloop()$ permettant de lancer la boucle principale d'exécution de cette fenêtre interactive :" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from tkinter import *\n", "fenetre=Tk()\n", "\n", "fenetre.mainloop()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On peut définir les dimensions de cette fenêtre par la méthode $.geometry$. On définit ainsi la taille en largeur sous forme de nombre de pixels et la taille en hauteur sous forme de nombre de pixels :" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "fenetre=Tk()\n", "fenetre.geometry(\"800x400\")\n", "fenetre.mainloop()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Insérer un widget Label" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour agrémenter cette fenêtre pour l'instant vide, on peut ajouter des champs de chaînes de caractères avec les $Label$ et la méthode $.pack$. Le champ $Label$ présente des options du type :\n", "\n", "- nature du texte à afficher\n", "\n", "- police de texte utilisée\n", "\n", "- couleur de fond ou couleur de texte utilisée : les codes couleurs en hexadécimale sont consultables sur le net" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "fenetre=Tk()\n", "fenetre.geometry(\"800x400\")\n", "\n", "titre=Label(fenetre,text=\"Placement d'un widget Label\",font=\"Arial 16 italic\",bg=\"#F7FE2E\",fg=\"red\")\n", "titre.pack() # on peut associer l'endroit d'emplacement par side=TOP, BOTTOM, RIGHT ou LEFT\n", "fenetre.mainloop()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "De manière générale, dans une fenêtre Tkinter, deux syntaxes sont communes à tous les widgets :\n", "\n", "- la méthode $.pack()$ permet d'insérer un widget\n", "\n", "- la méthode $.destroy()$ permet de supprimer un widget" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Insérer un widget Button" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Un premier intérêt de l'interface est de mettre en place des boutons d'actions, appelés $Button$. Un widget $Button$ permet :\n", "\n", "- de créer un bouton associé à un texte\n", "\n", "- de créer un bouton associé à une commande\n", "\n", "Un bouton se place via la même méthode $.pack$.\n", "\n", "Commençons par un widget $Button$ permettant de $Quitter$ la fenêtre, c'est-à-dire permettant de sortir de la boucle principale. La méthode $.destroy()$ mise à la fin conduira alors à la destruction de la fenêtre elle-même :" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "fenetre=Tk()\n", "fenetre.geometry(\"800x400\")\n", "\n", "Texte_Widget=Label(fenetre,text=\"Placement du widget Quitter\",font=\"Arial 16 italic\")\n", "Texte_Widget.pack() #placement du texte\n", "\n", "bouton_quitter = Button(fenetre, text=\"Quitter\", command=fenetre.quit)\n", "bouton_quitter.pack(side=BOTTOM,padx=5,pady=5) #placement du bouton\n", "\n", "\n", "fenetre.mainloop()\n", "fenetre.destroy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Voici un autre exemple de widget associée à une commande $ecrire()$. Cette commande est à implémenter en Python avant la définition de la fenêtre et cette commande ne doit admettre aucun argument positionnel ; autrement dit, la commande peut dépendre de certaines variables, mais il faut alors déclarer des variables en variables globales au sein de la fonction :" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "## Définition de la commande ecrire()\n", "from random import *\n", "\n", "Liste_Messages=[\"bonjour\",\"au revoir\",\"un autre message aléatoire\"]\n", "\n", "compteur=0\n", " \n", "def ecrire() :\n", " global Liste_Messages,compteur\n", " compteur+=1\n", " Indice=randint(0,2)\n", " Texte_Alea=Label(fenetre,text=Liste_Messages[Indice]+\" \" + \"Message numéro \"+str(compteur),font=\"Arial 12 italic\")\n", " Texte_Alea.pack(side=TOP)\n", "\n", "## Interface graphique\n", "from tkinter import *\n", "fenetre=Tk()\n", "fenetre.geometry(\"800x400\")\n", "\n", "Texte_Widget=Label(fenetre,text=\"Placement de plusieurs widgets Button\",font=\"Arial 16 italic\")\n", "Texte_Widget.pack() #placement du texte\n", "\n", "bouton_quitter = Button(fenetre, text=\"Quitter\", command=fenetre.quit)\n", "bouton_quitter.pack(side=BOTTOM) #placement du bouton\n", "\n", "bouton_ecriture=Button(fenetre, text=\"ecrire un message\", command=ecrire)\n", "bouton_ecriture.pack(side=BOTTOM) #placement du bouton\n", "\n", "fenetre.mainloop()\n", "fenetre.destroy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Insérer un widget Entry" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ce type de widget permet de rentrer une donnée, un peu comme la syntaxe $input()$ dans un script Python.\n", "\n", "La syntaxe est : $Entry(fenetre)$ pour indiquer l'endroit où le mettre. La méthode $.pack$ renseigne l'endroit dans la fenêtre où placer ce widget." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "from tkinter import *\n", "fenetre=Tk()\n", "fenetre.geometry(\"800x400\")\n", "\n", "Texte_Widget=Label(fenetre,text=\"Placement d'un widget Entry()\",font=\"Arial 16 italic\",fg=\"blue\")\n", "Texte_Widget.pack() #placement du texte\n", "\n", "bouton_quitter = Button(fenetre, text=\"Quitter\", command=fenetre.quit)\n", "bouton_quitter.pack(side=BOTTOM) #placement du bouton\n", "\n", "\n", "Entree=Entry(fenetre)\n", "Entree.pack(side=RIGHT)\n", "\n", "\n", "Texte_Widget=Label(fenetre,text=\"rentrer un message :\",font=\"Arial 14\",fg=\"magenta\")\n", "Texte_Widget.pack(side=RIGHT)\n", "\n", "fenetre.mainloop()\n", "fenetre.destroy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour l'instant apparaît une boîte pour rentrer n'importe quel texte. Le problème est que l'on n'en fait rien ... pour l'instant! \n", "\n", "Pour ensuite exploiter l'entrée renseignée, on utilise la méthode $.get()$ afin de récolter les données. Il est à noter que la récupération se fait par défaut en chaiîne de caractères. Il convient le cas échéant d'utiliser les syntaxes $int( ... )$, $float( ...)$ ou $list( ... )$ afin de convertir la donnée récoltée en entier, en nombre flottant ou en liste." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "## Commande Mystère\n", "from random import *\n", "\n", "Liste_Couleurs=[\"red\",\"yellow\",\"blue\",\"orange\",\"green\",\"magenta\"]\n", "\n", "\n", "def mystere() :\n", " global Liste_Couleurs\n", " Message=Entree.get() # récolte l'information dans le champ du widget Entry()\n", " Message_inverse=str(\"\")\n", " for lettre in Message :\n", " Message_inverse=lettre+Message_inverse\n", " Message_inverse*=randint(1,5)\n", " \n", " Texte_mystere=Label(fenetre,text=Message_inverse,fg=Liste_Couleurs[randint(0,len(Liste_Couleurs))])\n", " Texte_mystere.pack(side=TOP)\n", "\n", "## Interface graphique\n", "from tkinter import *\n", "fenetre=Tk()\n", "fenetre.geometry(\"800x400\")\n", "\n", "Texte_Widget=Label(fenetre,text=\"Placement d'un widget Entry()\",font=\"Arial 16 italic\",fg=\"blue\")\n", "Texte_Widget.pack() #placement du texte\n", "\n", "bouton_quitter = Button(fenetre, text=\"Quitter\", command=fenetre.quit)\n", "bouton_quitter.pack(side=BOTTOM) #placement du bouton\n", "\n", "bouton_mystere = Button(fenetre, text=\"Action mystère\", command=mystere)\n", "bouton_mystere.pack(side=BOTTOM) #placement du bouton\n", "\n", "Entree=Entry(fenetre)\n", "Entree.pack(side=RIGHT)\n", "\n", "\n", "Texte_Widget=Label(fenetre,text=\"rentrer un message :\",font=\"Arial 14\",fg=\"magenta\")\n", "Texte_Widget.pack(side=RIGHT)\n", "\n", "fenetre.mainloop()\n", "fenetre.destroy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Insérer un widget Canvas" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Le widget $Canvas$ est sans doute le widget le plus utile dans une interface $Tkinter$. En effet, ce widget $Canvas$ est une sous-fenêtre graphique dans laquelle est peut effectuer des dessins de formes (lignes, points, rectangles, polygones, cercles ...) ou même insérer du texte. \n", "\n", "Comme on le verra à travers des exemples, on peut effectuer des modélisations graphiques de beaucoup de phénomènes physiques mais également faire une sortie graphique d'un script Python, comme nous pourront le voir dans des T.D. ultérieurs.\n", "\n", "Voici un bref aperçu des syntaxes associées à Canvas :\n", "\n", "- la syntaxe $canevas = Canvas(fenetre, width =500, height =400, bg =\"$#$F5DA81\")$ permet de nommer un Canvas $canevas$, inséré dans la fenêtre $fenetre$, avec les dimensions en largeur et en hauteur (ennombre de pixels, sachant que le pixel $(0,0)$ correspond au bord supérieur gauche et le pixel $(500,0)$ correspond au bord supérieur droit), et $bg$ (pour background) colorie le fond du canevas en une certaine couleur\n", "\n", "- la méthode $.pack( padx =5, pady =5)$ permet d'insérer le $Canvas$ ainsi défini dans la fenêtre, les chiffres $5$ renseignant la marge du bord\n", "\n", "- la méthode $.create$_$rectangle(x_min,y_min,x_max,y_max,fill=\"red\",outline=\"green\")$ permet de créer un rectangle dont les sommmets opposés sont définis dans les options, la couleur de remplissage est $\"red\"$ et la couleur du bord est $\"green\"$\n", "\n", "- la méthode $.create$_$oval(x_min,y_min,x_max,y_max,fill=\"blue\")$ définit une ellipse insérée dans le rectangle de sommets opposés $(x_min,y_min)$ et $(x_max,y_max)$ et remplie de la couleur $\"blue\"$\n", "\n", "- la méthode $.create$_$line(x_0,y_0,x_1,y_1,x_2,y_2,...,x_n,y_n,fill=\"red\",width=largeur)$ crée la ligne polygonale non fermée reliant les points $(x_0,y_0),(x_1,y_1)...,(x_n,y_n)$, le dernier point n'étant pas pris en compte. Pour tracer un point, il suffit de tracer une ligne avec deux points, $(x,y)$ et $(x+1,y+1)$ en y ajoutant une option $width$, sinon, on ne voit rien cela tracera un seul point. \n", "\n", "On peut rajouter des options du type $smooth=True$ pour lisser la courbe et on utilise alors $splinesteps=12$ pour le lissage. On peut utiliser des flèches : $Arrow=BOTH$, ou $Arrow=FIRST$, ou $Arrow=LAST$ avec une utilisation assez intuitive\n", "\n", "- la méthode $.create$_$text(x,y,text=\"Texte à afficher\",fill=\"couleur\",font=\"police utilisée\")$ affiche un texte dans le $Canvas$\n", "\n", "- la méthode $canevas.delete(objet)$ détruit des objets existants dans le $canevas$. La méthode canevas.delete(objet) détruit $objet$ dans le $canevas$. Pour tout détruire, on utilise $.delete(ALL)$ et sinon $.destroy(objet)$ si $objet$ a été défini comme un widget de la $fenetre$. On pourra ainsi créer l'illusion d'un mouvement en : \n", "\n", " - affichant un objet\n", " - laissant un certain laps de temps d'attente par $fenetre.after(Delta$_$T)$, où $Delta$_$T$ est un nombre indiquant le temps d'attente en millisecondes\n", " - mettant à jour la fenêtre graphique par $fenetre.update()$\n", " - en réaffichant un autre objet ...\n", " \n", "Voici un premier exemple :" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "from tkinter import *\n", "fenetre=Tk()\n", "fenetre.geometry(\"700x500\")\n", "\n", "Texte_Widget=Label(fenetre,text=\"Placement d'un widget Canvas()\",font=\"Arial 16 italic\",fg=\"blue\")\n", "Texte_Widget.pack(side=TOP) #placement du texte\n", "\n", "canevas=Canvas(fenetre,width=500,height=400,bg=\"#A9F5E1\")\n", "canevas.pack(padx=5,pady=5) # laisse une certaine marge par rapport au bord de la fenêtre\n", "\n", "#canevas.create_line(10,10,50,200,100,20,150,400,200,50,400,400,50,100,width=1,fill=\"red\",smooth=True) #trace une ligne brisée ou ondulée\n", "\n", "#canevas.create_line(250,250,251,251,fill=\"black\",width=4) #trace un point\n", "\n", "canevas.create_polygon(10,10,50,200,100,20,150,400,200,50,400,400,50,100,width=3,fill=\"red\",outline=\"blue\",smooth=True) # créé une figure polygonale remplie ou un domaine fermé rempli\n", "\n", "Texte=canevas.create_text(300,340,text=\"Voici un petit message !!!\",fill=\"magenta\",font=\"Brush-Script-CE 20\") #écriture d'un texte sur le Canvas\n", "\n", "bouton_quitter = Button(fenetre, text=\"Quitter\", command=fenetre.quit)\n", "bouton_quitter.pack(side=BOTTOM) #placement du bouton\n", "\n", "\n", "fenetre.mainloop()\n", "fenetre.destroy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Il existe une dernière méthode à présenter concernant le Canvas : la méthode $.bind$.\n", " \n", "La syntaxe $canevas.bind($ *<*\"Button-1\"*>* $ , commande$_$click$_$gauche)$ induit par un clic-gauche sur l'un des pixels du canevas la fonction nommée $commande$_$click$_$gauche$, laquelle sera définie de la façon suivante :\n", "\n", "def $commande$_$click$_$gauche(event)$ : #l'élément $event$ fait référence au pixel pointé\n", "\n", "> x=event.x #on récupère l'abscisse du pixel pointé\n", "\n", "> y=event.y #on récupère l'ordonnée du pixel pointé\n", " \n", " suite de la fonction ..\n", " \n", "On voit donc que l'on peut interagir via la souris sur un canevas, \n", "\n", "On peut faire de même avec $canevas.bind($ *<*\"Button-1\"*>* $ , commande$_$click$_$droit)$ pour un clic-droit. Voici un petit exemple :" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "## Définition des fonctions de click et de la fonction recommencer\n", "\n", "def recommencer() :\n", " \"\"\" efface les tracés effectués\"\"\"\n", " global Liste_Points #pour pouvoir agir sur Liste_Points\n", " canevas.delete(ALL)\n", " Liste_Points=[] #on réinitialise Liste_Points à vide\n", "\n", "Liste_Points=[]\n", "\n", "def commande_click_gauche(event) :\n", " \"\"\" trace une ligne ondulée en remplissant une liste de points\"\"\"\n", " global Liste_Points # pour pouvoir modifier Liste_Points par cette fonction\n", " \n", " x,y=event.x,event.y # coordonnées du pixels pointé par clic-gauche\n", " Liste_Points.append([x,y])\n", " \n", " if len(Liste_Points)>1 : # si il y a au moins deux points pour pouvoir tracer un début de courbe ...\n", " canevas.create_line(Liste_Points[-2][0],Liste_Points[-2][1],Liste_Points[-1][0],Liste_Points[-1][1],fill=\"red\")\n", "\n", "def commande_click_droit(event) :\n", " \"\"\" trace le carré rempli de côté 10*10 pixels, à condition que celui-ci ne déborde pas du canevas\"\"\"\n", " x,y=event.x,event.y # coordonnées du pixels pointé par clic-gauche\n", " \n", " if x<470 and y<370 : #le carré ne va pas déborder du canevas de taille 500*400\n", " canevas.create_rectangle(x,y,x+30,y+30,fill=\"yellow\",outline=\"green\",width=3)\n", " \n", "## Interface\n", "\n", "from tkinter import *\n", "\n", "fenetre=Tk()\n", "fenetre.geometry(\"600x550\")\n", "\n", "Texte_Widget=Label(fenetre,text=\"Association d'un click et d'une commande\",font=\"Rockwell 18\",fg=\"blue\")\n", "Texte_Widget.pack(side=TOP) #placement du texte\n", "\n", "canevas=Canvas(fenetre,width=500,height=400,bg=\"#A9F5E1\")\n", "canevas.pack(padx=5,pady=5) # laisse une certaine marge par rapport au bord de la fenêtre\n", "\n", "canevas.bind(\"\", commande_click_gauche)\n", "canevas.bind(\"\", commande_click_droit)\n", "\n", "\n", "\n", "Texte_click_gauche=Label(fenetre,text=\"click_gauche : tracé d'une ligne brisée\",font=\"Brush-Script-CE 18\",fg=\"#01DF01\")\n", "Texte_click_gauche.pack() #placement du texte\n", "\n", "\n", "Texte_click_droit=Label(fenetre,text=\"click_droit : affichage d'un carré de côté 30 pixels\",font=\"Brush-Script-CE 18\",fg=\"red\")\n", "Texte_click_droit.pack() #placement du texte\n", "\n", "bouton_quitter = Button(fenetre, text=\"Quitter\", command=fenetre.quit)\n", "bouton_quitter.pack(side=RIGHT) #placement du bouton\n", "\n", "\n", "bouton_recommencer = Button(fenetre, text=\"Tout effacer\", command=recommencer)\n", "bouton_recommencer.pack(side=RIGHT) #placement du bouton\n", "\n", "fenetre.mainloop()\n", "fenetre.destroy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Il est tout à fait possible, lors d'une commande gérée par un widget $Button$ ou $Canvas$ via un clic-souris de détruire certains widgets existants et d'en faire apparaître d'autres. En particulier, il est tout à fait possible de créer un $.bind(...)$ gérant une certaine commande et que dans cette commande, on définisse un deuxième $.bind()$ remplaçant la première association $Canvas-commande$ en une autre." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Trois exemples d'interface graphique" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exemple 1 : le billard elliptique" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Voici un exemple d'interface graphique modélisant la trajectoire d'une bille à l'intérieur d'une ellipse, les points verts étant les deux foyers, les chocs étant élastiques, sans perte d'énergie, selon les lois de Descartes :" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "from tkinter import *\n", "\n", "from math import *\n", "\n", "import numpy\n", "\n", "\n", "######### Fonction calculant le vecteur tangent normalisé #########\n", "\n", "def T(x,y) : # x et y correspondent aux coordonnées du pixel\n", " X=x-Centre_X\n", " Y=y-Centre_Y # coordonnées dans le repère lié au centre de l'ellipse\n", " T_X=-Y/b**2\n", " T_Y=X/a**2\n", " Norme=sqrt(T_X**2+T_Y**2)\n", " return [T_X/Norme,T_Y/Norme]\n", "\n", "\n", "######### Fonction calculant à partir d'un vecteur incident u et d'un point de contact (x,y) avec le bord, le vecteur réfléchi v ##########\n", "\n", "def Reflechi(u,x,y) :\n", " Tang=T(x,y)\n", " C=Tang[0]**2-Tang[1]**2\n", " D=2*Tang[0]*Tang[1]\n", " return [C*u[0]+D*u[1],D*u[0]-C*u[1]]\n", " \n", "#print(Reflechi([5,3],2,2))\n", " \n", "######### Fonction testant la proximité du bord ##########\n", "\n", "def Bord(x,y) :\n", " X=x-Centre_X\n", " Y=Centre_Y-y # coordonnées dans le repère lié au centre de l'ellipse\n", " if X**2/a**2+Y**2/b**2<=1 :\n", " return False # le point est à l'intérieur\n", " else:\n", " return True # le point vien de dépasser le bord\n", "\n", "#print(Bord(440,50))\n", "\n", "\n", " \n", "######### démarrage de l'animation #############\n", "\n", "def point_Depart(event) :\n", " global Point,L\n", " Point=[event.x,event.y]\n", " L=Point\n", " canevas.create_oval(L[0]-Rayon,L[1]-Rayon,L[0]+Rayon,L[1]+Rayon,fill='blue')\n", "#Point=[Centre_X,Centre_Y]\n", "vecteur=[3,2]\n", "#L=Point\n", "u=vecteur\n", "def go() :\n", " global Point,vecteur,flag,L,u\n", " flag=1\n", " \n", " while flag :\n", " fenetre.update()\n", " while not(Bord(L[0],L[1])) and flag :\n", " \n", " canevas.delete(ALL)\n", " canevas.create_oval(50,50,w-50,h-50, fill='white')\n", " foyer=sqrt(Grand_Axe**2-Petit_Axe**2)/2\n", " canevas.create_oval(Centre_X-foyer-Rayon,Centre_Y-Rayon,Centre_X-foyer+Rayon,Centre_Y+Rayon,fill='green') # dessin du foyer gauche\n", " canevas.create_oval(Centre_X+foyer-Rayon,Centre_Y-Rayon,Centre_X+foyer+Rayon,Centre_Y+Rayon,fill='green')\n", " canevas.create_oval(L[0]-Rayon,L[1]-Rayon,L[0]+Rayon,L[1]+Rayon,fill='red')\n", " fenetre.after(5)\n", " fenetre.update()\n", " L=[L[0]+u[0],L[1]+u[1]] \n", " if flag :\n", " L=[L[0]-u[0],L[1]-u[1]]\n", " u=Reflechi(u,L[0],L[1])\n", " \n", " \n", "########## arrêt de l'animation ###########\n", "\n", "def stop() :\n", " global flag,L,u\n", " flag=0\n", " Point=L\n", " vecteur=u\n", "\n", "\n", " \n", "############### Interface Graphique ################\n", "Grand_Axe=700 # taille de l'ellipse\n", "\n", "Petit_Axe=370 # taille de l'ellipse\n", "\n", "a=Grand_Axe/2\n", "b=Petit_Axe/2\n", "w=Grand_Axe+100\n", "h=Petit_Axe+100\n", "Centre_X=w/2\n", "Centre_Y=h/2\n", "Rayon=5\n", "\n", "\n", "fenetre = Tk()\n", "\n", "titre=Label(fenetre,text=\"Le billard elliptique\",fg=\"red\",font=\"Arial 16\")\n", "titre.pack(side=TOP)\n", "\n", "canevas = Canvas(fenetre, width =w, height =h, bg =\"#F5DA81\")\n", "canevas.bind(\"\", point_Depart)\n", "canevas.pack( padx =5, pady =5)\n", "\n", "\n", "bouton_quitter = Button(fenetre, text=\"Quitter\", command=fenetre.quit)\n", "bouton_quitter.pack(side=RIGHT)\n", "\n", "\n", "\n", "b2 = Button(fenetre, text ='Stop', command =stop)\n", "b2.pack(side=RIGHT, padx =3, pady =3)\n", "\n", "\n", "b1 = Button(fenetre, text ='Go!', command =go)\n", "b1.pack(side=RIGHT, padx =3, pady =3)\n", "\n", "\n", "elli=canevas.create_oval(50,50,w-50,h-50, fill='white')\n", "\n", "foyer=sqrt(Grand_Axe**2-Petit_Axe**2)/2\n", "canevas.create_oval(Centre_X-foyer-Rayon,Centre_Y-Rayon,Centre_X-foyer+Rayon,Centre_Y+Rayon,fill='green') # dessin du foyer gauche\n", "canevas.create_oval(Centre_X+foyer-Rayon,Centre_Y-Rayon,Centre_X+foyer+Rayon,Centre_Y+Rayon,fill='green') # dessin du foyer droit\n", " \n", "\n", "fenetre.mainloop()\n", "fenetre.destroy()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exemple 2 : l'oscillateur à ressort" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Voici une deuxième interface modélisant la trajectoire d'une masse ponctuelle reliée à un ressort, dans le champ de pesanteur uniforme. Le widget $Entry()$ permet de quantifier le coefficient de frottement fluide (frottement proportionnel à la vitesse de déplacement) lors de cette trajectoire. \n", "\n", "Le point jaune donne la position d'équilibre." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "from scipy.integrate import *\n", "from numpy import *\n", "from pylab import *\n", "from tkinter import *\n", "\n", "\n", "\n", "######### Paramètres de l'oscillateur\n", "\n", "g= 9.81 # champ de pseanteur en m.s^(-2)\n", "\n", "l_0 = 100 # longueur à vide en pixels\n", "\n", "m =10 # masse ponctuelle en kg\n", "\n", "k_Raideur = 6 # constante de raideur du ressort en N.m^(-1)\n", "\n", "l_Equi=m*g/k_Raideur+l_0 # longueur à l'équilibre\n", "\n", "### caractéristiques de la fenètre et du ressort\n", "\n", "largeur=600 # largeur en pixels \n", "\n", "x_c = largeur//2 # abscisse du point d'attache\n", "\n", "hauteur=360 # hauteur en pixels\n", "\n", "y_x=hauteur//2 # ordonnée du point d'attache\n", "\n", "rayon=5\n", "\n", "x_C,y_C=largeur//2,hauteur//3 # coordonnées du point d'attache\n", "\n", "x_R=10 # épaisseur des spires en abscisse \n", "\n", "########### Interface graphique\n", "\n", "\n", "\n", "def Dessin_Ressort(L,A) :\n", " global m,g,k_Raideur,l_0 ,l_Equi\n", " \n", " canevas.delete(ALL)\n", " canevas.create_oval(x_C-rayon,y_C+l_Equi-rayon,x_C+rayon,y_C+l_Equi+rayon,fill=\"yellow\")\n", " y_R=(L-rayon)/21 # dilatation du ressort \n", " canevas.create_oval(x_C-rayon,y_C-rayon,x_C+rayon,y_C+rayon,fill=\"green\")\n", " RessortX=[x_C] + [x_C+x_R*(-1)**k for k in range(20)]+[x_C,x_C] # ressort initial pour les abscisses\n", " RessortY=[y_C]+ [y_C+(k+1)*y_R for k in range(21)]+[y_C+L] # ressort initial pour les ordonnées\n", " # il faut appliquer l'homothétie en ordonnées de rapport h de sorte que le centre du point M soit exactement à une ordonnée y_C+L, puis la rotation du tout d'un angle A autour du point (x_C,y_C)\n", " p=len(RessortX)\n", " for k in range(p) : # rotation du ressort\n", " RessortX[k],RessortY[k]=cos(A)*(RessortX[k]-x_C)+sin(A)*(RessortY[k]-y_C)+x_C,cos(A)*(RessortY[k]-y_C)-sin(A)*(RessortX[k]-x_C)+y_C\n", " for k in range(p-1) :\n", " canevas.create_line(RessortX[k],RessortY[k],RessortX[k+1],RessortY[k+1])\n", " canevas.create_oval(RessortX[-1]-rayon,RessortY[-1]-rayon,RessortX[-1]+rayon,RessortY[-1]+rayon,fill=\"red\")\n", "\n", "\n", "def point_Depart(event) :\n", " global largeur,hauteur,rayon,x_C,y_C,l_1,theta_1\n", " x,y=event.x,event.y\n", " \n", " Longueur=sqrt((x-x_C)**2+(y-y_C)**2)\n", " if x>=x_C :\n", " Angle=arccos((y-y_C)/Longueur)\n", " else :\n", " Angle=-pi+arccos((y_C-y)/Longueur)\n", " l_1,theta_1=Longueur,Angle\n", " Dessin_Ressort(Longueur,Angle)\n", " \n", " \n", "\n", " \n", "def go() :\n", " global flag, Longueur,Angle,sol_l,sol_theta,t\n", " flag=1\n", " ######### Résolution de l'équation \n", "\n", "\n", " t=linspace(0,500,1500) # intervalle d'étude\n", "\n", "# condition initiale du type y(t_0)=y_1 et y'(t_0)=y_2\n", " \n", "\n", " condition_Initiale=[l_1,theta_1,0,0] # à t=0, vitesse nulle en l et en theta\n", "\n", "# résolution de l'équation différentielle \n", "\n", " def rhs(Z,t) : # convention Z<->(l,theta,l',theta')\n", " \n", " f=Coeff_Frottement.get() # coefficient d'amortissement fluide [f=0 -> pas de frottements]\n", " if len(f)!=0 : # si on a rentré quelque chose ...\n", " f=float(f)\n", " else :\n", " f=0 # par défaut, pas de frottement ...\n", " return (Z[2],Z[3],Z[0]*Z[3]**2+g*cos(Z[1])-k_Raideur/m*(Z[0]-l_0)-f*Z[2]/m,-2*Z[2]*Z[3]/Z[0]- g/Z[0]*sin(Z[1])-f*Z[3]/m)\n", "\n", " sol=odeint(rhs,condition_Initiale,t) # les solutions sont de la forme d'une matrice à quatre colonnes, les lignes étant les [l(T),theta(T),l'(T),theta'(T)] lorsque T varie dans t\n", " sol_l=sol[:,0] # la première colonne : l en fonction de T\n", " sol_theta=sol[:,1] # la deuxième colonne : theta en fonction de T\n", " \n", " \n", " while flag :\n", " for T in t :\n", " if not(flag) :\n", " break\n", " L=sol_l[T]\n", " A=sol_theta[T]\n", " fenetre.after(2)\n", " Dessin_Ressort(L,A)\n", " fenetre.update()\n", " \n", "\n", "def stop() :\n", " global flag \n", " flag=0\n", "\n", "fenetre = Tk()\n", "\n", "\n", "titre=Label(fenetre,text=\"L'oscillateur à ressort\",fg=\"red\",font=\"Arial 20\")\n", "titre.pack(side=TOP)\n", "\n", "canevas = Canvas(fenetre, width =largeur, height =hauteur, bg =\"#F5DA81\")\n", "L,A=l_Equi,0\n", "Dessin_Ressort(L,A)\n", "canevas.bind(\"\", point_Depart)\n", "canevas.pack( padx =5, pady =5)\n", "\n", "\n", "bouton_quitter = Button(fenetre, text=\"Quitter\", command=fenetre.quit)\n", "bouton_quitter.pack(side=RIGHT)\n", "\n", "\n", "Coeff_Frottement=Entry(fenetre)\n", "Coeff_Frottement.pack(side=RIGHT)\n", "\n", "Texte=Label(fenetre,text=\"Coefficient de frottement : \",font=\"Arial 12\",fg=\"blue\")\n", "Texte.pack(side=RIGHT)\n", "\n", "\n", "b2 = Button(fenetre, text ='Stop', command =stop)\n", "b2.pack(side=RIGHT, padx =3, pady =3)\n", "\n", "\n", "b1 = Button(fenetre, text ='Go!', command =go)\n", "b1.pack(side=RIGHT, padx =3, pady =3)\n", "\n", "\n", " \n", "\n", "fenetre.mainloop()\n", "fenetre.destroy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exemple 3 : le Sudoku" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "L'interface qui suit modélise le jeu du Sudoku. \n", "\n", "Dans le script suivant :\n", "\n", "- on demande à l'utilisateur de remplir certaines cases ; la correction est possible avec la case vide ; les cases remplies sont vertes\n", "\n", "- lors de la résolution du jeu, les cases les plus faciles sont calculées ; ces cases sont en bleu\n", "\n", "- vient peut-être le moment où l'on doit tester des chiffres dans des cases encore vides ; à partir de ce moment, les cases remplies sont rouges.\n", "\n", "Une fois la résolution terminée, le temps de résolution est affiché." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pylab import *\n", "\n", "import time # pour les temps d'exécution\n", "\n", "\n", "from tkinter import * # pour l'interface graphique\n", "\n", "### un sudoku partiellement rempli est un tableau de format 9*9*10 ; chaque case $M[i,j]$ donne les chiffres autorisés dans la case $(i,j)$ et la première place initialement valant 0 donne le marquage de la case : la case est-elle définitive ou non ?\n", "\n", "\n", "### Variable de test de modification ###\n", "\n", "Test_Modif=True\n", "\n", "\n", "###\n", "\n", "\n", "def Sudoku_Vide() :\n", " Sudo=zeros((9,9,10),dtype=int)\n", " for i in range(9) :\n", " for j in range(9) :\n", " for k in range(10) :\n", " Sudo[i,j,k]=k\n", " return Sudo\n", "\n", "### Tableau vide ###\n", "\n", "M=Sudoku_Vide()\n", "\n", "# print(Sudoku_Vide())\n", "\n", "\n", "def Traitement(L) :\n", " \"\"\" retourne la liste sans les multiplicités d'occurrences\"\"\"\n", " A=[]\n", " for x in L :\n", " if not(x in A) :\n", " A.append(x)\n", " return A\n", "\n", "def Cases_Reliees(i,j) :\n", " Liste_Temp=[]\n", " for k in range(9):\n", " Liste_Temp.extend([(i,k),(k,j)])\n", " for x in range(3) :\n", " for y in range(3) :\n", " Liste_Temp.append((3*(i//3)+x,3*(j//3)+y))\n", " return [X for X in Traitement(Liste_Temp) if X!=(i,j)] # la case (i,j) n'est pas comptabilisée lors de la mise à jour (sinon, cela créé une case vide après...)\n", "\n", "# print(Cases_Reliees(7,1))\n", "\n", "# M=Sudoku_Vide()\n", "# M[8,7]=(1,1,2,0,0,0,0,0,0,0)\n", "# print(Recherche_Singleton(M))\n", "\n", "\n", "\n", "def Placement_Chiffre(M,c) :\n", " \"\"\" regarde si le chiffre c peut être placé dans la grille sachant qu'il est forcément sur une ligne, une colonne et dans un sous-carré \"\"\"\n", " global Test_Modif\n", "\n", " P=M.copy()\n", "\n", " for x in range(3) :\n", " for y in range(3) : # on fait varier les sous-carrés\n", " compteur=0\n", " for i in range(3) :\n", " for j in range(3) : # on travaille dans le sous-carré (x,y)\n", " if (c in P[3*x+i,3*y+j]) : # le chiffre c peut être sur cle\n", " compteur+=1\n", " cle=(3*x+i,3*y+j)\n", " if compteur==1 :\n", " a,b=cle\n", " if P[a,b,0]==0 and len([x for x in P[a,b,1:] if x!=0])!=1 : # si compteur = 1 et la case n'est pas encore remplie avec c\n", " P=Mise_A_Jour(P,a,b,c)\n", "\n", " for i in range(9) :\n", " compteur=0\n", " for j in range(9) :\n", " if (c in P[i,j]) : # le chiffre c peut être sur (i,j)\n", " compteur+=1\n", " colonne=j\n", " if compteur==1 :\n", " if P[i,colonne,0]==0 and len([x for x in P[i,colonne,1:]]) != 1 : # si compteur = 1 et la case n'est pas déjà remplie avec c\n", " P=Mise_A_Jour(P,i,colonne,c)\n", "\n", " for j in range(9) :\n", " compteur=0\n", " for i in range(9) :\n", " if (c in P[i,j]) : # le chiffre c peut être sur (i,j)\n", " compteur+=1\n", " ligne=i\n", " if compteur==1 :\n", " if P[ligne,j,0]==0 and len([x for x in P[ligne,j,1:]]) !=1 : # si compteur = 1 et la case n'est pas déjà remplie avec c\n", " P=Mise_A_Jour(P,ligne,j,c)\n", "\n", " return P\n", "\n", "\n", "def Mise_A_Jour(M,i,j,c) :\n", " \"\"\" met à jour le sudoku M en plaçant à la case [i,j] le chiffre c\"\"\"\n", " global Test_Modif\n", "\n", " Test_Modif=True # on obtient une modification du tableau ...\n", "\n", " P=M.copy()\n", " for k in range(1,10) :\n", " if k!=c :\n", " P[i,j,k]=0\n", " P[i,j,0]=c # marquage de la case en plaçant le bon chiffre en place [0] pour une identification de case plus simple\n", "\n", " L=Cases_Reliees(i,j)\n", " for case in L :\n", " r,s=case\n", " P[r,s,c]=0 # le chiffre $c$ ne peut se trouver dans les cases reliées\n", "\n", " return P\n", "\n", "def Successeur(M) :\n", " \"\"\" remplit les cases du sudoku M qui peuvent l'être, jusqu'à ne plus pouvoir le faire \"\"\"\n", " global Test_Modif\n", "\n", " P=M.copy()\n", " while Test_Modif :\n", " Test_Modif=False\n", " for c in range(1,10) :\n", " P=Placement_Chiffre(P,c)\n", " return P.copy()\n", "\n", "def Case_Mini(F) :\n", " \"\"\" recherche une case non encore remplie avec le minimum de possibilités \"\"\"\n", " m=10\n", " case=[True,True]\n", "\n", " for i in range(9) :\n", " for j in range(9) :\n", " if F[i,j,0]==0 :\n", " Q=[x for x in F[i,j,1:] if x!=0]\n", " if len(Q)1 :\n", " case=[i,j]\n", " m=len(Q)\n", "\n", " return case # si case=[-1,-1], alors sudoku rempli !!\n", "\n", "def Test_Valide(D) :\n", " \"\"\" teste si un tableau est valide \"\"\"\n", "\n", " for i in range(9) :\n", " for j in range(9) :\n", "\n", " if [x for x in D[i,j,1:] if x!=0] ==[] :\n", " return False\n", "\n", " for c in range(1,10) :\n", "\n", " for x in range(3) :\n", " for y in range(3) : # on fait varier les sous-carrés\n", " compteur=0\n", " for i in range(3) :\n", " for j in range(3) : # on travaille dans le sous-carré (x,y)\n", " if (c in D[3*x+i,3*y+j,1:]) : # le chiffre c peut être sur cle\n", " compteur+=1\n", "\n", " if compteur ==0 :\n", " return False\n", "\n", " for i in range(9) :\n", " compteur=0\n", " for j in range(9) :\n", " if (c in D[i,j,1:]) : # le chiffre c peut être sur (i,j)\n", " compteur+=1\n", " if compteur == 0 : # contradiction\n", " return False\n", "\n", " for j in range(9) :\n", " compteur=0\n", " for i in range(9) :\n", " if (c in D[i,j,1:]) : # le chiffre c peut être sur (i,j)\n", " compteur+=1\n", "\n", " if compteur == 0 : # contradiction\n", " return False\n", "\n", " return True\n", "\n", "def Sphere(M,case) :\n", " \"\"\" retourne la sphère de centre M et de rayon 1, en remplissant case avec des chiffres potentiellement valides \"\"\"\n", " L=[]\n", " i,j=case\n", " for x in M[i,j,1:] :\n", " if x!=0 :\n", " L.append(Mise_A_Jour(M,i,j,x))\n", " return L\n", "\n", "def Test_fin(S) :\n", " \"\"\" teste si une liste S de grilles contient une grille valide complète \"\"\"\n", "\n", " for V in S :\n", " if Test_Valide(V) and Case_Mini(V)==[True,True] :\n", " return True\n", "\n", " return False\n", "\n", "\n", "\n", "\n", "\n", "def Resolution(M) :\n", " \"\"\" on fait un parcours en largeur \"\"\"\n", " global rayon\n", "\n", " rayon=0\n", " P1=Successeur(M)\n", " L_S=[[P1.copy()]]\n", " for i in range(9) :\n", " for j in range(9) :\n", " if P1[j,i,0]!=0 and M[j,i,0]==0 :\n", " canevas.create_rectangle(Bord_Case(i),Bord_Case(j),Bord_Case(i)+40,Bord_Case(j)+40,fill=\"#A9A9F5\")\n", " canevas.create_text(Bord_Case(i)+20,Bord_Case(j)+20, text=str(P1[j,i,0]), font=\"Arial 25 italic\", fill=\"black\")\n", " fenetre.update()\n", "\n", " while Test_fin(L_S[-1])==False : # tant que la grille n'est pas complète\n", " rayon+=1 #on calcule le rayon pour adapter les couleurs des cases remplies\n", " L=L_S[-1]\n", " New_Sphere=[]\n", " for W in L :\n", " if Test_Valide(W) :\n", " case=Case_Mini(W)\n", " for Y in Sphere(W,case) :\n", " New_Sphere.append(Successeur(Y))\n", " L_S.append(New_Sphere.copy())\n", "\n", "\n", "\n", " for Z in L_S[-1] :\n", " if Test_fin([Z]) :\n", " P2=Z.copy()\n", " break\n", "\n", " # print('grille initiale',M[:,:,0])\n", " # print('après un pas',P1[:,:,0])\n", " # print('grille finale',P2[:,:,0])\n", "\n", " for i in range(9) :\n", " for j in range(9) :\n", " if P2[j,i,0]!=0 and P1[j,i,0]==0 :\n", " canevas.create_rectangle(Bord_Case(i),Bord_Case(j),Bord_Case(i)+40,Bord_Case(j)+40,fill=\"#F78181\")\n", " canevas.create_text(Bord_Case(i)+20,Bord_Case(j)+20, text=str(P2[j,i,0]), font=\"Arial 25 italic\", fill=\"black\")\n", " fenetre.update()\n", "\n", "### Fonctions de l'interface\n", "\n", "\n", "\n", "def Fin_Algorithme_Ecran() :\n", " global t_0,t_1,rayon\n", " Texte_A_Afficher=\"Algorithme Terminé en \"+str(round(t_1-t_0,2))+\" secondes !!!\\n Niveau de profondeur = \"+str(rayon)\n", " Texte_Fin=canevas.create_text(300,465,text=Texte_A_Afficher, font=\"Arial 13 italic\", fill=\"red\")\n", " for k in range(13) :\n", " canevas.delete(Texte_Fin)\n", " fenetre.after(300)\n", " fenetre.update()\n", " Texte_Fin=canevas.create_text(250,465,text=Texte_A_Afficher, font=\"Arial 13 italic\", fill=\"red\")\n", " fenetre.after(200)\n", " fenetre.update()\n", "\n", "flag = 0 # pour gérer le bouton b1\n", "\n", "def go():\n", " global t_0,t_1,b1,flag\n", " flag=1\n", " b1.destroy()\n", " t_0=time.perf_counter()\n", " Resolution(M)\n", " t_1=time.perf_counter()\n", " Fin_Algorithme_Ecran()\n", " flag=0\n", "\n", "def recommencer():\n", " global b1,M,Test_Modif,b2,flag\n", "\n", "\n", " canevas.delete(ALL)\n", " Dessiner_Sudoku_Vide()\n", "\n", "\n", " if flag :\n", " b1 = Button(fenetre, text ='Go!', command =go)\n", " b1.pack(side=RIGHT, padx =5, pady =5)\n", " flag=1\n", "\n", " M=Sudoku_Vide()\n", " Test_Modif=True\n", "\n", "\n", "\n", "# Remplissage des cases\n", "\n", "def Bord_Case(k):\n", " \"\"\" fonction utilisée pour calculer le pixel du bord de case k\"\"\"\n", " return 50+(k//3)*2+42*k+2\n", "\n", "\n", "Valeur=range(1,10)\n", "\n", "def valeur1() :\n", " global Valeur\n", " Valeur=1\n", "\n", "def valeur2() :\n", " global Valeur\n", " Valeur=2\n", "\n", "def valeur3() :\n", " global Valeur\n", " Valeur=3\n", "\n", "def valeur4() :\n", " global Valeur\n", " Valeur=4\n", "\n", "def valeur5() :\n", " global Valeur\n", " Valeur=5\n", "\n", "def valeur6() :\n", " global Valeur\n", " Valeur=6\n", "\n", "def valeur7() :\n", " global Valeur\n", " Valeur=7\n", "\n", "def valeur8() :\n", " global Valeur\n", " Valeur=8\n", "\n", "def valeur9() :\n", " global Valeur\n", " Valeur=9\n", "\n", "def valeurvide() :\n", " global Valeur\n", " Valeur=range(1,10)\n", "\n", "\n", "def remplir_Case(event):\n", " global Valeur,M\n", "\n", " x,y=event.x,event.y\n", " clic_test1,clic_test2=False,False\n", " for k in range(9) :\n", " if abs(Bord_Case(k)+20-x)<20 :\n", " i=k\n", " clic_test1=True\n", " break\n", " for l in range(9) :\n", " if abs(Bord_Case(l)+20-y)<20 :\n", " j=l\n", " clic_test2=True\n", " break\n", "\n", " if clic_test1 and clic_test2 :\n", " if type(Valeur)==int :\n", " M=Mise_A_Jour(M,j,i,Valeur) # lignes/colonnes matrices et graphiques inversées\n", " canevas.create_rectangle(Bord_Case(i),Bord_Case(j), Bord_Case(i)+40,Bord_Case(j)+40,fill=\"#BCF5A9\")\n", " canevas.create_text(Bord_Case(i)+20,Bord_Case(j)+20, text=str(Valeur), font=\"Arial 25 italic\", fill=\"black\")\n", "\n", " else : # on remet la case vide avec la couleur du fond initial\n", " canevas.create_rectangle(Bord_Case(i),Bord_Case(j), Bord_Case(i)+40,Bord_Case(j)+40,fill=\"#F5DA81\")\n", " for k in range(10) :\n", " M[j,i,k]=k\n", "\n", "\n", "# dessin du Sudoku initial\n", "\n", "def Dessiner_Sudoku_Vide():\n", " for k in range(10) :\n", " if k%3==0 :\n", " canevas.create_rectangle(48,Bord_Case(k)-4,436,Bord_Case(k),fill=\"black\")\n", " canevas.create_rectangle(Bord_Case(k)-4,48,Bord_Case(k),436,fill=\"black\")\n", " else :\n", " canevas.create_rectangle(48,Bord_Case(k)-2,436,Bord_Case(k),fill=\"black\")\n", " canevas.create_rectangle(Bord_Case(k)-2,48,Bord_Case(k),436,fill=\"black\")\n", "\n", "\n", "fenetre = Tk()\n", "largeur=484\n", "hauteur=484\n", "\n", "#titre=Label(fenetre,text=\"Le Sudoku\")\n", "#titre.pack(side=TOP)\n", "\n", "canevas = Canvas(fenetre, width =largeur, height =hauteur, bg =\"#F5DA81\")\n", "canevas.bind(\"\",remplir_Case)\n", "canevas.pack( padx =5, pady =5)\n", "Dessiner_Sudoku_Vide()\n", "\n", "bouton_quitter = Button(fenetre, text=\"Quitter\", command=fenetre.quit)\n", "bouton_quitter.pack(side=RIGHT)\n", "\n", "\n", "b2 = Button(fenetre, text ='Recommencer', command =recommencer)\n", "b2.pack(side=RIGHT, padx =5, pady =5)\n", "\n", "\n", "b1 = Button(fenetre, text ='Go!', command =go)\n", "b1.pack(side=RIGHT, padx =5, pady =5)\n", "\n", "\n", "bouton1 = Button(fenetre, text ='1',font=\"Arial 20 italic\", command =valeur1)\n", "bouton1.pack(side=LEFT, padx =5, pady =5)\n", "\n", "\n", "bouton2 = Button(fenetre, text ='2',font=\"Arial 20 italic\", command =valeur2)\n", "bouton2.pack(side=LEFT, padx =5, pady =5)\n", "\n", "bouton3 = Button(fenetre, text ='3',font=\"Arial 20 italic\", command =valeur3)\n", "bouton3.pack(side=LEFT, padx =5, pady =5)\n", "\n", "bouton4 = Button(fenetre, text ='4',font=\"Arial 20 italic\", command =valeur4)\n", "bouton4.pack(side=LEFT, padx =5, pady =5)\n", "\n", "bouton5 = Button(fenetre, text ='5',font=\"Arial 20 italic\", command =valeur5)\n", "bouton5.pack(side=LEFT, padx =5, pady =5)\n", "\n", "bouton6 = Button(fenetre, text ='6',font=\"Arial 20 italic\", command =valeur6)\n", "bouton6.pack(side=LEFT, padx =5, pady =5)\n", "\n", "bouton7 = Button(fenetre, text ='7',font=\"Arial 20 italic\", command =valeur7)\n", "bouton7.pack(side=LEFT, padx =5, pady =5)\n", "\n", "bouton8 = Button(fenetre, text ='8',font=\"Arial 20 italic\", command =valeur8)\n", "bouton8.pack(side=LEFT, padx =5, pady =5)\n", "\n", "bouton9 = Button(fenetre, text ='9',font=\"Arial 20 italic\", command =valeur9)\n", "bouton9.pack(side=LEFT, padx =5, pady =5)\n", "\n", "boutonvide = Button(fenetre, text ='vide',font=\"Arial 20 italic\", command =valeurvide)\n", "boutonvide.pack(side=LEFT, padx =5, pady =5)\n", "\n", "fenetre.mainloop()\n", "fenetre.destroy()\n", "\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exemple 4 : le GPS" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "L'interface qui suit modélise un calcul d'itinéraire routier. L'algorithme utilisé est l'algorithme A*..." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import numpy\n", "import scipy\n", "import matplotlib\n", "from pylab import *\n", "from random import *\n", "\n", "\n", "########## caractéristiques du labyrinthe\n", "\n", "########## Dans tout ce qui suit, les cases du labytinthe sont indexées par (i,j) correspondant à la case délimitée par (i,j) et (i+c,j+c). La diagonale du damier est (0,0) et (largeur,hauteur)\n", "\n", "######### grand labyrinthe\n", "\n", "c=8 # taille de chaque case\n", "largeur=150 # nombre de cases en largeur\n", "hauteur=70 # nombre de cases en hauteur\n", "\n", "######### petit labyrinthe\n", "# \n", "# c=20\n", "# largeur=40\n", "# hauteur=20\n", "# \n", "\n", "Taille_largeur=c*largeur\n", "Taille_hauteur=c*hauteur\n", "\n", "#Dep=[0,0] #case de départ\n", "\n", "#Arr=[largeur,hauteur] # case d'arrivée\n", "\n", "############ mise en place graphique #############\n", "\n", "from tkinter import *\n", "\n", "######### initialisation du labyrinthe ###########\n", "\n", "dico = {} #dictionnaire contenant les coordonnées de chaque cellule et une valeur 0 ou 1 si elles sont respectivement mortes ou vivantes\n", "\n", "for i in range(largeur+1) :\n", " for j in range(hauteur+1) :\n", " dico[i,j]=0\n", " \n", "\n", "def Heuristique(M,N) : #calcule la distance heuristique entre deux positions M et N\n", " #return abs(M[0]-N[0])+abs(M[1]-N[1]) : on choisit la distance de Manhattan\n", " return sqrt((M[0]-N[0])**2+(M[1]-N[1])**2) #: on choisit la distance euclidienne\n", "#print(Heuristique([4,5],[6,3]))\n", "\n", "def Dist_Heu(Dep,Arr,M) : # calcule la distance heuristique pour une position M\n", " return Heuristique(M,Arr)\n", "# on peut changer d'heuristique... comme on veut\n", "\n", "\n", "\n", "\n", "def Voisins(M) : # calcule la liste des voisins d'une position M relativement au labyrinthe\n", " global largeur, hauteur ,dico\n", " Liste_Voisins=[]\n", " if M[0]==0 : \n", " A=[1]\n", " elif M[0]==largeur : \n", " A=[-1]\n", " else :\n", " A=[-1,1]\n", " \n", " for i in A :\n", " if dico[M[0]+i,M[1]]==0 :\n", " Liste_Voisins.append([M[0]+i,M[1]])\n", " \n", " if M[1]==0 : \n", " B=[1]\n", " elif M[1]==hauteur : \n", " B=[-1]\n", " else :\n", " B=[-1,1]\n", " \n", " for j in B :\n", " if dico[M[0],M[1]+j]==0 :\n", " Liste_Voisins.append([M[0],M[1]+j])\n", " return Liste_Voisins\n", "\n", "\n", "def Test_Adj(M,N) : # teste si deux positions sont adjacentes\n", " return abs(M[0]-N[0])+abs(M[1]-N[1])==1\n", "\n", "\n", "#print(Test_Adj([5,6],[6,6]))\n", "\n", "\n", "def Meilleur(liste,Dep,Arr) : # calcule le meilleur noeud pour la distance heuristique dans une liste donnée\n", " M=liste[0] # par défaut, le meilleur est le premier terme \n", " for K in liste :\n", " if Dist_Heu(Dep,Arr,K)\", click_arrivee)\n", " \n", "def click_arrivee(event): # fonction qui choisit le point de départ\n", " global dico,Dep,Arr\n", " x=event.x\n", " y=event.y\n", " i=int(x/c)\n", " j=int(y/c)\n", " if [i,j] != Dep :\n", " Arr=[i,j]\n", " canevas.create_rectangle(i*c, j*c, (i+1)*c, (j+1)*c, fill='red')\n", " \n", " \n", " text_obst=Label(fenetre,text=\"Choix des obstacles \",fg=\"black\")\n", " text_obst.pack(side=LEFT)\n", " \n", " canevas.bind(\"\", click_gauche)\n", " canevas.bind(\"\", click_droit)\n", " \n", " b1 = Button(fenetre, text ='Imprimer le parcours non traité', command =parcours_Non_Traite)\n", " b2 = Button(fenetre, text ='Imprimer le parcours traité', command =parcours_Traite)\n", " b3 = Button(fenetre, text ='Recommencer', command =recommencer)\n", " b2.pack(side =BOTTOM, padx =3, pady =3)\n", " b1.pack(side =BOTTOM, padx =3, pady =3)\n", " b4 = Button(fenetre, text ='Quitter', command =fenetre.quit)\n", " b5 = Button(fenetre, text ='Obstacles aléatoires', command =laby_alea)\n", " b4.pack(side =RIGHT, padx =3, pady =3)\n", " b3.pack(side =RIGHT, padx =3, pady =3)\n", " b5.pack(side =RIGHT, padx =3, pady =3)\n", " dessiner()\n", "\n", "def laby_alea() : # créé un labyrinthe aléatoire\n", " canevas.delete(ALL)\n", " for i in range(largeur+1) :\n", " for j in range(hauteur+1) :\n", " if [i,j]!=Dep and [i,j]!=Arr :\n", " if randint(0,2) ==2 : # deux fois plus de cases blanches que de cases noires, en moyenne\n", " dico[i,j]=1\n", " else :\n", " dico[i,j]=0\n", " dessiner()\n", " \n", "def click_gauche(event): #fonction rendant vivante la cellule cliquée donc met la valeur 1 pour la cellule cliquée au dico\n", " global dico\n", " x=event.x\n", " y=event.y\n", " i=int(x/c)\n", " j=int(y/c)\n", " if [i,j]!=Dep and [i,j]!=Arr :\n", " canevas.create_rectangle(i*c, j*c, (i+1)*c, (j+1)*c, fill='black')\n", " dico[i,j]=1\n", "\n", "def click_droit(event): #fonction tuant la cellule cliquée donc met la valeur 0 pour la cellule cliquée au dico\n", " global dico\n", " x=event.x\n", " y=event.y\n", " i=int(x/c)\n", " j=int(y/c)\n", " if [i,j]!=Dep and [i,j]!=Arr :\n", " canevas.create_rectangle(i*c, j*c, (i+1)*c, (j+1)*c, fill='white')\n", " dico[i,j]=0\n", "\n", "\n", "def dessiner(): # dessins le tableau à partir des états\n", " global dico,Dep,Arr\n", " for i in range(largeur+2) :\n", " canevas.create_line(i*c,0,i*c,Taille_hauteur+c,width=1,fill='black')\n", " for j in range(hauteur+2) :\n", " canevas.create_line(0,j*c,Taille_largeur+c,j*c,width=1,fill='black')\n", " canevas.create_rectangle(Dep[0]*c,Dep[1]*c,Dep[0]*c+c,Dep[1]*c+c, fill='green') # case de départ\n", " canevas.create_rectangle(Arr[0]*c,Arr[1]*c,Arr[0]*c+c,Arr[1]*c+c, fill='red') # case d'arrivée\n", " for i in range(largeur+1) :\n", " for j in range(hauteur+1) :\n", " if dico[i,j]==1 :\n", " canevas.create_rectangle(i*c,j*c,(i+1)*c,(j+1)*c, fill='black')\n", " fenetre.update()\n", "\n", "def parcours_Non_Traite() : # imprime en couleur les cases de la liste fermée finale avant traitement\n", " canevas.delete(ALL)\n", " dessiner()\n", " Liste=Algorithme(Dep,Arr)\n", " for M in Liste[1:len(Liste)-1] : # pour ne pas colorier la dernière case d'arrivée\n", " canevas.create_rectangle(M[0]*c, M[1]*c, (M[0]+1)*c, (M[1]+1)*c, fill='#2EFEF7')\n", " fenetre.after(30)\n", " fenetre.update()\n", " if Liste[-1]!=Arr :\n", " for j in range(7) :\n", " canevas.create_rectangle(Liste[-1][0]*c, Liste[-1][1]*c, (Liste[-1][0]+1)*c, (Liste[-1][1]+1)*c, fill='#FF8000')\n", " fenetre.after(250)\n", " fenetre.update()\n", " canevas.create_rectangle(Liste[-1][0]*c, Liste[-1][1]*c, (Liste[-1][0]+1)*c, (Liste[-1][1]+1)*c, fill='#0101DF')\n", " fenetre.after(250)\n", " fenetre.update()\n", "\n", "def parcours_Traite() : # imprime en couleur les cases de la liste fermée finale avant traitement\n", " canevas.delete(ALL)\n", " dessiner()\n", " Liste=Traitement_Final(Algorithme(Dep,Arr))\n", " if Liste[-1]==Arr : # si le problème a une solution...\n", " for M in Liste[1:len(Liste)-1] : # pour ne pas colorier la dernière case d'arrivée\n", " canevas.create_rectangle(M[0]*c, M[1]*c, (M[0]+1)*c, (M[1]+1)*c, fill='#FF00BF')\n", " fenetre.after(30)\n", " fenetre.update()\n", " else :\n", " for j in range(7) :\n", " canevas.create_rectangle(Dep[0]*c, Dep[1]*c, (Dep[0]+1)*c, (Dep[1]+1)*c, fill='#FF8000')\n", " canevas.create_rectangle(Arr[0]*c, Arr[1]*c, (Arr[0]+1)*c, (Arr[1]+1)*c, fill='#0101DF')\n", " fenetre.after(250)\n", " fenetre.update()\n", " canevas.create_rectangle(Dep[0]*c, Dep[1]*c, (Dep[0]+1)*c, (Dep[1]+1)*c, fill='#0101DF')\n", " canevas.create_rectangle(Arr[0]*c, Arr[1]*c, (Arr[0]+1)*c, (Arr[1]+1)*c, fill='#FF8000')\n", " fenetre.after(250)\n", " fenetre.update()\n", "def recommencer() :\n", " global dico\n", " canevas.delete(ALL)\n", " dico = {} #dictionnaire contenant les coordonnées de chaque cellule et une valeur 0 ou 1 si elles sont respectivement mortes ou vivantes\n", "\n", " for i in range(largeur+1) :\n", " for j in range(hauteur+1) :\n", " dico[i,j]=0\n", " dessiner()\n", "########## interface graphique ##########\n", "\n", "\n", "fenetre = Tk()\n", "canevas = Canvas(fenetre, width =Taille_largeur+c, height =Taille_hauteur+c, bg ='white')\n", "\n", "canevas.bind(\"\", click_depart)\n", " \n", "canevas.pack(side =TOP, padx =5, pady =5)\n", "for i in range(largeur+2) :\n", " canevas.create_line(i*c,0,i*c,Taille_hauteur+c,width=1,fill='black')\n", " for j in range(hauteur+2) :\n", " canevas.create_line(0,j*c,Taille_largeur+c,j*c,width=1,fill='black')\n", "\n", "text_dep=Label(fenetre,text=\"Choix de la case de départ \",fg=\"green\")\n", "text_dep.pack(side=LEFT)\n", "\n", "fenetre.mainloop()\n", "fenetre.destroy()\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exemple 4 : les tables modulaires" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Analyser le script suivant et essayer d'expliquer les figures obtenues..." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from tkinter import *\n", "\n", "from math import *\n", "\n", "import numpy\n", "\n", "\n", "\n", " \n", "############### Variables initiales ###############\n", "\n", "table=2\n", "modulo=360\n", "\n", "\n", "w=500\n", "h=500\n", "\n", "rayon=w/2-50\n", "\n", "\n", "centre_X=w/2\n", "centre_Y=h/2\n", "\n", "pas=0.05 # déplacement par défaut des tables dans le sens 'avance'\n", "curseur=0# indique quelle fonction est active\n", "\n", "############### Fonctions des widgets ###############\n", "\n", "\n", "def etape_suivante() :\n", " global modulo, table,centre_X,centre_Y,rayon,pas,curseur,pas\n", " \n", " table+=pas\n", " \n", " for k in range(modulo) :\n", " canevas.create_line(centre_X+rayon*cos(2*k*pi/modulo),centre_Y+rayon*sin(2*k*pi/modulo),centre_X+rayon*cos(2*((table*k)%modulo)*pi/modulo),centre_Y+rayon*sin(2*((table*k)%modulo)*pi/modulo),fill='red')\n", " \n", " fenetre.update()\n", " canevas.delete(ALL)\n", "\n", "\n", " cercle=canevas.create_oval(50,50,w-50,h-50, fill=\"#A9F5A9\")\n", " texte=canevas.create_text(250,30,text=\"La table de \"+str(round(table,1)),font=\"Arial 16\")\n", " \n", " if curseur==-2 :\n", " pas=-0.1\n", " recule_rapide()\n", " elif curseur==-1 :\n", " pas=-0.05\n", " recule()\n", " elif curseur==0 :\n", " pas=0\n", " pause()\n", " elif curseur==1 :\n", " pas=0.05\n", " avance()\n", " else :\n", " pas=0.1\n", " avance_rapide()\n", " \n", "def avance_rapide() :\n", " global curseur,pas\n", " pas+=0.05\n", " curseur=2\n", " etape_suivante()\n", " \n", "def avance() :\n", " global curseur,pas\n", " pas=0.05\n", " curseur=1\n", " etape_suivante()\n", " \n", "def pause() :\n", " global curseur,pas\n", " pas=0\n", " curseur=0\n", " etape_suivante()\n", " \n", "def recule() :\n", " global curseur,pas\n", " pas=-0.05\n", " curseur=-1\n", " etape_suivante()\n", " \n", "def recule_rapide() :\n", " global curseur,pas\n", " pas-=0.05\n", " curseur=-2\n", " etape_suivante()\n", "\n", "\n", "\n", " \n", "############### Interface Graphique ################\n", "\n", "\n", "\n", "\n", "\n", "fenetre=Tk()\n", "fenetre.geometry(\"800x600\")\n", "\n", "titre=Label(fenetre,text=\"Les tables modulaires\",fg=\"magenta\",font=\"Arial 16\")\n", "titre.pack(side=TOP)\n", "\n", "canevas = Canvas(fenetre, width =w, height =h, bg =\"#F5DA81\")\n", "canevas.pack(padx =5, pady =5)\n", "\n", "\n", "bouton_quitter = Button(fenetre, text=\"Quitter\", command=fenetre.quit)\n", "bouton_quitter.pack(side=RIGHT)\n", "\n", "\n", "b4= Button(fenetre, text ='<<--', command =recule_rapide)\n", "b4.pack(side=LEFT, padx =3, pady =3)\n", "\n", "b3= Button(fenetre, text ='<-', command =recule)\n", "b3.pack(side=LEFT, padx =3, pady =3)\n", "\n", "\n", "b2 = Button(fenetre, text ='||', command =pause)\n", "b2.pack(side=LEFT, padx =3, pady =3)\n", "\n", "b1 = Button(fenetre, text ='->', command =avance)\n", "b1.pack(side=LEFT,padx =3, pady =3)\n", "\n", "\n", "b0 = Button(fenetre, text ='-->>', command =avance_rapide)\n", "b0.pack(side=LEFT,padx =3, pady =3)\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "cercle=canevas.create_oval(50,50,w-50,h-50, fill=\"#A9F5A9\")\n", "\n", " \n", "\n", "fenetre.mainloop()\n", "fenetre.destroy()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.5" } }, "nbformat": 4, "nbformat_minor": 1 }