NScriptしか知らない人向けのLua講座に挑戦 -- for〜next編

for〜nextについて

NScriptの場合

for %0=0 to 10 step 1 ; 変化値が+1の時は、step節自体を省略できる。
%0 / ; 0 1 2 3 4 5 6 7 8 9 10と表示される。
next

for (ループ変数)=(初期値) to (終了値) step (変化値)
; 処理を書く。
next

for〜nextはループのための特殊な文です。
実行行がnextまで来ると、forで宣言したループ変数の値をチェックします。それが終了値未満(初期値>終了値の時は逆に終了値超)の時、ループ変数に変化値を加え、forに実行行を戻します。条件を満たさなかった場合、次の行に戻します。
繰り返しを簡単にする、非常に応用範囲の広い命令ですので、是非とも使いこなせるようになりたいものです。

安全装置

「初期値<終了値」の時に変化値がマイナスだとそもそもループしません。(無限ループにはなりません)逆に、「初期値>終了値」の時に変化値がプラスでも同様です。

一行で済ませる
for %0=10 to 100:vsp %0,0:next ; 10番から100番までのスプライトを非表示にする。
print 1

このように、一行に収めても正しく動作します。

入れ子にできる
for %0=1 to 100
	for %1=1 to 100
		; なんか処理を書く
	next
next

こうすると、合計で10,000回ループします。
また、中のforと外のforで同じ数値変数を使うこともできますが、どのような動作をするか理解できない限りはやらない方が無難です。と言うより、あまり使い道がないですが。

途中で次のループに突入する小技
for %0=0 to 10
if %0=5 next ; (1)
%0 /
next ; (2)

これで、ループの途中で残りの処理をとばして次のループに突入できます。
ただし、(1)でループ変数を最終値にしてしまうと、(2)で「next文とfor文が正しく対応していません」エラーが発生します。

途中でループを終わらせる小技

forで使っている変数は、内容が保護されている訳ではないので、変更できます。なので。

for %0=0 to 10
if %0=5 mov %0,10
%0 / ; 0 1 2 3 4 10と表示される。
next

ループ変数の値をいきなり最終値以上(初期値>最終値の時は最終値以下)にしてしまえば、次のnextでループを抜けることができます。
もっとも、これはbreakを使えばより安全に、即座にループを抜けることができます。通常はこちらを使ってください。

for %0=0 to 10
if %0=5 break
%0 / ; 0 1 2 3 4と表示される。
next
ループをやり直す小技
for %0=0 to 10
if %0=5 && %1=0 mov %0,-5:mov %1,1
%0 / ; 0 1 2 3 4 −5 −4 −3 −2 −1 0 1 2 3 4 5 6 7 8 9 10と表示される。
next

同様に、ループ変数の値を任意の値に変更すれば、ループ全体を最初からやり直したり、ループ全体を拡張したりできます。ただし、これらのテクニックを使う場合は、無限ループにならないように細心の注意が必要です。

相性の悪い命令

for〜next中にgosubやdefsubで飛んでいくのは構いません。(いずれ戻ってくるのがわかっていれば)
しかし、goto, skip, jumpf/jumpb, trap系命令などでループの外に出てしまうと、スタックが残ったままになり、後々わかりにくいバグの元になりますのでやらないようにしてください。ただ、飛んだ先にnextを用意できればそれでも構いません。「途中で次のループに突入する小技」と同じような動作を期待できます。
trap系命令などと組み合わせる時は、ラベルとgotoで組み直すべきでしょう。

mov %0,0
*loop_label
; 処理を書く。
inc %0
if %0<10 goto *loop_label

このようにすれば、ほぼ同じ動作が期待できますし、trap系命令によってfor〜nextのスタックが残ってしまうこともありません。

Luaの場合

for i=1, 10 do
	-- 処理を書く
end
for i=1, 10, 2 do NSOkBox("一行にすることもできます。", "for") end

NScriptとの違いは

  • nextのかわりにendを使う。
  • to, stepはない。doを使う。
  • 初期値、最終値、変化値は、i=j, k, lと言うふうにコンマ区切りで表記する。(変化値は省略すれば1が使われる)

また、特記すべき点は、ループ変数はlocal宣言された扱いになるので、ループの中のiとループの外のiは違うものとして扱われます。

i = 20 -- i に 20 を設定する。
for i=1, 10 do
	NSOkBox(tostring(i), "i") -- 1〜10が表示される。
end
NSOkBox(tostring(i), "i") -- 20が表示される。
  • Luaではループ変数の中身は保護されており、NScriptでできた途中で書き換える小技は全て使えません。
  • endは一つしかおけませんので、NScriptでできたループの途中で次のループに突入する小技も使えません。gotoなどもないので、細かな制御は面倒になります。
for i=1, 10 do
	if i==5 then
		-- 何もしない。
	else
		NSOkBox(tostring(i), "i") -- 1〜4, 6〜10が表示される。
	end
end

Lua独特の拡張

Luaでは変数型にtable型が存在します。forにはこのtable型変数と非常に相性のよい別の書き方ができます。

local t = {}
t[1] = 20
t[2] = 30
t[3] = 40
for i, num in ipairs(t) do
	-- iとnumに、(1, 20)/(2, 30)/(3, 40)の組み合わせが順番に入る。
end
local t = {}
t.a = "abc"
t.b = 123
t.c = {}
t.d = true
for key, value in pairs(t) do
	-- keyとvalueに、("a", "abc")/("b", 123)/("c", {})/("d", true)の組み合わせが入る。順番はわからない。
end
  • for num, v in ipairs(t) do end
  • for k, v in pairs(t) do end

上記二つの文はLuaでは頻繁に使う形式ですので、覚えておいて損はないです。

こういう拡張はどのように使うか。
local party = {} -- パーティの入れ物
party[1] = chara1
party[2] = chara2
party[3] = chara3
party[4] = chara4
party[5] = chara5
party[6] = chara6
-- このようにパーティを作ったとする。

-- 全体魔法をかけられて全員に2d6のダメージ!
for num, chara in ipairs(party) do -- 順番にダメージを与えていく。
	local damage = math.random(6) + math.random(6)
	chara.damage = chara.damage + damage
end
-- 誰か死んでないか?
for num, chara in ipairs(party) do -- 順番にチェックしていく。
	if chara.damage < chara.hp then -- セーフ
	else
		die(chara) -- キャラクター死亡ルーチンへ。
	end
end