Mise en place

Étape 1
Étape terminée ?

Installation du script sur la console.
Introduction des fonctions.
Mise en place du moteur de jeu principal.
Notions d'espaces colorimétriques.
Définition du référentiel spatial de la console.
Introduction des variables globales.
Initialisation du moteur de rendu graphique.

Installation du script

On va commencer par créer le script du jeu et l'enregistrer directement sur la Gamebuino comme indiqué dans l'atelier de découverte à la section Exécutez votre premier programme. Je te laisse le soin d'aller vérifier comment procéder si tu as des fuites de mémoire...

Voilà notre script code.py de départ :

from gamebuino_meta import waitForUpdate, display, color, buttons

# ----------------------------------------------------------
# Main loop
# ----------------------------------------------------------

while True:
    waitForUpdate()

Une fois sauvegardé dans le volume CIRCUITPY, l'écran de ta META devient tout noir.

Remarque

Le symbole # te permet de préfixer des commentaires dans ton code. Tout ce qui est situé après ce symbole, et sur la même ligne, n'est pas interprété et ne peut donc pas être exécuté. Les commentaires peuvent te permettre d'apporter des éclaircissements dans ton code, pour expliquer dans quelle zone du code on se trouve, où décrire ce que fait telle ou telle portion de code, et pourquoi tu as défini les choses de cette manière là... Ça peut grandement faciliter la lecture de ton code lorsque tu dois le partager avec d'autres, ou même pour toi, lorsque tu y reviendras dans 6 mois et que t'auras complètement oublié pourquoi t'avais fait les choses ainsi ! Donc n'hésite pas à commenter ton code.  

Mise en place d'un ordonnanceur

Pour bien organiser ton code avec Python, je t'encourage à regrouper les instructions qui participent à une même tâche dans une fonction. Une fonction est un bloc d'instructions que tu pourras ensuite appeler au moment opportun, un peu comme une procédure qui décrit la tâche à réaliser.

La syntaxe générale d'une fonction en Python est la suivante :

def nomDeLaFonction(liste des paramètres):
    séquence des instructions à exécuter

La ligne de déclaration de la fonction (la première ligne) doit obligatoirement se terminer par le symbole deux-points : qui permet d'introduire le bloc d'instructions qui composent le corps de la fonction. Note bien l'indentation des instructions dans le corps de la fonction. Elle est nécessaire pour que l'interpréteur Python puisse déterminer que ces instructions font bien partie du corps de la fonction.

