■ 新・ゲーム開発講座




■ へっぽこプログラミング入門♪


■第27夜:分岐

いよいよ佳境?に入ってまいりました「へっぽこプログラミング入門」ですが、今回は条件分岐について取り上げたいと思います。前回作ったフラグ内容に応じて処理を分けるというものです。これが出来ないとゲーム的な処理が出来ないので、それなりに気合を入れましょう。え?何だか難しそう?・・・大丈夫、ここはへっぽこな講座ですから易きに流れ安直に進みます(をい ^^;)。さっそく書式を示しましょう。

#if_flag n {
フラグがONの場合の処理
/
フラグがOFFの場合の処理
}


なんだかC言語風?の
ブロック構造が出てきましたね。実はネスト記述も可能な本格?仕様です(^^;)。「#」「;」にひきつづき、半角の「{」「/」「}」も予約語として文中では使えなくなってしまいますが、まあ我慢していただきましょう(^^)。本日のソースはこちらです。

■ブロック内の歩き方

さて、実はコマンド本体を記述する前に、どうやってテキストブロック内を動き回るのか、ということについてお話しておきたいと思います。今回の仕様である 「{」 「}」 に挟まれた中にデリミタ(区切り文字)として「/」が配置してある構造の場合、たった2種類のサーチ関数があれば事足ります。今回のソースでは TextEngine.cpp に記述してある next_slush()jump_to_end_kakko() です。名前がダサいのは置いておいて(爆)、それぞれ「次に現れる "/"」「現在のブロック端 "}"」を探す関数です。

ネスト対策になっているのは「{」「}」に遭遇する度に増減するカウンタ kakko です。ネストが深くなると+1、浅くなると−1のカウント値が加算され、ちょうど0のときに遭遇した「/」や「}」のときにループを抜け出すようになっている訳です。たったこれだけでスクリプトで表現できる処理の幅がぐっと広くなります。


//----------------------------------------------
// 次の「/」の直後までテキストポインタを飛ばす
//----------------------------------------------

int next_slush()
{

int kakko=0; //「{」で+1、「}」で−1するカウンタ(ネスト対策)
int counter; //とりあえず無限ループを防ぐためのカウンタ(笑)

while( 1 ) {
if( *TEXT=='/' && *(TEXT-1)<0x80 && kakko==0 )break;
if( *TEXT=='{' && *(TEXT-1)<0x80 )kakko++;
if( *TEXT=='}' && *(TEXT-1)<0x80 )kakko--;

TEXT++;
counter++;

if( counter>0xffff ){
MessageBox(NULL,"サーチEND。{ }の対応が変?かも・・・(謎)","Error",MB_OK);
PostQuitMessage(0);
return 0;

}

}
TEXT++;

return 0;

}

//----------------------------------------------
// {}のブロック端を探す
//----------------------------------------------

int jump_to_end_kakko()
{

int kakko=1; //「{」で+1、「}」で−1するカウンタ(ネスト対策)
int counter; //とりあえず無限ループを防ぐためのカウンタ(笑)

while( 1 ){
if( kakko==0 )break;
if( *TEXT=='{' && *(TEXT-1)<0x80 )kakko++;
if( *TEXT=='}' && *(TEXT-1)<0x80 )kakko--;

TEXT++;
counter++;

if( counter>0xffff ){
MessageBox(NULL,"サーチEND。{ }の対応が変?かも・・・(謎)","Error",MB_OK);
PostQuitMessage(0);
return 0;

}

}
TEXT++;

return 0;


}



今回は、このサーチ関数を使って条件分岐を記述します。

