Last active
August 29, 2015 14:26
-
-
Save sortega/38106e7d84f59dd50c58 to your computer and use it in GitHub Desktop.
Bowling game score. Imperative, OO style
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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