×

[PR]この広告は3ヶ月以上更新がないため表示されています。
ホームページを更新後24時間以内に表示されなくなります。

ループ文その1

ループ文を使うと、同じ(似たような)動作をすっきりと書くことができます。


はじめに

ACSを書いていく上で、同じような操作を何回も行いたいときってありますよね。
例えば、
1から100までの整数の総和を求めたい時などです。
そんな時に登場するのがループ文です。
ループ文は非常に便利な概念です。是非マスターしましょう。


for文

まず、for文を学んでいきましょう。
まずは例から見てみましょう。
このACSは1〜100の整数の総和を表示します。


#include "zcommon.acs"

Script 1 ENTER
{
int sum=0;
for(int k=1;k<=100;k++){
sum += k;
}

print(d:sum);
}


と表示されます。(1〜100までの整数の総和)

赤い字の部分を見てみましょう。

この部分をfor文と呼びます。この部分を二つに分解してみると、

for(int k=1;k<=100;k++)

の部分でループの条件を指定し、

{
sum += k;
//変数sumの値をkだけ増やす。
//sum = sum + k;
//と同じ意味です。
}

の部分でループの内容を指定しています。
この内容はわかると思うので、ループの条件を見てみましょう。

一般に、for文の条件の指定は3つの部分から構成されます。

for(変数の宣言 ; ループする条件 ; ループ後に変数に行う操作)

変数の宣言の部分では、ループを制御する変数を指定します。(例ではkがそれです。kの値ははじめは1です。)
ループする条件の部分では、if文の時のように、ループするための条件を指定します。(例では「kが100以下である。」)
ループ後に変数に行う操作の部分では、ループの内容が一度実行された後に制御用の変数に与える操作を指定します。(例では「kの値を1増やす。」)

ループの構造を言葉で説明するのは難しいので、次の図を見てみましょう。

←イメージ

図の様に、ビジュアル的に覚えるのが良いと思います。
言葉で説明すると、
ループ文に入ると、まず
(図の)@が実行されます。(@が実行されるのはこのときだけです。)
次に、Aが実行され、真ならループの内容が実行されます。
(偽ならループ文は実行されず、ループ文の次の行へ移ります。)
ループの内容が実行された後は、
Bが実行され、
再びAで評価します。
・・・これをずっと繰り返します。

つまり、この例で考えると、kの値は1から始まり、1ずつ値が増えながら100になるまで変数sumに値を与え続けることがわかります。
つまり、最終的には変数sumには1から100までの総和が与えられているわけですね。

もっと実用的な例を見てみましょう。
(ここらへんから第2版の例の流用が多くなります。(^_^;)いきなり初見の関数がバンバンでますが、適宜コメントを付けていきます。)


#include "zcommon.acs"

Script 1 ENTER
{

//変数angleが256になるまでループします。
for(int angle = 0; angle < 256; angle += 8){

Thing_ProjectileGravity (300, T_ARACHNOTRONPLASMA, angle, 16, 32);
//Tidが300のThingからArachnotronのプラズマ弾を角度angle(Byte Angle)の方向に上向きに放物線を描いて発射します。

delay(2);
//Scriptを2/35秒停止させる。
}
}


本当だったら32回Thing_ProjectileGravity関数を呼び出さなければならないのに、ループ文を使うとすっきり書けました。
同じような(微妙に変更を加えた)内容を繰り返すときには、ループ文が適していることがわかります。

実行すると・・・

成功です。円形に一定間隔をあけてArachnotronのプラズマ弾が発射されています。


while文

while文は、「制御用の変数が必要ないときに使うfor文」といった感じです。
for文をマスターしていれば、すぐにwhile文もマスターできます。

まずは例を見てみましょう。


#include "zcommon.acs"

Script 1 ENTER
{

int counter = 1;

int sum = 0;

while(counter <= 100){

sum += counter;

counter++;
}


print(d:sum);
}


と表示されます。
実は、このACSも前回と同じように、1〜100の整数の総和を表示します。

赤い字の部分に注目しましょう。
この部分がwhile文です。

while(counter <= 100){

sum += counter;

counter++;
}

while文の

{

sum += counter;

counter++;
}

の部分はfor文の時と同じようにループの内容です。

while(counter <= 100)

を見てみましょう。
この括弧の中の条件式が真(TRUE)であればループ文の内容を実行し、
偽(FALSE)ならループ文から脱出し、ループ文の次の行に進みます。
(for文のAの部分ですね。)

←イメージ

↑for文と同じように、ビジュアル的に覚えましょう。

具体例を見てみましょう。


//具体例その1

#include "zcommon.acs"

Script 1 ENTER
{
int counter = 1;

while(counter <= 4){

Thing_Spawn(counter,T_IMP,128,0);

//TidがcounterのThing(MapSpot)に西向きにImpを召還する。

counter++;
}

}


実行すると・・・

無事Impが出てきました。(Tidが1〜4のMapSpotにImpが出てきます。)
本当は4回書かなければならなかったThing_Spawn関数をループ文を使うとすっきり書けました。

次の例を見てみましょう。


//具体例その2

#include "zcommon.acs"

