import random
import math
import os
import time
from tkinter import *

def enregistrer(mv,chemin):
    a = f'{mv}'
    b = ""
    for i in range(len(a)):
        match a[i]:
            case "(" : ()
            case ")" : ()
            case ":" : ()
            case "," : ()
            case " " : ()
            case "{" : ()
            case "}" : ()
            case _ : b += a[i]
    with open(chemin,'w',encoding="utf-8") as f:
        f.write(b)

def lire_mouvement(chemin):
    def int_vect(s):
        b = []
        d = False
        for i in range(len(s)):
            if s[i] == "-":
                d = True
            else:
                j = int(s[i])
                if d :
                    j = -j
                    d = False
                b.append(j)
        return tuple(b)
    str = ""
    with open(chemin,'r',encoding="utf-8") as f:
        str = f.read()
    mv = dict()
    v = int_vect(str)
    for i in range(1 + (len(v) - 12)//12):
        ind = []
        for j in range(3):
            ind.append(v[i*12+j])
            mat = [[1,0,0],[0,1,0],[0,0,1]]
            for j in range(3):
                for k in range(3):
                    mat[j][k] = v[i * 12 + 3 + j * 3 + k]
        mv[tuple(ind)] = tuple(map(tuple,mat))
    return mv

groupe_du_cube = (
    ((1, 0, 0), (0, 1, 0), (0, 0, 1)),
    ((1, 0, 0), (0, -1, 0), (0, 0, -1)),
    ((-1, 0, 0), (0, 1, 0), (0, 0, -1)),
    ((-1, 0, 0), (0, -1, 0), (0, 0, 1)),
    ((1, 0, 0), (0, 0, -1), (0, 1, 0)),
    ((0, 0, 1), (0, 1, 0), (-1, 0, 0)),
    ((0, -1, 0), (1, 0, 0), (0, 0, 1)),
    ((1, 0, 0), (0, 0, 1), (0, -1, 0)),
    ((0, 0, -1), (0, 1, 0), (1, 0, 0)),
    ((0, 1, 0), (-1, 0, 0), (0, 0, 1)),
    ((0, -1, 0), (-1, 0, 0), (0, 0, -1)),
    ((0, 1, 0), (1, 0, 0), (0, 0, -1)),
    ((-1, 0, 0), (0, 0, -1), (0, -1, 0)),
    ((0, 0, -1), (0, -1, 0), (-1, 0, 0)),
    ((0, 0, 1), (0, -1, 0), (1, 0, 0)),
    ((-1, 0, 0), (0, 0, 1), (0, 1, 0)),
    ((0, 1, 0), (0, 0, 1), (1, 0, 0)),
    ((0, 0, -1), (-1, 0, 0), (0, 1, 0)),
    ((0, 0, 1), (-1, 0, 0), (0, -1, 0)),
    ((0, 1, 0), (0, 0, -1), (-1, 0, 0)),
    ((0, 0, -1), (1, 0, 0), (0, -1, 0)),
    ((0, -1, 0), (0, 0, -1), (1, 0, 0)),
    ((0, -1, 0), (0, 0, 1), (-1, 0, 0)),
    ((0, 0, 1), (1, 0, 0), (0, 1, 0))
)

def decomposition(m):
    """renvoie la décomposition en quarts de tour autour des axes de l'inverse de la matrice m du groupe du cube"""
    lops = ["", "a0a0", "d0d0", "h0h0", "a0'", "d0'", "h0'", "a0", "d0", "h0",
                "h0'a0a0", "h0a0a0", "d0d0a0", "d0a0a0", "d0'a0a0", "d0d0a0'", "h0a0",
                "d0a0'", "d0'a0", "h0a0'", "d0a0", "h0'a0'", "h0'a0", "d0'a0'"]
    for i in range(len(groupe_du_cube)):
        if m == groupe_du_cube[i]:
            return lops[i]
    return ""

#les centres des minicubes
coins = ((1, 1, 1), (1, 1, -1), (1, -1, 1), (1, -1, -1), (-1, 1, 1),
               (-1, 1, -1), (-1, -1, 1), (-1, -1, -1))

def sign(indices, p):
    """renvoie la signature d'une permutation p des indices"""
    def N(v):
        return v[0] + v[1] * 3 + v[2] * 9
    s = 1
    for i in range(len(indices)):
        for j in range(i+1, len(indices)):
            ii = N(indices[i])
            jj = N(indices[j])
            pii = N(p(indices[i]))
            pjj = N(p(indices[j]))
            if ii < jj and pii > pjj or ii > jj and pii < pjj :
                s *= -1
    return s

def randomize(a):
    """ renvoie une permutation aléatoire d'un uplet a d'indices"""
    a1 = list(a)
    random.shuffle(a1)
    return a1

def produit_scalaire(a,b):
    return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]

def matrice_scalaire(a):
    return ((a,0,0),(0,a,0),(0,0,a))

def rot (v):
    if v == (1,0,0): return ((1,0,0),(0,0,-1),(0,1,0))
    if v == (0,1,0): return ((0,0,1),(0,1,0),(-1,0,0))
    if v == (0,0,1): return ((0,-1,0),(1,0,0),(0,0,1))
    if v == (-1,0,0): return ((1,0,0),(0,0,1),(0,-1,0))
    if v == (0,-1,0): return ((0,0,-1),(0,1,0),(1,0,0))
    if v == (0,0,-1): return ((0,1,0),(-1,0,0),(0,0,1))
    return matrice_scalaire(1)

def transpose(m):
     return ((m[0][0],m[1][0],m[2][0]),(m[0][1],m[1][1],m[2][1]),(m[0][2],m[1][2],m[2][2]))

def roti(v):
    return transpose(rot(v))

def produit_vecteur_matrice(v,m):
    t = transpose(m)
    return (produit_scalaire(v,t[0]),produit_scalaire(v,t[1]),produit_scalaire(v,t[2]))

def produit_matrices(m1,m2):
    return (produit_vecteur_matrice(m1[0],m2),produit_vecteur_matrice(m1[1],m2),produit_vecteur_matrice(m1[2],m2))

#-----------------------------------------------------------------------------
# GROUPE DES MOUVEMENTS
#-----------------------------------------------------------------------------

def produit_mouvements(mv1,mv2):
    t = dict()
    for i in coins:
        t[i] = produit_matrices(mv1[i],mv2[produit_vecteur_matrice(i,mv1[i])])
    return t

def inverse(mv):
    t = dict()
    for i in coins:
        t[produit_vecteur_matrice(i,mv[i])] = transpose(mv[i])
    return t

def cst(m):
    t = dict()
    for i in coins:
        t[i] = m
    return t

e = cst(matrice_scalaire(1))

def mv1_of_fun(f):
    t = dict()
    for i in coins:
        t[i] = f(i)
    return t

def fun_of_mv1(mv):
    return lambda x: mv[x]

def rub(v):
    t = dict()
    for i in coins:
        if produit_scalaire(i,v) == 1:
            t[i] = rot(v)
        else:
            t[i] = matrice_scalaire(1)
    return t

def rubi(v):
    return inverse(rub(v))

def marque(x):
    return (0,0,x[2])

