時計 

moveLeftmoveRight があるが、moveDown が無い。これは下向きの動きが他にもすることがあるからだ。床か別のブロックに当たり判定が出た場合は、現在のピースが固まって、新しいピースが送り込まれる。

まずは、動きから:

                                                                              s2"""
  Ticking the current piece should
    change the blocks in the view,                                            $tick1
                                                                              """
...

  def tick1 =
    tick(s1).blocks map {_.pos} must contain(exactly(
      (0, 0), (4, 16), (5, 16), (6, 16), (5, 17)
    )).inOrder

取り敢えずテストが通過するように moveBy を使って tick を実装する:

  val tick      = transit { _.moveBy(0.0, -1.0) }

次に、新しいピースの転送:

                                                                              s2"""
    or spawn a new piece when it hits something.                              $tick2
                                                                              """
...

  def tick2 =
    Function.chain(Nil padTo (18, tick))(s1).
    blocks map {_.pos} must contain(exactly(
      (0, 0), (4, 0), (5, 0), (6, 0), (5, 1),
      (4, 17), (5, 17), (6, 17), (5, 18)
    )).inOrder

transit メソッドは既に変更された状態の妥当性を知ってる。現在は getOrElse を使って古い状態を返しているだけだけど、そこで別のアクションを実行すればいい。

  private[this] def transit(trans: Piece => Piece,
      onFail: GameState => GameState = identity): GameState => GameState =
    (s: GameState) => validate(s.copy(
        blocks = unload(s.currentPiece, s.blocks),
        currentPiece = trans(s.currentPiece))) map { case x =>
      x.copy(blocks = load(x.currentPiece, x.blocks))
    } getOrElse {onFail(s)}

onFail が渡されなければ identity 関数が用いられる。以下が tick だ:

  val tick = transit(_.moveBy(0.0, -1.0), spawn)
  
  private[this] def spawn(s: GameState): GameState = {
    def dropOffPos = (s.gridSize._1 / 2.0, s.gridSize._2 - 3.0)
    val p = Piece(dropOffPos, TKind)
    s.copy(blocks = s.blocks ++ p.current,
      currentPiece = p)
  }

テストを通過したか確認する:

[info] Ticking the current piece should
[info] + change the blocks in the view,
[info] + or spawn a new piece when it hits something

タイマー 

抽象UI の中で tick を下矢印キーとタイマーに配線しよう:

  import java.{util => ju}

  private[this] val timer = new ju.Timer
  timer.scheduleAtFixedRate(new ju.TimerTask {
    def run { state = tick(state) }
  }, 0, 1000) 

  ...

  def down() {
    state = tick(state)
  }

これで現在のピースが勝手に動くようになったけど、swing UI はそのことを知らないので描画はされない。mainPanel を 10 fps で再描画するタイマーを加えてこの問題を直す:

    val timer = new SwingTimer(100, new AbstractAction() {
      def actionPerformed(e: java.awt.event.ActionEvent) { repaint }
    })
    timer.start

day2

一番下の列 

明らかな問題は一番下の列が消えていないことだ。以下のスペックでテストできると思う:

    """It should also clear out full rows."""               ! tick3^

...

  val s3 = newState(Seq(
      (0, 0), (1, 0), (2, 0), (3, 0), (7, 0), (8, 0), (9, 0))
    map { Block(_, TKind) })
  def tick3 =
  Function.chain(Nil padTo (18, tick))(s3).
    blocks map {_.pos} must contain(
      (5, 0), (4, 17), (5, 17), (6, 17), (5, 18)
    ).only.inOrder 

続きはまた明日。

$ git fetch origin
$ git co day2v2 -b try/day2
$ sbt swing/run