Script 1 ENTER
{
while(ThingCount(0,1))
delay(1);


delay(35);

//1秒待つ

Thing_Spawn(2,T_SOULSPHERE,128,0);
//Tidが2のThing(MapSpot)に西向きにSoulSphereを出現させる。
}


実行すると・・・(Former Humanを倒しています。)

このACSでは

while(ThingCount(0,1))
delay(1);

の部分が最も重要な部分です。

まず、ThingCount関数について説明します。
ThingCount関数の一般的な形は

ThingCount (int type, int tid)

この関数は、指定したThingの数を調べる関数です。

引数typeには数えたいThingのSpawnNumber(前述したzdefs.acsの中にあるT_...から始まる定数です。)を、
引数tidには数えたいThingのTidを入力します。
(typeでもtidでも特に指定が無い場合は0を入力します。また、指定したThingがモンスターなら、生存数を調べます。)

つまり、先ほどの
ThingCount(0,1)
の部分で、「Tidが1のThing(の生存数)」の数を数えています。
つまり、Tidが1のThingが存在している限り、ThingCount関数は1以上の値を返すので、while文の条件は真(TRUE)となります。
(真(TRUE)の定義はbool型変数の時に説明しましたよね。)


条件が真(TRUE)の場合、while文のすぐ下の部分の

delay(1);

の部分が実行されます。
({,}の括弧が省略されていますが、while文のループの内容はこのdelay関数のみです。)

この関数は、「このScriptを1/35秒間停止する」という働きをしています。
ほぼ一瞬の間だけScriptを停止するという動作を永遠に繰り返しています。

このACSを使用したMAPには、Tidが1のFormerHumanを一人配置しています。

つまり、
Tidが1のThingが生存している限り、while文より次の部分にScriptが進めないということを意味しています。

この
while(ThingCount( /*SpawnNumber*/ , /*Tid*/ ))
delay(1);

は、ACSを使う上で便利な構文です。ぜひ覚えておきましょう。

FormerHumanを倒したら、あとは例に書いてあるコメント通りにACSが進行し、SoulSphereが出現します。


until文

ACSではfor文やwhile文の他にも、ループ文としてuntil文が存在します。
が、文字通り、その役割はwhile文の逆です。
記述の仕方など基本的にはwhile文と全く同じですが、ループのための条件が変わります。
while文ではループのための条件が真(TRUE)であったのに対し、
until文ではループのための条件は偽(FALSE)です。
先ほどの具体例2をuntil文で書き換えてみます。(結果は同じです。)


#include "zcommon.acs"

Script 1 ENTER
{
until(!ThingCount(0,1))
delay(1);


delay(35);
//1秒待つ

Thing_Spawn(2,T_SOULSPHERE,128,0);
//Tidが2のThing(MapSpot)に西向きにSoulSphereを出現させる。
}


赤い字の部分の"!"は論理否定演算子です。(真と偽を入れ替えます。)
(!演算子のの詳しい働きは演算子表で確認して下さい。)

つまり、whileとuntilには次の様な関係が成り立つことがわかります。

while(/*内容*/)

until(!/*内容*/)
は等しく、

while(!/*内容*/)

until(/*内容*/)
も等しい。

つまり、while文の中で"!"の論理否定演算子を使わざるを得ないときに、until文を使うとすっきりとした記述を書くことができます。

ただし、until文を使わなくても、逆の関係演算子に変えたり(>から<=など)、ド・モルガンの法則を利用したりしたら、何とかなります。
実際、ACSでuntil文が使われているのはあまり見かけたことがありません。


while文、until文を使うときの注意

while文の説明をしたときに使用した最初のACSを少し変えてみます。
(1〜100の整数の総和を求めるACSです。)


#include "zcommon.acs"

Script 1 ENTER
{
int counter = 1;

int sum = 0;

while(counter <= 100){

sum += counter;

//counter++;の記述がなくなりました。
}

print(d:sum);
}


これでACSを実行してみると、下図のようなメッセージが出て、1番のScriptが強制終了します。

原因を考えてみましょう。
counter++;
の記述がなくなったことで、while文の条件が常に真となります。
つまり、ゲームの進行において、同じ時間帯に永遠に同じ計算を繰り返すことになります。
そのため、ZDOOMが1番のScriptを強制終了せざると得なくなったのですね。

このように、同じ時間帯に永遠にループさせる記述は避けましょう。
必ず脱出させるようにScriptを書き換えるか、delay関数をループの内容の部分に入れましょう。

for文を使うと、最初に制御用の変数を宣言したりできるので、このような自体になることはあまりありません。
for文は使いやすいですし安全なので、while文よりfor文を使うことをおすすめします。


まとめ

・for文↓
for(変数の宣言 ; ループする条件 ; ループ後に変数に行う操作){
//ループの内容
}
・while文↓
while(ループする条件){
//ループの内容
}
・until文はwhile文の逆。
・↓はセットで覚える
while(ThingCount( /*SpawnNumber*/ , /*Tid*/ ))
delay(1);
・while文とuntil文を使うときには、同じ時間帯に永遠にループしていないか確認。


Home Back Next