プラン言語チュートリアル

プラン言語の基本的な書きかたについて解説します。前提知識は仮定していないので、説明なしに術語を使ったりしていたらそれはこちらのミスです。その他、改善案や質問などありましたら掲示板までお願いします。

なお、プラン言語を学ぶのが面倒な場合のために、選択肢を選ぶことで定型的なプランを半自動で生成する仕組みを用意しています。詳しくは説明をご覧ください。

解釈手順

TCGでは、プランを記述する際、「〜の場合〜する」のように、条件と指示を日本語で指定します。それに対して、このシミュレータではひとつのプランをひとつのとして記述します。

プラン言語の式は「4 + 8」のような簡単な数式に例えられます。このような数式を計算すると結果として数が得られますが、プランを表す式を計算した場合、結果として得られるのはコマンドです。コマンドとは、キャラクターに特定の動作を指示するもので、例えば「スキルパターンをBにせよ」とか「斬撃を使うな」といったものが可能です。

試合中、プランによって動作が変わり得る機会(スキルパターンの変更判定、スキル維持判定、スキルを使う直前など)の度に、プランの式が計算され、その結果のコマンドに従ってキャラクターの動作が決定されます。このとき、問題になっている動作に関係のないコマンドは無視されます。例えばスキルパターン変更判定中に「霧の防壁を維持せよ」というコマンドが生成された場合がこれに当たります。

一つの式の計算結果が、試合の状況によって異なるコマンドになることがあります。逆に、状況に依存するように式を作ることで、条件分岐のあるプランを表現することができます。例えば、「先攻なら安らぎを使わない」というプランを表現するには、先攻のときに計算すると「安らぎを使うな」というコマンドになり、後攻時に計算すれば「なにもしない」というコマンドになるような式を書けばよいのです。

対話環境

プラン言語を学ぶ際、 実際に式を入力し、それがどのように計算されるかを観察できると便利です。このために、対話環境(Windows専用; 他の環境の人がいたら教えてください)を用意しました。以下の説明は、この対話環境に式を入力しながら読むことをお勧めします。

tcgsim-ie.exeを起動すると、黒い窓が現われ、「>」という記号の後にカーソルが表示されるはずです。ここに式を入力してEnterを押せば、その式が計算されて結果が表示されます。(試しに「1 + 2」と入力すれば「3」と返ってくるはずです)。その後、ふたたび「>」が表示されるので、別の式を入力し、以下同様に繰り返すことができます。飽きたら窓ごと閉じてください。

基本的な式

プラン言語の文法の紹介のために、基本的な式の書き方を説明します。

四則演算

簡単な式の例として、プラン言語は整数の足し算を行うことができます。

> 33 + 34
67

表記について。「>」で始まる行が式で、その次の行がそれを計算した結果です。なお、入力では全角半角および大文字小文字が区別されることに注意してください。この式では空白以外すべて半角で入力しないと正しく解釈されません。

同様に、引き算(-)、掛け算(*)、割り算(/)も使えます。割り算では余りは切り捨てられます。余りを得たい場合には専用の剰余算(%)が使えます。

> 7 - 4
3
> 7 * 4
28
> 7 / 4
1
> 7 % 4
3

これらは自由に組み合せることができ、式を括弧で括ることもできます。

> (1 - 7*(1+1)) % 5
2

AのB乗は「pow A, B;」と書きます。

> pow 2, 7;
128

powは関数です。プラン言語の関数は、数学でいう関数と似ていて、一つ以上の入力値から一つの出力値を決める規則のことです。この例では、関数powに入力「2」と「7」を与え、出力として「128」を得ています。関数の入力を引数(ひきすう)、出力を結果、関数を使って結果を得ることを「関数を適用する」と表現します。

関数の適用を表記するには、「pow 2,7;」のように、まず関数を書き、次に引数をコンマで区切って並べ、最後にセミコロンを置きます。セミコロンを省略することもできて、その場合は可能な限り右まで引数が続いているとみなされます。

