Newsgroup: it.comp.giochi.avventure.testuali
Titolo: [INFORM] Lo speech definitivo.

Gente, una delle cose più seccanti nel finire WarMage è stata
l'impossibilità di contare su una libreria che mi permettesse in modo
semplice di gestire chiacchere complesse. Ci sarebbero le avventure della
Short da cui attingere (avendo i sorgenti), ma Warmage non è improntato
sulle relazioni interpersonali... e il gioco era già abbastanza pesante
così; mi serviva un sistema semplice ma potente... che non avevo.
Ieri un messaggio su questo NG mi ha invitato a revisionare la sezione
iniziale del gioco (per la cronaca: l'ho fatto e sono molto soddisfatto del
risultato)... ma è strano come funziona la mente umana... ho finalmente
trovato il modo semplice per fare quello di cui avevo bisogno.
Le caratteristiche sono
1) integrazione completa con la libreria standard. Nessun rimpiazzo per la
Life, ma solo un'integrazione.
2) configurabilità in runtime...
3) velocità nello scrivere script complessi
4) possibilità di registrare stati (stati d'animo) in maniera semplice o
articolata, secondo le necessità.
Ecco il risutltato. Vediamo il pezzo di una routine life:
Answer, Ask: 
  switch( MFS_TopicParse() ) {  ! *nota
     'ciao': <<WaveHands self>>;
     'schiavi', 'schiavo':
        "~Gli schiavi sono troppo spaventati per reagire,...";
....
....
     default:
        if ( MFS_Speech(moltan_speech, speech_ask ) ) !* nota
            rtrue;
MFS_TopicParse() in realtà è una routine molto semplice tirata fuori dal DM4
che mi consente di prendere la prima parola rilevante del testo immesso dal
giocatore. Vi mostro questo pezzo per far vedere che il mio sistema si
integra in qualsiasi altro sistema pregresso; infatti, è importante poter
disporre anche di semplici token da gestire velocemente, ed all'interno del
personaggio stesso, come ad esempio la traduzione di 'ciao': <<WaveHands
self>>;, che mi permette di scrivere la risposta a "saluta" una sola volta.
Quello che conta è la MFS_Speech(), che ha due parametri. Il primo è un
oggetto che contiene lo speech. Può essere il personaggio stesso o un'altro
oggetto non animato e fuori scopo. Il secondo è il nome di una proprietà in
nell'oggetto speech che fa da "radice" per la conversazione.
Una proprietà speech si configura in maniera simile alla "scenic":
....
   speech_prop
        'word1' 'word2' ... 'wordn'... 0 action1
        'word1' 'word2' ... 'wordn'... 0 action2
        'word1' 'word2' ... 'wordn'... 0 action2, ! nota la virgola qui.
Ogni lista di parole è composta da una o più parole, uno 0 e una azione da
svolgere. Al contrario che nella "scenic", dove l'azione si verifica quando
si trova una parola qualsiasi, la speech property esegue l'azione solo
quando tutte le parole digitate dall'utente come TOPIC sono trovate nella
stessa sequenza. Parole "nere" (articoli, aggettivi) sono scartate.
Le azioni sono di due tipi: terminali e non terminali; le azioni terminali
possono essere:
1) stringhe
2) routines
3) proprietà dello stesso oggetto stringhe
4) proprietà dello stesso oggetto routines
Le azioni non terminali sono altre speech_prop che vengono eseguite in
cascata, nel caso esistano ancora delle parole da comprendere nell'input.
Tutte le parole sconosciute sono ignorate.
Dunque, siccome un esempio è più eloquente di mille parole, ecco il test che
ho fatto su moltan:
---------------------------------------------------
Object Moltan_speech "la mente di Moltan"
with
   speech_ask
      ! regola terminale
      'pergamena' 0 "~Di quale pergamena stai parlando, Yarvik?~"
        ! regola NON terminale
      'pergamena' 0 ms_scroll
      'altro' 0 "Cos'altro vuoi chiedermi?",
   ! prosegue da pergamena
   ms_scroll
      ! regola terminale
      'oscura' 0 "~@`E molto antica... e molto potente.~"
      ! terminale: proprietà-rotuine
      'antica' 0 about_golem
      ! terminale: routine pura
      'lunga' 0 ALongScroll
      ! terminale (due parole): proprietà-stringa
      'dio' 'grigio' 0 gray_string
      ! Non terminale, due parole
      'dio' 'grigio' 0 ms_graygreen,
   ! prosegue da pergamena dio grigio
   ms_graygreen
      'verde' 0 "~Grigio verde? Sei ammattito?~",
   gray_string "Splendida!",
   about_golem [;
      "~Il Golem! Che magnifica creatura... ricordo
          la prima volta che ne evocai uno... ma ti racconter@`o
          la storia un'altra volta.~";
   ];
[ ALongScroll oSender;
   ! Cambiamo commento:
   oSender.gray_string =
      "Notevole... ma anche la pergamena lunga non @`e male...";
   print (name) oSender, " dice: ";
   "~Un oggetto meraviglioso.
   Deve essere costato molta fatica...^
   ~Usalo con prudenza, perch@`e vi si possono scrivere numerosi
   incantesimi, e quindi rivolgerli contro nemici assai forti.~";
];
-----------------------------
In poche parole, ecco cosa succede:
chiedo a moltan della pergamena: la prima regola viene eseguita 
chiedo a moltan della pergamena oscura: la prima regola (terminale)
fallisce, dato che ci sono parole non analizzate; viene quindi scelta la
seconda. "Oscura" stampa una stringa in ms_scroll. 
chiedo a moltan della pergamena antica: la proprietà - routine about-golem
viene eseguita.
chiedo a moltan della pergamena lunga: la routine ALongScroll viene
eseguita, e il commento registrato come stringa in gray_string viene
cambiato.
chiedo a moltan della pergamena del dio grigio: 'del' viene ignorato e la
stringa gray_string (terminale) viene stampata. Può essere stata alterata
in precedenza da ALongScroll...
chiedo a moltan della pergamena del dio grigio verde: viene eseguita
l'azione terminale che stampa il messaggio.
------------------------------
Ora, la flessibilità di questo sistema è notevole. L'oggetto che contiene lo
speech può essere il personaggio stesso, per i casi più semplici, oppure
una serie di oggetti esterni da sostituire al momento giusto. La funzione
MFS_Speech() può essere chiamata con diversi oggetti e con diversi nodi di
partenza. I sinonimi possono puntare alla stessa proprietà che contiene lo
stesso messaggio. Le prioprietà che contengono i messaggi (o routine che
stampano i messaggi) possono essere sostituite con altre stringhe o
addirittura con routines. Essendo oggetti, è facile registrare negli speech
gli stati ai quali i personaggi reagiscono. Infine, sempre essendo oggetti,
gli speech possono essere derivati da una classe, in modo da poter
mantenere un corpus di messaggi stabile ma di rendere intercambiabili
alcuni messaggi specifici semplicemente cambiando l'oggetto di riferimento.
-----------------------------
Anche se ho usato il NextWord() della libreria inform, (il sistema TOPIC di
consult, ask, tell, answer ecc,) la routine è facilmente adattabile a
qualsiasi altro sistema di dialogo, purché esso fornisca la possibilità di
accedere alle parole (rilevanti) immesse una alla volta, e di andare avanti
o tornare indietro. Richiede di modificare il codice, ma tre o quattro
righe, non di più. E si potrebbe rendere indipendente dal codice
sottostante, volendo.
Con questa aggiunta, penso finalmente che 1) potrò migliorare molto warmage
in poco tempo. In una mezza giornata credo di essere in grado di dare quel
tocco di vita ai personaggi di cui qualcuno ha lamentato l'assenza. 2)
penso che le MFS siano piuttosto mature oramai per un rilascio... Molte
parti sono indipendenti (come questa, ad esempio).
Cosa ne pensate di questa idea?
Giancarlo.

 Cosa ne pensate di questa idea?
Che ci siamo giocati un numero della rubrica per Terra d'IF! :D
Rob
-- 
Posted via Mailgate.ORG Server - 
http://www.Mailgate.ORG

 
Oh, al contrario amico mio ;-)
Ci metterò un bel po' di numeri a illustrare tutte le potenzialità del
sistema.
Giancarlo

Cosa ne pensate di questa idea?
Mi sembra un'ottima idea. Però, prima di rilasciare le librerie,
dovresti IMHO rilasciare anche un piccolo manuale sul loro utilizzo.
Ti consiglio, se riesci, di rilasciare appena ti è possibile anche i
sorgenti di Warmage, in modo da vedere un utilizzo "pratico"
delle tue straordinarie librerie. :)
Per quanto mi riguarda, la prossima settimana rilascerò il paragrafo
3.8 e comincerò a lavorare al 3.9 che tratterà (guarda caso) proprio
il dialogo con i personaggi (la spiegazione cioè del codice
riguardante il sacerdote).
Complimenti e ciao da
Vincenzo

 >> Cosa ne pensate di questa idea?
A quando il rilascio delle librerie?
f.

 Cosa ne pensate di questa idea?
Sembra davvero buona, ma dovrei provarla per rendermene conto bene (e lo 
faro').
L'unica cosa che mi lascia perplesso e' la sintassi simile a quella di 
scenic.h (anche quella mi lascia ormai perplesso). Forse converrebbe 
usare un differente oggetto per ogni argomento di conversazione (che ne 
so, un oggetto di classe topic), o permettere entrambi i sistemi.
Per quanto riguarda le MFS, spero tu le rilasci abbastanza presto. 
Saranno modulari, vero?
E poi, sei sicuro che questo sistema di conversazione trovi il suo posto 
logico nelle MFS? Non starebbe meglio in una libreria a parte? (A meno 
che MFS non stia per Librerie di Giancarlo Niccolai, anche se l'acronimo 
non torna ;-) )
bye
-- 
Paolo Lucchesi
email: 
pluc...@tin.it
homepage: 
http://www.paololucchesi.it
"Non viviamo in un mondo perfetto (per fortuna)"

 Cosa ne pensate di questa idea?
Ottima :)
Mi piacerebbe avere il tempo che tu puoi mettere a disposizione per la 
programmazione sotto Inform :|
Saluti,
Dott. Piergiorgio.

 non torna ;-) )
Forse si, ma sono pigro... Avere una libreria in giro mi pare abbastanza;
più ricca è meglio è. Posso fare un solo manuale, un solo download, un solo
sito; inoltre, la logica è quella di arricchire il sistema di gioco... e
infine, una volta che ti scarichi la libreria, tutto quello che devi fare
se vuoi usare solo questa roba è include "MFS_Speech.h" e scartare il
resto... facile.
Ciao.
Giancarlo.

 stessa sequenza. Parole "nere" (articoli, aggettivi) sono scartate.
Tutto molto interessante, ma mi chiedevo se il fatto che la sequenza di
parole che genera una azione sia esattamente quella scritta non sia una
costrizione troppo grande per la flessibilità di un sistema di
coversazione. Ad esempio possono essere presenti:
1) parole facoltative
2) parole presenti in ordine diverso
Mentre per il primo caso puoi omettere la parola nella lista e il fatto
che sia presente o meno nel comando non pregiudica lo svolgimento
dell'azione, nel secondo il fatto di avere parole che sono presenti in
più di una lista in posti diversi appesantisce di molto la scittura di
un sistema di conversazione complesso.
Ad esempio nell'esempio della pergamena, supponi che io digiti nel 
comando l'aggettivo "oscura" prima del nome. Anziché avere una 
"pergamena oscura" ho una "oscura pergamena". Lo so che è brutto, ma 
quello che voglio dire che posso avere una regola non terminale in cui 
il proseguimento della regola ("oscura" nell'esempio) può andare sia 
prima che dopo la parola. Sarei costretto a scrivere due regole non 
terminali con due nodi radice differenti.
Sarebbe utile in questo caso un qualcosa per identificare queste parole 
flessibili. Non so come hai implementato la routine di parsing ma 
presumo che tu scarti tutte le parole fino a che non ne incontri una che 
  è presente in una lista di topic. Ora, per implementare quello che 
dico io, bisogna tenere comunque memorizzate tutte le parole digitate 
(vabbé solo le parole presenti nel dizionario e questo si verifica 
facilmente).  Ora supponi che io identifichi una parola "flessibile" con 
qualche simbolo, ad esempio posso usare un "+" (quindi nella regola ho 
un +'oscura' anziché un semplice 'oscura').
Quando chiedo della pergamena oscura tutto procede come tu hai 
descritto, mentre se chiedo della oscura pergamena incappo in un grosso 
problema, perché la prima parola che viene riconosciuta valida è 
pergamena e quindi viene eseguita la regola terminale. Però io ho 
memorizzato la lista delle parole del dizionario che precedono 
"pergamena" (in questo caso oscura) quindi devo impostare una qualche 
flag che mi dice che devo prima testare una regola non terminale (se 
presente) con le parole del dizionario memorizzate e solo in caso 
negativo la regola terminale.
Ricapitolando, "chiedi a moltan della oscura pergamena" dovrebbe essere 
trattata dal parser nel modo seguente:
1) "oscura" viene riconosciuta come parola nel dizionario
2) memorizzo "oscura" e imposto una flag a true
3) pergamena è presente in speech_ask
4) esiste una regola non terminale per pergamena? (questo test solo
    se la flag è true)
5) poiché esiste allora eseguo prima la regola non terminale
    (ms_scroll nell'esempio)
6) nella regola eseguita è presente "oscura" come parola flessibile
    e quindi viene eseguita proprio quella regola.
se non esiste la regola non terminale o se questa ha esito negativo 
allora eseguo la regola terminale.
Non so se sono stato chiaro. Certo l'esempio della pergamena oscura non 
è che sia proprio l'ideale, ma così al volo non mi è venuto in mente 
nulla. Comunque dovrebbero essere gli autori di IF a dire se un sistema 
più flessibile come quello che ho descritto sia effettivamente utile. 
Altrimenti rimangono solo elucubrazioni della mia mente distorta.
Ciao e complimenti ancora per Warmage.
Giovanni
-- 
Giovanni Riccardi - 
giov...@composizioni.net
 - 
http://www.composizioni.net

 2) parole presenti in ordine diverso
Per il momento, l'unico modo è scrivere una regola differente che punta allo
stesso messaggio. Ad esempio:
....
   speech_prop
         'pregamena' 'oscura' 0 ask_dark_scroll
         'oscura' 'pergamena' 0 ask_dark_scroll,
 
   ask_dark_scroll "Una pergamena davvero oscura!"
Idem per le parole facoltative:
....
   speech_prop
         'grigio' 0 ask_graygod
         'dio' 'grigio' 0 ask_gray_god,
   ask_graygod "Un dio davvero grigio!",
ULTIMORA
========
Mi sono reso conto che il limite di elementi che una proprietà supporta in
inform z5+ è 32 (non so se questo limite non esiste in glux, ma poco
importa). Per questo motivo ho aumentato la flessibiltà del sistema con due
token speciali: '-' e '*'
'-' esegue comunque la regola che segue immediatamente, e permette quindi
sia di estendere la regola corrente in altre proprietà che di creare
"alberi di regole"; Inoltre, non "consuma" nessuna parola, e la regola
chiamata viene controllata con lo stesso livello della chiamante (ossia,
riprendendo il parsing dallo stesso punto).
'*' fa il match di qualsiasi topic valido (o parola di vocabolario). Ecco un
esempio per moltan:
   speech_ask
      '-' ask_people
      '-' ask_god
      '-' ask_spells_raw
      '-' ask_spells
      '-' ask_places
      '-' ask_things
      '-' ask_world
      ,
oppure, per allungare una regola:
   ask_people
      'kandar' 0 "~Quel vigliacco! Mi ha ingannato. @`E un traditore.~"
      'mago' 0 "~Quale mago?~"
        ...
      'oste' 0 "~Uhm. Gli osti sono tutti grassi e untuosi.~"
      '-' ask_people2,
 ask_people2
      'sgherro' 0 about_pirates
      'guardia' 0 about_guards
      'guardie' 0 about_guards
.....
e così via all'infinito. Per quanto riguarda '*':
   ask_spells_raw
      'incantesimo' 0 "~Quale incantesimo?~"
      'incantesimo' '*' 0
         "[Usa semplicemente ~chiedi a moltan <nome incantesimo>~]",
o ancora, per creare regole di default:
 ! da match non terminale di pergamena 
 ms_ask_scroll
      'oscura' 0 about_powscroll
      'sigillata' 0 about_powscroll
      ...
      '*' 0 "~Non so dirti nulla di quella pergamena.~"
      ,
Sempre più in alto!!! :-))
In un paio d'ore ho già arricchito Moltan di circa una sessantina di topic
articolati, molti dei quali variabili in base al contesto. Non ho applicato
i "cambiamenti caratteriali" a moltan perché non è nello spirito
dell'avventura ma avrei potuto facilmente.
Giancarlo.

 stesso messaggio. Ad esempio:
infatti, ma mi chiedevo se sulla base del codice cha hai già scritto per 
il parsing dei topic, la variante che ti ho suggerito (sempre che sia 
effettivamente utile ad un autore di IF) possa essere implementata 
facilmente o è un lavoro più lungo. Praticamente: come funziona 
algoritmicamente la tua funzione di parsing (lo immagino, ma se ce la 
fai vedere siamo tutti più contenti, o almeno io).


 
MFS è attualmente sul mio CVS >:-> privato... se ti interessa ti posso dare
l'indirizzo privatamente. 
Ma se resisti ancora qualche oretta penso di pubblicare MFS, per ora senza
manuale aggiornato.
L'algoritmo è molto semplice e viene fatto un parsing incrementale. Il mio
problema era proprio quello di AVERE una rigida sequenza di parole, perché
un "set" di sinonimi li posso implementare facilmente con uno switch come:
...
life [;
        Ask: switch( MFS_GetWord() ) {
                'pirati', 'pirata' : "Sono cattiva gente";
                ...
        }
];
Il mio problema era ESATTAMENTE quello di individuare facilmente delle
sequenze rigide. I "sinonimi" me li implemento nelle life dei personaggi,
mentre le sequenze di topic le implemento con il mio sistema; le due cose
si integrano che è una bellezza, ed è proprio questa la parte più
interessante del sistema, quella di poter lavoare con il codice
tradizionale: se non viene trovata nessuna regola, la MFS_Speech torna
falso e questo permette al programma chiamante di decidere cosa fare.
Giancarlo