mfstのシナリオファイルの構造


概要

C言語に似せて作ってあります。関数がなく、そのかわりシーン(scene)とサブルーチン(sub)があります。
シナリオはこんな感じに記述します。
int x = 5; // 大域の固定変数 scene Start() { // シーン static int x = 3; // 局所の固定変数。最初の1回だけ初期化される int i; // 局所の自動変数 for( i=0; i<x; i++ ) { write("for文のサンプル。", i, "回目のループ" ); } jump End( i ); // 他のシーンへの移動 } scene End( int i ) { // 引数のついたシーン write( i ); exit(); }

C言語風なので記述は楽だと思いますが、そのかわり
・ラベルの使用数が増える(たぶん、HPNV Senario Compilerで直接書くときの2倍くらいに)
・遅くなる(特に式が...)
といった代償があります。今後、ある程度高速化するつもりですが。

全体の構造

シナリオファイルの先頭に大域変数の宣言を書き、その後はシーンまたはサブルーチンを0個以上ならべます。
変数の宣言はC言語とおなじで、下図のように書きます。
int a; // 1個だけの宣言も int b, c; // 複数まとめた宣言も int d = 4; // 初期値つきでも int e = d * 4; // 初期値として既に宣言した変数を使うこともできます

他シナリオにセーブデータを引き継ぐために、HPNV Senario Compilerの変数番号を明示するときは、宣言の前に「static 変数番号」を付けます。この場合、変数は1つずつ宣言します。(いまは、うまく値がロードできないのですが...これは僕の問題です)
static 7 int x; static 4 int y;

シーン

シナリオファイル中の先頭のシーンから実行されます。シーンの宣言はC言語の関数に似ています。シーンの例
scene シーン名 ( 引数宣言 ) { 0個以上の局所の固定変数の宣言 0個以上の局所の自動変数の宣言 0個以上の文 }

