本書はTADS製作者用手引きの一部です。
Copyright (C) 1987, 1996 by
Michael J. Roberts. All rights reserved.
この手引きはN. K. Guy、tela designによってHTMLに変換されました。
第4章
前の章は多目的のオブジェクト指向言語としてのTADSを、テキストアドベンチャーシステムとしての応用について少し触れながら説明しました。この章はTADSゲームのユーザインターフェースの核である「プレーヤーコマンドパーサー」を紹介します。
プレーヤーコマンドパーサーはプレーヤーからの命令を読み取り、それらの命令をゲームプログラム中のオブジェクトへ分解します。そして、それらの命令にしたがってオブジェクト内のメソッドを呼び出します。TADSのパーサーは非常に洗練されていて、プレーヤーに進歩的特長を提供するとともに、それらの特長を実装する必要をあなたのゲームから取り除いてくれます。パーサーの機能の多くを仕立て直す方法もありますが、あなたがある程度関数とオブジェクトを定義する必要があります。
本章はパーサーの特長および、どのように動作し、どのようにゲームプログラムとやり取りするのかを解説します。ゲームが必ず定義しなければならない特別な関数とオブジェクトも説明されます。簡潔に言うと、本章はゲームプログラムのランタイム環境全体を解説します。
テラ・デザインから10月23日付の注意 ― この章の内容は大部分がPart II of the 2.2 Release Notesに取って代わられています。
プレーヤーが完全な文章を入力すると、TADSプレーヤーコマンドパーサーがその文章を眺め始める前に、preparseというユーザが定義した特別な関数が実行されます。
この関数は引数を一つ取りますが、その引数はプレーヤーがコマンドラインにタイプしたテキストを含んでいる文字列です。テキストは改変されずにまるごと供給され、元の空白、句読点、大文字も保たれます。preparse()が呼ばれた時点では、パーサーがまだその文字列を見ていないので、アクター、動詞、その他のオブジェクトはまだ判明しません。
自前のpreparse関数でコマンド文字列を新しい文字列へ好きなように変えることができます。また、コマンドを完全に無効にしたり、何もしないことを選択したり、プレーヤーのコマンドを通常通り処理させたりできます。
preparse()が値として文字列を返す場合、その新しい文字列がプレーヤーのタイプした元のコマンドに取って代わります。パーサーは新しい文字列をプレーヤーがタイプしたものとして処理します。
この関数がnilを返す場合、そのコマンドは完全に中止され、プレーヤーは即座に新しいコマンドの入力を要求されます。
この関数がtrueを返す場合、プレーヤーによってタイプされた元の文字列はそのまま使用されます。
この関数は選択自由です。ゲームプログラムが定義していない場合、コンパイラは警告を出しますが、それでもゲームはコンパイルされ、実行されます。preparse関数はある特別な状況下でのみ役に立ちます。つまり、ほとんどのゲームにとって必要ではありません。たとえば、プレーヤーがある特別なコマンドをタイプするまでタイプしたことがそのままオウム返しになるような特別な効果を持った部屋を実現するためにpreparseを使えるでしょう。
プレーヤーは厳密に英語の文章のレベルでTADSアドベンチャーゲームとやり取りします。ゲームはプレーヤーの居場所を説明し、プレーヤーは何かを行うためにコマンド(一つの命令文)で応じます。ゲームはその行動の結果の説明で対応します。そしてプレーヤーは新しいコマンドを出し、ゲームはこれに応答します。プレーはプレーヤーが疲れて止める(またはゲームを完了する)までこんなふうに進められます。
プレーヤーが関係する範囲においてゲームの最重要部分は物語、つまり、状況、人物、ことがらの説明とコマンドへの反応です。アドベンチャー作家の仕事はこの部分を面白くすることであるべきです。
ですが、ゲームの非常に重要な部分は理想としてプレーヤーの目に触れないパーサーと言えます。パーサーはプレーヤーのコマンドを詳しく調べ、それらをアドベンチャー作家が実装したようにゲームにとって意味のあるものにしようと試みます。私たちはパーサーは「理想として目に触れない」と言いました。なぜなら、プレーヤーがパーサーのことを心配する必要があるべきではないし、それどころかその存在に気付くことすら望ましくないからです。パーサーがプレーヤーの言うことを理解するときにはプレーヤーがパーサーを気にかけることは決してありません。プレーヤーがパーサーに注意するのは、パーサーがコマンドを理解できない、ひどい場合、誤解してしまうときです。
パーサーを目に見えないものにする仕事の大部分はTADSランタイムシステムの手中にありますが、ランタイムシステムは多種多様な文が理解されることを保証すべく努力します。しかし、アドベンチャー作家は多くの同意語がそれぞれの単語としてしっかり認められるようにする責任があるのです。なぜなら、人によってボキャブラリーがまちまちだからです。優れた経験則としては、ゲームで使われている言葉、特に重要なオブジェクトを指す言葉はすべて理解してしかるべきでしょう。
すべてのプレーヤーコマンドが動詞を持ちます。一つの動詞が持てるすべてのものであるコマンドもあります。たとえば、「look」と「east」というコマンドは一つの動詞のみです。
他のコマンドは「take the book」「drop the box」のように直接目的語を取ります。冠詞の「the」は任意のものです。いくつかのアドベンチャーパーサーと違い、TADSのパーサーは冠詞を無視しません。そして、冠詞は名詞との組み合わせのみに使用されること、しかし冠詞は明らかに名詞の意味に影響を及ぼさないことを確約します。
「put the book in the box」「give the librarian the book.」のように間接目的語を取るコマンドもあります。前者は間接目的語を特定するために前置詞を用い、後者はどちらのオブジェクトがどちらかを特定するために文中における語の位置を利用します。
中にはもう少し複雑な動詞もあります。「pick up the book」と「pick it up」を例に取りましょう。この場合、前置詞は動詞の一部とみなされますので、動詞部分は「pick up」であり、直接目的語は「the book」になります。
TADSパーサーが扱えるのは一語からなる前置詞だけなので注意して下さい。やっかいなことに、英語には複数の前置詞が必要な構文がたくさんあります。たとえば、「take the book out of the bag」は二つの前置詞が連続しています。
特定の組み合わせの下に現われる「糊付け」された複数の語を定義できる特別なメカニズムがあります。このメカニズムは複数の語から成る前置詞を許容するとともにそれらを単一の語として扱うためのものです。例えば、「out of」を一つの語に変換することが可能です。共に使われるときに一つの単位に変換できる語を定義するために、compoundWord構文を使います。この構文はゲームファイル中の関数とオブジェクトの外側に登場します。adv.tは合成語の巨大なリストを定義していて、それらはほとんどのゲームを充足できます。
compoundWord構文は次のようになります:
compoundWord 'out' 'of' 'outof';
これはパーサーに対して、パーサーが「out of」という二語の連続を認めたときに、それらが単一の「outof」へ変換されるべきであることを告げるものです。これで合成語の「outof」は(prepositionプロパティーにおいてと同様)ボキャブラリー内で利用可能になりました。
三語以上の合成語を直接定義することはできないので注意してください。ただし、間接的には二語の命令を素にしてもっと長い語を組み立てることで可能です。例えば、「out from under」を「outfromunder」へ変換するには、こうします:
compoundWord 'out' 'from' 'outfrom'; compoundWord 'outfrom' 'under' 'outfromunder';
結果として生じる語(compoundWord命令における三番目の語)は一番目の語がくっついた二番目の語である必要はありません。とはいえ、TADSの古いバージョンは合成語をこの作法にしたがって自動的に組み立てましたから、従うべき理にかなった慣習です。別に気軽に「out from」を'out-from'または'asdf'その他へ望み通りに合成語を定義してもかまいません。
もう一つ特長があります。プレーヤーは「Librarian, take the book off of the shelf.」のように登場人物の名前とコンマを文の前に置くことにより、あるコマンドをゲーム中のもう一人の人物(「actor」という)に対して出すことができるのです。
プレーヤーのコマンドを解析しているとき、何よりも先に文全体が小文字に変換されるので、プレーヤーのコマンドの大文字小文字の別は問題になりません。さらに、プレーヤーはすべての語について最初の6文字をタイプするだけでいいのです。ただし、もしプレーヤーがさらにタイプすることを選択しても、余分な文字が単に無視されてしまうわけではありません。だから6文字より長い語が最初の6文字については他の語と区別できないにもかかわらず、最終的には正確に識別されるのです。これは特に長い語の複数形を扱う際に有用です。この理由で、もしプレーヤーが「barrel」とタイプしたら、それは「barrels」ではなく「barrel」として特定されます。
オブジェクトの名前は一つの名詞と一つ以上の形容詞から成ります。例えば、「book」「red book」「large red book」はすべてある特定のオブジェクトを参照するものです。
これに加えて、「of」という語は条件を満たす句をオブジェクト名へ結合できます。例えば、「the large pile of yellow straw」がそうです。内部的観点からいうと、ゲームをデザインするとき、アドベンチャー作家は単にオブジェクトの参照に使われる形容詞と名詞を表にするだけでよく、語の置き換えについて配慮する必要はありません。上の例において、プレーヤーはthe pile of strawは名詞「pile」「straw」と形容詞「large」「yellow」によって特定されると言うでしょう。だから、次の句もすべてむぎわらの山として認識されるのです:「straw」「pile」「pile of straw」「large pile」「yellow straw」。注意してほしいのですが、「of」という語はプログラムゲームにとって完全に透明です。パーサーは自動的に「of」を除去し、その言葉を適切に修整します。
もう一つの特別な事情は数が形容詞として使われるときに発生します。あるオブジェクトの形容詞を定義する際に、ある数が形容詞として使われるようにしたい場合、単にそれを他の形容詞と同様にシングル引用符でくくって、adjectiveリストの中に含めます。しかし、プレーヤーがコマンドをタイプしたときに、それは他の形容詞と少し違った扱いを受けます。どういうことかというと、数字が名詞をフォローできるのです。これは番号が付いた物品の場合に便利です。なぜなら名詞の後に数字を置くほうがより自然だからです。例えば、エレベータのボタンを「button 1」「button 2」などと呼ぶことが考えられます(もちろん、それらを「1 button」「2 button」という具合に参照することもできます。ただ、それだとボタンの番号というよりボタンの数に聞こえるので幾分まぎらわしくなります。)。「of」同様、この特別な問題は、パーサーが配慮するのはオブジェクトを参照する言葉の順番のみであるという事情によりゲームプログラムにとって透明です。
時々、曖昧なオブジェクトが存在します。例えば、a red book と a blue bookです。プレーヤーが単に「book」とタイプした場合、パーサーはプレーヤーが意図しているのはどちらの本か見極めようと最大限努力します。まず初めに、プレーヤーが発行しようとしている命令にとって意味のあるすべての名詞を特定しようとします。それにより大概絞り込まれ、さらなる情報は必要でなくなります。例えば、プレーヤーが青い本を持っていて、赤い本が今いる部屋にあるとします。パーサーは「take the book」が赤い本のことを指していることを理解します。なぜなら、青い本はtakeコマンドにとって無意味だからです。しかし、両方の本が部屋にある場合、パーサーはどうすればいいのか分かりません。そのために次のように質問します:
Which book do you mean, the red book, or the blue book?
ここで、プレーヤーは「red」または「blue」、もしくは「the red book」あるいは「the blue one」というようにパーサーが命令をまっとうできる選択肢を答えます。「both」「all」「the red one and the blue one」と答えることも可能で、その場合パーサーは両方の本を取ります。そういうわけで、プレーヤーは作者が複数形を用意していると推測したうえで最初のところで「take the books.」と言えばよかったということになります。
このことは私たちを複数の直接目的語という問題に案内します。プレーヤーは取りたい、またはそれに対して別の行為をしたいいくつかのオブジェクトを、「Take the red book, the box, and the lamp off the shelf.」のように単にコンマを使うことによって列挙できます。
また、プレーヤーはこれを「evrything」と「all」という語(両者は同意語)を使ってある程度まで省略できます。例えば、「Take everything off the shelf」または「drop all.」のように。もう少し細かく指定したいなら、「Drop everything except the keys on the desk」や「Take all but the rusty knife, the useless lantern, and the skeleton.」のようにできます。
アドベンチャー作家の立場からすると、プレーヤーが複数の直接目的語を使う場合にすべき仕事は残されていません。パーサーはそのコマンドをより簡素な複数のコマンドにばらすからです。例えば、プレーヤーが「Take the red book, the blue book, and the nasty knife」とタイプした場合、パーサーはこれをあらかじめ調理して、ゲームが三つの別々の命令:「Take the red book」「take the blue book」「take the nasty knife.」を理解するようにします(実際は、パーサーはこれ以上の働きをするのでゲームはもっと簡素な表現でも理解できます。コマンドの最終的に調理された形は後で論じられます)。
直前のいくつかのコマンドをピリオドで終えているのに注目してください。これは任意のものですが、パーサーにその文が終わることを明示します。プレーヤーが複数の文を一行の中に収めようとするときに有用です。
Take everything; go east, then take the lamp and look at it!
セミコロン、感嘆符、終止符、疑問符、「then」は皆同じ意味―その文が終わること―を持ちます。コンマと「and,」は一つのコマンドについて複数のオブジェクトを列挙する役目に加えて、コンマや「and」に続く語がもう一つのオブジェクトとして解釈される可能性がない限り、コマンドを分割するためにも用いられます。
次は「it,」とそのパートナーの「them.」です。これらの語は最後の直接目的語、またはある場合において、ゲームが場所や出来事の説明の中で言及したあるオブジェクトを指すために使われます(組み込み関数setit()はアドベンチャー作家が「it」が次のコマンド中で何を意味するのか定義するために使われます)。「Them」は「it,」のように働きますが、「Take the red book and the blue book and put them in the box.」のように複数の直接目的語が使用されている場合、オブジェクトの最後のリストを指します。
「it」「them,」に加えて、パーサーは登場人物に言及するための「him」「her」を理解します。あるオブジェクトがtrueに設定されたisHimプロパティを持っている場合、パーサーはプレーヤーがそのオブジェクトを「him」で表わすことを許します。もしもそのオブジェクトのisHerがtrueであるなら、プレーヤーはそのオブジェクトを指すのに「her」を使えます。isHimもisHerもtrueに設定されていないときは、そのオブジェクトについては「it」だけが使えます。また、あるオブジェクトについて「him」も「her」も使っておかしくない場合、isHim、isHer双方をtrueにしてもかまいません。なにもしなければ両方ともnilになります。
もう一つ、いささか特殊な問題:プレーヤーがコマンドの中で引用符で挟んだ文章または数字を使ったらどうなるのかという問題があります。例えば:
turn dial to 651 type "hello" on the computer keyboard
当然の話ですが、プレーヤーがダイヤルするかもしれないあらゆる数字、または、プレーヤーがコンピュータのキーボード上でタイプするかもしれないあらゆる文章をゲームデザイナーがあらかじめ列挙しておくのは現実的ではありません。ではどうなっているのかというと、この手のアイテムは特別な疑似オブジェクトとして扱われるのです。こうしたオブジェクトは通常の(しかし独特の)オブジェクトとしてゲームに登場しますが、ゲームがその値(651や「hello」)を覚えておけるように特別なプロパティーを持っています。この事情のおかげで、ゲームデザイナーはパーサーの仕事を少しも損なわさずに数字と文章を通常通り扱うことができるのです。
私たちはゲームプログラムがコマンドをいかに理解するのかという洞察を交えながらユーザの視点からパーサーを説明してきました。ここからはパーサーの仕事のもっと低い層を説明します。
パーサーがコマンドを一通り見終わった時点で、センテンスの文法になんの問題もなければ、コマンドはわずか五つの部分:アクター、動詞、直接目的語、間接目的語、前置詞、に分けられています。これらのうち、アクターと動詞のみが必ず存在し、ほかのものはnilである可能性があります。これらのアイテムはすべてnilまたはゲームプログラムによって定義されたオブジェクトのどちらかです。
パーサーは語をボキャブラリーに基づくオブジェクトへ分解します。ユーザーが利用可能なボキャブラリーを形成する語は(「and」や「then」のような)組み込み済みのいくつかの語とゲームの中で定義された語によって構成されます。ボキャブラリー・ワードは特別なプロパティーをオブジェクトに付けることだけで作られます:
RedBook: object noun = 'book' adjective = 'red' 'large' plural = 'books' ;
オブジェクト定義において、特別なプロパティー名 noun、adjective、pluralは一つ以上の単語として定義され、シングル引用符で挟まれます。こうした単語はプレーヤーが使用できる語彙となり、その言葉が使われる文脈を特定します。プレーヤーがある文中に「red book」という語を使うと、パーサーはプレーヤーがRedBookというオブジェクトを参照したことを理解します。
動詞も同様に定義されますが、いささか罠があります。前置詞を取る動詞があることを思い出してください。これらはこのように定義されます:
TakeVerb: object verb = 'take' 'pick up' ;
これは「take」とオブジェクトTakeVerbを関連づけるものであり、「pick up」も同様です。プレーヤーが「pick it up」のようなコマンドをタイプしたとき、パーサーはその動詞がオブジェクトTakeVerbだと判断します。一つのボキャブラリーワード文字列の中に二語が現われるこの書き方は、動詞に特有のものであり、他の場所では許されません。
ここで二種類のオブジェクトを見極めることが重要です。オブジェクトの一つ目の種類はゲームプログラムの中に現われる類のものです。これらは上位クラスとプロパティーを持ち、コードによって操作されます。もう一つはプレーヤーが「take the book.」というときに考えるものです。つまり、私たちがある動詞をオブジェクトと呼ぶときには、一つ目の意味で言っているのです。ただし、ユーザーが名詞と形容詞を用いてあるオブジェクトを指定することについて触れるときは、別の意味になります。
もう二つ別種のボキャブラリーワード:前置詞と冠詞があります。例えば:
OutP: object preposition = 'out' ;
AAnThe: object article = 'the' 'a' 'an' ;
二番目のオブジェクトは冠詞を区別しないことに注意して下さい。これでまったく問題がないのです。というのは冠詞がゲームプログラムに悪影響を及ぼすことは決してないからです。これらはただ単にユーザーにとってコマンドの文法をより自然にするために定義されるのです。反対に前置詞はそれぞれを区別する必要があります。put the book in the safe」は「put the book on the safe」と違うのです。
また、前置詞を動詞の中で使ってもそれは前置詞にならないことに注意して下さい。先に'pick up'を動詞として定義していたとしても、「up」のために独立した前置詞を定義しなければいけません:
UpP: object preposition = 'up' ;
ここから曖昧さの問題が出てきます。ほとんどのアドベンチャーが「go up」したいときに「up」とするのを許します。そこで、私たちは動詞をこう定義すべきです:
GoUp: object verb = 'go up' 'up' ;
一つの語を前置詞、動詞の前置詞、おまけに動詞としても定義しているのでこれは問題があるように見えるかもしれません。幸いなことに、パーサーは文脈の重要性を理解し、このような場合に混乱しません。しかし、このことをもってすべての語をすべての品詞として定義できると考えないでください。文脈による明確化が乱用されると、パーサーが混乱し、文を変なふうに解釈する恐れがあるからです。もし文のおかしな解釈に出くわしたなら、品詞の曖昧さを調べてみてください。
ボキャブラリー・プロパティーのverb、noun、adjective、article、preposition、pluralは特別な性格を持っています。これらはいくつかの理由により通常の意味でのプロパティーではありません。第一に、これらはボキャブラリーワードを作り、それらをObjectと結び付けるときに特別に認識されます。第二に、その非標準的文法、つまりワードのリストが角かっこで閉じられることなくプロパティー名の後に続いていることに注意して下さい。さらに他のプロパティーと違ってボキャブラリー・プロパティーを参照することができません。例えば、次の例は無効です:
say( dobj.noun ); // This won't work!
この理由により、ボキャブラリーと結合したいかなるオブジェクトも短い説明、つまりsdescプロパティーを持たなくてはいけません。したがって、上の前置詞「up」の完全な説明は次のようになります:
UpP: object preposition = 'up' sdesc = "up" ;
文法の誤りその他の問題について特定の訴えをする際に、パーサーは時折、前置詞、名詞あるいは動詞のsdescを必要としますので、各オブジェクトは必ずそれを持つようにしてください。冠詞はパーサーに関する限りけっしてsdescを必要としません。sdescはあるオブジェクトの短い説明を表示するためにパーサーが利用する内蔵プロパティーですが、ボキャブラリー・プロパティーと違って、通常のプロパティーと同様に振る舞います。
ボキャブラリー・ワードは通常、文字だけで構成されます。数字も、そのワードが少なくとも一つの文字から始まる、または全体が数字から成る限り、許されます。(全体が数字になりうるのは形容詞だけです)したがって、'a123' はどんな品詞にも有効であり、'123' は形容詞として有効ということになります。加えて、ハイフン('-')とシングル引用符が、そのワードの最初が文字である場合に限ってワード内で認められます。おしまいに、ワードは最後の文字としてピリオド('.')を持つことができます。つまり、これにより「Mr.」や名前のミドル・イニシャルのような語をボキャブラリー・ワードとして使えるのです。
sdescに加えて、名詞はthedescおよびadescを定義しなければいけません。これらは定冠詞と不定冠詞それぞれに続くオブジェクトの名前を表示します。ほとんどの場合、thedescは単純にそのオブジェクトのsdescの前につく「the」であり、同じく、adescはsdescの前の「a」になります。
Thing: object thedesc = { "the "; self.sdesc; } adesc = { "a "; self.sdesc; } ;
adescは特定のオブジェクトについてはオーバーライドされる必要があります。例えば、母音で始まるsdescを持つオブジェクトは「a.」ではなく「an」を使わなければいけません。集合、人々、なにかの量を表わすオブジェクトは意味を通すためにしばしば特別なadescが必要です:「a milk,」より「some milk」の方がふさわしく、「a Lloyd.」ではなく「Lloyd」であるべきです。
コマンドはアクター、動詞、直接目的語、間接目的語、前置詞へと分解されます。したがって、「pick up the red book」のようなコマンドは次のように分解されます:アクターはMe、プレーヤー・アクター;動詞はtakeVerb;直接目的語はRedBook;間接目的語と前置詞はnil。
では、この情報を使ってゲームは何をするのでしょう? 再び、パーサーは多くの仕事を行ってゲームデザイナーに手助けします。まず、パーサーはどんなオブジェクトがそのコマンドに関わるのか決定し、続いてそれらの有効性を動詞のvalidIoおよびvalidDoメソッドを用いて検査します(以降の意味限定についての話を参照)。もしもそのオブジェクトが無事に特定されコマンドに対して有効であるなら、パーサーはメッセージの束をオブジェクトに送り、それらがプレーヤーのコマンド内で使われていることを通知します。一連の流れを疑似コードで表現すると下のようになります。
actor.roomCheck(verb) actor.actorAction(verb, direct object, preposition, indirect object) actor.location.roomAction(actor, verb, direct object, preposition, indirect object) if (An indirect object was specified) direct object.verDoVerb(actor, indirect object) if (No Output resulted from verDoVerb) indirect object.verIoVerb(actor) if (No output resulted from verIoVerb) indirect object.ioVerb(actor, direct object) else if (A direct object was specified) direct object.verDoVerb(actor) if (No output resulted from verDoVerb) direct object.doVerb(actor) else verb.action(actor) Run each daemon that is currently active Run and remove each fuse that has burned down
これは複雑に見えるかもしれませんが、概ね心配には及びません。通常、自分のゲームの中で欲しい効果を得るにはこれらの手順のうち一つか二つだけ修正して、残りは標準定義に面倒を見させればいいのです。それでは流れを詳しく追っていきましょう。
最初の手順はその動詞がここで間接的にも許可されているのかどうか確かめるために、アクターを検査するべく、アクターのroomCheckメソッドを、動詞オブジェクトをパラメータにして呼び出します。その動詞が許可される場合、roomCheckメソッドはtrueを返します。許可されない場合はnilになります。この検査は最も大ざっぱなレベルの検査として意図されています。その目的は暗闇のような特別な状況にあるルームを許可し、ほとんどの動詞の活動を不許可にすることです。あなたがこのメソッドをオーバーライドすることはほとんどないでしょう。なぜなら、あらゆる一般的な種類のルーム(付録の「アドベンチャー定義」参照)に適した定義がすでに用意されているからです。roomCheckはアクターへ送られ、アクターは通常単にその場所のroomCheck値を返します。
次に、私たちはactorActionを通じてコマンドに関係するアクターに対し、アクターが何かするように要求されていることを告知します。Meアクター、プレーヤーは普通ここではほとんどなにもしませんが、他のアクターは通常その要求に対してなにかしら応答します。一般的に言って、Me以外のアクターはゲームデザインの問題から随意のコマンドを実行しません。これは彼らが異議を唱える機会であり、(「守衛があなたを睨んだまま路を塞いでいる」のような)メッセージを表示し、そしてexitします。これらのルーチンの中の一つがexitコマンドを実行する場合、コントロールはデーモンとヒューズに移り、すべてがスキップされます。
アクターがそのコマンドに同意し、exitコマンドを発しないとすると、アクターの位置はroomActionメソッドを介してそのイベントを告知されます。通常、ルームはその中で何が起こっているのか気にしませんので、roomActionは普通何もしません。しかし中にはその中でアクターによって行われる行動を制限したり変えたりするものもあります。例えば、罠が仕掛けられた部屋ならあるコマンドが出された(または出されなかった)場合にその罠が働くようにするためにroomActionが使われているかもしれません。actorActionと同様、ルームが単にそのコマンドを不許可にしたい場合、メッセージを表示し、exitコマンドを使用します。
roomCheckとroomActionの違いは、roomCheckの場合、オブジェクト確定が行われる前に呼び出されるということです。roomCheckが呼び出された時点では、コマンドに関連したいかなるオブジェクトも不明の状態です。一方、roomActionはオブジェクトが確定した後で呼ばれます。両方のチェックが必要です。ルームが暗かったり何らかの理由でコマンドがうまくいかないときに、そのコマンドが作用するところのオブジェクトのリストをパーサーに見せてほしいということにはなりません。プレーヤーはそういうオブジェクトの一部についてその存在すら気づいていない可能性があるからです。ですから確定作業の前のチェックが必要です。プレーヤーのコマンド中に使われる正確なオブジェクトに依ってそのルームが別のアクションを起こさなければいけないかもしれないので、確定後のチェックもまた必要になります。
そのルームになにも問題がないときは、次にコマンドの間接・直接目的語に通知します。通常、それらはコマンドの機能を実際に実行します(アクターとルームメソッドがコマンドの承認または拒絶以上のことはほとんどしないのと対照的)。プレーヤーコマンド中で特定されるオブジェクトが二つ、一つあるいは皆無かによって異なるメッセージの連続が送信されます。
もしも、コマンドの中に目的語が何もないのなら、動詞が単純にactionメッセージを受信します。この場合、アクターが唯一の関連情報ですので、それが唯一のパラメータになります。「north」「look」のような動詞は目的語を取らないので、それらの機能はそれらの持つactionルーチンによってまかなわれます。
直接目的語が一つだけでも存在すれば、動詞のactionメソッドは呼び出されません。その代わり、直接目的語そのものがコマンドを実行します。
アクターとルームのようなオブジェクトには命令全体に意義を唱える機会があります。これは、「verify direct object usage for Verb.」(動詞に対応する直接目的語の使用を検証する)を表わすそのverDoVerbメソッドを介して行われます。例えば、動詞が「take,」ならverDoTakeメソッドが呼び出されます。(このメソッドの実際の名称は動詞自身の中で定義されます。つまり、doActionプロパティーが呼び出されるメソッドに名前をつけます。takeVerbオブジェクトの中で、doAction = 'Take')と指定されたら、呼び出されるメソッドはverDoTakeになります) 検証のためのメソッドは特殊です。なぜかというと、単にメッセージを表示することによってそのコマンドに反対できるからです、exitコマンドも必要ありません。目的語検証ルーチンは便宜上こんなふうにデザインされています。それらは実際には動詞の行動を実行せず、なにか表示する唯一の理由は反対するためですから、システムはこの事実を発見するとあたかもexitコマンドのように扱います。
検証が成功したとなると、直接目的語のdoVerbメソッドが呼び出されます。(検証と同様に、メソッドの名前は動詞のdoActionプロパティーに由来しています。takeVerbにおいてdoAction = 'Take'と定義されている場合、「take」コマンドは直接目的語にdoTakeメッセージを送信します) このメソッドは動詞の機能を実行することと、コマンドの成功についてユーザーに報告することの責任を負っています。例えば、doTakeメソッドは直接目的語をユーザーの持ち物へ移動し、「Taken.」と表示するかもしれません。
直接および間接目的語双方が存在する時、間接目的語の検証メソッドが初めに呼ばれます。もしもそれがメッセージをまったく生成しない場合は、直接目的語の検証メソッドが呼び出されますが、それもまたメッセージを全然出さないとなると、間接語のioVerbメソッドが呼び出しを受けます。(verIoVerb、 verDoVerb、ioVerbの名称の派生の仕方が単一目的語コマンドと違うことに注意。複数の目的語を許容する各動詞は目的語間の前置詞ごとに一つのioActionプロパティーを持っています。例えば、「put the book on the table」 )はputVerbオブジェクトがioAction( onPrep )プロパティーを定義している場合に限って働きます。そんなプロパティーが定義されている場合、その値は各メソッド名の中でVerb接尾語として利用されます。例えば、putVerbがioAction(onPrep) = 'PutOn'と定義した場合、送信されるメッセージはverIoPutOn、verDoPutOnおよび ioPutOnとなります。)
こうした処理がすべて終わると、デーモンのすべてが一度実行され、そして燃え尽きたヒューズも全部実行されて、アクティブなヒューズリストから取り除かれます。なお、notify()関数により予定が組まれているオブジェクトへのメッセージは他のヒューズとdaemons同様に扱われます。この理由により、0に設定されたturnsを持つnotify()へのコールにより予定されたオブジェクトへのメッセージは他のデーモンと同時に扱われ、他のnotify()呼び出しは他のヒューズにより扱われます。
abortコマンドは命令を完全に停止させ、ヒューズとデーモンさえ実行されない状況を招きます。これは通常、ゲームのセーブなどの特別なシステム機能が実行された時に使用され、一つのターンとして計算されるものではありません。
プレーヤーの入力の理解におけるさらに難解な部分の一つが、複数のオブジェクトが同じ名前を持っているときにどのオブジェクトが参照されているのか決定するという問題です。都合上、プレーヤーは自分が意図するものを限りなく詳細に略さず綴ることを望みません。例えば、プレーヤーが一冊の赤い本を持っていて、部屋には一冊の青い本があるとすると、プレーヤーは「blue book.」と指定せずに「take the book」と言うことが可能であるべきです。同じように、二つドアがあるエアーロックのどちらか一方が開いているなら、開いているドアを指定しなくても「close the door」という命令で充分です。複数のオブジェクトが同じボキャブラリーワードを使用する時に、文脈に基づいてプレーヤーが言及しているオブジェクトを決定する過程を「disambiguation.」(曖昧さの除去)と呼びます。
プレーヤーが一冊の「book,」に言及するとき、パーサーはそのボキャブラリーワードを共有するすべてのオブジェクトに注意を払います。プレーヤーがもっと具体的に述べると、システムはプレーヤーが使用したすべての言葉が通用するオブジェクトだけを考慮に入れます。複数のオブジェクトがなおも候補に残っている場合、パーサーはプレーヤーに詳細を尋ねる前に二つのレベルの曖昧さ除去を行います。最初に、システムはvalidDoまたはvalidIoテストに合格しないオブジェクトを選考から外します。それでもオブジェクトを一つに絞れないとき、システムは次にひっそりとverDoVerb または verIoVerbテストを懸案中の各オブジェクトに適用します。その際に、システムはルーチンからのメッセージがあったとしてもこれを隠すのでプレーヤーが見ることはありません。しかし、パーサーは出力が結果として生じたかどうかは記録します。出力を生むオブジェクトはテストに失敗し、合格できません。テストに受かるオブジェクトが皆無である場合、すべてが選考に残ります。一つ以上合格した場合は、合格したオブジェクトだけが選考に残ります。
もしもこの時点で、オブジェクトが一つだけ残った場合、パーサーはプレーヤーはそのオブジェクトを意図しているのだと決定します。一つ以上残った場合、システムがプレーヤーにどれのことなのか「Which book do you mean, the red book, or the blue book?」のように質問します。
validDo および validIoの使用の背景にある概念は単純です。そのオブジェクトがプレーヤーにとってアクセス不可能ならば、プレーヤーはコマンド中でそれを意味深長に使用できないので、それは放棄される。これはまた後の検証用コードを簡潔にもします。なぜならコマンド中のアクターが意図されたオブジェクトへアクセスできることを検査する必要がないからです。
verDoVerb および verIoVerbに隠れている考え方はいささか複雑です。これらのメソッドは質問の中のオブジェクトが意味をなさないときにメッセージを出すものと想定されているので―例えば、プレーヤーがすでに持っているものを取ろうとしたり、開いていないドアを閉じようとした時―パーサーはプレーヤーはおそらくこれらのテストに失敗したオブジェクトを意図しているのではなかろうと推測します。ですから、プレーヤーが「take the book,」とタイプした時に、赤い本はプレーヤーがもう持っているのでテストに落ち除外され、部屋の中で取られるのを待っている青い本は合格します。同様に、プレーヤーが「close the door,」とタイプした場合に、閉じているドアは不合格になりますが、開いているドアは合格します。
このスキームの使用により、システムはプレーヤーが言うことに対する十分知的な推測を一般的に可能にしています。オブジェクトがその動詞にとって有効であることを保証するという第一の機能と同じくらいに、この心の中の機能でverDoVerb および verIoVerbルーチンを独自にデザインすることが重要です。
時には、見えるけれど入手はできないオブジェクトを作りたい時があります。例えば、閉じられたガラスの瓶の中にビー玉があるとき、そのビー玉はプレーヤーから見えますが(ガラスの瓶は透明だから中味が見える)、瓶を開けずにビー玉をいじることはできません。
これをサポートするために、パーサーは単に見えないオブジェクトと見えるけれどアクセスできないオブジェクトの区別を行います。その区別は重要性が低く、オブジェクトにアクセスできないときにパーサーが表示するエラー・メッセージの種類に影響を与えるだけです。ビー玉が見えず、(別の部屋にあったりして)アクセスもできない場合、パーサーは「I don't see any marble here.」とメッセージを出すでしょう。しかし、ビー玉が見えるのにアクセスできない場合(閉じられたガラス瓶の中にあるようなとき)には、パーサーはビー玉オブジェクト内のcantReach(actor)メソッドを呼びます。
パーサーはビー玉のisVisible(vantagePoint)メソッドを呼ぶことによってビー玉が見えるかどうか決定します。このメソッドがtrueを返す場合、ビー玉は見えます。反対にnilを返すなら、vantage point(見晴らしのいい場所)からビー玉を見ることができないということになります。引数の vantagePoint は現行のコマンド内のアクターのことです。
特定の動詞はあるオブジェクトが有効か(すなわち、その動詞のvalidDoメソッドがisVisibleによって定義されているか)否か決定するために、isVisibleを利用します。 例えば、inspectVerbは、操作できないあるオブジェクトを、それを見られる場合に限って検査できるようにします。
既定のisVisibleメソッド(adv.tの中でthingオブジェクトのために定義されている)は幾分複雑です。しかしその基本規則は以下のようになっています: オブジェクトのロケーションがバンテージ・オブジェクトの場合、またはそのロケーションの内容が可視であると同時にそのロケーションがバンテージ・オブジェクトから可視である場合、バンテージがロケーションを持ち、バンテージのロケーションの内容も可視であり、オブジェクトがバンテージのロケーションから可視である場合に、trueを返す。それ以外の場合はnilを返す。もっと簡単に言うと、光学の一番の規則:あなたに私が見えるときは、私もあなたが見えるということです(コンピュータプログラムであるため、TADSは容易に理解できるこの概念を複雑な再帰的アルゴリズムへ変換します)。
既定のisVisibleはほとんどの場合十分なものでしょう。既定のcantReachメソッドも同様だと思います。また、特別の効果を達成するためにisVisibleの新しい定義を利用することもできます。例えば、ほかの場所にあるオブジェクトを表示するビュー・スクリーンを実現するために利用できるでしょう。そうすればビュー・スクリーン上でオブジェクトを見られます。ただし、それらを操作することはできません。
特別なプロパティーであるdoDefaultとioDefaultはコマンドに既定の情報を提供するために使われます。doDefaultの目的は現行のコマンドにとって意味がある全オブジェクトのリストを返すことです。例えば、コマンドが「take,」なら、アクターにとってすぐにアクセス可能なすべてのオブジェクトが、もちろんアクターがすでに持っているものを除いて、そのコマンドに適合します。それと同様に「drop」すべき既定の直接目的語はオブジェクトが持っているものになります
プレーヤーが直接目的語のないコマンドをタイプしたとき、パーサーはその動詞用のdoDefaultを見ます。そして結果として生じているリストにその動詞にふさわしい曖昧さ除去機能を適用し、曖昧さ除去機能を通過したオブジェクトで新しいリストを作成します。リストの中に直接目的語がたった一つだけ残っている場合、パーサーはプレーヤーが言及している意味のあるオブジェクトはこれであると結論づけ、パーサーはそれを憶測によってコマンド内に補充します(プレーヤーに対し仮説を表示する)。ioDefaultプロパティーは間接目的語に関して同じ働きをします。
例えば、プレーヤーが「take,」とだけタイプした場合、システムはまず、takeVerbにaction()メソッドがないことを理由に目的語が必要であると判断します。次にtakeVerbを参照し、そこにprepDefaultによって与えられた既定の前置詞があるかどうか確認します。もしあるなら、これは目的語を二つ取る動詞なのだと推定し、既定の前置詞をコマンドに追加します。「take,」の場合、既定の前置詞はないので、システムはこれが目的語1個のコマンドだと理解します。次に、動詞と前置詞に基づいて適切なverDoVerbルーチンが指定されます。この例では、verDoTakeが選ばれるルーチンです。そして、システムはtakeVerb内のdoDefaultメソッドを呼び出し、返された各オブジェクトにverDoTakeを適用します。verDoTakeテストに合格した(すなわち出力がなかった)各オブジェクトは選考に残ります。オブジェクトが一つだけ残ったら、そのオブジェクトがそのコマンドに合った既定の目的語になります。リストに残ったオブジェクトが皆無であったり一つ以上あったりする場合、システムはユーザーにどれを取りたいのか尋ねます。
doDefaultのもう一つの使い方はプレーヤーのコマンド中にある「all」が意味するオブジェクトのリストを決定することの中にあります。「all」はdoDefaultによって生成されたリスト中にあるすべてのものを参照していると推定されます。
ほとんどの動詞について、deepverbから継承されたdoDefaultおよびioDefaultメソッドは十分なものでしょう(deepverbクラスに関する情報は付録の「Adventure Definitons」を参照)。takeVerbとdropVerbはとても一般的なケースなので、もう少し賢くなるようにdoDefaultメソッドを修整しています。そのおかげで、「take all」はすでに持っているものを無視し、「drop all」は持っていないものを無視するようになっています。
時折、ある複数の動詞を一定のオブジェクトに対して同意にしたい場合があります。例えば、コントロールパネルの上にタッチパッドがあるとした場合に、プレーヤーはただそれに触るのではなく押そうとするかもしれません。古いシステムでは、オブジェクトのverDoTouch、doTouch両メソッドをそれぞれ呼ぶためにverDoPushおよびdoPushメソッドに長々と指示しなければなりませんでした:
verDoPush( actor ) = { self.verDoTouch( actor ); } doPush( actor ) = { self.doTouch( actor ); }
追加された便利な特長はこれを自動的にやってくれます。doSynonym と ioSynonymという疑似プロパティーによって特定のプロパティー・ルート・ネームと同意であるプロパティー・ルート・ネームのリストを指定できるようになります。上の例に関して言えば、今は次のように書くことができます:
doSynonym( 'Touch' ) = 'Push'
これは、このオブジェクトのためにverDoPushまたはdoPushメソッドが呼ばれたときはいつでも、そのオブジェクトのverDoTouchあるいはdoTouchメソッドが(それぞれ)代わりに呼び出されるという意味です。(等号の右辺にある)シノニマス・ルート・ネームは一つ以上指定できます。例えば、verDoTapをverDoTouchと同意に、そしてdoTapをdoTouchと同意にしたいときは、上の定義を次のように変更します:
doSynonym( 'Touch' ) = 'Push' 'Tap'
ioSynonymも同様の働きをします。
プレーヤーコマンドパーサーに関するセクションではパーサーが使用するいくつかの特別な備え付けプロパティーの名前を述べました。このセクションはTADSランタイムシステムが定義している特別なプロパティー名をすべて列挙し、それぞれどのように使用するのか説明します。
action(actor): ある動詞が目的語を伴わず使用されたときに呼ばれるその動詞のメソッド。このメソッドはまずその動詞がその時点で許可されていることを確かめつつ、動詞のアクションを実行すべきである。saveのようなシステム動詞の場合、そのコマンドは通常1ターンとして加算されないので、それが無事に完了する場合であっても、ターンごとにデーモンとヒューズが実行されることのないようにabortキーワードを使用すべきである。jumpのような非システム動詞は無事完了する場合にabortを使ってはならない。
actorAction(verb, direct object, preposition, indirect object): パーサーによってコマンド中のアクターへ送信される。このメソッドはそのアクターがコマンドの受容に関係しているかどうか決定する。関係有りなら、そのコマンドは通常通り動詞またはオブジェクトの手にゆだねられるからなにもしない。関係なしなら、苦情を表示して、コマンドのそれ以降の実行を避けるべくexitを使用する。
adjective: オブジェクトを参照しうる形容詞から成る語彙のリスト
article: 冠詞(「the」や「a」)から成る語彙のリスト
cantReach(actor): actorがそのオブジェクト(self)に到達できないときに適切なメッセージを表示する。パーサーはあるオブジェクトが可視であるがアクセス不可能であるとき(例えばビー玉が閉じているガラス瓶の中にある場合、ビー玉は見えるが操作できない)にメッセージを表示するためにこのメソッドを呼び出す。adv.tによってthingオブジェクトとして定義されている既定のバージョンはほとんどの状況に対応する。
contents: あるオブジェクトが包含するオブジェクトのリスト。これは初期化されるべきではない。locationプロパティーに関する議論を参照。しかし、カレントのまま維持されるべきである。つまり、あるオブジェクトのlocationプロパティーが変わる度に、その古い入れ物のcontentsリストは除去されたオブジェクトを含むべきであり、新しい入れ物のcontentsリストは追加されたオブジェクトを含むべきである。
doAction: 動詞のプロパティー。動詞が一つの直接目的語だけを伴って使用されるときに、その直接目的語に送信されるメソッド名を形成するために、verDoとdoに適用される接尾語を定義する。これはシングル引用符で挟まれた文字列でなければならない。定義されない場合、その動詞は目的語が一つのみで使用することが許されない。例えば、動詞「take」がdoAction = 'Take'と定義した場合、verDoTakeおよびdoTakeメッセージが「take object」コマンドの直接目的語に送信される。
doDefault(actor, prep, indirectObject): 動詞の標準の直接目的語。「all」が使用された場合、このリスト全体が「all.」の代わりになる。直接目的語がまったく使用されない場合に、このリストが曖昧さのない状態ならば(例えば候補が一つしかない)、それがコマンドの直接目的語として使用される。
ioAction(preposition): 動詞のプロパティー。動詞が二つの目的語と前置詞と共に使用されたときに間接目的語に送信されるメソッド名を形成するためにverIo, verDo, ioに適用される接尾語を定義する。その値はシングル引用符で挟まれた文字列でなければならない。例えば、動詞「ask」がioAction(aboutPrep) = 'AskAbout'と定義するのなら、verIoAskAboutとioAskAboutがコマンド「ask direct-object about indirect-object」の間接目的語へ送信される。なお、verDoAskAboutメッセージは直接目的語に送信される。引数の前置詞はどこか別の所で定義された前置詞オブジェクトの名前である。オブジェクトはそのprepositionアトリビュートによって、プレーヤーが使用するボキャブラリーワードを定義する。
isHim: そのオブジェクト(self)が男性アクターであり、プレーヤーコマンドにおいて「him」で言及されるべき場合にtrueを返す。isHimがnilの時、そのオブジェクトは「him」として言及できない。「him」「her」のどちらかが使用されうる場合に、isHimとisHer双方をtrueに設定するのは合法である。双方共にnilの時は、「it」だけがそのオブジェクトに使用できる(これが標準である)。
isHer:
isHer: そのオブジェクト(self)が女性アクターであり、プレーヤーコマンドにおいて「her」で言及されるべきである場合にtrueを返す。isHerがnilである場合に、そのオブジェクトは「her」で言及できない。「him」「her」のどちらかが使用されうる場合に、isHimとisHer双方をtrueに設定するのは合法である。双方共にnilの時は、「it」だけがそのオブジェクトに使用できる(これが標準である)。
isVisible(vantage): そのオブジェクト(self)がvantageオブジェクトから見える場合にtrue、見えない場合にnilを返す。adv.tにおいてthingオブジェクトを用いて定義された実装は透明な入れ物を含む様々な状況に対応する。特別な効果を実現するために別の実装も利用可能である。
location: オブジェクトの入れ物。nilでも他のオブジェクトでもよい。ゲームの初めにおいてすべてのオブジェクトがそのロケーションに移動させられる。つまり、単なるオブジェクト(メソッドでもnilでもない)として定義された、ロケーションを持つすべてのオブジェクトについて、object.location.contentsはそこに付加されたオブジェクトを保有しているのである。これはlocationプロパティーとcontentsプロパティー間の一貫性を保証する。なお、classオブジェクトと、locationプロパティーのためのメソッドを持つオブジェクトはこの進行手順において無視される。警告:実行時にオブジェクトのlocationプロパティーに直接アサインしないこと。その代わりに必ずmoveInto(newlocation)を使用すべし。例えば、ナイフをプレーヤーが今いる場所に移動したいときに、knife.location := Me.locationと記述してはいけない。代わりに次のようにすべきである: knife.moveInto(Me.location) moveIntoメソッドはオブジェクトの新旧の入れ物のcontentsプロパティーがオブジェクトの新しいロケーションを反映するよう更新されることを保証してくれる。
locationOK: オブジェクトのlocationが意図的にオブジェクト以外のものであるように指定するフラグ。通常、オブジェクトのlocationがオブジェクトでない場合には、コンパイラが警告としてこれにフラグを立てる。なぜなら、コンパイラはlocationのcontentsリストに現在のオブジェクトを含めさせることができないからである。しかし、時には特別な効果を実現するためにlocationにメソッドを用意する必要がある。例えば、adv.tで定義されているtheFloorは(詳細は付録の「Adventure Definitions」を見よ)、location用のメソッドを使用することで、同じオブジェクトがすべてのルームに存在するように見せている。theFloor内でlocationをtrueに設定することによって、ゲームはコンパイラに非標準のlocationが意図されていることを知らせ、それにより警告メッセージは禁止される。このプロパティーにはその他の効果はない。
noun: オブジェクトに言及しうる名詞から成るボキャブラリーリスト
plural: オブジェクトがそう呼ばれうる複数形を提供する語彙リスト
prepDefault: 動詞の標準の前置詞を指定する。doActionプロパティーがまったく定義されていないのにプレーヤーがコマンド内で直接目的語を一つだけ指定した場合に使用される。パーサーは、コマンドに与えられた標準の前置詞を付加しようと試みる。そして、(まず動詞のioDefaultリストから、次にプレーヤーに質問することで)間接目的語を得ようとする。
roomAction(actor, verb, direct object, preposition, indirect object): 全コマンドに応じてアクターのlocationプロパティーによって与えられるオブジェクトへ送信される。プレーヤーコマンドに応じてパーサーから送信されるメッセージの連続に関するセクションを参照せよ。
roomCheck(verb): センテンス処理が始まったばかりの時点でMeオブジェクト(プレーヤー・アクター)へ送信される。その動詞がプレーヤーのルーム内で許可されるものならば、このプロパティーはtrueを返し、それ以外はnilを返す。後者の場合には、その動詞が許されない理由を説明するメッセージの表示も行う。このプロパティーは主に真っ暗なあるいはその他の制限があるルームにおいて不要な処理が起こるのを回避するために使用される。そして、ルームの最も基本的な特徴の検査を除いてなにもしない。roomCheckはコマンド内のオブジェクトを確定する前に呼び出されること、したがって、そのコマンドが適切か否かをオブジェクトを抜きにして決定しなければいけなことに注意。一方、roomActionはオブジェクトが判明した前に呼び出されるので、さらに明確な判断基準をコマンドに対して適用できるのである。
sdesc: 自ら表示する簡潔な説明。ダブル引用符で挟まれた文字列、または短い説明を表示するメソッド。
statusLine: これはプレーヤーを包含しうるルームまたはその他のオブジェクト(椅子や乗り物など)のsdescと関連がある。これは居場所の短い説明を表示するもので、その説明は居場所内のオブジェクトのリストを含まない、一行に収まるものであるべきである。adv.tで定義されたroomオブジェクトの子孫に当たるほとんどのロケーションはlookAroundメッセージ(roomオブジェクトのためにadv.tの中で定義されているメソッド)が送信されると、まずstatusLineを表示する。
いくつかのプラットホーム上で、TADSランタイムシステムはステータスラインを、プレーヤーに現在の居場所を示すスクリーン上端を横切る形で表示する。この行はすべてのターンの前に更新される。これらのプラットホームにおいて、ランタイムシステムはステータスライン情報を表示するために、プレーヤーの現在のロケーションのstatusLineメソッドを使用する。
statusLineはステータス行の「左半分」だけを表示するが、それは典型的にプレーヤーの現在位置を提供する。右半分はsetscore関数によって表示される。
thedesc: 定冠詞を伴った短い説明。通常単に「the」とsdescを表示するメソッドとして定義される。sdescと同様、thedescはセルフ・プリンティングでなければならない。
validDo(actor, object): 直接目的語がコマンドにとって有効であることを検査するために動詞へ送信される。そのオブジェクトが動詞とアクターにとって直接目的語として有効ならばtrueを返し、有効でないならnilを返す。一般的にいうと、このルーチンはそのオブジェクトがアクターにとってアクセス可能であるか検査する以上のことは行わない。validDoがそのオブジェクトが動詞にとって意味を成すかどうか調べる必要はない。したがって、アクターがすでに持っているものを取ろうとした場合はtrueを返し、別の部屋にあるものを取ろうとしたときにはnilを返す。オブジェクトが意味を成すかどうかを決めるのはオブジェクトのverDoVerbメソッドの仕事である。
validIo(actor, object, seqno): 間接目的語がコマンドに対して有効であるか検査するために動詞に送信される。validDoと同様、オブジェクトが動詞とアクターにとって間接目的語として有効ならばtrueを返し、有効でないならnilを返す。validDoについてと同様、このメソッドはそのオブジェクトがその動詞にとって意味があるかどうか検査することではなく、アクセス可能性の検証のみに責任を負う。したがって、アクターがなにかをアクセス可能なオブジェクトの中に入れようとする場合、このメソッドは問題のオブジェクトが入れ物であるか否かに関係なくtrueを返す。
seqnoはテストを受けているオブジェクトの「sequence number」である。プレーヤーがオブジェクトへの言及を曖昧に行った(つまり、一つ以上のオブジェクトに相当してしまう名詞と形容詞の組み合わせを使用した)ときには、可能性のあるオブジェクトが一度に一つずつvalidIoのテストを受けるのである。組みになった各オブジェクトは1から始まる別々の数字を付けられる。これらのシーケンスナンバーは随意であり、通常使用されることもない。しかし、特定の動詞は組になった曖昧なオブジェクトの内のどれが選択されているのかを気にしない。このような動詞は最初の目的語だけを受け入れ残りを拒否するためにseqnoを利用する。例えば、adv.t内のaskVerbはこれを行う(adv.tに関する情報は付録「Adventure Definitions」を参照)。
value: strObjおよびnumObjオブジェクトがプレーヤーコマンド内で使用されたときにその値を提供する特別なプロパティー。例えば、「turn the dial to 651」はnumObjを間接目的語にし、そのvalueプロパティーを651に設定する。
verb: 動詞オブジェクトのためのボキャブラリーワードのリスト。すべてのボキャブラリー・プロパティーと同様、その値はシングル引用符で挟まれた文字列で、オプションとして角括弧で閉じられねばならない。(オブジェクトがclassオブジェクトである場合、角括弧は必須である。) verb = 'pick up'のような特別な2語の記述は前置詞付の動詞に使用される。
TADSランタイムシステムはいくつかのオブジェクトと関数を定義するゲームプログラムを必要とします。これによりこれらのオブジェクトの詳細に関してユーザの管理が認められますが、それらの名称はシステムによって固定されています。必須のオブジェクトと関数を下に列挙します。
againVerb: 最後のコマンドを繰り返すために使用される「again」コマンド用のボキャブラリーを指定する特別な動詞。通常、このオブジェクトのverbプロパティーはこのコマンドのために「g」と「again」を受け入れるように設定される。
init(): ゲーム開始時に一度実行される関数。この関数はMeアクター(プレーヤー)を開始時の部屋に移動し、必要ななんらかのデーモンとヒューズを開始する、そして、タイトル、コピーライトメッセージ、ゲーム導入部の文章を表示する。
関連のあるpreinit() 関数も参照すべきである。これはゲーム開始前に実行されるべき時間のかかる計算のために使用される。
Me: プレーヤーのアクター。プレーヤーコマンド内で特定されるアクターが存在しないときには、Meオブジェクトが推定される。Meオブジェクトは他のアクターがパーサーから受信したメッセージと同じものをそっくりそのまま受信する。
adv.tはbasicMeクラスをMeの妥当な実装として定義する。自分のゲームにおいてMeのさらなるカスタマイゼーションを望まないときには、次のようにこれをゲームに込めることで無修整のbasicMeを利用できる:「Me: basicMe;」。
numObj: コマンドに数字が含まれるときに利用される特別なオブジェクト。そのvalueプロパティーはプレーヤーがコマンド内で使用した数字に設定される。例えば、プレーヤーが「Turn the dial to 617,」とタイプしたなら、間接目的語はnumObjになり、numObj.valueは617になる。
adv.tはbasicNumObjクラスをnumObjの妥当な実装として定義している。numObjのさらなる修整を望まないのなら、ゲーム内で次のように書けば無修整のbasicNumObjを利用できる:「numObj: basicNumObj;」。
pardon(): プレーヤーが空行を入力したときに呼び出される特別な関数。通常「I beg your pardon?」と表示するだけである。この関数は簡単に済ますのもこのメッセージを修整できるようにするのもゲーム作家次第である
parseError(errnum, errstr): この関数はパーサーエラーメッセージが表示されるべきときには必ず呼び出される。この関数は選択自由である。ゲーム内で定義されていない場合、システムは既定のメッセージを表示する。この関数によっていかなるパーサーエラーメッセージも変更および独自のメッセージによる置換が可能になる。
引数のerrnumは必要なメッセージを指定するための数字である。 errstrはパーサーが通常表示する既定のメッセージのテキストである。数字が用意されている理由は、それがエラーメッセージについてバージョンに依存しない鍵になるということである。つまり、メッセージの実際の文章に小さな変更が加えられても、その基本的な意味はバージョンからバージョンへと残存するから、メッセージの意味を示すためにerrnumを当てにできるのである。errstrが用意されている理由はメッセージの文章を完全に置き換えるのではなく小さな変更だけ行えるようにするためである。例えば、パーサーのメッセージを角括弧で括りたい場合、単にerrstrに角括弧を加えるだけで望み通りの文章を返すようになる。
この関数はnilまたは文字列値を返す。nilを返す場合、既定のメッセージが表示される。文字列を返す場合、その文字列が既定のメッセージに代わって表示される。
エラーメッセージと既定のメッセージは下に示されている。1から99までのエラーは普通のパーサーエラーメッセージである。100から109まではディスアンビギュエーション(曖昧さの除去)のための質問を組み立てるのに使われる特別なメッセージである。いくつかのエラーナンバーはたった一つのエラー表示を成り立たせるので、もしエラーメッセージにかっこを加えるような変形を行っているのなら、これらを除外したほうがよい。同じことが100以上の他のエラーについても言える。200はcantReachメッセージが生成されようとするときに利用される特別なメッセージである。ここにparseError関数の実例がある。これは単に、特別なメッセージ(番号が100以上のもの)は変えることなく、すべての普通のパーサーエラーメッセージを角括弧で括るものである。:
parseError: function( errno, str ) { if ( errno >= 100 ) return ( nil ); /* 特別なメッセージはそのままにする */ return( '[' + str + ']' ); /* メッセージを[]で括る */ }
C形式の整形用シーケンス%cと%sがこれらのメッセージの中で利用される。望むならメッセージ中の整形用シーケンスを省いてもよい。ただし、既定のバージョン中にシーケンスを持たないメッセージにこれを含めてはならない。そして、自分でこれらを用意するときには常にそれらが正しい数字、タイプ、順序になっていることを確認しなければいけない。整形シーケンスに対して何かをする必要はない―存在しているシーケンスをすべてそのままにしておけば、システムはメッセージを表示する前に正しい値を当てはめる。
普通のエラーメッセージ
1 : "I don't understand the punctuation "%c"."
2 : "I don't know the word "%s"."
3 : "The word "%s" refers to too many objects."
4 : "I think you left something out after "all of"."
5 : "There's something missing after "both of"."
6 : "I expected a noun after "of"."
7 : "An article must be followed by a noun."
8 : "You used "of" too many times."
9 : "I don't see any %s here."
10 : "You're referring to too many objects with "%s"."
11 : "You're referring to too many objects."
12 : "You can only speak to one person at a time."
13 : "I don't know what you're referring to with '%s'."
14 : "I don't know what you're referring to."
15 : "I don't see what you're referring to."
16 : "I don't see that here."
17 : "There's no verb in that sentence!"
18 : "I don't understand that sentence."
19 : "There are words after your command I couldn't use."
20 : "I don't know how to use the word "%s" like that."
21 : "There appear to be extra words after your command."
22 : "There seem to be extra words in your command."
23 : "internal error: verb has no action, doAction, or ioAction"
24 : "I don't recognize that sentence."
25 : "You can't use multiple indirect objects."
26 : "There's no command to repeat."
特別な曖昧さ除去エラーメッセージ
100 : "Let's try it again: "
101 : "Which %s do you mean, "
102 : ", "
103 : "or "
104 : "?"
Special: オブジェクトが動詞にとって不適当であることを訴えるために使用される
110 : "I don't know how to "
111 : " "
112 : " anything "
113 : "to"
114 : " "
115 : "."
Special: マルチプル・オブジェクトが使われたときに各オブジェクトの後に使用される
120 : ": "
Special: デフォルトに使用されているオブジェクトに注意するために使用される
130 : "("
131 : ")"
132 : " "
Special: あるオブジェクトがデフォルトにできないときにオブジェクトを求めるために使用される
140 : "What do you want to "
141 : " it "
142 : " to "
143 : "?"
Special: あるobjが到達不可能であるときに使用される: obj.sdesc 「: 」 obj.cantReach
200 : ": "
preinit(): この関数はinitと関係がある。コンパイラはプログラムのコンパイルに成功するとすぐに、preinit()を呼ぶ。そして、コンパイルされたゲームを収めたバイナリファイルが書かれる。
preinit()は実行のためにだけ用意されている。この関数がプログラム中に定義されていないと、コンパイラは警告を表示するが、それ以外の影響は受けない。この関数はバイナリファイルが書き上げられる前に呼び出されるため、プレーヤーがゲームを実行する前になんらかのゲーム前コンピレーションを実行することを許可する。一方、init()関数はプレーヤーがバイナリファイルをロードした後に実行されるので、プレーヤーはゲームが開始可能になる前にinit()関数の動作が完了するのを待たなければならない。
preinit()はいかなるメッセージも表示してはならないし、ヒューズ、デーモン、警告の設定も行ってはならない。なぜなら、ゲームはまだ始まっておらず、その手の行為はバイナリファイルが書かれた時点で失われてしまうからである。その代わり、preinit()関数はゲームの開始時に計算する必要があるオブジェクトのリストを用意するために使用されるよう意図されている(例えば、光をもたらすオブジェクトはfirstobj()とnextobj()を使った全オブジェクトの検索および、リストへのislampプロパティーがtrueに設定されたすべてのオブジェクトの保存によりコンパイルされる)。
preparse(str): この関数はプレーヤーが一行の文章を入力した後すぐに、なおかつプレーヤーコマンドパーサーがコマンドラインを見始める前に呼び出される。その引数はプレーヤーのコマンドラインの文章を手付かずのまま丸ごと含んでいる文字列である。その文章は元の姿のままで、元の空白、句読点、大文字を備えている。
この関数がnilを返す場合、コマンドは中止され、パーサーはそのコマンドを解釈しない。関数が文字列を返すときには、その新しい文字列がプレーヤーコマンドの元のテキストに置き換わり、パーサーはプレーヤーがそれをタイプしたものとして処理する。trueを返す場合、元のプレーヤーコマンドが通常通り解析される。
preparseの詳しい情報は本章の初めを見よ。この関数は選択自由である。定義されていない場合、コンパイラは警告を発するがゲームは問題なくコンパイルされる。
strObj: プレーヤーがコマンド内で引用符に挟まれた文字列を使用したときに、この特別なオブジェクトが利用され、そのvalueプロパティーはプレーヤーがコマンド内で使用した文字列に設定される。例えば、プレーヤーがtype "launch" on the consoleと入力した場合、直接目的語はstrObjであり、strObj.valueは文字列'launch'に設定される。
adv.tはbasicStrObjクラスをstrObjの妥当な実装として定義する。strObjのさらなる修整を望まないときには、ゲーム内で次のようにすれば無修整のbasicStrObjを利用できる:「strObj: basicStrObj;」
takeVerb: オブジェクトを取るために使用される動詞。これは他の動詞と変わりがなく、動詞の働きもゲームプログラムによって通常通り実装される必要があるが、パーサーはこの動詞を認知しなければいけないのでその名前はtakeVerbであることを求められる。一般的に、動詞およびその他のオブジェクトはこの一般的命名法を踏襲することが推奨されるので、この要求はほとんどのゲームに自然になじむだろう(パーサーがこの動詞を認知する必要がある理由は、アクターが存在し、よって話しかけることが可能かどうか決定するためにそれがそのvalidDoメソッドを使用するということである)。
一行に入力する文字数の詳細についてのわずらわしさを除くために、TADSランタイムシステムは自動的にフォーマッタを使い出力をフィルタ処理します。フォーマッタの機能は通常プログラマから見て透明ですが、時々特別な効果が欲しくなります。特殊制御シーケンスはプログラマに出力整形の完全な管理を提供するフォーマッタによって認知されます。
TADSコンパイラがソースファイルからテキストを読み込むとき、余計な空白は無視し、改行を空白として扱います。したがって、テキストがシステムによる出力である場合、入力されたところから編集し直されます。これによりきれいに整形された元になるテキストを維持する必要がなくなります。
普通、フォーマッタは単に各行にできるだけ多くの語を詰め込んでから新しい行に移ります。出力を自動的に「ワードラップ」するようになっています。つまり、改行は常に語と語の間で起こります。したがって、改行が一切ない文章を書いても、妥当な見栄えになります。フォーマッタはその行が本来もっと多くの文で満たされるような場合でも文章中に現われるハイフンの所で改行を行います(フォーマッタは文章内でハイフンによって結合されていない語を結合しようとはしません)。文章を書く際に、フォーマッタは慣例通り、終止符、感嘆符、疑問符の後に自動的に二つのスペースを挿入します。他の箇所ではスペースが一つだけ入ります。
通常ダブルスペースの挿入を招く句読点の後に引用符で挟まれたスペース(言語レファレンスの章のテキスト内における特殊制御シーケンスの説明を参照)を使うことで、ダブルスペースの自動挿入を却下できます。また、引用符で挟まれたスペースの使用によって複数のスペースを連続して挿入することもできます。出力フォーマッタを制御するために使用できるその他の特殊制御シーケンスについては、言語レファレンス章のダブル引用符で挟まれる文字列に関するセクションを参照してください。
TADSはほとんどの場所でプレーヤーオブジェクトを特別扱いしません。このことはプレーヤーからの注文を実行する他のアクターの実装をかなり容易にします。つまり、プレーヤーはコマンドを出す対象となるアクターを指定することができ、もしそのアクターが命令に従えば、そのアクターはコマンドに含まれるオブジェクトに対するパーサーのすべてのメソッドコールにおいて特定されます。
非プレーヤーアクターの行動の結果生じるテキストの表示を意味あるものにするため、すべてのメッセージは「you.」ではなく、そのコマンドを実際に実行したアクターの立場に立って紡がれねばなりません。こうしたメッセージの中で常にactor.thedescを使わなくてはならないとしたら極めて不便なので、TADSは「フォーマット・ストリングス」という格段に便利なメカニズムを用意しています。
フォーマット・ストリングは、出力フォーマッタに現在のコマンドのアクターのプロパティーの代わりをするよう合図を出す文字の特別な処理過程です。adv.tのメッセージは「you」およびその関連語を使用する代わりに、フォーマット・ストリングを使用します。ゲームプログラムは適切な場所で(例えば、プレーヤー以外のアクターが何かをする、もしくは、メッセージの中で叙述される場所)、同じことを行うべきです。出力フォーマッタはフォーマット・ストリングを見ると、それらを自動的に現在のアクターにふさわしい言葉で置き換えます。
adv.tはたぶん必要になるフォーマット・ストリングのほとんどを網羅していますが、自前のフォーマット・ストリングを定義することも可能です。基本的に、フォーマット・ストリングは特殊な出力シーケンスをプロパティー名と結び付けます。フォーマット・ストリングがパーセント記号(%)で挟まれると、表示されるメッセージの中に現われます。出力フォーマッタは、フォーマットストリングと結び付いたプロパティーを呼ぶことによって、(パーセント記号を含む)フォーマット・ストリングを除去し、置き換えます。例えば、adv.tは次のようにフォーマットストリングを定義しています:
formatstring 'you' fmtYou;
システムは次のシーケンスを見るたびに連絡を受けます:
%you%
表示されるテキストにおいて、シーケンスは丸ごと除かれ、現在のアクター内のfmtYouプロパティーを評価した結果得られたテキストに置き換えられます。
新しいadv.tにおいて、メッセージはこうしたフォーマットストリングを用いて表現されます。例えば、移動に関する新しいエラーメッセージは「%You% can't go that way.」です。プレーヤーが単に「east.」とだけタイプした場合、%You%はYouに置き換えられます。YouはMeオブジェクトについてfmtYouを評価した結果なので、メッセージは前と同じく「You can't go that way,」になります。しかし、プレーヤーが「joe, east,」とタイプした場合、今度はメッセージが「Joe can't go that way.」になります。
フォーマットストリングを置き換えるテキストにおける大文字の使用はフォーマットストリングそのもののそれを引き継ぎます。ですから、%you%はyouで置き換えられ、%You%はYouで置き換えられます。
adv.tには、アクターに依存するメッセージの中で使われそうなほとんどの語についてフォーマットストリングが定義されています。これらの語のいくつかは文章を文法通りにするためにアクターに依存します。例えば、「You aren't wearing your spacesuit」は「Joe isn't wearing his spacesuit」に変わるかもしれません。そうなると、「you」「aren't」「your」はすべて変える必要があります。adv.tで定義されたフォーマットストリングを使うことで、メッセージ・センテンスを次のように書き直せます:
"%You% %are%n't wearing %your% spacesuit."
自前のメッセージのなかでフォーマットストリングを使わなくてもゲームは動作し続けます。この特長を利用する必要はありません(adv.tはフォーマットストリングをすべてのメッセージの中で使用しているため、ある程度まで自ずと利用することになりますが)。しかし、テキストの直書きの代わりにフォーマットストリングを利用することはとても簡単ですし、ゲームをかなりマルチプル・アクターに適応したものにしてくれます。
パーサーはいくつかの語を特別なものとして扱います。TADSの古いバージョンでは、これらの語がシステムに組み込まれていたので、ゲームプログラムがそれらを変更する方法はありませんでした。新しい構造が加えられ、こうした語を自分の選択した語群で置き換えることが可能になりました(これは例えば、あなたが英語以外の言語でアドベンチャーを書く場合に助かります)。
備え付けの語を上書きするための命令がspecialWordsです。この命令は(formatStringやcompoundWordのような)他の特別な命令の一つが出現しうるところならどこにでも書けます。下に掲げたspecialWordsキーワードはすべてのスペシャルワードを決められた順番通り並べたリストです。下に示された順番でワードを指定する必要があります。また、すべての位置につき最低でも一つの語を用意しなければいけません。ただし、各位置に一つ以上の語を用意しても構いません。各位置は一つのコンマで分けられます。同意語(一つの位置にある複数の語)は一つの等号で分けられます。そして、リスト全体は一つのセミコロンで終わります。specialWordsがまったく使用されない場合に備え付けのリストに合う規定のリストは次のようになっています:
specialWords 'of', /* 「piece of paper」のような句に使われる */ 'and', /* 名詞列挙またはコマンド分割のための接続詞 */ 'then', /* コマンドを分割する接続詞 */ 'all' = 'everything', /* アクセス可能な全オブジェクトに言及する */ 'both', /* 複数形と共に、あるいは質問に答えるために使用される */ 'but' = 'except', /すべてのものから一部を除外するのに使う */ 'one', /* 質問に"the red one"のように答えるのに使う */ 'ones', /* 上と同様に複数形に使用:"the blue ones" */ 'it', /* 最後に使用された単数の直接目的語を差し示す */ 'them', /* 最後の直接目的語のリストに言及する */ 'him', /* 最後に言及された男性アクターに言及する */ 'her' /* 最後に言及された女性アクターに言及する */ ;
ある考えが人をはっとさせる時、文法の授業は邪魔者に見える。
Thomas Wentworth Higginson (1890)
笑顔はあらゆる曖昧さに最適な乗り物である。
HERMAN MELVILLE, Pierre, book IV (1852)
第3章 | 目次 | 第5章 |