Petits trucs bien utiles en Inform 6

[English version]

Retour à la page de fiction interactive.

Voici une liste de petits trucs bien utiles si vous programmez en Inform 6. Ce sont des petits trucs sur lesquels je suis tombé en programmant ; plusieurs de ces petits trucs ont également été trouvés sur Internet ou même dans un coin obscur du DM4. Pour plus de petits trucs, consultez la FAQ de Roger Firth, et les petits trucs d'Andrew Plotkin.


Quand votre jeu n'a pas de salle plongée dans l'obscurité (Zarf)

Par défaut, toute salle de votre jeu est plongée dans l'obscurité - c'est à dire que quand le joueur entre dedans, le jeu renvoie "Dans l'obscurité - Il fait noir et vous ne voyez rien". Et vous ne pouvez interagir qu'avec un nombre très limité d'objets, ou les découvrir en tâtonnant, etc ; en général il vous faut trouver une source de lumière avant de pouvoir continuer, et ça constitue une classe de puzzles présente dans beaucoup de jeux.

Le truc, c'est qu'en général, ces jeux sont des vieux jeux - Zork était si je me souviens bien le premier jeu à utiliser ce mécanisme (avec la présence des grues), et du coup c'est devenu un classique des jeux d'aventure (et puisque Inform est basé sur la Z-machine créée par Infocom, créateurs de Zork, j'imagine que c'était une convention qui devait rester par peur de casser la compatibilité avec des vieux jeux). Mais dans les jeux contemporains, je n'ai pas croisé beaucoup de puzzles basés sur l'obscurité ; en vérité, je ne crois pas avoir utilisé ce mécanisme une seule fois dans mes jeux.

