Hatena::ブログ(Diary)

中学生のためのSmall Basic日記

2012-01-25

キーボードイベントの利用

前々回の続きで今回はキーボードイベントを試してみましょう。

グラフィックスウィンドーでキーボードが押された時と離された時にイベントを発生させることができる。キーボードを押した歳のイベントは GraphicsWindow.KeyDown 、キーボードが離された際のイベントはGraphicsWindow.KeyUp でイベントハンドラを割り当てます。

この二つのイベントはどのように使い分けるのでしょうか?

実はGraphicsWindow.KeyDown イベントはキーボードのキーを押したときだけではなく、例えばそのキーを押しっぱなしにしていると定期的にイベントが発生します。ちょっと何の事を言っているか分かりにくいですよね。サンプルプログラムを見てください。

wWidth  = 800
wHeight = 800
cursize = 40
x = wWidth /2  - cursize /2
y = wHeight /2 - cursize /2
GraphicsWindow.Width  = wWidth
GraphicsWindow.Height = wHeight
cur = Shapes.AddEllipse(cursize,cursize)
Shapes.Move(cur, x, y)

GraphicsWindow.Show()
GraphicsWindow.KeyDown = OnKeyDown

Sub OnKeyDown
  If GraphicsWindow.LastKey = "H" Then
    x = x - 10
  ElseIf GraphicsWindow.LastKey = "L" Then
    x = x + 10
  ElseIf GraphicsWindow.LastKey = "J" Then
    y = y + 10
  ElseIf GraphicsWindow.LastKey = "K" Then
    y = y - 10
  EndIf
  Shapes.Animate(cur, x, y, 5)
EndSub

グラフィックスウィンドーが開くと真中に青い円が現れます。キーボードの"h", "j", "l", "k" を押すと青い円が上下左右に移動します。

f:id:JH_smallbasic:20120125215244p:image

プログラムを見てみましょう。GraphicsWindow.KeyDown イベントにサブルーチンOnKeyDownを割り当てているのは前々回の説明の流れなので分かると思います。サブルーチンOnKeyDown の中でGraphicsWindow.LastKeyというプロパティをチェックしています。このプロパティはもっとも最近に押されたキーを保持しています。つまり、例えばキーボードから「a」のキーを押すとこのプロパティには"A"という文字が入ります。小文字の「a」を押しても、大文字の「A」を押しても"A"という文字が入ることに気を付けてください。ついでに言うと、「Ctrl-A」を押した場合にも"A"が入ります。

ところで、Windowsの場合キーボードのあるキーを押しっぱなしにした場合、最初の文字が入力されてからほんの少し(0.5秒くらい?)だけまってからその文字が連続されて入力されますよね。「メモ帳」で試してみてください。この仕組みをキーリピートと呼んだりします。

キーを押しっぱなしにした場合、GraphicsWindow.KeyDown で発生するキーボードイベントもこの仕組みのもとで連続した複数のイベントを発生させます。

さて、キーボードイベントにはGraphicsWindow.KeyUp というキーを話した時に発生するイベントもあります。先ほどのサンプルプログラムの12行目の GraphicsWindow.KeyDown を GraphicsWindow.KeyUp に変更してみましょう。プログラムを実行するとどうなるでしょうか。先ほどと同様にキーボードの hjkl キーで青い円が移動しますが、先ほどと違い、キーを押しっぱなしにしても円は一度しか動きません。円を動かすためにはキーをポンポンと押したり離したりする度に円が移動します。GraphicsWindow.KeyUp イベントは実際にキーを話した際にだけ発生することに注意してください。

このあたりの動きがGraphicsWindow.KeyDown と GraphicsWindow.KeyUp イベントの違いです。

ところで補足です。通常の文字以外のキーが押された場合はどうなるのでしょうか。

キーLastKeyキーLastKeyキーLastKeyキーLastKey
F1〜F12"F1",..,"F12"Tab"Tab"左側Shift"LeftShift"左側Ctrl"LeftCtrl"
左側Windowsキー"LWin"Alt"System"スペース"Space"無変換"ImeNonConvert"
1〜0"D1",..,"D0"-"OemMinus"^"OemQuotes"\"Oem5"
["OemOpenBlackets"]"Oem6"+"OemPlus""Oem1"
,"OemComma"."OemPeriod"/"OemQuestion"\(_)"OemBackslash"
"Left""Right""Up""Down"
PgDown"Next"PgUp"PageUp"Home"Home"アプリケーションキー"Apps"
右側Ctrl"RightCtrl"右側Shift"RightShift"Enter"Return"Scroll Lock"Scroll"
Backspace"Back"Del"Delete"Ins"Insert"Pause"Pause"