引数宣言はcと同様です。他のシーンへ遷移するときは「jump シーン名(引数)」と書きます。シーンの例
scene サンプル ( int a, int b ) { int x = rand( 10 ); if ( a <= x && x < b ) { write( "乱数", x, "は範囲", a, "〜", b, "に入っています"); } jump 単純なシーン(); // ()をつけるのを忘れないように } scene 単純なシーン () { int a = rand( 4 ); int b = rand( 10 - a ) + a; jump サンプル( a, b ); // 引数があるシーンへの遷移 }
どこにも遷移しないでシーンの最後まで来たときは、シナリオが終了します。
あるシーンにジャンプしてきたときのジャンプ元のシーンは関数from()で調べられます(後述)。

サブルーチン

サブルーチンの宣言はシーンと似ていますが、引数や局所変数を使えません(HPNV Senario Compilerが間接アドレッシングできるようになれば、引数、戻り値、局所変数の使える関数に拡張したいです)。また、サブルーチンを宣言する前には、必ず1つ以上のシーンの宣言が必要です。シナリオの最初がいきなりサブルーチンというのはダメです。
サブルーチンを呼ぶには「call サブルーチン名()」です。サブルーチンから戻るには「return;」です。returnは明示しなくてもサブルーチンの最後にくれば自動にreturnします。
サブルーチンの例
sub サブ() { if ( x == 3 ) { write( "あああ" ); retrun; // リターンを明示するとき } call 他のサブルーチン(); // } // 何もかかなくても最後はリターンします
なお、サブルーチンコールは8段階までです。

変数

int型しかありません。大域変数はシナリオの先頭、局所変数はシーンの先頭でしか定義できません。C言語のように途中のブロックで変数を定義することはできません。
変数には、シナリオの先頭で宣言する大域変数と、シーンの内部で宣言する局所変数があります。大域変数はどのシーンからも参照・変更することができます。なお、局所変数の最大数と、大域変数の最大番号の和は92まで許されます。
局所変数の「最大数」というのは、シナリオ中の局所変数の総和ではなく、それぞれのシーン内で最も多く局所変数を宣言した数のことです。例えば、シナリオ中に局所変数を10個持つシーンと局所変数を7個持つシーンが存在するなら、最大数の局所変数は10個ということです。
大域番号の番号は、staticを使わなければ0から順番に使っていきますが、staticで大きな番号を指定すると残り番号が一気に減ってしまいます。
なお、変数の数の制限は、HPNV Senario Compilerの変数の数が増えれば緩和できます。
(HPNV Senario Compilerでは100個の変数を使えるのですが、そのうちmfstでは式の展開に4つ、その他の用途に4つ使用しているので、シナリオで利用できるのは残り92個です)

if文、for文、while文、do文、switch文はC言語とほとんど同じです。switch文のcaseには、数値だけでなく式も書けます。if文は{}を省略できません。
scene loopSample() { int i, j; for( i=0; i<10; i++ ) { write( "for", i ); if ( i==3 ) { break; } else if ( i==2 ) { write("i=2でした"); } else { write("その他"); } } while( i<10 ) { write( "while", i ); i++; } do { write( "do", i ); i--; } while( i>0 ); i = rand( 5 ); j = rand( 5 ); switch( i ) { case j: write( "i==j==", i ); break; case 3: write( "i=", i ); break; default: write( "i=", i, " j=", j ); break; } }

choice文は、画面に選択肢を出して、ユーザーの入力を求めます。文法はC言語のswitchとほとんど同じで、下図のようになっています。なお、選択肢は2個もしくは3個です。
choice("選択肢ウィンドウの名前"){ case "選択肢1": 文 break; case "選択肢2": 文 }

breakをつけないと、次の選択肢も実行してしまう点もswitchと同じなので注意してください。choiceの例。
choice("家に帰ったら何をしますか?") { case "食事をする": write("いただきます"); break; case "酒を飲む": write("すごく酔っぱらった"); case "寝る": write("おやすみなさい"); }
この例では、2つめのcaseにbreakがないので、「酒を飲む」を選択すると、「すごく酔っぱらった」に続いて、「おやすみなさい」も実行されます。

コマンド

コマンドには、write, picture, layer, eraselayer, cls, wait, paint, erasepaint, exit, loadfileがあります。これらはほHPNV Senario Compilerの命令そのものなので、詳しくはHPNV Senario Compilerのマニュアルを見てください。説明が面倒なので例だけ...(いずれ、ちゃんと書きます...)
write( "文字列", a+2, 4, ); // ,で区切って表示する文字列や式を指定します picture( "sample.bmp" ); // 画像の表示 picture( "sample.bmp", 4 ); // エフェクト4を使って画像を表示 layer( 1, "sample.bmp", 3 ); // エフェクト3で、レイヤ1に画像を表示 eraselayer( 2, a ); // エフェクトaでレイヤ2を消去 cls(); // 文字の消去(画像は消えません) wait(); // 何か入力があるまで待ちます paint( "ff00ff", 3 ); // エフェクト3を使いRGB値FF00FFで塗り潰す erasepaint(3); // エフェクト3を使い再描画 exit(); // シナリオの終了 loadfile( "anotherSenario" ); // 他のシナリオのセーブデータのロード
エフェクトは-1〜5があります。-1は画像を表示せず、0は瞬間的な表示、1〜5はさまざまな表示効果、となっています。
レイヤは1と2の2つがあります。他の場所には変数や式を書けますが、レイヤ番号を指定するところだけは、式は使えません。「1」か「2」の数字を直接書いてください。
loadfileは、理由はよく解りませんが今は動きません。他に動いている人がいるので、たぶん僕が何か間違えているのだと思います...

関数

関数には、rand, rect, from, idがあります。
a = rand( 10 ); // 0〜10の乱数 a = rect( x1, y1, x2, y2 ); // wait()コマンドの結果のタップ位置が // 座標(x1,y1)〜(x2,y2)内なら1。それ以外では0 a = from(); // 現在のシーンに飛んでくる前にいたシーン番号 // 最初のシーンでは-1、それ以外は0以上の値となる a = id( シーン名 ) // シーン名からシーン番号を得る

from()とid()を使うと、シーンの遷移を楽に書ける、かもしれません。
int 出発; int 到着; scene 新宿() { 出発 = from(); call 出身(); write("新宿に着きました。"); choice("どこに行きますか?") { case "渋谷": jump 渋谷(); case "池袋": jump 池袋(); case "東京": jump 東京(); } } scene 渋谷() { 出発 = from(); call 出身(); write("渋谷に着きました。"); choice("どこに行きますか?") { case "新宿": jump 新宿(); case "東京": jump 東京(); } } scene 東京() { 出発 = from(); call 出身(); write("東京に着きました。"); choice("どこに行きますか?") { case "池袋": jump 池袋(); case "新宿": jump 新宿(); case "渋谷": jump 渋谷(); } } scene 池袋() { 出発 = from(); call 出身(); write("池袋に着きました。"); choice("どこに行きますか?") { case "新宿": jump 新宿(); case "東京": jump 東京(); } } sub 出身() { switch( 出発) { case -1: write( "あなたは初めて日本に来て"); break; case id( 新宿 ): write( "あなたは新宿から出発し"); break; case id( 池袋 ): write( "あなたは池袋から出発し"); break; case id( 東京 ): write( "あなたは東京から出発し"); break; case id( 渋谷 ): write( "あなたは渋谷から出発し"); break; } }

ラベルとジャンプ

「:ラベル名」としてラベルを定義し、「goto ラベル名;」でそこにジャンプします。別のシーン内のラベルにもジャンプできます。でも引数やfrom()を使っているシーンの中に飛び込むと、引数やfrom()の正しい値が得られません。とはいえ、値が正しくないだけです。動作自体が異常になることはないので、安心して使ってください。(シナリオの見通しが悪くなるので、他のシーン内へのジャンプはお勧めしませんが)