En collaboration avec le fablab de Atelier 1901 à Cabourg, j’ai créé un atelier de découverte du code pour les collégiens, sur une journée (5 à 7h). Nous avons choisi pour cela d’utiliser la carte micro:bit, permettant de travailler en mode bloc (les collégiens y étant normalement initié grace à Scratch), avec un rendu sur un dispositif et des contrôles physique. De plus, la plateforme makecode permet de créer/tester le jeu, sans avoir de carte à disposition.
A travers une série de petits exercices, on découvre les notions de base (variables, fonctions, boucle, conditions logiques, évènements boutons, contrôle des LED de la matrice …), d’abord coté blocs, puis on bascule coté code et on expérimente. L’objectif étant, dans la deuxième partie de l’atelier, de mettre en pratique ces notions pour créer un jeu Asteroid sur la matrice de LEDs.
Malgré la limite de taille de « l’écran », 5×5 pixels, le jeu est jouable et est asseza amusant. Mais avouons-le, il est probablement plus amusant quand on a écrit le code 😅
Enfin, la simplicité du jeu permet d’ajouter des fonctionnalités assez facilement. Je laisse d’ailleurs de côté le score pour que les participants puissent, par la suite, l’ajouter eux même. Si les participants sont rapides, on peut commencer à travailler sur ces fonctionnalités en fin de stage.
Je vais détailler ici les phases de création du jeu de base, puis, dans un second poste, je proposerais quelques évolutions possibles.
Note : je vais uniquement faire du code, partant du principe que ceux qui liront ceci auront déjà fait ce que je qualifie de travail préparatoire de découverte :
– créer un comportement avec des blocs
– comprendre le code généré
– modifier le code pour le comprendre en changeant le comportement du script
1/ Les bases
1.1/ Concepts du jeu
Posons de suite les principes du jeu et créons les variables associées.
- notre vaisseau se déplacera sur la dernière ligne de la matrice de LEDs
- les boutons A et B permettent de déplacer le vaisseau latéralement
- les astéroides défileront de haut en bas sans changer de colonne
- on gardera toujours une ligne vide pour se déplacer entre chaque astéroide
- le logo tactile sert de start/pause
1.2/ Les variables
En conséquence, on définira 4 variables :
| enPause | définit l’état « jeu en pause » |
| enJeu | définit l’état « partie en cours » |
| posX | position X du vaisseau |
| listeAsteroides | tableau contenant les coordonnées des astéroides [[x,y],[x,y]…] |
let enPause = false
let enJeu = false
let posX = 0
let listeAsteroides: number[][] = []
1.3/ enJeu et enPause
À tout moment, avant de lancer une action de jeu (automatique ou par les contrôles), je vérifierais si une partie est en cours (enJeu = true) et si l’on n’est pas en pause (enPause = false). Vous trouverez donc à plusieurs reprises dans le jeu cette condition :
if (enJeu == true && enPause == false)
{
}
Je vous laisse, à chaque fois, bien comprendre pourquoi cette condition est là … ou pas.
1.4/ startGame()
Pour initialiser l’état enJeu=true, on va tout de suite créer une fonction de démarrage de partie startGame(). Nous améliorerons cette fonction au cours du poste, pour le moment, cette fonction va simplement indiquer qu’une partie est en cours. Et on va immédiatement éxecuter cette fonction après sa définition, pour commencer une partie à l’allumage de la carte.
function startGame() {
listeAsteroides = []
enJeu = true
}
startGame()
1.5/ L’écran
Le choix de la matrice LEDs comme écran induits un traitement assez particulier que l’on vera plus loin.
Dans un atelier, on passe un moment à découvrir cette matrice. Si vous ne l’avez jamais utilisé, avant de poursuivre, il peut être utile de passer un petit quart d’heure à la tester.
Notons ici, en essentiel, que la règle d’adressage des LEDs est basée sur 0, la première LED, en haut à gauche, a donc l’adresse [0,0].

