Home > Development > Dead simple API design for Dice Rolling

Dead simple API design for Dice Rolling

I wanted to create a small project where I could achieve results fairly quickly in technologies I never (or rarely) use. At the Mix-IT conference, I realized the few stuff I learned in Scala had been quickly forgotten. And I wanted wanted to give Gradle a try, despite my regular bitching about it. Since my Role-Playing crew wants to play Earthdawn (we stopped for like 20 years), I decided to create a Dice Roller app in Scala, running on Android (all of my friends have Android devices) and built with Gradle (I promised it at Devoxx).

I soon realized that there were a definite Dice Rolling API that could be isolated from the rest. Rolling a die in Earthdawn has some definite quirk – if you roll the highest result, you re-roll and add it to the result and so on until you roll not the highest, but the basics of rolling a die is similar in every system. It was time to design an extensible API. By extensible, I’m talking about something I could re-use in every RPG system.

I’d already been trying to create such an API and the root of it is the roll() method signature. Before, I used 2 methods in conjunction:

roll:Unit

getResult:T

This is a big mistake, as implementations will require state handling! This created plenty of problems, among them:

  • creating instances each time I needed to roll
  • calling 2 different methods in the right order, also known as time coupling

This time, having learned from my mistake, I replaced that with the following:

roll:T

This time, implementation can (and will) do without state.

The next design decision is about the result type. I’m not really sure about this point, but I formerly returned only the result. This time, I returned the result as well as the object itself, so I can pass both along and it let users know which die was rolled as well as the result. This is possible without creating a new class structure thanks to Scala’s out-of-the-box Tuple2. My final interface looks like:

trait Rollable[T] {
  roll:(Rollable[T],T)
}

As I’m aiming toward RPG, I just need to set the sides number as a parameter (to be able to create those strange 12 and 20-sided dice). A naive implementation is very straightforward:

class Die (val sides:Int)extends Rollable[Int] {

  var random:SecureRandom

  override def roll:(Die, Int) = (this, random.nextInt(sides) + 1)
}

Seems good enough. However, how can we test this design? If I need to build upon this, I will need to be able to set desired results: in this case, this is not cheating, it’s faking! As it turns out, I need to pass the random as a constructor parameter (but without a getter).

class Die (val sides:Int, random:Random)extends Rollable[Int] {

  override def roll:(Die, Int) = (this, random.nextInt(sides) + 1)
}

That’s better, but only marginally. With this code, I need to pass the random parameter each time I create a new instance. A slightly better option would be to add a constructor with a default SecureRandom instance. However, what if the next Java version offers an even better Random implementation? Or if the API user prefers to rely on an external secure entropy source? It would he would still have to pass the new improved random for each call – back to square one. Fortunately, Scala offers this one nice language feature called implicit. With implicit, API users only have to reference the random generator once in a file to use it everywhere. The improved design now looks like:

class Die (val sides:Int)(implicit random:Random) extends Rollable[Int] {
  override def roll:(Die, Int) = (this, random.nextInt(sides) + 1)
}

object SecureDie {
  implicit val random = new SecureRandom
}

Callers then just need to import the provided random, or to use their own and call the constructor with the desired sides number:

import SecureDie.random // or import MyQuantumRandomGenerator.random

val die = new Die(6)

The final step is to create Scala object (singletons) in order to offer a convenient API. This is only possible since we designed our classes with no state:

import SecureDie.random

object d3 extends Die(3)
object d4 extends Die(4)
object d6 extends Die(6)
object d8 extends Die(8)
object d10 extends Die(10)
object d12 extends Die(12)
object d20 extends Die(20)
object d100 extends Die(100)

Users now just need to call d6.roll to roll dice!

With a simple domain, I showed how Scala’s most basic feature can really help getting a clean design. Results are available on Github.

In the next article I will detail how I got Scala to run on Android and pitfalls I stumbled upon. Spoiler: there will some Gradle involved…

email
Send to Kindle
Categories: Development Tags: ,
  1. No comments yet.
  1. No trackbacks yet.