解析文字冒险中的用户命令的范围很广,从Adventure的简单“向北”到hhgttg中一些令人难以置信的聪明命令。
我似乎记得80年代在计算机杂志上阅读过不错的操作指南,但是现在,除了简短的Wikipedia参考文献之外,我在网上几乎找不到任何内容。
你会怎么做?
更新:我在Ludum Dare条目中采用了最简单的方法。
解析文字冒险中的用户命令的范围很广,从Adventure的简单“向北”到hhgttg中一些令人难以置信的聪明命令。
我似乎记得80年代在计算机杂志上阅读过不错的操作指南,但是现在,除了简短的Wikipedia参考文献之外,我在网上几乎找不到任何内容。
你会怎么做?
更新:我在Ludum Dare条目中采用了最简单的方法。
Answers:
您是否在互动小说社区中搜索?他们仍在编写解析器,有些人尝试通过实施新技术(例如自然语言处理)来突破极限。
例如,请参见以下链接,以获取描述使用方法的文章:
http://ifwiki.org/index.php/Past_raif_topics:_Development:_part_2#Parsing
您想要的术语是“自然语言处理”或NLP。但是,请记住,形式化方法旨在尝试理解真实世界的文本,而您通常只需要对自然语言的有限子集有用的内容。
通常,您可以从简单的语法和词汇开始,然后为其编写一个解析器。语法可能像这样简单:
sentence = verb [preposition] object
verb = "get" | "go" | "look" | "examine"
preposition = "above" | "below"
object = ["the"] [adjective] noun
adjective = "big" | "green"
noun = "north" | "south" | "east" | "west" | "house" | "dog"
上面是Backus-Naur形式的一种变体,它是表示语法的标准方法。无论如何,您可以使用解析器生成器生成代码来解析此语法,或者如果您的语言具有不错的字符串处理能力,则可以相当轻松地编写自己的语法。(查找“递归下降解析器”,对语法的每一行使用一个函数。)
解析后,您可以判断该句子是否有意义-“往北走”可能有意义,但“让绿色往北走”则没有意义。您可以通过两种方法解决此问题;使语法更正式(例如,仅具有某些特定类型的名词才具有不同类型的动词),或随后针对动词检查名词。第一种方法可以帮助您向播放器发出更好的错误消息,但是无论如何,您始终需要在某种程度上进行第二种操作,因为您始终需要检查上下文-例如。在语法上和语法上,“获取绿色键”都是正确的,但是您仍然需要检查绿色键是否存在。
最终,您的程序以一条经过验证的命令结束,并检查了所有各个部分。那么这只是用参数调用正确的函数来执行操作的情况。
今天进行文本冒险的最新技术是使用Inform 7。Inform 7的源代码读为“像英语”,就像基于Inform的游戏让您“写英语”一样。例如,从艾米丽·肖特(Emily Short)的铜牌中:
一件事有一些叫做气味的文字。事物的气味通常是“无”。
块气味规则未在任何规则手册中列出。
闻一闻:
说“从[名词]闻到[名词的气味]。”
而不是
散发房间的气味:如果演奏者可以触摸到有气味的东西,请说“您闻到了[演奏者可以触摸到的有气味的东西列表]”。
否则请说“这个地方极度无味。”
Inform 7解析器与Inform 7 IDE紧密集成在一起,整个源代码尚无法研究:
当前学会学习创建文本冒险分析器的两个最佳资源是IF社区和Mud社区。如果您在主要论坛中搜索这些论坛(Intfiction.org/forum,新闻组rec.arts.int-fiction,Mud Connector,Mudbytes,Mudlab,Top Mud Sites),则会找到一些答案,但是如果您只是在寻找对于文章,我建议Richard Bartle在MUD II中对解析器的解释:
http://www.mud.co.uk/richard/commpars.htm
关于rec.arts.int-fiction的解释如下:
http://groups.google.com/group/rec.arts.int-fiction/msg/f545963efb72ec7b?dmode=source
不会无视其他答案,但是创建CF语法或使用BNF并不是解决此问题的方法。这并不是说它不能解决另一个问题,那就是创建一个更高级的自然语言解析器,但这是大量研究的主题,而不是文本冒险范围内的IMO。
在大学的第一年,我们在Prolog中制作了一个冒险游戏,对于用户输入,我们必须使用定句语法或DCG。有关将其用作命令语言的示例,请参见http://www.amzi.com/manuals/amzi/pro/ref_dcg.htm#DCGCommandLanguage。当时似乎是原则性的(毕竟是统一的)和灵活的方法。
您需要定义一种特定于域的语言,该语言是您游戏中所有正确的句子。为此,您必须为您的语言(词汇和语法)定义语法。所需的语法类型是“上下文无关语法”,并且有一些工具可以从语法的综合描述开始自动生成解析器,例如ANTLR(www.antlr.org)。解析器仅检查句子是否正确,并生成该句子的抽象语法树(AST),该树是该句子的可导航表示,其中每个单词都具有您在语法中指定的角色。通过浏览AST,您必须添加代码以评估每个单词相对于句子中的其他单词在扮演该角色时所采用的语义,并验证语义是否正确。
例如,“石头吞噬男人”这句话在语法上是正确的,但在语义上却不一定正确(除非在您的世界中,石头,也许是魔石,可以吞噬男人)。
如果语义也正确,那么您可以例如根据它改变世界。这可能会改变上下文,因此相同的句子在语义上不再正确(例如,可能没有人吃饭)
我将Tads3(www.tads3.org)引擎用于我编写的某些文字冒险。尽管它对于计算机程序员来说更多,但是却是一种非常强大的语言。如果您是一名程序员,那么Tads3会比我之前使用过的Inform7更快地编写代码。对于程序员来说,Inform7的问题与“猜测动词”一样著名,这对于文本冒险的玩家来说是这样的,如果您不非常认真地写句子,就将打破游戏规则。如果您有耐心,可以使用Tokenizer类轻松地用Java编写一个解析器。我使用全局JTextArea和全局String []数组编写的示例。它会去除不需要的字符,但不包括叶子AZ和0-9以及问号(用于“帮助”命令快捷方式):
// put these as global variables just after your main class definition
public static String[] parsed = new String[100];
// outputArea should be a non-editable JTextArea to display our results
JTextArea outputArea = new JTextArea();
/*
* parserArea is the JTextBox used to grab input
* and be sure to MAKE sure somewhere to add a
* java.awt.event.KeyListener on it somewhere where
* you initialize all your variables and setup the
* constraints settings for your JTextBox's.
* The KeyListener method should listen for the ENTER key
* being pressed and then call our parseText() method below.
*/
JTextArea parserArea = new JTextArea();
public void parseText(){
String s0 = parserArea.getText();// parserArea is our global JTextBox
s0 = s0.replace(',',' ');
s0 = s0.replaceAll("[^a-zA-Z0-9? ]","");
// reset parserArea back to a clean starting state
parserArea.setCaretPosition(0);
parserArea.setText("");
// erase what had been parsed before and also make sure no nulls found
for(int i=0;i < parsed.length; i++){
parsed[i] = "";
}
// split the string s0 to array words by breaking them up between spaces
StringTokenizer tok = new StringTokenizer(s0, " ");
// use tokenizer tok and dump the tokens into array: parsed[]
int iCount = 0;
if(tok.countTokens() > 0){
while(tok.hasMoreElements()){
try{
parsed[iCount] = tok.nextElement().toString();
if(parsed[iCount] != null && parsed[iCount].length()>1){
// if a word ENDS in ? then strip it off
parsed[iCount] = parsed[iCount].replaceAll("[^a-zA-Z0-9 ]","");
}
}catch(Exception e){
e.printStackTrace();
}
iCount++;
}
/*
* handle simple help or ? command.
* parsed[0] is our first word... parsed[1] the second, etc.
* we can use iCount from above as needed to see how many...
* ...words got found.
*/
if(parsed[0].equalsIgnoreCase("?") ||
parsed[0].equalsIgnoreCase("help")){
outputArea.setText("");// erase the output "screen"
outputArea.append("\nPut help code in here...\n");
}
}
// handle other noun and verb checks of parsed[] array in here...
}// end of if(tok.countTokens() > 0)...
}// end of public void parseText() method
...我省略了主类定义和变量initialize()方法等,因为假设您知道Java,那么您已经知道如何进行设置。这个主类可能应该扩展JFrame,并在您的公共静态void main()方法中创建它的一个实例。希望其中一些代码可以帮助您。
编辑-好的,所以接下来要做的就是创建一个Actions类并扫描一个动作(即“获取灯”或“放下剑”)。为了使其更简单,您必须具有RoomScan对象或方法来扫描范围内可见的所有内容,并仅扫描该操作上的那些对象。对象本身会处理动作,默认情况下,您应该让Item类以默认方式处理所有已知的动作,该方式可以被覆盖。现在,例如,如果您要“获取”的物品是由非玩家角色持有的,则让该物品的所有者拥有该物品的默认响应应为“不会让您拥有它”。现在,您必须在Item或Thing类中为此创建大量默认操作响应。基本上,这是从Tads3的角度来看的。因为在Tads3中,每个项目都有其自己的默认动作处理例程,如果对它的动作被初始化,解析器将调用该例程。所以...我只是告诉你,Tads3已经具备了所有这些功能,因此以这种语言编写文本冒险非常容易。但是,如果您想像从Java一样从头开始(如上),那么我个人将以与Tads3设计相同的方式来处理它。这样,您可以覆盖对不同对象本身的默认操作处理例程,因此,例如,如果您要“获取灯”并且管家正在握住它,则可能在Item的默认“获取”操作方法中触发响应或对象,然后告诉您“管家拒绝移交黄铜灯”。我的意思是……一旦您像我一样从事了足够长的程序员工作,那么这一切都是非常简单的。我今年50岁以上,并且从7岁开始从事这项工作。父亲是70年代的惠普(Hewlett Packard)讲师,所以我最初从他那里学到了有关TON的计算机编程知识。我现在也基本上是美国陆军预备役的服务器管理员。嗯...是的,所以不要放弃。一旦将其真正分解为您希望程序执行的任务,它并不难。有时,反复试验是进行此类操作的最佳方法。只需测试一下,看看,永不放弃。好的?编码是一门艺术。它可以通过许多不同的方式来完成。不要让一种或另一种方式阻碍您进入设计的角落。我在美国陆军预备役中也基本上是服务器管理员。嗯...是的,所以不要放弃。一旦将其真正分解为您希望程序执行的任务,它并不难。有时,反复试验是进行此类操作的最佳方法。只需测试一下,看看,永不放弃。好的?编码是一门艺术。它可以通过许多不同的方式来完成。不要让一种或另一种方式阻碍您进入设计的角落。我在美国陆军预备役中也基本上是服务器管理员。嗯...是的,所以不要放弃。一旦将其真正分解为您希望程序执行的任务,它并不难。有时,反复试验是进行此类操作的最佳方法。只需测试一下,看看,永不放弃。好的?编码是一门艺术。它可以通过许多不同的方式来完成。不要让一种或另一种方式阻碍您进入设计的角落。