Trois boules : blanche, rouge et grise sur un tapis vert ... et deux joueurs.
Après avoir placé les boules sur le billard et réglé la direction, la vitesse,
l'effet et la hauteur de tir, le premier joueur pourra "continuer", "rejouer le coup" ou
"passer la main", en fonction des résultats de son score.
L'algorithme de choc passe par le calcul du recul des boules après intersection de
leurs contours, pour revenir au strict contact, puis par la détermination des nouveaux
vecteurs vitesses selon les lois de la mécanique des chocs inélastiques.
Ce script devrait intéresser les débutants qui cherchent à mettre en oeuvre les interfaces
graphiques Tkinter, avec les widgets Canvas, Button, Text, Scale et Label.
Source / Exemple :
#! /usr/bin/env python
# -*- coding: Latin-1 -*-
# <<< Billard >>>
# REMERCIEMENTS : ce projet est très largement inspiré des exemples fournis par Mr Gérard Swinnen dans la deuxième édition de son ouvrage intitulé "Apprendre à programmer avec Python".
from Tkinter import *
from math import *
# CONVENTIONS
# boule BLANCHE....(coordonnées:x1,y1):B1
# boule ROUGE......(coordonnées:x2,y2):B2
# boule GRISE......(coordonnées:x3,y3):B3
# bille bleue......(coordonnées:x4,y4):b4(Réglage de l'effet latéral et de la hauteur de tir)
# cercle blanc.....(coordonnées:x5,y5):b5(Réglage de l'orientation et de la vitesse de tir)
# pupitre de simulation : indice "S"
########## Commande "préparer le jeu" ####################################
def jouer():
"Préparer le jeu"
global flag,texte
if flag==0:flag=1
jouer;config_init() # mise en place de la configuration initiale (boules,flèches,réglages)
can.bind("<Button-1>", mouseDown) # commandes avec le clic gauche de la souris
can.bind("<Button1-Motion>", mouseMove)
can.bind("<Button1-ButtonRelease>", mouseUp)
T.pack(),J.pack_forget() # gestion des boutons de commande
can.delete(texte)
########## Commandes de réglage du tir ###################################
def mouseDown( event):
"Opérations à effectuer quand le bouton gauche de la souris est enfoncé"
global selObject,X,Y,x5,y5,v,o
can.currObject=None
X,Y=event.x,event.y # coordonnées du clic
D1=hypot(X-x1-r,Y-y1-r) # distance entre le clic et le centre de B1
D2=hypot(X-x2-r,Y-y2-r) # idem B2
D3=hypot(X-x3-r,Y-y3-r) # idem B3
D4=hypot(X-x4-R,Y-y4-R) # idem bille bleue
D5=hypot(X-x5-R,Y-y5-R) # idem cercle blanc
X5,Y5=x5-7L/8+R,y5-H-h/2+R
H5=hypot(X5,Y5)
if D1<r:selObject=b1 # sélection de B1 si le clic est à l'intérieur de B1
if D2<r:selObject=b2 # idem B2
if D3<r:selObject=b3 # idem B3
if D4<R:selObject=b4 # idem bille bleue
if D5<4*R:selObject=b5 # plage de sélection du cercle blanc: cercle de rayon 4R
if selObject==b5: # procédure de réglage fin, par clic sussessifs à proximité
dx,dy=(X-x5-R)/40,(Y-y5-R)/40 # pas = 1/40 ème de la distance du clic au centre du cercle
x5,y5=x5+dx,y5+dy
can.move(selObject,dx,dy) # déplacement du cercle blanc
C5=can.coords(b5)
x5,y5=C5[0],C5[1]
o=-atan2(y5-H-h/2+R,x5-7*L/8+R) # récupération de l'angle de tir (o)
wo=round(float(o*180/pi),1)
can.orientation.config(text='%s'%wo) # valeur d'affichage de (o)
v=1+14*hypot(x5+R-7*L/8-2*R*cos(o),y5+R-H-h/2+2*R*sin(o))/(3*h/8-3*R) # récupération de la vitessede tir (v)
wv=round(float(v),1)
can.vitesse.config(text='%s'%wv) # valeur d'affichage de (v)
if B1==1:fl(flèche1,o,x1,y1)
if B3==1:fl(flèche3,o,x3,y3)
can.coords(curseur5,7*L/8+R*cos(o),H+h/2-R*sin(o),x5+R-R*cos(o),y5+R+R*sin(o))
can.itemconfig(selObject,width=2) # le contour de l'objet sélectionné double d'épaisseur
def mouseMove(event):
"Opérations à effectuer quand la souris se déplace, bouton gauche enfoncé"
global O,X,Y
global x1,y1,x2,y2,x3,y3,x4,y4,x5,y5
global e,ha,v,o,o4
XX,YY=event.x,event.y
dx,dy=XX-X,YY-Y
if selObject==b1:
if dx+x1>0 and dx+x1<L-2*r and dy+y1>0 and dy+y1<H-2*r:
can.move(selObject,dx,dy);X,Y=XX,YY # déplacement de B1
C1=can.coords(b1)
x1,y1=C1[0],C1[1] # récupération des coordonnées du coin supérieur gauche
if B1==1:fl(flèche1,pi,x1,y1) # déplacement de la flèche de B1
if selObject==b2 :
if dx+x2>0 and dx+x2<L-2*r and dy+y2>0 and dy+y2<H-2*r:
can.move(selObject,dx,dy);X,Y=XX,YY # déplacement de B2
C2=can.coords(b2)
x2,y2=C2[0],C2[1]
if selObject==b3 :
if dx+x3>0 and dx+x3<L-2*r and dy+y3>0 and dy+y3<H-2*r:
can.move(selObject,dx,dy);X,Y=XX,YY # déplacement de B3
C3=can.coords(b3)
x3,y3=C3[0],C3[1]
if B3==1:fl(flèche3,pi,x3,y3) # déplacement de la flèche de B3
if selObject==b4:
DX4,DY4=dx+x4-L/8+R,dy+y4-H-h/2+R
HD4=hypot(DX4,DY4)
if HD4<R4-R:
can.move(selObject,dx,dy);X,Y=XX,YY # déplacement de la bille bleue
C4=can.coords(b4)
x4,y4=C4[0],C4[1]
o4=atan2(DY4,DX4)
e=(x4-L/8+R)*2/(R4-R)*pi/180 # calcul de l'effet (-2<e<+2)
we=round(float(e*180/pi),1);can.effet.config(text='%s'%we)
ha=-2*(y4-H-h/2+R)/(R4-R) # calcul de la hauteur (-2<h<+2)
wha=round(float(ha),1);can.hauteur.config(text='%s'%wha)
can.coords(curseur4,L/8+R4*cos(o4),H+h/2+R4*sin(o4),L/8-R4*cos(o4),H+h/2-R4*sin(o4))
if selObject==b5:
DX5,DY5=dx+x5-7*L/8+R,dy+y5-H-h/2+R
HD5=hypot(DX5,DY5)
if HD5<3*h/8-R and HD5>2*R:
can.move(selObject,dx,dy);X,Y=XX,YY # déplacement du cercle blanc dans l'espace d'orientation et de vitesse
C5=can.coords(b5)
x5,y5=C5[0],C5[1]
o=-atan2(y5-H-h/2+R,x5-7*L/8+R) # récupération de l'angle de tir (o)
wo=round(float(o*180/pi),1)
can.orientation.config(text='%s'%wo)
v=1+14*hypot(x5+R-7*L/8-2*R*cos(o),y5+R-H-h/2+2*R*sin(o))/(3*h/8-3*R)# récupération de la vitesse de tir (v)
wv=round(float(v),1)
can.vitesse.config(text='%s'%wv)
can.coords(curseur5,7*L/8+R*cos(o),H+h/2-R*sin(o),x5+R-R*cos(o),y5+R+R*sin(o))
if B1==1:fl(flèche1,o,x1,y1)
if B3==1:fl(flèche3,o,x3,y3)
def mouseUp(event):
"Opérations à effectuer quand le bouton gauche de la souris est relâché"
global selObject
can.itemconfig(selObject,width=1) # le contour de l'objet sélectionné revient à son épaisseur initiale
if selObject==b1:selObject=None
if selObject==b2:selObject=None
if selObject==b3:selObject=None
if selObject==b4:selObject=None
if selObject==b5:selObject=None
########## Commande "tirer" ##############################################
def tirer():
"tirer "
global T1,T3,t1,t3,S1,S3,E1,E3,R1,R3,PM1,PM3
global x1,y1,x3,y3,dx1,dy1,dx3,dy3
global xx1,yy1,xx2,yy2,xx3,yy3
global dxx1,dyy1,dxx2,dyy2,dxx3,dyy3
global xx4,yy4,xx5,yy5
global ee,hh,oo,vv,oo4
if B1==1:
T1=1;t1+=1 # indicateur et compteur de tir
dx1,dy1=v*cos(o),-v*sin(o) # coordonnées du vecteur vitesse
can.coords(flèche1,x1+r,y1+r,x1+r,y1+r) # origine de la flèche
can.itemconfig(b4,fill='white',outline='white') # effacement de la bille bleue
can.itemconfig(curseur4,fill='white') # effacement du diamètre de la bille bleue
if B3==1:
T3=1;t3+=1
dx3,dy3=v*cos(o),-v*sin(o)
can.coords(flèche3,x3+r,y3+r,x3+r,y3+r) # idem
can.itemconfig(b4,fill='grey',outline='grey') # idem
can.itemconfig(curseur4,fill='grey')
xx1,yy1,xx2,yy2,xx3,yy3=x1,y1,x2,y2,x3,y3 # mémorisation de la position des trois boules
dxx1,dyy1,dxx2,dyy2,dxx3,dyy3=dx1,dy1,dx2,dy2,dx3,dy3 # mémorisation de la vitesse des trois boules
xx4,yy4,xx5,yy5=x4,y4,x5,y5 # mémorisation de la position de la bille et du cercle
ee,hh,oo,vv,oo4=e,ha,o,v,o4 # mémorisation des réglages du tir
S1,S3,E1,E3,R1,R3,PM1,PM3,CT1,CT3=0,0,0,0,0,0,0,0,0,0 # mise à zéro des indicateurs
T.pack_forget();FI.pack() # gestion des boutons de commande
can.itemconfig(texte1,fill="brown")
can.delete(texte2);can.delete(texte3)
can.itemconfig(b5,outline='dark green') # effacement du cercle blanc
can.itemconfig(curseur5,fill='dark green') # effacement de la flèche
move() # appel de la fonction "mouvement" des boules
###### Commandes "continuer/rejouer/passer la main/figer/relancer" #######
def continuer():
"continuer"
global flag,CT1,CT3,B1,B3
global x1,y1,x2,y2,x3,y3,x4,y4,x5,y5
global e,ha,o,v,o4,o6
if flag==0:flag=1
e,ha,o,v,o4=0,0,pi,8,pi/2 # réglages de départ
x4,y4,x5,y5=x4o,y4o,x5o,y5o # coordonnées de départ de la bille et du cercle
effacer_simulation()
continuer;config() # mise en place de la configuration
if S1==1:CT1=1;B1,B3=1,0;fl(flèche1,o,x1,y1)
if S3==1:CT3=1;B1,B3=0,1;fl(flèche3,o,x3,y3)
T.pack();CT.pack_forget();RJ.pack_forget()
effacer_texteR()
def rejouer():
"rejouer le coup"
global flag,R1,R3,r1,r3
global x1,y1,x2,y2,x3,y3
global e,ha,o,v,o4
if flag==0:flag=1
e,ha,o,v,o4 =ee,hh,oo,vv,oo4 # récupération des réglages avant tir
x1,y1,x2,y2,x3,y3=xx1,yy1,xx2,yy2,xx3,yy3 # récupération des positions avant tir des boules
x4,y4,x5,y5=xx4,yy4,xx5,yy5 # récupération des positions avant tir de la bille et du cercle
rejouer;config() # mise en place de la configuration
if B1==1 :R1=1;fl(flèche1,o,x1,y1);r1+=1
if B3==1 :R3=1;fl(flèche3,o,x3,y3);r3+=1
T.pack();RJ.pack_forget();PM.pack_forget();CT.pack_forget()
effacer_texteR()
def passer():
"passer la main"
global flag,PM1,PM3,B1,B3
global x1,y1,x2,y2,x3,y3,x4,y4,x5,y5
global e,ha,o,v,o4
if flag==0:flag=1
e,ha,o,v,o4=0,0,pi,8,pi/2 # réglages de départ
x4,y4,x5,y5=x4o,y4o,x5o,y5o # coordonnées de départ de la bille et du cercle
effacer_simulation()
passer;config() # mise en place de la configuration
if E1==1:PM1=1;B1,B3=0,1;fl(flèche3,o,x3,y3) # changement de boule (B3 prend la main)
if E3==1:PM3=1;B1,B3=1,0;fl(flèche1,o,x1,y1)
if B3==1:can.itemconfig(REG4,fill='grey');can.itemconfig(REG5B,fill='grey')
if B1==1:can.itemconfig(REG4,fill='white');can.itemconfig(REG5B,fill='white')
T.pack();RJ.pack_forget();PM.pack_forget();CT.pack_forget()
effacer_texteR()
def stop():
"Figer le jeu"
global flag,figer
figer=1;flag=0
dxx1,dyy1,dxx2,dyy2,dxx3,dyy3=dx1,dy1,dx2,dy2,dx3,dy3 # mémorisation de la vitesse des trois boules
RL.pack();FI.pack_forget()
def go():
"Relancer le jeu"
global flag,figer
flag=1;figer=0
dx1,dy1,dx2,dy2,dx3,dy3=dxx1,dyy1,dxx2,dyy2,dxx3,dyy3 # récupération de la vitesse avant arrêt des trois boules
FI.pack();RL.pack_forget();RJ.pack_forget();PM.pack_forget()
move() # redémarrage du mouvement d'ensemble
########## Commande "score" ##############################################
def score():
"Détermination du score"
global score1,score3,texte1S,texte1E,texte3S,texte3E,S1,S3,E1,E3
if B1==1:
if figer==1:RJ.pack_forget();PM.pack_forget()
else:
if point1==1:
S1=1;score1+=1 # Constat de succès : le score de B1 augmente d'un point
texte1S=can3.create_text(2*h/3,h/4,text='La BLANCHE continue\nou\nrejoue le coup',fill ="red",font ="Arial 8")
CT.pack();RJ.pack();FI.pack_forget()
if point1==0:
E1=1;score1+=0 # Constat d'échec
texte1E=can3.create_text(2*h/3,h/4,text='La BLANCHE rejoue le coup\nou\npasse la main',fill ="red",font ="Arial 8")
RJ.pack();PM.pack();FI.pack_forget()
if R1==1:score1+=-1
can.score1.config(text='%s'%score1)
if B3==1:
if figer==1:RJ.pack_forget();PM.pack_forget()
else:
if point3==1:
S3=1;score3+=1
texte3S=can3.create_text(2*h/3,h/4,text='La GRISE continue\nou\nrejoue le coup',fill ="red",font ="Arial 8")
CT.pack();RJ.pack();FI.pack_forget()
if point3==0:
E3=1;score3+=0
texte3E=can3.create_text(2*h/3,h/4,text='La GRISE rejoue le coup\nou\npasse la main',fill ="red",font ="Arial 8")
RJ.pack();PM.pack();FI.pack_forget()
if R3==1:score3+=-1
can.score3.config(text='%s'%score3)
########## Fonction "mouvement" ##########################################
def move():
"mouvement des trois boules au fur et à mesure des chocs"
global flag
global x1,y1,x2,y2,x3,y3
global dx1,dy1,dx2,dy2,dx3,dy3
global c12,c23,c31,point1,point3
global T1,T3,RT1,RT3,RN1,RN3,rt
global sim1,sim3
x1,y1,x2,y2,x3,y3=x1+dx1,y1+dy1,x2+dx2,y2+dy2,x3+dx3,y3+dy3 # création du mouvement
dx1,dy1,dx2,dy2,dx3,dy3=k*dx1,k*dy1,k*dx2,k*dy2,k*dx3,k*dy3 # freinage du tapis (k)
if hypot(dx1,dy1)<s:dx1,dy1=0,0 # arrêt de B1 lorsque la vitesse de B1 devient trop faible (<s)
if hypot(dx2,dy2)<s:dx2,dy2=0,0 # idem B2
if hypot(dx3,dy3)<s:dx3,dy3=0,0 # idem B3
X12,Y12,X23,Y23,X31,Y31=x2-x1,y2-y1,x3-x2,y3-y2,x1-x3,y1-y3
Z12,Z23,Z31=hypot(X12,Y12),hypot(X23,Y23),hypot(X31,Y31)
dx12,dy12,dx23,dy23,dx31,dy31=dx2-dx1,dy2-dy1,dx3-dx2,dy3-dy2,dx1-dx3,dy1-dy3
dz12,dz23,dz31=hypot(dx12,dy12),hypot(dx23,dy23),hypot(dx31,dy31)
k12,K12,k23,K23,k31,K31=dz12*dz12,X12*dx12+Y12*dy12,dz23*dz23,X23*dx23+Y23*dy23,dz31*dz31,X31*dx31+Y31*dy31
u12,u23,u31=Z12-2*r,Z23-2*r,Z31-2*r
# Choc de B1 sur B2 :
if u12<=0: # la distance entre centres est <= à la somme des rayons
# algorithme de recul des deux boules :
c12+=1 # compteur de chocs B1/B2
m12=(K12+sqrt(K12*K12-k12*(Z12*Z12-4*r*r)))/k12
x1,y1,x2,y2=x1-m12*dx1,y1-m12*dy1,x2-m12*dx2,y2-m12*dy2 # recul des deux boules sur leurs directions initales
# algorithme de modification des vecteurs vitesse après le choc:
A12,B12,C12,D12=(X12*dx1+Y12*dy1)/Z12,(-Y12*dx1+X12*dy1)/Z12,(X12*dx2+Y12*dy2)/Z12,(-Y12*dx2+X12*dy2)/Z12
dx1,dy1,dx2,dy2=(-Y12*B12+X12*C12)/Z12,(X12*B12+Y12*C12)/Z12,(-Y12*D12+X12*A12)/Z12,(X12*D12+Y12*A12)/Z12
if T1==1 or T3==1 and RT1==0 and RN1 ==0:
effet_choc(dx1,dy1,dx2,dy2)
dx1,dy1,dx2,dy2=dxAE,dyAE,dxBE,dyBE # vitesse corrigée de l'effet
if B1==1 or B3==1 and c12==1:
hauteur(dxx1,dyy1,dxx2,dyy2) # vitesse corrigée de la hauteur de tir
dx1,dy1,dx2,dy2=dx1+dxACR,dy1+dyACR,dx2+dxBCR,dy2+dyBCR
# Choc de B2 sur B3 :
if u23<=0:
c23+=1 # compteur de chocs B2/B3
m23=(K23+sqrt(K23*K23-k23*(Z23*Z23-4*r*r)))/k23
x2,y2,x3,y3=x2-m23*dx2,y2-m23*dy2,x3-m23*dx3,y3-m23*dy3
A23,B23,C23,D23=(X23*dx2+Y23*dy2)/Z23,(-Y23*dx2+X23*dy2)/Z23,(X23*dx3+Y23*dy3)/Z23,(-Y23*dx3+X23*dy3)/Z23
effet_choc(dx2,dy2,dx3,dy3)
dx2,dy2,dx3,dy3=dxAE,dyAE,dxBE,dyBE
dx2,dy2,dx3,dy3=(-Y23*B23+X23*C23)/Z23,(X23*B23+Y23*C23)/Z23,(-Y23*D23+X23*A23)/Z23,(X23*D23+Y23*A23)/Z23
if B3==1 and c23==1:
hauteur(dxx3,dyy3,dxx2,dyy2)
dx3,dy3,dx2,dy2=dx3+dxACR,dy3+dyACR,dx2+dxBCR,dy2+dyBCR
# Choc de B3 sur B1 :
if u31<=0:
c31+=1 # compteur de chocs B3/B1
m31=(K31+sqrt(K31*K31-k31*(Z31*Z31-4*r*r)))/k31
x3,y3,x1,y1=x3-m31*dx3,y3-m31*dy3,x1-m31*dx1,y1-m31*dy1
A31,B31,C31,D31=(X31*dx3+Y31*dy3)/Z31,(-Y31*dx3+X31*dy3)/Z31,(X31*dx1+Y31*dy1)/Z31,(-Y31*dx1+X31*dy1)/Z31
dx3,dy3,dx1,dy1=(-Y31*B31+X31*C31)/Z31,(X31*B31+Y31*C31)/Z31,(-Y31*D31+X31*A31)/Z31,(X31*D31+Y31*A31)/Z31
if T3==1 or T1==1 and RT3==0 and RN3==0:
effet_choc(dx3,dy3,dx1,dy1)
dx3,dy3,dx1,dy1=dxAE,dyAE,dxBE,dyBE
if B3==1 or B3==1 and c31==1:
hauteur(dxx3,dyy3,dxx1,dyy1)
dx3,dy3,dx1,dy1=dx3+dxACR,dy3+dyACR,dx1+dxBCR,dy1+dyBCR
if c12*c31>0:point1=1 # succès=1 point
else:point1=0 # échec
if c23*c31>0:point3=1
else:point3=0
if x1>L-2*r or x1<0 or y1>H-2*r or y1<0: # Rebonds de la BLANCHE sur les bandes:
if T1==1 and c12==0 and c31==0:
rebond_tir(x1,y1,dx1,dy1)
x1,y1,dx1,dy1,RT1=xR,yR,dxR,dyR,RT
else :
rebond_normal(x1,y1,dx1,dy1)
x1,y1,dx1,dy1,RN1=xR,yR,dxR,dyR,RN
T1=0
if x2>L-2*r or x2<0 or y2>H-2*r or y2<0: # Rebonds de la ROUGE sur les bandes:
rebond_normal(x2,y2,dx2,dy2)
x2,y2,dx2,dy2=xR,yR,dxR,dyR
if x3>L-2*r or x3<0 or y3>H-2*r or y3<0: # Rebonds de la GRISE sur les bandes:
if T3==1 and c31==0 and c23==0:
rebond_tir(x3,y3,dx3,dy3)
x3,y3,dx3,dy3,RT3=xR,yR,dxR,dyR,RT
T3=0
else:
rebond_normal(x3,y3,dx3,dy3)
x3,y3,dx3,dy3,RN3=xR,yR,dxR,dyR,RN
Cboule(b1,x1,y1,r);Cboule(b2,x2,y2,r);Cboule(b3,x3,y3,r) # Nouvelles coordonnées de B1,B2,B3
CbouleS(b1S,x1,y1,RS);CbouleS(b2S,x2,y2,RS);CbouleS(b3S,x3,y3,RS)
if B1==1:sm(x1,y1,dx1,dy1);sim1=sim;can.itemconfig(sim1,fill='white')
if B3==1:sm(x3,y3,dx3,dy3);sim3=sim;can.itemconfig(sim3,fill='grey')
# Algorithmes à l'arrêt des trois boules
if flag==0:
c12+=-c12 # mise à zéro des compteurs de choc
c23+=-c23
c31+=-c31
score() # appel de la commande "score"
rt=0
if flag>0:
root.after(10,move) # déclenchement du mouvement des boules et des flèches
if dx1==0 and dy1==0 and dx2==0 and dy2==0 and dx3==0 and dy3==0:flag=0 # arrêt complet
########## Procédures diverses ###########################################
def config_init():
"Mise en place de la configuration de départ (à partir des 'données initiales)"
global B1,B3,b1,b2,b3,b4,b5,b1S,b2S,b3S
B1,B3=1,0 # Sélection de la boule à tirer (B1)
boule(x1,y1,r,color='white');b1=b # Mise en place de B1
boule(x2,y2,r,color='red');b2=b # idem B2
boule(x3,y3,r,color='grey');b3=b # idem B3
bouleS(x1,y1,RS,color='white');b1S=bS # idem B1/Pupitre
bouleS(x2,y2,RS,color='red');b2S=bS # idem B2/Pupitre
bouleS(x3,y3,RS,color='grey');b3S=bS # idem B3/Pupitre
boule(x4,y4,R,color='blue');b4=b # Mise en place de la bille bleue commandant "effet" et "hauteur"
boule(x5,y5,R,color='dark green');b5=b # Mise en place du cercle des commandant "orientation" et "vitesse"
can.itemconfig(b5,outline='white') # couleur blanche pour le contour
can.itemconfig(curseur4,fill='blue') # couleur blanche pour le contour
can.itemconfig(curseur5,fill='white') # couleur blanche pour le contour
if B1==1:fl(flèche1,o,x1,y1) # Mise en place de la flèche liée à B1
if B3==1:fl(flèche3,o,x3,y3) # Mise en place de la flèche liée à B3
def config():
"Mise en place de la configuration courante"
Cboule(b1,x1,y1,r);Cboule(b2,x2,y2,r);Cboule(b3,x3,y3,r) # nouvelles coordonnées de B1,B2,B3
CbouleS(b1S,x1,y1,RS);CbouleS(b2S,x2,y2,RS);CbouleS(b3S,x3,y3,RS)# idem B1,B2,B3 /Pupitre
Cboule(b4,x4,y4,R);Cboule(b5,x5,y5,R) # idem bille bleue et cercle blanc
can.itemconfig(b4,fill='blue',outline='blue')
can.itemconfig(b5,outline='white')
can.itemconfig(curseur4,fill='blue')
can.itemconfig(curseur5,fill='white')
if continuer or passer:
wo=round(float(o*180/pi),1);can.orientation.config(text='%s'%wo)
wv=round(float(v),1);can.vitesse.config(text='%s'%wv)
we=round(float(e*180/pi),1);can.effet.config(text='%s'%we)
wha=round(float(ha),1);can.hauteur.config(text='%s'%wha)
can.coords(curseur4,L/8,H+h/2+R4,L/8,H+h/2-R4)
can.coords(curseur5,7*L/8-R,H+h/2,7*L/8-3*h/16+R/2,H+h/2)
if rejouer:
can.coords(curseur4,L/8+R4*cos(o4),H+h/2+R4*sin(o4),L/8-R4*cos(o4),H+h/2-R4*sin(o4))
can.coords(curseur5,7*L/8+R*cos(o),H+h/2-R*sin(o),x5+R-R*cos(o),y5+R+R*sin(o))
can.itemconfig(texte1,fill="white")
def effet_choc(dxA,dyA,dxB,dyB):
"Correction au choc pour tenir compte de l'effet latéral"
global dxAE,dyAE,dxBE,dyBE,e
e=(x4-L/8+R)*2/(R4-R)*pi/180
dxAE=dxA*cos(e)+dyA*sin(e)
dyAE=-dxA*sin(e)+dyA*cos(e)
dxBE=dxB*cos(e)+dyB*sin(e)
dyBE=-dxB*sin(e)+dyB*cos(e)
we=round(float(e*180/pi),1)
can.effet.config(text='%s'%we)
def hauteur(dxA,dyA,dxB,dyB):
"Correction au choc pour tenir compte de la hauteur du coup"
global dxACR,dyACR,dxBCR,dyBCR,ha
ha=-2*(y4-H-h/2+R)/(R4-R)
dxACR=dxA*ha/10
dyACR=dyA*ha/10
dxBCR=dxB*abs(ha/10)
dyBCR=dyB*abs(ha/10)
wha=round(float(ha),1)
can.hauteur.config(text='%s'%wha)
def rebond_tir(x,y,dx,dy):
"Rebond sur les bandes pour le premier tir"
global xR,yR,dxR,dyR,RT,rt
RT=1;rt+=1
if rt==1:m=(x4-L/8+R)*2/(R4-R)*(1+(v-1)/20)
else:m=0
if x>L-2*r: # bande droite
dxs,dys=dx+abs(m),dy+m
xR,yR,dxR,dyR=L-2*r,y,-dxs*hypot(dx,dy)/hypot(dxs,dys)*n,dys*hypot(dx,dy)/hypot(dxs,dys)*n
if y>H-2*r: # bande inférieure
dxs,dys=dx-m,dy+abs(m)
xR,yR,dxR,dyR=x,H-2*r,dxs*hypot(dx,dy)/hypot(dxs,dys)*n,-dys*hypot(dx,dy)/hypot(dxs,dys)*n
if x<0: # bande gauche
dxs,dys=dx-abs(m),dy-m
xR,yR,dxR,dyR=0,y,-dxs*hypot(dx,dy)/hypot(dxs,dys)*n,dys*hypot(dx,dy)/hypot(dxs,dys)*n
if y<0: # bande supérieure
dxs,dys=dx+m,dy+abs(m)
xR,yR,dxR,dyR=x,0,dxs*hypot(dx,dy)/hypot(dxs,dys)*n,-dys*hypot(dx,dy)/hypot(dxs,dys)*n
def rebond_normal(x,y,dx,dy):
"Rebond sur les bandes pour les coups suivants"
global xR,yR,dxR,dyR,RN
RN=1
if x>L-2*r:xR,yR,dxR,dyR=L-2*r,y,-dx*n,dy*n # bande droite
if y>H-2*r:xR,yR,dxR,dyR=x,H-2*r,dx*n,-dy*n # bande inférieur
if x<0:xR,yR,dxR,dyR=0,y,-dx*n,dy*n # bande gauche
if y<0:xR,yR,dxR,dyR=x,0,dx*n,-dy*n # bande supérieure
def fl(flèche,a,x,y):
"calcul de la longueur et mise en place de la flèche de tir"
global xf,yf,lf
SG=pi-atan(float(y+r)/float(x+r)) # le coin supérieur gauche du billard est vu par la boule sous l'angle SG
SD=atan(float(y+r)/float(L-x-r)) # le coin supérieur droit du billard est vu par la boule sous l'angle SD
ID=atan(-float(H-y-r)/float(L-x-r)) # le coin inférieur droit du billard est vu par la boule sous l'angle ID
IG=-pi-atan(-float(H-y-r)/float(x+r)) # le coin inférieur gauche du billard est vu par la boule sous l'angle IG
if a==0:lf=L-x-r # limitation de lf par la bande droite (cas limite)
if a==pi or a==-pi:lf=x+r # limitation de lf par la bande gauche (cas limite)
if a<SG and a>SD:lf=(y+r)/sin(a) # limitation de lf par la bande supérieure
if a<=SD and a>ID:lf=(L-x-r)/cos(a) # limitation de lf par la bande droite
if a<=ID and a>IG:lf=-(H-y-r)/sin(a) # limitation de lf par la bande inférieure
if a>=SG and a<pi:lf=-(x+r)/cos(a) # limitation de lf par la bande gauche
if a<IG and a>-pi:lf=-(x+r)/cos(a)
xf,yf=x+r+lf*cos(a),y+r-lf*sin(a) # extrémité de la flèche sur la bande
can.coords(flèche,x+r,y+r,xf,yf) # la flèche accompagne la boule pendant le déplacement
if B1==1:can.itemconfig(flèche,fill='white')
if B3==1:can.itemconfig(flèche,fill='grey')
def sm(x,y,dx,dy):
"Simulation du parcours de la boule tirée"
global sim
sim=can.create_line(3*h*x/2/L+L/2-3*h/4+RS,3*h*y/4/H+H+h/8+RS,3*h*(x+dx)/2/L+L/2-3*h/4+RS,3*h*(y+dy)/4/H+H+h/8+RS)
def effacer_simulation():
"effacer la trajectoire de la boule"
global b1S,b2S,b3S
REG66=can.create_rectangle(L/2-3*h/4,H+h/8,L/2+3*h/4,H+7*h/8,width=2,fill='dark green')
bouleS(x1,y1,RS,color='white');b1S=bS # remise en place de B1/Pupitre
bouleS(x2,y2,RS,color='red');b2S=bS # idem B2/Pupitre
bouleS(x3,y3,RS,color='grey');b3S=bS # idem B3/Pupitre
def effacer_texteR():
"Effacement des textes liés au résultat du tir"
can3.delete(texte1S);can3.delete(texte1E);can3.delete(texte3S);can3.delete(texte3E)
def boule(x,y,r,color):
"Coordonnées de la boule"
global b
b=can.create_oval(x,y,x+2*r,y+2*r,width=1,fill=color)
def Cboule(b,x,y,r):
"Coordonnées de la boule"
can.coords(b,x,y,x+2*r,y+2*r)
def bouleS(x,y,r,color):
"Coordonnées de la boule"
global bS
X,Y=3*h*x/2/L+L/2-3*h/4,3*h*y/4/H+H+h/8 # changement de système de coordonnées (billard vers simulation)
bS=can.create_oval(X,Y,X+2*RS,Y+2*RS,width=1,fill=color)
def CbouleS(bS,x,y,r):
"Coordonnées de la boule"
X,Y=3*h*x/2/L+L/2-3*h/4,3*h*y/4/H+H+h/8 # changement de système de coordonnées (billard vers simulation)
can.coords(bS,X,Y,X+2*RS,Y+2*RS)
def presentation():
"Fenêtre-message contenant la description sommaire du principe du jeu"
msg =Toplevel()
Message(msg, bg ="dark green", fg ="white", width =450,font ="Arial 8",
text =" PRESENTATION\n\n"\
"Au lancement du jeu, trois boules (BLANCHE, ROUGE et GRISE), apparaissent.La BLANCHE commence.\n\n"\
"La première étape, si l'on souhaite un jeu différent, consiste à déplacer les boules (abscisses et ordonnées), vers de nouvelles positions.\n\n"\
"La seconde vise à choisir la direction, la vitesse,l'effet latéral et la hauteur de tir de la boule BLANCHE.\n"\
"Une flèche de tir apparait, dont la longueur est ajustée aux dimensions du billard.\n\n"\
"Les boules étant en place, la touche <Tir !> met en mouvement la boule BLANCHE dans la direction choisie.\n"\
"Aux premiers chocs, les autres boules se mettent à leur tour en mouvement.\n"\
"La vitesse des boules se réduit à chaque itération, pour simuler le freinage du tapis, ainsi qu'à chaque choc (y/c avec les bandes).\n\n"\
"A l' arrêt des boules, le score de la boule BLANCHE est comptabilisé.\n"\
"En cas de succès, la BLANCHE gagne un point.Elle peut alors <Continuer !> ou <Rejouer le coup !> (pour améliorer les positions finales).\n"\
"En cas d' échec, elle peut aussi rejouer le coup,et, sinon, <Passer la main !> à la GRISE.\n\n"\
"La touche <Figer !> permet d'interrompre le mouvement à tout moment, et la touche <Relancer !> de le redémarrer.\n\n\n"\
"ALGORITHME\n\n"\
"Lorsque la boule BLANCHE approche, par exemple, de la boule GRISE, l'algorithme compare la distance entre leurs centres à la somme de leurs rayons.\n"\
"Quand l'écart devient négatif, il y a intersection des deux contours.\n\n"\
"L'algorithme calcule alors le recul en position des deux boules sur leurs directions initiales et en proportion de leurs vitesses, pour revenir au strict contact.\n\n"\
"La sortie du choc, en direction et en vitesse, se fait ensuite selon les lois de la mécanique des chocs inélastiques entre solides.\n"\
"Elle prend en compte (de façon très simplifiée) l'effet latéral et la hauteur de tir imprimés au moment du tir.\n\n"\
"Un coefficient (n) de réduction de la vitesse intervient à chaque choc, y/c avec les bandes.\n"
"Un ralentissement (k) a lieu également à chaque itération.\n").pack(padx =10, pady =10)
def processus():
""
msg =Toplevel()
Message(msg, bg ="dark green", fg ="white", width =450,font ="Arial 8",
text="PROCESSUS à suivre\n\n"\
"1. Cliquer sur <Préparer le jeu !>\n\n"\
"2. Régler vitesse et orientation\n"\
"en déplaçant le cercle blanc\n"\
"(réglage fin par clics successifs)\n\n"\
"3. Régler effet latéral et hauteur\n"\
"en déplaçant la bille bleue\n\n"\
"4. Appuyer sur la touche <Tirer !>\n\n"\
"5. En cas de succès, <Continuer !>\n"\
"ou <Rejouer !> : pour améliorer\n\n"\
"6. Si échec, <Passer la main !>\n"\
"ou <Rejouer !>: pour réessayer\n\n"\
"7. Possibilité de <Figer !> le mouvement\n"\
"puis de <Relancer !>\n\n\n"\
"Echelle du jeu:\n"\
"Elle est peut être modifiée en\n"\
"intervenant dans le menu <Règle du jeu>.\n"\
"Valeurs possibles: de 0.5 à 1.0\n"\
"(valeur actuelle=0.5)\n").pack(padx =10, pady =10)
def aPropos():
"Fenêtre-message indiquant l'auteur"
msg =Toplevel()
Message(msg, width =200, aspect =100, justify =CENTER,
text ="Billard français \n\nHCD, Octobre 2005.\n"\
"Python version 2.4.2\nTk version 8.4").pack(padx =10, pady =10)
def scale(E):
"Changement d'échelle"
global L,H,h,r,rr,R,R4,R6,RS
global x1,y1,x2,y2,x3,y3
global x4o,y4o,x5o,y5o,x4,y4,x5,y5
E=float(E)
L,H,h,r,rr,R,R4,R6,RS=E*L,E*H,E*h,E*r,E*rr,E*R,E*R4,E*R6,E*RS
x1,y1,x2,y2,x3,y3=E*x1,E*y1,E*x2,E*y2,E*x3,E*y3
can.config(height=H+h,width=L)
can2.config(width=h)
can3.config(height=h/3,width=h)
x4o,y4o,x5o,y5o=E*x4o,E*y4o,E*x5o,E*y5o
x4,y4,x5,y5=E*x4,E*y4,E*x5,E*y5
can.coords(Pupitre,E*4,H,L,H+h)
can.coords(texte,E*L/2,E*L/3)
can.coords(t1,L/8,H+h-E*10)
can.coords(t2,L/2,H+h-E*10)
can.coords(t3,7*L/8,H+h-E*10)
can.coords(texte1,L/2,H+E*7.5)
can.coords(texte2,L/8,H+E*7.5)
can.coords(texte3,7*L/8,H+E*7.5)
can.coords(REG4,L/8-R4,H+h/2-R4,L/8+R4,H+h/2+R4)
can.coords(curseur4,L/8,H+h/2+R4,L/8,H+h/2-R4)
can.coords(REG6,L/2-3*h/4,H+h/8,L/2+3*h/4,H+7*h/8)
can.coords(REG5,7*L/8-R6,H+h/2-R6,7*L/8+R6,H+h/2+R6)
can.coords(REG5B,7*L/8-R,H+h/2-R,7*L/8+R,H+h/2+R)
can.coords(curseur5,7*L/8-R,H+h/2,7*L/8-3*h/16+R/2,H+h/2)
can.coords(pA,L/4-rr,H/2-rr,L/4+rr,H/2+rr)
can.coords(pB,L/2-rr,H/2-rr,L/2+rr,H/2+rr)
can.coords(pC,3*L/4-rr,H/2-rr,3*L/4+rr,H/2+rr)
can.coords(pD,3*L/4-rr,H/3-rr,3*L/4+rr,H/3+rr)
can.coords(pE,3*L/4-rr,2*H/3-rr,3*L/4+rr,2*H/3+rr)
can3.coords(texte1S,2*h/3,h/4)
can3.coords(texte1E,2*h/3,h/4)
can3.coords(texte3S,2*h/3,h/4)
can3.coords(texte3E,2*h/3,h/4)
EE=1.0
def echelle():
"Choix de l'échelle"
éch=Toplevel()
curE=Scale(éch,length=200,label="Echelle du jeu",orient=HORIZONTAL,from_=0.95,to=1.05,resolution=0.05,command=scale)
curE.set(1.0) # position initiale du curseur
curE.pack()
######## Programme principal ############################################
# Création du widget principal :
root = Tk()
root.title('>>>>>>> BILLARD <<<<<<<')
# données initiales:
flag=0 # compteur
# Billard
L=600 # longueur du billard
H=L/2 # largeur du billard
r,rr=10,1 # rayons des boules et des marques du tapis
k,s=0.996,0.5 # coefficient de freinage du tapis, vitesse d'arrêt du mouvement
n=0.8 # coefficient de réduction de la vitesse lors d'un choc
x1,y1,x2,y2,x3,y3=3*L/4-r,H/2-r,L/2-r,H/2-r,L/4-r,H/2-r # coordonnées courantes des boules
dx1,dy1,dx2,dy2,dx3,dy3=0,0,0,0,0,0 # composantes courantes des vecteurs vitesse des boules
c12,c23,c31=0,0,0 # compteurs de choc
point1,point3,score1,score3=0,0,0,0 # compteurs de point et de score
R1,R3,T1,T3,PM1,PM3,CT1,CT3=0,0,0,0,0,0,0,0 # indicateurs (rejouer,tirer,passer la main,continuer)
RN1,RN3,RT1,RT3=0,0,0,0 # indicateurs de rebond sur les bandes et après choc
j,j1,j3,t1,t3,r1,r3,rt,t=0,0,0,0,0,0,0,0,0 # compteurs de lancements de jeux et de tirs
texte1E,texte1S,texte3E,texte3S=0,0,0,0
S1,S3,E1,E3=0,0,0,0 # résultats (S=succès,E=échec)
e,ha,o,v,o4=0,0,pi,8,pi/2 # réglages initiaux
figer=0
tA=0
# Pupitre de réglage du tir
h=H/2 # hauteur du pupitre de réglage
R,R4,R6,RS=4,25,3*h/8,3*h*r/4/H # rayons du système de réglage
x4o,y4o,x5o,y5o=L/8-R,H+h/2-R,7*L/8-3*h/16-3*R/2,H+h/2-R
x4,y4,x5,y5=L/8-R,H+h/2-R,7*L/8-3*h/16-3*R/2,H+h/2-R
# création des widgets "dépendants" :
can=Canvas(root,bg='dark green',height=H+h,width=L,highlightbackground='brown')
can.grid(row=1,column=0,rowspan=2)
can2=Canvas(root,bg='brown',highlightbackground='brown')
can2.grid(row=1,column=1,sticky=N)
can3=Canvas(root,bg='white',height=h/3,width=h)
can3.grid(row=2,column=1,padx=0,pady=0,ipadx=10,ipady=10,sticky=N+S)
# Pupitre de commande
Pupitre=can.create_rectangle(4,H,L,H+h,width=2,fill='brown')
t1=can.create_text(L/8,H+h-10,text='Effet et hauteur de tir',fill="white")
t2=can.create_text(L/2,H+h-10,text='Simulation',fill="white")
t3=can.create_text(7*L/8,H+h-10,text='Vitesse et orientation de tir',fill="white")
texte1=can.create_text(L/2,H+7.5,text='Régler le tir, puis "Tirer !"',fill="white")
texte2=can.create_text(L/8,H+7.5,text='Déplacer la bille bleue',fill="white")
texte3=can.create_text(7*L/8,H+7.5,text='Déplacer le cercle blanc',fill="white")
REG4=can.create_oval(L/8-R4,H+h/2-R4,L/8+R4,H+h/2+R4,width=1,fill='white')
curseur4=can.create_line(L/8,H+h/2+R4,L/8,H+h/2-R4,width=1,fill='white')
REG6=can.create_rectangle(L/2-3*h/4,H+h/8,L/2+3*h/4,H+7*h/8,width=2,fill='dark green')
REG5=can.create_oval(7*L/8-R6,H+h/2-R6,7*L/8+R6,H+h/2+R6,width=1,fill='dark green')
REG5B=can.create_oval(7*L/8-R,H+h/2-R,7*L/8+R,H+h/2+R,width=1,fill='white')
curseur5=can.create_line(7*L/8-R,H+h/2,7*L/8-3*h/16+R/2,H+h/2,width=1,fill='dark green',arrow=LAST)
# création des cinq points gris sur le tapis et des deux flèches
pA=can.create_oval(L/4-rr,H/2-rr,L/4+rr,H/2+rr,width=0,fill='light grey')
pB=can.create_oval(L/2-rr,H/2-rr,L/2+rr,H/2+rr,width=0, fill='light grey')
pC=can.create_oval(3*L/4-rr,H/2-rr,3*L/4+rr,H/2+rr,width=0,fill='light grey')
pD=can.create_oval(3*L/4-rr,H/3-rr,3*L/4+rr,H/3+rr,width=0,fill='light grey')
pE=can.create_oval(3*L/4-rr,2*H/3-rr,3*L/4+rr,2*H/3+rr,width=0,fill='light grey')
flèche1=can.create_line(0,0,0,0,arrow=LAST)
flèche3=can.create_line(0,0,0,0,arrow=LAST)
# Message d'introduction
texte=can.create_text(L/2,L/3,text='Cliquer sur le bouton "Règle du jeu"\n\nou\n\ndirectement sur "Préparer le jeu"',fill="white")
# Menu <Règle du jeu>
RdJ = Menubutton(can2, text ='Règle du jeu',height=2,width=25,relief=GROOVE,bg="dark green",fg="white")
RdJ.pack(padx=5,pady=5,side=BOTTOM,anchor=SW)
me1 = Menu(RdJ)
me1.add_command(label='Présentation',underline=0,command=presentation)
me1.add_command(label='Processus',underline=0,command=processus)
me1.add_command(label='Echelle',underline=0,command=echelle)
me1.add_command(label='A propos ...',underline=0,command=aPropos)
RdJ.configure(menu=me1)
# Affichage des réglages et des scores:
# hauteur:
can.hauteur=Label(can2,text='0',fg='white',bg='brown')
can.hauteur.pack(side=BOTTOM)
Label(can2,text="hauteur",fg='white',bg='brown').pack(side=BOTTOM)
# effet:
can.effet=Label(can2,text='0',fg='white',bg='brown')
can.effet.pack(side=BOTTOM)
Label(can2,text="effet",fg='white',bg='brown').pack(side=BOTTOM)
# vitesse:
can.vitesse=Label(can2,text='0',fg='white',bg='brown')
can.vitesse.pack(side=BOTTOM)
Label(can2,text="vitesse",fg='white',bg='brown').pack(side=BOTTOM)
# orientation:
can.orientation=Label(can2,text='0',fg='white',bg='brown')
can.orientation.pack(side=BOTTOM)
Label(can2,text="orientation",fg='white',bg='brown').pack(side=BOTTOM)
# Score B3:
can.score3=Label(can2,text='0',fg='white',bg='brown')
can.score3.pack(side=BOTTOM)
Label(can2,text="score GRISE",fg='white',bg='brown').pack(side=BOTTOM)
# Score B1:
can.score1=Label(can2,text='0',fg='white',bg='brown')
can.score1.pack(side=BOTTOM)
Label(can2,text="score BLANCHE",fg='white',bg='brown').pack(side=BOTTOM)
# Boutons de commande
# 'Continuer ?':
CT=Button(can2,text='Continuer? ',height=2,width=25,relief=GROOVE,bg="white",activebackground="dark green",activeforeground="white",command=continuer)
CT.pack(padx=5,pady=5,side=BOTTOM,anchor=SW)
CT.pack_forget()
# 'Passer la main ?':
PM=Button(can2,text='Passer la main? ',height=2,width=25,relief=GROOVE,bg="white",activebackground="dark green",activeforeground="white",command=passer)
PM.pack(padx=5,pady=5,side=BOTTOM,anchor=SW)
PM.pack_forget()
# 'Rejouer le coup ?':
RJ=Button(can2,text='Rejouer le coup ?',height=2,width=25,relief=GROOVE,bg="white",activebackground="dark green",activeforeground="white",command=rejouer)
RJ.pack(padx=5,pady=5,side=BOTTOM,anchor=SW)
RJ.pack_forget()
# bouton d'arrêt du mouvement:
FI=Button(can2,text='Figer le mouvement',height=2,width=25,relief=GROOVE,bg="white",activebackground="dark green",activeforeground="white",command=stop)
FI.pack(padx=5,pady=5,side=BOTTOM,anchor=SW)
FI.pack_forget()
# bouton de redémarrage du mouvement:
RL=Button(can2,text='Relancer le mouvement',height=2,width=25,relief=GROOVE,bg="white",activebackground="dark green",activeforeground="white",command=go)
RL.pack(padx=5,pady=5,side=BOTTOM,anchor=SW)
RL.pack_forget()
# 'Tirer !':
T=Button(can2,text='Tirer !',height=2,width=25,relief=GROOVE,bg="white",activebackground="dark green",activeforeground="white",command=tirer)
T.pack(padx=5,pady=5,side=BOTTOM,anchor=SW)
T.pack_forget()
# 'Préparer le tir !':
J=Button(can2,text='Préparer le jeu !',height=2,width=25,relief=GROOVE,bg="white",activebackground="dark green",activeforeground="white",command=jouer)
J.pack(padx=5,pady=5,side=BOTTOM,anchor=SW)
root.mainloop()
Conclusion :
Merci à tous ceux qui ont bien voulu prendre connaissance des premières versions.
Une nouvelle visite du script devrait les intéresser, car de nombreuses fonctionnalités
ont été ajoutées.
Commencer par appuyer sur le bouton "Règle du jeu" qui donne une présentation d'ensemble,
ainsi que le processus à suivre pour jouer.
PS : commentaires et cotations seront les bienvenus !
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.