S'amuser avec plusieurs tortues

À ce stade du tutoriel, nous avons fait le tour d’une bonne partie de turtle. Découvrons maintenant l’utilisation orientée objet de celui-ci ainsi que quelques autres fonctionnalités.

La classe Screen

Pour commencer, la classe Screen est une classe permettant de manipuler une fenêtre avec un canvas. Elle hérite de TurtleScreen. Il faut utiliser la première lorsque notre programme turtle est indépendant tandis qu’il faut utiliser la seconde, dont l’instanciation nécessite un canvas, lorsqu’il fait partie d’une application (si nous utilisons turtle à l’intérieur d’un programme tkinter par exemple).

La classe Screen est instanciable une seule fois, c’est-à-dire que nous pourrons faire autant de fois appel au constructeur de cette classe, celui-ci nous retournera le même objet. En programmation, on appelle cela un singleton. C’est un patron de conception restreignant l’instanciation d’une classe à un unique objet :

print(turtle.Screen() == turtle.Screen())  #Affiche 'True' : les deux objets retournés sont identiques

Grâce à notre objet, nous pouvons, comme nous avons appris à le faire au cours de ce tutoriel, paramétrer la fenêtre ou encore interagir avec l’utilisateur :

ecran = turtle.Screen()  #Instanciation
ecran.bgcolor('green')
ecran.numinput("Nombre", "Un nombre, il me faut un nombre : ", 0, 0, 100)
ecran.exitonclick()

Nous savons que notre fenêtre a besoin d’un crayon pour dessiner dedans. Pour savoir s’il y en a, nous pouvons faire appel à la méthode turtles qui retourne une liste :

print(turtle.Screen().turtles())  #Affiche les crayons actuels de notre fenêtre

Pour le reste, vous pouvez retrouver les méthodes utilisables dans la partie 24.1.2.2. Methods of TurtleScreen/Screen de la documentation.

La classe Turtle

La classe Turtle nous permet d’instancier et de contrôler un crayon. Si une fenêtre n’a pas encore été créée, le constructeur s’occupe d’instancier une objet de type Screen. La classe Turtle hérite de la classe RawInput (alias RawPen) dont le constructeur nécessite un canvas.

Remarquons que comme les objets Turtle sont liés à un objet Screen unique (dû au singleton), ce n’est pas la solution à choisir si nous souhaitons manipuler plusieurs canvas (ou plusieurs fenêtres).

Avec notre objet, nous pouvons tracer dans la fenêtre de façon personnalisée :

crayon = turtle.Turtle()  #Instanciation
crayon.resizemode('user')
crayon.pensize(20)
crayon.color('green', 'white')
crayon.forward(100)

De plus, nous pouvons lier des événements à ces crayons, chose que nous n’avons pas encore vue, avec les méthodes onclick, onrelease et ondrag. Elles se comportent comme la fonction onscreenclick précédemment étudiée c’est-à-dire qu’elles prennent en paramètre la fonction associée à l’événement ainsi que, optionnellement, le bouton de la souris générant l’événement et si la fonction s’ajoutera à (avec True) ou remplacera (avec False ou rien) la ou les fonctions déjà associée(s). Voici un exemple, suivi d’une illustration :

Le code :

import turtle

crayon_clique, crayon_relache = turtle.Turtle(), turtle.Turtle()
crayons = { 'pink' : crayon_clique, 'grey' : crayon_relache }
pos = [-75, 0]
for clef, valeur in crayons.items():
    valeur.color(clef, clef)
    valeur.shapesize(7, 7)
    valeur.up(); valeur.goto(pos); valeur.down(); pos[0] += 150
crayon_clique.onclick(lambda x, y: print("cliqué !"))
crayon_relache.onrelease(lambda x, y: print("relâché !"))
Cliquer ou relâcher, telle est la question.
Cliquer ou relâcher, telle est la question.

Avec cet exemple, le clique gauche sur le crayon rose générera un "cliqué !" tandis qu’un relâchement gauche depuis le crayon gris engendrera un "relâché !".

Pour le reste, vous pouvez retrouver les méthodes utilisables dans la documentation.

TP : Clique la tortue !

