Tout d’abord, merci pour votre patience. Oui, cet article est la suite de celui-ci et en parallèle de l’article de smwhr. (L’article de smwhr vous explique certaines fonctionnalités basiques pour manipuler les listes.)

Aujourd’hui, je vous explique quelle est la vraie puissance des listes, de mon point de vue.

Comme vous le savez, principalement bien expliqué par l’article de smwhr, les listes ink peuvent être utilisées pour gérer un inventaire.

Moi, ce que j’aime faire avec, c’est les utiliser pour ajouter un peu d’aléatoire dans mes histoires. Cela va les rendre plus génériques. L’idée, c’est que les lecteurs découvrent de nouvelles choses en rejouant le jeu, ou en discutant entre eux.

Changer le nom de personnages non joueurs

Changer les noms de mes personnages à chaque partie, par exemple. C’est très simple à faire, et ça permet d’ajouter un variation assez sympa qui donne l’impression que c’est une histoire complètement différente d’une partie à l’autre (alors qu’en fait, le changement n’est pas si grand).

Pour des raisons de facilité d’utilisation, je choisis tout de même dans l’écriture le genre du personnage. Est-il masculin ou féminin ? Ou a-t-il un prénom épicène, qui peut aller à un personnage masculin ou à un personnage féminin ?

La première étape consiste à lister les différents prénoms qu’on veut pouvoir assigner à nos personnages. Par exemple, pour les noms masculins :

// Créer une réserve de tous les noms (divisé en Masculin, Féminin, Épicène)
LIST NAMES_M = Charles, Harry, Aurélien, Albert
LIST NAMES_F = …
LIST NAMES_E = …

Rien de bien compliqué je pense, non ? Vous avez peut-être remarqué que je n’initialise pas mes listes avec des éléments activés. C’est parce que je me retrouve toujours à oublier quand j’ajoute de nouveaux éléments. Je préfère initialiser le plus clairement possible, tout simplement sans avoir à faire de parenthèses et faire la commande LIST_ALL qui les ajoute automatiquement à ma variable.

~ NAMES_M = LIST_ALL(NAMES_M)
~ NAMES_F = LIST_ALL(NAMES_F)
~ NAMES_U = LIST_ALL(NAMES_U)

Après, on va appeler un de nos personnage d’un nom de code. Ce sera celui qu’on utilisera tout au long de la fiction interactive pour représenter ce personnage. C’est là où c’est le plus délicat.

Soit je vais appeler l’un de mes personnages PersoA, ce que je trouve assez rédhibitoire pour écrire à propos de ce personnage, soit je lui donne un nom de code.

Par exemple, c’est le personnage pirate de l’histoire. Même si le lecteur-joueur ne le sait pas, il boit du rhum, possède un bateau pirate et vogue sur la mer pour « faire des profits ». Dans le code, ce sera PersoPirate.

Son nom ? PersoPirate est un homme, du coup son nom vient de la réserve NAMES_M + NAMES_E, c’est-à-dire de la réserve des noms masculins et des noms épicènes.

Comment on fait pour piocher un nom au hasard ? On utilise la fonction magique LIST_RANDOM, ma petite favorite. (Pour l’instant je simplifie volontairement, en tirant le nom de la réserve des prénom masculins.)

VAR PersoPirateName = ()
~ PersoPirateName = LIST_RANDOM(NAMES_M)

Vous êtes des pros, maintenant, après tous les autres tutos. PersoPirateName est déclaré en tant que variable. Mais d’un type un peu particulier, le type « list » ce qui veut dire qu’elle peut contenir des valeurs qui font partie d’une liste, comme le nom tiré au hasard. (De la même manière que dans le tuto de smwhr, nous avions le saladier.)

La seconde ligne, c’est celle qui nous intéresse. On affecte à cette nouvelle variable une valeur au hasard parmi les valeurs de la liste passée en argument (entre parenthèses).

Petit aparté sur les parenthèses et les listes

La partie suivante creuse plus en profondeur les notations pour créer des listes. Si vous préférez, vous pouvez sautez et aller directement à la partie suivante plus bas.

Vous savez qu’on peut définir/préciser une liste juste avec des parenthèses et donc écrire :

