MOGWAI Snake — Un jeu complet en langage RPN dans un terminal

Interface d'un jeu classique de serpent avec un fond noir et des carrés verts représentant la nourriture, affichant le score et le score élevé en haut.
MOGWAI Snake tournant dans MOGWAI CLI — score, high score, barre de santé et pièges en temps réel.

Un Snake… en RPN ?

MOGWAI est un moteur de scripting RPN (Reverse Polish Notation) pour .NET, inspiré du HP RPL des calculatrices HP 28S et HP 48. La notation postfixée, le modèle de pile, les primitives de bas niveau… rien qui ressemble a priori à l’environnement naturel d’un jeu vidéo.

Et pourtant, voilà MOGWAI Snake : un Snake jouable, complet, avec gestion du score, du high score, d’une barre de santé qui se vide en temps réel, des pièges positionnés aléatoirement sur le terrain, et un système de redémarrage propre — le tout dans un terminal TUI, en pur script MOGWAI.

Ce projet n’avait pas pour ambition de devenir un jeu à part entière. C’est avant tout un banc d’essai : est-ce que MOGWAI permet d’écrire quelque chose d’aussi dynamique et événementiel qu’un jeu en temps réel ? La réponse est oui, et voici comment.

Ce que vous voyez à l’écran

La status bar affiche en permanence :

  • MOGWAI SNAKE en cyan
  • SCORE et HIGH SCORE en bleu et magenta
  • HEALTH : une barre visuelle ▓▓▓▓▓▓░░░░ qui se vide progressivement et se recharge quand on ramasse un prix

Le terrain est délimité par un cadre Unicode (┌─┐│└┘). Les pièges (carrés verts ) sont placés aléatoirement au démarrage, en dehors d’une zone de sécurité autour du centre. Le serpent est dessiné en cyan (). Les prix à ramasser sont des chiffres en rouge (1 à 9) — leur valeur détermine à la fois les points gagnés et la croissance du serpent.

Architecture : 5 mécaniques clés

1. Le buffer $SCREEN — source de vérité unique

Le terrain de jeu est modélisé par un tableau de données linéarisé de taille WIDTH × HEIGHT. Chaque cellule encode son état :

0  →  free
1 → snake body
2 → border (forbidden zone)
8 → trap
to 'setScreenValue' with [x: .number y: .number v: .number] do
{
v &$SCREEN y $WIDTH * x + set
}

to 'getScreenValue' with [x: .number y: .number] do
{
&$SCREEN y $WIDTH * x + get
}

La détection de collision est ainsi réduite à un seul test : si la valeur à la position de la tête est supérieure à 0, c’est perdu. Bordures, corps du serpent et pièges sont tous gérés par le même mécanisme.

if ($SX $SY getScreenValue 0 >) then { gameOver }

Notez l’utilisation du sigil & pour accéder au tableau par référence directe — ce qui évite une copie coûteuse à chaque accès.

2. $HEADING — la file de directions pour la queue

C’est la mécanique centrale du Snake : la queue ne suit pas la tête immédiatement, elle retrace son chemin avec un décalage. La solution ici est élégante : à chaque cycle, la direction courante est ajoutée en fin de liste $HEADING. Quand il est temps de déplacer la queue, on consomme la première entrée de la liste.

# Store the current direction
$HEADING $CURDIR + -> '$HEADING'

# ...later, for the tail:
$HEADING first -> 'heading'
$HEADING butfirst -> '$HEADING'

La liste $HEADING agit comme une file FIFO naturelle avec les primitives first et butfirst de MOGWAI. Plus le serpent est long, plus la liste est grande — et la queue suit exactement le chemin de la tête.

3. $WAITING_LOOP — la croissance du serpent

Quand le serpent mange un prix de valeur n, il doit grandir de n × 2 cellules. La solution adoptée est minimaliste : un compteur $WAITING_LOOP est mis à n × 2. Tant qu’il est supérieur à 0, la queue ne bouge pas — le serpent grandit donc naturellement, sans aucune structure supplémentaire.