ところで、今回のプログラムを実行して青い丸を動かし続けると画面の外に出てしまいます。青い丸が外に出ないようにするには画面の(座標の)境界を調べて、境界に青い丸が来た場合にはそれ以上移動しないようにすればよいでしょう。

wWidth  = 800
wHeight = 800
cursize = 40
x = wWidth /2  - cursize /2
y = wHeight /2 - cursize /2
GraphicsWindow.Width  = wWidth
GraphicsWindow.Height = wHeight
cur = Shapes.AddEllipse(cursize,cursize)
Shapes.Move(cur, x, y)

GraphicsWindow.Show()
GraphicsWindow.KeyDown = OnKeyDown

Sub OnKeyDown
  If GraphicsWindow.LastKey = "H" Then
    x = x - 10
    If x < 0 Then
      x = 0
    EndIf
  ElseIf GraphicsWindow.LastKey = "L" Then
    x = x + 10
    If x > wWidth - cursize then
      x = wWidth - cursize
    EndIf
  ElseIf GraphicsWindow.LastKey = "J" Then
    y = y + 10
    If y > wHeight - cursize then
      y = wHeight - cursize
    EndIf
  ElseIf GraphicsWindow.LastKey = "K" Then
    y = y - 10
    If y < 0 then
      y = 0
    EndIf
  EndIf
  Shapes.Animate(cur, x, y, 5)
EndSub

2012-01-22

Shapesの利用とアニメーション

Small Basic のグラフィックスにはShapesと呼ばれる面白い機能があります。グラフィックスウィンドーに例えばGraphicsWindow.DrawEllipse() やGraphicsWindow.DrawRectangle()等で描かれた円や長方形は一旦グラフィックスウィンドーに描かれると移動させたりサイズを変えたりといったことはできません。スケッチブックに色鉛筆で絵を描いてしまったようなものですね。

でも例えばゲームをプログラミングしようと思うとそういうグラフィックスを移動させたりしたくなってきますよね。疑似的に動いているように見せることはできます。どうするかと言うと、要は描いたグラフィックスを一旦消して、少し移動した場所に描きなおせばいいのです。例えば下の例で、太陽のマークのグラフィックスを右に移動させたいとします。

f:id:JH_smallbasic:20120122225614p:image

まずは、最初の位置の太陽を消すように背景色で塗りつぶします。黒い枠で囲ったあたりを背景色で塗りつぶせばよいですよね。

f:id:JH_smallbasic:20120122225613p:image

次に移動したい場所に太陽のグラフィックスを描画します。

f:id:JH_smallbasic:20120122225612p:image

これをくりかええばあたかも太陽のグラフィックスが左から右へ移動しているように見えるでしょう。ちなみに離れた位置に再描画すれば素早く、近い位置に再描画すればゆっくりと移動したように見えるでしょう。

では、この仕組みをつかってサンプルプログラムを作ってみました。ちょっと長いですが簡単なゲームになっています。

GraphicsWindow.Width = 800
GraphicsWindow.Height = 800
GraphicsWindow.BackgroundColor = "Black"
cx = 400
cy = 400
earthR=2000
altitude = 400
ground = 600
G = 0.05
speed = 0.0

GraphicsWindow.Show()
DrawStars()
GraphicsWindow.BrushColor = "Blue"
GraphicsWindow.FillEllipse(cx-earthR, ground, earthR*2, earthR*2)
GraphicsWindow.PenColor = "Black"

GraphicsWindow.MouseDown = OnMouseDown

loop:
  GraphicsWindow.BrushColor = "Black"
  GraphicsWindow.FillRectangle(cx-10, ground - altitude, 20, 20)
  If altitude <= 0 Then
    If speed < 2 then
      GraphicsWindow.ShowMessage("無事に着地", "成功")
    else
      GraphicsWindow.ShowMessage("地面に激突", "失敗")
    endif
    Program.End()
  EndIf
  speed = speed + G
  altitude = altitude - speed
  GraphicsWindow.BrushColor = "White"
  GraphicsWindow.FillRectangle(cx-10, ground - altitude, 20, 20)
  Program.Delay(33)
Goto loop

Sub OnMouseDown
  speed = speed - 1.0
EndSub

Sub DrawStars
  For I=0 To 4000
    GraphicsWindow.SetPixel(Math.GetRandomNumber(800), Math.GetRandomNumber(800), GraphicsWindow.GetRandomColor())
  EndFor
EndSub

このゲームはロケットの着陸船を無事に着陸させるゲームです。そのまま放っておくと重力に引かれて次第に落下し、地面に激突してしまいます。ですので、マウスをクリックして着陸ロケットを噴射し、降下速度を落として着陸させる必要があります。まぁ、着陸船は単なる白い正方形だし、もっと凝った作りにもできるかもしれませんが、サンプルなどのこの程度で許してください。

理科で「速度と加速度」や「重力加速度」を習っていれば内容を理解しやすいかもしれませんが、そうでない場合はまぁ、色々計算している部分は適当に流し読みしてしまいましょう。

f:id:JH_smallbasic:20120122231612p:image

着陸船の移動ですが、(1)の箇所でブラシの色を背景の黒にして正方形を描いていますが、要はここと次の行(2)で以前に書いた白色の着陸船を消しているのです。(3)の箇所で着陸船の色である白にブラシを選択し、(4)で着陸船を描画しています。これを繰り返すことによりまるでアニメーションのように着陸船が移動しているように見えるのです。

でも、あれ?よく見てみると、着陸船が通った後、背景にあった星が消えて真っ黒になってしまっていますね。これは、当たり前と言えば当たり前で、着陸船を移動する際に単純に黒い正方形で塗りつぶしているからです。このように背景のグラフィックが消えてしまう事を防ぐテクニックももちろんあります。要は着陸船を描画する前に、そこに描画されていたグラフィックスをどこかに記憶しておいて、着陸船を消す代わりに記憶しておいたグラフィックスをつかって描画しなおしてやればよいのです。

でも、なんだか面倒だよね。

Shapesの利用

Small Basicにはこのような場面で利用できるとても便利な機能があります。それが、Shapesです。Shapes は単純な図形を格納する変数のようなものです。Shapesに図形を格納し、そのShapesをグラフィックスウィンドーの好きな位置に移動させたり拡大させたり透明度を変えたりすることができます。ちょうど、スケッチブックにシールを張るようなものです。シールをはがして別の場所に移動させれば、そのシールに描かれた図形は移動するし、スケッチブックのシールが貼ってあった箇所の図形もそのまま残っているはずです。まぁ、シールの糊がそれほど強力でなければね。

f:id:JH_smallbasic:20120122234431p:image

Shapesに例えば矩形(長方形)を登録するには、次のようにします。

lander = Shapes.AddRectangle(20,20)

これは、幅20、高さ20の長方形(つまり正方形)をShapesに追加し、これを"lander"という変数に格納しています。別の言い方をすると幅高さそれぞれ20の正方形をShapesとして登録し、それに"lander"という名前を付けているともいえます。

この"lander"という名前の正方形を移動するには次のようにします。

Shapes.Move(lander, x, y)

"lander"はもちろん今回作成したshapesの名前で、あとのxとyはこのShapesを移動するグラフィックスウィンドー上の座標です。

では、このShapesを使って先ほどの着陸ゲームを作り直してみましょう。

GraphicsWindow.Width = 800
GraphicsWindow.Height = 800
GraphicsWindow.BackgroundColor = "Black"
cx = 400
cy = 400
earthR=2000
altitude = 400
ground = 600
G = 0.05
speed = 0.0

GraphicsWindow.Show()
DrawStars()
GraphicsWindow.BrushColor = "Blue"
GraphicsWindow.FillEllipse(cx-earthR, ground, earthR*2, earthR*2)
GraphicsWindow.PenColor = "Black"
GraphicsWindow.BrushColor = "White"
lander = Shapes.AddRectangle(20,20)
GraphicsWindow.MouseDown = OnMouseDown

loop:
  Shapes.Move(lander, cx-10, ground - altitude) 
  If altitude <= 0 Then
    If speed < 2 then
      GraphicsWindow.ShowMessage("無事に着地", "成功")
    else
      GraphicsWindow.ShowMessage("地面に激突", "失敗")
    endif
    Program.End()
  EndIf
  speed = speed + G
  altitude = altitude - speed
  Program.Delay(33)
Goto loop

Sub OnMouseDown
  speed = speed - 1.0
EndSub

Sub DrawStars
  For I=0 To 4000
    GraphicsWindow.SetPixel(Math.GetRandomNumber(800), Math.GetRandomNumber(800), GraphicsWindow.GetRandomColor())
  EndFor
EndSub

ちょっとだけすっきりとしたかな?実際に動かしてみると分かりますが、着陸船が移動しても背景の星々は消えることはありません。

さてこのゲームですが、アイディア次第で色々と改造できると思います。例えば燃料の残量の概念を導入して着陸ロケットの噴射回数、つまりマウスをクリックできる回数を制限してみたり、時間制限を導入してみたり、等など。

試してみるといいでしょう。

Shapesにはもちろん長方形以外の図形も登録できます。工夫してみてください。

2012-01-15

イベント発生時のコールバックとしてのサブルーチンの利用

タイトルを見るとオドロオドロシイ感じがするかもしれませんが、まずはサンプルプログラムを見てみましょう。

GraphicsWindow.MouseDown = OnMouseDown                         <=== (1)

Sub OnMouseDown                                                <=== (2)
  x = GraphicsWindow.MouseX
  y = GraphicsWindow.MouseY
  GraphicsWindow.PenColor = GraphicsWindow.GetRandomColor()
  GraphicsWindow.DrawEllipse(x,y,20,20)
EndSub

実行したら、グラフィックウィンドーが開くのでマウスを左クリックしてみよう。

f:id:JH_smallbasic:20120115000040p:image

プログラム自体はとてもシンプルで、メインの処理が一行(1)と、サブルーチンの定義(2)があるだけです。しかも、(1)ではサブルーチンの名前をGraphicsWindow.MouseDown にセットしているだけで、このサブルーチン自体を呼び出してはいません。ちなみに前回、サブルーチンの定義を行っている部分(Sub からEndSubまで)はメインの処理の流れのなかでは無視されることを説明しました。しかし、このプログラムを実行すると明らかにサブルーチンOnMouseDownの処理が何度か、しかもマウスボタンをクリックした際に実行されることがわかると思います。しかし、このサブルーチンOnMouseDownを呼び出してる箇所は実際にはどこにも見当たらないことに気づくと思います。

Small Basicプログラムは書かれた順に実行されます。プログラムの流れを指で辿るように追ってゆくことができます。しかし例外があります。なにか特別な状況が発生した際にはプログラムの通常の流れとは外れたところで処理の流れに割り込みをかけて、その状況のための特別な処理を実行することができます。このような「特別な状況」のことを「イベント」と呼び、イベントを処理するプログラムの部分を「イベントハンドラ」と呼びます。ハンドラ、とは「処理する者」位の意味です。

あまりいい例えではないかもしれませんが、テレビの番組を放送中に重大事件が発生すると番組をいったん中断し、その事件についての報道を行った後もう一度番組に戻ってくることがありますよね。この場合、重大事件がイベント、重大事件の報道イベントハンドラに相当します。重大事件はいつ発生するか分からず予定の立っているものではありません。また、重大事件の報道が終わると、通常の番組の中断された箇所に戻り番組が再開されるでしょう。

イベントとイベントハンドラの関係もそれに似ています。

でも、イベントとは具体的にどんなものなのでしょうか? Small Basic では幾つものイベントが提供されています。例えば、マウスのボタンが押された時、キーボードからキーが押された時等などにイベントが発生します。イベントハンドラはサブルーチンの形で定義します。そして、イベントハンドラ(サブルーチン)をイベントに代入することでイベントとイベントハンドラを関係づけることができます。

先ほどのサンプルプログラムの場合、(1)がイベントとイベントハンドラを関連付けている箇所になります。GraphicsWindow.MouseDown はマウスボタンが押された瞬間に発生するイベントです。ちなみに、GraphicsWindow.MouseUpというイベントもあり、こちらはマウスボタンが離された瞬間に発生します。

ところでイベントに対してイベントハンドラを複数関連付けた場合どうなるのでしょうか?

GraphicsWindow.MouseDown = OnMouseDown1                               <==== (1)
GraphicsWindow.MouseDown = OnMouseDown2                               <==== (2)

Sub OnMouseDown1
  GraphicsWindow.PenColor=GraphicsWindow.GetRandomColor()
  GraphicsWindow.BrushColor=GraphicsWindow.GetRandomColor()
  GraphicsWindow.FillEllipse(GraphicsWindow.MouseX, GraphicsWindow.MouseY,50,50)
EndSub
  
Sub OnMouseDown2
  GraphicsWindow.PenColor=GraphicsWindow.GetRandomColor()
  GraphicsWindow.BrushColor=GraphicsWindow.GetRandomColor()
  GraphicsWindow.FillRectangle(GraphicsWindow.MouseX, GraphicsWindow.MouseY,20,20)
EndSub

(1)と(2)でGraphicsWindow.MouseDownというイベントに対し、イベントハンドラが二回定義されています。実行すると分かりますが、イベントに対し複数回イベントハンドラが関連付けられた場合、最後に関連付けられたイベントハンドラが有効になります。この場合、(2)で関連付けられたサブルーチン、OnMouseDown2ですね。

その他のイベント

他にどのようなイベントがあるのでしょうか?ここに列挙したものはSmall Basic で提供される全てではありませんが、以下に主なイベントをリストしてみました。

マウスイベントのイベント
GraphicsWindow.MouseDown
マウスボタンが押されたときに発生するイベント
GraphicsWindow.MouseUp
マウスボタンが離されたときに発生するイベント
GraphicsWindow.MouseMove
マウスカーソルが移動した際に発生するイベント
グラフィックスウィンドーでのキーボードのイベント
GraphicsWindow.KeyDown
グラフィックスウィンドーでキーボードのキーが押されたときに発生するイベント
GraphicsWindow.KeyUp
グラフィックスウィンドーでキーボードのキーが離されたときに発生するイベント
グラフイックスウィンドーでテキストを入力した際のイベント
GraphicsWindow.TextInput
グラフィックスウィンドーでテキストが入力されたときに発生するイベント
タイマーイベント
Timer.Tick
前もってTimer.Interval に対して定義された時間間隔(ミリセカンド[1/1000秒]単位で指定)ごとに発生するイベント

マウスイベントを利用して絵を描いてみる

さて、では試しにマウスイベントを利用して絵を描く簡単なプログラムを作ってみましょう。まずは、どういう方針で作るか考えてみます。例えば…マウスカーソルが動いたときにその座標に点を打ってみることにします。とは言っても、マウスカーソルが動いた場合常に点を打つのではなく、やはり例えば左ボタンが押されている間だけ描画したいので、Mouse.IsLeftButtonDown というプロパティを使ってみます。

GraphicsWindow.MouseMove = OnMouseMove

Sub OnMouseMove
  If Mouse.IsLeftButtonDown Then
    GraphicsWindow.SetPixel(GraphicsWindow.MouseX, GraphicsWindow.MouseY, "Black")
  EndIf
EndSub

Mouse.IsLeftButtonDown はマウスの左ボタンが押されているかどうかを調べるためのプロパティです。"Is Left Button Down ?(左ボタンが押下されている?"位の意味ですね。試しに実行してみましょう。

f:id:JH_smallbasic:20120115232304p:image

うーん...正直微妙。

練習問題として試しに作ってみるとよいでしょう。ヒントを幾つか出しておきます。マウスが移動する直前の座標を記憶しておいて、マウス移動後現在の座標と記憶しておいた座標の間で線を引くとそれらしくなります。こんな感じ。

f:id:JH_smallbasic:20120115232801p:image

例を置いておきますが、一応隠しておきますね。

続きを読む

2012-01-09

サブルーチン

少し長めのプログラムを作っていると、同じような手続きの繰り返しがそこかしこに出てくることがよくあります。このような場合、「サブルーチン」を使うと便利です。サブルーチンは手続きに名前をつけたもので、あたかも新たなSmall Basicの命令が作られたようなイメージで使う事ができます。

例を見てみましょう。このサンプルプログラム自体はあまり意味のあることをやっているわけではありません。

debug=1

GraphicsWindow.Height=600
GraphicsWindow.Width=600
GraphicsWindow.BackgroundColor="darkgreen"
GraphicsWindow.Show()
GraphicsWindow.PenColor="white"

For x=0 to 600 step 40
  For y=0 To 600 Step 40
    DebugOutput()                                   <=== (1)
    GraphicsWindow.DrawRectangle(x,y,30,30)
    y=y+2
    DebugOutput()                                   <=== (2)
  EndFor
  x=x+2
EndFor

Sub DebugOutput                                     <=== (3)
  If debug = 1 Then
    TextWindow.Write("DBG: Time: "+Clock.Time+" ")
    TextWindow.WriteLine("x="+x+", y="+y)
  EndIf
EndSub                                              <=== (4)

(3) の行で新しいキーワード"Sub"を使い、「Sub DebugOutput」という名前が定義されています。この(3)から(4)の"EndSub"まででサブルーチン "DebugOutput"を定義しています。このサブルーチンでやっていることは単純なので見ればわかると思いますが、変数 debug というのが1だった場合に、その時の時刻と変数x,yの値をテキストウィンドーに表示しています。

この手続き(サブルーチン)を実際に使っているのが(1)と(2)の行です。(1)の行が実行されると、Goto 文と似たように処理がDebugOutput() が定義されている(3)に飛びます。そして、Sub とEndSub で囲まれた(3)〜(4)の手続きが実行され、次にこの手続きを呼び出した(1)の行に自動的に戻ってきて次の行の実行を行います。ここがGoto文とサブルーチンの違いで、またサブルーチンの便利なところです。Goto文で同じことをしようとしても、帰り先をラベルで明示的に指定してやらなければいけませんし、また帰り先を呼び出しの度に変更するという事もできません。

f:id:JH_smallbasic:20120109113319p:image

もちろんここに(3)と(4)に挟まれた手続きを(1)や(2)の位置にコピー&ペーストでタラタラと書き流してもよいのだけど、それだと更に同じ処理が追加で何か所にも出てくると面倒になってくると思います。例えば、この例では日付は表示せずに時刻だけを表示していましたが、何かの理由で日付も表示したくなったとします。100個所にコピー&ペーストしていた場合、100個所を修正しなくてはいけないし、たぶんそのうちに修正漏れやミスが発生すると思います。サブルーチンを使った場合、たった一か所だけを修正するだけですみます。

ところで試しに上の例の(1)と(2)の行を取り去って、プログラムの最後に一行追加してみましょう。

debug=1

GraphicsWindow.Height=600
GraphicsWindow.Width=600
GraphicsWindow.BackgroundColor="darkgreen"
GraphicsWindow.Show()
GraphicsWindow.PenColor="white"

For x=0 to 600 step 40
  For y=0 To 600 Step 40
    GraphicsWindow.DrawRectangle(x,y,30,30)
    y=y+2
  EndFor
  x=x+2
EndFor
                                                    <=== (A)
Sub DebugOutput                                     <=== (3)
  If debug = 1 Then
    TextWindow.Write("DBG: Time: "+Clock.Time+" ")
    TextWindow.WriteLine("x="+x+", y="+y)
  EndIf
EndSub                                              <=== (4)

GraphicsWindow.ShowMessage("hi", "hi")              <=== (B)

この時、サブルーチンDebugOutoutを呼び出している個所はどこにもありません。ですので、プログラムが実行されてもテキストウィンドーは開かれず、なにもテキストは表示されません。また、プログラムは(B)を実行しメッセージボックスを表示します。

なにを言いたいかというと、プログラムの処理が(A)の位置に達しても(3)から(4)の間のサブルーチンの行はあたかもなにもなかったように無視され、そのまま(B)の処理へ移るという事です。この動きもGoto文とは違いますね。

プログラムを見やすくするためにサブルーチンを使う

さて、前節では同じ手続きが何か所も実行される場合に手続きに名前を付けてプログラムを簡略化するためにサブルーチンを使う方法を紹介しました。しかし、実際には一回しか実行されないような処理にもサブルーチンが使われることが多くあります。これは、まとまった手続きに名前をつけることにより全体のプログラムの流れを把握しやすくするために役立ちます。もう一度先ほどのサンプルプログラムを見てみましょう。

debug=1

GraphicsWindow.Height=600                        <=== (1)
GraphicsWindow.Width=600                         <=== (2)
GraphicsWindow.BackgroundColor="darkgreen"       <=== (3)
GraphicsWindow.Show()                            <=== (4)
GraphicsWindow.PenColor="white"                  <=== (5)

For x=0 to 600 step 40
  For y=0 To 600 Step 40
    DebugOutput()
    GraphicsWindow.DrawRectangle(x,y,30,30)
    y=y+2
    DebugOutput()
  EndFor
  x=x+2
EndFor

Sub DebugOutput
  If debug = 1 Then
    TextWindow.Write("DBG: Time: "+Clock.Time+" ")
    TextWindow.WriteLine("x="+x+", y="+y)
  EndIf
EndSub

Small Basicでグラフィックスプログラミングをやっていくと、このサンプルの(1)〜(5)の処理、つまりグラフィックスウィンドーの体裁を定義するという処理は何度も出てくるパターンのようなものだという事に気づくと思います。

これを、次のように変えてみましょう。

debug=1

InitGraphicsScreen()                 <==== (1)
DrawRectangles()                     <==== (2)

'=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

Sub InitGraphicsScreen               <==== (3)
  GraphicsWindow.Height=600
  GraphicsWindow.Width=600
  GraphicsWindow.BackgroundColor="darkgreen"
  GraphicsWindow.Show()
  GraphicsWindow.PenColor="white"
EndSub

Sub DrawRectangles                    <==== (4)
  For x=0 to 600 step 40
    For y=0 To 600 Step 40
      DebugOutput()
      GraphicsWindow.DrawRectangle(x,y,30,30)
      y=y+2
      DebugOutput()
    EndFor
    x=x+2
  EndFor
EndSub

Sub DebugOutput
  If debug = 1 Then
    TextWindow.Write("DBG: Time: "+Clock.Time+" ")
    TextWindow.WriteLine("x="+x+", y="+y)
  EndIf
EndSub  

サブルーチンInitGraphicsScreenは画面の初期化を行うだけなので最初に一回だけ実行されます。何度も実行される処理ではないでしょう。また、サブルーチンDrawRectanglesも同様です。でも、敢えてサブルーチン化しているます。このプログラム、実質的には手続きは(1)と(2)の二つの行で定義されていることがわかるでしょうか。このプログラムでやりたかったことのアウトラインは、

  1. 画面を初期化し、
  2. 長方形(正方形)をたくさん描画する

という二つの事だけです。この二つをどう具体的に行うかは、別途考えてもよいことなのです。ある意味、ここではそれぞれの処理を「抽象化」したとも言えます。大きなプログラムを作る場合、処理の大まかな流れを最初に考えて、そのあとディテールを考えていくというトップダウンのやり方をを取るほうがプログラムの設計がしやすいことが多いのです。

例えば50階建のビルの設計をするとして、たぶん最初に行うのは大体の外観をデザインして、それから少しづつ詳細の設計に入っていくと思います。最初からトイレの電気の配線の設計の様なディテールを考える建築士はいないはずです。

また、この事にはもう一つの面があって、処理が抽象化されているので後からそのプログラムを見た人が全体の流れを把握しやすいという事が利点があります。プログラムを見る人は最初に大雑把な流れをつかんでからディテールを見たいでしょう。「ふむふむ、最初に画面を初期化して、その後に長方形を描画するのだな。では、画面の初期化は具体的には、ふむふむこうやっているのか…」と言った感じ。

ところでちょっと補足しておくと、ここで言っている「後からプログラムを見る人」とは別に赤の他人だけとは限りません。「未来の自分」かもしれません。例えばプログラムを作ってから1年もたってからそのプログラムを見直す必要ができたとき、プログラムの詳細まで覚えている人はどれだけいるでしょう。未来の自分は十分に赤の他人と言ってもいいのです。

まとめ

ここではサブルーチンを使う目的として次の二つを紹介しました

  1. 同じような手続きに名前をつけ、ひとまとまりの処理として扱う
  2. 手続きを抽象化プログラムを見やすくする

実際にはその他の目的でもサブルーチンが使われることがあります。次回は「イベント発生時のコールバック」としての使い方を紹介します。今はなんのこっちゃ分からないと思うけど、心配しないください。

ところでSmall Basicの他のプログラミング言語でもサブルーチンやそれに似た言語機構を持っていて、それらは関数だとかメソッドだとか別の名前で呼ばれることがあります。それらについてはいずれ機会があれば説明したいと思います。

2012-01-08

乱数について

前回のモンテカルロ法について少し補足です。

改めてリファレンスマニュアルを読むと、Small BasicのMath.GetRandumNumber()は少し気をつけて使わないといけないようです。

Math.GetRandomNumber(maxNumber)

maxNumber: 要求された乱数値の最大値。
戻り値:    指定された最大値よりも小さいか等しい乱数。

つまり、例えば引数として100を渡した場合、1〜100までの乱数整数で返す、という仕様のようです。これは乱数としてはモンテカルロ法のために使うには少し性能面で問題がありそうです。気をつけて使わないといけません。