1. Introduction

Dans tous les sports de compétition, lorsqu'on s'entraîne seul, on désire dans la plupart des cas se mesurer aux autres pour évaluer notre niveau. C'est en faisant cela qu'on progresse le plus efficacement, car l'envie de battre les autres et être le meilleur nous pousse à nous dépasser. Un jeu comme le Simon qu'on trouve en magasins ne possède pas la possibilité de jouer contre un autre joueur, mais seulement l'IA. Cela peut devenir rageant, car l'IA ne perd jamais. Cela m'a amené à vouloir créer un Simon jouable en joueur contre joueur, un Super Simon. Ce jeu se jouerait en face à face telle une partie de bataille navale.

Le principal problème avec ce projet réside dans le fait qu'il est impossible de le rendre aussi compact et esthétique que le jeu commercialisé. Cela à cause du manque de matériel à notre disposition. Autrement, le projet est tout à fait réalisable et pourrait offrir de nouvelles perspectives au classique du jeu de société qu'est le Simon.

2. Matériel et méthodes

2.1 Matériel

  • Un Raspberry Pi 3
  • 8 boutons poussoir avec 4 branches (2x rouge, 2x verte, 2x bleue, 2x jaune)
  • 8 LEDs (2x rouge, 2x verte, 2x bleue, 2x jaune)
  • Un module Pi wedge pour Raspberry
  • 2 breadboards 5.5 x 17 cm
  • Des câbles
  • Des résistances 330 Ohm
  • Une séparation en papier
  • Un Bose micro soundlink

2.2 Méthode

2.2.1 Pull-up

Pour commencer mon projet, j'ai d'abord installé toute la partie hardware. Pour le câblage des boutons on utilise une technique appelée "pull-up". Il s'agit de brancher le bouton en parallèle d'une résistance et qu'ils soient traversés par une tension de 5 volts en direction d'un des pins du GPIO. Les LEDs sont branchées en série avec une résistance. Le courant nécessaire à l'allumage des LEDs est fourni par un pin lorsque le bouton correspondant est enfoncé et après être passé dans la LED et la résistance, il va jusqu'au ground. Schéma représentant un bouton branché avec la technique pull-up pour alimenter une LED.

Pour la partie codage, il suffit de configurer les bons pins aux boutons et aux LEDs. Il faut aussi importer le module time pour éviter les bugs avec les LEDs.

buttonred1 = 18
ledred1 = 19

GPIO.setup(buttonred1, GPIO.IN)
GPIO.setup(ledred1, GPIO.OUT)

Ici, on configure les pins pour savoir s'il vont recevoir ou donner du courant.

    if GPIO.input(buttonred1) == GPIO.LOW:
        GPIO.output(ledred1, GPIO.HIGH)
        time.sleep(t)
    else:
        GPIO.output(ledred1, GPIO.LOW)

Ce code permet l'allumage de la LED rouge 1, lorsque le bouton rouge 1 est pressé. t correspond au temps pendant lequel la LED sera allumée lors d'un pressage du bouton.

2.2.2 Mise en forme et câblage

Comme expliqué plus haut, on utilise ici la technique du "pull-up" pour faire fonctionner nos boutons et nos LEDs. J'ai donc effectué 4 fois ce processus sur les deux breadboards. On a au final 4 boutons et 4 LEDs par breadboard. Les deux breadboards sont séparées par une séparation en papier qui empêche les deux joueurs de tricher sur l'autre. Photo de l'ensemble du montage.

Photo de la breadboard du joueur 1.

Photo de la breadboard du joueur 2.

2.2.3 Liste et combinaison

Pour avoir l'occasion de comparer les combinaisons effectuées par les deux joueurs, j'ai dû utiliser les listes. Une liste pour chaque joueur. Les deux sont vides au début de la partie puis vont être incrémentées à l'aide de la commande "append" au fur et à mesure de la partie.

La première chose à faire était de dissocier les boutons des deux joueurs et de faire en sorte qu'on ait bien deux combinaisons distinctes. Pour cela, on va utiliser un modulo avec le nombre de round pour savoir si nous sommes dans un round pair ou impair. Si nous sommes dans un round impair, le joueur 1 va joueur et seuls ses boutons seront utilisables. Il en va de même pour le joueur 2 lors des rounds pairs.

        if GPIO.input(buttonred1) == GPIO.LOW and Round % 2 == 1:
            GPIO.output(ledred1, GPIO.HIGH)
            time.sleep(t)
            combinaison1.append('red')
         else:
            GPIO.output(ledred1, GPIO.LOW)

        if GPIO.input(buttonred2) == GPIO.LOW and Round % 2 == 0:
            GPIO.output(ledred2, GPIO.HIGH)
            time.sleep(t)
            combinaison2.append('red')
        else:
            GPIO.output(ledred2, GPIO.LOW)

