Skip to content

Instantly share code, notes, and snippets.

@battermann
Last active November 20, 2017 10:13
Show Gist options
  • Save battermann/58fe0f88fd9c5ca9a990b0185ec7fdb8 to your computer and use it in GitHub Desktop.
Save battermann/58fe0f88fd9c5ca9a990b0185ec7fdb8 to your computer and use it in GitHub Desktop.
A simple dice game implemented purely functional.
lazy val root = (project in file(".")).
settings(
inThisBuild(List(
scalaVersion := "2.12.4",
version := "0.1.0-SNAPSHOT"
)),
name := "Dice Game",
scalacOptions += "-Ypartial-unification",
libraryDependencies += "org.typelevel" %% "cats-effect" % "0.5"
)
// Note: pure != good, this is just a demo for the sake of purity. There are no claims that this is a particularly good design.
import PRNG.Seed
import cats.data.{State, StateT}
import cats.effect._
import scala.util.Try
object DiceGame extends App {
type StateIO[A] = StateT[IO, Seed, A]
def lift[A](v: IO[A]): StateIO[A] = StateT.lift[IO, Seed, A](v)
def putStrLn(line: String): StateIO[Unit] = lift(IO {
println(line)
})
val getLine: StateIO[String] = lift(IO {
scala.io.StdIn.readLine()
})
val roll: StateIO[Int] = PRNG.nextInt(6).map(_ + 1).transformF(IO.eval)
def maybePlayAgain: StateIO[Unit] = {
for {
again <- getLine
_ <- if (again == "y") {
program
} else if (again == "n") {
putStrLn("Bye!")
} else {
putStrLn("Please enter 'y' or 'n'.")
.flatMap(_ => maybePlayAgain)
}
} yield ()
}
def getAndEvaluateInput(secretNumber: Int): StateIO[Unit] = {
for {
guess <- getLine
_ <- if (guess.trim == secretNumber.toString) {
for {
_ <- putStrLn("Correct!")
_ <- putStrLn("Play again? (y/n)")
_ <- maybePlayAgain
} yield ()
} else if (Try(guess.toInt).map(n => n < secretNumber && n >= 1).getOrElse(false)) {
putStrLn("The number is greater.")
.flatMap(_ => getAndEvaluateInput(secretNumber))
} else if (Try(guess.toInt).map(n => n > secretNumber && n <= 6).getOrElse(false)) {
putStrLn("The number is smaller.")
.flatMap(_ => getAndEvaluateInput(secretNumber))
} else if (guess == "q") {
putStrLn("Bye!")
} else {
putStrLn("You probably mistyped. Enter a number between 1 and 6. (enter 'q' to quit)")
.flatMap(_ => getAndEvaluateInput(secretNumber))
}
} yield ()
}
def program: StateIO[Unit] = {
for {
secretNumber <- roll
_ <- putStrLn("I rolled a number. Can you guess it? (enter 'q' to quit)")
_ <- getAndEvaluateInput(secretNumber)
} yield ()
}
program.runA(Seed(System.currentTimeMillis)).unsafeRunSync()
}
object PRNG {
final case class Seed(seed: Long) {
private lazy val next = Seed(seed * 6364136223846793005L + 1442695040888963407L)
def nextInt: (Seed, Int) = {
(next, (next.seed >>> 16).asInstanceOf[Int])
}
def nonNegativeInt: (Seed, Int) = {
val (seed, value) = nextInt
(seed, if (value < 0) -(value + 1) else value)
}
def nextInt(bound: Int): (Seed, Int) = {
val (seed, value) = nonNegativeInt
(seed, value % bound)
}
}
def nextInt(bound: Int): State[Seed, Int] = State[Seed, Int](s => s.nextInt(bound))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment