Last active
November 25, 2018 16:18
-
-
Save gustavofranke/479e36cc82206bc2c5a19d540cca2273 to your computer and use it in GitHub Desktop.
John de Goes' purely functional hangman
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 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