使用Switch-blocks解析Text Adventure的输入?

时间:2011-01-22 14:51:33

标签: c# string switch-statement text-based adventure

好的,我现在有一个目标是进行基本的文字冒险。但是,要做到这一点,我需要/想要一个可以执行以下操作的switch语句:

  • 检查字符串中是否有单词SOMEWHERE。
  • 检查一个字符串是否在内部的任何组合中有两个单词。

我将如何做到这一点?你能告诉我这个具体例子的编码:

提示用户输入数据。 switch语句将“look box”视为一种情况,并将“sleep”视为另一种情况。该程序不关心任何单词的顺序,但是关心字母的顺序。

请详细解释一切。我刚刚开始编码。

编辑:谢谢你的所有答案。我知道有更好,更复杂,更有用的处理方式,但它还不是我的水平。

6 个答案:

答案 0 :(得分:12)

人们有时会问我为什么不建造船只。我很方便,我喜欢建造东西,然后我开航。我总是告诉他们,那些喜欢航行的人不应该建造船只,因为你最终会在你的船库里建造一艘船前花三年时间才能开航。如果你的目标是航行,买船。如果你的目标是建造一艘船,建造一艘船。

如果您的目标是通过撰写文本冒险来学习C#,那么您将学到很多东西。如果你的目标是编写文本冒险,那么不要使用C#,使用Inform7。它易于学习,专门用于编写文本冒险,可能是世界上最高级的语言。这是一种很棒的编程语言,我强烈推荐它。

回答你的具体问题:这不是一个很好的方法。文本冒险处理器实际工作的方式是首先编写一个程序,将用户键入的句子分解为标记。您必须逐个字符地搜索字符串,以查找单词之间的边界,例如空格,逗号,句点等。一旦找到边界,就可以提取边界之间的子串,并尝试通过将每个单词与字典中的单词进行比较来识别每个单词。

一旦你有一系列令牌,你就会尝试将序列与语法进行匹配。也就是说,您可以看到令牌序列是否可以归类为单字命令,如{“look”}或动词 - 名词短语,如{“look”,“at”,“the”,“red”,“按钮“}。你想打破它 - “看”是动词,“at”是介词,“红色按钮”是动词的对象,“the”是文章,“red”是形容词和“按钮”是名词。

听起来你是初学者,所以首先要集中精力进行词法分析;一次遍历字符串一个字符,识别单词边界,并构建List<string>个标记。语法分析技术可能非常复杂;得到简单的东西,先坚实。

答案 1 :(得分:2)

考虑到你的开始,我们可以先在简单的案例中看一下,但是你不能用switch语句来实现这个目标。

为了简单起见,我们假设您的命令限制为1或2个单词,并且第一个单词是动词,第二个单词如果存在则是名词。这为我们提供了很多可能性:

North
South
Examine
Take
Drop

等...

鉴于我们的输入字符串为strInput

string strInput = "examine hat";

我们想首先将其拆分。我们可以使用String.Split

执行此操作
string[] arguments = strInput.Split(' ');

这会给我们一个字符串数组:

参数[0]是检查

参数[1]是帽子

注意,如果用户输入了以下内容,我们并不总是第二个:

`North`

然后:

参数[0]是

我们需要检查一下!现在,检查这个的可怕(但简单)方法是:

if(arguments[0] == "North")
{
    // code to go North
}
else if(arguments[0] == "Take")
{
    // code to work with Take.  We'd check on arguments[1] here!
}
// etc...

不幸的是,这段代码会变得冗长,复杂且无法使用。你怎么知道在任何阶段你能做什么,不能做什么?你如何添加新命令?因此,让我们使用C#的精彩委托功能,并介绍Dictionary。字典允许我们将一种类型(一种键)映射到另一种类型(在本例中为委托)。使用此方法,我们可以创建委托来处理不同类型的命令。

public delegate void HandleCommand(string[] _input);

在这里,我们委派了一名代表。不要担心它,但让我们介绍一些可以使用命令的函数:

public void Handle_North(string[] _input)
{
    // code to go North.  This function could just as easily be something that handles
    // *all* directions and checks _input[0] to see where to go!
}

public void Handle_Take(string[] _input)
{
    if(_input.Length > 1) // Did the user specify an object to take?
    {
        // code to handle Take.
    }
}

等等。现在我们需要创建一个字典来将命令映射到这些函数:

Dictionary<String, HandleCommand> map = new Dictionary<String, HandleCommand>();

在这里,我们声明一个将字符串映射到我们的委托类型HandleCommand的字典。现在我们需要填充它!

map["north"] = Handle_North;
map["take"]  = Handle_Take;
// and the rest of our commands

现在,根据我们之前的示例,让我们像以前一样拆分字符串,并调用正确的处理程序!

string[] arguments = strInput.Split(' ');
if(arguments.Length > 0 && map.ContainsKey(arguments[0]))
    map[arguments[0]](arguments);  // calls our function!

现在我们有了一个可扩展的系统。添加新命令和处理程序很容易!它变得更复杂,但实质上这是做你想做的事情的好方法。

编辑:我知道你的问题说它不应该关心单词的顺序。如果你正在写一个文本冒险游戏,你最好形成一些动词/名词或其他语法,而不是允许随机输入内容。

答案 2 :(得分:1)

您无法使用switch执行此操作,您必须使用if-else-if类型的结构。