LIST ANIMAUX = CHAT, SOURIS, DRAGON
{LIST_RANDOM((CHAT, SOURIS))}

Notez bien qu’il y a deux paires de parenthèses autour de CHAT et de SOURIS. Les accolades autour de la ligne ne sont là que pour afficher le résultat directement.

Ces deux paires de parenthèses sont de deux origines différentes. La paire extérieure est pour les arguments de la fonction. Les fonctions ink, comme vous avez déjà pu le remarquer, sont composées d’un nom en LETTRES_CAPITALES_AVEC_TIRET_HUIT accompagné d’une paire de parenthèses qui délimite les arguments d’entrée de la fonction. Ici, LIST_RANDOM veut une LIST, ou une VAR de type liste comme argument.

Or, créer une liste temporaire est facilement faisable avec une paire de parenthèses. Il s’agit ici des parenthèses intérieures ! Elles définissent la liste composée de deux éléments (CHAT et SOURIS) qui ont été définis par la liste ANIMAUX. Contrairement à la liste ANIMAUX, elles ne contiennent pas le DRAGON.

Êtes-vous prêt pour un défi un peu plus grand ? Juste histoire de savoir si vous avez bien compris cette histoire de parenthèses ? 

Il faut savoir que les listes se combinent avec un + entre deux listes, ou entre une liste et un élément d’une liste. On peut donc réécrire cette dernière ligne… {LIST_RANDOM(() + (CHAT) + (() + SOURIS))} !

Avouez que c’est mignon avec toutes ces paires de parenthèses en plus ! Décomposons doucement cette nouvelle écriture :

{LIST_RANDOM(() + (CHAT)  + (() + SOURIS))}

Le () est juste une liste vide. Totalement inutile la plupart du temps. Sauf évidemment quand on veut (ré)initialiser une variable pour qu’elle ne contienne rien.

Le plus qui suit combine cette liste vide avec l’élément suivant.

Ce (CHAT) définit une liste qui contient uniquement CHAT. Elle se combine à la liste vide automatiquement, les deux sont de type liste et le résultat, c’est une liste qui contient uniquement la valeur CHAT.

Le plus suivant, comme le précédent, sert à combiner les listes ensembles.

Avec (() + SOURIS), on a une liste déclarée, car on la reconnaît avec la paire de parenthèses. Elle est elle-même composée du dernier cas de composition : une liste vide () et directement une valeur SOURIS. Quand ink combine une valeur de liste et une liste, ça ajoute automatiquement cette valeur dans la liste précédente.

Retour à nos dragons… Euh, à nos personnages aléatoires

Nous avons donc un personnage PersoPirate, qui a un nom qui change à chaque fois. Quand le joueur le rencontre, il ne sait pas si c’est PersoPirate ou PersoChoupi qu’il a en face de lui. Et PersoPirate adore arnaquer les gens, tandis que PersoChoupi les laisse toujours gagner. C’est déjà un peu de surprise préservée pour votre lecteur-joueur, mais c’est très facile de se rendre compte qu’il n’y a que le nom qui change. Et du coup, une fois cette découverte passée, c’est raté. On rencontre PersoChoupi toujours quand on se balade dans le parc avec les fleurs, et on rencontre toujours PersoPirate au bar, alors qu’il boit des canons et qu’il chante (mal) à tue-tête.

Résultat, le lecteur-joueur s’en moque du nom qu’on a mis tant d’efforts à rendre aléatoire.

Mais il y a une solution : aller à un niveau de généricité encore plus haut. C’est-à-dire, la première fois, c’est bien que vous rencontriez le pirate dans la taverne, mais peut-être qu’on peut avoir d’autres situations. Comment faire en sorte qu’on rencontre soit PersoChoupi, soit PersoPirate dans la taverne, soit personne ? Ça me rappelle un tuto sur l’aléatoire, ça… Donc oui, si on veut avoir plus de chances de trouver PersoPirate à la taverne, c’est la structure shuffle qu’il faudrait utiliser.

Mais ce n’est pas cette structure que je veux explorer dans ce tuto-ci. Je veux qu’on ait autant de chances de rencontrer le pirate que le perso choupi sur la place du village. Je vais donc avoir une liste de personnages qu’on peut rencontrer à cet endroit.