> pow
#<function pow>
> pow 2, 3 + 4;
127
> pow 2, 3 + 4
127
> pow 2, 3; + 4
12
> (pow 2, 3) + 4
12
> pow 2, pow 2, pow 2, 2
65526

大小比較

等号や不等号を使って整数の等値比較および大小比較を行うことができます。プランでは、「自分のHPが10未満なら〜」といった条件を記述するのに使えます。数学と異なり、等号や不等号を使った式も(値を持つ)通常の式です。式の結果は、その関係が成り立つか否かによってtrueかfalseとなります。この二つを総称して真偽値と呼びます。

> 1 < 2
true
> 4 - 3 > 2
false

等号は「==」、≠は「/=」、≦は「<=」、≧は「>=」とそれぞれ表記します。

> 2 == 5 / 2
true
> 2 /= 5 / 2
false

大小比較は整数をはじめとする限られた種類の値にしか使えませんが、「==」と「/=」を使っての等値比較はほとんどあらゆる値に対して適用できます。例えば真偽値を比較することができます。

> true == false
false
> (1 <= 2) == (1 < 2)
true

論理演算

論理でいう「かつ」と「または」はそれぞれ「&&」と「||」を使って表現します。AとBが真偽値なら、「A && B」はAもBもtrueであるときのみtrueでそれ以外の場合はfalse、「A || B」はAもBもfalseであるときのみfalseでそれ以外はtrueです。

> true || false
true
> 1 < 2 && 3 < 4
true

「〜でない」という否定には関数notを使います。Aがtrueなら「not A;」はfalseで、逆も同様です。

> not false;
true
> not 1 < 2
false

コマンド

例/常に維持する

以上で、簡単なプランを記述する準備がほとんど整いました。例として、実用的なプランの中で最も単純な部類に入る、「焦熱の外套を常に維持する」というものを書いてみましょう。

まず、「焦熱の外套」というスキルをこの言語で表現する必要があります。これは単に「焦熱の外套」と書くだけです。

> 焦熱の外套
焦熱の外套
> 焦熱の外套 == 焦熱の外套
true

一般に、式中にスキル名をそのまま書けば、そのスキルを表す値を記述したことになります。「1」とか「2」とか書くのと同じ感覚で「報復」とか「斬撃」とか書くことができるわけです。

さて、上述のように、この言語においてプランとは、計算するとコマンドが得られるような式のことです。この例では、計算結果が「焦熱の外套を維持せよ」というコマンドになるように、式を書く必要があります。これには、コマンドを生成するための専用の関数群が用意されているので、それを使います。「〜を維持せよ」という形のコマンドは、関数holdを使って生成します。

holdはスキルをコマンドに変換する関数です。引数として「X」というスキルが与えられたら、結果は「Xを維持せよ」というコマンドになります。したがって、「焦熱の外套を維持せよ」というコマンドは「hold 焦熱の外套」と書くことができます。

このようにして「hold 焦熱の外套」という式が完成しましたが、これは実際にプランとして機能します。試してみてください。

よく使われるコマンド生成関数

実際のプランでは、holdの他に以下のような関数がよく使われます。

dont_use スキル;
スキルを使わない。
prioritize スキル;
スキルを最優先にする。
switch_to スキルパターン;

パターンをスキルパターンに変更する。スキルパターンとしては整数か文字列を指定する。整数nを指定した場合は上からn番目(0から数える)のパターンが、文字列を指定した場合はその名前を持つパターンがそれぞれ選ばれる。

文字列とはその名の通り文字の列を表現する値で、その内容を二重引用符で括って書き表す。例えば、「A」と「B」の二文字からなる文字列は「"AB"」と書く。「自暴自棄」という名前のスキルパターンに変更したいなら、「switch_to "自暴自棄"」とすればよい。

