%%% % Billard %%% \def\filedateBillard{2026/10/25}% \def\fileversionBillard{0.3a}% \message{-- \filedateBillard\space v\fileversionBillard}% % \newtoks\tokPfCMotBillard{}% \def\UpdateTokBillard#1\nil{\addtotok\tokPfCMotBillard{"#1"}}% % Formes (v0.3a) : clé Forme = Rectangle (défaut), L, T, U, Escalier, TriangleRectangle. % Forme : forme du contour. Longueur x Largeur est toujours la boîte englobante. % Orientation : T, U, Escalier, TriangleRectangle : nombre de quarts de tour antihoraires (0 à 3). % Encoche : L seulement : coin retiré, parmi BG, BD, HD, HG (bas/haut, gauche/droite). % EncocheLong : L : largeur de l'encoche ; T : largeur du pied ; U : largeur du creux. % EncocheLarg : L : hauteur de l'encoche ; T : hauteur de la barre ; U : profondeur du creux. % (proportions de Longueur et de Largeur, entre 0.2 et 0.8) % Marches : Escalier : nombre de marches, de 2 à 4 (2 donne un L). % TriangleRectangle : l'angle droit est en bas à gauche pour Orientation=0. % Avec une forme autre que le rectangle, Depart varie de 0 au nombre de sommets, dans % le sens antihoraire : L 6 ; T et U 8 ; Escalier 2*Marches+2 ; TriangleRectangle 3. \setKVdefault[ClesBillard]{Longueur=8cm,Largeur=5cm,Solution=false,Angle=70,Depart=0.5,Vrai=false,Nom=JeuA,Creation=false,Forme=Rectangle,Orientation=0,Marches=3,Ajuster=false,Encoche=HD,EncocheLong=0.5,EncocheLarg=0.5} \def\PfCBillardFormeRectangle{Rectangle}% \NewDocumentCommand\Billard{o m}{% \useKVdefault[ClesBillard]% \setKV[ClesBillard]{#1}% \tokPfCMotBillard{}% \ifboolKV[ClesBillard]{Creation}{% \expandafter\UpdateTokBillard#2\nil% }{\tokPfCMotBillard{#2}}% \edef\PfCBillardForme{\useKV[ClesBillard]{Forme}}% \ifx\PfCBillardForme\PfCBillardFormeRectangle\relax% \ifboolKV[ClesBillard]{Solution}{% \MPBillardSolution{\the\tokPfCMotBillard}{\useKV[ClesBillard]{Depart}}{\useKV[ClesBillard]{Angle}}% }{% \MPBillard{\the\tokPfCMotBillard}{\useKV[ClesBillard]{Depart}}{\useKV[ClesBillard]{Angle}}% }% \else% \ifcsname MPBillardForme\PfCBillardForme\endcsname\relax% \ifboolKV[ClesBillard]{Solution}{% \csname MPBillardForme\PfCBillardForme Solution\endcsname{\the\tokPfCMotBillard}% }{% \csname MPBillardForme\PfCBillardForme\endcsname{\the\tokPfCMotBillard}% }% \else% \PackageError{ProfCollege}{Billard : Forme inconnue (\PfCBillardForme)}{Formes possibles : Rectangle, L, T, U, Escalier, TriangleRectangle.}% \fi% \fi% }% \def\MPBillard#1#2#3{% \setbox1=\hbox{,}% \xdef\PfCNomBillard{\useKV[ClesBillard]{Nom}}% \mplibforcehmode% \begin{mplibcode}[\PfCNomBillard] _tfig:=20cm; boolean Vrai; Vrai:=\useKV[ClesBillard]{Vrai}; % On définit la liste des 26 lettres alphabétiques dans laquelle on choisit autant de lettres que la longueur du mot vardef ChoixLettre= save Lettre,choixalea; string Lettre; choixalea=floor(uniformdeviate(26)+1); if choixalea=1: Lettre="A"; elseif choixalea=2: Lettre="B"; elseif choixalea=3: Lettre="C"; elseif choixalea=4: Lettre="D"; elseif choixalea=5: Lettre="E"; elseif choixalea=6: Lettre="F"; elseif choixalea=7: Lettre="G"; elseif choixalea=8: Lettre="H"; elseif choixalea=9: Lettre="I"; elseif choixalea=10: Lettre="J"; elseif choixalea=11: Lettre="K"; elseif choixalea=12: Lettre="L"; elseif choixalea=13: Lettre="M"; elseif choixalea=14: Lettre="N"; elseif choixalea=15: Lettre="O"; elseif choixalea=16: Lettre="P"; elseif choixalea=17: Lettre="Q"; elseif choixalea=18: Lettre="R"; elseif choixalea=19: Lettre="S"; elseif choixalea=20: Lettre="T"; elseif choixalea=21: Lettre="U"; elseif choixalea=22: Lettre="V"; elseif choixalea=23: Lettre="W"; elseif choixalea=24: Lettre="X"; elseif choixalea=25: Lettre="Y"; elseif choixalea=26: Lettre="Z"; fi; Lettre enddef; % On crée un Quick Sort def QS(expr ndeb,nfin)= begingroup save v,m,x; if ndeb3: angledepart=90+angleref; Pt[2]=demidroite(Pt[1],rotation(M1,Pt1,angledepart)) intersectionpoint (subpath(0,3) of rec); dotlabel.lft(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(0,1) of #1),Pt[1]); elseif l1>2: angledepart=180+angleref; Pt[2]=demidroite(Pt[1],rotation(M3,Pt1,angledepart)) intersectionpoint ((subpath(3,4) of rec)--(subpath(0,2) of rec)); dotlabel.top(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(0,1) of #1),Pt[1]); elseif l1>1: angledepart=angleref-90; Pt[2]=demidroite(Pt[1],rotation(M2,Pt1,angledepart)) intersectionpoint ((subpath(2,4) of rec)--(subpath(0,1) of rec)); dotlabel.rt(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(0,1) of #1),Pt[1]); else: angledepart=angleref; Pt[2]=demidroite(Pt[1],rotation(M2,Pt1,angledepart)) intersectionpoint (subpath(1,4) of rec); dotlabel.bot(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(0,1) of #1),Pt[1]); fi; drawarrow Pt[1]--(Pt[1]+1.5cm*unitvector(Pt[2]-Pt[1])) withpen pencircle scaled 1.25; tourne=90; numeric Blong; BLong:=length #1; %% Détermination des points. if Vrai: for k=3 upto BLong: Intermed:=symetrie(Pt[k-2],Pt[k-1],Pt[k-1]+M2-M3); if (demidroite(1/1000[Pt[k-1],Intermed],Intermed) intersectiontimes rec)<>(-1,-1): Pt[k]:=demidroite(1/1000[Pt[k-1],Intermed],Intermed) intersectionpoint rec; else: Intermed:=symetrie(Pt[k-2],Pt[k-1],Pt[k-1]+M1-M2); Pt[k]:=demidroite(1/1000[Pt[k-1],Intermed],Intermed) intersectionpoint rec; fi; endfor; else: for k=3 upto BLong: Intermed:=rotation(Pt[k-2],Pt[k-1],tourne); if (demidroite(1/1000[Pt[k-1],Intermed],Intermed) intersectiontimes rec)<>(-1,-1): Pt[k]:=demidroite(1/1000[Pt[k-1],Intermed],Intermed) intersectionpoint rec; else: Intermed:=rotation(Pt[k-2],Pt[k-1],-tourne); Pt[k]:=demidroite(1/1000[Pt[k-1],Intermed],Intermed) intersectionpoint rec; fi; endfor; fi; %% Ajout des faux points path SPath[]; cpt[1]:=1; cpt[BLong+1]:=BLong+1; l[BLong+1]:=4; for k=2 upto BLong: SPath[k]=rec cutafter demidroite(Co,Pt[k]); l[k]=arclength SPath[k]; l[k]:=arctime l[k] of rec; cpt[k]:=k; endfor; QS(1,BLong+1); for k=2 upto BLong+1: if l[cpt[k]]-l[cpt[k-1]]>0.3: nbfaux:=nbfaux+1; FauxPt[nbfaux]=point(l[cpt[k-1]]+0.1) of rec; nbfaux:=nbfaux+1; FauxPt[nbfaux]=point(l[cpt[k]]-0.1) of rec; fi; endfor; drawoptions(); % Tracés drawarrow Pt[1]--(Pt[1]+1.5cm*unitvector(Pt[2]-Pt[1])) withpen pencircle scaled 1.25; %% Labelisation for k=2 upto BLong: if (demidroite(1/1000[Pt[k-1],Pt[k]],Pt[k]) intersectiontimes cote[1])<>(-1,-1): dotlabel.bot(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(k-1,k) of #1),Pt[k]); elseif (demidroite(1/1000[Pt[k-1],Pt[k]],Pt[k]) intersectiontimes cote[2])<>(-1,-1): dotlabel.rt(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(k-1,k) of #1),Pt[k]); elseif (demidroite(1/1000[Pt[k-1],Pt[k]],Pt[k]) intersectiontimes cote[3])<>(-1,-1): dotlabel.top(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(k-1,k) of #1),Pt[k]); elseif (demidroite(1/1000[Pt[k-1],Pt[k]],Pt[k]) intersectiontimes cote[4])<>(-1,-1): dotlabel.lft(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(k-1,k) of #1),Pt[k]); fi; endfor; for k=1 upto nbfaux: if (demidroite(Co,FauxPt[k]) intersectiontimes cote[1])<>(-1,-1): dotlabel.bot(TEX("\rule[-\dp1]{0pt}{0pt}"&ChoixLettre),FauxPt[k]); elseif (demidroite(Co,FauxPt[k]) intersectiontimes cote[2])<>(-1,-1): dotlabel.rt(TEX("\rule[-\dp1]{0pt}{0pt}"&ChoixLettre),FauxPt[k]); elseif (demidroite(Co,FauxPt[k]) intersectiontimes cote[3])<>(-1,-1): dotlabel.top(TEX("\rule[-\dp1]{0pt}{0pt}"&ChoixLettre),FauxPt[k]); elseif (demidroite(Co,FauxPt[k]) intersectiontimes cote[4])<>(-1,-1): dotlabel.lft(TEX("\rule[-\dp1]{0pt}{0pt}"&ChoixLettre),FauxPt[k]); fi; endfor; picture Reponse; Reponse=image( trace segment((0,0),(7*BLong*mm-2mm,0)) dashed dashpattern(on5mm off2mm); ); trace Reponse shifted((xpart(Co),5mm)-center Reponse); clip currentpicture to polygone((0,0),(\useKV[ClesBillard]{Longueur}+2cm,0),(\useKV[ClesBillard]{Longueur}+2cm,\useKV[ClesBillard]{Largeur}+3cm),(0,\useKV[ClesBillard]{Largeur}+3cm)); \end{mplibcode}% }% \def\MPBillardSolution#1#2#3{% \setbox1=\hbox{,}% \mplibforcehmode% \xdef\PfCNomBillard{\useKV[ClesBillard]{Nom}}% \begin{mplibcode}[\PfCNomBillard] trace rec; % Tracés drawarrow Pt[1]--(Pt[1]+1.5cm*unitvector(Pt[2]-Pt[1])) withpen pencircle scaled 1.25; for k=1 upto BLong-1: trace segment(Pt[k],Pt[k+1]); endfor; if Vrai=false: for k=2 upto BLong-1: trace codeperp(Pt[k-1],Pt[k],Pt[k+1],5); endfor; fi; % Labelisation if l1>3: dotlabel.lft(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(0,1) of #1),Pt[1]); elseif l1>2: dotlabel.top(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(0,1) of #1),Pt[1]); elseif l1>1: dotlabel.rt(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(0,1) of #1),Pt[1]); else: dotlabel.bot(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(0,1) of #1),Pt[1]); fi; path segmenttest; for k=2 upto BLong: segmenttest:=(9/10)[Pt[k-1],Pt[k]]--(11/10)[Pt[k-1],Pt[k]]; if segmenttest intersectiontimes cote[1]<>(-1,-1): dotlabel.bot(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(k-1,k) of #1),Pt[k]); elseif segmenttest intersectiontimes cote[2]<>(-1,-1): dotlabel.rt(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(k-1,k) of #1),Pt[k]); elseif segmenttest intersectiontimes cote[3]<>(-1,-1): dotlabel.top(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(k-1,k) of #1),Pt[k]); elseif segmenttest intersectiontimes cote[4]<>(-1,-1): dotlabel.lft(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(k-1,k) of #1),Pt[k]); fi; endfor; trace Reponse shifted((xpart(Co),5mm)-center Reponse); % On affiche les lettres de la réponse. for k=1 upto BLong: label.top(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(k-1,k) of #1),((xpart(Co),5mm)-center Reponse)+(k-1)*(7mm,0)+(2.5mm,0)); endfor; \end{mplibcode}% }% %%% % Contour en L : \MPBillardL (énoncé) et \MPBillardLSolution (corrigé). % % Le contour est un rectangle dont on retire un coin (clé Encoche). Il est décrit % par 6 sommets BilV[0..5], dans le sens antihoraire. Le côté i va de BilV[i] à % BilV[i+1] (indices modulo 6) et le temps du chemin BilRec est le numéro du % côté : Depart=2.5 est le milieu du côté 2. Le sommet 0 est M1 (bas gauche) ; % si l'encoche est en BG, c'est le sommet (0, hauteur de l'encoche). % % Algorithme de la trajectoire (aucune intersection de chemins METAPOST) : % 1. Départ P1 = point de temps Depart. Direction d = dir(Angle). Si P1+eps*d % n'est pas strictement dans le L, on prend -d (« ramené à l'intérieur »). % 2. Lancer de rayon : pour chaque côté (horizontal ou vertical), on calcule % le paramètre t>0 où le rayon P+t*d le coupe, on garde le plus petit t % dont le point est sur le côté. On obtient le point suivant et son côté. % 3. Rebond au point Pk (k>=3), d' = direction du segment précédent : % - rebonds à angle droit : d = rotation de d' de 90 degrés ; si Pk+eps*d % n'est pas dans le L, on prend -d ; % - option Vrai : on inverse la composante de d' perpendiculaire au côté % touché (réflexion). % Puis retour à l'étape 2. % 4. Les lettres sont placées côté extérieur (à droite du sens de parcours). % 5. Faux points : on classe les points par abscisse curviligne sur le contour, % et on répartit régulièrement des lettres dans chaque intervalle (une tous % les 10mm environ), hors voisinage des sommets. %%% \def\PfCBillardCoinBG{1}% \def\PfCBillardCoinBD{2}% \def\PfCBillardCoinHD{3}% \def\PfCBillardCoinHG{4}% \def\MPBillardL#1{% \edef\PfCBillardCoinTmp{\useKV[ClesBillard]{Encoche}}% \ifcsname PfCBillardCoin\PfCBillardCoinTmp\endcsname% \xdef\PfCBillardKc{\csname PfCBillardCoin\PfCBillardCoinTmp\endcsname}% \else% \xdef\PfCBillardKc{0}% \fi% \xdef\PfCNomBillard{\useKV[ClesBillard]{Nom}}% \setbox1=\hbox{,}% \mplibforcehmode% \begin{mplibcode}[\PfCNomBillard] _tfig:=20cm; boolean Vrai; Vrai:=\useKV[ClesBillard]{Vrai}; % Lettres des faux points. string BilAlpha; BilAlpha:="ABCDEFGHIJKLMNOPQRSTUVWXYZ"; vardef BilLettre= save kk; numeric kk; kk:=floor(uniformdeviate(26)); substring(kk,kk+1) of BilAlpha enddef; % Dimensions et coin retiré. numeric BilW,BilH,BilNw,BilNh,BilKc,BilNv,BilJ; pair BilO,BilCorner[],BilV[]; BilW:=\useKV[ClesBillard]{Longueur}; BilH:=\useKV[ClesBillard]{Largeur}; BilNw:=\useKV[ClesBillard]{EncocheLong}*BilW; BilNh:=\useKV[ClesBillard]{EncocheLarg}*BilH; if (BilNw<0.2*BilW) or (BilNw>0.8*BilW) or (BilNh<0.2*BilH) or (BilNh>0.8*BilH): errmessage "Billard L : EncocheLong et EncocheLarg doivent etre compris entre 0.2 et 0.8"; fi; % Le coin est converti en nombre côté TeX (\PfCBillardKc : 1 BG, 2 BD, 3 HD, 4 HG, 0 invalide). BilKc:=\PfCBillardKc; if BilKc=0: BilKc:=3; errmessage "Billard L : Encoche doit valoir BG, BD, HD ou HG"; fi; % Les 6 sommets, sens antihoraire : le coin retiré est remplacé par 3 sommets. BilO:=u*(1,2); BilCorner[1]:=BilO; BilCorner[2]:=BilO+(BilW,0); BilCorner[3]:=BilO+(BilW,BilH); BilCorner[4]:=BilO+(0,BilH); BilNv:=0; for i=1 upto 4: if i=BilKc: BilJ:=BilNv; if i=1: BilV[BilNv]:=BilO+(0,BilNh); BilV[BilNv+1]:=BilO+(BilNw,BilNh); BilV[BilNv+2]:=BilO+(BilNw,0); elseif i=2: BilV[BilNv]:=BilO+(BilW-BilNw,0); BilV[BilNv+1]:=BilO+(BilW-BilNw,BilNh); BilV[BilNv+2]:=BilO+(BilW,BilNh); elseif i=3: BilV[BilNv]:=BilO+(BilW,BilH-BilNh); BilV[BilNv+1]:=BilO+(BilW-BilNw,BilH-BilNh); BilV[BilNv+2]:=BilO+(BilW-BilNw,BilH); else: BilV[BilNv]:=BilO+(BilNw,BilH); BilV[BilNv+1]:=BilO+(BilNw,BilH-BilNh); BilV[BilNv+2]:=BilO+(0,BilH-BilNh); fi; BilNv:=BilNv+3; else: BilV[BilNv]:=BilCorner[i]; BilNv:=BilNv+1; fi; endfor; path BilRec; BilRec:=BilV[0]--BilV[1]--BilV[2]--BilV[3]--BilV[4]--BilV[5]--cycle; trace BilRec; % Rectangle de l'encoche (sert au test « à l'intérieur »). numeric BilXa,BilXb,BilYa,BilYb; if xpart BilV[BilJ+1]xpart BilO) and (xpart pypart BilO) and (ypart p=BilXa) and (xpart p<=BilXb) and (ypart p>=BilYa) and (ypart p<=BilYb)) enddef; % Lancer de rayon : BilT = distance au premier côté touché, BilI = son numéro. numeric BilT,BilI,BilTa,BilXx,BilYy,BilLo,BilHi,BilEd[]; pair BilA,BilB; def BilTir(expr P,d)= BilT:=4000; BilI:=-1; for ii=0 upto 5: BilA:=BilV[ii]; BilB:=BilV[(ii+1) mod 6]; if abs(ypart BilA-ypart BilB)<0.01: if abs(ypart BilA-ypart P)<4000*abs(ypart d): BilTa:=(ypart BilA-ypart P)/ypart d; if BilTa>0.5: BilXx:=xpart P+BilTa*xpart d; if xpart BilA=BilLo-0.01) and (BilXx<=BilHi+0.01) and (BilTa0.5: BilYy:=ypart P+BilTa*ypart d; if ypart BilA=BilLo-0.01) and (BilYy<=BilHi+0.01) and (BilTaxpart BilA: dotlabel.bot(TEX("\rule[-\dp1]{0pt}{0pt}"&s),P); else: dotlabel.top(TEX("\rule[-\dp1]{0pt}{0pt}"&s),P); fi; else: if ypart BilB>ypart BilA: dotlabel.rt(TEX("\rule[-\dp1]{0pt}{0pt}"&s),P); else: dotlabel.lft(TEX("\rule[-\dp1]{0pt}{0pt}"&s),P); fi; fi; enddef; % Trajectoire. numeric BLong,BilD; BLong:=length #1; pair BilPt[],BilDir,BilPd; BilD:=\useKV[ClesBillard]{Depart}; BilPt[1]:=point BilD of BilRec; BilEd[1]:=floor(BilD) mod 6; BilDir:=dir(\useKV[ClesBillard]{Angle}); if not BilInside(BilPt[1]+0.05*BilDir): BilDir:=-BilDir; if not BilInside(BilPt[1]+0.05*BilDir): errmessage "Billard L : depart ou angle impossible (Angle parallele a un cote ?)"; fi; fi; BilTir(BilPt[1],BilDir); BilPt[2]:=BilPt[1]+BilT*BilDir; BilEd[2]:=BilI; for k=3 upto BLong: BilPd:=unitvector(BilPt[k-1]-BilPt[k-2]); if Vrai: BilA:=BilV[BilEd[k-1]]; BilB:=BilV[(BilEd[k-1]+1) mod 6]; if abs(ypart BilA-ypart BilB)<0.01: BilDir:=(xpart BilPd,-ypart BilPd); else: BilDir:=(-xpart BilPd,ypart BilPd); fi; else: BilDir:=(-ypart BilPd,xpart BilPd); if not BilInside(BilPt[k-1]+0.05*BilDir): BilDir:=-BilDir; fi; fi; BilTir(BilPt[k-1],BilDir); BilPt[k]:=BilPt[k-1]+BilT*BilDir; BilEd[k]:=BilI; endfor; drawarrow BilPt[1]--(BilPt[1]+1.5cm*unitvector(BilPt[2]-BilPt[1])) withpen pencircle scaled 1.25; % Abscisses curvilignes des points sur le contour. numeric BilCum[],BilPer,BilS[],BilQ[],BilEcart,BilGa,BilGb,BilNd,BilSf,BilDv,BilE,BilX; pair BilP; BilCum[0]:=0; for ii=0 upto 5: BilCum[ii+1]:=BilCum[ii]+abs(BilV[(ii+1) mod 6]-BilV[ii]); endfor; BilPer:=BilCum[6]; for k=1 upto BLong: BilS[k]:=BilCum[BilEd[k]]+abs(BilPt[k]-BilV[BilEd[k]]); BilQ[k]:=BilS[k]; BilDv:=BilS[k]-BilCum[BilEd[k]]; if BilCum[BilEd[k]+1]-BilS[k]BilQ[j+1]: BilX:=BilQ[j]; BilQ[j]:=BilQ[j+1]; BilQ[j+1]:=BilX; fi; endfor; endfor; % Lettres des vrais points. for k=1 upto BLong: BilEtiquette(substring(k-1,k) of #1,BilPt[k],BilEd[k]); endfor; % Faux points, répartis régulièrement entre deux vrais points consécutifs. BilEcart:=10mm; for j=1 upto BLong: BilGa:=BilQ[j]; if j=BilPer: BilSf:=BilSf-BilPer; fi; BilE:=0; for ii=1 upto 5: if BilSf>=BilCum[ii]: BilE:=ii; fi; endfor; BilDv:=BilSf-BilCum[BilE]; if BilCum[BilE+1]-BilSf5mm: BilP:=BilV[BilE]+(BilSf-BilCum[BilE])*unitvector(BilV[(BilE+1) mod 6]-BilV[BilE]); BilEtiquette(BilLettre,BilP,BilE); fi; endfor; endfor; % Ligne de réponse. picture BilRep; BilRep:=image( trace segment((0,0),(7*BLong*mm-2mm,0)) dashed dashpattern(on5mm off2mm); ); pair BilShift; BilShift:=(xpart(BilO)+BilW/2,5mm)-center BilRep; trace BilRep shifted BilShift; clip currentpicture to polygone((0,0),(BilW+2cm,0),(BilW+2cm,BilH+3cm),(0,BilH+3cm)); \end{mplibcode}% }% \def\MPBillardLSolution#1{% \setbox1=\hbox{,}% \mplibforcehmode% \xdef\PfCNomBillard{\useKV[ClesBillard]{Nom}}% \begin{mplibcode}[\PfCNomBillard] trace BilRec; drawarrow BilPt[1]--(BilPt[1]+1.5cm*unitvector(BilPt[2]-BilPt[1])) withpen pencircle scaled 1.25; for k=1 upto BLong-1: trace segment(BilPt[k],BilPt[k+1]); endfor; if Vrai=false: for k=2 upto BLong-1: trace codeperp(BilPt[k-1],BilPt[k],BilPt[k+1],5); endfor; fi; for k=1 upto BLong: BilEtiquette(substring(k-1,k) of #1,BilPt[k],BilEd[k]); endfor; trace BilRep shifted BilShift; % On affiche les lettres de la réponse. for k=1 upto BLong: label.top(TEX("\rule[-\dp1]{0pt}{0pt}"&substring(k-1,k) of #1),BilShift+(k-1)*(7mm,0)+(2.5mm,0)); endfor; \end{mplibcode}% }% %%% % Autres formes : \MPBillardPoly{}{"mot"} % % Moteur commun à T, U, Escalier et TriangleRectangle (le L garde son code propre). % Le code de la forme remplit BilV[0..BilN-1] (BilN sommets, sens antihoraire) avec % BilAdd((x,y)), dans un repère canonique de taille BilCW x BilCH dont l'origine est % le coin bas gauche. Le moteur tourne ensuite la forme de Orientation quarts de tour, % la place dans la boîte Longueur x Largeur et trace la trajectoire. % % Algorithme (côtés de direction quelconque) : voir \MPBillardL, avec deux différences. % - Test « à l'intérieur » : nombre de croisements d'un rayon horizontal avec le contour. % - Lancer de rayon : pour un côté de sommet A et de vecteur unitaire e, le rayon P+t*d % le coupe en t=((A-P)xe)/(dxe), à la distance s=((A-P)xd)/(dxe) de A (x : produit % vectoriel) ; il faut t>0 et 0<=s<=longueur du côté ; on garde le plus petit t. % - Lettre d'un côté horizontal ou vertical : même placement que pour le L ; d'un côté % oblique : à 4.5mm du point, dans la direction de la normale extérieure. % Variables et macros METAPOST préfixées Bil, comme pour le L. %%% \long\def\MPBillardPoly#1#2{% \setbox1=\hbox{,}% \xdef\PfCNomBillard{\useKV[ClesBillard]{Nom}}% \mplibforcehmode% \begin{mplibcode}[\PfCNomBillard] _tfig:=20cm; boolean Vrai; Vrai:=\useKV[ClesBillard]{Vrai}; % Lettres des faux points. string BilAlpha; BilAlpha:="ABCDEFGHIJKLMNOQPRSTUVWXYZ"; vardef BilLettre= save kk; numeric kk; kk:=floor(uniformdeviate(26)); substring(kk,kk+1) of BilAlpha enddef; vardef BilCross(expr a,b)= (xpart a*ypart b-ypart a*xpart b) enddef; % Dimensions, orientation, paramètres de la forme. numeric BilW,BilH,BilCW,BilCH,BilQuart,BilN,BilFx,BilFy,BilMa,BilXs,BilXt,BilYs,BilRW,BilRH,BilX; pair BilO,BilV[]; BilW:=\useKV[ClesBillard]{Longueur}; BilH:=\useKV[ClesBillard]{Largeur}; BilQuart:=\useKV[ClesBillard]{Orientation} mod 4; if BilQuart mod 2=1: BilCW:=BilH; BilCH:=BilW; else: BilCW:=BilW; BilCH:=BilH; fi; BilFx:=\useKV[ClesBillard]{EncocheLong}; BilFy:=\useKV[ClesBillard]{EncocheLarg}; BilMa:=\useKV[ClesBillard]{Marches}; def BilAdd(expr p)= BilV[BilN]:=p; BilN:=BilN+1; enddef; def BilCheckFrac= if (BilFx<0.2) or (BilFx>0.8) or (BilFy<0.2) or (BilFy>0.8): errmessage "Billard : EncocheLong et EncocheLarg doivent etre compris entre 0.2 et 0.8"; fi; enddef; % Sommets de la forme (repère canonique). BilN:=0; #1 % Rotation de Orientation quarts de tour antihoraires, puis mise en place. BilRW:=BilCW; BilRH:=BilCH; for q=1 upto BilQuart: for ii=0 upto BilN-1: BilV[ii]:=(BilRH-ypart BilV[ii],xpart BilV[ii]); endfor; BilX:=BilRW; BilRW:=BilRH; BilRH:=BilX; endfor; BilO:=u*(1,2); for ii=0 upto BilN-1: BilV[ii]:=BilV[ii]+BilO; endfor; path BilRec; BilRec:=BilV[0]--BilV[1]; for ii=2 upto BilN-1: BilRec:=BilRec--BilV[ii]; endfor; BilRec:=BilRec--cycle; trace BilRec; % Vecteur unitaire et longueur de chaque côté. pair BilEu[]; numeric BilEl[]; for ii=0 upto BilN-1: BilEu[ii]:=unitvector(BilV[(ii+1) mod BilN]-BilV[ii]); BilEl[ii]:=abs(BilV[(ii+1) mod BilN]-BilV[ii]); endfor; % Test « strictement à l'intérieur » par croisements d'un rayon horizontal. vardef BilInside(expr p)= save cc,pa,pb; numeric cc; pair pa,pb; cc:=0; for ii=0 upto BilN-1: pa:=BilV[ii]; pb:=BilV[(ii+1) mod BilN]; if ((ypart pa>ypart p) and not (ypart pb>ypart p)) or ((ypart pb>ypart p) and not (ypart pa>ypart p)): if xpart p0.5 enddef; % Lancer de rayon : BilT = distance au premier côté touché, BilI = son numéro. numeric BilT,BilI,BilTa,BilSs,BilDen,BilNum,BilNumDeux,BilEd[]; def BilTir(expr P,d)= BilT:=4000; BilI:=-1; for ii=0 upto BilN-1: BilDen:=BilCross(d,BilEu[ii]); if abs(BilDen)>0.001: BilNum:=BilCross(BilV[ii]-P,BilEu[ii]); BilNumDeux:=BilCross(BilV[ii]-P,d); if (abs(BilNum)<4000*abs(BilDen)) and (abs(BilNumDeux)<4000*abs(BilDen)): BilTa:=BilNum/BilDen; BilSs:=BilNumDeux/BilDen; if (BilTa>0.5) and (BilSs>=-0.01) and (BilSs<=BilEl[ii]+0.01) and (BilTa0: dotlabel.bot(TEX("\rule[-\dp1]{0pt}{0pt}"&s),P); else: dotlabel.top(TEX("\rule[-\dp1]{0pt}{0pt}"&s),P); fi; elseif abs(xpart BilEu[e])<0.01: if ypart BilEu[e]>0: dotlabel.rt(TEX("\rule[-\dp1]{0pt}{0pt}"&s),P); else: dotlabel.lft(TEX("\rule[-\dp1]{0pt}{0pt}"&s),P); fi; else: dotlabel("",P); label(TEX("\rule[-\dp1]{0pt}{0pt}"&s),P+4.5mm*BilEn); fi; enddef; % Abscisses curvilignes des sommets. numeric BilCum[],BilPer,BilS[],BilQ[],BilEcart,BilGa,BilGb,BilNd,BilSf,BilDv,BilE; pair BilP; BilCum[0]:=0; for ii=0 upto BilN-1: BilCum[ii+1]:=BilCum[ii]+BilEl[ii]; endfor; BilPer:=BilCum[BilN]; % Trajectoire d'un essai (Depart BilD, Angle BilAng). BilFatal : pas de point suivant ; % BilBon : trajectoire utilisable (points distants d'au moins 5mm, et de 3mm des sommets). numeric BLong,BilD,BilAng,BilEss,BilMax; boolean BilBon,BilFatal,BilOK,BilAjuste; BLong:=length #2; pair BilPt[],BilDir,BilPd; def BilTraj= BilBon:=true; BilFatal:=false; BilPt[1]:=point BilD of BilRec; BilEd[1]:=floor(BilD) mod BilN; BilDir:=dir(BilAng); if not BilInside(BilPt[1]+0.05*BilDir): BilDir:=-BilDir; if not BilInside(BilPt[1]+0.05*BilDir): BilFatal:=true; fi; fi; if BilFatal: BilPt[2]:=BilPt[1]; BilEd[2]:=BilEd[1]; else: BilTir(BilPt[1],BilDir); if BilI<0: BilFatal:=true; BilPt[2]:=BilPt[1]; BilEd[2]:=BilEd[1]; else: BilPt[2]:=BilPt[1]+BilT*BilDir; BilEd[2]:=BilI; fi; fi; for k=3 upto BLong: if BilFatal: BilPt[k]:=BilPt[k-1]; BilEd[k]:=BilEd[k-1]; else: BilPd:=unitvector(BilPt[k-1]-BilPt[k-2]); if Vrai: BilDir:=2*(xpart BilPd*xpart BilEu[BilEd[k-1]]+ypart BilPd*ypart BilEu[BilEd[k-1]])*BilEu[BilEd[k-1]]-BilPd; else: BilDir:=(-ypart BilPd,xpart BilPd); if not BilInside(BilPt[k-1]+0.05*BilDir): BilDir:=-BilDir; fi; fi; BilTir(BilPt[k-1],BilDir); if BilI<0: BilFatal:=true; BilPt[k]:=BilPt[k-1]; BilEd[k]:=BilEd[k-1]; else: BilPt[k]:=BilPt[k-1]+BilT*BilDir; BilEd[k]:=BilI; fi; fi; endfor; if BilFatal: BilBon:=false; else: for k=1 upto BLong: BilS[k]:=BilCum[BilEd[k]]+abs(BilPt[k]-BilV[BilEd[k]]); BilDv:=BilS[k]-BilCum[BilEd[k]]; if BilCum[BilEd[k]+1]-BilS[k]0: message "Billard : Depart et Angle ajustes automatiquement"; fi; else: if BilFatal: errmessage "Billard : trajectoire impossible (aucun point suivant), modifier Depart et Angle ou ajouter la cle Ajuster"; else: message "Billard : deux points trop proches ou un point trop pres d'un sommet, modifier Depart ou Angle (ou cle Ajuster)"; fi; fi; drawarrow BilPt[1]--(BilPt[1]+1.5cm*unitvector(BilPt[2]-BilPt[1])) withpen pencircle scaled 1.25; for k=1 upto BLong: BilS[k]:=BilCum[BilEd[k]]+abs(BilPt[k]-BilV[BilEd[k]]); BilQ[k]:=BilS[k]; endfor; for i=1 upto BLong-1: for j=1 upto BLong-i: if BilQ[j]>BilQ[j+1]: BilX:=BilQ[j]; BilQ[j]:=BilQ[j+1]; BilQ[j+1]:=BilX; fi; endfor; endfor; % Lettres des vrais points. for k=1 upto BLong: BilEtiquette(substring(k-1,k) of #2,BilPt[k],BilEd[k]); endfor; % Faux points, répartis régulièrement entre deux vrais points consécutifs. BilEcart:=10mm; for j=1 upto BLong: BilGa:=BilQ[j]; if j=BilPer: BilSf:=BilSf-BilPer; fi; BilE:=0; for ii=1 upto BilN-1: if BilSf>=BilCum[ii]: BilE:=ii; fi; endfor; BilDv:=BilSf-BilCum[BilE]; if BilCum[BilE+1]-BilSf5mm: BilP:=BilV[BilE]+(BilSf-BilCum[BilE])*BilEu[BilE]; BilEtiquette(BilLettre,BilP,BilE); fi; endfor; endfor; % Ligne de réponse. picture BilRep; BilRep:=image( trace segment((0,0),(7*BLong*mm-2mm,0)) dashed dashpattern(on5mm off2mm); ); pair BilShift; BilShift:=(xpart(BilO)+BilW/2,5mm)-center BilRep; trace BilRep shifted BilShift; clip currentpicture to polygone((0,0),(BilW+2cm,0),(BilW+2cm,BilH+3cm),(0,BilH+3cm)); \end{mplibcode}% }% % Un macro par forme : \MPBillardForme{mot} et \MPBillardFormeSolution{mot}, % appelées par \Billard selon la clé Forme. Le corrigé est le même pour toutes les formes % construites sur les noms Bil... (\MPBillardLSolution). \def\MPBillardFormeL#1{\MPBillardL{#1}}% \def\MPBillardFormeLSolution#1{\MPBillardLSolution{#1}}% % T : barre en haut (hauteur EncocheLarg*Largeur), pied centré (largeur EncocheLong*Longueur). \def\MPBillardFormeT#1{% \MPBillardPoly{% BilCheckFrac; BilXs:=(BilCW-BilFx*BilCW)/2; BilXt:=BilXs+BilFx*BilCW; BilYs:=BilCH-BilFy*BilCH; BilAdd((BilXs,0)); BilAdd((BilXt,0)); BilAdd((BilXt,BilYs)); BilAdd((BilCW,BilYs)); BilAdd((BilCW,BilCH)); BilAdd((0,BilCH)); BilAdd((0,BilYs)); BilAdd((BilXs,BilYs)); }{#1}% }% \let\MPBillardFormeTSolution\MPBillardLSolution % U : creux en haut (largeur EncocheLong*Longueur, profondeur EncocheLarg*Largeur), centré. \def\MPBillardFormeU#1{% \MPBillardPoly{% BilCheckFrac; BilXs:=(BilCW-BilFx*BilCW)/2; BilXt:=BilXs+BilFx*BilCW; BilYs:=BilCH-BilFy*BilCH; BilAdd((0,0)); BilAdd((BilCW,0)); BilAdd((BilCW,BilCH)); BilAdd((BilXt,BilCH)); BilAdd((BilXt,BilYs)); BilAdd((BilXs,BilYs)); BilAdd((BilXs,BilCH)); BilAdd((0,BilCH)); }{#1}% }% \let\MPBillardFormeUSolution\MPBillardLSolution % Escalier : Marches marches (2 à 4), la plus haute à droite. Marches=2 donne un L. \def\MPBillardFormeEscalier#1{% \MPBillardPoly{% if (BilMa<2) or (BilMa>4): errmessage "Billard : Marches doit valoir 2, 3 ou 4"; fi; BilAdd((0,0)); BilAdd((BilCW,0)); BilAdd((BilCW,BilCH)); for j=1 upto BilMa-1: BilAdd((BilCW-j*BilCW/BilMa,BilCH-(j-1)*BilCH/BilMa)); BilAdd((BilCW-j*BilCW/BilMa,BilCH-j*BilCH/BilMa)); endfor; BilAdd((0,BilCH/BilMa)); }{#1}% }% \let\MPBillardFormeEscalierSolution\MPBillardLSolution % Triangle rectangle : angle droit en bas à gauche, jambes Longueur et Largeur. \def\MPBillardFormeTriangleRectangle#1{% \MPBillardPoly{% BilAdd((0,0)); BilAdd((BilCW,0)); BilAdd((0,BilCH)); }{#1}% }% \let\MPBillardFormeTriangleRectangleSolution\MPBillardLSolution