1. Introduction

En tant qu epassioné de jeux-vidéos, dès l'annonciation du projet H, j'étais sûr de voiuoir créer "ma propre console de jeu". Mon projet initial était d'émuler le jeu "puissance 4" sur une matrice LED RGB et de le rendre contrôlable avec un joystick et un bouton. Toutefois j'étais prêt à émuler un autre jeu, si le puissance 4 se révélait impossible, car mon objectif était avant tout de réussir à coder un simple programme interactif affichant des éléments mobiles sur la matrice. Toutefois la question se posait sérieuxement: un projet comme celui-ci était-il réalisable en un temps limité avec mes connaissances initales très limitées?

2. Matériel et méthodes

2.1 Matériel

  • Raspberry Pi 3 (Model B), fourni par le GyRe
  • 8x Transistor 2N2219, achetés à ZigoBot
  • Matrice LED monochrome carrée 8x8, achetée à ZigoBot
  • Matériel éléctronique de base (3x breadboards, 33x fils éléctriques, 8x résistances 330 Ohms), fourni par le GyRe

2.2 Méthode

Avant toute chose, je devais trouver une matrice LED et effectuer un montage me permettant d'afficher mon jeu. Je me suis rendu chez ZigoBot, un magasin d'éléctronique spécialisé. J'y ai trouvé ma matrice monochrome mes transistors et mon plan pour l'affichage d'images sur ma matrice. Toutefois le vendeur ne disposait pas de matrice autre que monochrome. J'ai dû me contenter d'une matrice LED à une couleur me forcant à jouer sur les intensités pour afficher les 2 différentes "couleurs" de mon puissance 4.

Le vendeur m'a proposé plusieurs drivers simplifiant énormément l'affichage d'images sur la matrice mais tous n'étaient compatibles qu'avec Arduino, un microcontrolleur dont l'utilisation pour ce projet était déconseillée par mon professeur. J'ai donc décidé de n'utiliser aucune librairie pour mon affichage et avec l'aide du vendeur de chez ZigoBot, j'ai mis au point un système utilisant 8 transistors:

La matrice dispose de 16 pins, 8 par lignes et par colonnes. Si un pin connecté à une ligne est connecté au courant et un pin connecté à une colonne au ground, alors la LED se trouvant à l'intersection de cette ligne et de cette colonne s'allume. J'ai donc déterminé, avec quelques test, la correspondance entre les pins et les lignes et colonnes de la matrice. J'ai ensuite connecté chaque ligne à des pins paramétrables du Raspberry Pi et chaque colonne au ground, avec huit transistors, interrompant la connection entre chaque colonne et le ground, dont les bases étaient connectées à des pins paramétrabes également. Ainsi je pourrais utiliser le Pi pour laisser passer le courant à travers chaque transistor à la suite très rapidement et allumer avec chaque colonne, les pins correspondant aux lignes dont les LEDs doivent être allumées pour dite colonne.

C = [25, 24, 23, 22, 21, 20, 19, 18] #attributing pins to lines and columns
L = [6, 5, 4, 17, 16, 13, 26, 27]

def affichage():
     for N  in range(8):
          #balayge des colonnes
          for O in range(8):
               GPIO.output(C[O], 0) #nettoyage des colonnes
               GPIO.output(L[O], 0) #nettoyage des lignes
          GPIO.output(C[N], GPIO.HIGH) #activation de la bonne colonne
          for M in range(8):
               if len(matrix[N][M]) >= 1:
                    #si il y a un élément dans une case, alors allumer la case
                    columns[N][M] = 1
               else:
                    columns[N][M] = 0
               GPIO.output(L[M], columns[N][M]*GPIO.HIGH) 
               #allumage des bonnes lignes pour N colonne(checke la matrice)

(note: ici la combinaison de listes "columns" est une matrice qui contient l'information des LEDs devant être allumées sous forme de 0 ou de 1 et la matrice "matrix" est la matrice contenant les éléments de jeu, que j'aborderai plus tard)

Avant de pouvoir produire ce code j'ai du apprendre à me conecter en ssh à mon Pi ce qui à pris quelques temps. J'ai ensuite pû cloner le git et commencer un script en python dans ma section nommé "projetH.py (Source/jad.zahar/projetH.py).

Pour me familiariser avec les bases de python j'ai suivi un cours sur l'application SoloLearn qui m'a permis de maîtriser les bases fondamentales du language en assez peu de temps.