Nous voilà quasiment à la fin de ce tutoriel et quoi de mieux qu’un TP pour finir en beauté ? Dans celui-ci, nous allons réaliser un autre jeu : clique la tortue !

Cahier des charges

Le principe du jeu de cliques est très simple : il suffit de cliquer sur quelque chose de spécifique pour gagner des points avec lesquels il est possible de monter de niveau voire de débloquer des bonus. Bien sûr, au fur et à mesure de la progression, il est important de gagner de plus en plus de points vu qu’il en faut de plus en plus, ne serait-ce que pour monter de niveau.

Pour notre jeu, nous cliquerons sur des tortues et nous n’utiliserons qu’un seul bonus lié au niveau et qui rapportera niveau - 1 point(s) par tortue cliquée. Ainsi, ce bonus rapportera 0 point au niveau 1 (le niveau initial) ou encore 17 points au niveau 18, par exemple. Le coût de chaque niveau vaut niveau * 10.

Les tortues apparaîtront régulièrement dans la fenêtre et auront deux couleurs : une couleur de tracé et une couleur de remplissage, toutes deux choisies aléatoirement, de même que leur position et leur orientation. Un clique sur la tortue la fera disparaître et rapportera des points en fonction de ces couleurs et du bonus. Voici la grille des points associés aux couleurs :

Point Couleur
1 'black' et 'grey'
2 'brown' et 'orange'
4 'pink' et 'yellow'
8 'purple' et 'green'
16 'blue'
32 'red'

En définitive un clique sur une tortue rapportera la somme du :

  • Nombre de points correspondant à la couleur de remplissage ;
  • Nombre de point correspondant à la couleur de tracé si et seulement si celle-ci est la même que la couleur de remplissage ;
  • Nombre de point donné par le bonus de niveau

Une partie se terminera lorsque le joueur aura atteint le niveau 20.

Vous êtes libre de choisir comment quitter le jeu, comment monter de niveau ou encore à quel clique associer chaque tortue. Avant de vous lancer, je vous encourage à réfléchir à l’organisation de ce projet, à ce dont vous avez besoin qu’il fasse, etc.

Pour ma part, si vous avez besoin d’aide, j’ai évidemment choisi la programmation orientée objet vu que c’est le thème de cette partie. Ensuite, mon jeu est constitué de la sorte :

Premièrement, il y a une classe Jeu, qui représente le jeu. Elle est notamment composée d’un écran, d’un nombre de points et d’un bonus. Cette classe permet d’initialiser le jeu, d’afficher la partie et de gérer son fonctionnement. De plus, elle donne son instance à la classe Tortue et à la classe Bonus de sorte que celles-ci puissent connaître les informations dont elles ont besoin voire faire appel à des méthodes.

Deuxièmement, il y a classe Bonus, héritant de turtle.Turtle, qui permet gérer le niveau ainsi que le bonus associé, et de progresser en cliquant sur le curseur carré bleu (affiché en bas à droite de la fenêtre).

Troisièmement, il y a la classe Tortue, elle aussi héritant de turtle.Turtle qui permet de créer, d’afficher et de gérer une Tortue à l’écran.

Au final, chaque tortue correspond à un crayon, le bonus correspond à un crayon et le jeu a un crayon pour dessiner dans la fenêtre.

Enfin, les captures d’écran ci-dessous peuvent vous inspirer si besoin :

Clique la tortue ! (1)
Clique la tortue ! (1)
Clique la tortue ! (2)
Clique la tortue ! (2)
Clique la tortue ! (3)
Clique la tortue ! (3)
Correction

Puisque le programme est plus consistant que d’habitude, je vais vous présenter les gros morceaux utilisés indépendamment.

Tout d’abord, nous importons évidemment turtle, puis la fonction randint pour les choix aléatoires et enfin le module sys pour quitter le programme. Les constantes ont des noms explicites correspondant à leurs contenus. Enfin, nous instancions un objet de type Jeu qui initialise la fenêtre et le jeu, puis nous lançons l’exécution de celui-ci :

import turtle
from random import randint
import sys