string input=...
if(input.Contains("sleep")){ //contains sleep? 
  //do stuff for some word
}else if(input.Contains("look") && input.Contains("box")){ //contains look and box
  //do stuff for the combination thing
}

switch每个case必须是一些静态的唯一值。因此,您无法使用.Contains作为案例。

答案 3 :(得分:1)

这是另一个想法:

    string input = "look at the sleep box";
    bool caseA = input.Contains("sleep");
    bool caseB = input.Contains("look") && input.Contains("box");

    int scenarioId;
    if (caseA && caseB)
        scenarioId = 1;
    else if (caseA)
        scenarioId = 2;
    else if (caseB)
        scenarioId = 3;
    // more logic?
    else
        scenarioId = 0;

    switch (scenarioId)
    {
        case 1:
            Console.WriteLine("Do scenario 1");
            break;
        case 2:
            Console.WriteLine("Do scenario 2");
            break;
        case 3:
            Console.WriteLine("Do scenario 3");
            break;
        // more cases
        default:
            Console.WriteLine("???");
            break;
    }

它使用if / then / else来评估特定场景,包括诸如"look at the sleep box"之类的输入之类的潜在组合,然后使用switch语句来执行。

答案 4 :(得分:0)

你不能直接使用switch,但是我认为你不应该这样做。您可能应该具有在与包含switch的逻辑不同的位置查找单词的逻辑。

考虑使用枚举来包含所有可能的操作:

public enum AdventureAction
{
    LookBox,
    Sleep
}

考虑编写一个执行“解析”的方法:

public static AdventureAction Parse(string text)
{
    if (text.Contains("look") && text.Contains("box"))
        return AdventureAction.LookBox;

    if (text.Contains("sleep"))
        return AdventureAction.Sleep;
}

然后您可以使用简单的switch语句来执行操作:

var action = Parse(input);
switch (action)
{
    case AdventureAction.LookBox:
        // do something interesting with the box
        break;

    case AdventureAction.Sleep:
        // go to sleep
        break;
}

答案 5 :(得分:0)

我目前正在编写自己的文本冒险引擎,因为Inform / Adrift / Quest都倾向于有一个致命的缺陷让我感到困惑 - 通知噩梦般的,混淆语法(这来自UX设计师喜欢为初学者提供尽可能简单的东西),Adrift丑陋的读者和Adrift / Quests缺乏真正的课堂/对象支持。

这可能不是最好的方法,但它到目前为止工作正常。我查看了Regex,但决定这样做。

首先要做的是将播放器的输入/命令字符串拆分为List。幸运的是,在这些游戏中,这个列表的第一个元素几乎总是一个动词。

  • 外观
  • 蓝色

您将需要可通过键访问的动词/对象/ etc数据类,其中包含可与其匹配的所有值,例如&#34;查看,检查,ex&#34;。

class Verb
{
    string uniqueID;
    List<string> values;
}

class Object
{
    public uniqueID; // Because this is shared with Verbs, you could do a better unique ID system, but hey...
    public List<string> articles;
    public List<string> adjectives;
    public List<string> noun;
}

你还需要一堆&#34;动作&#34;将从玩家的输入中匹配的子类。在这里你&#34;建立&#34;你的句子结构需要与玩家的输入相匹配。

  • 动作(基类)
    • 外观
    • look {at} [object]
    • 看{at} book
    • 跳转
    • 跳转{on / onto} [object]

class Action
{
    string uniqueID;
    List<SentenceElement> sentence;

    void Execute();
    bool RestrictionsCheck();
}

class Look : Action
{
    override void Execute();
    override bool RestrictionsCheck();
}

class LookAtObject : Action
{
    override void Execute();
    override bool RestrictionsCheck();
}

class LookAtBook : Action
{
    override void Execute();
    override bool RestrictionsCheck();
}

基类Action类有一个使用SentenceElements的句子构建器。它可以是一个逐段描述句子的列表。

class SentenceElement
{
    List<string> values;
    bool isOptional;
}

class VerbID : SentenceElement {}
class ObjectID : SentenceElement {}
class ObjectAny : SentenceElement {}
class CharacterID : SentenceElement {}
class CharacterAny : SentenceElement {}
class TextOptional : SentenceElement {}
class TextRequired : SentenceElement {}
class Parameter : SentenceElement {}

您现在可以搜索您的&#34;行动&#34;类,将第一个SentenceElement与玩家第一个输入动词进行比较,并保留一个匹配的列表作为&#34; potentialActions&#34;。 &#34; string.Contains&#34;会工作的。

现在你必须通过踩过你的玩家来找到最接近的匹配动作&#39;输入命令,并逐步执行比较它们的每个SentenceElement。保留每个位置的索引(playerInputIndex,potentialActionsIndex,sentenceElementIndex)。

如果找到匹配项,请增加playerInputIndex,直到它与SentenceElement不匹配,检查您的设置(isOptional等),然后移动到下一个句子元素索引并再次运行比较。最终,您将到达播放器输入或SentenceElements的末尾。

如果SentenceElements是&#34; isOptional&#34;,则会添加复杂性,因此必须进行检查,或者具有ObjectAny类型的SentenceElements的动作不应该与现有的一致,但显示一个&#34;你想吃哪个对象?&#34;信息。此外,与动词不同,对象具有额外的匹配参数,例如前缀/形容词/名词,而不是动词。

此系统还需要为您可能想要执行的每个操作创建一个新类。每个动作都有自定义&#34;限制&#34;它必须在它运行之前通过,例如&#34;引用的字符是否存活?&#34;等等。