def gg(i,j):
    for m in groupe_du_cube:
        if produit_vecteur_matrice(i,m) == j and produit_vecteur_matrice(marque(i),m) == marque(j):
            return m
    return matrice_scalaire(1)

def sur(mv):
    return lambda i: produit_vecteur_matrice(i,mv[i])

def sec(p):
    return mv1_of_fun (lambda i: gg(i,p(i)))

def ker(mv):
    return produit_mouvements(mv,inverse(sec(sur(mv))))

# stabilisateur du coin i
def st(i):
    k = (1,1,1)
    m = ((0,0,1),(1,0,0),(0,1,0))
    return produit_matrices(gg(i,k),produit_matrices(m,gg(k,i)))

# pour les conversions des mouvements 'mv1' en mouvements 'mv2'
def l_rtc(m):
    def rtc_aux(k):
        f = fun_of_mv1(k)
        def indexc(i):
            if f(i) == st(i): return 1
            if f(i) == transpose(st(i)): return 2
            return 0
        return tuple(map(indexc, coins))
    return rtc_aux(ker(m))

# rotation totale des coins    
def rtc(mv):
    s = 0
    k = ker(mv)
    for i in coins:
        if k[i] == st(i): s += 1
        elif k[i] == transpose(st(i)): s += 2
    return s % 3

# test d'appartenance d'un mouvement au sous-groupe R des mouvements de Rubik
# par nullité de la rotation totale des coins
def est_dans_R(mv):
    return rtc(mv) == 0

# mouvement défini par des permutations des coins
# et un exposant pour chaque centre
def nouveau_mv1(pc,ec):
    def aux(i):
        x = ec(i)
        if x == 1:
            return st(i)
        if x == 2:
            return transpose(st(i))
        return matrice_scalaire(1)
    k = mv1_of_fun(aux)
    l = mv1_of_fun(lambda i: gg(i,pc(i)))
    return produit_mouvements(k,l)

# exposant aléatoire pour les coins
def ec_r(i):
    return random.randrange(0,3)

# permutation aléatoire des coins
def pc_r():
    coins1 = randomize(coins)
    def perm(i):
        for j in range(len(coins)):
            if coins[j] == i :
                return coins1[j]
    return perm

# mouvement aléatoire
def mv1_r():
    return nouveau_mv1(pc_r(),ec_r)

# mouvement de rubik aléatoire
def mv1_rubik_r():
    def rot_coin(i,n):
        return nouveau_mv1((lambda x: x),(lambda x: (n if x == i else 0)))
    mv = mv1_r()
    n = rtc(mv)
    if n == 0: return mv
    return produit_mouvements(mv,rot_coin((1,1,1), (3 - n)))

# exécution d'une liste de mouvements
def exe1(l):
    for i in range(len(l)):
        l[i]()

# liste des noms d'opérations se succédant du début à la fin d'une chaîne de caractères 's'
def op_names_from_string(s):
    if s == "": return []
    def tete(s):
        t = []
        for c in s: t.append(c)
        l = len(t)
        if l == 0: return []
        if l == 1: return [t[0]]
        if l == 2:
            if t[1] == "0" or t[1] == "'": return t
            return [t[0]]
        match (t[1], t[2]):
            case ["0", "'"]: return t[0:3]
            case ("0", _): return t[0:2]
            case ("'", _): return t[0:2]
            case _: return [t[0]]

    def scinde(s):
        t1 = []
        for c in s: t1.append(c)
        t = tete(s)
        r = t1[len(t):len(t1)]
        return ("".join(t), "".join(r))
 
    (t,r) = scinde(s)
    if r == "":  return [t]
    return [t] + op_names_from_string(r)

# formatage pour caml-light et ocaml
def format_string(op_names_string):
    return "exec " + op_names_string + ";;"

# mouvements de rubik des faces du cube
def rotations_faces():
    def fct(x): # sens horaire
        global context
        global mouvement
        t = produit_vecteur_matrice(x,transpose(context))
        mouvement = produit_mouvements(mouvement,rub(t))
    def fcti(x): # sens inverse
        global context
        global mouvement
        t = produit_vecteur_matrice(x,transpose(context))
        mouvement = produit_mouvements(mouvement,rubi(t))
    adh = tuple(map(lambda i:(lambda:fct(i)),matrice_scalaire(1)))
    adhi = tuple(map(lambda i:(lambda:fcti(i)),matrice_scalaire(1)))
    pgb = tuple(map(lambda i:(lambda:fct(i)),matrice_scalaire(-1)))
    pgbi = tuple(map(lambda i:(lambda:fcti(i)),matrice_scalaire(-1)))
    return (adh,adhi,pgb,pgbi)

# mouvements globaux de rotation du cube
def rotations_cube():
    def rotate(pp):
        global context
        context = produit_matrices(context,pp)
        return 
    adh0 = tuple(map(lambda i:(lambda:rotate(rot(i))),matrice_scalaire(1)))
    adh0i = tuple(map(lambda i:(lambda:rotate(roti(i))),matrice_scalaire(1)))
    return (adh0,adh0i)

# opération associée au nom donné par la chaîne 's'
def op_with_name(s):
    x = rotations_cube()
    ((a0, d0, h0), (a0i, d0i, h0i)) = (x[0],x[1])
    y = rotations_faces()
    ((a, d, h),(ai, di, hi), (p, g, b), (pi, gi, bi)) = (y[0],y[1],y[2],y[3])
    dico = {"a0":a0, "d0":d0, "h0":h0, "a0'":a0i, "d0'":d0i, "h0'":h0i,
                    "a":a, "d": d, "h": h, "a'":ai, "d'":di, "h'":hi,
                    "p":p, "g":g, "b":b, "p'":pi, "g'":gi, "b'":bi}
    return dico[s]

# a, d, h sont les initiales de antérieure, droite, haute
# p, g, b sont les initiales de postérieure, gauche, basse   
def nom_de_face(v):
    match v:
        case (1, 0, 0) : return "a"
        case (0, 1, 0) : return "d"
        case (0, 0, 1) : return "h"
        case (-1, 0, 0) : return "p"
        case (0, -1, 0) : return "g"
        case (0, 0, -1) : return "b"
        case _ : return "" #erreur dans nom_de_face

# on associe au nom de face 's' une ligne de la matrice 'mat' ou de son opposée
# ces lignes sont censées être des triplets (x,y,z) où x,y,z valent 0 ou 1 ou -1 avec deux d'entre-eux valant 0
# considérées comme des normales aux faces. En fait 'mat' est censée un élément du 'groupe_du_cube' 
def associe(mat,s):
    n_plus = tuple(map(nom_de_face,mat))
    n_moins = tuple(map(nom_de_face,produit_matrices(matrice_scalaire(-1),mat)))
    match s:
        case "a": return n_plus[0]
        case "d": return n_plus[1]
        case "h": return n_plus[2]
        case "p": return n_moins[0]
        case "g": return n_moins[1]
        case "b": return n_moins[2]
        case "a'": return n_plus[0] + "'"
        case "d'": return n_plus[1] + "'"
        case "h'": return n_plus[2] + "'"
        case "p'": return n_moins[0] + "'"
        case "g'": return n_moins[1] + "'"
        case "b'": return n_moins[2] + "'"
        case _ : return "" #erreur dans associe

