Accueil > Bienvenue > Journal de bord > Gestion des conditions

Gestion des conditions

samedi 30 novembre 2024, par Mathieu Brèthes

Toutes les versions de cet article : [Deutsch] [English] [français]

Finalement ce blog risque fort de devenir des notes de projet. Bon c’est pas grave. J’ai bien avancé dans le traducteur de diagrammes Dia vers le code RenPY. Mais le travail de traduction automatisée, à savoir ici traduire du code d’une langue vers une autre, présente des écueils. Le problème qui m’intéresse ici est la gestion des conditions.

Un programme est, à la base, une séquence d’étapes, chacune modifiant l’état de l’ordinateur sur lequel elle s’exécute (pour faire très, très résumé). Tout l’intérêt de l’informatique consiste à en faire le moins possible pour obtenir le résultat maximal. Aussi, on a inventé, je pense dès le début de l’histoire du médium, la possibilité de faire des boucles. Imaginez que votre ordinateur a des pieds. Vous voulez le faire marcher pendant un kilomètre. Sans boucle, vous allez devoir écrire un programme de mille lignes, chaque ligne disant "fais un pas" :

fais un pas
fais un pas
fais un pas
fais un pas
fais un pas
fais un pas
fais un pas
fais un pas
fais un pas
... // 990 lignes plus loin...
fais un pas
ça use les souliers

Avec boucle, vous allez écrire un programme de deux lignes : "fais un pas", puis "retourne à la ligne précédente" :

fais un pas
retourne à la ligne précédente
ça use les souliers

Quand on en est ici on touche immédiatement au problème qui nous concerne aujourd’hui : avec votre boucle ci-dessus, comment dites-vous à votre ordinateur de s’arrêter ? En effet, l’ordinateur va exécuter à l’infini le pas, puis retourner à la ligne précédente, puis le pas, puis... sans jamais arriver à la ligne "ça use les souliers."

Il faut donc rajouter le deuxième élément fondamental du programme : la condition. Celle-ci permet de vérifier l’état de l’ordinateur, et de prendre des décisions en fonction de celui-ci. Voici le programme amélioré avec une boucle et une condition :

fais un pas
est-on à 1km ?
non : retourne deux lignes plus haut
oui : ça use les souliers

Félicitations, je viens d’inventer le Basic !

DiaVN permet de représenter ce genre de joyeusetés à l’aide de diagrammes plutôt qu’à l’aide de texte. Chaque élément du diagramme représente une action (une ligne, en quelque sorte). Chaque flèche, le flot entre deux actions. L’utilité principale est de pouvoir créer des programmes (ici des jeux d’aventure) moins linéaires que ce qu’invite à faire la programmation "textuelle" (voir Un Visual Novel sous forme de graphe ?). La problématique du jour a donc à voir avec la représentation des conditions dans DiaVN.

En effet, DiaVN génère du code compatible RenPY, RenPY étant lui-même plus ou moins du Python. La syntaxe des conditions en Python est très avancée. Avec Dia, je représente une condition par un losange, et chaque solution possible (par exemple le oui/non ci-dessus) par une flèche qui part de la condition et qui va vers un autre élément. Ainsi, on représente les "branches" du flot.

On pourrait imaginer juste laisser carte blanche à l’utilisateur pour mettre ce qu’il veut dans son élément condition, puis de labelliser comme il veut les branches, et espérer que le traducteur copie-colle tout ça directement en code RenPY. C’est l’approche "je m’en foutiste", où DiaVN est juste chargé de la gestion du flot, et contient dans chaque élément du code RenPY/Python (ou approchant).

Le premier problème c’est que DiaVN n’est pas conçu que pour RenPY : à terme, j’aimerais pouvoir générer d’autres langages avec (dans l’idée de peut-être un jour générer un jeu pour la Neo Geo Pocket avec, donc code assembleur ; ou de basculer vers un autre logiciel de fiction interactive si RenPY disparaît par exemple). Si je me contente d’encapsuler du code RenPY dans mes éléments, je perds la notion de portabilité qui m’intéresse ici.

