Let’s implement a small feature too. During the spawning process collision against existing blocks are not checked. If the new piece collides, it should end the game. Here’s the spec:
s2"""
Spawning a new piece should
end the game it hits something. $spawn1
"""
...
def spawn1 =
Function.chain(Nil padTo (10, drop))(s1).status must_==
GameOver
Let’s define GameStatus
trait:
sealed trait GameStatus
case object ActiveStatus extends GameStatus
case object GameOver extends GameStatus
The test fails as expected after adding it to the GameStatus
:
[info] Spawning a new piece should
[error] x end the game it hits something.
[error] 'ActiveStatus' is not equal to 'GameOver' (StageSpec.scala:29)
Current implementation of spawn
is loading nextPiece
without checking for collision:
private[this] lazy val spawn: GameState => GameState =
(s: GameState) => {
def dropOffPos = (s.gridSize._1 / 2.0, s.gridSize._2 - 2.0)
val next = Piece((2, 1), s.kinds.head)
val p = s.nextPiece.copy(pos = dropOffPos)
s.copy(blocks = s.blocks ++ p.current,
currentPiece = p, nextPiece = next, kinds = s.kinds.tail)
}
All we have to do is validate the piece before loading it in.
private[this] lazy val spawn: GameState => GameState =
(s: GameState) => {
def dropOffPos = (s.gridSize._1 / 2.0, s.gridSize._2 - 2.0)
val s1 = s.copy(blocks = s.blocks,
currentPiece = s.nextPiece.copy(pos = dropOffPos),
nextPiece = Piece((2, 1), s.kinds.head),
kinds = s.kinds.tail)
validate(s1) map { case x =>
x.copy(blocks = load(x.currentPiece, x.blocks))
} getOrElse {
s1.copy(blocks = load(s1.currentPiece, s1.blocks), status = GameOver)
}
}
Next, reject state transition during GameOver
status:
private[this] def transit(trans: Piece => Piece,
onFail: GameState => GameState = identity): GameState => GameState =
(s: GameState) => s.status match {
case ActiveStatus =>
// do transition
case _ => s
}
Let’s rub it into the player.
view.status match {
case GameOver =>
g setColor bluishSilver
g drawString ("game over",
12 * (blockSize + blockMargin), 7 * (blockSize + blockMargin))
case _ => // do nothing
}
As always, the code’s up on github:
$ git fetch origin
$ git co day4v2 -b try/day4
$ sbt swing/run