# contours des faces externes (visibles) associées au centre 'c'
def faces(c):
    def face(c,v):
        e = produit_scalaire(v,(1,1,1))
        w = [e,e,e]
        w1 = list(produit_vecteur_matrice(w,rot(v)))
        w2 = list(produit_vecteur_matrice (w1,rot(v)))
        w3 = list(produit_vecteur_matrice(w2,rot(v)))
        l = [w,w1,w2,w3]
        for i in range(len(l)):
            for j in range(3):
                l[i][j] += c[j]
        return l
    d = ((c[0],0,0),(0,c[1],0),(0,0,c[2]))
    return (d, tuple(map(lambda x: face(c,x),d)))

#-----------------------------------------------------------------------------
# COULEURS
#-----------------------------------------------------------------------------

class couleur():
    ROUGE = 1
    ORANGE = 2
    BLEU = 3
    VERT = 4
    JAUNE = 5
    BLANC = 6
    GRIS = 7
    NOIR = 8

def rgb(red,green,blue):
    return f'#{hex(red)[2:]:02}{hex(green)[2:]:02}{hex(blue)[2:]:02}'

def set_color(c):
    global couleur_courante
    couleur_courante = c

def couleur_rvb_de_couleur(c):
    match c:
        case couleur.ROUGE: return rgb(255,0,0)
        case couleur.ORANGE: return rgb(255,165,0)
        case couleur.BLEU: return rgb(0,150,225)
        case couleur.VERT: return rgb(0,255,0)
        case couleur.JAUNE: return rgb(255,255,0)
        case couleur.BLANC: return rgb(255,255,255)
        case couleur.GRIS: return rgb(100,100,100)
        case couleur.NOIR: return rgb(0,0,0)

def couleur_de_face(v):
    match v:
        case (1,0,0): return couleur.ORANGE
        case (-1,0,0): return couleur.ROUGE
        case (0,1,0): return couleur.VERT
        case (0,-1,0): return couleur.BLEU
        case (0,0,1): return couleur.BLANC
        case (0,0,-1): return couleur.JAUNE
        case _ : return couleur.GRIS

def couleur_rvb_de_face(v):
    return couleur_rvb_de_couleur (couleur_de_face(v))

def nom_de_couleur(c):
    match c:
        case couleur.ORANGE : return "ORANGE"
        case couleur.ROUGE : return "ROUGE"
        case couleur.VERT : return "VERT"
        case couleur.BLEU : return "BLEU"
        case couleur.BLANC : return "BLANC"
        case couleur.JAUNE : return "JAUNE"
        case _ : return "GRIS"

def nom_couleur_de_face(v):
    match v:
        case (1,0,0): return "orange"
        case (-1,0,0): return "rouge"
        case (0,1,0): return "verte"
        case (0,-1,0): return "bleue"
        case (0,0,1): return "blanche"
        case (0,0,-1): return "jaune"
        case _ : return "grise"

def couleurs_faces_adh():
    noms = tuple(map(nom_couleur_de_face,transpose(context)))
    return "Résultat avec :\r\nface antérieure " + noms[0] + "\r\nface droite " + noms[1] + "\r\nface haute " + noms[2]

def couleurs_adh(x):
    def eclate(x):
        return ((x[0],0,0),(0,x[1],0),(0,0,x[2]))
    p = transpose(context)
    m = produit_matrices(eclate(x), produit_matrices(p, fun_of_mv1(inverse(mouvement))(produit_vecteur_matrice(x,p))))
    return tuple(map(couleur_de_face,m))

def noms_couleurs_coin_adh():
    noms = tuple(map(nom_de_couleur,couleurs_adh((1,1,1))))
    return "(" + noms[0] + ", " + noms[1] + ", " + noms[2] + ")"

#-----------------------------------------------------------------------------
#résolution du cube par étages
#-----------------------------------------------------------------------------