2/ Les astéroïdes ☄️
J’ai décidé de créer les nouveaux astéroïdes au rythme d’un par seconde. Le déplacement d’un cran vers le bas aura donc lieu toutes les 0.5 seconde. On aura donc 2 boucles temporelles :
| Temps (ms) | Nom | Action |
| 500 | move | mouvement vers le bas |
| 1000 | create | création d’un nouvel astéroïde |
2.1/ La boucle « create »
Je fais le choix de gérer l’affichage des astéroïdes exclusivement sur la boucle move.
La boucle create aura donc, pour seul but, d’ajouter dans listeAsteroides, une paire de coordonnées :
[
position X = un nombre alétoire entre 0 et 4
position Y = -1
]
On initialise la position Y à -1, car la boucle de mouvement augment la position en Y avant l’affichage, en commençant par 0, les nouveaux astéroïdes apparaitraient en ligne 1.
On peut se dire que l’on crée les astéroïdes avant leur entrée dans le champ de vision.
Pour faciliter le traitement des contacts et le vidage progressif du tableau, je fais le choix d’ajouter ces nouvelles coordonnées en début de tableau avec la fonction Array.unshift()
loops.everyInterval(1000, function () {
if (enJeu == true && enPause == false)
{
listeAsteroides.unshift([randint(0, 4), -1])
}
})
2.2/ La boucle « move »
A chaque exécution de cette boucle, on va augmenter la coordonnée Y de chaque astéroïde.
On arrive à la particularité de l’utilisation de la matrice LEDs comme écran (c.f. 1.5). Si on créait ce jeu en HTML/CSS/JS, on se contenterait de changer les coordonnées d’un élément HTML. Dans le cas de la matrice LEDs, on allume un pixel pour afficher un astéroïde, et cela implique que l’on éteint la LED de position précédente.
On devra donc, dans l’ordre :
– éteindre la LED de la position actuelle
– augmenter la position Y
– allumer la nouvelle LED de position
Si l’obligation de suivre cet ordre ne vous semble pas logique, posez vous … déroulez les actions dans votre tête … ou testez. A part si vous voulez calculer 2 fois une position, c’est la bonne méthode.
De plus, si l’astéroïde est en Y=4 au moment du déplacement, il sort de l’écran, on peut donc le supprimer de listeAsteroides avec la fonction Array.removeAt()
Enfin, à l’arrivée d’un astéroïde, avec cette logique, on tente d’éteindre une LED avec Y=-1. Cela ne pose pas de problème, la carte va l’ignorer, mais pour être rigoureux et éviter tout problème d’une éventuelle mise à jour, j’ajoute une condition pour éteindre une LED, uniquement si Y>=0
loops.everyInterval(500, function () {
if (enJeu == true && enPause == false)
{
for (let i = 0; i <= listeAsteroides.length - 1; i++)
{
if (listeAsteroides[i][1] >= 0)
{
led.unplot(
listeAsteroides[i][0],
listeAsteroides[i][1]
)
}
if (listeAsteroides[i][1] < 4)
{
listeAsteroides[i][1] += 1
led.plot(
listeAsteroides[i][0],
listeAsteroides[i][1]
)
}
else
{
listeAsteroides.removeAt(i)
}
}
}
})
3/ Le vaisseau 🚀
A ce point, on a une pluie d’astéroïdes, tous séparés d’une ligne vide, on va maintenant s’occuper du vaisseau.
3.1/ Apparition en début de partie
On commence par initialiser la position X du vaisseau au centre de la dernière ligne ( [2,4] ). On va en profiter pour effacer l’écran. En effet, si on a joué une autre partie avant, on aura besoin de repartir sur un écran vide.
Pour cela, on amende la fonction startGame() ainsi :
function startGame() {
listeAsteroides = []
basic.clearScreen()
posX = 2
led.plot(posX, 4)
enJeu = true
}
On garde toujours le enJeu = true en dernier, de façon à réinitialiser toutes les valeurs avant d’indiquer que la partie a commencé.
3.2/ Mouvement du vaisseau
On va maintenant attacher un évènement « bouton pressé » à droite et à gauche pour déplacer le vaisseau. Selon le cas, on va donc ajouter ou retirer 1 à posX, et déplacer la LED. Comme dans le cas de astéroïdes, on devra donc, dans l’ordre :
– éteindre la LED de la position actuelle
– modifier la position X
– allumer la nouvelle LED de position
On prendra garde à ne pas sortir de l’écran, on ajoutera donc des conditions permétant de s’assurer que :
– on ne se déplace à gauche que si posX > 0
– on ne se déplace à droite si posX < 4
On obtient ainsi le code :
input.onButtonPressed(Button.A, function () {
if (enJeu == true && enPause == false)
{
if (posX > 0)
{
led.unplot(posX, 4)
posX--
led.plot(posX, 4)
}
}
})
input.onButtonPressed(Button.B, function () {
if (enJeu == true && enPause == false)
{
if (posX < 4)
{
led.unplot(posX, 4)
posX++
led.plot(posX, 4)
}
}
})
4/ Crash 💥
A ce point du code, on a une pluie d’astéroïdes, un vaisseau qui se déplace … mais si un astéroide passe sur le vaisseau, la LED est éteinte à la disparition de l’astéroïde et le vaisseau réapparait quand on le déplace … c’est parfaitement logique.
On va maintenant créer les conditions d’échec et de déclenchement de fin de partie.
4.1/ gameOver()
La première chose sera la définition de la fonction gameOver(), cette fonction sera appelée quand un astéroïde et le vaisseau entrent en contact. On va traiter cela dans une fonction à part car cela se produit dans 2 cas (3 endroits dans notre code) :
– un astéroïde tombe sur le vaisseau (boucle move)
– le vaisseau percute un astéroïde en déplacement latérale (boutons A et B)
Niveau action au niveau de la fin de partie on va :
– indiquer que l’on n’a plus de partie en court
– effacer l’écran
– afficher un smiley triste 😞
function gameOver()
{
enJeu = false
basic.clearScreen()
basic.showIcon(IconNames.Sad)
}
4.2/ Crash d’astéroide
Commençons par le cas où l’astéroïde tombe sur le vaisseau. Ici, on va se placer dans la boucle « move ». On sait qu’il y aura contact si au moment de déplacer l’astéroïde, il a pour coordonnées :
– X = posX
– Y = 3
Je fais le choix de contrôler le contact avant de calculer la nouvelle position en Y, inutile de calculer si on sait qu’il va y avoir contact. Voici le nouveau de notre boucle « move »
loops.everyInterval(500, function () {
if (enJeu == true && enPause == false) {
for (let i = 0; i <= listeAsteroides.length - 1; i++) {
if (listeAsteroides[i][1] >= 0) {
led.unplot(
listeAsteroides[i][0],
listeAsteroides[i][1]
)
}
if (listeAsteroides[i][1] < 4)
{
if (listeAsteroides[i][1] == 3 && listeAsteroides[i][0] == posX)
{
gameOver()
}
else
{
listeAsteroides[i][1] += 1
led.plot(
listeAsteroides[i][0],
listeAsteroides[i][1]
)
}
}
else
{
listeAsteroides.removeAt(i)
}
}
}
})
4.3/ Crash de vaisseau
Voyons maintenant le cas où le vaisseau vient percuter l’astéroïde. Dans ce cas, on va simplement, après avoir calculé la nouvelle valeur de posX au moment du déplacement, s’il percute un astéroïde.
Notre tableau étant dans l’ordre et vidé au fur et à mesure, on sait que le dernier astéroïde du tableau est le seul à pouvoir être en la ligne 4 (il peut aussi être en ligne 3) et donc être percuté.
La condition à contrôler est donc simple, on vérifie si le dernier astéroïde de la liste :
– est en ligne 4
– est dans la colonne posX
On va donc, sur chaque bouton, après avoir calculé la nouvelle valeur de posX, vérifier s’il y a contact. Si oui, on lance gameOver(), sinon on allume la nouvelle position du vaisseau.
input.onButtonPressed(Button.A, function () {
if (enJeu == true && enPause == false)
{
if (posX > 0)
{
led.unplot(posX, 4)
posX += -1
if (listeAsteroides[listeAsteroides.length - 1][1] == 4 && listeAsteroides[listeAsteroides.length - 1][0] == posX)
{
gameOver()
}
else
{
led.plot(posX, 4)
}
}
}
})
input.onButtonPressed(Button.B, function () {
if (enJeu == true && enPause == false)
{
if (posX < 4)
{
led.unplot(posX, 4)
posX += 1
if (listeAsteroides[listeAsteroides.length - 1][1] == 4 && listeAsteroides[listeAsteroides.length - 1][0] == posX)
{
gameOver()
}
else
{
led.plot(posX, 4)
}
}
}
})
Je reste sur du code pour débutant, mais on pourra facilement déplacer cela dans une fonction, pour se contenter, en pressant le bouton, de dire deplacerVaisseau(1) ou deplacerVaisseau(-1).
5/ Rejouer et pause
Le jeu est maintenant fonctionnel. Il n’y a qu’un problème, dès que la partie est fini, on doit redémarrer la carte pour relancer une partie. Il nous faut donc maintenant ajouter une le logo tactile avec 3 possibilités :
| En jeu | Pause | Action |
| oui | non | mettre le jeu en pause => enPause = true |
| oui | oui | arrêter le mode pause => enPause = false |
| non | – | relancer une partie => startGame() |
On peut donc ajouter le code suivant :
input.onLogoEvent(TouchButtonEvent.Pressed, function () {
if (enJeu == false)
{
startGame()
}
else
{
if (enPause == false)
{
enPause = true
}
else
{
enPause = false
}
}
})
On peut profiter de l’occasion pour expliquer qu’un booléen peut facilement être inversé avec une négation, et que dans notre cas, on peut s’épargner le contrôle logique et simplement inverser la valeur de enPause si on sait qu’une partie est en cours :
input.onLogoEvent(TouchButtonEvent.Pressed, function () {
if (enJeu == false)
{
startGame()
}
else
{
enPause = !enPause
}
})
Pour conclure
Les notions apportées ici sont basiques, mais il faut se montrer logique. C’est un bon exemple pour débuter et comprendre que l’on code de façon progressive, ajoutant des briques une à une. Des comportements, des évènements, des conditions. On doit imaginer un plan et le dérouler de façon méthodique.
Ce jeu ouvre à la possibilité de plusieurs ajouts :
- ajouter un score
- ajouter du son
- pouvoir passer d’un coté à l’autre
- accélérer
- jouer sur plusieurs cartes
- pencher la carte pour avancer/reculer le vaisseau
- changer les points si on accélère
- changer des options en pause (activer le son, le passage d’un coté à l’autre)
J’en détaillerais certains que j’ai implémenté dans un autre poste.
Ressources en ligne
Vous pouvez retrouver ce code :
- makecode
- github, en plusieurs versions :
- entièrement commentée
- avec le score
- téléportation de chaque coté
- avec son
- avec accéleration
- version complète
Je vous invite tout de même, avant de récupérer le code tout fait, à essayer d’ajouter ces compléments par vous-même. Vous pourriez d’ailleurs implémenter ces options d’une autre façon. J’ai réécrit ce code à 3 reprises et j’ai toujours fini sur une variante à niveau organisation. Il y a toujours plusieurs façons de coder une application, pour un fonctionnement identique. C’est aussi une leçon importante.
Happy coding !


