Pharo6.1のBlocで落ちゲーを作ってみた

# はじめに

Pharo では UIフレームワークがどんどん出てきています。「SmalltalkといえばMVCなのに最近のUIは乱れておる」などと言う人たちは横においても、やっぱり新しいUIフレームワークを追いかけるのはしんどいです。しんどい時に事務処理的なコードを書くのはつらいので、楽しく落ちゲーを作ってお勉強してみました。
しょせんオレが楽しく作れる程度のものなのでたいしたことはないです。某宝石が3つずつ上から落ちてきて、タテヨコナナメのどれかで3つ以上揃えば消える、そして連鎖する、例のあの落ちゲー風の、シンプルなやつです。結局、クラス数5個ぽっきりでできちゃいました。てへぺろ。
名前は、某宝石の落ちゲーのパッケージに書いてあったセリフにインスパイアされて、Starfall Night（星降る夜）という名前にしました。

<img width="656" alt="Starfalling Night 画面" src="https://qiita-image-store.s3.amazonaws.com/0/59918/1efbc703-9d1f-3058-183d-86879c2ec280.png">


## インストール

ソースはgithubに転がしてあります。BaselineOfも今のところありません。手抜きでごめんね。（っていうか、BaselineOf書いて置いてみたんだけど、Metacelloから読み込めない。誰かぼすけて。）
https://github.com/tomooda/StarfallNight/tree/master/repository/StarfallNight
これをシェル上でgit cloneしてきて

```
git clone https://github.com/tomooda/StarfallNight.git
```
そして Pharo 6.1上で読み込みます。が、まずはBlocをインストールする必要があります。本家のREADMEにあるようにPlayground上で以下のコード片をDo itしてください。（というか、Do itよりも、Inspect itすると終了したことがわかりやすいのでオススメです）

```smalltalk
Metacello new
   baseline: 'Bloc';
   repository: 'github://pharo-graphics/Bloc:pharo6.1/src';
   load: #core
```

次に、StarfallNightを読み込みます。Monticello Browserを開いてください。「+Repository」ボタンを押すとRepository typeを訊かれるので、tonel://を選んでください。すると取り込んでくる元のディレクトリを選択するよう求められるので、さきほどgit cloneしたディレクトリのStarfallNight/repository ディレクトリを選択してください。すると、たぶん、ちゃんと読めるはずです。

とりあえず、Playgroundで

```smalltalk
StarfallNightModel example inspect
```
を実行してください。するとインスペクタが開きますが、それがゲーム画面になっています。
何かキーを押すと星降る夜が始まります。左右で宝石を移動して、上で宝石のローテーション、下で宝石を落とします。あとはまあ、わかりますね？例のあの落ちゲーのシンプル版です。

## Bloc

Blocはまだ開発が進められている、Pharoの新しいUIフレームワークです。Spartaを使ってなんかすごいパフォーマンスが出るレンダリングエンジンを使うらしいです。
Blocはgithub上で開発が進められています。
https://github.com/pharo-graphics/Bloc
チュートリアルもちゃんと用意されています。よくわからないけど、カードをめくるゲームみたいです。とりあえず拾い読みした程度ですが、けっこう役に立ちました。おすすめです。

# 全体像

とりあえず、Blocではモデルとビューが分離されています。モデルではStarfall Nightのルールをコードとして記述します。ビューは、Blocの要素であるBlElementのサブクラスとして定義して、drawSpartaCanvasOn:メソッドで描画します。まあこの辺の感覚はMorphと似てるかもしれません。モデルとビューの連携はAnnouncement機構を使います。今回はモデルが変更されたらビューにそれを通知するだけなのですが、一応画面の意味的に、ボードと落ちてくる宝石と次の宝石それぞれの変更について専用のAnnouncementを作っています。

# まずはゲームのモデルを書こう

```smalltalk
StarfallNightModel browse
```
すると、モデルのソースが読めます。もちろん、普通にシステムブラウザでStarfallNightModelを探して選択しても同じです。これの、actionsプロトコルあたりのメソッドを眺めると、どんなゲームかわかると思います。
基本的に落ちて固定化された宝石の盤面をインスタンス変数columnsに格納して、現在落ちている宝石3つ組をcurrentJewels、次に落ちてくる宝石3つ組をnextJewelsに格納しています。落ちている宝石の一番下の宝石のx座標がcolumn、y座標がrowです。
あとはactionsプロトコルにあるように、宝石を左に移動するleftメソッド、右に移動するrightメソッド、床まで落とすdownメソッド、ローテーションさせるrotateでゲームを操作します。
あとは1コマずつ落下していくfallメソッド、宝石が床まで落下したら、タテヨコナナメで消す処理をするvanishTriples、宝石が消えた分詰めるshrink、そして次の宝石を出すnext、といったあたりを実装してあります。まあ、どれも簡単なものです。
あとは、落ちゲーなのでタイマー仕掛けで一定速度で落とす処理をしてあげる必要があります。それがonStepメソッドなのですが、これをBlAnimationというクラスを使ってタイマー仕掛けで一定間隔で叩いてあげます。と言う処理をしているのがgameStartです。このあたり、steppingでお気楽にインターバルタイマーが使えたMorphicとの違いがあります。

```smalltalk
gameStart
	self initialize.
	animation
		ifNil: [ animation := BlAnimation new
				duration: 300 milliSeconds;
				beInfinite;
				onFinishedDo: [ self onStep ] ].
	animation isRunning
		ifFalse: [ animation start ]
```

ちなみにprintOn:で固定化された宝石の盤面を文字で表示するようにしたので、表示がなくてもこれだけでコマンドライン的に遊ぶことができたりします。

# 表示してみよう
ようするにBlElementのサブクラスを作って、モデルを参照しながら絵を drawSpartaCanvasOn:で描画してあげるだけです。あとはキーボードイベント処理ぐらい。
まずはモデルとの連携ですが、こんな感じ。

```smalltalk
starfallNight: aStarfallNightModel
	starfallNight := aStarfallNightModel.
	self
		addEventHandler:
			(BlEventHandler
				on: BlKeystrokeEvent
				do: [ :anEvent | self onKeystroke: anEvent ]).
	self requestFocus.
	starfallNight announcer
		when: StarfallNightBoardUpdatedAnnouncement
		send: #onBoardChange
		to: self.
	starfallNight announcer
		when: StarfallNightNextUpdatedAnnouncement
		send: #onNextChange
		to: self.
	starfallNight announcer
		when: StarfallNightJewelsUpdatedAnnouncement
		send: #onJewelsChange
		to: self
```
requestFocusしてあげないとイベントが拾えなくて、ハマりました。みなさん、気をつけましょう。
あとは描画ですが、とても雑に書いたコードなんですが、まあ今更恥ずかしがる年でもないので、晒します。こんな感じ。

```smalltalk
drawOnSpartaCanvas: aCanvas
	aCanvas fill
		paint: self backgroundPaint;
		path: self boundsInLocal;
		draw.
	1 to: starfallNight numberOfRows do: [ :r | 
		1 to: starfallNight numberOfColumns do: [ :c | 
			| rect |
			rect := aCanvas shape
				roundedRectangle: (((c - 1) * 40) @ ((starfallNight numberOfRows - r) * 40) extent: 39 @ 39)
				radii: (BlCornerRadii radius: 10).
			aCanvas fill
				paint:
					((starfallNight at: r @ c)
						ifNotNil: [ :jewelIndex | self colorAt: jewelIndex ]
						ifNil: [ Color black ]);
				path: rect;
				draw ] ].
	starfallNight currentJewels
		ifNotNil: [ 1 to: 3 do: [ :index | 
				| r c |
				r := starfallNight row + index - 1.
				c := starfallNight column.
				(r between: 1 and: starfallNight numberOfRows)
					ifTrue: [ | rect |
						rect := aCanvas shape
							roundedRectangle: (((c - 1) * 40) @ ((starfallNight numberOfRows - r) * 40) extent: 39 @ 39)
							radii: (BlCornerRadii radius: 10).
						aCanvas fill
							paint: (self colorAt: (starfallNight currentJewels at: index));
							path: rect;
							draw ] ] ].
	starfallNight nextJewels
		ifNotNil: [ 1 to: 3 do: [ :index | 
				| rect |
				rect := aCanvas shape
					roundedRectangle: ((6 * 40 + 10) @ (160 - (index * 40)) extent: 39 @ 39)
					radii: (BlCornerRadii radius: 10).
				aCanvas fill
					paint: (self colorAt: (starfallNight nextJewels at: index));
					path: rect;
					draw ] ]
```
スパルタのキャンバスは、描く種別ごとにキャンバスからビルダーを取り出して、あれこれ設定して、drawというと、絵を描いてくれる、そんな造りになっています。なんかPharoの人たちって本当にビルダーが好きですね。ほのぼのします。

# その他
その他のクラスはまあAnnouncementなんで、どうでもいいです。サブクラス作るだけ。
あとは、モデルのクラスにexampleメソッドを書いて動かしているのですが、オレみたいなロートルはついコメントにコードを書きたくなるのですが、それ用のpragmaを使うのが今風なようです。

```smalltalk
example
	<script: 'StarfallNightModel example'>
	| model element space |
	model := StarfallNightModel new.
	element := StarfallNightElement new.
	element starfallNight: model.
	^ element
```
こいつをInspectしてやれば、Moldable Inspectorの力で、Liveタブをゲーム画面として使うことができます。Rawタブを選べば、普通のインスペクタとしても使えます。べんりー。

# まとめ
というわけで、星降る夜は刻を忘れてSmalltalkを楽しみましょう。ちゃんちゃん。