Skip to content

Instantly share code, notes, and snippets.

@floriancargoet
Last active February 14, 2025 12:06
Show Gist options
  • Save floriancargoet/ad139f1739a3372171a3c763f50059aa to your computer and use it in GitHub Desktop.
Save floriancargoet/ad139f1739a3372171a3c763f50059aa to your computer and use it in GitHub Desktop.
Relations ink - fiction-interactive.fr
INCLUDE relations-system.ink
INCLUDE mes-relations.ink
INCLUDE texte.ink
INCLUDE utils.ink
/*
Objectif : il y a 3 bols, il faut déposer dans chacun le bon objet (qu'on déduit de la forme dessinée au fond du bol) et qu'il soit fait du même matériau. Au besoin, le transmuteur permet de changer le matériau d'un objet.
*/
// Quelques relations initiales
~ relate(Pièce, EstEn, Cuivre)
~ relate(Pièce, EstDeForme, Triangle) // Et ouais
~ relate(Dé, EstEn, Bois)
~ relate(Dé, EstDeForme, Carré)
~ relate(Bille, EstEn, Verre)
~ relate(Bille, EstDeForme, Cercle)
// Oh, on peut gérer un inventaire avec les relations !
~ relate(Hervé, Possède, Dé)
~ relate(Hervé, Possède, Bille)
// La pièce est dans une salle
// On veut tirer des matières au hasard sans piocher 2 fois la même.
// On fait donc une copie de la liste dont on va à chaque fois enlever un élément aléatoirement (pop_random, une fonction disponible dans l'éditeur Inky).
~ temp m = LIST_ALL(Matières)
~ relate(BolGauche, EstEn, pop_random(m))
~ relate(BolCentral, EstEn, pop_random(m))
~ relate(BolDroit, EstEn, pop_random(m))
~ temp f = LIST_ALL(Formes)
~ relate(BolGauche, Réclame, pop_random(f))
~ relate(BolCentral, Réclame, pop_random(f))
~ relate(BolDroit, Réclame, pop_random(f))
// Si c'était un vrai jeu et pas juste un exemple, on pourrait vouloir s'assurer qu'on n'est pas tombé aléatoirement sur un cas trop simple à résoudre (tous les objets déjà de la bonne matière).
-> intro
=== intro
À la lueur de sa lampe à pétrole, Sandrine termine de déchiffrer ce qui ressemble à des instructions peintes sur la paroi de la grotte. Pour votre part, plus modestement, vous essayez de lire l'heure sur votre montre. Vous savez très bien l'heure qu'il est puisque vous consultez les aiguilles au moins trois fois par ligne que Sandrine arrive à décoder.
— Détends-toi Hervé, s'il te plait. Plus tu trépignes et plus c'est difficile pour moi de me concentrer. J'arrive à la fin, je pense avoir saisi l'idée générale.
Elle prend encore cinq interminables minutes puis se redresse.
— Si je comprends bien ces symboles, il faut déposer le bon objet dans chacun de ces trois petits bols, et si on ne se trompe pas, un trésor sera revélé.
+ [Le médaillon ?]
-
— Ça dit si c'est bien le médaillon de Nicolas Flamel ? demandez-vous.
— Je ne suis pas sûre, la moitié des symboles sont ambigus, tu sais… Il me semble clair que ce n'est pas la pierre philosophale. Peut-être un autre transmuteur mineur, comme celui de tout à l'heure.
— Et si on se trompe pour les bols ?
— Classique, un énorme rocher sera libéré et roulera vers nous jusqu'à nous écraser inexorablement…
+ [Continuer] -> salle_des_bols
=== pre_fin(status)
Vous remplissez le dernier bol avec ce que vous espérez être le bon objet.
— Sandrine, comment sait-on si c'est bon ? Il ne se passe rien.
— Chut… Il y a peut-être un mécanisme qui s'active.
Vous tendez l'oreille, en vain.
— Éteins la lampe, ordonne Sandrine, je crois que je vois quelque chose !
— Mais il va faire tout noir…
— Éteins, je te dis !
Vous soulevez le verre qui protège la flamme et soufflez sur celle ci. En quelques secondes, vos yeux s'habituent à l'obscurité et vous remarquez enfin ce que Sandrine a repéré. Les bols semblent luire d'une teinte {status == Victoire:verte|rouge}. La lueur est d'abord presque imperceptible puis, petit à petit, son intensité augmente jusqu'à faiblement éclairer la salle.
— {status == Victoire:Vert|Rouge}, c'est bien, non ? Et je n'entends pas de gros rocher qui roule…
->->
=== victoire
-> pre_fin(Victoire) ->
La lueur continue à croître jusqu'à la limite de l'inconfort puis quitte soudainement les bols sous la forme de trois rayons qui vous frappent avant même que vous ayez le temps ne serait-ce que de songer à les éviter.
Pendant quelques secondes, c'est vous qui brillez de cette étrange lumière verte. Impossible de dire quoi, mais vous sentez qu'elle vous fait quelque chose, qu'elle vous change. Un frisson vous parcourt puis la lumière disparait.
— Hervé ! crie Sandrine, morte d'inquiétude. Ça va ? Où es-tu, je ne vois rien !
— Ça va, enfin, je crois, tentez-vous de la rassurer.
Vous entendez Sandrine tâtonnez dans le noir pour vous retrouver. Vous tendez la main pour attraper la sienne et tressaillez sous le choc : sa main est dure et froide.
— Aah ! hurle Sandrine. Qu'est-ce que… Ça se propage Hervé ! Arrête !
Vous lui lâchez la main mais elle continue de crier.
— Ça ne s'arrête pas ! Ma gorge ! Hervé, c'est toi le transmut-
-> END
=== defaite
-> pre_fin(Défaite) ->
La lueur continue à croître, encore et encore, et devient inconfortable. Ce n'est plus une lueur, les bols sont en flamme et vous sentez leur chaleur, qui devient vite intolérable.
Alors que vous attrapez la main de Sandrine pour courir vous réfugier dans la salle du transmuteur, les bols se mettent soudainement à irradier avec la puissance d'une étoile et vous vaporise tous les deux en un instant.
-> END
=== salle_des_bols
{ TURNS_SINCE(-> salle_du_transmuteur) == 1:
Vous reprenez le tunnel jusqu'aux bols.
- else:
Trois bols sont alignés sur un piédestal en pierre.
}
// Ne montrer que les bols tant qu'on ne les a pas tous examinés
// On pourrait gérer ça avec un simple compteur de visite (vu_bol_xyz) sur chaque choix de bol, mais c'est un article sur les relations ;-)
~ temp tous_bols_vus = LIST_COUNT(whatIs(Hervé, Connait)) == 3
+ [Bol gauche {info_bol(BolGauche)}] -> menu_bol(BolGauche) ->
+ [Bol central {info_bol(BolCentral)}] -> menu_bol(BolCentral) ->
+ [Bol droit {info_bol(BolDroit)}] -> menu_bol(BolDroit) ->
+ {tous_bols_vus}
[{salle_du_transmuteur:Aller au transmuteur|Explorer la grotte}]
-> salle_du_transmuteur
+ {tous_bols_vus}
[Inventaire]
-> inventaire ->
-
-> redirect_0(-> salle_des_bols)
=== menu_bol(bol)
~ relate(Hervé, Connait, bol)
~ temp contenu = whatIs(bol, Contient)
~ temp matière = whatIs(bol, EstEn)
~ temp symbole = whatIs(bol, Réclame)
Ce bol est {texteMatiere(matière)}.
<> {contenu:Il contient {t("un", contenu)}|Il est vide}.
<> {contenu:Sous {texteObjet("le", contenu)}|Au fond}, vous distinguez un symbole : {texteSymbole(symbole)}.
// S'il y a quelque chose dans le bol, on peut le reprendre, sinon on peut y déposer un objet
+ {contenu} [Reprendre {contenu}]
~ relate(Hervé, Possède, contenu)
~ unrelate(bol, Contient, contenu)
+ {not contenu} [Déposer] -> menu_deposer(bol) ->
// Après un dépôt, on vérifie les bols
{ verifier_bols():
- Victoire: -> victoire
- Défaite: -> defaite
}
+ [Retour]
-
->->
=== salle_du_transmuteur
{ TURNS_SINCE(-> salle_des_bols) == 1:
{La grotte n'est constituée que de deux chambres connectées par un étroit tunnel. Vous vous retrouvez donc la salle du transmuteur que vous aviez découvert précédemment avec Sandrine.|Vous vous faufilez jusqu'au transmuteur}
}
// Vu qu'on ne s'est pas embêté à gérer de manière générique la possibilité qu'une salle contiennent des objets, on gère la pièce en cuivre ainsi : si ni Hervé ni le transmuteur ni un bol ne contient la pièce, alors elle est dans la salle du transmuteur.
~ temp piece_ici = LIST_COUNT(whatIs(Contient, Pièce) + whatIs(Possède, Pièce)) == 0
{piece_ici: Un objet triangulaire traine dans la poussière.}
~ temp contenu_transmuteur = whatIs(Transmuteur, Contient)
+ {piece_ici} [Ramasser l'objet triangulaire]
Vous ramassez le triangle, qui semble {texteMatiere(whatIs(Pièce, EstEn))}. Vous reconnaissez le symbole qui est gravé sur une des faces : c'est une pièce de monnaie.
~ relate(Hervé, Possède, Pièce)
+ {contenu_transmuteur} [Récupérer {t("le", contenu_transmuteur)}]
~ relate(Hervé, Possède, contenu_transmuteur)
~ unrelate(Transmuteur, Contient, contenu_transmuteur)
+ {not contenu_transmuteur} [Déposer un objet dans le transmuteur]
-> menu_deposer(Transmuteur) ->
~ contenu_transmuteur = whatIs(Transmuteur, Contient)
{ contenu_transmuteur:
-> transmuter(contenu_transmuteur) ->
}
+ [Retourner dans la chambre des bols]
-> salle_des_bols
+ [Inventaire]
-> inventaire ->
-
-> redirect_0(-> salle_du_transmuteur)
=== menu_deposer(contenant)
~ temp objets = whatIs(Hervé, Possède)
{ LIST_COUNT(objets) == 0:
Vous n'avez rien à déposer.
->->
}
// On utilise un thread résursif pour générer un choix pour chaque item de la liste
<- thread_choix_deposer(contenant, objets, -> retour)
+ [Retour]
- (retour) // On a besoin d'un point de retour pour que le thread puisse revenir dans le tunnel
->->
=== thread_choix_deposer(contenant, liste, -> retour)
~ temp item = LIST_MIN(liste)
{ item:
<- thread_choix_deposer(contenant, liste - item, retour)
+ [Déposer {t("le", item)}]
~ unrelate(Hervé, Possède, item)
~ relate(contenant, Contient, item)
-> retour
}
=== inventaire
~ temp objets = whatIs(Hervé, Possède)
{ objets:
Vous avez :
-> recursion_objets(objets) ->
- else:
Vous n'avez rien.
}
->->
=== recursion_objets(liste)
~ temp item = LIST_MIN(liste)
{ not item: ->-> }
\- {t("un", item)}
-> recursion_objets(liste - item) ->
->->
=== transmuter(objet)
~ temp matière = whatIs(objet, EstEn)
{ not matière: ->-> }
~ temp nouvelle_matière = rotate_list_item(matière)
Le transmuteur vibre un moment puis s'arrête.
{t("Le", objet)} est désormais {texteMatiere(nouvelle_matière)}.
~ unrelate(objet, EstEn, matière)
~ relate(objet, EstEn, nouvelle_matière)
->->
=== function info_bol(bol)
// Afficher les infos uniquement si le bol est connu
~ temp connu = isRelated(Hervé, bol, Connait)
{ not connu:
~ return
}
~ temp contenu = whatIs(bol, Contient)
~ temp matière = whatIs(bol, EstEn)
~ temp texteContenu = "{contenu:contient {t("un", contenu)}|vide}"
~ return "({texteMatiere(matière)}, {texteContenu})"
=== function verifier_bols()
LIST StatutBols = Incomplet, Victoire, Défaite
~ temp contenuGauche = whatIs(BolGauche, Contient)
~ temp contenuCentral = whatIs(BolCentral, Contient)
~ temp contenuDroit = whatIs(BolDroit, Contient)
// Si < 3 contenus, le puzzle est incomplet
{ LIST_COUNT(whatIs((BolGauche, BolCentral, BolDroit), Contient)) < 3:
~ return Incomplet
}
{ verifier_bol(BolGauche) and verifier_bol(BolCentral) and verifier_bol(BolDroit):
~ return Victoire
- else:
~ return Défaite
}
=== function verifier_bol(bol)
// On valide l'objet et sa matière
~ temp contenu = whatIs(bol, Contient)
~ temp matière_contenu = whatIs(contenu, EstEn)
~ temp forme_contenu = whatIs(contenu, EstDeForme)
~ temp bol_ok = isRelated(bol, forme_contenu, Réclame) and isRelated(bol, matière_contenu, EstEn)
~ return bol_ok
// Voici les relations qu'on peut avoir dans ce jeu
LIST Relations = Contient, EstEn, EstDeForme, Réclame, Possède, Connait
// Et les choses auxquelles elles s'appliquent
LIST Personnes = Hervé, Sandrine
LIST Objets = Transmuteur, BolGauche, BolCentral, BolDroit, Pièce, Dé, Bille
LIST Matières = Cuivre, Terre, Fer, Verre, Argile, Bois
LIST Formes = Cercle, Carré, Triangle
// La fonction spéciale qui dit à ink quelles listes sont en relation
=== function relationDatabase(relation, gauche)
// Si gauche == true, ink veut connaitre la liste de gauche de la relation
// Si gauche == false, ink veut connaitre la liste de droite de la relation
// On utilise une fonction gauche_droite juste pour que ça tienne sur une ligne que qu'on voit tout d'un coup d'œil
{ relation:
// La relation EstEn lie des Objets (à gauche) à des Matières (à droite)
- EstEn: ~ return gauche_droite(gauche, Objets, Matières)
- Contient: ~ return gauche_droite(gauche, Objets, Objets)
- Réclame: ~ return gauche_droite(gauche, Objets, Formes)
- EstDeForme: ~ return gauche_droite(gauche, Objets, Formes)
- Possède: ~ return gauche_droite(gauche, Personnes, Objets)
- Connait: ~ return gauche_droite(gauche, Personnes, Objets)
}
=== function gauche_droite(gauche, listeGauche, listeDroite)
{ gauche:
~ return listeGauche
- else:
~ return listeDroite
}
// Juste le système, extrait de https://gist.github.com/joningold/6f3e8c4d7740de42c79a227a77baa8cf
// Pour l'utiliser, il faut l'inclure (INCLUDE relations-system.ink)
// Et définir deux choses:
// - la liste des relations: LIST Relations = …
// - la fonction qui dit quels types sont en relation avec quels autres types
/*--------------------
Standard utils
--------------------*/
=== function ITEM_IS_MEMBER_OF_LIST(k, list)
~ return k && LIST_ALL(list) ? k
=== function pop(ref list)
~ temp x = LIST_MIN(list)
~ list -= x
~ return x
/*--------------------
Relation API
--------------------*/
=== function validate(x1, x2, rel)
{ x1 && not ITEM_IS_MEMBER_OF_LIST(x1, relationDatabase(rel, true)):
[ ERROR: {x1} not a valid lhs for {rel} ]
~ return false
}
{ x2 && not ITEM_IS_MEMBER_OF_LIST(x2, relationDatabase(rel, false)):
[ ERROR: {x2} not a valid rhs for {rel} ]
~ return false
}
~ return true
=== function relate(x1, rel, x2)
{ not validate(x1, x2, rel):
~ return ()
}
~ _relate(x1, x2, rel)
=== function unrelate(x1, rel, x2)
{ validate(x1, x2, rel):
// rebuild the whole pair string
~ pairstore = _rebuildPairStringExcept(LIST_ALL(Relations), x1, x2, rel)
}
=== function whatIs(a1, a2)
{
- ITEM_IS_MEMBER_OF_LIST(a2, Relations):
~ return getRelatesTo(a1, a2)
- ITEM_IS_MEMBER_OF_LIST(a1, Relations):
~ return getRelatedFrom(a2, a1)
- else:
[ ERROR: whatIs needs a relation!
}
=== function getRelatesTo(x1, rel)
{ not validate(x1, (), rel):
~ return ()
}
~ temp searchSpace = LIST_ALL(relationDatabase(rel, false))
~ return _getMatchedPairs(x1, searchSpace, rel, false)
=== function getRelatedFrom(x2, rel)
{ not validate((), x2, rel):
~ return ()
}
~ temp searchSpace = LIST_ALL(relationDatabase(rel, true))
~ return _getMatchedPairs(searchSpace, x2, rel, true)
=== function isRelated(x1, x2, rel)
{ validate(x1, x2, rel):
{LIST_COUNT(x1) > 1 || LIST_COUNT(x2) > 1:
[ERROR: Testing relation {rel} on non-unary lists {x1} and {x2} ]
~ return false
}
~ return _isRelated(x1, x2, rel)
}
/*--------------------
Internal datastore functions
--------------------*/
VAR pairstore = ""
=== function _isRelated(x1, x2, rel)
~ temp relString = _pairString(x1, x2, rel)
~ return pairstore ? relString
=== function _getMatchedPairs(list1, list2, rel, getLhs)
~ temp ret = ()
{ LIST_COUNT(list1):
- 0: ~ return ()
- 1: ~ temp el2 = pop(list2)
{ el2:
{ _isRelated(list1, el2, rel):
{ getLhs:
~ ret = list1
- else:
~ ret = el2
}
}
~ return ret + _getMatchedPairs(list1, list2, rel, getLhs)
}
~ return ()
- else:
~ temp el1 = pop(list1)
~ return _getMatchedPairs(el1, list2, rel, getLhs) + _getMatchedPairs(list1, list2, rel, getLhs)
}
=== function _pairString(x1, x2, rel)
~ return ":{x1}>{rel}>{x2};"
=== function _relate(list1, list2, rel)
{ LIST_COUNT(list1):
- 0: ~ return
- 1: ~ temp el2 = pop(list2)
{ el2:
{ not _isRelated(list1, el2, rel):
~ pairstore += _pairString(list1, el2, rel)
}
~ return _relate(list1, list2, rel)
}
~ return ()
- else:
~ temp el1 = pop(list1)
~ return _relate(el1, list2, rel) + _relate(list1, list2, rel)
}
=== function _rebuildPairStringExcept(allRels, x1, x2, rel)
~ temp relEl = pop (allRels)
{
- not relEl:
~ return ""
- relEl == rel:
~ return _validPairsIn(LIST_ALL(relationDatabase(rel, true)), LIST_ALL(relationDatabase(rel, false)), relEl, x1, x2) + _rebuildPairStringExcept(allRels, x1, x2, rel)
- else:
~ return _validPairsIn(LIST_ALL(relationDatabase(relEl, true)), LIST_ALL(relationDatabase(relEl, false)), relEl, (), ()) + _rebuildPairStringExcept(allRels, x1, x2, rel)
}
=== function _validPairsIn(list1, list2, rel, not1, not2)
~ temp ret = ""
{ LIST_COUNT(list1):
- 0: ~ return ()
- 1: ~ temp el2 = pop(list2)
{ el2:
{ _isRelated(list1, el2, rel) && not (not1 ? list1 && not2 ? el2):
~ ret = _pairString(list1, el2, rel)
}
~ return ret + _validPairsIn(list1, list2, rel, not1, not2)
}
~ return ""
- else:
~ temp el1 = pop(list1)
~ return _validPairsIn(el1, list2, rel, not1, not2) + _validPairsIn(list1, list2, rel, not1, not2)
}
// Les fonctions suivantes permettent de générer du texte en utilisant le bon article.
// Sans entrer dans les détails, on peut demander texteObjet("le", objet) et obtenir selon le genre, le nombre et la première lettre du mot : le persil, la coriandre, l'aneth, les herbes. Ici c'est simplifié puisqu'on ne gère pas le pluriel.
=== function t(article, objet)
~ temp matière = whatIs(objet, EstEn)
{ matière:
~ return texteObjet(article, objet) + " " + texteMatiere(matière)
- else:
~ return texteObjet(article, objet)
}
=== function texteArticle(art, def, indef, Def, Indef)
{ art:
- "le": ~ return def
- "un": ~ return indef
- "Le": ~ return Def
- "Un": ~ return Indef
}
=== function texteObjet(article, objet)
{ objet:
- Transmuteur: ~ return texteArticle(article, "le ", "un ", "Le ", "Un ") + "transmuteur"
- BolGauche: ~ return texteArticle(article, "le ", "un ", "Le ", "Un ") + "bol"
- BolCentral: ~ return texteArticle(article, "le ", "un ", "Le ", "Un ") + "bol"
- BolDroit: ~ return texteArticle(article, "le ", "un ", "Le ", "Un ") + "bol"
- Pièce: ~ return texteArticle(article, "la ", "une ", "La ", "Une ") + "pièce"
- Dé: ~ return texteArticle(article, "le ", "un ", "Le ", "Un ") + "dé"
- Bille: ~ return texteArticle(article, "la ", "une ", "La ", "Une ") + "bille"
}
=== function texteMatiere(matière)
{ matière:
- Cuivre: ~ return "en cuivre"
- Terre: ~ return "en terre"
- Fer: ~ return "en fer"
- Verre: ~ return "en verre"
- Argile: ~ return "en argile"
- Bois: ~ return "en bois"
}
=== function texteSymbole(symbole)
{ symbole:
- Triangle: ~ return "trois séries de trous poinçonnés sont arrangées en triangle"
- Carré: ~ return "quatre petits sillons ont été creusés et forment un carré approximatif"
- Cercle: ~ return "une marque noire, vraisemblablement tracée au charbon, dessine un cercle"
}
// Fonction donnée par Inky pour extraire d'une liste un élément au hasard
=== function pop_random(ref list)
~ temp el = LIST_RANDOM(list)
~ list -= el
~ return el
// Ce noeud sert à forcer un changement de noeud quand on veut rester sur un noeud mais en remettant TURNS_SINCE à 0.
=== redirect_0(-> knot)
-> knot
=== function rotate_list_item(item)
~ temp all = LIST_ALL(item)
~ temp max = LIST_MAX(all)
{ item == max:
~ return LIST_MIN(all)
- else:
~ return item + 1
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment