Pendules : Une horloge analogique

Art

Une horloge analogique… Qu’est-ce cela peut bien être… Eh bien, c’est tout simplement une horloge avec des aiguilles ! Ainsi, ce programme vous permettra, grâce à Turtle, d’afficher une horloge analogique…

Introduction

La spécificité de cette horloge, est que celle-ci est dynamique. C’est-à-dire que les aiguilles bougent en fonction de l’heure réel. Sinon cela serait trop facile, il suffirait seulement de dessiner un cercle pour le cadran, et des segments pour les aiguilles, et c’est fini. Heureusement, ce n’est pas le cas.

Quelques fonctions

Pour afficher l’heure réel, il faut d’abord récupérer l’heure réel… Pour cela, j’utilise la fonction time.strftime(), à partir du module time, je récupère une donnée de la date actuelle, en l’occurrence j’ai besoin de 3 données : l’heure, la minute, et la seconde. Voici donc une fonction que j’ai codé qui m’a permis de récupérer une de ces 3 données :

def get_time(u):
    match u:
        case "h":
            return int(strftime("%I"))
        case "m":
            return int(strftime("%M"))
        case "s":
            return int(strftime("%S"))

Ici, j’ai utilisé la déclaration match, qui permet, entre autres, de comparer la valeur d’une variable, ici la variable u (pour unité). Par exemple, en appelant la fonction suivante get_time("h"), elle me renvoi la valeur de l’heure actuelle, à l’heure où j’écris cet article, il est exactement 01:44 donc en appelant la fonction maintenant, elle me renverrait la valeur 1.
À savoir qu’utiliser la déclaration match n’est absolument pas obligatoire, j’aurais très bien pu utiliser une structure conditionnelle traditionnelle avec les déclarations if, elif, et else.

Désormais, j’ai besoin d’afficher le cadran de l’horloge, pour cela, rien de plus simple :

def draw_clock(x, y, rayon):
    travel(x, y-rayon)
    pencolor((0,)*3)
    fillcolor((255,)*3)
    begin_fill()
    circle(rayon)
    end_fill()
    travel(x, y-rayon+10)
    fillcolor(7, 24, 31)
    begin_fill()
    circle(rayon-10)
    end_fill()
    travel(x, y-5)
    fillcolor(85, 91, 92)
    begin_fill()
    circle(5)
    end_fill()
    penup()
    goto(x, y)
    setheading(90)
    pencolor((255,)*3)
    for i in range(60):
        forward(rayon-(25 if i%5 == 0 else 15))
        pendown()
        forward(25 if i%5 == 0 else 15)
        penup()
        goto(x, y)
        right(6)

C’est une fonction plutôt simple, bien qu’elle soit assez lourde. On dessine des cercles, et on fait les marquages des minutes et des heures. J’ai dans cette fonction, utilisé une autre fonction qui ne vient pas des modules importés, la fonction travel() (pour voyager). J’aime beaucoup cette fonction car elle est extrêmement simple, et permet d’économiser quelques lignes :

def travel(x, y):
    penup()
    goto(x, y)
    pendown()

En fait, c’est tout simplement l’équivalent d’un goto(), mais sans laisser les traces du pinceau ! Voici donc le cadran, il est fixe :

J’ai également voulu ajouter dans le fond un mur en briques. Pour ne pas laisser le fond vide, la fonction fait seulement 7 lignes :

def wall_brick(x, y, width_bricks, height_bricks):
    for i in range(height_bricks):
        for j in range(width_bricks):
            if i % 2 == 0:
                fill_rect(x+65*j, y+35*i, 60, 30, (161, 84, 68))
            else:
                fill_rect(x+30+65*j, y+35*i, 60, 30, (161, 84, 68))

Les paramètres sont simples, la position du mur (le bas à gauche du mur) via x et y, et le nombre de briques en longueurs, ainsi que le nombre de briques en hauteur. fill_rect() est une fonction maison inspiré de kandinsky.fill_rect(), elle permet de dessiner des rectangles :

def fill_rect(x, y, w, h, c=(0, 0, 0)):
    travel(x, y)
    pencolor(c)
    fillcolor(c)
    begin_fill()
    for i in range(4):
        forward(w if i % 2 == 0 else h)
        right(90)
    end_fill()

Ci-dessous donc, le mur en briques, contruit par la fonction wall_brick(-668, -330, 21, 21) :

Difficultés rencontrées

Il ne reste donc plus qu’à dessiner les aiguilles et les faire pivoter au fur et à mesure. Voici donc la fonction qui occupe cette lourde tâche, et qui m’a bien cassé la tête :

def clock_hands(colors_hands, size_hands):
    s, m, h, k = get_time("s"), get_time("m"), get_time("h"), 0
    update_hours(h, colors_hands[0], size_hands[0])
    update_minutes(m, colors_hands[1], size_hands[1])
    while True:
        update_seconds(s, colors_hands[2], size_hands[2])
        sleep(.6 if k < 90 else .5 if k < 180 else .3 if k < 240 else 0)
        s += 1
        if s == m+2:
            update_minutes(m, colors_hands[1], size_hands[1])
        if s+1 == (h+1)*5:
            update_hours(h, colors_hands[0], size_hands[0])
        if s == 60:
            s, m, h = get_time("s"), get_time("m"), get_time("h")
            update_minutes(m, colors_hands[1], size_hands[1])
            update_hours(h, colors_hands[0], size_hands[0])
            update_seconds(s, colors_hands[2], size_hands[2], 1)
        k += 1

C’est sans doute la fonction la plus compliquée du programme. Les deux paramètres ne sont qu’esthétique, ils permettent de modifier la couleur de chaque aiguille, ainsi que la largeur de chaque aiguille, on ne donc y s’attarder plus que ça.
L’objectif est simple, avoir l’heure la plus précise possible, mais en gardant un mouvement des aiguilles fluide.
J’ai fait le choix d’appeler la fonction get_time() le moins de fois possible, car elle requiert de faire un nombre extrêmement élevé de requêtes plusieurs fois par secondes. Et faire des requêtes inutiles, ça ralentit le programme… La fonction n’est donc appelée que lors de l’initialisation (avant de rentrer dans la boucle infinie), et lorsque la trotteuse à passe à la soixantième seconde. Nous appelons donc la fonction get_time() environ une fois par minute ; et c’est suffisant. Ensuite, on met à jour la position des aiguilles toutes les secondes… Enfin, c’est un peu plus compliqué que ça. En effet, il faut que le mouvement soit fluide, et donc il faut supprimer toutes les actions inutiles. Sauf que 98% du temps, il n’y a que l’aiguille des secondes qui bouge, celle des minutes et des heures ne bougent beaucoup moins souvent. Donc cela ne sert à rien de mettre à jour ces aiguilles à chaque seconde. On le fait donc à des instants précis.
Malgré toutes les optimisations pour fluidifier le rafraichissement, il n’est malheureusement pas parfait.
Également, il s’avère que plus le programme tourne longtemps, plus il est lent… Il faut donc adapter la pause de la fréquence en fonction de quand le programme a démarré. Voici à quoi sert la variable k, et la fonction time.sleep(), nous perdons donc beaucoup de précisions au fil du temps. Mais en contrepartie, j’ai mis en place un système pour rerégler automatiquement les aiguilles.

L’image finale

À toi de « jouer » ?!

Tu ne pourras malheureusement pas vraiment t’amuser avec ce programme. À moins que tu veuilles fixer une horloge. Néanmoins, si tu veux savoir comment elle fonctionne exactement, et la voir bouger, n’hésite pas à télécharger le script !

Télécharger le .py