2.タートルグラフィクスのコマンドを作る
コマンドとは
タートルグラフィクスでは、亀に、「前に5歩進め」「右に45度向け」などの指示を出して亀を歩かせます。このような指示(命令)を「コマンド」といいます。これをCindyScriptの「関数」として定義することにしましょう。
日本語のLOGOでは「前へ」「右へ」「左へ」のようなコマンドになりますが、CindyScriptは英語が基本なのでこれらのコマンドも英語にしましょう。
ひとまず、次の3つを作ります。
forward(n) 前へnだけ進め nは整数とは限りませんので、nではなくxを使うことも考えられます。
right(θ) 右へθだけ向きを変えろ(θは「シータ」と読みます。ギリシャ文字で、角度を表すときによく使います)
left(θ) 左へθだけ向きを変えろ
CindyScriptで関数を作るには、次のようにします。
forward(n):=(行うことがら)
ここで、単なる等号ではなく、コロンと等号を使います。すると、右辺に書かれた内容が左辺の言葉の定義となります。括弧の中の数は「引数(ひきすう)」といいます。
では、前へ進むコマンド forwardを作りましょう。ただ前へ進むだけでなく、動いたあとを線分として引きます。また、進んだ先をあらためて原点とします。つまり、座標系を平行移動するのです。関数として定義する(行なうことがら)は、次の2つです。
原点からx軸上の点(n,0)まで線を引く・・・・ draw()関数を使います。
(n,0)まで平行移動する。 ・・・・translate()関数を使います。
------------------------------------------------------------------------------------------------------
forward(n):=(
draw([0,0],[n,0]);
translate([n,0]);
);
------------------------------------------------------------------------------------------------------
次に、「右へ向きを変える」と「左へ向きを変える」です。座標系を回転する rotate(θ) 関数を使いますが、θはプラスのときは反時計回りに、マイナスのときには時計回りに回転します。また、「θ」は入力しにくい(漢字入力モードで「しーた」と打って変換)ので、適当なアルファベットの文字(言葉)にします。角をあらわす angle とでもしましょう。
------------------------------------------------------------------------------------------------------
left(angle):=rotate(angle);
right(angle):=rotate(-angle);
------------------------------------------------------------------------------------------------------
では、この3つの関数の働きを確かめましょう。亀に正三角形を描かせます。
------------------------------------------------------------------------------------------------------
repeat(3,
forward(2);
left(120°);
);
------------------------------------------------------------------------------------------------------
コマンドの記号化と星形五角形
次に、「前へ」「右へ」「左へ」という亀への命令をそれぞれ記号で表すことにします。リンデンマイヤーのL-Systemでは、「前に」を A,B,またはF、「右へ」を − 、「左へ」を+ としていますので、そのようにしましょう。そうしておいて、進む長さと向きを変える角度を決めておくと、これらの記号を使って星形五角形を描くことができます。いままでのものも含めて、次のようにします。
------------------------------------------------------------------------------------------------------
forward(n):=(
draw([0,0],[n,0]);
translate([n,0]);
);
left(angle):=rotate(angle);
right(angle):=rotate(-angle);
turtle(s,n,angle):=(
if(s=="A",forward(n));
if(s=="B",forward(n));
if(s=="F",forward(n));
if(s=="+",left(angle));
if(s=="-",right(angle));
);
kakudo=72°;
fig="+F--F--F--F--F";
repeat(length(fig),
turtle(fig_#,2,kakudo);
);
------------------------------------------------------------------------------------------------------
turtle(s,n,angle) という関数の中で、文字sによって前に進んだり向きを変えたりします。
fig="+F−−F−−F−−F−−F"; が亀に指令する命令文です。(−記号は2つずつです)
まず、+で72°左(反時計回り)に向き、
Fで一歩進み-- で72°右(時計回り)に2回すなわち144°向く というのを繰り返すという命令文です。
次の
repeat(length(fig),
turtle(fig_#,2,kakudo);
);
では、
length(fig) 回だけ turtle(fig_#,2,kakudo); を実行します。
lengthというのはCindyScriptの組み込み関数で、文字列 fig の文字数を数えます。fig="+F--F--F--F--F"ですから、length(fig)の結果は 14 になります。つまり、14回繰り返すことになります。
fig_# のアンダーバーは、文字列 fig の#番目の文字を取り出す演算子です。「演算子」というのは「計算」と同じようなものです。#番目の#には、repeatで繰り返すときに、CindyScriptが自動的に1から順に数字を代入していきます。この#は「実行変数」と呼ばれます。1回目は#は1に、繰り返した2回目は#は2に・・・・ となっていきます。
結果として、"+F--F--F--F--F"で表された命令「左へ72°向く」「前へ2進む」「右へ144°向く」「前へ2進む」「右へ144°向く」・・・を順に実行することになるのです。
星形がどの順に描かれたかわかりますか?亀になったつもりで追ってみましょう。
スロットの利用
さて、forwardなどの関数は、一度定義してしまえば、そのあとは変更する必要はないですね。このような、最初に1度だけ実行すればよいものは、Initialization スロットに書き、いろいろ変更する可能性のあるものを Drawスロットに書くようにすると見通しがよくなります。
次のように、スロットに分けて実行してみましょう。
さて、drawスロットに書いた repeat(length(fig), turtle(fig_#,2,kakudo);); ですが、これも定型的な処理ですから、関数化して、Initilizationスロットに入れてしまいましょう。関数名を tree とします。文字列はs、長さはn、角はangleにしました。
------------------------------------------------------------------------------------------------------
tree(s,n,angle):=(
gsave();
repeat(length(s),turtle(s_#,n,angle));
grestore();
);
------------------------------------------------------------------------------------------------------
ここで、gsave() と grestore() は先ほどありませんでした。gsave()は、現在の画面設定を保存する関数、grestore()は保存した画面設定を戻す関数です。この関数が必要な理由は後述します。
この関数を使って、drawスロットは次のように変更します。
------------------------------------------------------------------------------------------------------
kakudo=72°;
fig="+F--F--F--F--F";
tree(fig,2,kakudo);
------------------------------------------------------------------------------------------------------
ところで、この星形五角形ですが、描き終わったあと亀がどちらを向いているかわかりますか?
亀になったつもりで命令を受けてみると、最後に向きを変えたあと、 F で長さ2の線分を描いて終わっています。そのときの向きは? 線分を描いた方向ですから左下ですね。では、このあと続いて、draw([2,0],color->[1,0,0],size->3); で点を打ってみましょう。亀の向いている方向は常にx軸の正の方向と思っているわけですから、左斜め下に点が打たれるはずですが・・・
x軸上の(2,1)に点が打たれています。亀は最初の状態に戻っています。
実は、これが gsave()とgrestore() の働きなのです。
gsave() はそのときの状態を記憶します。原点の位置、軸の方向などです。
grestore() は記憶した状態に戻します。
では、ほんとうにそうなのか確かめましょう。initializationスロットに書いた、tree()関数の、gsave()とgrestore()の前に // を書いてコメント行にしてみます。コメント行なので 関数としては働きません。実行するとつぎのようになります。
亀は星形五角形を書き終わって、左下を向いたままです。そちらがx軸の正の方向と思っているわけですから、左下に赤い点を打ちました。亀にしてみれば正しい行動をしたことになりますね。