コンピューターのプログラミングというのは何か複雑な仕事をコンピューター が自動的に行うことができるように、仕事の手順の設計書を作ることです。複 雑な仕事はコンピューターがすでに知っている単純な仕事の組み合わせで表現 します。この手順をどのように表現するかという規則がプログラミング言語で す。人間の言語の文法にいろいろなものがあるのと同様、プログラミング言語にもいろいろなものがあります。
人間はあいまいさのある自然言語での対話によって相手の意志を推理し理解す る能力がありますが、コンピューターはあいまいさの無い言語しか理解するこ とができません。複雑な内容を誤解無く伝えるのは人間どうしの間でも困難で すからコンピューターが相手の場合はなおさらです。コンピューターは複雑で 大規模な仕事でも、正確に教えられさえすればきちんとこなせる能力を持って いますが、そもそも複雑なことを正確に教えるということに困難性があるわけ です。ここの問題を何とかするためにプログラミングの方法論が進歩しいろい ろなプログラミング言語が生まれました。
このテキストでは Logo というプログラミング言語を使ってプログラミングの 入門を行ないます。Logo は初めてプログラミングを勉強する子供のための やさしいプログラミング言語です。この言語はコンピューターにあまり習熟し ていなくても簡単に使ってすぐに試してみることができるように作られていま す。
我々の日常にはいろいろな道具や機械が満ち溢れていますが、残念なことに、 子供時代を除けばその内部がどうなっているのかに興味がある人や、自分も作っ てみたいと思う人は限られています。プログラミングはコンピューターの内部 の機能を理解しそこから新しい機能を持った装置を自分で作り出すことです。 子ども時代のような好奇心と科学する忍耐力や緻密さがあるかどうかをこのテ キストで判断することができるのではないかと思います。
では頑張ってみてください。
コンピューターのプログラミングに使う高級言語にはインタープリター型言語と コンパイラー型言語があります。
LISP や Mathematica のように、インタープリター型言語ではキーボードから 打ち込んだりファイルから読み込んだりしたコマンドやプログラムをその場で すぐに理解して実行することができます。たとえば,UNIX のシェルが実行し ているものもインタープリター型言語です。シェルスクリプトはシェルのプロ グラムです。インタープリター型言語ではプログラムをすばやく作成すること ができます。
C 言語や PASCAL、Java のようなコンパイラー型言語は、あらかじめ書いてお いたプログラムを徹底的に分析してマシン用言語に変換します。このとき,実 行速度を最高にするために実行手順を最適化します。このため、コンパイラー 型言語では命令をその場で一つずつ実行して試してみるということはできませ んが、実行速度は非常に速くなります。
インタープリター型言語とコンパイラー型言語は適材適所で使い分けます。ひ とつのプロジェクトのある部分にインタープリターを使い、別の部分にコンパ イラーを使うこともあります。
これから勉強する Logo はインタープリター型です。インタープリターの良い ところは、プログラムを直したらすぐに実行してチェックができる事です。ま た、プログラムを1行ずつ実行してひとつひとつの機能を確かめることができ ます。したがって、プログラムの作成がすばやく行なえますし、言語の学習も 能率良く行えます。
このテキストでは、Logo 言語のシステムとして UCB Logo という処理系を使 用することを想定しています。OS は Linux です。
UCB Logo が使えることを確かめるため、インタープリターを起動して見ましょ う。端末(kterm)で次のように打ってください。
ucblogo
起動メッセージの後に ? が表示され入力待の状態になります。ここで
bye
と打ちなさい。これで UCB Logo は終了しシェルのプロンプトに戻ります。
UCB Logo はプログラムを編集するために外部のテキストエディターを呼び出 します。これが mule や emacs のようなおなじみのエディターならよいので すが、使ったこともないようなものであると困ります。エディターとして何を 使うかは自分で設定することができますから、この設定をやっておくのが良い でしょう。それには、使いたいエディターを起動するコマンド名を EDITOR 環 境変数に設定します。
EDITOR 環境変数に何かが設定されているかどうかを調べるため、端末で、
echo $EDITOR
と打ってみてください。 もし何も表示されないか知らないエディターの名前 が表示される場合は、EDITOR 環境変数を自分で設定し直します。たとえば、 emacs を使いたい場合は、次の1行をシェルの設定ファイルに追加して書き込ん でください(tcsh シェルなどの場合)。
setenv EDITOR emacs
シェルの設定ファイルについてはオンラインテキストの「Unix の基本操作と 環境設定」(Unix-setting)を見てください。設定ファイルを修正したら、ログ インをやり直すか端末ウィンドウを新たに開いて続行します。
emacs を使う場合は、emacs の設定ファイル .emacs に次の1行を追加してお くと、プログラム編集機能が拡張されて便利になります。
(load "/usr/local/lib/logo/emacs/dot.emacs")
UCB Logo の英語マニュアルを web ブラウザーで表示するには次の URI を指 定してください(日本語マニュアルはありません)。
file:///usr/local/lib/logo/docs/html/usermanual.html
印刷形式のマニュアルを表示するには、端末で
acroread /usr/local/lib/logo/docs/usermanual.pdf
と打ちます。マニュアルを印刷すると約90ページになり、このテキストで扱う のは Logo の機能のほんの一部なので、マニュアルを印刷しておくのは無駄で す。
(1) プログラミングとはだいたいどのようなことを言いますか?
端末ウィンドウ(たとえば kterm)で作業します。まず、mkdir コマンドを使い プログラムを保存するための場所として logo というディレクトリを作り、cd コマンドでそこに移り、pwd コマンドで正しいディレクトリーにいることを確 かめなさい。作業ディレクトリーの名前をノートに書いておくこと。
念のためですが,端末ウィンドウを一旦閉じてから新しく開き,作業ディレク トリーがあることを ls コマンドで確認し,cd コマンドでそこに移り,pwd コマンドで確認してください。
OK なら
ucblogo
と打ちなさい。 ? が表示されますが、これは Logo のコマンド入力待ちのプ ロンプトです。すなわち、この状態ではキー入力は Logo へのコマンド入力に なり、Logo からの出力が表示されます。たとえば ls と打つと
I don't know how to ls
と表示されます。シェルで ls と打つとカレントディレクトリーのファイルリ ストが表示されるのは知っているでしょう。しかし、今はLogo と対話してい ます。Logo はシェルではありませんから、ls コマンドを知りません。それで、 I don't know ... という返事になったというわけ。
Logo を終了させるには bye と打ちます。試してみなさい。
なお、Logo の中でタイプミスを
再び ucblogo と打って Logo を起動しましょう。今度は Logo にもわかるコマ ンドを与えて Logo に仕事をさせてみましょう。プロンプト ? に対して
print "Hello
と打ってみなさい。"Hello" でも ''Hello でも Hello でもなく "Hello です。 print の後にスペースを入れるのを忘れないように。すぐ下に Hello と表示 されれば成功。コマンド print には省略形があり、
pr "Hello
と打っても OK です。しかし、
prant "Hello
とやるとエラーになります。prant などというコマンドを Logo は知らないか らです。
次に
pr "1+2
と打ってみなさい。さらに、
pr 1+2
と打ってみなさい。 " が付くと式がそのまま表示されますが、" が付かなけ れば式が計算されてその結果が表示されます。 "Hello とか "1+2 のようなも のは文字列リテラル(literal)と言います。 これは書いたとおりの文字の並び として扱われるデータです。 それに対して 1+2 は計算されて 3 という数値 に変換されます。文字列リテラルが何か意味を持っているかどうかについて Logo は関知しませんが、コマンドや式は Logo によって解釈が行なわれ、表 示とか計算といったなんらかの動作が行なわれるのです。
次に、
pr "Hello everyone
と打ってみなさい。Hello は表示されますが、everyone についてはどうして いいかわからないというメッセージが出ますね。空白が入ると " の効果はそ こでおしまいになり、残った everyone については Logo はどう解釈してよい か知らないのでエラーメッセージが出ます。Hello everyone と表示するには
pr "Hello\ everyone
とやります。\ は特殊な文字で、次の空白の区切り文字としての意味を失わせ る働きがあります。数式の場合は
pr 1 + 2
のように間に空白が入ってもだいじょうぶです。次に、
pr 1230000000000000
と打ってどう表示されるか見なさい。数値は全部で16桁です。また、
pr 0.0000123
と打ってみなさい。1.23e+15 と出たら 1.23 の 10 の 15 乗倍という意味で す。1.23e-5 と出たら 1.23 の 10 の -5 乗倍という意味です。非常に大きい 数や非常に小さい数はこのように表示されます。 関数電卓を使っている方は これが電卓の表示と同じなのに気付きましたか。 表示だけではなく入力する ときにも同じ表わしかたができるかどうかを調べるために
pr 1.23e+5
などと打ってみなさい。この結果表示されたものは打ち込んだデータに一致 していますか?
足し算のほかには引き算、掛け算、割り算も使えます。1-2、2*3、3/4 などと 書きます。マイナスの数には前に - を付けるだけです。 2/-3 を計算してみ なさい。
割り算の余りを計算する remainder という命令があります。たとえば
pr remainder 1995 4
とやると 1995 を 4 で割った余りが表示されます。pr コマンドを付けないで remainder 1995 4 だけだと "You don't say what to do with 3" と文句を言っ てきます。remainder コマンドは計算を行なって 3 という結果を出してはい ますが、それをどうするかを指定する必要があるのです。
remainder 10 6 + 1 という式がどう解釈されるかやってみて考えなさい。 pr コマンドを忘れないように。 (remainder 10 6) + 1 のように括弧でくくると どうなるでしょうか? remainder 10 6 + 1 は remainder 10 (6 + 1) と同じ であることを確かめなさい。 算数では、掛け算と足し算が混ざっている式を 計算する場合、足し算の前に掛け算を行うことになっているでしょう。 これ は演算の優先順位と呼んでいます。 同じ意味で、Logo では四則演算のほうが コマンドの実行(たとえば remainder コマンド)よりも優先順位が高いのです。 また、算数での規則と同じで、括弧で括るとその中の部分は外側よりも優先度 が高くなります。
べき乗は power です。power 3 2 なら 3 の 2 乗と言う意味です。remainder のときと同様、power 2 1 + 2 は power 2 (1 + 2) と同じです。 その他、計 算をする命令には次のようなものがあります。
int, round, sqrt, exp, log10, ln, sin, cos, arctan
radsin, radcos, radarctan, random
help コマンドを使えばこれらの命令の説明が表示されます。help "sin など と打ってみなさい。 " が必要なことに注意してください。help とだけ打つと 基本コマンドの長いリストが表示されます。
数値の大小関係を調べるには = と < と > を使います。 pr 123 > 122 とか pr 123 = 122 とか pr 123 > 123 とか打ってみなさい。 結果は true か false になるでしょう。 結果が true か false になる式は論理式といいます。
pr (power 2 8) > (23 * 10)
のように、比較の対象には式やコマンドの結果も使えますが、一つ一つ括弧で 括るのが無難ですし読み易いでしょう。
pr not 123 < 123
と打ってみなさい。not コマンドは条件を逆にするものです。< は「より小さ い」という意味ですが not と組み合わせると「より大きいか等しい」つまり 「以上」という意味になります。 UCBLOGO では「以上」を意味する記号はた ぶんありませんから < を not と組み合わせて使う必要があります。
2つの関係が同時に成り立っているかどうかを調べるには and コマンドを使 います。
pr and (2 > 1) (34>32)
などとやってみなさい。pr (2 > 1) and (34>32) のように and を中置きにする ことはできませんから注意してください。2つの条件の内少なくとも一方が成り 立っているかどうかを調べるには or コマンドを使います。使い方は and と同 様です。
(and (1>2) (100>99) (1000>45)) のように全体を括弧でくくると3つ以上の条 件を一つの and コマンドでテストすることができます。or も同様です。
(1) 割られる数か割る数が負の数である場合に remainder で得る余りはどの ように定義されているでしょうか? 実際にいろいろ試して調査し、規則を 見付けなさい。(p:割られる数, d:割る数, r:余り, q:商 とすると、 p = dq + r であるが、p と d が与えられたとき、他の者がどう決まるか?)
(2) power 命令では負の数による冪乗をどのように扱っていますか? 小数に よる冪乗はどうですか?実際にいろいろ試して調査し、規則を見付けなさ い。
(3) random 命令の使い方を help 命令で調べ、そのとおりになっているかどうか を何度もrandom命令を実行してテストしなさい。
(4) 200 より小さい 33 の倍数のうち最大のものを少ない手順で求めたい。計算 のしかたを計画し、自分の頭ではなく Logo を用いて実行しなさい。
Logo は画面に描画するコマンドを持っています。描画は専用のウィンドウの 中に行われます。このグラフィックスウィンドウは描画コマンドを実行すると 自動的に現れます。描画はタートル(turtle = カメ)という仮想的な物体を 動かして線を描くことによって行います。タートルは位置と向きとを保持して いてコマンドによって進めたり向きを変えたりすることができます。またター トルはペンを持っていて、画面にペンを下ろしているか画面からペンを上げて いるかという状態とペンのインクの色をコマンドで変えることができます。
はじめはペンをおろしていてインクの色はたぶん白、位置はウィンドウのまん なか、向きは上向きです。
描画ウィンドウはがあらわれたときに邪魔をしないようするため、不要なウイ ンドウは閉じてしまい、必要なウインドウはサイズと位置を調整してください。
では、UCB Logo を起動したら、まず
fd 100
と打ちなさい。描画ウィンドウの位置を調整して、他のウインドウに隠されな いようにしなさい。
fd は forward と書いてもよく、意味は前進です。 fd 100 なら 100 単位前 進します。ウィンドウの端から端までの距離は約 500 単位です。ウィンドウ の端を越えて進むと反対側の端にワープします。それを確かめるために、
fd 250
と打ってみなさい。次に
rt 90
と打ちなさい。rt は right でも同じです。これはタートルの向きを右回りに 90 度だけ変更します。次に fd 100 と打つと新しい向きに 100 単位進みます。 さらにタートルの向きを左に 90 度変えて 100 単位進ませなさい。左に 90 度なら rt -90 で OK ですが、lt 90 または left 90 という書き方もありま す。
次に
arc 360 50
と打ちなさい。これは 360 度の円弧をタートルの位置を中心として半径 50 単位で描きますが、タートルの位置や向きは変わりません。
次に pu と打ってからタートルを 100 単位進めなさい。pu は penup でも同 じでペンを上げて線を描かないようにします。再び線を描くにはペンを下げる pd コマンドを使います。pd は pendown でも同じです。何か描こうとしたと き、エラーにならなかったのに何も描かれない場合は、まずペンの状態を疑い ましょう。pendown か pd と打つと描けるようになることがあります。ペンの 色が背景と同じになっている場合も何も描かれません。この場合はたとえば
setpc 5
とやってペンの色を赤にすれば描けるようになるかもしれません。
最後に cs と打ちなさい。 cs は clearscreen でも同じで、画面を消去して タートルの位置と向きを元に戻します。画面を消去するだけなら clean、ター トルの位置と向きを元に戻すだけなら home というコマンドが使えます。
以上出てきたコマンドを十分理解するために自由にやってみなさい。既に何か 描かれているところへ別のものを描くと紛らわしい場合は cs や clear でと きどき全部消すのがよいでしょう。一つのコマンドを打ち込むたびに改行を入 れなくても、複数のコマンドを空白(スペースキー)で区切りながら 1 行に並 べて打ち込んで最後に改行を一つ入れてもだいじょうぶです。その場合はコマ ンドは基本的には左のものから順に実行されます。
(1) 次の例を参考にして、ここまでに出て来た描画用命令の一覧表を作りなさい。
fd
別名: forward
引数1: 進む距離
例: fd 100
効果: タートルを指定した距離だけ進める
備考: 進む向きはタートルの向き
cs と pd で初期状態に戻しなさい。辺の長さが 100 の正方形を描くために、 fd 100 と rt 90 を 4 回繰り返したいと思います。これでどんな正方形が描 かれるか、まず想像してから進んでください。
fd 100 rt 90 fd 100 rt 90 fd 100 rt 90 fd 100 rt 90
と打てば描けますね。同じことを繰り返すときに同じコマンドを繰り返し書く 手間を省く方法があります。cs で消去してから、次のように打ってください (括弧の種類に注意)。
repeat 4 [fd 100 rt 90]
これは括弧の中のものを 4 回繰り返して実行します。同じことを繰り返して 行なうのはプログラミング言語の重要な機能の一つです。同様にして正六角形 や正三角形などを描いて見なさい。rt に指定する値はタートルの向きを何度 変えるかであって辺と辺の間の角度ではないことに注意しなさい。
(1) 一筆描きの5角の星型と7角の星型を描きなさい。
ヒント: 最後の方向転換でタートルの向きが最初の向きに戻らなければな らない。これはすべての方向転換の角度の合計が360度の整数倍になるとい うこと。360度ではただの正5角形や正7角形になってしまうが、360度の2 倍か3倍か4倍... にすると星型になるかもしれない。
一発で正方形を描くコマンドがあればよいと思うでしょう。そういうコマンド を作ることができます。
to square
と打って改行してから
repeat 4 [fd 100 rt 90]
と打って改行し、最後に
end
と打ってください。 "square defined" と表示されれば成功です。改行を入れ ないで書くとうまくいきませんから注意してください。
cs で初期化してから square と打つと正方形が描かれます。これでやっとプロ グラミングになりました。画面に正方形を描くマシンを作ったのです。一旦マシ ンを作れば、後はその構造を思い出さなくてもマシンを動かすことができます。 どのマシンかを指定するため square という名前だけは必要になりますが。
square は正方形を描くコマンドの名前ですが、新しく定義するものですから 勝手にどんな名前を作って使ってもよろしい。sq でもよいし sikaku でもよ いのです。ただ、既にあるものとそっくり同じ名前にしようとするとエラーに なります。Logo が防御を働かせるのです。Logo では名前の大文字と小文字の 違いはありません。square と Square と SQUARE はみな同じです。これにも 注意してください。
ed "square または edit "square と打つと外部エディターが起動してその中 で square の定義が表示されます。 square の左に " を付けるのを忘れない ようにしてください。
このエディターはたぶん emacs になっていると思います(全く知らないエディ
ターが起動してしまったという場合は、このテキストの始めの方を見てエディ
ターの設定を行ってください)。 このエディター画面で square の定義を修正
して保存しエディターを終了することによって、square の機能を変更するこ
とができます。emacs では
このときの保存は修正した内容を Logo に伝える働きをしますが、プログラム をファイルとして永久保存するものではありません。永久保存は Logo の save コマンドで行います。save コマンドについては後から出て来ます。
上のようにしてエディターを起動して、square が描く正方形の辺の長さを 200 に変更し、変更がうまくいったかどうか試してみなさい。(エディターを 終了することと cs で画面を消去することを忘れないでください。)さらに、 再びエディターを起動して正方形の4つの角に小さい丸を付けましょう。fd 100 の前に arc 360 30 と付け加えればよいでしょう。
できたプログラムを保存するために、Logo のプロンプトに対して
save "nanjamonja.lgo
と打ちなさい。Logo を一旦終了させ、シェルから ls コマンドで nanjamonja.lgo というファイルがあることを確かめ、その内容を less か emacs で見てください。不必要なところや見にくいところがあれば emacs で 修正してもよろしい。それから、もう一度 Logo を起動しなさい。ここで square と打ってもエラーになります。保存したプログラムを Logo に読み込 むためには load コマンドを使います。
load "nanjamonja.lgo
と打ちなさい。それから square コマンドが実行できることを確かめなさい。
保存したプログラムを最初から読み込んだ状態で Logo を起動するには、起動 時の ucblogo コマンドにファイル名を付けます。たとえばシェルのプロンプ トに対して
ucblogo nanjamonja.lgo
と打てば OK です。この場合は通常の UNIX コマンドですからファイル名には " を付けません。 Logo をこのようにして起動すれば、load コマンドを使う 必要はありません。
なお、今はまだ一つのコマンドの定義しかありませんが、コマンドの定義はい くつでも行うことができます。 save は現在定義されているものを全部一つの ファイルに一括して保存します。
不要なコマンド定義などは Logo の中で erase コマンドを使って消去するこ ともできます。erase コマンドは erase "xxx のように使います。erase はファ イルを変更するわけではありません。ed コマンドで行なったプログラムの変 更もそのままではプログラムを保存してあるファイルには影響を与えません。 ファイルに変更点を反映させるには save コマンドを使う必要があります。
Logo に現在読み込まれているコマンド定義のリストを見るには、pots と打ち ます。pots はすべてのコマンド定義の1行目だけを表示します。どうして pots というのかはわかりません。
square で描く正方形の辺の長さを 100 ではなく 200 にするには、プログラ ムを修正して fd 100 のところの数値を変える必要があります。ところが、た とえば fd コマンドでは fd 100 とか fd 200 などと指定して、コマンドの後 に付ける数値で進む距離を変えることができます。square にも同様の機能を 加え、square 100 とか square 200 というように正方形の大きさを任意に指 定できるようにしましょう。一つのコマンドでできることにバリエーションを 持たせるわけです。ed "square で emacs を起動して square の定義を下のよ うに修正しなさい。:size は size ではだめですから気を付けてください。
to square :size
repeat 4 [ arc 360 30 fd :size rt 90 ]
end
これで、square 100 とか square 200 などと打つといろいろな大きさの正方 形が描かれるはずです。(角の丸はそのままです。)しかし今度は square と だけ打つとエラーになります。square は一つの「引数」を必要とするように 定義されたからです。定義されたとはどういうことでしょうか?
square 100 の 100 は一般にはコマンドの引数(ひきすう)といいます。コマン ド・パラメーターということもありますし、Logo では入力とか input という ことも多いようです。インプット・アウトプットのインプットですね。
square を定義するプログラムの方では、100 でも 200 でも任意の値の引数に 対応しなければならないので、具体的な数値ではなく名前で引数を表すように します。それには、まず引数に適当な名前を付けてコマンド名の後に付けます。 上の 1 行目にある :size がそれです。これで square が一つの引数を必要と することも定義されているのです。引数の名前の始めには : (コロン)を付け ることになっていますから忘れないように。2 行目以降では実際に実行する時 に与えられる引数値の代わりとしてこの引数名を使います。今の場合は fd コ マンドに :size を与えて辺の長さが引数値で決まるようにしています。ここ でも : を忘れないように。
今は引数名を :size としましたが、これはあくまでもsquare の定義の中で使 われるだけの「仮の」名前であり、これを別の名前にしてもプログラムの意味 や働きは変わりません。:size のかわりに :ookisa を使ってもよいし、:x を 使ってもよいのです。 ただし、プログラムを読むときに意味がわかりやすい 名前を考えて使うようにこころがけましょう。
(1) 上の任意の大きさの正方形を描くプログラムで :size をすべて :x と書 き換え、このプログラムが前と全く同じように動作することを確かめなさ い。
今度は、正五角形や正六角形などの任意の正多角形を描くコマンドをプログラ ムしてみましょう。辺の長さも任意に指定できるようにします。多角形は英語 で polygon ですからコマンドの名前は polygon がよいでしょう。もちろん poly でも omawari でも好きな名前にすることができます。引数は角の数と辺 の長さの 2 つで、たとえば polygon 5 100 とやると辺の長さが 100 の五角 形が描かれるようにします。
プログラムは次のようになります。これを ? プロンプトで直接入力してもよ いのですが、emacs で書く方が楽ですね。ed "polygon とやれば emacs が起 動して to polygon と end だけが自動的に書き込まれます。足りないところ を書き足して保存終了すれば OK です。
to polygon :n :s
repeat :n [
forward :s
right (360 / :n)
]
end
このプログラムの repeat :n [ のところは一行に書く必要があります。[ を 次の行にまわすと、深いわけがあってエラーになるのです。
プログラムでは、一番目の引数 :n は多角形の角数で、repeat の繰り返し回 数に使っています。さらに、ぐるりとひとまわりして元に戻るので一回あたり の方向の変化は 360 度を角数で割ったものになりますから、それを right コ マンドに与えてやって方向を変えています。2 番目の引数 :s は辺の長さで、 そのまま forward コマンドに与えています。
polygon を実行するには定義の時に決めた順番と同じ順番で引数を与えます。 始めが角数で 2 番目が辺の長さでしたね。polygon 5 100 などと打って試し てみなさい。pots で定義済みのコマンドのコマンド名と引数定義が表示され ます。pots と打ってみなさい。
なお、上のプログラムは各行の左にスペースを入れて段を付けて格好良く書い てみました。こういう書き方を字下げ、indentation といい、視覚的にめりは りのある見やすいプログラムにするために必要なテクニックです。箇条書きの 一つの項目の中がさらに箇条書きになっている場合に段を付けて書くのと同じ です。例えば一つの大学の学部や学科といった組織構成を箇条書きにしてあら わしたものを思い浮かべるとよいでしょう。
(1) 角に任意の半径の丸を付ける機能を polygon に付け加えなさい。丸の半 径は 3 番目の引数として与えることにすればよいでしょう。引数が 3 つ になっても要領は同じです。なお、丸を描くコマンド arc については既に 出て来ていますね。
(2) 3 以上の任意の奇数個の頂点と任意の長さの辺を持つ一筆描きの星を描く コマンドを作りなさい。ただし、頂角はなるべく小さくなるようにするこ と。 ヒント: 頂点数 3、5、7 の場合、頂点の外角(度)をそれぞれ、360/3、 720/5、1080/7 とすればよいことは実験でも計算でも確かめることができ る。
次にタートルの現在位置が中心となるような正多角形を描くプログラムを作り ましょう。辺の長さのかわりに中心から頂点までの距離を与えることにします。 多角形の大きさがどれくらいかがわかりやすいのでこうします。多角形の向き は、中心からタートルの向きに延長した線上に一つの頂点があるようにします。 こうすると、あらかじめタートルの向きを変えることによって多角形の向きを 変えることができます。多角形を描き終えたらタートルを元に戻すようにして おくのが気持が良いと思いますのでそうします。
始めに一つの頂点までタートルを移動させて向きを変えてから前と同じやり方 で多角形を描き、タートルの向きと位置を元に戻しておしまいです。始めにター トルを移動させるときと終わりに元に戻すときは線が描かれないようにしなけ ればなりません。それにはペンを上げたり下げたりするコマンドである pu (または penup)と pd (または pendown)を使います。プログラムは次のよう になるでしょう。むかしやった(かもしれない)図形の計算を思い出しながら プログラムをよく見てください。辺の長さを計算するのに三角関数の sin も 出て来ていますよ。
to cpolygon :n :r
forward 0
penup
forward :r
right (90 + 180 / :n)
pendown
repeat :n [
forward (2 * :r * sin (180 / :n))
right (360 / :n)
]
penup
left (90 + 180 / :n)
forward -:r
pendown
end
cpolygon 5 100 などと試してみなさい。タートルの向きや位置をあらかじめ fd コマンドや rt コマンドで変えてから cpolygon を実行すると多角形の位 置や向きが変わるはずですから、試してみなさい。ペンの色は setpc コマン ドで変えることができ、多角形の色に反映されます。0 から 15 までの整数を 使って setpc 5 などとやります。どの数がどの色になるかはやってみればわ かります。
プログラムの最後に pendown を追加したのは、タートルを元の位置に戻す時に ペンを上げたので、そのままでは cpolygon を実行した後で何も描けなくなる可 能性があるからです。
プログラムの最初に forward 0 を入れているのは、UCBLOGO のバグ対策です。 最初に penup をやる前に描画コマンドが一つも実行されていない場合にpenup が誤動作することがあるので、しかたなくこのコマンドを入れているのです。 forward 0 は全くタートルを移動させませんから何もしないのと同じですが、 バグ対策にはなるようです。バグ対策までやると、ちょっとプロフェッショナ ルな感じですね。
このプログラムの多角形の辺を描く前と後でタートルの向きを変えているとこ ろに注目してください。right (90 + 180 / :n) と left (90 + 180 / :n)で すね。まったく同じ結果になる計算を2回も行っているのは無駄ではないでしょ うか? この計算はなんとか1回だけにしたいところですね。また、repeat で 繰り返される部分では、forward (2 * :r * sin (180 / :n)) のように辺を描 くたびに辺の長さを計算していますし、向きの変化量も同様です。辺の長さと 向きの変化量は毎回同じなので、これらの計算も一回だけにしたいところです。 一度計算した結果を後で使いたいというわけですが、それには「変数」という ものを使います。変数は電卓のメモリーのようなものです。Logo では変数に 好きな名前を付けていくつでも使うことができますが、変数を使う前にある準 備が必要です。まずプログラムを見てください。
to cpolygon :n :r
local "turn1
local "turn2
local "length
make "turn1 (90 + 180 / :n)
make "turn2 (360 / :n)
make "length (2 * :r * sin (180 / :n))
penup
forward :r
right :turn1
pendown
repeat :n [
forward :length
right :turn2
]
penup
right -:turn1
forward -:r
pendown
end
turn1、turn2、および length が変数です。使う場所によって " が付いたり : が付いたりしていることに注意してください。"turn1 は turn1 という名前を意味しているのに対して、:turn1 は turn1 が表わすものの内容、つまり、変数が記憶している値を表わします。turn1 だけだと Logo はこれをコマンド名と解釈して実行しようとしますが、そういうコマンドは無いのでエラーになります。
変数を使う場合、変数名を決めなければなりませんが、すでにどこかで何かの目的に使われているものと同じ名前になると困ります。こちらで変数に何かを記憶させると関係無いところに影響してしまうかもしれないからです。これを防ぐために local コマンドを使います。local "turn1 は turn1 を外の世界 と隔離します。これで安心して変数名を考えることができます。これが変数を使うための準備です。そういえば引数名はどこかで使っている名前とかちあう心配は無いのでしょうか? 実は、Logo では引数名は自動的に内部に閉じ込められ隔離されるようになっているので、全く心配無用なのです。変数名を隔離しないで使うと一般には危険ですが、外部と情報をやりとりするためにわざと隔離しない場合もあります。
make は 変数に値を記憶させる命令です。make "turn1 (90+180/:n) は turn1 に 90+180/:n という計算の結果を記憶させています。計算の結果であって、計算式を記憶させているわけではありません。具体的には、turn1 には始めと終りにタートルを回転させる角度が入ります。turn2 には各辺を描いた後タートルを回す角度が入ります。そして length には辺の長さが入ります。
上のプログラムを入力して cpolygon コマンドを試しなさい。
cpolygon は 2 つの引数を必要とします。これはプログラムの 1 行目からわかりますね。始めの引数は多角形の角数、2 番目の引数は中心から頂点までの距離です。これはプログラムの処理内容によるので、プログラムをちょっと見てもわかりません。ですから、引数の意味についてはコマンドの機能と一緒にメモを残しておかなければなりません。期待した通りに動くコマンドができたら後はそれを使ってもっと複雑なことをすることができるわけですが、その時に前に作ったプログラムの中身をいちいち調べなければならないのではとても先へは進めないでしょう。したがってメモ書きは非常に重要です。プログラムの中にメモ書きを入れることもできますが、図も含めて自由に何でも書くことはできませんからここでは述べません。cpolygon の機能と使い方についてノートに書いてみてください。
さて、 cpolygon を使ってなにかおもしろい図形を描くプログラムを作ってみなさい。私はこんなのを作ってみました。このプログラムでは、repeat の繰り返しの中で、五角形を描くたびに色とサイズを変えています。2つの変数はそのために使っています。最後にある setpc 7 はペンの色を白に戻します。
to flower
local "color
local "size
make "color 1
make "size 15
repeat 15 [
setpc :color
cpolygon 5 :size
make "color :color + 1
make "size :size + 15
right 24
]
setpc 7
end
cpolygon のような自分で作ったコマンドも始めからあるコマンドと同じように他のコマンドの定義の中で使うことができます。しかし、自分で作ったコマンドは Save で保存しておかないと後で使えません。必要なときに Load で読み込むと使えるようになります。これが始めからあるコマンドとの違いです。 ですから、save "nanjamonja.lgo などと打って保存しておいてください。保存するファイルの名前は任意に付けられますから、いいものができたら何か違う名前で保存しておくと良いでしょう。
図を後で再表示したいのなら Logo のプログラムを保存しておいて後で必要なときにそれを実行すればよいでしょう。ここでは図そのものを保存したい場合について述べます。UCBLOGO には描いた図を保存する機能も一応はありますが、データの形式が独自のものなので、保存したものを他のプログラムで利用する わけにはいきませんし印刷も不可能です。そこで、画面表示をそのまま取り込むプログラムを使って保存します。そのような機能を持っているプログラムには xv、ImageMagic などがあります。
表示された画像を保存するには ImageMagic を使うのがおそらく最も簡単です。ImageMagic はいくつかのコマンドの集合体で、ImageMagic というコマンドはありません。 ウィンドウの画像をファイルに落すためには import コマンドを使います。 たとえば、
import flower.eps
と打った後で保存したいウィンドウの中をマウスでクリックすると、このウィンドウの表示内容が画像としてカレントディレクトリーの flower.eps というファイルに保存されます。データ形式はファイル名の拡張子部分で決まります。今の場合、拡張子部分は .eps ですね。これは Encapsulated PostScript という 形式です。これは図を文書中に挿入するための形式で、LaTeX の文書でも使えます。 うまくいったかどうかをチェックするには、
display flower.eps
などと打ってみます。display も ImageMagic の一員です。これで画像が画面に表示されれば OK です。 画像データは大きくなりがちですが、それを非常にコンパクトにできる形式として、JPEG 形式があります。 この形式でファイルに落すには
import flower.jpg
のように打ちます。 ファイル名の拡張子が jpg になります。 やはり display でテストすることができます。 JPEG 形式はファイルサイズが小さいかわりに細い線など細かい部分が不鮮明になることがありますから、テストは不可欠です。
保存した画像を加工するには display コマンドや xv コマンドを使うことができますし、他にも gimp のような強力なプログラムもあります。これらのプログラムでは、メニューとダイアログを使って各種の加工を行います。しかし、慣れるには練習が必要です。
簡単な加工はコマンドラインですませることもできます。例を一つ紹介しましょう。カレントディレクトリーに flower.eps があるとして、シェルで
convert -crop 0x0 flower.eps flower-c.eps
と打ちます。すると、flower.eps の画像中のまわりの余分な部分を切り捨てて 小さい画像を作り flower-c.eps に結果を収めます。convert コマンドのオプショ ン -crop 0x0 は自動的に余分を切り捨てることを指示するものです。ほかにも いろいろなことがコマンドラインでできますが詳細は man ページを見て下さい。
同心円を描きましょう。どのような同心円でしょうか? 中心は現在のタートルの位置です。最も外側と最も内側の円の半径は任意に与えることができるものとします。外側から2番目の円の半径は、最も外側と最も内側の円の半径の加算平均とします。加算平均とは足して2で割ったもののことです。外側から3番目のは 外側から2番目のと最も内側のとの加算平均で、後も同様です。何の役に立つのかなどとは聞かないで下さい。
外側から円を描いていくと最も内側の円との間がどんどん詰まっていきますが、いくら円を描いても最も内側の円に達することはできません。アルキメデスのウサギとカメの話しを思い出しますね。とにかく、どこかで描くのをやめなければいつまでたっても終わりません。実際には線に太さがあるので、最も内側 の円の近くは円で塗りつぶされていくら円を描いても無駄という状態になりますから、最も内側の円に線の幅の程度まで近づいたらおしまいにするとよいでしょう。
円を繰返して描くには repeat を使うことができそうですが、repeat では繰り返し回数が始めに決まっている必要があるので、あらかじめ繰り返し回数を計算しなければなりません。私は計算が苦手なのでもっと楽な方法を使います。次のプログラムを入力して下さい。
to coax :maximum :minimum
local "radius
make "radius :maximum
while [ :radius - :minimum > 1 ] [
arc 360 :radius
make "radius (:radius + :minimum) / 2
]
arc 360 :minimum
end
同心円は英語で coaxial circles です。英語がやはりかっこいいので、コマンド名は coax としました。coax の最初の引数は最も外側の円の半径で、2番目の引数は最も内側の円の半径です。とりあえず coax 200 20 などと打って試してみてください。
半径は英語で radius なので、描く円の半径を保持する変数の名前を radius にしました。 radius の最初の値は最も外側の円の半径です。 while は repeat のように繰り返し処理を行いますが、決まった回数繰返すのではなくなんらかの条件が成り立つ限り繰り返しを行うものです。while の直後の [ ] の中が繰り返しの条件です。線の太さはだいたい 1 だと思いますから、円の半径と最も内側の円の半径の差が1以下になると繰り返しをやめるようにしました。次の [ ] の中は繰り返す内容です。(始めの [ の前では改行してはいけません。)この中では円を描くことと、次に描く円の半径を計算することを行なっています。繰り返し条件の判定はまず最初に行われ OK なら繰り返しを 1回実行してから再び繰り返し条件の判定に戻ります。しかし繰り返し条件のテストにパスしなければただちに次の命令の処理に移ります。
(1) wait という命令は指定した時間だけ一時的にプログラムの実行を停止する ものです。プログラム中に wait 1 と書いておくとそこで 1/60 秒停止しま す。wait 6 なら 0.1 秒、wait 60 なら 1 秒です。つまり、時間を 1/60 秒単位で指定します。これを上の coax 命令の中で使って、円が描か れる様子を落ち着いて観察できるようにしなさい。
もし、最大円の半径 150、最小円の半径 100 の同心円の描き方、つまり coax 150 100 の実行手順がわかっているとすると、coax 200 100 を実行する には半径 200 の円を描いた後で、coax 150 100 を実行すればおしまいです。 200 と 100 の加算平均は 150 だからです。もし coax 150 100 の手順がわか らないとしても、coax 125 100 ができれば、半径 150 の円を描いてから coax 125 100 をやればよいでしょう。それもわからなければ ... と進んでい くと、coax 103.125 100、coax 101.5625 100、coax 100.78125 100 へと進ん でいきます。これぐらいになると同心円は塗りつぶしの状態になるので、もう 一番内側の円を一つ描くだけでよくなります。この描き方は自明です。この考 えをプログラムにすると次のようになります。前の coax をそのまま残してお くために、コマンド名はちょっと変えます。同心円コマンドの第2版です。
to coaxial :maximum :minimum
ifelse :maximum - :minimum > 1 [
arc 360 :maximum
coaxial (:maximum + :minimum) / 2 :minimum
] [
arc 360 :minimum
]
end
ifelse は条件が成り立つかどうかで実行するものを切り替えます。条件は ifelse のすぐ後に書いた条件式です。この条件が成り立つ場合は条件式のす ぐ次の [ ] の中を実行しその次の [ ] の中は実行しないで飛ばしますが、条 件が成り立たない場合はすぐ次の [ ] の中は飛ばしてその次の [ ] の中を実 行します。[ ] の内、始めの [ は上のような位置に書かなければだめです。 深いわけがあるのです。そういうものだとあきらめてください。ifelse は実 は if と else という英単語でできているのですが、これも暗号じみています。 そういうことよりも、プログラムの構造をおおざっぱに観察してください。
coaxial の中で、最大円の半径が最小円の半径に十分近くないときには、最大 円を描いてから自分自身である coaxial を呼び出しています。引数は元のと は違います。また、常に自分自身を呼び出すのではいつまでたっても結論が出 ませんから、ある条件が整えば自分自身を呼び出すのをやめる必要があります。 こういう方法を「再帰」と言っています。前の coax より coaxial のやりか たの方がエレガントだなあと思った人はプログラマーの素質があるのかもしれ ません。でも、素質がなくても coaxial を使うことぐらいはできますね? coaxial がうまく動くかどうか試してください。
coaxial が実際にどのように呼び出されているのかを見るには、次のように pr コマンドを入れたものを coaxial 100 90 のようにして実行してみてくだ さい。pr コマンドは4箇所に入っています。
to coaxial :maximum :minimum
(pr "Entering\ coaxial :maximum :minimum)
ifelse :maximum - :minimum > 1 [
(pr "Drawing\ a\ circle\ with\ radius :maximum)
arc 360 :maximum
coaxial (:maximum + :minimum) / 2 :minimum
] [
(pr "Drawing\ the\ smallest\ circle\ with\ radius :minimum)
arc 360 :minimum
]
wait 60
(pr "Exitting\ coaxial :maximum :minimum)
end
括弧に入った pr コマンドは複数の引数を表示するものです。コマンドと引数 を括弧に入れるのは Lisp 言語の流儀です。Logo は実は Lisp と仲が良く、 Lisp に似て「リスト」の処理が得意なのですが、ここではそういう側面には 触れません。
再帰呼び出しはコマンドの中でそのコマンド自身を呼び出すのですが、1ヶ所だ けではなく2ヶ所以上で呼び出すこともできます。こうすると連鎖反応のように 処理が増え、単純な繰り返しとは全く違うものになります。次の branch では内 部で branch 自身を 2 度呼び出すことによって木の枝分かれを真似ています。
to branch :size :level
if and (:level > 0) (:size > 0) [
forward :size
right 45
branch (:size * 0.7) (:level - 1)
right -45
left 40
branch (:size * 0.7) (:level - 1)
left -40
forward -:size
]
end
branch 100 7 と打って実行してみなさい。図がはみだすようならタートルを 少し下にずらしてから実行してください。1 番目の引数は最初の枝(幹)の長 さです。2 番目の引数は枝別れの回数ですが、これを1増やすだけで全体の枝 の数は倍に増えて処理時間も同様に増えますから注意してください。なかなか 止まらない場合は、コントロールキーを押しながらバックスラッシュ \ を何 度か押すと ? プロンプトに戻ります。
木に実を付けると次のようになります(fluits 付きなので fbranch とした)。
to fbranch :size :level
ifelse and ( :level > 0 ) ( :size > 10 ) [
forward :size
right 45
fbranch (:size * 0.7) (:level - 1)
right -45
left 40
fbranch (:size * 0.7) (:level - 1)
left -40
forward -:size
] [
arc 360 10
]
end
cs fd -100 fbranch 100 4 などと打って実行できます。丸を描く時ウィン ドウからはみ出ると、Logo が異常終了してしまうことがあるので気をつけ てください。
(1) fbranch を修正して枝の :level によって異なる色が付くようにしなさい。
setpc :level
のようなコマンドをどこかに入れるとできます。ただし forward -:size
で後戻りするところでペンを上げて移動するように変更しないと、前に塗
られた色が後から上塗りされてしまうのでうまくいきません。
上のプログラムがうまく動いたら、save "kinomidana.lgo のように打って保 存してください。ファイル名は好きなのを使ってください。次に emacs でこ のファイルを開いてその最後に
fd -100
setpc 5
fbranch 100 5
と付け加えて保存してください。それから UNIX のプロンプトに対して
ucblogo kinomidana.lgo
と打つとすぐに木が描かれるでしょう。このように、Logo のプログラムファ イルに書いたコマンドはそのまま実行されます。Logo を起動してから load コマンドでファイルを読み込む場合も同じことが起こります。コマンドの定義 だけでは実行されないことにも注意して下さい。fbranch 100 5 のようなコマ ンドの呼び出しを書く必要があるのです。
最後に次のように書いておくと,10 秒間表示した後で Logo は自動的に終了 します。
wait 600
bye
readrawline コマンドを使うと, Logo のプロンプトでリターンキーを押せば 終了するようにできます。readrawline の結果をどうするか指定しなければ エラーになるので,意味はありませんが変数に代入でもしておきます。
make "x readrawline
bye
shell コマンドで外部プログラムを動かすと,そのプログラムが終了するまで 待たせることができます。たとえば,次のようにすると,時計が表示されます。 この時計をマウス操作で終了させると,Logo が終了します。
make "x shell "xclock
bye
次のように,Xdialog を使ってボタンを表示することもできます。
make "x shell "Xdialog\ --ok-label\ 終了\ --msgbox\ \"\"\ 3\ 10
bye
線で囲まれた図形の内部をぬりつぶすには,タートルを図形の中に入れてから fill コマンドを実行します。ぬりつぶされる領域はタートルがある点を含む 同色の連続した領域です。そのため,ペンを上げてからタートルを中に入れる 必要があります。ぬりつぶしに使う色は setpc で決めます。
setxy 200 100 ;座標(200,100) まで移動
setheading 45 ;向きを北東にする (seth)
xcor ;X座標の値
ycor ;Y座標の値
heading ;方位