基于面向对象编程的文本游戏中优雅的命令解析

8

我在用Ruby写一个MUD/文字冒险游戏(请不要笑),与解析输入文本相关的优雅的基于oop的解决方案,有人能给我一些指导吗?我们只需要处理比如“将魔杖放在桌子上”这样简单的命令,但是所有东西都需要灵活;我希望以后可以轻松扩展命令集。

我的当前想法,稍微简化一下:

  1. 每个物品类别(盒子、桌子、房间、玩家)知道如何识别属于它的命令。

  2. 游戏类别了解一种领域特定语言,其中包括诸如“将对象X移动到对象Y内”、“显示对象X的描述”等操作。

  3. 游戏类别询问房间中的每个物品是否识别输入命令。首先回答“是”的物品胜出。

  4. 然后将控制权传递给物品类别中处理命令的方法。该方法使用DSL重新表述命令,将其传递回游戏对象以使其发生。

肯定有行之有效的优雅方法来处理这些事情。但是似乎无法通过谷歌找到任何东西。


作为一个脚注,我的当前计划中棘手的部分是实现涉及两个对象的操作。 - Shadowfirebird
1
你可以从http://stackoverflow.com/questions/tagged/natural-language开始,看看它会带你去哪里。 - dmckee --- ex-moderator kitten
谢啦!我刚刚给那个问题加了这个标签。 - Shadowfirebird
我的第一个勾选标记! :) 我将研究有限状态机和解释器模式。对我来说,后者似乎很符合我的当前思路。但魔鬼在于细节,也就是解析。 我得到了大量的想法。感谢大家。 - Shadowfirebird
5个回答

3

解释器设计模式是我所知道的最面向对象的解析方法,但编译器专家肯定会指出更强大的算法。


我对设计模式一窍不通,而我的谷歌搜索也没有找到这个。我肯定会看看这个的,谢啦。 - Shadowfirebird
实际上,解释器模式看起来像编译过程中的表达式树!很好。它似乎是编译器的一个简单实现。(在字符串标记解析之后) - Antoine Claval
我仍在努力将这个模式应用到我的项目中,但看起来它确实是我所追求的想法。然而,它没有说出命令字符串是如何解析的。 - Shadowfirebird

2

听起来你需要一个解析器。

将输入字符串分割成标记(单词)。然后逐个将标记馈送到状态机中。我发现下推自动机是编写这种状态机的直观而强大的方式。


有什么建议可以帮助我开始学习状态机的概念吗?当然,维基百科上的条目是必须的。 - Shadowfirebird
1
我收回对维基百科的评论。下推自动机页面看起来很有趣,但是它是用高等数学写的。 - Shadowfirebird
1
@Shadow:https://dev59.com/questions/x3VD5IYBdhLWcg3wXaed 是SO上编译器资源的规范页面。但你需要的是专业性的,可能没有得到很好的处理。这个答案在技术上是正确的,但并不是很有帮助,因为自然语言处理是出了名的困难,有它自己的文化和语言。 - dmckee --- ex-moderator kitten

1

对于命令解释器,我比较喜欢这种简单而不太优雅的模式。动态语言中的模式往往涉及的方框和线条比GOF模式少。

class Thing

  # Handle a command by calling the method "cmd_" + command.
  # Raise BadCommand exception if there is no method for that command.

  def handle_command(command, args)
    method_name = "cmd_#{command}"
    raise BadCommand, command unless respond_to?(method_name)
    send(method_name, args)
  end

  def cmd_quit(args)
    # the code for command "quit"
  end

  def cmd_list(args)
    # the code for command "list"
  end

  ...

end

以这种方式,添加新命令只需要添加新方法。无需调整表格或情况语句。

2
这乍一看像是访问者模式、策略模式和命令模式之间的奇怪三角关系,但正如你所说:在一种表达性语言中(我不太相信“动态”部分,它在Scala、F#或Haskell中看起来可能同样优雅),这并不是什么大不了的事情。记住:模式只是你希望编写的程序,但你的语言不允许你这样做。好吧,Ruby确实允许你这样做。 - Jörg W Mittag
@Jorg,说得好。我喜欢那个模式的定义。这是你自己的吗? - Wayne Conrad
3
我不这么认为。那个确切的措辞可能是我的,但这个想法是普遍存在的。实际上,如果你翻开《设计模式》(GoF)第1.1节的最后一段,就可以看到其中阐述了这个想法。对此的典型抨击可以在 http://blog.plover.com/prog/design-patterns.html 找到,并配有这篇回复 http://www.cincomsmalltalk.com/userblogs/ralph/blogView?entry=3335803396 以及这篇文章 http://blog.plover.com/prog/johnson.html 。当然,还有著名的 Norvig 演讲:http://norvig.com/design-patterns/。 - Jörg W Mittag
@Jog,这是一大堆很棒的链接。谢谢! - Wayne Conrad

0

将其分成令牌,因为格式始终如下:
[命令] [对象1] ([引用] [对象2])

您可以在房间中调用方法[命令]来处理[对象1],如果适用,可以传递[对象2]。


这非常诱人。但是,“打开灯光”。或者,同样地,能够接受“打开灯光”将会非常好。 - Shadowfirebird
1
你可以通过从字符串中简单删除通常称为“停用词”的单词来实现。例如“the”、“an”、“on”等单词。在这种情况下,“现在打开那里的灯”这句话中剩下的只有“打开 灯”。当然,这可能会多了,但在这种特定情况下,缺少“关闭”一词告诉您所需知道的一切。 - Jörg W Mittag
@Jorg:实际上这基本上就是我的当前方法所做的,只不过它检测“开始词”而已。因此(略微简化一下),light类寻找/light.*(on|off)/。也许你是对的,而我错了。 - Shadowfirebird
“停用词(stop words)”的概念源自搜索引擎,这些是一些在搜索中没有任何有用贡献的单词。因此,它们通常会从索引和搜索查询中过滤掉。这里肯定需要进行一些微调。你可以查看一下Git版本控制系统中的自然语言日期解析器。它看起来可以解析像“大约一个小时之前”这样非常复杂的表达式,但实际上它只是简单地丢弃它不理解的任何内容,所以最终只剩下“小时”,它被定义为3600,就是这样。工作得很好。 - Jörg W Mittag

0

好的。所以你需要一个语义吗?(turn是一个动作,light是一个对象,on是一个参数...(我与您对dbemerlin的评论有关)。

为什么不定义一个语法呢?嗯...我猜lex和yacc不是一个选择?(因为它根本不是面向对象的,但你想做的是“编译”用户输入以产生一些东西 - 执行一些改变房间数据的代码并输出结果)

你可以为你的对象及其动作设计一个面向对象的设计(例如,所有对象都有一个.describeMe()方法..),除此之外,还有一个输入解析器+编译器。

我完全偏离了主题吗?

编辑:在查看Marc Seeman指出的解释器模式后,如果您希望使用面向对象的方式,似乎这是正确的方法。(但您将在某种程度上重新发明轮子)


我确实看过lex和yacc。但是它们让我感到头疼,而这个项目应该是一个有趣的项目。此外,如果我理解正确,它们会将我绑定到比英语实际上更严格的语法上。我想我的目标是让用户能够输入“在桌子上放置魔杖”并被理解……或者至少让游戏尝试将桌子放在魔杖上 ;) - Shadowfirebird

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接