Le second problème d’utiliesr DiaVN comme un simple "encapsuleur" RenPY c’est que cela rendra le code difficile à déboguer en cas de problème. En effet, l’interpréteur RenPY va renvoyer des erreurs à des numéros de lignes correspondant au code RenPY généré par DiaVN, et auquel l’utilisateur n’a (a priori) pas touché. C’est ensuite à l’utilisateur de remonter dans le temps et de trouver à quelle partie de son diagramme DiaVN correspond le code RenPY défaillant. Sur un gros projet, ce n’est pas une mince affaire ! Et les endroits qui sont les plus à risque de générer des erreurs de code RenPY, ce sont justement les endroits où le code RenPY est en réalité du Python... C’est à dire, précisément les conditions.

Mon approche consiste donc à contraindre fortement la façon dont une condition est envisagée dans DiaVN. L’utilisateur n’aura ainsi que 3 possibilités :

  1. comparer une variable (contenue dans l’élément condition) à une valeur immédiate (nombre ou chaîne de caractères), ou une autre variable. En l’absence de comparateurs, chaque valeur est comparée par le signe "égal".
  2. comparer une variable par la même méthode que ci-dessus, mais l’utilisateur précise le comparateur dans chacune des flèches (utile pour tester si une valeur est supérieure ou inférieure à un certain seuil)
  3. comparer le résultat d’une expression Python à une valeur vrai ou faux.
Diagramme des différents types de conditions supportées par DiaVN

On notera que dans la troisième possibilité, on laisse quand même une grosse liberté à l’utilisateur, qui peut rédiger son bloc conditionnel comme il veut (à partir du moment où il renvoie vrai ou faux).

La question des listes

Tout ceci est bien joli, mais un problème se pose dans le problème (et c’est là que c’est problématique, tous ces problèmes).

Dans certains langages de programmation (le Python, donc par extension RenPY), on ne "sait" pas à l’avance ce que contient une variable. Est-ce un entier ? Une chaîne ? Une... Liste ?

Python laisse à l’utilisateur la responsabilité de ne pas se tromper dans son programme, et génèrera des erreurs dans le cas de comparaisons d’éléments de types incompatibles. Plus pernicieusement, il pourra permettre des comparaisons d’éléments qui ne sont a priori pas compatibles (en renvoyant des valeurs aberrantes, par exemple en Python on peut comparer un entier et une chaîne).

Ca encore à la limite on peut le gérer. Mais là où ça devient compliqué, c’est quand le symbole utilisé pour comparer deux éléments change en fonction du type d’élément. C’est ce qui se produit avec les listes.

Une liste (pour celleux qui viennent du Basic...) c’est un élément qui contient une séquence d’autres élements (qui eux-mêmes peuvent être d’autres listes, etc.). Quand un utilisateur veut faire une comparaison d’une variable avec une liste, il veut généralement savoir si la valeur contenue dans sa variable, se retrouve dans un des éléments de la liste. Or, dans ce cas, c’est un opérateur "spécial liste" qu’il faut utiliser, l’opérateur "in" ! Et si on ne sait pas de quel type est notre variable, eh bien on va nécessairement produire du code... Faux.

En bref, on a ici deux réponses possibles au problème :

  • on interdit l’utilisation des listes

ou

  • on trouve un moyen de donner à DiaVN le don de prescience sur le contenu des variables.

En 2024, c’est peut-être un peu dommage de se priver des listes. Donc, nécessairement, pour gérer ce cas particulier de condition, il faut qu’on introduise dans DiaVN soit le typage des variables, soit un mot-clé spécial pour indiquer, au moment de la condition, que la variable comparée est une liste.

Etant un peu vieille école, c’est pour le premier choix que je vais opter. Je pense par exemple proposer de faire commencer les noms de variables par :

  • i_ : nombre entier
  • f_ : nombre à virgule flottante
  • s_ : chaîne de caractères (string, en anglais)
  • l_ : liste

Et leurs équivalents en valeurs immédiates pour les éléments comparés si besoin :

  • entier : 42
  • flottant : 0.1 (le point fait office de virgule, pour représenter un entier dans un flottant on utilise 1.0)
  • chaîne : "hello, world"
  • liste : [42, 63, 28, 12] (ici une liste d’entiers)

Mais me direz-vous, quid des listes de type mixte, des listes qui contiennent des variables, des listes qui contiennent d’autres listes... ?

Dans un premier temps, on va poser des limites (une liste est une liste d’éléments du même type, et elle ne contient pas d’autres listes, point).

Après, on verra...

Pfou, quel bazar les copains.