Building a memory game with Blocの手習い(Chapter 4)

これはSmalltalk Advent Calendar 2017の記事です。

前回に引き続いて、Bloc のチュートリアルである Building a memory game with Bloc のChapter 4(Adding a board view) の内容をやっていく。

元ネタは
https://github.com/pharo-graphics/Bloc
の中にある、
http://files.pharo.org/books-pdfs/booklet-Bloc/2017-11-09-memorygame.pdf
である。

3章ではカードの描画を行った。4章ではゲームボードの描画を実装する。具体的にはゲームボード用のエレメントを作ってレイアウトを設定する。

4.1 The GameElement class

MgdGameElement クラスを作ってゲームボードの描画を行う。 MgdRawCardElement クラスと同様に、 BlElement クラスのサブクラスを継承して定義する。このクラスはゲームモデルへの参照用のインスタンス変数を持っている。

BlElement subclass: #MgdGameElement
  instanceVariableNames: 'memoryGame'
  classVariableNames: ''
  package: 'Bloc-MemoryGame-Demo-Elements'

次に memoryGame インスタンス変数のアクセッサを作成する。 セッターメソッドである memoryGame: は次の節ですぐに修正することになる。

MgdGameElement >> memoryGame: aMgdGameModel
  memoryGame := aMgdGameModel.

ゲッターメソッドも定義する。

MgdGameElement >> memoryGame
  ^ memoryGame

レイアウトを設定するために initialize メソッドをオーバーライドする。グリッド(マス目)式のレイアウトとして水平方向に設定する。

MgdGameElement >> initialize
  super initialize.
  self layout: BlGridLayout horizontal.

4.2 Creating cards

ゲームのモデルから、ゲーム盤の横方向のカード数と、カードのエレメントを作るためのカードのモデルを得る。カードのエレメントは、 addChild: メソッドでゲーム盤のの子エレメントにする。

MgdGameElement >> memoryGame: aGameModel
  memoryGame := aGameModel.
  memoryGame availableCards
    do: [ :aCard | self addChild: (self newCardElement card: aCard) ].

newCardElement メソッドでカードのエレメントを作る。

MgdGameElement >> newCardElement
  ^ MgdRawCardElement new

ここで、今まで作ったゲーム盤のエレメントを表示させてみる。

game := MgdGameModel new initializeForSymbols: '12345678'.
grid := MgdGameElement new.
grid memoryGame: game.
grid.

上記を Playground に入力して Do it all and go をすると、下の画面のようになる。

中途半端にカードの一部が表示されているのがわかる。これはゲーム盤が子エレメントの大きさに合わせて表示していないからだそうな。

4.3 Updating the container to its children

Bloc におけるレイアウトは、子エレメントを格納する入れ物内での配置を行うのであって、入れ物そのものの配置は行わない。それをするには「制約(constraints)」を使う。

MgdGameElement >> initialize
  super initialize.
  self layout: BlGridLayout horizontal.
  self constraintsDo: [ :aLayoutConstraints |
    aLayoutConstraints horizontal fitContent.
    aLayoutConstraints vertical fitContent ].

再度、 Playground でゲーム盤のエレメントを表示させてみると以下のようになった。

カードの全体が表示されたのは良いが、一列になってしまい Playground からはみ出している。

4.4 Getting all the children displayed

グリッドで横方向に表示するエレメントの個数を設定することで、4行4列のカードを表示させる。ゲームのモデルがないと個数がわからないので、memoryGameのセッターメソッド内で設定する。

MgdGameElement >> memoryGame: aGameModel
  memoryGame := aGameModel.
  self layout columnCount: memoryGame gridSize.
  memoryGame availableCards
    do: [ :aCard | self addChild: (self newCardElement card: aCard) ].

columnCount: でのエレメント数の設定は、子エレメントを追加した後に行っても問題ないようだ。

ついでにカードを表にして表示してみる。

game := MgdGameModel new initializeForSymbols: '12345678'.
game availableCards do: #flip.
grid := MgdGameElement new.
grid memoryGame: game.
grid.

あれれ、数字のフォントサイズが小さすぎるぞ。

4.5 Separating cards

カードがくっついて見難いので、cellSpacing: で隙間をあける。ついでに background: でゲーム盤の背景色も設定する。カードのエレメントの背景色は角丸四角形の外形として塗りつぶしていたが、ゲーム盤面の背景色は文字通り背景として設定する。

background: メッセージの引き数は Color のインスタンスのままでも、 BlBackground のインスタンスでも良いらしい。

蛇足

以上でゲーム盤の描画の実装は終わったけど、数字のフォントが小さいのは気持ち悪い。

aからhを表示させたのがこちら。a だけ大きいなんてひどいじゃないか。

drawFlippedSideOn: aCanvas
  | font origin textPainter metrics |
  font := aCanvas font
    named: 'TakaoExGothic';
    size: 50;
    build.

フォントを TakaoExGothic に変えてみたら問題なく表示されたのでこちらにする。

いよいよ次の最終章でゲームの操作関係の実装。これでゲームっぽくなる。