テキストアドベンチャーでのユーザーコマンドの解析は、アドベンチャーの単純な「北へ」から、hhgttgでの気が遠くなるような巧妙なものまでさまざまです。
80年代にコンピューター雑誌で素敵なハウツーを読んだことを覚えているようですが、今はウィキペディアの簡単な参考資料を除いて、ネット上でほとんど何も見つかりません。
どうしますか?
更新:Ludum Dareエントリーで可能な限り単純なアプローチを使用しました。
テキストアドベンチャーでのユーザーコマンドの解析は、アドベンチャーの単純な「北へ」から、hhgttgでの気が遠くなるような巧妙なものまでさまざまです。
80年代にコンピューター雑誌で素敵なハウツーを読んだことを覚えているようですが、今はウィキペディアの簡単な参考資料を除いて、ネット上でほとんど何も見つかりません。
どうしますか?
更新:Ludum Dareエントリーで可能な限り単純なアプローチを使用しました。
回答:
インタラクティブなフィクションコミュニティで検索しましたか?彼らは今でもパーサーを書いており、自然言語処理などの新しい技術を実装することで限界を押し広げようとしている人もいます。
使用されたアプローチを説明する記事については、例えばこのリンクを参照してください。
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形式の変形で、文法を表す標準的な方法です。とにかく、パーサージェネレーターを使用してこの文法を解析するコードを生成するか、言語に適切な文字列処理がある場合は独自のコードを簡単に作成できます。(文法の各行に1つの関数を使用する「再帰降下パーサー」を探します。)
構文解析したら、文が理にかなっている場合は解決できます-「北へ行く」は理にかなっているかもしれませんが、「緑の北を取得する」は理にかなっていません。これは2つの方法で解決できます。文法をより形式的にする(たとえば、特定の種類の名詞でのみ有効なさまざまな種類の動詞を持つ)か、後で動詞に対して名詞をチェックします。最初の方法は、プレーヤーにより良いエラーメッセージを提供するのに役立ちますが、コンテキストを常に確認する必要があるため、とにかくある程度は常に2番目の方法を実行する必要があります。「グリーンキーを取得」は文法的にも構文的にも正しいですが、グリーンキーが存在することを確認する必要があります。
最終的に、プログラムはすべてのさまざまな部分がチェックされた検証済みコマンドで終了します。それは、アクションを実行するための引数を使用して適切な関数を呼び出す場合にすぎません。
今日のテキストアドベンチャーを作成するための最新技術は、Inform 7を使用しています。Inform 7のソースは、Informベースのゲームで「英語を書く」のと同じ方法で「英語のように」読みます。たとえば、エミリーショートのブロンズから:
ものには香りと呼ばれるテキストがあります。物の香りは通常「無」です。
ブロック臭いルールは、どのルールブックにもリストされていません。
何かの匂いを嗅ぐ:
「(名詞から)あなたは(名詞の香り)を嗅ぐ」と言う。
部屋の匂いを嗅ぐ代わりに:
香りのあるものにプレーヤーが触れることができる場合、「あなたはにおいを嗅ぐ[プレーヤーが触れることができる香りのあるもののリスト]」と言います。
そうでなければ「その場所は至福の臭いがない」と言う。
Inform 7パーサーはInform 7 IDEと密接に統合されており、ソースコード全体はまだ学習できません。
テキストアドベンチャーパーサーの作成方法を学習するための現在の2つの最良のソースは、(前述したように)IFコミュニティと泥コミュニティです。それらの主要なフォーラム(Intfiction.org/forum、ニュースグループrec.arts.int-fiction、Mud Connector、Mudbytes、Mudlab、Top Mud Sites)を検索すると、いくつかの答えが見つかりますが、見ているだけなら記事については、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ではなく、かなりの研究の主題です。
大学の1年生で、Prologでアドベンチャーゲームを作成しました。ユーザー入力には、確定条項の文法またはDCG を使用する必要がありました。コマンド言語として使用する例については、http://www.amzi.com/manuals/amzi/pro/ref_dcg.htm#DCGCommandLanguageを参照してください。当時は原則的(結局ユニ)で柔軟なアプローチのようでした。
ゲーム内で正しいすべての文であるドメイン固有の言語を定義する必要があります。そのためには、言語(語彙と構文)の文法を定義する必要があります。必要な文法の種類はContext Free Grammarであり、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を拡張し、パブリックstatic void main()メソッドでインスタンスを作成するだけです。このコードの一部が役に立てば幸いです。
編集-さて、次はアクションクラスを作成し、アクション(つまり、「ランプを取得」または「剣を落とす」)をスキャンします。より簡単にするには、スコープ内に表示されているすべてをスキャンし、そのアクションのオブジェクトのみをスキャンするRoomScanオブジェクトまたはメソッドが必要です。オブジェクト自体がアクション処理を処理します。デフォルトでは、Itemクラスに既知のすべてのアクションをデフォルトの方法で処理させる必要があります。これはオーバーライドできます。たとえば、「取得」したいアイテムが非プレイヤーキャラクターに保持されている場合、そのアイテムを所有者に保持させるためのデフォルトの応答は、「あなたにそれを持たせない」のようなものです。ここで、ItemクラスまたはThingクラスで、これに対する大量のデフォルトアクションレスポンスを作成する必要があります。これは基本的に、すべての設計に関するTads3の観点から来ています。Tads3では、各アイテムには独自のデフォルトアクション処理ルーチンがあり、そのアクションが初期化されるとパーサーがそのルーチンを呼び出すためです。だから...ただ、Tads3にはこれがすべて揃っているので、その言語でのテキストアドベンチャーでのコーディングは非常に簡単です。しかし、Java(上記)のように最初からやりたい場合は、個人的にTads3が設計されたのとほぼ同じ方法で処理します。そのようにして、異なるオブジェクト自体のルーチンを処理するデフォルトのアクションをオーバーライドできます。たとえば、「ランプを取得」したいが執事がそれを保持している場合、Itemのデフォルトの「get」アクションメソッドで応答をトリガーできますまたは反対し、「執事は真鍮のランプを渡すことを拒否します」とあなたに伝えます。つまり、あなたが私と同じくらい長くプログラマーになれば、これはとても簡単なことです。私は50歳以上で、7歳のときからこれをやっています。私の父は70年代にヒューレットパッカードのインストラクターだったので、最初はコンピュータープログラミングで彼からTONを学びました。私は現在、基本的にサーバー管理者として米国陸軍予備軍にもいます。ええと...ええ、あきらめないでください。プログラムで何をしたいのかを実際に解いてしまえば、それほど難しくはありません。この種のものを試すには、試行錯誤が最善の方法である場合があります。試してみて、あきらめないでください。はい?コーディングは芸術です。それは多くの異なる方法で行うことができます。設計の隅にあなたをブロックするように思わないでください。基本的にサーバー管理者としての米国陸軍予備役のm。ええと...ええ、あきらめないでください。プログラムで何をしたいのかを実際に解いてしまえば、それほど難しくはありません。この種のものを試すには、試行錯誤が最善の方法である場合があります。試してみて、あきらめないでください。はい?コーディングは芸術です。それは多くの異なる方法で行うことができます。設計の隅にあなたをブロックするように思わないでください。基本的にサーバー管理者としての米国陸軍予備役のm。ええと...ええ、あきらめないでください。プログラムで何をしたいのかを実際に解いてしまえば、それほど難しくはありません。この種のものを試すには、試行錯誤が最善の方法である場合があります。試してみて、あきらめないでください。はい?コーディングは芸術です。それは多くの異なる方法で行うことができます。設計の隅にあなたをブロックするように思わないでください。