def resoudre_le_cube():
    global context
    global mouvement
    global mctx
    global lo
    
    ocaml = True
    matr = context
    nbqt = 0
    lo = ""
    mctx = matrice_scalaire(1)
    def pos0(x):
        def pos(mv1,x):
            p = context
            m = fun_of_mv1(produit_mouvements((inverse(cst(p))), produit_mouvements(mouvement, cst(p))))(x)
            return (produit_vecteur_matrice(x,m),m)
        return pos(mouvement,x)

    def pos1(x):
        def pos(mv1,p,x):
            m = fun_of_mv1(produit_mouvements((inverse(cst(p))), produit_mouvements(mv1, cst(p))))(x)
            return (produit_vecteur_matrice(x,m),transpose(m))
        return pos(inverse(mouvement),context,x)

    def exec1(str):
        global mctx
        temp = context
        listop = op_names_from_string(str)
        exe1(tuple(map(op_with_name,listop)))
        temp1 = context
        mctx = produit_matrices(mctx,produit_matrices(transpose(temp),temp1))

    def exec(str):
        nonlocal matr
        global mctx
        global lo
        listop = op_names_from_string(str)
        nonlocal nbqt
        nbqt += len(listop)
        if mctx != matrice_scalaire(1):
            lo += format_string(decomposition(transpose(mctx))) + "\r\n"
            mctx = matrice_scalaire(1)
        exe1(tuple(map(op_with_name,listop)))
        lo += format_string(str) + "\r\n"
        temp = tuple(map(lambda x:(associe(produit_matrices(transpose(context),matr), x)),listop))
        s = ""
        for i in temp: s += i
        print(format_string(s)+'\r')

    def niveau_superieur():
        global mctx
        global lo
        lo += "(* NIVEAU SUPÉRIEUR *)\r\n";
        def descendre_coin():
            (v, m) = pos0((1, 1, 1))
            if (v != (1, 1, 1)) or (m != matrice_scalaire(1)):
                match v:
                    case (-1, 1, 1): exec("p'b'p")
                    case (-1, -1, 1): exec("pbbp'")
                    case (1, -1, 1): exec("gbg'")
                    case (1, 1, 1): exec("aba'b'")
                    case (-1, 1, -1): exec("b'")
                    case(-1, -1, -1): exec("bb")
                    case (1, -1, -1): exec("b")
                    case (1,1, -1): ()
                    case _ : () #erreur dans descendre_coin
        def remonter_coin():
            (v, m) = pos0((1, 1, 1))
            if (v != (1, 1, 1)) or (m != matrice_scalaire(1)):
                match m[2]:
                    case (1,0,0): exec("da'd'a")
                    case (0,1,0): exec("a'dad'")
                    case (0,0,-1): exec("ab'a'bbda'd'a")
                    case _ :() #erreur dans remonter_coin
        for i in range(4):
            t = ["antérieur droit", "postérieur droit", "postérieur gauche", "antérieur gauche"]
            print(f'(* - Mise en place du coin {t[i]} *)\r')
            lo += "(* - Mise en place du coin " + t[i] + " *)\r\n"
            descendre_coin()
            remonter_coin()
            exec1("h0")
        if mctx != matrice_scalaire(1):
            lo += format_string(decomposition(transpose(mctx))) + "\r\n"
            mctx = matrice_scalaire(1)

    def niveau_inferieur():
        global lo
        global mctx
        lo += "\n(* NIVEAU INFÉRIEUR retourné *)\r\n"
        exec1("d0d0")
        #exec1("h0a0a0")
        if mctx != matrice_scalaire(1):
            lo += format_string(decomposition(transpose(mctx))) + "\r\n"
            mctx = matrice_scalaire(1)
        def placer_les_coins():
            global lo
            lo += "(* - Placer les coins *)\r\n"
            def permuter():
                # sens horaire
                exec("g'hdh'ghd'h'")
            def permuterI():
                #sens inverse
                exec("hdh'g'hd'h'g")
            def chercher_un_coin_bien_place():
                i = 0
                while i < 4 and (pos0((1, 1, 1)))[0] != (1, 1, 1):
                    exec1("h0")
                    i += 1
                return i
            j = chercher_un_coin_bien_place()
            if j == 4: # aucun coin bien placé
                (v, _) = pos0((1,1,1))
                match v:
                    case (-1,1,1): exec("ghah'a'g'")
                    case (1,-1,1): exec1("h0'"); exec("ghah'a'g'")
                    case (-1,-1,1): exec("hh")
                    case _ : () #erreur dans placer_les_coins
            else:
                (v, _) = pos0((1,-1,1))
                match v:
                    case (-1,-1,1): permuterI()
                    case (-1,1,1): permuter()
                    case _ : ()
            
        def orienter_les_coins():
            global lo
            lo += "(* - Orienter les coins *)\r\n"
            def config():
                v1 = tuple(map(st,( ( 1, 1, 1 ), ( -1, 1, 1 ), ( -1, -1, 1 ), ( 1, -1, 1 ) )))
                v2 = tuple(map((lambda x: x[1]),(map(pos1,( ( 1, 1, 1 ), ( -1, 1, 1 ), ( -1, -1, 1 ), ( 1, -1, 1 ) )))))
                def aux(x,y):
                    if y[2] == (0,0,1): return 0
                    if produit_vecteur_matrice(y[2],x) == (0,0,1): return 1
                    return -1
                tab = []
                for i in range(len(v1)): tab.append(aux(v1[i],v2[i]))
                return tab
            def n_config(conf):
                i = 0
                for j in range(4):
                    if conf[j] == 0: i += 1
                return i
            def aux():
                conf = config()
                n = n_config(conf)
                match n:
                    case 0:
                        if conf[3] == 1:
                            exec("dhd'hdh'h'd'")
                        else:
                            exec1("h0")
                        aux()
                    case 1:
                        match conf:
                            case [ 0, 1, 1, 1 ] : exec1("h0"); exec("dhd'hdh'h'd'")
                            case [ 1, 0, 1, 1 ] : exec1("h0h0"); exec("dhd'hdh'h'd'")
                            case [ 1, 1, 0, 1 ] : exec1("h0'"); exec("dhd'hdh'h'd'")
                            case [ 1, 1, 1, 0 ] : exec("dhd'hdh'h'd'")
                            case [ 0, -1, -1, -1 ] : exec1 ("h0'"); exec("dhhd'h'dh'd'")
                            case [ -1, 0, -1, -1 ] : exec("dhhd'h'dh'd'")
                            case [ -1, -1, 0, -1 ] : exec1("h0"); exec("dhhd'h'dh'd'")
                            case [ -1, -1, -1, 0 ] : exec1("h0h0"); exec("dhhd'h'dh'd'")
                            case _ : () #erreur 1 dans orienter_les_coins
                        aux()
                    case 2:
                        if conf[3] == -1:
                            exec("dhd'hdh'h'd'")
                        else:
                            exec1("h0")
                        aux()
                    case 4: ()
                    case _ : () #erreur 2 dans orienter_les coins
            aux()
            if pos0((1,1,1))[0] != (1,1,1): exec("hh")
            
        # faire un quart de tour si la permutation des coins est impaire
        # car 'placer les coins' suppose cette permutation paire
        if sign(coins, sur(mouvement)) == -1:
            print("(* - Contrôle de parité *)\r")
            lo += "(* - Contrôle de parité *)\r\n"
            exec("h")
        print("(* - Placement des coins *)\r")
        placer_les_coins ()
        print("(* - Orientation des coins *)\r")
        orienter_les_coins ()

        s = decomposition(transpose(produit_matrices(mctx, produit_matrices(transpose(context), matr))))
        if s != "":
            lo += "(* Ramener le cube à sa position initiale *)\r\n"
            lo += format_string(s) + "\r\n"

    mat = context
    noms_couleurs = noms_couleurs_coin_adh()
    if ocaml:
        print("#use \"interfaces/interface2.oml\";;\r\n")
    else:
        print("include \"interfaces/interface2.ml\";;\r\n")
    print("let mv = mouvement \"mouv2222\";;\r")
    print(f'let ctx = context_adh {noms_couleurs} mv;;\r')
    if ocaml:
        print("Graphics.open_graph \" 612x612\";;\r\n")
    else:
        print("graphics__open_graph \" 612x612\";;\r\n")
    print(f'(* coin Antérieur Droit Haut : {noms_couleurs} *)\r\n')
        
    print("(* RÉSOLUTION SANS ROTATIONS GLOBALES *)\r\n")
    print(f'(* {couleurs_faces_adh()} *)\r\n')
    print("cube.context1.matrice <- ctx.matrice;;\r")
    print("cube.mouvement1.mv1 <- mv;;\r")
    print("dessine_cube ctx mv;;\r\n")
    print("(* NIVEAU SUPÉRIEUR *)\r")
    niveau_superieur()
    print("\n(* NIVEAU INFÉRIEUR *)\r")
    niveau_inferieur()
    context = mat;
    print("\r\n")
        
    print("(* RÉSOLUTION AVEC ROTATIONS GLOBALES *)\r\n")
    print("cube.context1.matrice <- ctx.matrice;;\r")
    print("cube.mouvement1.mv1 <- mv;;\r")
    print("dessine_cube ctx mv;;\n\r")
    print(f'{lo}')
    print(f'nombre de quarts de tour: {nbqt}\r\n')

    return nbqt

def basculer():
    global mouvement
    global mouvement_duplique
    mouvement_duplique=mouvement
    p = context
    x = produit_vecteur_matrice((1,1,1),transpose(p))
    r = fun_of_mv1(mouvement)(x)
    mouvement = produit_mouvements(mouvement,cst(transpose(r)))

#-----------------------------------------------------------------------------
# MOUVEMENTS 2, CODAGE ET DÉCODAGE, solutions optimales
#-----------------------------------------------------------------------------

# construction d'une table de transitions