VAR RencontrePlaceVillage = ()
~ RencontrePlaceVillage = (PersoChoupi, PersoPirate)

OK, pour l’instant, rien d’extraordinaire. Je veux sélectionner un personnage au hasard et présenter l’interaction au joueur. Du coup, la sélection se fera avec LIST_RANDOM et cette nouvelle liste qu’on vient de créer.

- (VillageRencontre)
VAR persoRandom = ()
~ persoRandom = LIST_RANDOM(RencontrePlaceVillage)
* {persoRandom == PersoChoupi} Vous voyez un(e) jeune ->RencontrerChoupinouSurPlaceVillage
* {persoRandom == PersoPirate} Vous voyez un(e) moins jeune ->RencontrerPirateSurPlaceVillage
+ ->
// Au cas où il n'y aurait aucune rencontre.
Je n'ai plus personne à rencontrer sur la place du village

Puis, suivant le résultat, j’affiche un choix ou l’autre. J’ai aussi un choix par défaut si aucun des deux n’est présent, c’est une bonne pratique même si vous ne pensez jamais rentrer dans ce cas de figure.

La mise en place de ces « fallbacks » ou choix par défaut est une des raisons qui fait la différence entre un code ink qui plante et un qui ne plante pas. Un choix par défaut assure que le lecteur-joueur aura toujours un chemin, même si aucune des autres solutions ne s’applique à lui.

Aussi, on ne peut choisir les choix en * qu’une seule fois. Ce n’est pas un souci pour les choix de « première rencontre » avec un personnage, comme on ne veut le rencontrer pour la première qu’une seule fois (logique). Par contre, le choix par défaut, lui doit rester possible plusieurs fois dans l’histoire, d’où le +).

Si vous ne savez pas quand utiliser un choix par défaut, utilisez-en un dès que votre choix devient complexe, surtout si vous avez une structure en « hub », où le joueur repassera souvent dans des conditions différentes.

Ce que j’aime bien faire, c’est noter que le personnage a été rencontré, et le retirer des futures possibilités. Mon code de RencontrerChoupinouSurPlaceVillage ou RencontrerPirateSurPlaceVillage fait appel à la même fonction qui enlève ce personnage des possibilités de rencontres à cet endroit.

=== RencontrerChoupinouSurPlaceVillage

~ EnleverPersoRencontre(PersoChoupi)
J'ai rencontré Perso Choupi, c'est le plus choupi des persos !
-> VillageRencontre

=== RencontrerPirateSurPlaceVillage

~ EnleverPersoRencontre(PersoPirate)
J'ai rencontré le terrible pirate qui hante les cinq mers !
-> VillageRencontre

=== function EnleverPersoRencontre(perso)

~ RencontrePlaceVillage -= perso
~ return

Il y a aussi une opportunité pour faire des textes différents, en utilisant les autres systèmes d’aléatoire, je vous renvoie à l’article précédent de cette série.

Une autre chose à noter est que dans ce court exemple, je le fais boucler sur lui-même, alors le choix par défaut intervient à un moment. Dans un vrai texte, ce n’est certainement pas ce qu’on veut. J’imagine facilement donner quelques personnages présents parmi l’ensemble possible, et laisser le joueur faire toutes les rencontres qu’il veut avant de passer à la suite.

Sauriez-vous faire le changement pour sélectionner jusqu’à trois personnages aléatoirement et les proposer en choix à rencontrer au joueur, tout en lui laissant un choix d’aller ailleurs ?

Mettez en pratique ce que vous venez de lire, et revenez voir la suite de l’article quand vous l’avez fait. C’est super important de devenir à l’aise avec les listes et d’apprendre à enlever, ajouter, et les manipuler si on veut avoir plein de possibilités pour ses histoires.

La solution

Alors, en premier, vous avez dû passer par une étape où vous avez créé les nouveaux persos dans la liste. Vous les avez aussi bien sûr ajoutés dans la liste des personnages à rencontrer à la place du village.

Pour moi, j’ai écrit le texte suivant. Désolé, j’écris ça à neuf heures du matin, je n’ai pas beaucoup d’inspiration.

