功能编程和文字冒险


14

这主要是关于FP的理论问题,但是我将以文字冒险(例如老式的Zork)来说明我的观点。我想知道您对如何使用FP建模有状态模拟的意见。

文字冒险似乎真的需要OOP。例如,所有“房间”都是一个Room类的实例,您可以拥有一个基本的Item类和接口,例如Item<Pickable>可以携带的东西等等。

FP中的世界建模的工作原理有所不同,特别是如果您要在一个必须随着游戏的进行而不断变化的世界(对象移动,敌人被击败,得分提高,玩家改变其位置)中实施不变性的情况下。我想象一个World拥有全部的大对象:您可以探索哪些房间,它们如何链接,玩家所携带的东西,触发了哪些杠杆。

我认为一种纯粹的方法是基本上将这个大对象传递给任何函数,然后由它们返回(可以修改)。例如,我有一个moveToRoom获取World并返回World.player.location到新房间的函数,World.rooms[new_room].visited = True依此类推。

即使这是更“正确”的方法,它也似乎在强制执行纯净。根据编程语言的不同,World来回传递可能非常大的对象可能很昂贵。同样,每个功能可能都需要访问任何World对象。例如,一个房间可能是可进入的还是不可进入的,这取决于另一个房间中触发的杠杆,因为它可能被淹没了,但是如果玩家携带救生衣,它仍然可以进入。怪物是否具有侵略性,取决于玩家是否在其他房间杀死了他的堂兄。这意味着该roomCanBeEntered功能需要访问World.player.invetoryWorld.roomsdescribeMonster需要访问World.monsters等等,(基本上,您必须传递整个负载)。在我看来,这确实需要全局变量,即使这只是良好的编程风格,尤其是在FP中。

您将如何解决这个问题?


4
“取决于编程语言,来回传递这个可能非常大的World对象可能很昂贵。” 它可能会通过引用传递。“此外,每个功能可能都需要访问任何World对象。” 我发现很难相信每个功能都需要访问游戏的整个状态。
2015年

2
我认为克里斯·马滕(Chris Marten)的研究将很有趣,它的目的是展示如何使声明性语言中的交互式小说变得更好。github.com/chrisamaphone/interactive-lp
Daniel Gratzer

2
您可能想看看这个博客,它描述了作者以功能性方式对此类游戏进行编程的方法。特别是这篇文章很重要。
加莱2015年

3
我想知道这个问题是否影响了@EricLippert的以后决定,撰写有关在Ocaml中实现Z机器(的一部分)的一系列文章...?
Jules

1
@Jules对于那些感兴趣的人,到该系列赛开始的链接:ericlippert.com/2016/02/01/west-of-house
KChaloux

Answers:


4

请注意,功能语言使用数据结构和分离的函数而不是对象。例如,您将拥有一组房间和一个清单项目清单作为一个世界。

理想情况下,您还应该将提供给函数的数据量限制为实际需要的尽可能多的数据,而不是传递整个世界(例如,从您的世界中提取一个相关的房间;当然,完全相互依赖的世界可能很难分离)。结果将重新合并到世界数据结构中,从而创建一个新状态。如果不使用状态,则无法对状态建模。正如您所说,某些事情天生就需要突变。

大多数实用的功能语言都提供了一种直接实现突变的方法(例如,Haskell中的ST monad或Clojure中的瞬态),或有效地对其进行仿真(通常通过重用数据结构的不变部分(Clojure的默认数据结构是一个很好的例子)) )。无论哪种方式,都可以保持纯度。

由于需要突变的状态数量似乎很有限,因此我不必担心性能问题,并且可以使用(可能已经优化的)幼稚的功能方法。

我看到的另一种选择是,仅返回指令以从您的个人功能更改世界的某些部分,然后根据这些功能更新您的世界。可以在http://prog21.dadgum.com/23.html上找到一系列描述此问题的博客文章。

这两个答案都更多地与如何组织变更有关,而不是不让整个世界付诸实践,因为一个完全相互依存的事物无法按照定义进行细分 -但在您的情况下,请尽力做到最好,不仅是功能强大,也很好的做法。


0

我本人一定会研究该语言访问某种形式的数据库的能力。大多数同时改变世界状况的事件都将被简单地记录到磁盘上,并且不会影响当前房间中的当前播放器(在特殊情况下,例如爆炸或MMO中,打开门的开关除外)远程,其他玩家的叫喊声等)。

这样,当前客户实际上只需要知道该Room对象以及直接影响该对象的事物。noticableEventsOutsideRoom可能只是Room受数据库最近更改影响的子类,并且您的游戏对象变得越来越小。


我理解这种方法不适用于寻路或触发本地事件(如农业在附近的小怪)做多,但我已经知道在过去滥用数据库...我可能只是发一个电话update mobs set agro=1 where distance<5,并完成它。也许这不是最佳做法,但它适合我的目的。至于通过数据库进行寻路,总是可以使用Dijkstra的最短路径算法 ...
Ayelis,2015年

0

真正的解决方案不是将所有内容收集到一个大型World对象,然后将其传递。相反,建议您准确指定要处理的函数的类型。这里有一些例子:

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

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

不好的例子是试图修改现有的对象,而好的例子是试图从游戏所具有的状态空间创建一个世界。基本上,要创建一个世界,您需要了解选择正确世界所需的所有数据。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.