def transitions(n,e,op,code, decode,lg,chr):
    t = []
    for i in range(n):
        t.append(" ")
    t[code(e)] ="$"
    def suivants(i1,t1,i2,t2):
        i2 = 0
        def ajouter(y,c):
            nonlocal i2
            if t[y] == " ":
                t[y] = c
                t2[i2] = y
                i2 += 1
        def f(y):
            x = decode(y)
            for gen in lg:
                ajouter(code(op(x,gen)),chr(gen))
        for i in range(i1):
            f(t1[i])
        return (i1,i2)
    total = 1
    def aux(i1,t1,i2,t2,i):
        nonlocal total
        tm = time.time()
        (i1,i2) = suivants(i1,t1,i2,t2) 
        if i2 > 0:
            total += i2
            temps = time.time() - tm
            print(f'à distance {i}, {i2} éléments ajoutés en {temps:.3f} s')
            aux(i2,t2,i1,t1,i+1)
    i1 = 1
    i2 = 0
    t1 = []
    t2 = []
    for i in range(n):
        t1.append(" ")
    for i in range(n):
        t2.append(" ")
    t1[0] = code(e)
    print("Au départ 1 seul élément : le neutre")
    tm = time.time()
    aux(i1,t1,i2,t2,1)
    temps = time.time() - tm
    print(f'En tout {total} éléments obtenus en {temps:.3f} s\n')
    return t

#numérotation des coins
def num_of_coin(c):
    i = 0
    while c != coins[i]:
        i+=1
    return i

#conversions des mouvements 'mv1' en mouvements 'mv2'
def mv2_of_mv1(mv1):
    rc = l_rtc(mv1)
    pc = tuple(map(num_of_coin,map(sur(mv1),coins)))
    return (rc,pc)

#insère l'entier a à l'emplacement numéro i dans une copie y de la permutation x et renvoie y
def insere(a,i,x):
    y = []
    for j in range(i):
        y.append(x[j])
    y.append(a)
    for j in range(i,len(x)):
        y.append(x[j])
    return y

def permutations(n):
    if n > 1:
        l = []
        perm = permutations(n-1)
        for i in range(n): 
            l1 = list(map(lambda x: insere(n-1,i,x),perm))[::-1]
            l.extend(l1)
        return l[::-1]
    return [[0]]

# les seules permutations des 8 numéros de coins qui nous intéressent sont celles qui laissent fixe 0 i.e. le numéro du minicube de centre (1,1,1) considéré ici comme fixe
def filter():
    res = []
    for i in permutations(8):
        if i[0] == 0:
            res.append(i)
    return res
tab_perm_coins = filter()

# récupération rapide du code d'une permutation i.e. de son indice dans 'tab_perm_coins'
# un peu au détriment de la mémoire
def base8(c):
    return c[1]+c[2]*8+c[3]*64+c[4]*512+c[5]*4096+c[6]*32768+c[7]*262144

tableau_pc = []
for i in range(base8([0,1,2,3,4,5,6,7]) + 1):
    tableau_pc.append(0)
for i in range(len(tab_perm_coins)):
    tableau_pc[base8(tab_perm_coins[i])] = i

code_perm_coins = lambda x : tableau_pc[base8(x)]

# codage des rotations des coins
def tab_rot_coins_aux():
    def chiffres(num):
        rc = [0,0,0,0,0,0,0,0]
        n = num
        for i in range(2,8):
            rc[i] = n % 3
            n = n // 3
        x = 0
        for j in range(2,8):
            x += rc[i]
        x %= 3
        if x == 0:
            rc[1] = 0
        else:
            rc[1] = 3 - x
        return rc 
    
    t = []
    for i in range(729):
        t.append(chiffres(i))
    return t

tab_rot_coins = tab_rot_coins_aux()

def base4(c):
    return c[2] + (c[3] << 2) + (c[4] << 4) + (c[5] << 6) + (c[6] << 8) + (c[7] << 10)

tableau_rc = []
for i in range(base4([0,1,2,3,4,5,6,7]) + 1):
    tableau_rc.append(i)
for i in range(len(tab_rot_coins)):
    tableau_rc[base4(tab_rot_coins[i])] = i

code_rot_coins = lambda x : tableau_rc[base4(x)]

# codage des mouvements mv2  laissant fixe le minicube numéroté 0
def code2(mv2):
    return code_rot_coins(mv2[0]) + 729 * code_perm_coins(mv2[1])