LIST PERSOS = PersoChoupi, PersoPirate, PersoEnfant, PersoStresse, PersoMeme, PersoBebe, PersoDragueur, PersoBatailleur
VAR RencontrePlaceVillage = ()
~ RencontrePlaceVillage = (PersoChoupi, PersoPirate, PersoEnfant, PersoStresse, PersoMeme, PersoBebe, PersoDragueur, PersoBatailleur)

Ce qui est important, c’est qu’il y ait beaucoup plus d’entrées que le nombre que vous voulez présenter au joueur.

Le point suivant, c’est évidemment qu’il faut prendre en compte tous ces nouveaux choix. Rappelez-vous, il existe un opérateur ink qui permet des savoir si une liste contient un élément spécifique : has ou ?

Ma version donne ça.

* {persoRandom has PersoChoupi} Vous voyez un(e) jeune -> RencontrerChoupinouSurPlaceVillage
* {persoRandom has PersoPirate} Vous voyez un(e) moins jeune -> RencontrerPirateSurPlaceVillage
* {persoRandom has PersoEnfant} Vous voyez un(e) enfant -> RencontrerEnfantSurPlaceVillage
* {persoRandom has PersoStresse} Vous voyez quelqu'un faire les cent pas -> RencontrerStresseSurPlaceVillage
* {persoRandom has PersoMeme} Vous voyez une vieille mémé qui tricote -> RencontrerMemeSurPlaceVillage
* {persoRandom has PersoBebe} Curieux... Un bébé est tout seul -> RencontrerBebeSurPlaceVillage
* {persoRandom has PersoDragueur} Un lourdaud de première semble vouloir vous brancher -> RencontrerDragueurSurPlaceVillage
* {persoRandom has PersoBatailleur} Un gars plein de cicatrices gonfle ses biceps dès que la moindre personne fait un geste en sa direction -> RencontrerBatailleurSurPlaceVillage

Bien sûr, l’important, c’est que chacun de vos persos créés ait son entrée. Et que dans chaque entrée, vous ayez le texte personnalisé et surtout que vous n’oubliez pas d’avoir le petit paragraphe où vous enlevez ce personnage-là de la liste à rencontrer sur la place du village.

Le dernier gros point de modification, pour moi, c’est de la structure. Au lieu d’avoir un choix de personnage à chaque fois, on en veut plusieurs. Ma solution est rapide, mais loin d’être parfaite.

- (VillageRencontreGenerateur)

VAR persoRandom = ()
~ persoRandom = LIST_RANDOM(RencontrePlaceVillage)
~ persoRandom += LIST_RANDOM(RencontrePlaceVillage)
~ persoRandom += LIST_RANDOM(RencontrePlaceVillage)

DEBUG ==> {persoRandom}