De cette manière, les deux joueurs auront chacun leur liste qui pourront être comparées par la suite pour déterminer si oui ou non on peut passer au prochain round.

2.2.4 Fin de round et compteur

Pour déterminer les fins de round, j'ai créé une variable "compteur" qui est incrémentée de 1 à chaque fois qu'un bouton est pressé, une variable "round" qui est initialement égale à 0 et une dernière variable "answer" qui est True.

On commence avec une simple commande pour déterminer le début de la partie dans le terminal.

        if Round == 0:
            print('round', 1)
            Round = Round + 1

Ensuite on incrémente le compteur.

        if GPIO.input(buttonred1) == GPIO.LOW and Round % 2 == 1:
            GPIO.output(ledred1, GPIO.HIGH)
            time.sleep(t)
            combinaison1.append('red')
            compteur = compteur + 1
        else:
            GPIO.output(ledred1, GPIO.LOW)

Ce compteur va servir à savoir quand la combinaison est terminée et quand le code doit effectuer la vérification avec l'autre combinaison. Lorsque le compteur est égal au round - 1, on va comparer les contenus des deux listes. Si le contenu est juste, answer reste True, autrement, answer devient False. On vide aussi la liste du joueur qui joue le prochain round avec la commande *= afin qu'il recommence sa combinaison de 0.

        if combinaison1 == combinaison2 and compteur == Round - 1 and Round % 2 == 1:
            answer = True
            combinaison2 *= 0

        if combinaison1 == combinaison2 and compteur == Round - 1 and Round % 2 == 0:
            answer = True
            combinaison1 *= 0

Une fois la vérification effectuée, le joueur dont c'est le tour de jouer va ajouter une couleur à la combinaison et le compteur sera donc égal au round. A ce moment, le code va déterminer si oui ou non on passe au prochain round.

        if Round == compteur and answer == True and Round >= 1:
            print "bravo !"
            print('round', Round + 1)
            Round = Round + 1
            compteur = 0
            answer = False
        elif Round == compteur and answer == False and Round >= 1:
            print "perdu !"
            compteur = 0

2.2.5 Reproduction de la combinaison

Le but de ce Super Simon est qu'à la fin de chaque round la machine reproduit la combinaison effectuée par le premier joueur pour le second. Pour ce faire, j'ai utilisé une boucle de type "for" à l'intérieur de la boucle "while True". Une boucle "for" sert à limiter à i fois le nombre de fois que la boucle va s'effectuer. i étant la longueur de la combinaison à répéter. J'ai utilisé la fonction range() qui fait en sorte que chaque valeur de la liste soit prise indépendamment des autres à chaque fois. Par exemple, lors du 3ème tour de la boucle, c'est la 2ème valeur de la liste qui est prise en compte si on utilise la fonction range(x). Ensuite, j'ai créé une variable "data" qui sera la donnée qui sera tirée de la combinaison à chaque tour de la boucle. La donnée va ensuite être analysée avec des "if" pour savoir de quelle couleur il s'agit et les LEDs correspondantes chez le second joueur s'allumeront.

Cette portion du code complète la partie du code qui détermine la fin du round pour que la reproduction se fasse bien à la fin du round.

        # compteur de round
        if Round == compteur and answer == True and Round >= 1:
            print("bravo !")
            print('round', Round + 1)
            Round = Round + 1
            compteur = 0
            answer = False

            # IA qui reproduit la combinaison pour l'autre joueur
            for i in range(len(combinaison1)):
                time.sleep(0.3)
                data = combinaison1[i]

                if data == 'red':
                    GPIO.output(ledred2, GPIO.HIGH)
                    time.sleep(t)
                    GPIO.output(ledred2, GPIO.LOW)

                if data == 'blue':
                    GPIO.output(ledblue2, GPIO.HIGH)
                    time.sleep(t)
                    GPIO.output(ledblue2, GPIO.LOW)

                if data == 'yellow':
                    GPIO.output(ledyellow2, GPIO.HIGH)
                    time.sleep(t)
                    GPIO.output(ledyellow2, GPIO.LOW)

                if data == 'green':
                    GPIO.output(ledgreen2, GPIO.HIGH)
                    time.sleep(t)
                    GPIO.output(ledgreen2, GPIO.LOW)

            for i in range(len(combinaison2)):
                time.sleep(0.3)
                data = combinaison2[i]

                if data == 'red':
                    GPIO.output(ledred1, GPIO.HIGH)
                    time.sleep(t)
                    GPIO.output(ledred1, GPIO.LOW)

                if data == 'blue':
                    GPIO.output(ledblue1, GPIO.HIGH)
                    time.sleep(t)
                    GPIO.output(ledblue1, GPIO.LOW)

                if data == 'yellow':
                    GPIO.output(ledyellow1, GPIO.HIGH)
                    time.sleep(t)
                    GPIO.output(ledyellow1, GPIO.LOW)

                if data == 'green':
                    GPIO.output(ledgreen1, GPIO.HIGH)
                    time.sleep(t)
                    GPIO.output(ledgreen1, GPIO.LOW)