J'ai ensuite pû produire le code ci-dessus qui me permettait de faire correspondre les LEDs de la matrice à ceux d'une matrice composée de 0s et de 1s dans mon code (pour faire cela, j'ai utilisé huit listes chacune elle même item d'une liste générale, je me référais donc à une LED N;M en appelant l'item NM de ma matrice générale ("columns" dans le code).

En voyant le temps qu'il me restait je décidai de m'attaquer à un problème plus simple qu'un puissance quatrem je souhaitais créer un jeu contenant des éléments multiples et en mouvement constant, de plus, les différentes lignes et colonnes de la matrice semblaient avoir des résistances différents (raison pour laquelle la colonne 8 est connectée à une résistance 330 Ohm alors que les autres non) la manipulation des intensités s'avverait complexe. Le choix du jeu snake est venu résoudre tous ces problèmes.

Ayant utilisé le logiciel Unity (un logiciel dédié à la création de jeux) très ocasionellement par le passé, mon réflexe a été de créer des "GameObjects" c'est-à-dire des variables auxquelles j'attribuerai différentes propriétés tout au long de mon code et qui correspondraient aux différents éléments présents dans le jeu (par exemple le serpent et les pommes). J'ai également décidé de considérer chaque "pixel" du serpent comme un objet à part, en rétrospective, je me rends compte que ce choix n'était de loins pas le plus judicieux ni le plus élégant mais il m'a parru plus instinctif.

J'ai donc créé des variables de type String avec des noms différents et une "matrice de jeu" que j'utiliserai pour gérer le jeu. Pour commencer, j'ai fait se déplacer un "pixel" ce qui s'est avéré assez simple. (en supprimant l'objet de la case de ma matrice et en l'ajoutant à la case qui suit). Le code inital posait problème car le code déplaçait un objetsur une case, puis le détectait sur cette case immédiatement et, le considérant comme un autre objet, le faisait se dépalcer immédiatement. J'ai donc créé une matrice "de transition" qui copiait toutes les positions des objets dans la matrice de jeu et qui était ensuite utilisée comme référence pour appliquer des modifications à la matrice principale.

     for rF in range(8):
          #collones
          for rf in range(8):
               #lignes
               oldCol[rF][rf] = matrix[rF][rf] #stocke les positions 'passées' 

Toutefois ce système a vite posé problème avec plusieurs pixels à la suite car, un objet ajouté à une case dans laquelle un objet se trouvait déjà venait à faire disparaître l'objet. J'ai donc crée une liste pour chaque case de ma matrice ce qui me permettait de considérer plusieurs éléments par case.

     for rF in range(8):
     #collones
          for rf in range(8):
               #lignes
               for digit in range(len(matrix[rF][rf])):
                    oldCol[rF][rf] += matrix[rF][rf][digit] #stocke les positions 'passées'

Les différents"Game objects" fonctionnent de cette manière:

  • Les membres principaux du serpent (s, n, a, k) se dépalcent dans une direction unique.
if s in oldCol[F][f]:
     #right
     matrix[F][(f+1)%8] += [s] #déplace le 'pixel'
     oldCol[F][f].remove(s)
     matrix[F][f].remove(s) #cleanup oldcol et matrix
  • Les éléments "changement de direction" (e, g, l, o) transforment les membres principaux se trouvant dans la même case en un membre principal d'une direction précise, permettant à un flux d'objets se dépalcant sur une ligne de "tourner" en changeant de direction.
for F in range(8):
     #columns
     for f in range(8):
          #lines
          #-change orientation
          if (e in oldCol[F][f]) and (len(oldCol[F][f])>=2): 
               #si il y a e et autre chose, transformer autre chose en s
               #to right
               for tori in range((len(oldCol[F][f])-1)):
                    if oldCol[F][f][tori+1] in body:
                         oldCol[F][f][tori+1] = s #pour BODY
                         del matrix[F][f][tori+1]
                         matrix[F][f] += [s] 
  • Le membre "last" du serpent, qui se trouve à la "fin" du serpent, se comporte comme un membre normal à l'exception du fait qu'il supprime un changement de direction après l'aavoir emprunté.
  • Le membre "head" à l'inverse, produit un changement de direction lorsqu'il détecte un imput particulier.
  • L'objet "apple" est instantié sur une case aléatoire (non-occupée par un autre objet) de la matriceau début du code .
while True: #first apple spawn
     R1 = randrange(8)
     R2 = randrange(8)
     if len(matrix[R1][R2]) == 0:
          matrix[R1][R2] += A
          break

si la tête se trouve sur la même case qu'une pomme, la pomme disparait, le membre last et remplacé par un membre normal et est copié à "l'arrière" de celui ci (selon sa direction). Puis une nouvelle pomme apparaît sur une case aléatoire.

Si la tête du serpent se trouve sur la même case qu'un autre membre, le programme s'arrête avec un "game over".

if len(matrix[rrF][rrf])>=2: 
     print "gameover"
     sys.exit()

(note: ici, la case rrFrrf contient forcément la tête et si elle contenait une pomme, celle-ci a déjà été recoltée)

Ces différents élément premettent d'émuler le jeu de manière fonctionelle et sont tous regroupés sous la méthode Game() de mon code.

Celle-ci est appelée à chaque tour par la boucle principale. Un "tour" représente un temps fixé dans cette même loop (je sais c'est une magic value...). Cette boucle appelle également une méthode qui me rendra l'input des flèches du clavier.

def inputFunction():
     #returns input
     if keyboard.is_pressed("up"):
          return "up"
     elif keyboard.is_pressed("right"):
          return "right"
     elif keyboard.is_pressed("left"):
          return "left"
     elif keyboard.is_pressed("down"):
          return "down"
     else:
          return 0

Bien que la méthode Game() soit appelée tous les x temps, la fonction d'affichage qui fait corresspondre la matrice d'affichage à celle de jeu et la fait appraître sur la matrice LED est constament appelée par la boucle principale que voici:

while True:
     input = inputFunction()
     #chaque 0.1s, le jeu 'calcule une frame' ( fonction Game() ), 
     #l'affichage en revanche se fait en continu
     if time.time() - timeI >= 0.15:
          Game()
          timeI = time.time() #on recommence a compter apres chaque 'frame'
     affichage() #allume les bonnes LEDs sur la matrice

Ce code suffit à faire fonctionner le snake sur mon installation.

Toutefois, j'ai manqué de temps et je n'ai réussi à le faire fonctionner qu'avec un clavier branché au Pi pour les inputs Le joystick fourni par le GyRe que je voulais initialement utiliser produisait un signal analogique que je n'ai pas réussi à transformer. Cela est dû à la complexité du convertisseur ADC fourni par le gymnase et aux librairies de ce dernier uniquement fonctionelles en python 3 (alors que mon code est écrit en python 2).

3. Résultats

Malgré l'absence du joystick, le projet est fonctionnel! Le jeu snake est jouable sans trop d'accrocs et l'affichag est fontionnel. Il arrive toutefois qu'il produise un game over non justifié quand deux inputs différents sont donnés au code trop rapidement. https://www.swisstransfer.com/d/fd42a513-37b1-4bd9-bde7-2714e2b2dc83 (Ce lien n'est valable que jusqu'au 04 mars 2020)

4. Discussion

Le résultat est satisfaisant. Si j'avais eu plus de temps j'aurais aimé pouvoir utiliser le joystick, bien que je me rendre compte que le clavier est un controlleur bien plus pratique pour ce genre de jeu. Le bug du "faux game-over" est peut-être dû à une simple faiblesse de mon code. Je pourrais facilement le résoudre en interdissant à la fonction de lire plus d'un input unique par x temps. Je peux bien-sûr toujours améliorer mon projet an prefectionnant le code actuel (en résolvant certains bugs par exemple) ou même en ajoutant d'autres jeux que le snake.

5. Conclusion

Ce projet s'est avéré réalisable. J'ai appris énomément, autant en éléctronique qu'en informatique. Je suis fier du résultat.

Références

Magasin ZigoBot, https://www.zigobot.ch/en/

Plateforme d'apprentissage de Python SoloLearn, https://www.sololearn.com/

Autres références:

https://www.hashbangcode.com/article/stopping-code-execution-python https://stackoverflow.com/questions/3996904/generate-random-integers-between-0-and-9 https://www.appdynamics.com/blog/engineering/the-key-differences-between-python-2-and-python-3/ https://docs.python.org/3/library/exceptions.html https://www.csestack.org/how-to-get-user-input-in-python/#raw-input-py2 https://www.katacoda.com/courses/python/playground