seq コマンド1, コマンド2;

二つのコマンドの合成。合成結果のコマンドの作用は、コマンド1を実行してからコマンド2を実行するのとほぼ同じ。ただし、矛盾する指定があった場合にはコマンド1のものが優先される。

この関数は、一つのプランに複数の指示を込めたい場合に使える。

条件分岐

維持指定以外のほとんどのコマンドは、常に発行していてもあまり効果的でないので、特定の条件が満たされているときのみ実行されるようにしたいと思うでしょう。プラン言語には、そのような場合のために、条件分岐の機構が用意されています。構文は次の通りです。

条件 ? 式1 | 式2

この式を計算する場合、まず条件を計算し、その値がtrueであれば式1を、falseであれば式2をそれぞれ計算して、その結果を式全体の結果とします。

> true ? 1 | 2
1
> 2 == 3 ? 1 | 2
2

これを使って、「自分のHPが10以下なら、スキルパターンを防衛に変更する」というプランを書いてみましょう。

まず、「自分のHP」は「HP self;」と書きます。「HP」はキャラからその現在のHPへの関数であり、「self」は自分自身を表します。これが10以下であるという条件は次のように書けます。

HP self; <= 10

この条件が成り立つ場合に実行すべきコマンドは、「switch_to "防衛"」と書けます。条件が成り立たなかった場合、とくに実行すべきことはありません。このような場合、空のコマンドである「nop」が利用できます。nopはコマンドですが、一切の効果を持ちません。これを利用して、条件が成り立たなかったらnopを結果とすることにします。

これらを全てまとめると以下のようなプランが完成します。

HP self; <= 10 ? switch_to "防衛" | nop

なお、「自分のHP」の代わりに「相手のHP」としたいなら、「self」を「opp」に置き換えればよいです。

条件付きコマンドの略記

特定の条件を満すときにあるコマンドを実行し、そうでないときはなにもしない、というパターンはよくあるので、専用の略記法が用意されています。

条件 ? コマンド | nop

この形の式は次のように略記できます。

条件 => コマンド

この記法を使うと、上に書いたプラン「自分のHPが10以下なら、スキルパターンを防衛に変更する」は次のように書けます。

HP self; <= 10 => switch_to "防衛"

集合と関数

集合の操作

プランでよくある条件に、「相手が〜を構えている場合」というものがあります。キャラが特定のスキルを構えているかどうかを知るにはready関数が使えます。

ready キャラ, スキル;
キャラスキルを構えているかどうか。

しかし、「相手が火属性スキルを構えている場合」のような条件を記述するにはこれでは不足です。このような場合、集合の操作が必要になります。

skills_ready関数を使うと、指定したキャラが構えているスキルの集合を得ることができます。

> skills_ready opp
{炸撃:霧の防壁}

プラン言語における集合は、かならず有限集合である点を除いて数学の集合と同じようなものです。表記の際は、要素をコロンで区切って列挙し、全体を中括弧で囲みます。

ある値が集合に属しているかを判定するにはmember関数を使います。

> member 1, {1:2:3:4:5}
true
> member 斬撃, {炸撃:霧の防壁}
false

any関数を使うと、集合中に、ある「特徴」を持った値が存在するかどうか判定できます。

> any is_fire, {炸撃:霧の防壁}
true
> any is_counter, {炸撃:霧の防壁}
false

is_fireは「火属性である」という特徴であり、is_counterは「反撃スキルである」という特徴です。これを使うと、「相手が火属性スキルを構えている」という条件は次のように書けます。

any is_fire, skills_ready opp

第一級の関数

is_fireやis_counterはスキルの特徴ですが、実は、その正体は単なるスキルから真偽値への関数です。実際に、直接これらをスキルに適用することができます。

> is_fire 炸撃
true
> is_fire 霧の防壁
false
> is_counter 炸撃
false

