Programmazione funzionale e avventure testuali


14

Questa è principalmente una domanda teorica su FP, ma prenderò avventure testuali (come la vecchia scuola Zork) per illustrare il mio punto. Mi piacerebbe conoscere le tue opinioni su come modellare una simulazione stateful con FP.

Le avventure testuali sembrano davvero richiedere OOP. Ad esempio, tutte le "stanze" sono istanze di una Roomclasse, puoi avere una Itemclasse di base e interfacce come Item<Pickable>per cose che puoi portare e così via.

La modellizzazione del mondo in FP funziona in modo diverso, soprattutto se si desidera imporre l'immutabilità in un mondo che deve mutare man mano che il gioco procede (gli oggetti vengono spostati, i nemici vengono sconfitti, il punteggio aumenta, il giocatore cambia la sua posizione). Immagino un unico grande oggetto Worldche ha tutto: quali sono le stanze che puoi esplorare, come sono collegate, cosa sta portando il giocatore, quali leve sono state attivate.

Penso che un approccio puro sarebbe fondamentalmente passare questo grande oggetto a qualsiasi funzione e farlo restituire da loro (possibilmente modificato). Ad esempio, ho una moveToRoomfunzione che la ottiene Worlde la restituisce World.player.locationcambiata nella nuova stanza World.rooms[new_room].visited = Truee così via.

Anche se questo è il modo più "corretto", sembra imporre la purezza per il gusto di farlo. A seconda del linguaggio di programmazione, passare Worldavanti e indietro questo oggetto potenzialmente molto grande può essere costoso. Inoltre, potrebbe essere necessario che ogni funzione abbia accesso a qualsiasi Worldoggetto. Ad esempio, una stanza può essere accessibile o meno a seconda di una leva attivata in un'altra stanza perché potrebbe essere allagata, ma se il giocatore porta un giubbotto di salvataggio, può comunque entrarvi. Un mostro può essere aggressivo o meno a seconda che il giocatore abbia ucciso suo cugino in un'altra stanza. Ciò significa che la roomCanBeEnteredfunzione deve accedere World.player.invetorye World.rooms, describeMonsterdeve accedere World.monsterse così via (in pratica, è necessariopassare l'intero carico in giro). Questo mi sembra davvero richiedere una variabile globale, anche se questo è quasi un buon stile di programmazione soprattutto in FP.

Come risolveresti questo problema?


4
"A seconda del linguaggio di programmazione, passare avanti e indietro questo oggetto World potenzialmente molto grande può essere costoso." Probabilmente verrà passato per riferimento. "Inoltre, potrebbe essere necessario che ogni funzione abbia accesso a qualsiasi oggetto Mondo." Trovo difficile credere che ogni funzione abbia bisogno di accedere all'intero stato del gioco.
Doval,

2
Penso che la ricerca di Chris Marten sarebbe interessante, ha lo scopo di mostrare come rendere piacevole la narrativa interattiva in linguaggi dichiarativi. github.com/chrisamaphone/interactive-lp
Daniel Gratzer

2
Potresti dare un'occhiata a questo blog che descrive l'approccio dell'autore alla programmazione di un gioco del genere in modo funzionale. Questo post in particolare è abbastanza pertinente.
gallais,

3
Mi chiedo se questa domanda abbia influenzato la successiva decisione di @ EricLippert di scrivere una serie di articoli sull'implementazione (parti di) una macchina Z in Ocaml ...?
Jules,

1
@Jules Un link per l'inizio di quella serie, per chi fosse interessato: ericlippert.com/2016/02/01/west-of-house
KChaloux,

Risposte:


4

Ricorda che i linguaggi funzionali utilizzano strutture di dati e funzioni separate anziché oggetti. Ad esempio, avresti un set di stanze e un elenco di articoli dell'inventario come mondo.

Idealmente, limiteresti anche la quantità di dati che fornisci alle funzioni a quanto effettivamente richiedono il più possibile invece di passare dal mondo intero (supponi di estrarre una singola stanza rilevante dal tuo mondo; ovviamente mondi completamente interdipendenti possono essere difficili da separato). Il risultato verrebbe reincorporato nella struttura di dati mondiale, creando un nuovo stato. Non è possibile modellare lo stato senza usare state; come dici, alcune cose richiedono intrinsecamente una mutazione.

I linguaggi funzionali più pratici forniscono un modo per realizzare direttamente la mutazione (diciamo la monade ST in Haskell o i transitori in Clojure) o simularla in modo efficiente (spesso riutilizzando parti invariate della struttura dei dati (le strutture di dati predefinite di Clojure sono un buon esempio qui) ). In entrambi i casi viene mantenuta la purezza.

Poiché la quantità di stato che deve essere mutata sembra limitata, non mi preoccuperei troppo dei problemi di prestazioni e andrei con l'approccio funzionale ingenuo (forse già ottimizzato).

Un'opzione diversa che ho visto sarebbe quella di restituire solo le istruzioni per cambiare una parte del mondo dalle tue singole funzioni, e quindi aggiornare il tuo mondo in base a queste. Una serie di post sul blog che descrive questo è disponibile su http://prog21.dadgum.com/23.html .

Entrambe queste risposte si occupano più di come organizzare i cambiamenti che di non far passare il tuo intero mondo alle funzioni, perché uno perfettamente interdipendente non può essere segmentato per definizione - ma cerca di farlo nel miglior modo possibile nel tuo caso, che non è solo funzionale, ma anche buona pratica.


0

Io stesso, esaminerei sicuramente la capacità della lingua in questione di accedere a una qualche forma di database. La maggior parte degli eventi che accadono contemporaneamente per cambiare lo stato del mondo verrebbero semplicemente registrati su disco e non influenzerebbero l'attuale giocatore all'interno della stanza attuale (al di fuori di circostanze speciali come esplosioni o in un MMO, interruttori che aprono le porte da remoto, grida di altri giocatori, ecc.).

In quanto tale, l'attuale client deve solo essere consapevole Roomdell'oggetto e delle cose che lo influenzano direttamente. noticableEventsOutsideRoompotrebbe quindi essere semplicemente una sottoclasse Roominteressata dalle recenti modifiche al database e l'oggetto di gioco è diventato molto più piccolo.


Capisco che questo approccio non fa molto per l'individuazione di percorsi o l'attivazione di eventi locali (come l'agro su mob vicini), ma in passato sono stato conosciuto per aver abusato di database ... Probabilmente avrei semplicemente inviato una chiamata update mobs set agro=1 where distance<5ed essere fatto con esso. Forse questa non è la migliore pratica, ma si adatta ai miei scopi. Per quanto riguarda l'individuazione del percorso tramite database, si potrebbe sempre usare l'algoritmo del percorso più breve di Dijkstra ...
Ayelis,

0

La vera soluzione non è raccogliere tutto in un grande oggetto del mondo e poi passarlo in giro. Invece, ti consigliamo di specificare con precisione il tipo di funzione con cui hai a che fare. Ecco alcuni esempi:

BAD:
   f :: (World, Int) -> World

Good:
   f :: (Int,Int,Int,Int) -> World

Il cattivo esempio sta provando a modificare l'oggetto esistente, ma il buon esempio sta cercando di creare un mondo dallo spazio degli stati del tuo gioco. Fondamentalmente, per creare un mondo, è necessario conoscere tutti i dati necessari per selezionare il mondo corretto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.