Une fonction peut accepter une liste de paramètres (qu'on appelle aussi des arguments). Cette liste peut aussi être vide, si la fonction n'attend pas de paramètre particulier.

Boucle de contrôle principale

On va créer une première fonction qui va permettre d'ordonnancer les tâches principales à réaliser à chaque cycle d'exécution du programme.

Le script comporte en effet une boucle inifinie qui s'exécute indéfiniment, tant que le programme tourne :

while True:
    waitForUpdate()

La structure de contrôle while True signifie littéralement tant que Vrai, autrement dit la condition qui détermine si la structure while doit continuer à s'exécuter ou pas est toujours vraie, donc l'exécution se poursuit indéfiniment.

Comme pour les fonctions, le bloc d'instructions devant s'exécuter est annoncé par le symbole : et est indenté.

Tu noteras que la boucle comporte déjà un appel à la fonction waitForUpdate(), qui ne prend pas de paramètre, et qui se charge d'attendre que l'affichage graphique exécuté à l'itération précédente est terminé avant de te rendre la main pour que tu puisses exécuter ton code et effectuer un raffraîchissement d'écran.

Les itérations sont exécutées à la fréquence de 25 fps (frames per second). Autrement dit, en une seconde, 25 itérations sont exécutées. Donc, chaque itération dispose de 40 ms (1000 ms / 25) pour s'exécuter. Si la durée d'exécution d'une itération dépasse 40 ms, le déroulement global de l'application est automatiquement ralenti. Ce phénomène est perceptible à l'affichage... tu verras que « ça lag ».

Bien, commençons par définir notre propre fonction, qui va se charger d'ordonnancer les différentes tâches à réaliser pour la mise en œuvre du jeu. Appelons-la, par exemple, tick, puisqu'elle s'exécutera à chaque tic d'horloge en quelque sorte :

def tick():
    display.clear(color.BROWN)

Ici, la fonction tick() efface simplement l'écran en appliquant une couleur de fond marron.

Et pour demander à notre ordonnanceur de s'exécuter à chaque itération de la boucle de contrôle principale, il suffira d'effectuer un appel à la fonction tick() :

while True:
    waitForUpdate()
    tick()

Espaces colorimétriques

Il existe toute une liste de couleurs prédéfinies que tu peux utiliser. Je te renvoie à la documentation concernant la palette des couleurs officielles de la META. Cette documentation cible le langage C++, qui est le langage par défaut qu'on utilise pour programmer sur la Gamebuino, mais la liste des couleurs disponibles est la même en Python.

Tu y trouveras la définition de la couleur BROWN qui correspond au code couleur hexadécimal #CF8E44 dans l'espace colorimétrique RGB888. Une couleur RGB888 est définie à partir des 3 couleurs primaires rouge (Red), vert (Green) et bleu (Blue) et est encodée sur 3 octets : 1 octet qui indique l'intensité du niveau de couleur rouge, 1 octet pour le vert et 1 octet pour le bleu. Chaque octet comporte 8 bits (qui peuvent chacun prendre la valeur 0 ou 1) et peut donc encoder 28 valeurs différentes, c'est-à-dire 256 valeurs. Si ces valeurs sont caractérisées par des entiers, chaque entier est donc compris entre 0 et 255 (entre 00 et FF en notation hexadécimale). Si on combine les 3 octets qui codent les intensités des 3 couleurs primaires, on obtient donc 2563 = 16 777 216 combinaisons, autrement dit : plus de 16 millions de couleurs.

En réalité, la META n'est pas capable d'afficher 16 millions de couleurs. Le référentiel colorimétrique dans lequel on peut piocher des couleurs n'est pas RGB888, mais RGB565. Dans RGB888 on avait 8 bits par couleur primaire (rouge, vert ou bleu). Dans RGB565, on dispose seulement de 5 bits pour le rouge, 6 pour le vert et 5 pour le bleu. Autrement dit, le nombre de combinaisons possibles correspond à 25 x 26 x 25 = 216 = 65 536 couleurs. Donc beaucoup moins qu'en RGB888 ! Mais c'est déjà pas mal...

Mais alors comment convertir une couleur de RGB888 vers RGB565 ?
Comme ça dépasse un peu le cadre de cet atelier, je préfère te proposer un petit convertisseur automatique pour te permettre d'effectuer cette opération :

Essaie de convertir le code hexa de la couleur BROWN par exemple (#CF8E44). Tu devrais obtenir le code couleur RGB565 0xCC68. Le préfixe 0x indique simplement que l'entier est exprimé dans la base hexadécimale.

Tu peux tout à fait utiliser n'importe quelle couleur de RGB565 dans ton script. Par exemple, tu peux remplacer l'instruction :

display.clear(color.BROWN)

par :

display.clear(0xCC68)

Tu obtiendras le même résultat sur l'écran, puisqu'on utilise ici le code de la couleur BROWN. Mais remplace le code hexadécimal par celui d'une autre couleur et tu verras que le résultat sera différent.

Pour la couleur de fond de notre scène de jeu, on utilisera une teinte marron légèrement différente : 0x69c0.

Bien, on va également délimiter l'enceinte du terrain de jeu que le serpent ne devra pas franchir. On la représentera par un simple rectangle orangé, en utilisant le code couleur 0xed40 :

def tick():
    display.clear(0x69c0)
    display.setColor(0xed40)
    display.drawRect(0, 0, 80, 64)

Tu vois qu'après avoir effacé l'écran avec une couleur, si on souhaite dessiner une forme avec une autre couleur, on doit le spécifier avec la fonction display.setColor(). Ici on dessine simplement un rectangle avec la fonction display.drawRect(x, y, width, height) qui accepte 4 arguments : les coordonnées (x,y) du coin supérieur gauche, ainsi que la largeur et la hauteur du rectangle.

Référentiel spatial

Dans le mesure où on va réaliser des tracés graphiques à l'écran, on va avoir besoin de se répérer facilement dans l'espace pour pouvoir positionner les différents éléments du jeu. L'écran de la META comporte 80x64 pixels, et son référentiel spatial se présente de la façon suivante :

Référentiel spatial

L'origine (0,0) du repère se trouve en haut à gauche, et chaque pixel est caractérisé par des coordonnées (x,y) où :

  • x varie dans l'intervalle [0,79]
  • y varie dans l'intervalle [0,63]

Pour positionner les différents éléments du jeu, on aura besoin d'effectuer des calculs en ayant systématiquement recours aux dimensions de l'écran. Il peut donc être utile de déclarer des constantes globales, en début de programme, pour définir une bonne fois pour toutes ces valeurs, et s'y référer dans nos calculs. En Python, la notion de constante n'existe pas... on utilisera donc des variables globales qui feront office de constantes. Et pour bien les identifier, on les nommera en majuscule :

from gamebuino_meta import waitForUpdate, display, color, buttons

# ----------------------------------------------------------
# Global variables
# ----------------------------------------------------------

SCREEN_WIDTH  = 80
SCREEN_HEIGHT = 64
COLOR_BG      = 0x69c0
COLOR_WALL    = 0xed40

Tu vois que j'en ai profité pour déclarer également les couleurs qu'on utilisera comme des variables globales. De cette manière, si demain tu souhaites modifier leurs valeurs, tu pourras le faire plus facilement, sans avoir à chercher dans ton code à quel endroit tu les as utilisées.

On peut maintenant modifier notre fonction tick() pour qu'elle s'appuie désormais sur ces variables globales :

def tick():
    display.clear(COLOR_BG)
    display.setColor(COLOR_WALL)
    display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)

Tu vois que le code est beaucoup plus explicite quand il est écrit de cette manière.

Et finalement, toutes les instructions qui composent la fonction tick() sont pour l'instant entièrement dédiées à l'affichage de la scène de jeu. Mais nous allons ajouter d'autres tâches tout à l'heure et il serait donc opportun ici de rassembler toutes ces instructions spécifiques à l'affichage dans une fonction dédiée, qu'on pourrait nommer draw() et appeler directement à partir de la fonction tick() :

# ----------------------------------------------------------
# Game management
# ----------------------------------------------------------

def tick():
    draw()

# ----------------------------------------------------------
# Graphic display
# ----------------------------------------------------------

def draw():
    display.clear(COLOR_BG)
    display.setColor(COLOR_WALL)
    display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)