{persoRandom == (): Il n'y a vraiment plus personne à rencontrer, je devrais arrêter de tourner en rond.}

- (VillageRencontreGenere)

Son problème ? Il peut très bien y avoir trois fois la même entrée qui est choisie. Comment faire pour changer ça ? Une solution tout aussi facile serait d’enlever dans les entrées possible à chaque fois qu’on tire un nouveau personnage comme ceci

VAR persoRandom = ()
~ persoRandom = LIST_RANDOM(RencontrePlaceVillage)
~ RencontrePlaceVillage -= persoRandom
~ persoRandom += LIST_RANDOM(RencontrePlaceVillage)
~ RencontrePlaceVillage -= persoRandom
~ persoRandom += LIST_RANDOM(RencontrePlaceVillage)
~ RencontrePlaceVillage -= persoRandom

Le code final une fois tout assemblé :

LIST PERSOS = PersoChoupi, PersoPirate, PersoEnfant, PersoStresse, PersoMeme, PersoBebe, PersoDragueur, PersoBatailleur
VAR RencontrePlaceVillage = ()
~ RencontrePlaceVillage = (PersoChoupi, PersoPirate, PersoEnfant, PersoStresse, PersoMeme, PersoBebe, PersoDragueur, PersoBatailleur)

- (VillageRencontreGenerateur)

VAR persoRandom= ()
~ persoRandom = LIST_RANDOM(RencontrePlaceVillage)
~ RencontrePlaceVillage -= persoRandom
~ persoRandom += LIST_RANDOM(RencontrePlaceVillage)
~ RencontrePlaceVillage -= persoRandom
~ persoRandom += LIST_RANDOM(RencontrePlaceVillage)
~ RencontrePlaceVillage -= persoRandom

DEBUG ==> {persoRandom}

{persoRandom == (): Il n'y a vraiment plus personne à rencontrer, je devrais arrêter de tourner en rond.}

- (VillageRencontreGenere)
* {persoRandom?PersoChoupi} Vous voyez un(e) jeune -> RencontrerChoupinouSurPlaceVillage
* {persoRandom?PersoPirate} Vous voyez un(e) moins jeune -> RencontrerPirateSurPlaceVillage
* {persoRandom?PersoEnfant} Vous voyez un(e) enfant -> RencontrerEnfantSurPlaceVillage
* {persoRandom?PersoStresse} Vous voyez quelqu'un faire les cent pas -> RencontrerStresseSurPlaceVillage
* {persoRandom?PersoMeme} Vous voyez une vieille mémé qui tricote -> RencontrerMemeSurPlaceVillage
* {persoRandom?PersoBebe} Curieux... Un bébé est tout seul -> RencontrerBebeSurPlaceVillage
* {persoRandom?PersoDragueur} Un lourdaud de première semble vouloir vous brancher -> RencontrerDragueurSurPlaceVillage
* {persoRandom?PersoBatailleur} Un gars plein de cicatrices gonfle ses biceps dès que la moindre personne fait un geste en sa direction -> RencontrerBatailleurSurPlaceVillage
+ ->
// Par défaut.
Je n'ai plus personne à rencontrer sur la place du village
- Je pourrais rencontrer plus de monde !
+ Oh oui ! -> VillageRencontreGenerateur

=== RencontrerChoupinouSurPlaceVillage
~ EnleverPersoRencontre(PersoChoupi)
J'ai rencontré Perso Choupi, c'est le plus choupi des persos !
-> VillageRencontreGenere

=== RencontrerPirateSurPlaceVillage
~ EnleverPersoRencontre(PersoPirate)
J'ai rencontré le terrible pirate qui hante les cinq mers !
-> VillageRencontreGenere

=== RencontrerEnfantSurPlaceVillage
~ EnleverPersoRencontre(PersoEnfant)
C'était le Perso enfant!
-> VillageRencontreGenere

=== RencontrerStresseSurPlaceVillage
~ EnleverPersoRencontre(PersoStresse)
En voilà un de stressé!
-> VillageRencontreGenere

=== RencontrerMemeSurPlaceVillage
~ EnleverPersoRencontre(PersoMeme)
Elle ressemble à ma grand-mère...
-> VillageRencontreGenere

=== RencontrerBebeSurPlaceVillage
~ EnleverPersoRencontre(PersoBebe)
Un bébé qui gazouille !
-> VillageRencontreGenere

=== RencontrerDragueurSurPlaceVillage
~ EnleverPersoRencontre(PersoDragueur)
J'avais raison. Un gros lourdeau, j'ai fui. Pas besoin de faire sa connaissance, je m'en porte mieux.
-> VillageRencontreGenere

=== RencontrerBatailleurSurPlaceVillage
~ EnleverPersoRencontre(PersoBatailleur)
Il veut la baston celui-là ? S'il veut l'avoir, il va l'avoir !
-> VillageRencontreGenere

=== function EnleverPersoRencontre(perso)
~ RencontrePlaceVillage-=perso
~ return

C’est tout beau… Mais c’est juste pour la rencontre sur la place du village, ça.

Petit défi de la fin ?

Certains de ces personnages, on pourrait aussi les rencontrer à la taverne… Et leur texte et leur description seraient différents, non ?

Seriez vous capable d’écrire tout seul pour la rencontre à la taverne, et la rencontre près des champs de fleurs ?

Attention, j’ai toujours supposé qu’on ne pouvait rencontrer qu’une seule fois chaque personnage. Donc si vous rencontrez le pirate à la taverne, il ne fait plus partie des possibilités de rencontre sur la place du village !

Si vous y arrivez, avec l’article de smwhr sur l’inventaire avec les listes, je pense que vous êtes maintenant des pro des listes, et vous pouvez les utiliser pour vos histoires en utilisant toute leur puissance !