TITRE = "Clique la tortue !"
IMAGE_FOND = "fond_clique_tortue.png"
LARGEUR, HAUTEUR = 640, 480
COULEURS = { 'black' : 1, 'grey' : 1,
             'brown' : 2, 'orange' : 2,
             'pink' : 4, 'yellow': 4,
             'purple' : 8, 'green' : 8,
             'blue' : 16,
             'red' : 32 }
CLES_COULEURS = list(COULEURS.keys())
TEMPS_APPARITION = 1000

#Classes

if __name__ == "__main__":
    jeu = Jeu()  #Initialisation jeu
    jeu.ecran.ontimer(jeu.tour_de_jeu, TEMPS_APPARITION)  #Exécution jeu

Ensuite, la classe Bonus contient le niveau courant et la valeur de points nécessaires pour passer au niveau suivant. En outre, celle-ci permet d’afficher un bouton (qui est en fait un crayon de forme carré) et de gérer le passage au niveau supérieur avec le clique sur celui-ci :

class Bonus(turtle.Turtle):

    jeu = None  #Attribut de classe

    def __init__(self):
        """Constructeur"""
        turtle.Turtle.__init__(self)  #Constructeur classe mère
        self.niveau = 1  #Niveau initial
        self.cout_niveau = self.niveau * 10  #Cout niveau supérieur initial
        self.bouton_bonus()  #Affichage du bouton bonus
        self.onclick(lambda x, y : self.niveau_suivant())  #Gestion du clique sur le bouton

    def bouton_bonus(self):
        """Affichage du bouton lié au bonus"""
        self.speed(0)  
        self.shape("square")
        self.shapesize(1.5)
        self.color("lightblue", "darkblue")
        self.up()
        self.goto(LARGEUR // 2 - 25, -HAUTEUR // 2 + 25)
        
    def niveau_suivant(self):
        """Gestion passage au niveau suivant"""
        if Bonus.jeu != None:
            if jeu.points >= self.cout_niveau:
                self.onclick(None)  #Désactivation gestion clique le temps des modifications
                Bonus.jeu.points -= self.cout_niveau
                self.niveau += 1
                self.cout_niveau = self.niveau * 10
                Bonus.jeu.affiche_bonus()
                Bonus.jeu.affiche_nombre_points()
                self.onclick(lambda x, y : self.niveau_suivant())  #Réactivation gestion clique

La classe Tortue permet quant à elle d’afficher une tortue de manière aléatoire (couleurs, orientation, position) à l’écran. En outre, elle gère le clique sur celle-ci :

class Tortue(turtle.Turtle):
    
    jeu = None  #Attribut de classe
    
    def __init__(self):
        """Constructeur"""
        turtle.Turtle.__init__(self)  #Constructeur classe mère
        self.hideturtle()
        self.couleur_trace = self.couleur_remplissage = None  #Couleurs
        self.initialise_tortue()  #Initialisation du crayon
        self.showturtle()
                                     
    def initialise_tortue(self):
        """Initialise la tortue"""
        self.couleur_trace = CLES_COULEURS[randint(0, len(CLES_COULEURS)-1)]
        self.couleur_remplissage = CLES_COULEURS[randint(0, len(CLES_COULEURS)-1)]
        self.color(self.couleur_trace, self.couleur_remplissage)
        self.speed(0); self.shape("turtle"); self.setheading(randint(0, 359))
        self.up()
        self.goto(randint(-LARGEUR//2+15, LARGEUR//2-15),
                  randint(-HAUTEUR//2+40, HAUTEUR//2-15))
        self.onclick(lambda x, y : self.clique_tortue())
    
    def clique_tortue(self):
        """Gestion du clique sur la tortue"""
        if Tortue.jeu != None:
            self.onclick(None)  #Désactivation du clique sur la tortue
            points = COULEURS[self.couleur_remplissage]
            if self.couleur_trace == self.couleur_remplissage:
                points *= 2
            points += jeu.bonus.niveau - 1
            Tortue.jeu.points += points
            Tortue.jeu.affiche_nombre_points()
            self.hideturtle()
            del self

La classe Jeu permet d’afficher les informations et de gérer le jeu. Elle contient des méthodes pour afficher notamment le nombre de points ou encore le niveau actuel, à l’aide d’un crayon.

class Jeu():
    
    def __init__(self):
        """Constructeur"""
        self.ecran = turtle.Screen()  #Instanciation d'un écran
        self.points = 0
        self.bonus = Bonus()  #Instanciation d'un bonus
        self.crayon = turtle.Turtle()  #Instanciation d'un crayon
        self.initialise_crayon()  #Initialisation du crayon
        self.initialise_fenetre()  #Initialisation de la fenêtre
        self.lie_classe()

    def initialise_crayon(self):
        """Initialise crayon de la fenêtre"""
        self.crayon.speed(0)
        self.crayon.hideturtle()
        self.crayon.up()
        
    def initialise_fenetre(self):
        """Initialise la fenêtre"""
        self.ecran.setup(LARGEUR, HAUTEUR)
        self.ecran.title(TITRE)
        self.ecran.bgpic(IMAGE_FOND)
        self.affiche_nombre_points()
        self.affiche_bonus()
        self.ecran.onscreenclick(lambda x, y : self.quitter(), 3)

    def lie_classe(self):
        """Lie les classe Bonus et Tortue au jeu"""
        Tortue.jeu = self
        Bonus.jeu = self
        
    def affiche_bonus(self):
        """Affiche le niveau actuel"""
        self.crayon.up()
        self.crayon.goto(-140, - HAUTEUR // 2 + 10)
        self.dessine_rectangle((30, 375), ('darkblue', 'lightblue'))
        self.crayon.pencolor("black")
        self.crayon.forward(10)
        self.crayon.write(f"Bonus niveau {self.bonus.niveau} (Up pour : {self.bonus.cout_niveau} points)",
                          align = "left",
                          font = ("Aria", 14, "bold"))
    
    def dessine_rectangle(self, dimensions, couleurs):
        """Dessine un rectangle personnalisé"""
        self.crayon.setheading(0)
        self.crayon.color(couleurs[0], couleurs[1])
        self.crayon.down()
        self.crayon.begin_fill()
        for i in range(4):
            if i%2 == 0:
                self.crayon.forward(dimensions[1])
            else:
                self.crayon.forward(dimensions[0])
            self.crayon.left(90)
        self.crayon.end_fill()
        self.crayon.up()
                    
    def tour_de_jeu(self):
        """Gestion du tour de jeu"""
        if self.bonus.niveau >= 20:
            self.crayon.up()
            self.crayon.home()
            self.crayon.pencolor("darkblue")
            self.crayon.write("Victoire !",
                              align = "center",
                              font = ("Arial", 27, "bold"))
            self.ecran.ontimer(self.quitter, TEMPS_QUITTER)
        else:
            Tortue()
            self.ecran.ontimer(self.tour_de_jeu, TEMPS_APPARITION)

    def affiche_nombre_points(self):
        """Affiche le nombre de points"""
        self.crayon.up()
        self.crayon.goto(-LARGEUR // 2 + 5, - HAUTEUR // 2 + 10)
        self.dessine_rectangle((30, 140), ('darkblue', 'lightblue'))
        self.crayon.pencolor("black")
        self.crayon.forward(10)
        self.crayon.write(f"{self.points} point(s)",
                          align = "left",
                          font = ("Arial", 15, "bold"))
    def quitter(self):
        """Fin du game"""
        self.ecran.bye()
        sys.exit(0)

Si vous testez l’ensemble, vous vous rendrez compte que nous apercevons parfois un curseur noir au centre avant qu’il ne disparaisse. Cela est en fait la nouvelle tortue juste après son début d’initialisation avec turtle.Turtle.__init__(self) et juste avant que nous la cachions avec self.hideturtle, le temps de la mettre dans le bain ;) .

Encore une fois, il y a de nombreuses améliorations possibles. Par exemple, vous pouvez mieux gérer la courbe de progression, créer de nouveaux bonus, faire varier la vitesse d’apparition des tortues, ajouter des tailles de tortue différentes qui rapporteraient des points comme avec les couleurs, ou encore rajouter de nouvelles couleurs.

Voilà, j’espère que vous vous êtes bien amusé à programmer ce jeu !


Au terme de cette partie, vous savez désormais utiliser turtle de façon orientée objet.