# décodage associé
def decode2(num):
    (rc,pc) = (num % 729, num // 729)
    return (tuple(tab_rot_coins[rc]),tuple(tab_perm_coins[pc]))

#-----------------------------------------------------------------------------
# groupe des mouvements mouv2
#-----------------------------------------------------------------------------

# loi de groupe pour les mouvements 'mv2'
def produit_mouvements2(m,m1):
    nc = len(m[0])
    npc = len(m[1])
    rc = []
    pc = []
    for i in range(nc):
        rc.append(0)
    for i in range(npc):
        pc.append(0)
    for i in range(nc):
        rc[i] = (m[0][i] + m1[0][m[1][i]]) % 3
    for i in range(npc):
        pc[i] = m1[1][m[1][i]]
    return (tuple(rc),tuple(pc))

# élément neutre
e2 = mv2_of_mv1(e)

# mouvements de Rubik élémentaires par quarts de tour des faces gauche, basse et postérieure
rg = mv2_of_mv1(rub((0,-1,0)))
rgi = mv2_of_mv1(rubi((0,-1,0)))
rb = mv2_of_mv1(rub((0,0,-1)))
rbi = mv2_of_mv1(rubi((0,0,-1)))
rp = mv2_of_mv1(rub((-1,0,0)))
rpi = mv2_of_mv1(rubi((-1,0,0)))

dg = produit_mouvements2(rg,rg)
db = produit_mouvements2 (rb,rb)
dp = produit_mouvements2(rp,rp)

# listes de générateurs pour construire la table de transitions du groupe
liste_gen_qdt = [rgi, rbi, rpi, rg, rb, rp]
liste_gen_dt_qdt = [dg, db, dp, rgi, rbi, rpi, rg, rb, rp]

# fonction abrégeant chaque générateur en un caractère dans la table de transitions
def chr(gen):
    dico = {code2(rg):"g", code2(rgi):"G", code2(rb):"b", code2(rbi):"B", code2(rp):"p", code2(rpi):"P", code2(dg):"D", code2(db):"H", code2(dp):"A"}
    return dico[code2(gen)]

# fonction donnant pour chaque caractère l'inverse du générateur représenté
def gen(chr):
    dico = {"g":rgi, "G":rg, "b":rbi, "B":rb, "p":rpi, "P":rp, "D":dg, "H":db, "A":dp}
    return dico[chr]

# écriture dans le fichier 'nom' d'un tableau 'tab' de caractères
def ecrire_table(nom,tab):
    with open(nom,'w',encoding="utf-8") as f:
        f.write("".join(tab))

# lecture du fichier précédent
def lire_table(nom):
    res = ""
    if os.path.exists(nom):
        with open(nom,'r',encoding="utf-8") as f:
            res = f.read()
    return tuple(res)

def tabchar(nom,message,liste_gen):
    tc = lire_table(nom)
    if len(tc) != 0:
        return tc
    print(message+"\n")
    tc = transitions(3674160, e2, produit_mouvements2, code2, decode2, liste_gen, chr)
    ecrire_table(nom, tc)
    return tc

# construction  et enregistrement des tables de transitions
# avec pour générateurs les quarts de tour
# puis les demi-tours et quarts de tour,
# ou lecture des tables de transitions enregistrées
tabchar_qdt = tabchar("tab_qdt", "Calcul d'une table de transitions par quarts de tour:", liste_gen_qdt)
tabchar_dt_qdt = tabchar("tab_dt_qdt", "Calcul d'une table de transitions par demi-tours et quarts de tour:", liste_gen_dt_qdt)

# retrouver à partir du code d'un mouvement une séquence minimale de Rubik qui le résout;
# le code i donne en t[i] une manoeuvre de Rubik vers un mouvement plus proche du neutre
# on reprend avec le code j de ce mouvement plus proche et t[j] etc...
def solution(tabchar,x):
    c = tabchar[x]
    if c == "$":
        return ""
    dico = {"p":"p'", "g":"g'", "b":"b'", "P":"p", "G":"g", "B":"b", "A":"p2", "D":"g2", "H":"b2"}
    return dico[c] + solution(tabchar, code2(produit_mouvements2(decode2(x),gen(c))))

def solution_qdt(code):
    return solution(tabchar_qdt,code)
def solution_dt_qdt(code):
    return solution(tabchar_dt_qdt,code)

def solutions():
    p = context
    mv0 = mouvement
    mv =  produit_mouvements(inverse(cst(p)),produit_mouvements(mv0,cst(p)))
    r = fun_of_mv1(mv)(( 1, 1, 1 ))
    if r == matrice_scalaire(1):
        cd2 = code2(mv2_of_mv1(mv))
        return (solution_qdt(cd2), solution_dt_qdt(cd2))
    return ("","")

#-----------------------------------------------------------------------------
# composer...
#-----------------------------------------------------------------------------

mvi = cst(matrice_scalaire(0))

def est_vide_a_gauche(v):
    return mvi[v] != ((0,0,0),(0,0,0),(0,0,0))

def est_vide_a_droite(v):
    for i in coins:
        if produit_vecteur_matrice(i,mvi[i]) == v:
            return False
    return True

def tout_vide_a_gauche():
    for i in coins:
        if not est_vide_a_gauche(i): return False
    return True

def est_dans_poly(p,x): # (x intérieur au quadrilatère supposé convexe limité par p)
    def det(v1,v2):
        return v1[0] * v2[1] - v1[1] * v2[0]
    p0 = (p[0][0] - x[0],p[0][1]-x[1])
    p1 = (p[1][0] - x[0],p[1][1]-x[1])
    p2 = (p[2][0] - x[0],p[2][1]-x[1])
    p3 = (p[3][0] - x[0],p[3][1]-x[1])
    return  det(p0,p1) * det(p1,p2) > 0 and det(p1,p2) * det(p2,p3) > 0 and det(p2,p3) * det(p3,p0) > 0

#-----------------------------------------------------------------------------
# graphisme
#-----------------------------------------------------------------------------

def proj(x,y,z):
    c = math.sqrt(6.0)/2.0
    return (c * (y - x) / math.sqrt(2.0) , c * (-(x + y) + 2.0 * z) / math.sqrt(6.0))

class Plan:
    def __init__(self,ox,oy,ux,uy, largeur,hauteur,root):
        self.ox = ox
        self.oy = oy
        self.ux = ux
        self.uy = uy
        self.orig = (0,0)
        self.largeur = largeur
        self.hauteur = hauteur
        self.canvas = Canvas(root,bg="white",width=largeur,height=hauteur)
        self.boutons1 = []
        self.boutons11 = []
        self.lines = dict()
        self.rectangles = dict()
        self.polygons = dict()
        self.texts = dict()
        self.res_gauche = None

    def xx(self,v,pt3):
        (x,y,z) = (pt3[0], pt3[1], pt3[2])
        def aux():
            if produit_scalaire(v , [1,1,1]) == 1: return (x, y, z)
            match (v[0],v[1],v[2]):
                case (_,0,0) : return (x - 7.0, y, z)
                case (0,_,0) : return (x, y - 7.0, z)
                case _ : return (x, y, z - 7.0)
        (x1, y1, z1) = aux()
        (a, _) = proj(x1, y1, z1)
        return round(self.ox + a * self.ux)

    def yy(self,v,pt3):
        (x, y, z) = (pt3[0], pt3[1], pt3[2])
        def aux():
            if produit_scalaire(v , (1,1,1)) == 1: return (x, y, z)
            match (v[0],v[1],v[2]):
                case (_,0,0) : return (x - 7.0, y, z)
                case (0,_,0) : return (x, y - 7.0, z)
                case _ : return (x, y, z - 7.0)
        (x1, y1, z1) = aux()
        (_, b) = proj(x1, y1, z1)
        return self.hauteur - round(self.oy + b * self.uy)

    def moveto(self,pt):
        self.orig = pt

    def lineto(self,pt):
        if (self.orig,pt) in self.lines:
            line = self.lines[(self.orig,pt)]
            self.lines.pop((self.orig,pt))
            self.canvas.delete(line)       
        line = self.canvas.create_line(self.orig,pt,fill = couleur_courante)
        self.lines[(self.orig,pt)] = line
        self.orig = pt

    def fill_rect(self,ox,oy,largeur,hauteur):
        rectangle = None
        if (ox,oy,largeur,hauteur) in self.rectangles:
            rectangle = self.rectangles[(ox,oy,largeur,hauteur)]
            self.rectangles.pop((ox,oy,largeur,hauteur))
            self.canvas.delete(rectangle)
        rectangle = self.canvas.create_rectangle(ox,oy,ox+largeur,oy+hauteur,fill=couleur_courante)
        self.rectangles[(ox,oy,largeur,hauteur)] = rectangle

    def fill_poly(self,poly):
        if poly in self.polygons:
            polygon = self.polygons[poly]
            self.polygons.pop(poly)
            self.canvas.delete(polygon)
            polygon = self.canvas.create_polygon(poly,fill = couleur_courante)
            self.polygons[poly] = polygon
        else:
            polygon = self.canvas.create_polygon(poly,fill = couleur_courante)
            self.polygons[poly] = polygon

    def drawPoly(self,poly):
        (x,y) = poly[0]
        self.moveto((x,y))
        for i in range(1,len(poly)):
            (x,y) = poly[i]
            self.lineto((x,y))
        (x,y) = poly[0]
        self.lineto((x,y))

    def draw(self,a,b):
        set_color(b)
        self.fill_poly(a)
        set_color(rgb(0,0,0))
        self.drawPoly(a)

    def draw_string(self,s):
        cle = self.orig
        if cle in self.texts:
            iden = self.texts[cle]
            self.texts.pop(cle)
            self.canvas.delete(iden)
        iden = self.canvas.create_text(cle,anchor='w',fill=couleur_courante, text=s)
        self.texts[cle] = iden
  
    def text_size(self,s):
        def ret(text):
            bbox = self.canvas.bbox(text)
            (l,h) = (bbox[2]-bbox[0],bbox[3]-bbox[1])
            self.canvas.delete(bbox)
            return (l,h)            
        if s in self.texts:
            text = self.texts[s]
            return ret(text)
        text = self.canvas.create_text(-10, -10, text=s)
        self.texts[s] = text
        return ret(text)

    def affiche1(self,mat,context,centre):
        p = context
        (d,f) = faces(centre)
        m = mat
        for i in range(len(d)):
            v = produit_vecteur_matrice(d[i],m)
            g = tuple(map(lambda x: produit_vecteur_matrice(x,m),f[i]))
            self.draw(tuple(map(lambda pt:(self.xx(v,pt), self.yy(v,pt)),g)),couleur_rvb_de_face(produit_vecteur_matrice(d[i],transpose(p))))

    def affiche_mouvement(self,context,mv):
        p = context
        for x in coins:
            self.affiche1(produit_matrices(transpose(p), produit_matrices(mv[x],p)), context, produit_vecteur_matrice(x,p))

    # affichage des solutions minimales d'un mouvement basculé
    def dessine_solutions(self):
    # ci-dessous 202 = 10 + 32 * 6 et 30 est la hauteur commune des boutons et des champs
    # 32 est la largeur des boutons, 10 est un décalage pour draw_string 
        (solution_qdt, solution_qdt_dt) = solutions()
        poly1=((10,485),(202,485),(202,455),(10,455))
        self.draw(poly1,rgb(255,255,255))
        self.moveto((20,470))
        self.draw_string(solution_qdt_dt)
        poly2=((10,515),(202,515),(202,485),(10,485))
        self.draw(poly2,rgb(255,255,255))
        self.moveto((20,500))
        self.draw_string(solution_qdt)

    #-----------------------------------------------------------------------------
    # graphisme fenêtre composer
    # gestion souris de la composition d'un cube par tranfert des minicubes de
    # gauche à droite
    #-----------------------------------------------------------------------------

    def choix_a_gauche(self,mousex,mousey):
        if mousex >= 300 : return
        for i in coins:
            (d, f) = faces(i)
            for j in range(3):
                face = tuple(map(lambda pt: (self.xx(d[j], pt), self.yy(d[j], pt)),f[j]))
                if est_dans_poly(face, (mousex, mousey)):
                    self.res_gauche = (i,d[j])

    def choix_a_droite(self,mousex,mousey):
        global mvi
        if mousex < self.largeur // 2 : return
        self.ox += self.largeur // 2
        for i in coins:
            (d, f) = faces(i)
            for j in range(3):
                face = tuple(map(lambda pt: (self.xx(d[j], pt), self.yy(d[j], pt)),f[j]))
                if est_dans_poly(face, (mousex, mousey)):
                    (w1,w2) = (i,d[j])
                    p = context
                    if est_vide_a_droite(produit_vecteur_matrice(w1,transpose(p))):
                        if self.res_gauche != None:           
                            (v1,v2) = self.res_gauche
                            mat = [m for m in groupe_du_cube if produit_vecteur_matrice(v1,m)==w1 and produit_vecteur_matrice(v2,m)==w2]
                            mvi[produit_vecteur_matrice(v1,transpose(p))] = produit_matrices(p,produit_matrices(mat[0],transpose(p)))
                    else:
                        for j in coins:
                            if produit_vecteur_matrice(j,produit_matrices(mvi[j],p)) == w1 :
                                mvi[j] = ((0,0,0),(0,0,0),(0,0,0))
                                break
                    self.res_gauche = None
        self.ox -=self.largeur // 2

    def griser(self,offset,centre):
        (d,f) = faces(centre)
        for i in range(len(d)):
            v = d[i]
            self.ox += offset
            self.draw(tuple(map(lambda pt: (self.xx(v,pt),self.yy(v,pt)),f[i])),couleur_rvb_de_couleur(couleur.GRIS))
            self.ox -= offset

    def affiche_mvi(self,context,mvi):
        p = context
        for i in coins:
            self.griser(self.largeur//2,i)
        for i in coins:
            if est_vide_a_gauche(i):
                self.griser(0,produit_vecteur_matrice(i,p))
            else:
                self.affiche1(matrice_scalaire(1),p,produit_vecteur_matrice(i,p))
            if mvi[i] != ((0,0,0),(0,0,0),(0,0,0)):
                self.ox += self.largeur//2
                self.affiche1(produit_matrices(transpose(p), produit_matrices(mvi[i],p)),context,produit_vecteur_matrice(i,p))
                self.ox -= self.largeur//2

    #-------------------------------------------------------------------------
    # boutons
    #-------------------------------------------------------------------------
          
    class bouton:
        def __init__(self,titre,orx,ory,largeur,hauteur,couleur,action):
            self.titre = titre
            self.orx = orx
            self.ory = ory
            self.largeur = largeur
            self.hauteur = hauteur
            self.couleur = couleur
            self.action = action

    def dessine_bouton(self,b):
        set_color(couleur_rvb_de_couleur(b.couleur))
        self.fill_rect(b.orx, b.ory, b.largeur, b.hauteur)
        (x,y) = self.text_size(b.titre)
        self.moveto((b.orx + 603//36 - 3, b.ory + 15))
        set_color(rgb(0,0,0))
        self.draw_string(b.titre)
        p = [(b.orx, b.ory), (b.orx + b.largeur, b.ory), (b.orx + b.largeur, b.ory + b.hauteur), (b.orx, b.ory + b.hauteur), (b.orx, b.ory)]
        self.drawPoly(p)

    def gestion_bouton(self,b,mousex,mousey):
        if b.orx <= mousex and mousex <= b.orx + b.largeur and b.ory <= mousey and mousey <= b.ory + b.hauteur:
            b.action()
            self.affiche_mouvement(context, mouvement)
            self.dessine_bouton(b)

    # rangée des boutons en bas de fenêtre manipulant le cube
    def cree_boutons1(self):
        titres = ["A", "A'", "H", "H'", "D", "D'", "a", "a'", "h", "h'", "d", "d'", "p", "p'", "b", "b'", "g", "g'"]
        x = rotations_cube()
        ((a0, d0, h0), (a0i, d0i, h0i)) = (x[0],x[1])
        y = rotations_faces()
        ((a, d, h),(ai, di, hi), (p, g, b), (pi, gi, bi)) = (y[0],y[1],y[2],y[3])
        v = (a0, a0i, h0, h0i, d0, d0i, a, ai, h, hi, d, di, p, pi, b, bi, g, gi)
        n = len(titres)
        for i in range(n):
            self.boutons1.append(self.bouton(titres[i], i * 32 + 10, hauteurA - 55,32,30,couleur.BLANC, lambda j=i: (v[j](),print(titres[j], end = "", flush = True))))

    def dessine_boutons1(self):
        def couleur_titre(titre):
            def face(titre):
                match(titre):
                    case "A" | "A'" | "a" | "a'" : return (1,0,0)
                    case "D" | "D'" | "d" | "d'": return (0,1,0)
                    case "H" | "H'" | "h" | "h'": return (0,0,1)
                    case "p" | "p'": return (-1,0,0)
                    case "g" | "g'": return (0,-1,0)
                    case "b" | "b'": return (0,0,-1)
                    case _: return (0,0,0) #erreur dans face dans dessine_boutons1
            return couleur_de_face(produit_vecteur_matrice(face(titre),transpose(context)))
        for i in range(len(self.boutons1)):
            self.boutons1[i].couleur = couleur_titre(self.boutons1[i].titre)
            self.dessine_bouton(self.boutons1[i])

    def gestion_boutons1(self,mousex,mousey):
        for i in range(len(self.boutons1)):
            self.gestion_bouton(self.boutons1[i], mousex, mousey)
            if i < 6: self.dessine_boutons1()

    def cree_boutons11(self):
        titres = ["A", "A'", "H", "H'", "D", "D'"]
        def ff(x):
            global mouvement
            mouvement = produit_mouvements(mouvement,cst(produit_matrices(rot(x),transpose(context))))
        def ffi(x):
            global mouvement
            mouvement = produit_mouvements(mouvement,cst(produit_matrices(roti(x),transpose(context))))
        v = [lambda:ff((1,0,0)),lambda:ffi((1,0,0)),lambda:ff((0,0,1)),lambda:ffi((0,0,1)),lambda:ff((0,1,0)),lambda:ffi((0,1,0))]    
        n = len(titres)
        for i in range(n):
            self.boutons11.append(self.bouton(titres[i], i * 32 + 10, hauteurA - 85,32,30,couleur.BLANC, v[i]))

    def dessine_boutons11(self):
        for i in range(len(self.boutons11)):
            self.boutons11[i].couleur = couleur.BLANC
            self.dessine_bouton(self.boutons11[i])

    def gestion_boutons11(self,mousex,mousey):
        for i in range(len(self.boutons11)):
            self.gestion_bouton(self.boutons11[i], mousex, mousey)

    def bouton2(self,titre,orx,ory,couleur,action):
        (largeur,hauteur) = self.text_size(titre)
        hauteur = 30
        return self.bouton(titre,orx,ory,largeur,hauteur,couleur,action)

    def dessine_bouton2(self,b):
        set_color(couleur_rvb_de_couleur(b.couleur))
        self.fill_rect(b.orx, b.ory, b.largeur+5,b.hauteur)
        self.moveto((b.orx+5,b.ory + 15))
        set_color(rgb(0,0,0))
        self.draw_string(b.titre)

    def gestion_bouton2(self,b,mousex,mousey):
        if b.orx <= mousex and mousex <= b.orx + b.largeur and b.ory <= mousey and mousey <= b.ory + b.hauteur:
            b.action()

###############################################################################################
# planB (fenêtre 'Composer un mouvement')

def action_OK():
    global window
    global mouvement
    if tout_vide_a_gauche():
        if rtc(mvi) == 0:
            mouvement = mvi
            enregistrer(mouvement, 'mouv2222')
            print('mouvement de Rubik enregistré dans mouv2222 !\r\n')
        else:
            print(f'rotation totale: {rtc(mvi)}\r')
            print('cette rotation totale devrait être nulle\r\n')
    else:        
        print('composition de mouvement inachevée !\r')
    window.destroy()
    window = None

def composer():
    global window

    if window != None:
        window.focus_force()
        return

    largeurB=600
    hauteurB=300
    ux = 10  
    uy = 10
    ox = 150  #(largeurB//4)
    oy = 150  #(hauteurB//2)
     
    window = Toplevel()
    window.title("Composer un mouvement")
    window.geometry(f'{largeurB}x{hauteurB}')
    window.protocol("WM_DELETE_WINDOW", action_OK)

    planB = Plan(ox,oy,ux,uy,largeurB,hauteurB,window)
    planB.canvas.bind("<Button-1>", show_clickA)
    planB.canvas.pack()

    set_color(rgb(0,0,0))
    planB.moveto((largeurB // 2, 0))
    planB.lineto((largeurB // 2, hauteurB))
    planB.affiche_mvi(context,mvi)

    bouton_OK=planB.bouton2('OK',530,20,couleur.JAUNE,action_OK)
    planB.dessine_bouton2(bouton_OK)

    def handle_clickB(event):
        mousex = event.x
        mousey = event.y
        planB.choix_a_gauche(mousex,mousey)
        planB.choix_a_droite(mousex,mousey)
        planB.affiche_mvi(context,mvi)
        planB.gestion_bouton2(bouton_OK,mousex,mousey)

    planB.canvas.bind("<Button-1>", handle_clickB)

###############################################################################################
#planA (fenêtre principale 'Résoudre le cube')
window = None
        
def action_resoudre():
    global mouvement_duplique
    mouvement_duplique=mouvement
    enregistrer(mouvement,"mouv2222")
    resoudre_le_cube()

def action_melanger():
    global mouvement
    global mouvement_duplique
    mouvement_duplique=mouvement
    mouvement=mv1_rubik_r()

def action_restituer():
    global mouvement
    mouvement=mouvement_duplique

def action_composer():
    composer()

def action_basculer():
    basculer()

def action_quitter():
    root.destroy()

context = matrice_scalaire(1)
if os.path.exists("mouv2222"):
    mouvement = lire_mouvement("mouv2222")
else:
    mouvement = e
mouvement_duplique = mouvement

largeurA = 600
hauteurA = 600

from tkinter import *
root = Tk()
root.title("Résoudre le cube")
root.geometry(f'{largeurA}x{hauteurA}')
planA = Plan(largeurA//2,hauteurA/2,20,20,largeurA,hauteurA,root)
planA.canvas.pack()

planA.affiche_mouvement(context, mouvement)
planA.dessine_solutions()

bouton_resoudre=planA.bouton2('Résoudre',20,20,couleur.JAUNE,action_resoudre)
planA.dessine_bouton2(bouton_resoudre)
bouton_melanger=planA.bouton2('Mélanger',250,20,couleur.JAUNE,action_melanger)
planA.dessine_bouton2(bouton_melanger)
bouton_restituer=planA.bouton2('Restituer',250,50,couleur.JAUNE,action_restituer)
planA.dessine_bouton2(bouton_restituer)
bouton_composer=planA.bouton2('Composer',250,80,couleur.JAUNE,action_composer)
planA.dessine_bouton2(bouton_composer)
bouton_basculer=planA.bouton2('Basculer',250,110,couleur.JAUNE,action_basculer)
planA.dessine_bouton2(bouton_basculer)
bouton_quitter=planA.bouton2('Quitter',530,20,couleur.JAUNE,action_quitter)
planA.dessine_bouton2(bouton_quitter)

planA.cree_boutons1()
planA.dessine_boutons1()
planA.cree_boutons11()
planA.dessine_boutons11()

def show_clickA(event):
    planA.gestion_boutons1(event.x,event.y)
#    planA.dessine_boutons1()
    planA.gestion_boutons11(event.x,event.y)
    planA.gestion_bouton2(bouton_resoudre,event.x,event.y)
    planA.gestion_bouton2(bouton_melanger,event.x,event.y)
    planA.gestion_bouton2(bouton_restituer,event.x,event.y)
    planA.gestion_bouton2(bouton_composer,event.x,event.y)
    planA.gestion_bouton2(bouton_basculer,event.x,event.y)
    planA.dessine_solutions()
    planA.affiche_mouvement(context, mouvement)
    planA.gestion_bouton2(bouton_quitter,event.x,event.y)

planA.canvas.bind("<Button-1>", show_clickA)

root.mainloop()