※↑リスト中で if( counter>0xffff ){・・・ とやっているのは、万一サーチエラーになった場合に、適度なところで強制終了してハングしないように・・・との安直な記述です(^^)あまり深い意味はありません(爆)

■Mode_stat

今回の処理では Mode_stat は使用しません。

■コマンド解釈部

TextEngine.cpp の void Command_call() に以下の部分を加えます。



void Command_call()
{

//省略

//コマンド名の評価 → 処理関数呼び出し
if( strcmp(com_name,"delay" )==0 ){ Com_delay(); flag=ON;} //遅延
if( strcmp(com_name,"wait" )==0 ){ Com_cursor_blink(); flag=ON;} //カーソルブリンク
if( strcmp(com_name,"w" )==0 ){ Com_cursor_blink(); flag=ON;} //カーソルブリンク
if( strcmp(com_name,"halt" )==0 ){ Com_halt(); flag=ON;} //終了
if( strcmp(com_name,"page" )==0 ){ Com_page(); flag=ON;} //改ページ
if( strcmp(com_name,"g_load" )==0 ){ Com_g_load(); flag=ON;} //BMPのLOAD
if( strcmp(com_name,"g_copy" )==0 ){ Com_g_copy(); flag=ON;} //BMPのCOPY
if( strcmp(com_name,"g_change")==0 ){ Com_g_change(); flag=ON;} //画面切替
if( strcmp(com_name,"flag_on" )==0 ){ Com_flag_on(); flag=ON;} //イベントフラグON
if( strcmp(com_name,"flag_off")==0 ){ Com_flag_off(); flag=ON;} //イベントフラグOFF
if( strcmp(com_name,"disp_flag")==0 ){ Com_disp_flag(); flag=ON;} //イベントフラグ表示
if( strcmp(com_name,"if_flag" )==0 ){ Com_if_flag(); flag=ON;} //フラグによる分岐

//省略

}

■コマンド実行部

コマンド実行部は、以下のようになります。ずいぶんシンプルですね(^^)。フラグの番号を解析した後、while()ループでテキストブロックの最初の「{」を探し、その直後までテキストポインタを進めます。フラグの内容がONならば、実行すべきテキストはすぐ直後の位置なので何もしないで終了します。フラグの内容がOFFならば next_slush() を呼んで「/」の直後までテキストポインタを進めて終了します。


int Com_if_flag()
{

int num;

//フラグ番号解析
TEXT++;
num = kaiseki_10();

while( *TEXT != '{' )TEXT++; //最初の { を探す
TEXT++;

if( Mode_stat.event_flag[num]==ON ){
//なにもしない(^^)
}else{
next_slush();
}

return 0;

}

■ブロック端の処理

さて、テキストポインタをフラグに応じて「ご案内」するのは上記内容で完了ですが、これだけですと実は困ったことになります。「ブロック端」の処理を忘れているからです。試しにこの状態で以下のスクリプトを実行すると、処理1→処理2→処理3→処理4→処理5→処理6→処理7→処理8→処理9・・・と、全ての処理が実行されてしまいます(爆)フラグの設定意図からすれば、本来なら 処理1→処理2→処理4→処理9 と進んで欲しいところですよね(^^)。

#flag_on 0
#flag_on 1
#flag_on 2

#if_flag 0 {
■処理1
#if_flag 1 {
■処理2

/
■処理3

}
■処理4

/
■処理5
#if_flag 2 {
■処理6

/
■処理7

}
■処理8

}
■処理9


ではブロック端はどのように扱ったら良いのでしょう。ブロック端とはデリミタ文字「/」と「}」のことですから、これらの文字を見つけたところでなにかすれば良い訳です。具体的には、TextEngine.cpp の文字チェック部分に以下のような記述を追加します。

//--------------------------------------------------------
// 制御文字の処理
//--------------------------------------------------------

int Check_control_chr()
//*TEXT のポイントする1バイト文字をチェックし、
//制御文字であれば処理して true を返し、それ以外は
//false を返す

{
switch ( *TEXT ) {
case '#': Command_call(); //コマンド処理部を呼ぶ
TEXT++;
return true;
case 0x09: //TAB
case 0x0a: //LF
case 0x0d: //CR
case '{' :
case '}' : return true; //何もしないで戻る==無視♪
case '/' : jump_to_end_kakko();
return true;
case ';': Com_linefeed(); //[;] 改行
return true;
}

return false; //制御文字に該当しなかった → falseを返す
}



え〜っ、「{」と「}」は無視していいの?・・・との声が聞こえてきそうですが、無視して結構です。ブロック端デリミタのうち「}」はブロック端サーチ関数の識別子としてのみ存在すればよく、またこの種の制御文字は画面に表示されては困るので無視します。「{」に至ってはプログラム的には何の意味もないただの飾りです(人間がスクリプトのブロック構造を把握しやすいように置いているだけなのです)。そして、「/」に遭遇した場合のみ、既出の jump_to_end_kakko() (・・・それにしてもダサイ名前だな -_-;)を呼んでブロックからの脱出をしています。ナナメ読みで本稿を読んで「・・・???」という方は、焦らずにゆっくり紙上DEBUGをしてみてください。

ソース付属のサンプルスクリプト default.txt では、上記の処理1〜9の部分のスクリプトを実行しています。フラグ内容を書き換えながら実行状況を確認してみてください(^0^)。

※補足:今回のソースではテキストの表示ウェイトを 60 → 30 msec に変更してちょっと高速化してあります。条件分岐の神通力で早くなった訳ではありませんので誤解なきよう・・・(笑 ^^;)