2日目 

今日は、昨日からの続きで失敗しているテストがある。これは、趣味のプロジェクトの場合は一日の作業を終えるのに便利な方法だ。

[info]   Moving to the left the current piece should
[info]     + change the blocks in the view
[info]     x as long as it doesn't hit the wall.
[error]  List((0,0), (-1,17), (0,17), (1,17), (0,18)) does not contain (2,17), (1,18) and must not contain '(-1,17)' is equal to '(0,17)', '(0,18)' is equal to '(2,17)' in order (StageSpec.scala:22)

最後に自分が何をやっていて、次に何をするべきなのかを探るのに 5分以上かかってしまうこともある。失敗しているテストは未来の自分へ「次にやるのはこれ!」とメッセージを残しておくようなものだ。

検証 

まず現行の moveBy の実装をみてみよう:

  private[this] def moveBy(delta: (Double, Double)): this.type = {
    val unloaded = unload(currentPiece, blocks)
    val moved = currentPiece.moveBy(delta)
    blocks = load(moved, unloaded)
    currentPiece = moved
    this
  }

moved を検証して moved.current内の全てのブロックが範囲内に収まってるかをチェックするだけでいい。Scala コレクションライブラリ にある forall メソッドが正にこの用途にあっている。Scala 逆引きレシピだと、「118: List の要素が条件を満たすか調べたい」が参考になる。if 文をループさせるようなことはここでは必要ない:

  private[this] def moveBy(delta: (Double, Double)): this.type = {
    validate(
        currentPiece.moveBy(delta),
        unload(currentPiece, blocks)) map { case (moved, unloaded) =>
      blocks = load(moved, unloaded)
      currentPiece = moved
    }
    this
  }
  private[this] def validate(p: Piece, bs: Seq[Block]): Option[(Piece, Seq[Block])] =
    if (p.current map {_.pos} forall inBounds) Some(p, bs)
    else None
  private[this] def inBounds(pos: (Int, Int)): Boolean =
    (pos._1 >= 0) && (pos._1 < size._1) && (pos._2 >= 0) && (pos._2 < size._2)

これでテストはパスするはずだ:

[info] Moving to the left the current piece should
[info] + change the blocks in the view,
[info] + as long as it doesn't hit the wall