Tu vois que le code commence à se structurer en faisant clairement apparaître l'organisation et la sémantique des tâches à réaliser. Il est important de bien nommer tes fonctions et tes variables pour qu'on puisse facilement comprendre ce qu'elles font ou à quoi elles servent.

Bien, en définitive, voilà le code complet que l'on obtient et qui termine cette étape de mise en place (utilise la barre de défilement pour parcourir l'ensemble du code) :

from gamebuino_meta import waitForUpdate, display, color, buttons

# ----------------------------------------------------------
# Global variables
# ----------------------------------------------------------

SCREEN_WIDTH  = 80
SCREEN_HEIGHT = 64
COLOR_BG      = 0x69c0
COLOR_WALL    = 0xed40

# ----------------------------------------------------------
# Game management
# ----------------------------------------------------------

def tick():
    draw()

# ----------------------------------------------------------
# Graphic display
# ----------------------------------------------------------

def draw():
    display.clear(COLOR_BG)
    display.setColor(COLOR_WALL)
    display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)

# ----------------------------------------------------------
# Main loop
# ----------------------------------------------------------

while True:
    waitForUpdate()
    tick()

Voilà l'affichage que tu devrais obtenir en exécutant ce code sur ta META (il te suffit simplement de sauvegarder le script code.py sur le volume CIRCUITPY) :

Démo

Remarque

Tout au long de cet atelier, n'oublie pas de sauvegarder régulièrement ton script code.py. Typiquement, à chaque fois que tu définis une nouvelle fonction. Effectue également des copies du script sur ton ordinateur, car il peut arriver que le volume CIRCUITPY soit sauvagement ejecté... et tu perdrais tout ton code si tu n'en as pas fait de copie au préalable.... Donc prends cette habitude dès maintenant, avant que le code ne devienne plus conséquent.  

Étapes