# When a prize of value $PRIZE_VALUE is caught:
$PRIZE_VALUE 2 * -> '$WAITING_LOOP'

# At the end of each cycle:
'$WAITING_LOOP' --

if ($WAITING_LOOP 0 <=) then
{
1 -> '$WAITING_LOOP'
hideSnakeTail
# ... tail movement
}

4. Timer + flag — santé sans race condition

La barre de santé se vide toutes les 2 secondes via un timer MOGWAI. Mais le timer ne modifie pas directement $HEALTH_LEVEL : il se contente de lever un flag.

timer 'HEALTH_TIMER' every 2000 do
{
'HEALTH_CHANGE_FLAG' flag.set
}

C’est la boucle principale qui consomme ce flag à chaque cycle :

if ('HEALTH_CHANGE_FLAG' flag.isSet) then
{
'HEALTH_CHANGE_FLAG' flag.clear
'$HEALTH_LEVEL' --

if ($HEALTH_LEVEL 1 <) then { gameOver }
}

Ce découplage est une bonne pratique dans tout environnement avec des callbacks asynchrones : le timer signale, la boucle décide. On évite ainsi toute modification d’état en dehors du fil d’exécution principal.

5. post { startGame } — le redémarrage propre

Quand le joueur répond Y au prompt GAME OVER, le jeu redémarre. La tentation serait d’appeler startGame directement. Mais startGame contient la boucle principale (forever do), ce qui créerait une récursion indéfinie.

La solution : post, qui planifie l’exécution de startGame après le retour de l’appel courant. La pile d’appels reste propre, quelle que soit la durée de la session.

if (" NEW GAME (Y/N) ? " waitForYesOrNoChoice) then
{
post { startGame }
}

Vérifications au démarrage

Le script vérifie deux prérequis avant de lancer le jeu. D’abord, le host doit posséder le skill TERMINAL — une nouveauté de MOGWAI 8.11 qui permet à un hôte de déclarer ses capacités :

if ('TERMINAL' hasSkill not) then
{
"Unable to run the game." ?
"The host must have the 'TERMINAL' skill" ?
mogwai.exit
}

Ensuite, la version du runtime est vérifiée :

if (mogwai.info->version: "8.11" ver>= not) then
{
"Unable to run the game." ?
"The MOGWAI runtime must be at least version 8.11" ?
mogwai.exit
}

Ces deux gardes illustrent la philosophie de MOGWAI : un script doit pouvoir valider son environnement d’exécution et échouer proprement si les conditions ne sont pas réunies.

Ce que ce projet démontre

MOGWAI Snake n’est pas un jeu sérieux — c’est une démonstration. En un peu plus de 600 lignes de script RPN, on dispose de :

  • Un rendu TUI dynamique avec positionnement précis au caractère près
  • Une boucle de jeu temps réel avec gestion des entrées clavier non-bloquante
  • Un système de timers asynchrones découplé de la logique principale
  • Une détection de collision unifiée (corps, bordures, pièges) sur un seul buffer
  • Un mécanisme de croissance du serpent sans structure de données dédiée
  • Un système de redémarrage sans récursion

Le tout sans framework, sans bibliothèque de jeu, sans moteur graphique — juste le runtime MOGWAI et les primitives TUI de MOGWAI CLI.

Tester MOGWAI

MOGWAI est open source (Apache 2.0), disponible sur GitHub et NuGet.

Le script snake.mog est inclus dans les exemples du dépôt. Il nécessite MOGWAI CLI v8.11 ou supérieur avec le skill TERMINAL activé.

Laisser un commentaire

En savoir plus sur CODING 4 PHONE

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Poursuivre la lecture

En savoir plus sur CODING 4 PHONE

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Poursuivre la lecture