Il y a deux boucles "for", une pour chaque joueur.

2.2.6 Son et haut-parleur

Pour connecter un haut-parleur au raspberry, il suffit de simplement brancher le raspberry à un écran grâce à un câble HDMI pour pouvoir connecter en bluetooth via l'interface du bureau.

Ensuite, pour les sons que font chaque bouton lorsqu'ils sont pressés, j'ai utilisé une vidéo youtube d'une personne jouant au simon. Puis, j'ai converti la vidéo en mp3 et l'ai découpée sur le logiciel "adobe premier pro" pour avoir 4 sons distincts. Pour la lecture des sons, j'ai téléchargé une commande bash nommée "mpg123". Cette commande, je peux l'utiliser grâce au module "os" que j'ai aussi importé. Le mp3 va être cherché dans mes fichiers et le terminal du raspberry va jouer le son en entier.

 #configuration  des leds/boutons
 if GPIO.input(buttonred2) == GPIO.LOW and Round % 2 == 0:
            GPIO.output(ledred2, GPIO.HIGH)
            os.system("mpg123 " + "simon_3.mp3")
            time.sleep(t)
            combinaison2.append('red')
            compteur = compteur + 1
        else:
            GPIO.output(ledred2, GPIO.LOW)

J'ai donc ajouté une ligne de code à chaque partie de code qui était liée à l'allumage d'une LED et de ce fait chaque allumage de LED est accompagné d'un son correspondant à sa couleur.

3. Résultats

Mon Super Simon fonctionne bel et bien dans l'ensemble. L'allumage des LEDs fonctionne sans problème et le compteur de round, le système de vérification et de reproduction de la combinaison fonctionnent parfaitement aussi. Le son est bien moins performant. Le haut-parleur se déconnecte sans raison de façon aléatoire et la commande "mpg123" lit les fichiers sons quand elle en a l'envie. L'autre désavantage de cette commande est qu'elle affiche un message dans le terminal à chaque fois qu'elle essaie de lire un fichier. Cela rend le compteur de round un peu illisible. Un autre problème est le fait que la dernière LED pressée reste à chaque round allumée lors de la reproduction de la combinaison chez l'autre. Elle s'éteint lorsque la reproduction est terminée. Le dernier bug notable que j'ai pu remarqué est qu'il faut bien presser une seule brève fois sur un bouton à la fois. Sinon, les boutons ne vont pas être correctement pris en compte et les combinaisons ne seront pas claires.

4. Discussion

Etant donné que mon jeu est fonctionnel, je pense avoir atteint mes objectifs qui était d'au moins pouvoir utiliser mon jeu et qu'il soit plutôt compact. Malgré cela, le Super Simon pourrait être amélioré et optimisé en terme de codage avec une meilleure connaissance du langage Python et de ses limites.

Pour la lecture du son, j'ai conscience qu'il existe des moyens plus connus et mieux fait que "mpg123" comme avec les modules "playsound" ou "pygame", mais j'ai été sujet à des bugs et des messages d'erreur à profusion lorsque j'ai essayé de les utiliser. J'ai donc préféré utiliser "mpg123" au vu du manque de temps avant le rendu du projet.

J'aurais aussi pu mieux me renseigner sur la création de fonction pour mon code, car j'ai perdu beaucoup de temps avant de maîtriser les fonctions et mon code est donc moins compact et agréable à lire. Cependant, j'ai bien réussi à comprendre les boucles "for" et le fonctionnement des booléens dans mon code.

5. Conclusion

En conclusion, mon Super Simon est une réussite pour moi, même si il reste quelques bugs que je pourrais résoudre avec plus de temps. Je suis aussi satisfait de ma progression en codage étant donné que je partais de zéro en début de projet. J'ai pu apprendre beaucoup sur le langage Python et sur le fonctionnement d'un raspberry. Le processus de création de mon projet H a donc été autant enrichissant intellectuellement qu'il a été ludique.

Ce projet m'a encouragé à progresser et à continuer à coder pour produire d'autres programmes et objets de ce genre.

Références

https://note.nkmk.me/en/python-list...

https://www.geeksforgeeks.org/diffe...

https://www.bristolwatch.com/debian...

https://www.youtube.com/watch?v=1Yq...