Skip to content

Instantly share code, notes, and snippets.

@gustavofranke
Last active November 25, 2018 16:18
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 gustavofranke/479e36cc82206bc2c5a19d540cca2273 to your computer and use it in GitHub Desktop.
Save gustavofranke/479e36cc82206bc2c5a19d540cca2273 to your computer and use it in GitHub Desktop.
John de Goes' purely functional hangman
package hangman
import java.io.IOException
//import cats._
import cats.{Apply, Functor, Monad}
import cats.implicits._
import scalaz.zio._
import scalaz.zio.console._
import scala.language.higherKinds
// "org.scalaz" %% "scalaz-zio" % "0.3.2"
object FunctionalScala extends App {
case class State(name: String, guesses: Set[Char], word: String) {
def failures: Int = (guesses -- word.toSet).size
def playerLost: Boolean = failures > 10
def playerWon: Boolean = (word.toSet -- guesses).isEmpty
}
lazy val Dictionary = List("aaron", "abelian")
val getName: IO[IOException, String] =
putStrLn("What is your name?") *> getStrLn
def nextInt(max: Int): IO[Nothing, Int] = IO.sync(scala.util.Random.nextInt(max))
val chooseWord: IO[Nothing, String] = nextInt(Dictionary.length).map(int =>
Dictionary.lift(int).getOrElse("bug in your program"))
val getChoice: IO[IOException, Char] =
for {
_ <- putStrLn("Please guess a letter:")
line <- getStrLn
char <- line.toLowerCase.trim.headOption match {
case None =>
putStrLn("You did not enter a letter") *> getChoice
case Some(c) => IO.now(c)
}
} yield char
def gameLoop(state: State): IO[IOException, State] =
for {
guess <- getChoice
state <- IO.now(state.copy(guesses = state.guesses + guess))
_ <- renderState(state)
loop <- if (state.playerWon)
putStrLn(s"Congratulations, ${state.name}, you won the game!!")
else if (state.playerLost)
putStrLn(s"Sorry, ${state.name}, you lost the game")
else if (state.word.contains(guess))
putStrLn(s"You guessed correctly, ${state.name}, Keep going!")
else putStrLn(s"You guessed wrong, ${state.name}, but keep trying!")
// state <- if (loop) gameLoop(state) else IO.now(state)
state <- if (!state.playerLost && !state.playerWon) gameLoop(state) else IO.now(state)
} yield state
def renderState(state: State): IO[IOException, Unit] = {
val word = state.word.toList.map(c =>
if (state.guesses.contains(c)) s" $c " else " "
).mkString("")
val line = List.fill(state.word.length)(" - ").mkString("")
val guesses = " Guesses: " + state.guesses.mkString(", ")
val text = word + "\n" + line + "\n\n" + guesses + "\n"
putStrLn(text)
}
val hangmanGame: IO[IOException, Unit] =
for {
_ <- putStrLn("Welcome to Purely Functional Hangman!")
name <- getName
_ <- putStrLn("Great too meet you, " + name + ", let's begin!")
word <- chooseWord
state = State(name, Set(), word)
_ <- renderState(state)
_ <- gameLoop(state)
} yield ()
override def run(args: List[String]): IO[Nothing, ExitStatus] =
hangmanGame.redeemPure(
_ => ExitStatus.ExitNow(1),
_ => ExitStatus.ExitNow(0)
)
}
//object Main extends scala.App {
// FunctionalScala.run(Nil).unsafeRun
//}
trait Random[F[_]] {
def nextInt(max: Int): F[Int]
}
object Random {
def apply[F[_]](implicit F: Random[F]): Random[F] = F
implicit def RandomIO[E]: Random[IO[E, ?]] = new Random[IO[E, ?]] {
override def nextInt(max: Int): IO[E, Int] = IO.sync(scala.util.Random.nextInt(max))
}
}
trait Console[F[_]] {
def printLine(line: String): F[Unit]
def readLine: F[String]
}
object Console {
def apply[F[_]](implicit F: Console[F]): Console[F] = F
implicit def ConsoleIO[E]: Console[IO[E, ?]] = new Console[IO[E, ?]] {
override def printLine(line: String): IO[E, Unit] = IO.sync(println(line))
override def readLine: IO[E, String] = IO.sync(scala.io.StdIn.readLine())
}
}
object FunctionalScala1 extends App {
def nextInt[F[_]: Random](max: Int): F[Int] = Random[F].nextInt(max)
def printLine[F[_]: Console](str: String): F[Unit] = Console[F].printLine(str)
def readLine[F[_]: Console]: F[String] = Console[F].readLine
case class State(name: String, guesses: Set[Char], word: String) {
def failures: Int = (guesses -- word.toSet).size
def playerLost: Boolean = failures > 10
def playerWon: Boolean = (word.toSet -- guesses).isEmpty
}
lazy val Dictionary = List("aaron", "abelian")
def getName[F[_]: Console : Apply]: F[String] =
printLine[F]("What is your name?") *> readLine[F]
def chooseWord[F[_]: Random: Functor]: F[String] = nextInt[F](Dictionary.length).map(Dictionary(_))
def getChoice[F[_]: Console: Monad]: F[Char] =
for {
_ <- printLine[F]("Please guess a letter:")
line <- readLine[F]
char <- line.toLowerCase.trim.headOption match {
case Some(c) if c.isLetter => c.pure[F]
case None =>
printLine[F]("You did not enter a letter") *> getChoice[F]
}
} yield char
def gameLoop[F[_]: Console : Monad](state: State): F[State] =
for {
guess <- getChoice[F]
state <- state.copy(guesses = state.guesses + guess).pure[F]
_ <- renderState[F](state)
_ <- if (state.playerWon) printLine[F](s"Congratulations, ${state.name}, you won the game!!")
else if (state.playerLost) printLine[F](s"Sorry, ${state.name}, you lost the game")
else (if (state.word.contains(guess)) printLine[F](s"You guessed correctly, ${state.name}, Keep going!")
else printLine[F](s"You guessed wrong, ${state.name}, but keep trying!")) *> gameLoop[F](state)
} yield state
def renderState[F[_]: Console](state: State): F[Unit] = {
val word = state.word.toList.map(c =>
if (state.guesses.contains(c)) s" $c " else " "
).mkString("")
val line = List.fill(state.word.length)(" - ").mkString("")
val guesses = " Guesses: " + state.guesses.mkString(", ")
val text = word + "\n" + line + "\n\n" + guesses + "\n"
printLine[F](text)
}
def hangmanGame[F[_]: Console : Monad : Random]: F[Unit] =
for {
_ <- printLine[F]("Welcome to Purely Functional Hangman!")
name <- getName[F]
_ <- printLine[F]("Great too meet you, " + name + ", let's begin!")
word <- chooseWord[F]
state <- State(name, Set(), word).pure[F]
_ <- renderState[F](state)
_ <- gameLoop[F](state)
} yield ()
import Console._
import Random._
implicit def MonadIO[E]: Monad[IO[E, ?]] = new Monad[IO[E, ?]] {
override def pure[A](x: A): IO[E, A] = IO.now(x)
override def flatMap[A, B](fa: IO[E, A])(f: A => IO[E, B]): IO[E, B] = fa.flatMap(f)
override def tailRecM[A, B](a: A)(f: A => IO[E, Either[A, B]]): IO[E, B] = f(a).map(_.right.get) // ?
}
override def run(args: List[String]): IO[Nothing, ExitStatus] =
hangmanGame.redeemPure(
_ => ExitStatus.ExitNow(1),
_ => ExitStatus.ExitNow(0)
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment