Smalltalk のプログラミングの最終回では,流れ星を作ってみよう.主なオ ブジェクトは次のとおりである.
下の図のように,Squeak の Window 上に ClippingBox という長方形の領 域を設定する.長方形は,左上の点と右下の点により定義する.
インスタンスメソッドとして,長方形の定義をするメソッド (lx:ly:rx:ry:),位置(Point のインスタンス)がClippingBox の内部に位置 する場合に true を返すメソッド(isIn:)を作る.クラスメソッドとして,イ ンスタンスの生成と長方形の定義を同時に行うメソッド(lx:ly:rx:ry:)を作る.
インスタンス変数としては,長方形の2点の座標(lx,ly,rx,ry)が必要で ある.
(クラス) Object subclass: #ClippingBox instanceVariableNames: 'lx ly rx ry ' (インスタンスメソッド1) lx: x1 ly: y1 rx: x2 ry: y2 x1 < x2 ifTrue: [lx := x1. rx := x2] ifFalse: [lx := x2. rx := x1]. y1 < y2 ifTrue: [ly := y1. ry := y2] ifFalse: [ly := y2. ry := y1] (インスタンスメソッド2) isIn: pnt (lx > pnt x or: rx < pnt x or: ly > pnt y or: ry < pnt y) ifTrue: [^ false] ^ true (クラスメソッド1) lx: x1 ly: y1 rx: x2 ry: y2 ^ self new lx: x1 ly: y1 rx: x2 ry: y2 |
(Workspace上での動作確認) c1 := ClippingBox new lx: 100 ly: 100 rx: 200 ry: 200 (Alt-dを押す) c1 isIn: 110@120 (Alt-p を押すと true が表示される) c2 := ClippingBox lx: 100 ly: 100 rx: 200 ry: 200 (Alt-dを押す) c2 isIn: 90@120 (Alt-p を押すと false が表示される) |
PointGenerator は,要求を受けるたびに,位置を返すオブジェクトである.
PointGenerator として必須の機能は,next メッセージを受け付けて, Point のインスタンスを返すことである.この性質を定義するために, PointGeneratorには,抽象メソッドとして next を定義する.また,初期位置 から提供するメソッド reset も同様である.
(クラス) Object subclass: #PointGenerator (インスタンスメソッド1) next self subclassResponsibility (インスタンスメソッド2) reset self subclassResponsibility |
具体的に,位置の系列を返すために,配列を使用する方法がある.next の要求のたびに,配列から1つずつ位置をとりだして,答えるという方法をと る.配列を使用する PointGenerator のことを,ArrayedPointGenerator とし て実装する.
ArrayedPointGenerator は,インスタンス変数として,配列変数 pnts, 参照位置 t を使用する.
(クラス) PointGenerator subclass: #ArrayedPointGenerator instanceVariableNames: 't pnts' (インスタンスメソッド1) next | pre | pre := t. t := t + 1. t > pnts size ifTrue: [t := 1]. ^ pnts at: pre (インスタンスメソッド2) reset t := 1 (インスタンスメソッド3) setArray: a t := 1. pnts := a |
(Workspace上での動作確認) a := Array new: 3. a at: 1 put: 100@100. a at: 2 put: 110@150. a at: 3 put: 120@200. g := ArrayedPointGenerator new. g setArray: a. g next (Alt-p を押すごとに座標が得られる) |
新たに次の機能を追加する.
以上の機能を実現するために,以下のインスタンス変数を追加する.
(クラス) StarMorph subclass: #MyStarMorph instanceVariableNames: 'fout clipping meteorFlag returnFlag ' (インスタンスメソッド1) init returnFlag := true (インスタンスメソッド2) setClipping: c clipping := c (インスタンスメソッド3) exmode: b returnFlag := b (インスタンスメソッド4) stop fout = nil ifFalse: [fout close. fout := nil] ifTrue: [ meteorFlag := false ] (インスタンスメソッド5) position: pos ? ? 前回までのとおり ? ? ? clipping = nil ifFalse: [(clipping isIn: pos) ifFalse: [Exception new signal: 'OutOfClipping']]. super position: pos (インスタンスメソッド5) meteor: gen meteorFlag := true. [[ meteorFlag ] whileTrue: [(Delay forMilliseconds: 100) wait. [self position: gen next] on: Exception do: [: ex | ex messageText = 'OutOfClipping' ifTrue: [returnFlag ifTrue: [gen reset. ex resume] ifFalse: [meteorFlag := false]]]]. nil] fork |
以上を入力したら,実行する前に,ワールドを保存し ておこう. fork を使用しているので,思わぬ不具合が発生すること がよくある.復帰が難しいので,ウインドウを閉じるしかなくなることがある.
以下の手順で動作する.
(ワークスペース) a := Array new: 10 a at: 1 put: 100@100. a at: 2 put: 103@112. a at: 3 put: 106@124. a at: 4 put: 109@136. a at: 5 put: 112@148. a at: 6 put: 115@160. a at: 7 put: 118@172. a at: 8 put: 121@184. a at: 9 put: 124@196. a at: 10 put: 127@208. g := ArrayedPointGenerator new. g setArray: a. c := ClippingBox lx: 90 ly: 90 rx: 200: ry: 250. s1 := MyStarMorph new. s1 init; openInWorld; setClipping: c; meteor: g. s2 := MyStarMorph new. s2 init; openInWorld; setClipping: c; meteor: g. (止めるとき) s1 stop. s2 stop. (再開するとき) s1 meteor: g. s2 meteor: g. |
次のような改良をしてみよう.
ArrayedPointGenerator のサブクラスとして,FiledPointGenerator を作 る.FiledPointGenerator は,前回の宿題で作成した位置ファイルを読み込み, Array に格納し,ArrayedPointGenerator と同一の動作をする.
MyStarMorph の meteor メソッドに、Delay forMilliseconds: 100 とい う部分がある.ここの 100 という数字を大きくすると,星の動きはゆっくり になる.そこで,インスタンス変数で,ここの数字を表すことにして,その変 数値をあるメソッドから操作できるようにする.
たとえば,instanceVariableNames に wtime を追加し,Delay forMilliseconds: wtime とする.新たなメソッド setWait: t において, wtime := t を実行するように定義する.また,メソッド init において, wtime := 100 という初期化を追加する.実行時に s1 setWait: 300 とすると, 1つだけゆっくりと動くことになる(インスタンス変数を操作するためのメソッ ドをアクセサと言う).