anyは、第一引数の関数を集合の各要素に適用して、一つでも結果がtrueであるものがあるかを判定しているのです。

自分で「特徴」を記述できると便利なことがあります。例えば「相手が炸撃以外の火属性スキルを構えている場合」という条件を書きたいとしましょう。「炸撃以外の火属性スキルである」という特徴は、あらかじめ用意されているスキルの特徴一覧のなかにないので、自分で書くことになります。スキルの特徴とはスキルから真偽値への関数であることをふまえると、この関数を自分で用意すれば良いということになります。

関数を新たに作るには、以下のようにします。

  1. 引数の値がXだと仮定して、関数の結果を表す式をXを使って書く。
  2. 上で作った式をEとすると、「\X -> E」とすれば望む関数が得られる。

実例として、あるスキルが「炸撃以外の火属性スキル」であるかどうかを判定する関数を作ってみましょう。

  1. 引数の値、すなわち入力スキルがXだと仮定する。「Xが炸撃でない」は「X /= 炸撃」、「Xが火属性スキル」は「is_fire x」とそれぞれ表せるから、あわせて「X /= 炸撃 && is_fire X」が求める式である。
  2. よって、「\X -> X /= 炸撃 && is_fire X」が求める関数。

このように作った関数は、通常の関数と全く同じように適用することができます。

> (\X -> X /= 炸撃 && is_fire X) 炸撃
false
> (\X -> X /= 炸撃 && is_fire X) 斬撃
false
> (\X -> X /= 炸撃 && is_fire X) 爆撃
true

また、通常の関数と同じようにanyの引数として指定できます。

> any (\X -> X /= 炸撃 && is_fire X), {炸撃:霧の防壁}
false

なお、実際には、入力の値が入るとする変数の名前が「X」である必要はなく、アルファベットからなる任意の名前(ラテンアルファベットに限定せず、漢字やキリル文字なども可)で構いません。また、「\」と「->」の間に複数の変数を空白で区切って並べることで、複数の引数を受け付ける関数を書くことができます。

> any (\sk -> is_sword sk; && not is_attack sk), {斬撃:沙羅双樹}
true
> (\a b -> (a - b) * (a - b)) 1, 3
4

変数

プランを書いていると、同じ式を何度も書かなければならないことがあります。例えば、「相手HP-自分HPが4以上ならスキルパターンをAに、3ならBに、2以下ならCにする」というプランを素朴に翻訳すると以下のようになります。

switch_to
  HP opp; - HP self; >= 4 ? "A" |
  HP opp; - HP self; == 3 ? "B" |
  "C"

これには「HP opp; - HP self;」が二回出現していて、読み書きが面倒です。

このような場合、変数を使ってこれを一つにまとめることができます。変数とは、値に付けておく名前のことで、一度変数を導入すれば、それが有効な範囲内で何回でも参照することができます。変数導入の構文は以下の通りです。

変数 = 式1 | 式2

この式を計算するときは、まず式1を計算し、変数にその結果を値として持たせた上で式2を計算します。例をいくつか示しておきます。

> a = 1 | a
1
> a = 1 | a + a
2
> foo = 1 + 1 | foo + foo + 1
5

これを使うと、前に挙げた条件は次のように書き直せます。

switch_to
  hpdiff = HP opp; - HP self; |
    hpdiff >= 4 ? "A" |
    hpdiff == 3 ? "B" |
    "C"

このほかに、変数は長大な式を分割して扱いやすくするのに使うこともできます。

大域変数

上では、「nopはコマンドである」「holdは関数である」などと説明しましたが、これらは実際にはどこからでも参照できる変数であり、その値がコマンドだったり関数だったりするだけです。

イベントと歴史

TODO: 書く。

終わり

まだ説明していない関数や、特殊な言語機能については、リファレンスを参照してください。今のところ全体的に説明が不十分です。何か不明な点があれば遠慮なく掲示板で質問してください。