Jeu de Rotations

Vous trouvez ci-dessous un jeu écrit en Javascript, que vous pouvez essayer affichant les 9 chiffres de 1 à 9 dans le désordre, dont l'objectif est de faire pivoter les 9 chiffres pour pouvoir les ranger dans l'ordre comme l'indique la figure ci-contre : L'objectif du jeu

Rotations

Initialisation

Exercice : Programmation du jeu en langage Python

    Ecrire un programme en langage Python réalisant ce jeu :
  1. Il utilise la bibliothèque Tkinter.
  2. Quand on finit une partie :
    • Il affiche la durée de la partie
    • Il donne la possibilité au joueur de pouvoir continuer à jouer comme l'indique la figure ci-contre :
Ce qu'il faut réaliser

Une proposition de réponse :

# -*- coding: utf-8 -*-
from tkinter import Canvas, Tk
import random

def _create_circle(self, x, y, r, **kwargs):
    """Fonction pour créer les cercles à partir du centre
    (à but de simplicité)

    :param x: Le centre x du cercle à créer
    :param y: Le centre y du cercle à créer
    :param r: Le rayon du cercle à créer
    :type x: int
    :type y: int
    :type r: int
    :return: Le return de la fonction Tkinter create_oval()
    :rtype: int
    """
    return self.create_oval(x - r, y - r, x + r, y + r, **kwargs)


def onCircleRotationClick(x):
    """Fonction de gestionnaire d'évenement clic sur cercle de rotation

    :param x: Variable de l'évenement du clic
    """
    global animating #Variable globale pour qu'elle soit modifiable par d'autres fonctions

    if not animating:#On ne peut faire des rotations que si l'animation est terminée

        offset = 120 #Variable de zone de recherche
        #print('Got object click', x.x, x.y) DEBUG

        #
        global closest #ID du cercle de rotation à l'origine du clic
        global closestes #ID des cercles et des textes liés au cercle de rotation
        #Pour que les variables soient accessibles par d'autres fonctions

        closest = (x.widget.find_closest(x.x, x.y))#Recherche du cercle de rotation à l'origine du clic
        closestes = (x.widget.find_enclosed(x.x - ( offset ), x.y -(offset) , x.x +(offset), x.y +(offset)))#Recherche des cercles et des textes proches du cercle de rotation en utilisant un rectangle pour définir la zone de recherche

        anim_rotation_cercle()

def anim_rotation_cercle(movement = 10):
    """
    Fonction qui gère l'animation de la rotation du cercle

    :param movement: Valeur du mouvement déjà effectué (Par défaut 10 si aucun mouvement effectué)
    :type movement: int
    """

    global animating
    animating = True
    ecart_cercle = 150 #Constante de distance de déplacement des cercles
    i=1
    rowFinal = 3 #Variable pour définir la ligne du cercle en haut à gauche (référence pour la rotation)
    columnFinal = 3 #Variable pour définir la colonne du cercle en haut à gauche (référence pour la rotation)

    for obj in closestes:#On vérifie tous les objets trouvés par la fonction find_enclosed()
        #Déterminer les positions d'objets
        if not(obj in cercles_rotation[0] or obj in cercles_rotation[1]):
            if not findRow(cercles, obj) == None: #Si l'obj du Canvas est bien un cercle

                #Objectif : trouver le premier cercle en haut à gauche afin de trouver les trois autres
                row = findRow(cercles,obj)
                column = cercles[row].index(obj)

                if row == rowFinal and column < columnFinal:
                    columnFinal = column
                if row < rowFinal:
                    rowFinal = row
                    columnFinal = column

    rotate_cercles(rowFinal, columnFinal)

    movement = movement + 10
    if(movement <= ecart_cercle):#Si l'animation n'est pas finie
        canvas.after(16, lambda move=movement: anim_rotation_cercle(move)) #Toutes les 16 milisecondes, on bouge l'objet d'une valeur donnée
    else:
        update_array_rotate(rowFinal, columnFinal)#Une fois l'animation terminée, on met à jour le tableau des cercles et des textes
        canvas.after(50, setAnimatingToFalse)#Délai pour éviter les bugs de rotation

def rotate_cercles(firstRow, firstColumn):
    """Fonction qui déclenche la rotation graphique des cercles

    :param firstRow: Numéro de la ligne du cercle en haut à gauche
    :param firstColumn: Numéro de la colonne du cercle en haut à gauche
    :type firstRow: int
    :type firstColumn: int
    """

    #Toutes les objets sont déplacés d'une valeur de 10 pour une animation fluide
    canvas.move(cercles[firstRow][firstColumn], 10 ,0) #Cercle Gauche/Haut
    canvas.move(textes_cercles[firstRow][firstColumn], 10 ,0) #CercleTexte Gauche/Haut

    canvas.move(cercles[firstRow][firstColumn + 1], 0, 10) #Cercle Droit/Haut
    canvas.move(textes_cercles[firstRow][firstColumn + 1], 0, 10) #CercleTexte Droit/Haut

    canvas.move(cercles[firstRow + 1][firstColumn], 0, -10) #Cercle Gauche/Bas
    canvas.move(textes_cercles[firstRow + 1][firstColumn], 0, -10) #CercleTexte Gauche/Bas

    canvas.move(cercles[firstRow + 1][firstColumn + 1], -10 ,0) #Cercle Droite/Bas
    canvas.move(textes_cercles[firstRow + 1][firstColumn + 1], -10 ,0) #CercleTexte Droite/Bas