Si comme moi vous avez un jeu qui n'utilise pas l'obscurité, il faut alors qu'il y ait de la lumière dans toutes les salles. Vous avez alors plusieurs possibilités : vous pouvez donner l'attribut light à toutes vos salles, mais c'est long et ennuyeux pour vous (sans compter que si vous oubliez ne serait-ce qu'une seule fois, vous avez un bug qui peut handicaper le joueur) ; vous pouvez aussi (et c'est ce que je faisais dans mes jeux pendant un long moment) rajouter la ligne suivante dans votre routine Initialise :

give player light;
ce qui transforme le joueur en un objet qui émet de la lumière (mais ça n'a aucune incidence sur les réponses du jeu - >x moi ne va pas vous répondre "Vous brûlez de mille feux."), ce qui résout bien le problème du "je veux que le joueur n'entre jamais dans une pièce noire". Cependant, il faut faire attention : si vous utilisez la routine ChangePlayer(obj) à un moment dans votre code, il faudra penser à donner l'attribut light à obj, et de même à chaque fois que vous utilisez cette commande.

Il y a cependant une autre solution, plus simple et qui évite des calculs inutiles (même si ça n'aura sans doute aucune incidence en terme de performances sur votre jeu...). Quand le joueur entre dans une pièce, Inform consulte la routine OffersLight, qui lui dit de regarder tous les objets dans la salle (y compris la salle elle-même) et de chercher si un de ces objets a l'attribut light, et de renvoyer vrai si il y en a au moins un (Quand vous faites la première solution, c'est la salle elle-même qui l'a ; quand vous faites la deuxième solution, c'est l'objet joueur). Une alternative est donc de remplacer ce comportement de OffersLight par un comportement beaucoup plus simple : si la salle est l'objet vide, pas de lumière (par convention et pour éviter de casser des trucs), sinon il y en a. Voilà comment ça s'implémente : il faut dire au compilateur qu'on veut remplacer la routine OffersLight, en plaçant la ligne

Replace OffersLight;
au début de votre code, avant même d'inclure le Parser et les autres fichiers (je le mets juste après le numéro de Release). Puis, juste après avoir inclus le Parser et les autres fichiers, placez ce petit bout de code :
[ OffersLight obj;
if (obj == nothing)
rfalse;
else
rtrue;
];
Et voilà ! Le problème est résolu, et désormais dès que le joueur rentre dans une salle, le jeu fait un test très facile pour déterminer si il y a de la lumière ou non.

Une alternative est la suivante : définir une classe Room d'objets qui ont par défaut l'attribut light. Concrètement ça donne ça :

Class Room has light;

Room piece1 "ma pièce";

C'est aussi efficace, et éventuellement un peu plus cohérent !

Reconnaître des noms avec des apostrophes dedans

Si l'un de vos noms ou synonymes pour un objet a un apostrophe dedans, il est possible de faire qu'Inform le reconnaisse. En fait, il y a deux choses à savoir :

La première règle, c'est parce que Inform veut pouvoir parser les commandes du genre >x l'oiseau ; une telle commande sera transformée en >x l' oiseau, puis "l'" sera reconnu comme synonyme de "le" et à partir de là il sait faire. Bref, si vous voulez reconnaître des penne all'arrabiatta, il faut écrire
name 'all^' 'arrabiatta' ...
et pas
name 'all^arrabiatta'
puisqu'en vertu de la première règle ce mot sera coupé en deux, et aucune des deux parties ne correspondra au mot que vous avez écrit, donc le parser ne comprendra pas.

parse_name est votre ami

parse_name est une routine spécifiée dans un objet (au même titre que description ou before) ; elle remplace la routine name, qui sert à contrôler les réactions du parser.

Plus précisément, voilà ce qui se passe normalement (i.e. si la routine name est présente mais pas parse_name) quand le joueur entre une commande. Le parser essaie de trouver à quel objet ces mots font référence : si il n'y a pas de routine parse_name, le parser regarde la routine name, qui donne une liste de mots faisant référence à l'objet, et donne "un point" à l'objet pour chaque mot entré par le joueur qui correspond à un des mots indiqués dans name ; l'objet avec le plus de points "gagne" (ie est choisi par le parser comme destinataire de l'action), et si deux objets sont à égalité, le parser renvoie "Précisez : l'objet1 ou l'objet2 ?".

Maintenant, si au lieu d'indiquer un name pour l'objet, vous indiquez un parse_name, le parser va demander à cette routine de lui renvoyer un score pour les mots donnés par le joueur, et c'est là que ça devient rigolo. Avec une routine parse_name, vous pouvez :

Souvenez-vous-en !

Éviter (très) localement une clarification

Mettons que vous ayiez deux objets par terre, réagissant au même mot, tel que vous voulez que l'action >prendre x déclenche une action où le joueur prend les deux à la fois. L'action >prendre x déclenche une demande de clarification du parser, clarification inutile puisque le joueur se retrouvera avec les deux en sa possession à la fin de l'action. Pour résoudre le problème, il suffit de rajouter une contrainte locale dans un parse_name pour que, dans ce cas-là et avec cette action-là, un des deux objets gagne et déclenche automatiquement l'action où le joueur prend les deux. Ce qui peut être fait avec quelque chose comme ça :

parse_name [ n w;
w = NextWord(); while(w) {
if (w == 'nom') {
if (action_to_be == ##Action && condition) {n=n+10;} else {n++;}
}
w = NextWord();
}
return n;
],
L'objet qui a ce bout de code gagnera la condition locale par rapport à son congénère qui n'a que name 'nom' dans ses attributs, et le tour est joué.

La morale de l'histoire : parse_name c'est chouette. Mais aussi : si vous voulez adapter le choix des objets en fonction de l'action du joueur, comme l'action n'a pas encore de sujet (puisque vous êtes toujours dans le parse_name), il faut utiliser action_to_be et pas action.
(Merci à un lecteur anonyme pour m'avoir signalé une erreur dans le code, depuis corrigée.)


À quoi sert life ? (Roger Firth)

Si vous avez un NPC (un objet avec l'attribut animate) ou juste un objet avec l'attribut talkable, vous pouvez définir une routine life, qui définit les réactions du jeu aux actions spécifiques aux NPCs. Il y en a dix : Attack, Kiss, WakeOther, ThrowAt, Give, Show, Ask, Tell, Order et Answer.

Mais pourquoi le faire ? Après tout, si vous mettez ces propriétés dans la routine before, ça marche exactement pareil ! (Deux exceptions cependant : Give et Show, qui doivent impérativement être mises dans life, mais on ne sait pas trop pourquoi.) Il n'y a pas vraiment de réponse satisfaisante à ceci : la meilleure réponse que j'ai à vous donner, c'est que vous pouvez ainsi mettre les verbes d'interaction dans une routine séparée et vous y retrouver plus facilement. Aussi, si vous prenez l'habitude de déclarer des routines life, le jour où vous oubliez de rajouter l'attribut talkable ou animate, le compilateur vous le fera peut-être remarquer.

Un dernier point bizarre qu'il faut que je mentionne. Si vous voulez lancer une hache à Bob, le jeu fera les actions suivantes (dans cet ordre) : regarder le before de l'objet hache pour voir si il y a quelque chose à ThrowAt ; regarder le before de Bob pour voir si il y a quelque chose à la fausse action ThrownAt ; regarder le life de Bob pour voir si il y a quelque chose à l'action ThrowAt (??!) ; renvoyer la réponse par défaut de l'échec de l'action. En clair, si vous voulez que Bob réponde au fait que vous lui lancez une hache à la figure, vous pouvez capter l'action dans son life, auquel cas il faudra utiliser le verbe ThrowAt, ou capter l'action dans son before, auquel cas il faudra utiliser le verbe ThrownAt !! C'est vraiment illogique, alors il faut s'en souvenir ! (Et espérer qu'un jour le problème sera résolu.)


Quelques astuces relatives au score (Zarf)

Le score était un élément important dans les ancêtres de la fiction interactive (Adventure, Zork...), si bien que compter le score est inclus par défaut dans le langage Inform. Cependant, il existe beaucoup de jeux (et notamment les jeux contemporains) qui n'ont pas besoin de score. On peut décider de ne jamais augmenter le score du joueur, mais cela a un inconvénient : quand le jeu se termine, un message comme celui-ci apparaîtra :
*********************
Vous avez gagné !

Dans cette partie vous avez obtenu un score de 0 sur un score maximum de 0, en 153 tours.
La bibliothèque Inform évoque le score par défaut ; il faut donc chercher à passer outre. En fait, le score est géré par la routine ScoreSub ; il faut la remplacer par la routine de votre choix. Ici on va faire une routine qui affiche "Il n'y a pas de score dans ce jeu." en réponse à la commande >SCORE du joueur, et rien sinon, de telle sorte que le message affiché sera un simple "Vous avez gagné !". Pour ce faire :

Je mentionne une astuce de plus : si par hasard vous ne voulez pas que le message "[Votre score vient d'augmenter de x points.]" s'affiche automatiquement à chaque fois que le joueur gagne des points, il suffit de faire notify_mode = false; dans votre code source (notify_mode est la variable qui allume ou éteint ce comportement). Si vous voulez changer la teneur de ce message, par exemple pour qu'il colle plus à l'ambiance de votre jeu, allez chercher dans la bibliothèque French.h (ou English.h si votre jeu est en anglais), c'est le message 50 dans la routine Miscellany.

Enfin, si vous voulez être perfectionniste et faire disparaître le "Score : 0" dans l'en-tête du jeu quand vous y jouez (celui qui affiche le nom du lieu, le score et le nombre de tours), il faudra remplacer la routine DrawStatusLine par une routine de votre cru ; par contre, je n'ai pas d'exemple de code qui pourrait vous servir !


N'utilisez pas l'attribut general

L'attribut general, qui peut être donné à n'importe quel objet, est un attribut "inutile", dans le sens où il ne modifie rien du point de vue du joueur. Du point de vue de l'auteur, il peut être utilisé comme booléen pour un objet ; par exemple, si vous voulez contrôler si le joueur a lu le livre posé sur la table, vous donnez l'attribut general au livre quand le joueur le lit pour la première fois pour signifier qu'il a été lu.

Cependant, le problème de cet attribut est qu'il est... général. Il peut être utilisé pour tout et n'importe quoi, et par-dessus tout son nom est pensé pour ne rien décrire. C'est de mon point de vue un gros problème : si vous devez relire votre code et que vous avez quatre ou cinq attributs general sur des objets différents, chacun dénotant un état / une action différents, vous pouvez vous perdre. Et même si vous avez mis un commentaire pour savoir, pour chaque objet, à quoi correspond l'attribut general dans ce cas-là, essayez de vous y retrouver quand un objet fait appel à l'attribut general d'un autre objet...

Bref, même si cet attribut part d'un bon sentiment, puisqu'il donne un booléen gratuit à l'auteur, ça n'est pas une bonne pratique que de l'utiliser. Mieux vaut déclarer une variable locale au sein de votre objet, l'initialiser à 0, la faire passer à 1 quand y'en a besoin, et surtout (le plus important !) donner un nom explicite à cette variable ! Au lieu de tester si l'objet livre a l'attribut general, mieux vaut écrire if (livre.lu ==1) {..., c'est beaucoup mieux pour s'y retrouver quand on relit son code (ou le code de quelqu'un).


Réglages par défaut du mode de description du monde (superbrief/brief/verbose) et de l'inventaire (en ligne ou en liste)

Vous pouvez contrôler la façon dont votre jeu décrit les salles et l'inventaire du joueur par défaut. Les réglages par défaut de Inform sont : mode brief (la description d'une salle déjà visitée n'est pas réécrite) et inventaire "en liste". Personnellement, je n'aime ni l'un ni l'autre ! Pour changer ces réglages par défaut, il faut utiliser quelques commandes exotiques, qui doivent être placées dans la routine Initialise() (ou ailleurs si vous voulez changer ces modes de description pendant le jeu, pourquoi pas !).

Pour le mode de description des salles, vous devez utiliser la commande suivante :

lookmode = 2;
Ça c'est pour le mode verbeux (les salles sont toujours décrites). Si jamais vous le voulez vraiment, vous pouvez le mettre à 0 pour le mode superbrief, qui n'affiche jamais la description des salles quand le joueur y entre. Et 1, c'est la valeur par défaut, correspondant au mode brief. À noter que vous modifiez ainsi les réglages par défaut du jeu, mais si le joueur n'est pas content, il peut toujours changer le mode de description en tapant superbrief, brief ou verbose (à moins que vous ne l'en empêchiez en hackant sordidement dans les bibliothèques, mais c'est une autre histoire !).

En ce qui concerne le mode d'affichage de l'inventaire, si vous voulez que l'inventaire soit affiché sous la forme d'une phrase ("Vous avez une flûte, une guitare et un hippopotame."), il faut utiliser la commande suivante :

inventory_style = ENGLISH_BIT+RECURSE_BIT+FULLINV_BIT;
(Ne cherchez pas trop à quoi ça correspond, ce sont des réglages internes de la bibliothèque Inform 6 qui décrivent comment décrire l'inventaire.) Si vous voulez revenir à une description en cascade à un moment du jeu, il suffit de mettre inventory_style à 0, Inform se chargera du reste. À noter encore une fois que vous modifiez ainsi les réglages par défaut du jeu mais que le joueur peut toujours changer le mode de description de l'inventaire en tapant ">inventaire haut" (pour l'affichage en liste) ou ">inventaire large" (des commandes que personne ne semble connaître non plus !).

Attendre que le joueur appuie sur une touche (Roger Firth)

Parfois, vous voulez attendre que le joueur appuie sur une touche. Il y a plein de bonnes raisons : imposer un rythme dans votre texte, éviter les "[MORE]" disgracieux qui s'affichent sur l'écran (même si il faut garder en tête que les dimensions d'écrans diffèrent, et que le [MORE] peut s'afficher n'importe quand !), ou encore faire comme si le joueur tapait une commande (vous affichez un faux parser ">", puis à chaque touche vous écrivez la lettre suivante, jusqu'à ce qu'il n'y ait plus de lettres - par exemple, dans Filaments, votre ami dit quelque chose de stupide, et le jeu vous "force" à "écrire" >etrangler jonas, pour illustrer la colère de votre personnage au sein de la cinématique).

Pour faire ceci, utilisez la commande suivante :

KeyCharPrimitive();
Si je ne m'abuse, cette commande n'est pas vraiment connue (elle n'est pas dans le Designer's Manual). J'utilisais une routine ad-hoc, piquée dans le code source de Filaments, mais quelqu'un m'a fait remarquer sur le forum que cette routine risquait de ne pas être compatible Glulx - alors que normalement y'a aucun problème avec KeyCharPrimitive !

Effacer l'écran (Roger Firth)

Si vous devez effacer l'écran, il y a deux façons de le faire : vous pouvez utiliser un opcode :
@erase_window -1;
Ou vous pouvez utiliser tout simplement la commande équivalente suivante :
ClearScreen();
Cette dernière n'est pas connue de beaucoup de gens, et pour tout dire elle n'est même pas dans le Designer's Manual !!

"Grammar properties may not work correctly" (Zarf)

Celui-ci est un bug assez ésotérique, et pas très bien documenté ; comme j'ai déjà eu affaire à lui, je le mentionne ici. Quand on ne le connaît pas, on a l'impression qu'il apparaît plus ou moins au hasard, et son comportement est pour le moins perturbant. Il s'agit d'un warning qui apparaît parfois quand vous compilez votre jeu avec des switches de debug (comme -X) ; il n'apparaît en général que si vous avez des gros tableaux dans votre jeu, ou un gros dictionnaire (ça vous apprendra à connaître autant de mots).

Ce warning est un bug lié à la mémoire, et plus particulièrement à l'adressage de la Z-Machine. Apparemment, il y a 65536 adresses possibles pour la Z-Machine, et pour économiser de la place elles sont stockées sous la forme suivante : la mémoire commence à 0, s'arrête à 32768 et continue à -32768, jusqu'à -1. Ceci crée une situation potentiellement dangereuse quand votre dictionnaire dépasse l'adresse 32768 (ou $8000, en hexadécimal) : si votre dictionnaire est "à cheval" entre ces deux parties de la mémoire, il peut y avoir des problèmes. Apparemment, dans certaines parties de la bibliothèque Inform, on souhaite parfois stocker l'opposé de l'adresse du mot auquel on fait référence (je ne sais pas trop pourquoi, mais peut-être pour accoler un bit d'information de plus au mot, sous la forme de la présence ou de l'absence d'un signe moins) ; un test un peu compliqué se alors charge de retrouver le mot auquel on faisait référence. Sauf que si par malheur, l'adresse i et l'adresse -i correspondent toutes deux à un mot, le test n'est pas capable de savoir lequel on veut, et patatras !

Il faut donc "décaler" le bloc mémoire qui correspond au dictionnaire, de sorte que, pour n'importe quelle adresse i qui correspond à un mot (plus précisément, le début d'un mot du dictionnaire, qui est lui-même un bloc de taille standardisée, égale à 6 ou 9 mais la même pour tout les mots), l'adresse -i ne corresponde pas au début du mot. En gros, si vous voyez votre mémoire comme un ruban qui va de 0 à 65536, il faut que quand vous le repliez au milieu, les blocs des deux morceaux ne soient pas parfaitement alignés. Vous suivez ?

C'est cette condition bizarre qui fait agir votre jeu de façon étrange. Supprimez une chaîne de caractère, une description, et le bug sera toujours là ; changez la taille d'un tableau, supprimez une abbréviation, le bug disparaît... et peut réapparaître à tout moment ! Heureusement, la solution est fort simple : tant que vous n'avez pas fini de travailler sur votre jeu, ignorez le warning. Au moment où vous avez fini, compilez le jeu avec le switch -X, et regardez si vous avez ce problème. Si non, plus la peine de s'en soucier : le hasard a fait que votre mémoire est telle que les deux bouts ne se superposent pas bien (ie la mémoire n'est pas alignée). Si par contre vous l'avez toujours, ajoutez la ligne suivante à votre code :

Array pousse_dictionnaire 2;
Ce bout de code aura pour effet de déclarer un tableau (que vous n'utiliserez pas), ce qui décalera toute la mémoire d'après (dont le dictionnaire) de 4 bits. (Normalement ça devrait faire disparaître le warning, mais si par hasard non, essayez de remplacer 2 par 3.) Bon, d'accord, vous aurez un warning que ce tableau n'est pas utilisé, mais ça n'aura aucune incidence sur le jeu, alors que l'autre warning peut, lui, le faire bugguer !

Si votre jeu est long, utilisez les abréviations

Pour tout dire, ce point n'est pas forcément très utile, mais bizarrement j'aime bien, et j'espère que ça pourra vous amuser autant que moi. C'est mieux si vous utilisez cette astuce pour un jeu assez gros (disons un jeu où la source fait plus de 100Ko), mais vous pouvez vous amuser et appliquer ceci à tout ce que vous voulez.

Le compilateur Inform a la possibilité de faire des abréviations, c'est à dire représenter certains groupes de lettres qui apparaissent dans les textes de votre jeu par des raccourcis de façon interne. Vous avez droit à 64 abréviations. Concrètement, pour un jeu en français, Inform pourrait stocker la chaîne de caractères " vous " quelque part, et indiquer qu'il faut l'afficher par un caractère spécial. Si la chaîne de caractères est utilisée suffisamment, ça peut faire diminuer la taille du jeu final de quelques Ko. Bien sûr, ce genre de choses étaient utiles dans le temps où les ressources (stockage, bande passante, etc) étaient rares, mais de nos jours, ça ne sert pas à grand-chose. Mais c'est rigolo ! Alors j'en parle.

(Note : si votre jeu est un peu trop gros pour rentrer dans du .z8, c'est à dire la taille du fichier final est un peu plus grosse que 512Ko, vous pouvez essayer d'utiliser les abréviations pour le faire rentrer dans les 512Ko. Et pourquoi vouloir utiliser le format .z8 quand il y a Glulxe ? Parce qu'il y a beaucoup plus d'implantations de Z-Machines que d'implantations de Glulxe. Par exemple, vous pourrez jouer à votre jeu sur un Commodore 64 grâce à Zeugma tout en écoutant The Cure sur votre radio-cassette. C'est ça la technologie.)

Pour déclarer vos abréviations, il faut le faire au tout début de votre code, avant tout texte ; en général je le fais après avoir déclaré le nom du jeu et le numéro de Release. Voilà comment ça se fait :

Abbreviate "Vous " " vous " ", " ". " "ez ";
On déclare donc ici 5 abréviations ; notez que des abréviations de 2 caractères sont possibles (elles économisent en moyenne 0.6 octets par occurence). Ça n'est pas fini, il faut maintenant rajouter à votre makefile les lettres -e ou -e -f (-e veut dire qu'il faut utiliser les abréviations, -f veut dire qu'il faut afficher après la compilation le nombre d'octets économisés). Attention : un bug sur certaines versions du compilateur Inform 6 faisait qu'on ne pouvait pas mettre d'accents dans les abréviations - il a été réglé en août 2013, c'est à dire que tout va bien pour les version 6.33 et plus du compilateur.

Et si vous ne savez pas quelles abréviations utiliser, Inform le fera pour vous si vous rajoutez au makefile -u : ça prend du temps et vous le voyez chercher quelles abréviations sont les meilleures, et à la fin il vous donne 64 abréviations ! Il suffit alors de les recopier dans votre code et de constater les économies. Attention je le redis : même si Inform vous dit qu'il faut utiliser une abréviation qui contient un accent, il sera incapable de faire l'abréviation quand vous lui demanderez, alors n'essayez pas (du coup, ça vous laisse deux ou trois abréviations en plus que vous pouvez fixer à ce que vous voulez, par exemple un des candidats qui a été rejeté dans le processus). Et sachant que les abréviations renvoyées par le compilateur ne sont parfois pas optimales (par exemple, " passeport" est mieux que "passeport" car il y a un espace en plus, même si il apparaît un petit peu moins de fois - et sans compter que certaines abréviations suggérées auront des accents, donc vous avez un peu de place qui reste parmi les 64 abréviations), vous pouvez vous amuser à essayer de battre les optimisations du compilateur ! Personnellement, les économies que j'ai constatées sont de l'ordre de 10%, parfois plus. Bien sûr, on parle ici d'économiser 20Ko pour un fichier de 200Ko (les aventures en mode texte sont plutôt économes en terme de ressources en général...), mais ça occupe un samedi soir à moindres frais.


Cette page web est optimisée avec grand soin pour Netscape 2.0. Les opinions exprimées sur ce site Internet sont les miennes et ne représentent pas les opinions de toute institution à laquelle je suis rattaché.

Licence Creative Commons
Tout élément de cette page est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Pas de Modification 3.0 non transposé.

web analytics