Skip to content

Instantly share code, notes, and snippets.

@p3t0r
Created June 30, 2011 08:37
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 p3t0r/1055868 to your computer and use it in GitHub Desktop.
Save p3t0r/1055868 to your computer and use it in GitHub Desktop.
Scala TDD workshop - Implement a Yahtzee scorer
/**
* Results of a 1 hour 'bucketline' exercise @ Marktplaats to test-drive the development
* of a Yahtzee scorer in Scala.
*/
// --- the test --- //
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import Scorer._
class ScorerTest extends FlatSpec with ShouldMatchers {
"Scorer" should "score small street (sequence of 4) at 30 points" in {
smallStreet(List(1,2,3,4,4)) should be(30)
smallStreet(List(1,1,1,1,1)) should be(0)
smallStreet(List(1,3,4,5,5)) should be(0)
smallStreet(List(1,2,4,5,6)) should be(0)
// big street
smallStreet(List(1,2,3,4,5)) should be(30)
List.fill()
}
it should "score big street at 40 points" in {
bigStreet(List(1,2,3,4,5)) should be(40)
bigStreet(List(2,3,4,5,6)) should be(40)
bigStreet(List(6,3,4,2,5)) should be(40)
bigStreet(List(1,3,2,4,6)) should be(0)
}
it should "correctly score above-the-line values (n-of-a-kind)" in {
// three ones above the line
aboveTheLine(List(1,1,1,4,5), 1) should be (3)
aboveTheLine(List(2,2,2,4,5), 2) should be (6)
aboveTheLine(List(2,2,1,4,5), 2) should be (4)
aboveTheLine(List(2,5,1,4,5), 2) should be (2)
}
it should "score three of a kind " in {
threeOfAKind(List(1,1,1,2,3)) should be (8)
threeOfAKind(List(1,1,1,1,3)) should be (7)
threeOfAKind(List(1,1,1,1,1)) should be (5)
threeOfAKind(List(1,1,2,2,3)) should be (0)
}
it should "give highest score for a given roll" in {
pending
}
it should "score chance (garbage) with the sum of all dices" in {
chance(List(1,1,1,1,1)) should be(5)
chance(List(1,2,3,4,5)) should be(15)
}
it should "score full house" in {
fullHouse(List(2,2,4,4,4)) should be(25)
fullHouse(List(1,2,4,4,4)) should be(0)
fullHouse(List(2,2,2,2,4)) should be(0)
}
}
// --- the implementation --- //
/**
* A collection of functions for calculating Yathzee scores.
*/
object Scorer {
def chance(dices:Seq[Int]) = dices.reduceLeft(_+_)
def fullHouse(dices:Seq[Int]) = if (groupWithCounts(dices).filter(e => e._2 == 2 || e._2 == 3).size == 2) 25 else 0
def groupWithCounts(dices:Seq[Int]) = dices.groupBy(v => v).map(e => (e._1, e._2.size))
def street(dices:Seq[Int], size:Int, points:Int) = {
val sortedDices = dices.sorted
val containsSlice = sortedDices.zip(sortedDices.tail)
.map(t => t._1 - t._2)
.containsSlice(List.fill(size)(-1))
if (containsSlice) points else 0
}
def threeOfAKind(dices:Seq[Int]) = {
if (groupWithCounts(dices).exists(_._2 >= 3)) dices.sum else 0
}
def smallStreet(dices:Seq[Int]) = street(dices, 3, 30)
def bigStreet(dices:Seq[Int]) = street(dices, 4, 40)
def aboveTheLine(dices:Seq[Int], v:Int) = dices.filter(_ == v).sum
}
@p3t0r
Copy link
Author

p3t0r commented Jun 30, 2011

Also notice that one of the 'uglier' expressions is a perfect fit for inline pattern matching

def fullHouse(dices:Seq[Int]) = if (groupWithCounts(dices).filter(e => e._2 == 2 || e._2 == 3).size == 2) 25 else 0

Could be written as:

def fullHouse(dices:Seq[Int]) = if (groupWithCounts(dices).filter{case (eyes,size) => size == 2 || size == 3).size == 2} 25 else 0

@erikvanoosten
Copy link

Similarly, you can write
map(t => t._1 - t._2)
as
map(case (d1, d2) => d1 - d2)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment