Skip to content

Instantly share code, notes, and snippets.

@sortega
Last active August 29, 2015 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sortega/38106e7d84f59dd50c58 to your computer and use it in GitHub Desktop.
Save sortega/38106e7d84f59dd50c58 to your computer and use it in GitHub Desktop.
Bowling game score. Imperative, OO style
package bowling
class Game {
trait Line {
def addRoll(pins: Int): Unit
def score: Int
}
class Frame(next: Line) extends Line {
private var rolls = Seq.empty[Int]
private var bonusRolls = Seq.empty[Int]
override def addRoll(pins: Int): Unit = {
if (completed) addNextFrameRoll(pins)
else rolls :+= pins
}
private def addNextFrameRoll(pins: Int): Unit = {
addBonusRoll(pins)
next.addRoll(pins)
}
def addBonusRoll(pins: Int): Unit = {
if (pendingBonusRolls) {
bonusRolls :+= pins
}
}
private def pendingBonusRolls: Boolean = bonusRolls.size < maxBonusRolls
private def completed: Boolean = isStrike || attemptsExhausted
override def score: Int = ownScore + next.score
private def ownScore: Int = rolls.sum + bonusRolls.sum
private def maxBonusRolls: Int =
if (isStrike) 2
else if (isSpare) 1
else 0
private def isSpare: Boolean = allPinsDown && attemptsExhausted
private def isStrike: Boolean = allPinsDown && !attemptsExhausted
private def attemptsExhausted: Boolean = rolls.size == Game.FrameRolls
private def allPinsDown: Boolean = rolls.sum == Game.TotalPins
}
object LineEnd extends Line {
override def addRoll(pins: Int): Unit = {}
override def score = 0
}
private val line: Line = {
var head: Line = LineEnd
(1 to Game.TotalFrames).foreach { _ =>
head = new Frame(head)
}
head
}
def addRoll(pins: Int): Unit = {
line.addRoll(pins)
}
def score: Int = line.score
}
object Game {
val TotalFrames = 10
val TotalPins = 10
val FrameRolls = 2
}
package bowling
import org.scalatest.{FlatSpec, ShouldMatchers}
class GameTest extends FlatSpec with ShouldMatchers {
"A game" should "score 0 for the gutter game" in new Fixture {
forEachFrame { givenGutterFrame() }
game.score shouldBe 0
}
it should "score the sum of the pins stroked down" in new Fixture {
forEachFrame { givenFrame(1, 2) }
game.score shouldBe 30
}
it should "count double the first roll after spare" in new Fixture {
val strokedPins = 37
val spareBonus = 1
givenSpareFrame()
(2 to Game.TotalFrames).foreach { _ =>
givenFrame(1, 2)
}
game.score shouldBe strokedPins + spareBonus
}
it should "getting spare at the last frame allow for a bonus ball" in new Fixture {
val bonusRoll = 5
val regularRolls = 37
(1 to (Game.TotalFrames - 1)).foreach { _ =>
givenFrame(1, 2)
}
givenSpareFrame()
givenBonusRolls(bonusRoll)
game.score shouldBe regularRolls + bonusRoll
}
it should "score 150 for an all-5 game" in new Fixture {
forEachFrame { givenFrame(5, 5) }
givenBonusRolls(5)
game.score shouldBe 150
}
it should "count double the next two rolls after strike" in new Fixture {
val strokedPins = 37
val strikeBonus = 3
givenStrikeFrame()
(2 to Game.TotalFrames).foreach { _ =>
givenFrame(1, 2)
}
game.score shouldBe strokedPins + strikeBonus
}
it should "score 300 for the perfect game" in new Fixture {
forEachFrame { givenStrikeFrame() }
givenBonusRolls(Game.TotalPins, Game.TotalPins)
game.score shouldBe 300
}
trait Fixture {
protected val game = new Game()
protected def givenGutterFrame(): Unit = {
givenFrame(0, 0)
}
protected def givenSpareFrame(): Unit = {
givenFrame(6, 4)
}
protected def givenStrikeFrame(): Unit = {
givenFrame(Game.TotalPins)
}
protected def givenFrame(rolls: Int*): Unit = {
rolls.foreach(game.addRoll)
}
protected def givenBonusRolls(rolls: Int*): Unit = {
rolls.foreach(game.addRoll)
}
protected def forEachFrame(action: => Unit): Unit = {
(1 to Game.TotalFrames).foreach { _ => action }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment