POUR LE BINÔME ADULTE
Lignes avec - : retirées. Lignes avec + : ajoutées.
Les autres donnent le contexte. Ce fichier ne se lance pas.

--- rubiks_etape_04.py
+++ rubiks_etape_05.py
@@ -1,5 +1,5 @@
-"""Rubik's Cube — étape 04.
-Lancement : py rubiks_etape_04.py
+"""Rubik's Cube — étape 05.
+Lancement : py rubiks_etape_05.py
 Chaque fichier fonctionne seul. Lis aussi le carnet Word.
 """
 
@@ -20,6 +20,62 @@
                    for ligne in range(3)] for face in COULEURS}
 
 
+# Chaque groupe décrit quatre cases voisines qui échangent leur contenu.
+# Dans un groupe : la première va dans la deuxième, etc.
+# La dernière revient dans la première. Il y a trois groupes par face.
+VOISINS = {
+    "U": [
+        [('F', 0, 0), ('L', 0, 0), ('B', 0, 0), ('R', 0, 0)],
+        [('F', 0, 1), ('L', 0, 1), ('B', 0, 1), ('R', 0, 1)],
+        [('F', 0, 2), ('L', 0, 2), ('B', 0, 2), ('R', 0, 2)],
+    ],
+    "D": [
+        [('F', 2, 0), ('R', 2, 0), ('B', 2, 0), ('L', 2, 0)],
+        [('F', 2, 1), ('R', 2, 1), ('B', 2, 1), ('L', 2, 1)],
+        [('F', 2, 2), ('R', 2, 2), ('B', 2, 2), ('L', 2, 2)],
+    ],
+    "F": [
+        [('U', 2, 0), ('R', 0, 0), ('D', 0, 2), ('L', 2, 2)],
+        [('U', 2, 1), ('R', 1, 0), ('D', 0, 1), ('L', 1, 2)],
+        [('U', 2, 2), ('R', 2, 0), ('D', 0, 0), ('L', 0, 2)],
+    ],
+    "B": [
+        [('U', 0, 0), ('L', 2, 0), ('D', 2, 2), ('R', 0, 2)],
+        [('U', 0, 1), ('L', 1, 0), ('D', 2, 1), ('R', 1, 2)],
+        [('U', 0, 2), ('L', 0, 0), ('D', 2, 0), ('R', 2, 2)],
+    ],
+    "R": [
+        [('U', 0, 2), ('B', 2, 0), ('D', 0, 2), ('F', 0, 2)],
+        [('U', 1, 2), ('B', 1, 0), ('D', 1, 2), ('F', 1, 2)],
+        [('U', 2, 2), ('B', 0, 0), ('D', 2, 2), ('F', 2, 2)],
+    ],
+    "L": [
+        [('U', 0, 0), ('F', 0, 0), ('D', 0, 0), ('B', 2, 2)],
+        [('U', 1, 0), ('F', 1, 0), ('D', 1, 0), ('B', 1, 2)],
+        [('U', 2, 0), ('F', 2, 0), ('D', 2, 0), ('B', 0, 2)],
+    ],
+}
+
+
+def tourner(cube, face, inverse=False):
+    # Un tour inverse équivaut à trois tours dans le sens horaire.
+    repetitions = 3 if inverse else 1
+    for repetition in range(repetitions):
+        ancienne_face = cube[face]
+        cube[face] = [[ancienne_face[2 - colonne][ligne]
+                       for colonne in range(3)] for ligne in range(3)]
+        for groupe in VOISINS[face]:
+            valeurs = [cube[f][l][c] for f, l, c in groupe]
+            for indice, (f, l, c) in enumerate(groupe):
+                cube[f][l][c] = valeurs[indice - 1]
+
+
+def est_resolu(cube):
+    # Les centres restent fixes : chaque case doit retrouver sa face.
+    return all(case == face for face, lignes in cube.items()
+               for ligne in lignes for case in ligne)
+
+
 # Réglages à personnaliser.
 TAILLE_CASE = 44
 ECART = 4
@@ -38,35 +94,33 @@
 def main():
     pygame.init()
     fenetre = pygame.display.set_mode((1000, 700))
-    pygame.display.set_caption("Mon Rubik's Cube - Etape 4")
+    pygame.display.set_caption("Mon Rubik's Cube - Etape 5")
     horloge = pygame.time.Clock()
     police = pygame.font.Font(None, 25)
     petite = pygame.font.Font(None, 21)
     cube = creer_cube()
+    # Un bouton contient un rectangle, un texte et une action.
+    boutons = []
+    for indice, face in enumerate("F"):
+        boutons.append((pygame.Rect(685, 110 + indice * 47, 110, 37),
+                        face, (face, False)))
     en_marche = True
     while en_marche:
+        action = None
         for evenement in pygame.event.get():
             if evenement.type == pygame.QUIT:
                 en_marche = False
+            action = None
             if evenement.type == pygame.MOUSEBUTTONDOWN and evenement.button == 1:
-                # Laboratoire de couleurs seulement : ce clic ne tourne pas le cube.
-                for face in POSITIONS:
-                    x, y = origine(face)
-                    for ligne in range(3):
-                        for colonne in range(3):
-                            case = pygame.Rect(x + colonne * TAILLE_CASE,
-                                               y + ligne * TAILLE_CASE,
-                                               TAILLE_CASE - ECART, TAILLE_CASE - ECART)
-                            if case.collidepoint(evenement.pos):
-                                palette = list(COULEURS)
-                                actuelle = cube[face][ligne][colonne]
-                                suivante = (palette.index(actuelle) + 1) % len(palette)
-                                cube[face][ligne][colonne] = palette[suivante]
-            if evenement.type == pygame.KEYDOWN and evenement.key == pygame.K_DELETE:
-                cube = creer_cube()
+                for rectangle, texte, commande in boutons:
+                    if rectangle.collidepoint(evenement.pos):
+                        action = commande
+            if isinstance(action, tuple):
+                face, inverse = action
+                tourner(cube, face, inverse)
 
         fenetre.fill(FOND)
-        fenetre.blit(police.render('Le laboratoire des couleurs', True, (245, 245, 245)), (24, 20))
+        fenetre.blit(police.render('Mon premier vrai mouvement', True, (245, 245, 245)), (24, 20))
         for face in POSITIONS:
             x, y = origine(face)
             fenetre.blit(petite.render(face, True, (240, 240, 240)), (x, y - 20))
@@ -79,10 +133,11 @@
                     pygame.draw.rect(fenetre, couleur, rectangle)
         legende = "U haut   D bas   F devant   B derrière   L gauche   R droite"
         fenetre.blit(petite.render(legende, True, (230, 230, 235)), (24, 590))
-        aide = "Clique une case pour changer sa couleur. Suppr : recommencer."
-        fenetre.blit(petite.render(aide, True, (240, 240, 240)), (24, 630))
-        avertissement = "Ces couleurs libres ne représentent pas forcément un cube réalisable."
-        fenetre.blit(petite.render(avertissement, True, (255, 210, 90)), (24, 655))
+        for rectangle, texte, commande in boutons:
+            pygame.draw.rect(fenetre, (65, 75, 100), rectangle, border_radius=6)
+            etiquette = police.render(texte, True, (255, 255, 255))
+            fenetre.blit(etiquette, etiquette.get_rect(center=rectangle.center))
+        fenetre.blit(petite.render("Clique F quatre fois : retour au départ !", True, (240,240,240)), (24,630))
         pygame.display.flip()
         horloge.tick(60)
     pygame.quit()
