在基于Java文本的游戏中解析命令的最佳方法

时间:2014-12-22 16:29:52

标签: java parsing

我正在用java开发一个基于文本的游戏,我正在寻找处理玩家命令的最佳方法。命令允许玩家与环境交互,例如:

  • “向北看”:详细描述你在北方的情况
  • “饮料药水”:在您的库存中挑选一个名为“药水”的物品并将其饮用
  • “触摸'奇怪的按钮'”:触摸一个名为“奇怪按钮”的对象并触发一个动作,如果有一个动作,就像“哎呀你死了......”
  • “库存”:了解您的广告资源的完整说明 等...

我的目标是开发一套完整的简单命令,但是我很难找到一种简单的方法来解析它。我想开发一个灵活且可扩展的解析器,它可以调用主命令,如“look”,“use”,“attack”等......并且每个命令都会在游戏中具有特定的语法和动作。

我找到了许多解析命令行参数的工具,比如-i -v --verbose,但它们似乎都没有足够的灵活性来满足我的需求。它们可以逐个解析,但不考虑每个参数的特定语法。我试过看起来很完美的JCommander但是我在参数,参数,谁叫谁等之间迷失了......

因此,如果有人可以帮助我选择正确的java库来做到这一点,那就太好了:)

3 个答案:

答案 0 :(得分:2)

除非您处理涉及例如算术表达式或平衡括号的复杂命令字符串,否则我建议您使用普通的扫描仪。

这是一个我觉得可读且易于维护的例子:

interface Action {
    void run(Scanner args);
}

class Drink implements Action {
    @Override
    public void run(Scanner args) {
        if (!args.hasNext())
            throw new IllegalArgumentException("What should I drink?");
        System.out.println("Drinking " + args.next());
    }
}

class Look implements Action {
    @Override
    public void run(Scanner args) {
        if (!args.hasNext())
            throw new IllegalArgumentException("Where should I look?");
        System.out.println("Looking " + args.next());
    }
}

并将其用作

Map<String, Action> actions = new HashMap<>();
actions.put("look", new Look());
actions.put("drink", new Drink());

String command = "drink coke";

// Parse
Scanner cmdScanner = new Scanner(command);
actions.get(cmdScanner.next()).run(cmdScanner);

您甚至可以将它变得更加漂亮并使用注释,如下所示:

@Retention(RetentionPolicy.RUNTIME)
@interface Command {
    String value();
}

@Command("drink")
class Drink implements Action {
    ...
}

@Command("look")
class Look implements Action {
    ...
}

使用如下:

List<Action> actions = Arrays.asList(new Drink(), new Look());

String command = "drink coke";

// Parse
Scanner cmdScanner = new Scanner(command);
String cmd = cmdScanner.next();
for (Action a : actions) {
    if (a.getClass().getAnnotation(Command.class).value().equals(cmd))
        a.run(cmdScanner);
}

答案 1 :(得分:1)

我认为您不想解析命令行参数。这意味着游戏中的每个“移动”都需要运行一个新的JVM实例来运行不同的程序,以及在JVM会话之间保存状态等额外的复杂性。

这看起来像一个基于文本的游戏,您可以提示用户下一步该做什么。您可能只想让用户在STDIN上输入输入。

示例,假设您的屏幕显示:

  

你现在在一个黑暗的房间里。有一个电灯开关   你想做什么?

1. turn on light
2. Leave room back the way you came.

Please choose option:

然后用户输入12,或者如果你想要花哨turn on light等,那么你从STDIN得到readLine()并解析字符串以查看用户选择了。我建议您查看java.util.Scanner以了解如何轻松解析文本

Scanner scanner = new Scanner(System.in);
String userInput = scanner.readLine();
//parse userInput string here

答案 2 :(得分:1)

它的有趣之处在于让一些命令是人类可读的,同时它的机器可以解析。

首先,您需要定义语言的语法,例如:

look (north|south|east|west)

但它是正则表达式,一般来说,这不是解释语法规则的最佳方式,所以我想说这更好:

Sequence("look", Xor("north", "south", "east", "west"));

所以通过这样做,我认为你已经有了这个想法。你需要定义类似的东西:

public abstract class Syntax { public abstract boolean match(String cmd); }

然后

public class Atom extends Syntax { private String keyword; }

public class Sequence extends Syntax { private List<Syntax> atoms; }

public class Xor extends Syntax { private List<Syntax> atoms; }

使用一堆工厂函数来包装构造函数,返回语法。那么你最终会得到这样的东西:

class GlobeSyntax
{
    Syntax syntax = Xor( // exclusive or
         Sequence(Atom("look"), 
                  Xor(Atom("north"), Atom("south"), Atom("east"), Atom("west"))),
         Sequence(Atom("drink"),
                  Or(Atom("Wine"), Atom("Drug"), Atom("Portion"))), // may drink multiple at the same time
         /* ... */
    );
}

左右。

现在你需要的只是一个根据这些规则的递归解析器。

你可以看到,它的递归结构,非常容易编码,而且非常容易维护。通过这样做,您的命令不仅是人类可读的,而且是机器可解析的。

确定它还没有完成,你需要定义动作。但它很容易吗?这是典型的OO技巧。所有需要做的就是在Atom匹配时执行某些操作。