Last active
November 17, 2018 20:09
-
-
Save LordRaydenMK/0be8f70f860a862e69daf262b4a83e17 to your computer and use it in GitHub Desktop.
Functional Hangman abstracted over the container type
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 io.github.lordraydenmk.tf | |
import arrow.Kind | |
import arrow.core.getOrElse | |
import arrow.core.right | |
import arrow.core.toOption | |
import arrow.effects.IO | |
import arrow.effects.SingleK | |
import arrow.effects.fix | |
import arrow.effects.instances.io.monad.flatMap | |
import arrow.effects.instances.io.monad.monad | |
import arrow.effects.instances.io.monadDefer.monadDefer | |
import arrow.effects.singlek.async.async | |
import arrow.effects.singlek.monadDefer.monadDefer | |
import arrow.effects.typeclasses.Async | |
import arrow.effects.typeclasses.MonadDefer | |
import arrow.typeclasses.binding | |
import java.io.IOException | |
import kotlin.random.Random | |
import kotlin.streams.toList | |
fun <F> putStrLn(A: MonadDefer<F>, line: String): Kind<F, Unit> = A { | |
println(line) | |
} | |
fun <F> getStrLn(A: MonadDefer<F>): Kind<F, String> = A { | |
readLine() ?: throw IOException("Failed to read input!") | |
} | |
class Hangman<F>(val A: MonadDefer<F>) { | |
data class State(val name: String, val guesses: Set<Char> = emptySet(), val word: String) { | |
val failures: Int = (guesses.toSet().minus(word.toSet())).size | |
val playerLost: Boolean = failures > 8 | |
val playerWon: Boolean = (word.toSet().minus(guesses)).isEmpty() | |
} | |
val dictionary: List<String> by lazy { | |
javaClass.classLoader.getResource("words.txt") | |
.openStream() | |
.bufferedReader() | |
.lines() | |
.toList() | |
} | |
val hangman: Kind<F, Unit> = A.binding { | |
putStrLn(A, "Welcome to purely functional hangman").bind() | |
val name = getName.bind() | |
putStrLn(A, "Welcome $name. Let's begin!").bind() | |
val word = chooseWord.bind() | |
val state = State(name, word = word) | |
renderState(state).bind() | |
gameLoop(state).bind() | |
Unit | |
} | |
fun gameLoop(state: State): Kind<F, State> = A.binding { | |
val guess = getChoice().bind() | |
val updatedState = state.copy(guesses = state.guesses.plus(guess)) | |
renderState(updatedState).bind() | |
val loop = when { | |
updatedState.playerWon -> putStrLn(A, "Congratulations ${state.name} you won the game").map { false } | |
updatedState.playerLost -> putStrLn(A, "Sorry ${state.name} you lost the game. The word was ${state.word}").map { false } | |
updatedState.word.contains(guess) -> putStrLn(A, "You guessed correctly!").map { true } | |
else -> putStrLn(A, "That's wrong, but keep trying").map { true } | |
}.bind() | |
if (loop) gameLoop(updatedState).bind() else updatedState | |
} | |
val getName: Kind<F, String> = A.binding { | |
putStrLn(A, "What is your name: ").bind() | |
getStrLn(A).bind() | |
} | |
fun getChoice(): Kind<F, Char> = A.binding { | |
putStrLn(A, "Please enter a letter").bind() | |
val line = getStrLn(A).bind() | |
val char = line.toLowerCase().first().toOption().fold( | |
{ | |
putStrLn(A, "Please enter a letter") | |
.flatMap { getChoice() } | |
}, | |
{ char -> | |
A { char } | |
} | |
).bind() | |
char | |
} | |
fun nextInt(max: Int): Kind<F, Int> = A { Random.nextInt(max) } | |
val chooseWord: Kind<F, String> = A.binding { | |
val rand = nextInt(dictionary.size).bind() | |
dictionary[rand] | |
} | |
fun renderState(state: State): Kind<F, Unit> { | |
val word = state.word.toList().map { if (state.guesses.contains(it)) " $it " else " " } | |
.joinToString("") | |
val line = state.word.map { " - " }.joinToString("") | |
val guesses = "Guesses: ${state.guesses.toList().sorted().joinToString("")}" | |
val text = "$word\n$line\n\n$guesses\n" | |
return putStrLn(A, text) | |
} | |
} | |
fun main() { | |
Hangman(SingleK.monadDefer()).hangman.fix().single.subscribe() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment