%%%
% Serpent
%%%
\def\filedateSerpent{2024/08/04}%
\def\fileversionSerpent{0.1}%
\message{-- \filedateSerpent\space v\fileversionSerpent}%
%
\newtoks\tokListeSerpentPerso{}%
\def\UpdatetoksSerpent#1\nil{\addtotok\tokListeSerpentPerso{"#1"}}%

\setKVdefault[Serpent]{Cases=9,Case=1,Solution=false,PasQuad=7mm,ValeurMin=2,ValeurMax=10,Creation=false,Graines=false,Direct=false,Disque=false,Spirale=false}
\defKV[Serpent]{CouleurSolution=\setKV[Serpent]{Solution}}%
\defKV[Serpent]{Perso=\setKV[Serpent]{Creation}\xdef\PfCFooCheminSerpent{#1}}%+Liste des déplacements
\defKV[Serpent]{Graine=\setKV[Serpent]{Graines}}%

\NewDocumentCommand\Serpent{o}{%
  \useKVdefault[Serpent]%
  \setKV[Serpent]{#1}%
  \tokListeSerpentPerso{}%
  \ifboolKV[Serpent]{Creation}{%
    \setsepchar{,}\ignoreemptyitems%
    \expandafter\UpdatetoksSerpent\PfCFooCheminSerpent\nil%
    \BuildSerpent{\the\tokListeSerpentPerso}%
    \reademptyitems%
  }{%
    \ifboolKV[Serpent]{Direct}{%
        \BuildSerpentDirect{}%
    }{\ifboolKV[Serpent]{Spirale}{%
        %\BuildSerpentSpirale{}%
      }{%
        \BuildSerpent{}%
      }%
    }%
  }%
}%

\NewDocumentCommand\BuildSerpent{m}{%
  \ifluatex%
    \mplibforcehmode%
    \mplibnumbersystem{double}%
    \begin{mplibcode}
      %
      boolean Solution,Perso,Graines,Disque;
      Solution=\useKV[Serpent]{Solution};
      Perso=\useKV[Serpent]{Creation};
      Graines=\useKV[Serpent]{Graines};
      Disque=\useKV[Serpent]{Disque};
      %
      if Graines:
      randomseed:=\useKV[Serpent]{Graine};
      fi;
      %
      color CoulSolution;
      if Solution:
      CoulSolution=\useKV[Serpent]{CouleurSolution};
      fi;
      %
      numeric LongueurChemin,Depart;
      LongueurChemin=\useKV[Serpent]{Cases};
      ValeurMin=\useKV[Serpent]{ValeurMin};
      ValeurMax=\useKV[Serpent]{ValeurMax};
      CaseDepart=\useKV[Serpent]{Case};
      Depart=ValeurMin+floor(uniformdeviate(ValeurMax-ValeurMin+1));
      %
      boolean Impossible[][];
      % 
      vardef InitialisationZone=
      for k=-1 upto HautQuad+1:
      for l=-1 upto LargQuad+1:
      Impossible[k][l]=true;
      endfor;
      endfor;
      for k=1 upto HautQuad:
      for l=1 upto LargQuad:
      Impossible[k][l]:=false;
      endfor;
      endfor;
      enddef;
      %
      vardef ReInitialisationZone=
      for k=-1 upto HautQuad+1:
      for l=-1 upto LargQuad+1:
      Impossible[k][l]:=true;
      endfor;
      endfor;
      for k=1 upto HautQuad:
      for l=1 upto LargQuad:
      Impossible[k][l]:=false;
      endfor;
      endfor;
      enddef;
      %
      vardef RAZZone=
      pair PileChemin[];
      indiceChemin:=0;
      enddef;
      % 
      def PushChemin(expr tt)=
      if indiceChemin<1:
      PileChemin[1]:=tt;
      indiceChemin:=1;
      else:
      PileChemin[indiceChemin+1]:=tt;
      indiceChemin:=indiceChemin+1;
      fi;
      enddef;
      %
      def PopChemin=
      currentpicture:=nullpicture;
      RAZZone;
      ReInitialisationZone;
      %Redemarrage
      nbcaseschoisies:=1;
      PushChemin((ChoixLigneD,ChoixColonneD));
      N[nbcaseschoisies]=4*PasQuad*(ChoixColonneD-1,ChoixLigneD-1);
      Impossible[ChoixLigneD][ChoixColonneD]:=true;
      VoisinDispo(ChoixLigneD,ChoixColonneD);
      enddef;
      %
      %Pile des cases voisines de la case parcourue
      pair PileVoisin[];
      numeric indiceVoisin;
      indiceVoisin=0;
      % 
      vardef RAZPileVoisin=
      indiceVoisin:=0;
      enddef;
      %
      def PushVoisin(expr tt)=
      if indiceVoisin<1:
      PileVoisin[1]:=tt;
      indiceVoisin:=1;
      else:
      PileVoisin[indiceVoisin+1]:=tt;
      indiceVoisin:=indiceVoisin+1;
      fi;
      enddef;
      % 
      vardef VoisinDispo(expr la,lo)=
      RAZPileVoisin;
      numeric nbvoisin;
      nbvoisin:=0;
      if Impossible[la+1][lo]=false:
      nbvoisin:=nbvoisin+1;
      PushVoisin((la+1,lo));
      fi;
      if Impossible[la-1][lo]=false:
      nbvoisin:=nbvoisin+1;
      PushVoisin((la-1,lo));
      fi;
      if Impossible[la][lo+1]=false:
      nbvoisin:=nbvoisin+1;
      PushVoisin((la,lo+1));
      fi;
      if Impossible[la][lo-1]=false:
      nbvoisin:=nbvoisin+1;
      PushVoisin((la,lo-1));
      fi;
      enddef;
      %
      PasQuad=\useKV[Serpent]{PasQuad};
      HautQuad=if (LongueurChemin mod 2)=1:(LongueurChemin+1) div 2 else: LongueurChemin div 2 fi;
      LargQuad=HautQuad;
      % Démarrage du choix perso ou automatique du serpent
      InitialisationZone;
      RAZZone;
      %
      ChoixLigneD=(HautQuad div 2)+1;
      ChoixColonneD=(LargQuad div 2)+1;
      %
      pair N[];
      vardef RemplirChemin(text t)=
      nbpoints:=1;
      N[1]:=4*PasQuad*(0,0);
      string p_;
      for k=2 upto LongueurChemin:
      nbpoints:=nbpoints+1;
      p_:=substring(k-2,k-1) of t;
      if p_=">":
      N[nbpoints]=N[nbpoints-1]+4*PasQuad*(1,0);
      elseif p_="<":
      N[nbpoints]=N[nbpoints-1]+4*PasQuad*(-1,0);
      elseif p_="+":
      N[nbpoints]=N[nbpoints-1]+4*PasQuad*(0,1);
      elseif p_="-":
      N[nbpoints]=N[nbpoints-1]+4*PasQuad*(0,-1);
      fi;
      endfor;
      enddef;
      %
      if Perso:
      % chemin personnel
      RemplirChemin(#1);
      else:
      PushChemin((ChoixLigneD,ChoixColonneD));
      nbcaseschoisies=1;
      N[nbcaseschoisies]=4*PasQuad*(ChoixColonneD-1,ChoixLigneD-1);
      Impossible[ChoixLigneD][ChoixColonneD]:=true;
      VoisinDispo(ChoixLigneD,ChoixColonneD);
      % Le "parcours"
      forever: exitif nbcaseschoisies>LongueurChemin-1;
      nb:=ceiling(uniformdeviate(nbvoisin));
      if nb>0:
      for k=1 upto nbvoisin:
      Impossible[xpart(PileVoisin[nb])][ypart(PileVoisin[nb])]:=true;
      endfor;
      PushChemin((xpart(PileVoisin[nb]),ypart(PileVoisin[nb])));
      nbcaseschoisies:=nbcaseschoisies+1;
      N[nbcaseschoisies]:=4*PasQuad*(ypart(PileVoisin[nb])-1,xpart(PileVoisin[nb])-1);
      VoisinDispo(xpart(PileChemin[indiceChemin]),ypart(PileChemin[indiceChemin]));
      else:
      PopChemin;
      fi;
      endfor;
      fi;
      % Tracage
      if CaseDepart=1:
      label(TEX("\Large"&decimal(Depart)),N[1] shifted(PasQuad*(1,1)));
      else:
      if Solution:
      label(TEX("\Large"&decimal(Depart)),N[1] shifted(PasQuad*(1,1))) withcolor CoulSolution;
      fi;
      fi;
      for k=1 upto LongueurChemin:
      if Disque:
      trace (fullcircle scaled (2*PasQuad)) shifted (N[k]+PasQuad*(1,1));
      else:
      trace (unitsquare scaled (2*PasQuad)) shifted N[k];
      fi;
      endfor;
      for k=1 upto LongueurChemin-1:
      drawarrow (center (unitsquare scaled (2*PasQuad)) shifted N[k])--(center (unitsquare scaled (2*PasQuad)) shifted N[k+1]) cutbefore ((unitsquare scaled (2*PasQuad)) shifted N[k]) cutafter ((unitsquare scaled (2*PasQuad)) shifted N[k+1]);
      endfor;
      %%drawoptions();
      boolean PremierChiffre;
      PremierChiffre=true;
      %% Test Premier
      vardef TestPremier(expr nombre)=
      nbdiviseur:=0;
      nbdiv:=0;
      for k=2 upto nombre:
      if (nombre mod k)=0:
      nbdiviseur:=nbdiviseur+1;
      if k<11:
      nbdiv:=nbdiv+1;
      Diviseur[nbdiv]:=k;
      fi;
      fi;
      endfor;
      if nbdiviseur>1:
      PremierChiffre:=false;
      else:
      PremierChiffre:=true;
      fi;
      enddef;
      % Les opérations
      vardef EcrireSomme(expr tt)=
      Somme:=5+ceiling(uniformdeviate(10));
      Chiffre[tt]=Chiffre[tt-1]+Somme;
      St[tt]=iso(N[tt-1]+PasQuad*(1,1),N[tt]+PasQuad*(1,1));
      if xpart(N[tt])-xpart(N[tt-1])=0:
      label.rt(TEX("$+"&decimal(Somme)&"$"),St[tt]);
      else:
      label.bot(TEX("$+"&decimal(Somme)&"$"),St[tt]);
      fi;
      enddef;
      %
      vardef EcrireProduit(expr tt)=
      Multiple:=1+ceiling(uniformdeviate(8));
      Chiffre[tt]=Chiffre[tt-1]*Multiple;
      St[tt]=iso(N[tt-1]+PasQuad*(1,1),N[tt]+PasQuad*(1,1));
      if xpart(N[tt])-xpart(N[tt-1])=0:
      label.rt(TEX("$\PfCSymbolTimes"&decimal(Multiple)&"$"),St[tt]);
      else:
      label.bot(TEX("$\PfCSymbolTimes"&decimal(Multiple)&"$"),St[tt]);
      fi;
      enddef;
      numeric Chiffre[];
      pair St[];
      Chiffre[1]=Depart;
      %
      vardef EcrireDifferenceCent(expr tt)=
      Difference:=ceiling(Chiffre[tt-1]-15+uniformdeviate(10));
      Chiffre[tt]=Chiffre[tt-1]-Difference;
      St[tt]=iso(N[tt-1]+PasQuad*(1,1),N[tt]+PasQuad*(1,1));
      if xpart(N[tt])-xpart(N[tt-1])=0:
      label.rt(TEX("$-"&decimal(Difference)&"$"),St[tt]);
      else:
      label.bot(TEX("$-"&decimal(Difference)&"$"),St[tt]);
      fi;
      enddef;
      %
      vardef EcrireDifference(expr tt)=
      Difference:=if Chiffre[tt-1]<20:1+floor(uniformdeviate(Chiffre[tt-1]-1)) else: 5+ceiling(uniformdeviate(10)) fi;
      Chiffre[tt]=Chiffre[tt-1]-Difference;
      St[tt]=iso(N[tt-1]+PasQuad*(1,1),N[tt]+PasQuad*(1,1));
      if xpart(N[tt])-xpart(N[tt-1])=0:
      label.rt(TEX("$-"&decimal(Difference)&"$"),St[tt]);
      else:
      label.bot(TEX("$-"&decimal(Difference)&"$"),St[tt]);
      fi;
      enddef;
      %
      vardef EcrireQuotient(expr tt)=
      choixdiv:=ceiling(nbdiv);
      Quotient:=Diviseur[choixdiv];
      Chiffre[tt]=Chiffre[tt-1] div Quotient;
      St[tt]=iso(N[tt-1]+PasQuad*(1,1),N[tt]+PasQuad*(1,1));
      if xpart(N[tt])-xpart(N[tt-1])=0:
      label.rt(TEX("$\PfCSymbolDiv"&decimal(Quotient)&"$"),St[tt]);
      else:
      label.bot(TEX("$\PfCSymbolDiv"&decimal(Quotient)&"$"),St[tt]);
      fi;
      enddef;
      %
      for k=2 upto LongueurChemin:
      if (Chiffre[k-1]=1) or (Chiffre[k-1]=0):
      % On additionne ou on multiplie si c'est un
      if Chiffre[k-1]=1:
      alea:=ceiling(uniformdeviate(2));
      if alea=1:
      EcrireSomme(k);
      else:
      EcrireProduit(k);
      fi;
      else:
      EcrireSomme(k);
      fi;
      else:
      TestPremier(Chiffre[k-1]);
      if PremierChiffre:
      if Chiffre[k-1]>100:
      EcrireDifferenceCent(k);
      else:
      alea:=ceiling(uniformdeviate(3));
      if alea=1:
      EcrireSomme(k);
      elseif alea=2:
      EcrireDifference(k);
      elseif alea=3:
      EcrireProduit(k);      
      fi;
      fi;
      if k=CaseDepart:
      label(TEX("\Large"&decimal(Chiffre[k])),N[k] shifted(PasQuad*(1,1)));
      elseif Solution:
      label(TEX("\Large"&decimal(Chiffre[k])),N[k] shifted(PasQuad*(1,1))) withcolor CoulSolution;
      fi;
      else:
      if Chiffre[k-1]>100:
      alea:=ceiling(uniformdeviate(2));
      if alea=1:
      EcrireDifferenceCent(k);
      else:
      EcrireQuotient(k);
      fi;
      else:
      alea:=ceiling(uniformdeviate(4));
      if alea=1:
      EcrireSomme(k);
      elseif alea=2:
      EcrireDifference(k);
      elseif alea=3:
      EcrireProduit(k);
      elseif alea=4:
      EcrireQuotient(k);
      fi;
      fi;
      fi;
      fi;
      if k=CaseDepart:
      label(TEX("\Large"&decimal(Chiffre[k])),N[k] shifted(PasQuad*(1,1)));
      else:
      if Solution:
      label(TEX("\Large"&decimal(Chiffre[k])),N[k] shifted(PasQuad*(1,1))) withcolor CoulSolution;
      fi;
      fi;
      endfor;
    \end{mplibcode}
    \mplibnumbersystem{scaled}%
  \fi
}%

\NewDocumentCommand\BuildSerpentDirect{m}{%
  \ifluatex%
    \mplibforcehmode%
    \mplibnumbersystem{double}%
    \begin{mplibcode}
      %
      boolean Solution,Graines,Disque;
      Solution=\useKV[Serpent]{Solution};
      Graines=\useKV[Serpent]{Graines};
      Disque=\useKV[Serpent]{Disque};
      %
      if Graines:
      randomseed:=\useKV[Serpent]{Graine};
      fi;
      %
      color CoulSolution;
      if Solution:
      CoulSolution=\useKV[Serpent]{CouleurSolution};
      fi;
      %
      numeric LongueurChemin,Depart;
      LongueurChemin=\useKV[Serpent]{Cases};
      ValeurMin=\useKV[Serpent]{ValeurMin};
      ValeurMax=\useKV[Serpent]{ValeurMax};
      CaseDepart=\useKV[Serpent]{Case};
      Depart=ValeurMin+floor(uniformdeviate(ValeurMax-ValeurMin+1));
      %
      PasQuad=5mm;%\useKV[Serpent]{PasQuad};
      %
      pair N[];
      N[1]:=4*PasQuad*(0,0);
      for k=2 upto LongueurChemin:
      N[k]=N[k-1]+4*PasQuad*(1,0);
      endfor;
      % Tracage
      if CaseDepart=1:
      label(TEX("\Large"&decimal(Depart)),N[1] shifted(PasQuad*(1,1)));
      else:
      if Solution:
      label(TEX("\Large"&decimal(Depart)),N[1] shifted(PasQuad*(1,1))) withcolor CoulSolution;
      fi;
      fi;
      for k=1 upto LongueurChemin:
      if Disque:
      trace (fullcircle scaled (2*PasQuad)) shifted (N[k]+PasQuad*(1,1));
      else:
      trace (unitsquare scaled (2*PasQuad)) shifted N[k];
      fi;
      endfor;
      %
      path cheminfleche[];
      path CheminFlecheBase;
      CheminFlecheBase=(0,2*PasQuad){dir45}..(4*PasQuad,2*PasQuad);
      for k=1 upto LongueurChemin-1:
      cheminfleche[k]=CheminFlecheBase shifted (N[k]+(PasQuad,0));
      %cheminfleche[k]=(center (unitsquare scaled (2*PasQuad)) shifted N[k]){dir45}..(center (unitsquare scaled (2*PasQuad)) shifted N[k+1]) cutbefore ((unitsquare scaled (2*PasQuad)) shifted N[k]) cutafter ((unitsquare scaled (2*PasQuad)) shifted N[k+1]);
      drawarrow cheminfleche[k];
      endfor;
      %%drawoptions();
      boolean PremierChiffre;
      PremierChiffre=true;
      %% Test Premier
      vardef TestPremier(expr nombre)=
      nbdiviseur:=0;
      nbdiv:=0;
      for k=2 upto nombre:
      if (nombre mod k)=0:
      nbdiviseur:=nbdiviseur+1;
      if k<11:
      nbdiv:=nbdiv+1;
      Diviseur[nbdiv]:=k;
      fi;
      fi;
      endfor;
      if nbdiviseur>1:
      PremierChiffre:=false;
      else:
      PremierChiffre:=true;
      fi;
      enddef;
      % Les opérations
      numeric Chiffre[];
      pair St[];
      Chiffre[1]=Depart;
      %
      vardef EcrireSomme(expr tt)=
      Somme:=5+ceiling(uniformdeviate(10));
      Chiffre[tt]=Chiffre[tt-1]+Somme;
      St[tt]=point(0.5*length cheminfleche[tt-1]) of cheminfleche[tt-1];
      label.top(TEX("$+"&decimal(Somme)&"$"),St[tt]);
      enddef;
      %
      vardef EcrireProduit(expr tt)=
      Multiple:=1+ceiling(uniformdeviate(8));
      Chiffre[tt]=Chiffre[tt-1]*Multiple;
      St[tt]=point(0.5*length cheminfleche[tt-1]) of cheminfleche[tt-1];
      label.top(TEX("$\PfCSymbolTimes"&decimal(Multiple)&"$"),St[tt]);
      enddef;
      %
      vardef EcrireDifferenceCent(expr tt)=
      Difference:=ceiling(Chiffre[tt-1]-15+uniformdeviate(10));
      Chiffre[tt]=Chiffre[tt-1]-Difference;
      St[tt]=point(0.5*length cheminfleche[tt-1]) of cheminfleche[tt-1];
      label.top(TEX("$-"&decimal(Difference)&"$"),St[tt]);
      enddef;
      %
      vardef EcrireDifference(expr tt)=
      Difference:=if Chiffre[tt-1]<20:1+floor(uniformdeviate(Chiffre[tt-1]-1)) else: 5+ceiling(uniformdeviate(10)) fi;
      Chiffre[tt]=Chiffre[tt-1]-Difference;
      St[tt]=point(0.5*length cheminfleche[tt-1]) of cheminfleche[tt-1];
      label.top(TEX("$-"&decimal(Difference)&"$"),St[tt]);
      enddef;
      %
      vardef EcrireQuotient(expr tt)=
      choixdiv:=ceiling(nbdiv);
      Quotient:=Diviseur[choixdiv];
      Chiffre[tt]=Chiffre[tt-1] div Quotient;
      St[tt]=point(0.5*length cheminfleche[tt-1]) of cheminfleche[tt-1];
      label.top(TEX("$\PfCSymbolDiv"&decimal(Quotient)&"$"),St[tt]);
      enddef;
      %
      for k=2 upto LongueurChemin:
      if (Chiffre[k-1]=1) or (Chiffre[k-1]=0):
      % On additionne ou on multiplie si c'est un
      if Chiffre[k-1]=1:
      alea:=ceiling(uniformdeviate(2));
      if alea=1:
      EcrireSomme(k);
      else:
      EcrireProduit(k);
      fi;
      else:
      EcrireSomme(k);
      fi;
      else:
      TestPremier(Chiffre[k-1]);
      if PremierChiffre:
      if Chiffre[k-1]>100:
      EcrireDifferenceCent(k);
      else:
      alea:=ceiling(uniformdeviate(3));
      if alea=1:
      EcrireSomme(k);
      elseif alea=2:
      EcrireDifference(k);
      elseif alea=3:
      EcrireProduit(k);      
      fi;
      fi;
      if k=CaseDepart:
      label(TEX("\Large"&decimal(Chiffre[k])),N[k] shifted(PasQuad*(1,1)));
      elseif Solution:
      label(TEX("\Large"&decimal(Chiffre[k])),N[k] shifted(PasQuad*(1,1))) withcolor CoulSolution;
      fi;
      else:
      if Chiffre[k-1]>100:
      alea:=ceiling(uniformdeviate(2));
      if alea=1:
      EcrireDifferenceCent(k);
      else:
      EcrireQuotient(k);
      fi;
      else:
      alea:=ceiling(uniformdeviate(4));
      if alea=1:
      EcrireSomme(k);
      elseif alea=2:
      EcrireDifference(k);
      elseif alea=3:
      EcrireProduit(k);
      elseif alea=4:
      EcrireQuotient(k);
      fi;
      fi;
      fi;
      fi;
      if k=CaseDepart:
      label(TEX("\Large"&decimal(Chiffre[k])),N[k] shifted(PasQuad*(1,1)));
      else:
      if Solution:
      label(TEX("\Large"&decimal(Chiffre[k])),N[k] shifted(PasQuad*(1,1))) withcolor CoulSolution;
      fi;
      fi;
      endfor;
    \end{mplibcode}
    \mplibnumbersystem{scaled}%
  \fi
}%