def update_array_rotate(row, column):
    """Fonction pour la rotation des valeurs dans le tableau de variable

    :param row: Numéro de la ligne du cercle en haut en gauche
    :param column: Numéro de la colonne du cercle en haut en gauche
    :type row: int
    :type column: int
    """

    #On effectue la rotation dans le tableau à deux dimensions
    cercles[row][column], cercles[row][column+1], cercles[row+1][column], cercles[row+1][column +1] = cercles[row + 1][column], cercles[row][column], cercles[row +1][column + 1],  cercles[row][column+1]
    #       Haut Gauche              Haut Droit             Bas Gauche                   Bas Droit

    textes_cercles[row][column], textes_cercles[row][column+1], textes_cercles[row+1][column], textes_cercles[row+1][column +1] = textes_cercles[row + 1][column], textes_cercles[row][column], textes_cercles[row +1][column + 1],  textes_cercles[row][column+1]
    testwin()#On regarde si l'utilisateur a gagné

def findRow(tableau, entree):
    """Fonction pour trouver le numéro de colonne dans un tableau à deux dimensions

    :param tableau:Le tableau à deux dimensions dans lequel chercher le numéro de colonne
    :param entree: L'ID de l'objet dont il faut trouver le numéro de colonne
    :type tableau: int
    :type entree: int
    :return: Le numéro de la colonne trouvée correspondant à l'objet
    :rtype: int
    """
    rowNumber = None
    for row in tableau:
        if(entree in row):
            rowNumber = tableau.index(row)
            break

    return rowNumber

def testwin():
    """Fonction pour tester si l'utilisateur a gagné
    La fonction crée un texte si l'utilisateur a gagné
    """
    if cercles == [[1,3,5],[7,9,11], [13,15,17]]:
        print("WIN")
        canvas.create_text(600/2,40, text="Vous avez gagné !", font=("Segoe UI", 35), anchor="center")

def setAnimatingToFalse():
    """Fonction pour débloquer la rotation de cercles (pour éviter les bugs)"""
    global animating
    animating = False

Canvas.create_circle = _create_circle

# Création fenêtre
fenetre = Tk()
win_width = 600
win_height = 600
# Création d'un espace de dessin
canvas = Canvas(fenetre, width=win_width, height=win_height, background="white")
# Ajout du canvas à la fenêtre
lignesGrilles = []

cercles = [[0,0,0],
            [0,0,0],
            [0,0,0]]  # Cercles conteneurs chiffre / En 2D, 3 cercles x 3 cercles = 9 cercles
textes_cercles = [[0,0,0],
            [0,0,0],
            [0,0,0]]  # Textes associés aux cercles  / En 2D, 3 textes x 3 textes = 9 textes
cercles_rotation = [[], []] # Cercles de rotations, 4 cercles = 2 cercles * 2 cercles

couleur_cercles = ["blue","green", "red"] #Couleurs des cercles

debug = False #DEBUG

closest = None #Variable qui contient l'ID du dernier cercle de rotation cliqué
closestes = None #Variable qui contient les ID des cercles et des textes autour du cercle de rotation

animating = False #Variable globale qui indique si une animation est en cours

randomPositions = [0,1,2,3,4,5,6,7,8] #Variables de placement de cercles et des textes de cercles
random.shuffle(randomPositions)#Mélange des positions

# Création d'une grille ( à but de déboguage )
if debug:
    for i in range(2):
        lignesGrilles.append([])
        for ii in range(3):
            if i == 0:
                lignesGrilles[i].append(canvas.create_line(600 * (ii + 1) / 4, 0, 600 * (ii + 1) / 4, 600))
            else:
                lignesGrilles[i].append(canvas.create_line(0, 600 * (ii + 1) / 4, 600, 600 * (ii + 1) / 4))



# Création des cercles et des textes
actualCirclePosition = 0 #Itérateur pour garder en mémoire le chiffre associé au cercle

for i in range(3):
    for ii in range(3):
        ligne = int((randomPositions[actualCirclePosition] - randomPositions[actualCirclePosition] % 3) / 3) #EN comptant à partir de zero
        #La position moins le reste de la division par 3 divisé par 3  = ligne
        colonne = (randomPositions[actualCirclePosition] % 3)

        cercles[ligne][colonne] = (canvas.create_circle((colonne + 1) / 4 * win_width, (ligne + 1) / 4 * win_height, 30, outline=couleur_cercles[i]))
        textes_cercles[ligne][colonne] = (canvas.create_text((colonne + 1) / 4 * win_width, (ligne + 1) / 4 * win_height, text=str((ii + 1) + (3*i) ), font=("Helvetica", 26), fill=couleur_cercles[i]))

        actualCirclePosition = actualCirclePosition + 1

#Création des cercles de rotation
for l in range(2):
    for c in range(2):
        cercles_rotation[l].append(canvas.create_circle( (3 + (2*c)) /8*win_width, (3+(2*l)) /8*win_height, 15, fill="deep sky blue", outline=""))

        #Ajout d'un listener sur le clic d'un cercle de rotation
        canvas.tag_bind(cercles_rotation[l][c], "", onCircleRotationClick)
canvas.pack()
# Demarrage de la boucle
fenetre.mainloop()