第5回 メッセージによる制御構造(2)(5月18日)

今日の課題

■ Smalltalk 最終回

Smalltalk のプログラミングの最終回では,流れ星を作ってみよう.主なオ ブジェクトは次のとおりである.

■ ClippingBox

下の図のように,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
※ クラスメソッドを定義するには,システムブラウザ上で,instance ? class という並びの部分の「class」を選択する.
(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 は,要求を受けるたびに,位置を返すオブジェクトである.

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 を押すごとに座標が得られる)

■ MyStarMorph

新たに次の機能を追加する.

以上の機能を実現するために,以下のインスタンス変数を追加する.

(クラス)
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.

■ 発展

次のような改良をしてみよう.

FiledPointGenerator

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つだけゆっくりと動くことになる(インスタンス変数を操作するためのメソッ ドをアクセサと言う).


2005.5.16 by tokuhisa