Programação funcional e aventuras de texto

14

Esta é principalmente uma questão teórica sobre FP, mas vou fazer aventuras em texto (como Zork da velha escola) para ilustrar meu argumento. Eu gostaria de saber suas opiniões sobre como você modelaria uma simulação estável com o FP.

As aventuras em texto realmente parecem exigir POO. Por exemplo, todas as "salas" são instâncias de uma Roomclasse, você pode ter uma Itemclasse básica e interfaces como Item<Pickable>as que você pode carregar e assim por diante.

A modelagem do mundo no FP funciona de maneira diferente, especialmente se você deseja imutar um mundo que deve sofrer mutações à medida que o jogo avança (objetos são movidos, inimigos são derrotados, a pontuação aumenta, o jogador muda sua localização). Eu imagino um único objeto grande Worldque tem tudo: quais são as salas que você pode explorar, como elas estão conectadas, o que o jogador está carregando, quais alavancas foram acionadas.

Penso que uma abordagem pura seria basicamente passar esse grande objeto para qualquer função e devolvê-lo (possivelmente modificado). Por exemplo, eu tenho uma moveToRoomfunção que obtém Worlde retorna com a World.player.locationalteração para a nova sala, World.rooms[new_room].visited = Truee assim por diante.

Mesmo que este seja o caminho mais "correto", parece estar impondo pureza por causa disso. Dependendo da linguagem de programação, passar esse Worldobjeto potencialmente muito grande para frente e para trás pode ser caro. Além disso, todas as funções podem precisar ter acesso a qualquer Worldobjeto. Por exemplo, uma sala pode estar acessível ou não, dependendo de uma alavanca acionada em outra sala, porque pode ser inundada, mas se o jogador carregar um colete salva-vidas, ele poderá entrar de qualquer maneira. Um monstro pode ser agressivo ou não, dependendo de o jogador ter matado seu primo em outra sala. Isso significa que a roomCanBeEnteredfunção precisa acessar World.player.invetorye World.rooms, describeMonsterprecisa acessar World.monsterse assim por diante (basicamente, você devepasse toda a carga). Isso realmente me parece exigir uma variável global, mesmo que seja apenas um bom estilo de programação, especialmente no FP.

Como resolveria este problema?

pistacchio
fonte
4
"Dependendo da linguagem de programação, passar para frente e para trás esse objeto potencialmente muito grande do mundo pode ser caro." Provavelmente será passado por referência. "Além disso, todas as funções podem precisar ter acesso a qualquer objeto do mundo". Acho difícil acreditar que todas as funções precisam acessar todo o estado do jogo.
Doval
2
Acho que a pesquisa de Chris Marten seria interessante, tem como objetivo mostrar como tornar agradável a ficção interativa em linguagens declarativas. github.com/chrisamaphone/interactive-lp
Daniel Gratzer
2
Você pode dar uma olhada neste blog descrevendo a abordagem do autor para programar esse jogo de maneira funcional. Este post em particular é bastante direto.
Gallais
3
Tenho que me perguntar se essa pergunta influenciou a decisão posterior de EricLippert de escrever uma série de artigos sobre a implementação (partes de) de uma máquina Z no Ocaml ...?
Jules
1
@Jules Um link para o início dessa série, para os interessados: ericlippert.com/2016/02/01/west-of-house
KChaloux

Respostas:

4

Lembre-se de que linguagens funcionais usam estruturas de dados e funções separadas em vez de objetos. Por exemplo, você teria um conjunto de salas e uma lista de itens de inventário como um mundo.

Idealmente, você também limitaria a quantidade de dados que você fornece às funções quanto elas realmente exigem, tanto quanto possível, em vez de passar pelo mundo inteiro (digamos, você extrai uma única sala relevante do seu mundo; é claro que mundos completamente interdependentes podem ser difíceis de separado). O resultado seria reincorporado na estrutura de dados mundiais, criando um novo estado. Você não pode modelar estado sem usar estado; como você diz, algumas coisas inerentemente exigem mutação.

As linguagens funcionais mais práticas fornecem uma maneira de realizar a mutação diretamente (digamos, a mônada ST em Haskell ou transitória em Clojure) ou simulá-la com eficiência (geralmente reutilizando partes inalteradas da estrutura de dados (as estruturas de dados padrão da Clojure são um bom exemplo aqui) ) De qualquer maneira, a pureza é mantida.

Como a quantidade de estado que precisa ser modificada parece limitada, não me preocuparia muito com problemas de desempenho e seguiria a abordagem funcional ingênua (possivelmente já otimizada).

Uma opção diferente que eu vi seria retornar apenas instruções para mudar uma parte do mundo de suas funções individuais e, em seguida, atualizar seu mundo de acordo com elas. Uma série de postagens no blog descrevendo isso está disponível em http://prog21.dadgum.com/23.html .

Ambas as respostas tratam mais de como organizar mudanças do que não passar todo o mundo para funções, porque uma perfeitamente interdependente não pode ser segmentada por definição - mas tente fazê-lo da melhor maneira possível no seu caso, o que não é apenas funcional, mas também boas práticas.

hyperfekt
fonte
0

Eu mesmo analisaria definitivamente a capacidade do idioma em questão de acessar alguma forma de banco de dados. A maioria dos eventos que alteram o estado do mundo simultaneamente seria simplesmente gravada em disco e não afetaria o jogador atual dentro da sala atual (fora de circunstâncias especiais, como explosões ou em um MMO, interruptores que abrem portas). remotamente, gritos de outros jogadores, etc).

Como tal, o cliente atual realmente precisa apenas estar ciente do Roomobjeto e das coisas que o afetam diretamente. noticableEventsOutsideRoompoderia ser simplesmente uma subclasse Roomafetada por alterações recentes no banco de dados, e o objeto do jogo se tornou muito menor.

Ayelis
fonte
Entendo que essa abordagem não faz muito para encontrar caminhos ou acionar eventos locais (como agro em mobs próximos), mas sou conhecido por abusar de bancos de dados no passado ... Provavelmente, basta enviar uma chamada update mobs set agro=1 where distance<5e ser feito com isso. Talvez essa não seja uma prática recomendada, mas atenda aos meus propósitos. Quanto pathfinding via banco de dados, pode-se usar sempre algoritmo de menor caminho de Dijkstra ...
Ayelis
0

A solução real não é coletar tudo para um grande objeto do mundo e depois distribuí-lo. Em vez disso, é recomendável especificar com precisão o tipo de função com a qual você está lidando. Aqui estão alguns exemplos:

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

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

O mau exemplo está tentando modificar o objeto existente, mas o bom exemplo está tentando criar um mundo a partir do espaço de estado que seu jogo possui. Basicamente, para criar um mundo, você precisa conhecer todos os dados necessários para selecionar o mundo correto.

tp1
fonte