Wordle wouldn't be complete without the results being appropriately hued! 🧑🏼🎨 Fortunately, we can rely on a library for bulk of this functionality. Take a look at the Terminus library for ANSI terminal interactions We'll use that for producing green and yellow text.
Pull that library down and install it as a dependency with the pull
command. It should look something like
wordle/main> pull @runarorama/terminus/releases/0.0.2 lib.terminus_0_0_2
See if you can write a function that renders the stubs.Result
of a user's guess appropriately.
- A character that is in its proper location should be green
- A character that exists in the target word should be yellow
- All others can remain unchanged
Users will likely need to see the history of their guesses, so it would be nice if we also had a rendering function that could print out the grid of guesses too.
use stubs Result
renderChar : Result -> Text
renderChar = todo "render single character"
renderRow : [Result] -> Text
renderRow results = todo "render the guessed word"
renderGuesses : [[Result]] -> Text
renderGuesses guesses = todo "render the list of the user's guesses"
Then write a function which exercises all the functionality we've written thus far, including creating the stubs.Target
, and stubs.Guess
values, tallying up their results, and printing them to the console with the printLine
function found in base. You can call it whatever you'd like but you should use the run
command to start it, and it should have the signature:
stubs.renderTest : '{IO, Exception} ()
You may have to play around with different colors in the foreground or background to get a readable colorized letter depending on your console.
Here's an example of how you can set the foreground and background color of text using the library.
colorize = do
style.fg (Bright IsoColor.Red)
printLine "I am red"
style.fg (Bright IsoColor.Green)
printLine "I am green"
style.bg (Bright IsoColor.Blue)
printLine "I have a blue background"
style.reset
printLine "I am back to normal"
Enter run colorize
in the UCM to see the output.
These functions work by adding an ansi escape sequence to the given text argument. The terminal can interpet specific byte sequences as commands instead of text.
printLine
and the IO abilityprintLine
and the IO ability
The curly braces indicate printLine
has two ability requirements. Abilities are one of the ways we encode computational effects in the Unison language. We can read this signature as "printLine is a function which performs the IO and Exception abilities in the process of returning unit."
If you'd like, you can pause here and check out an introduction to abilities. But for now you should know that abilities can be broken down into two things:
- An interface which specifies the abstract operations of an effectful computation
- Handlers which supply the specific implementation for how the effect should behave
The handler for the IO
ability is special because it can only be supplied by the Unison runtime itself. You can run a function which performs the IO
and Exception
abilities with the UCM run
command. Instead of wrapping the code that performs an ability with a handler function in the program, you'll issue the run
command in the UCM console itself. The run command expects a delayed computation, which is commonly introduced by the keyword, do
.
Resources
Solution
The stubs.renderChar
function uses an earlier ansi library to set the foreground and background colors with functions prefixed fg
and bg
, respectively. The terminus library implementation should work similarly. These functions operate on Text
values so we first have to turn the Char
back into Text
values first.
basic.renderChar : basic.Result -> Text
basic.renderChar = cases
basic.Result.NotFound c ->
Char.toText (ascii.toUpper c) |> bg.black.span |> fg.white.span
basic.Result.Exists c ->
Char.toText (ascii.toUpper c) |> bg.black.span |> fg.yellow.span
basic.Result.InPlace c ->
Char.toText (ascii.toUpper c) |> bg.black.span |> fg.green.span
The pipe forward operator, |>
is being used here to take the result of running the function on the left hand side and apply it as an argument to the function on the right.
Both basic.renderRow
and basic.renderGuesses
make use of the Text.join
function to concatenate Text
values with a character delimeter.
basic.renderRow : [basic.Result] -> Text
basic.renderRow : [basic.Result] -> Text
basic.renderRow results =
Text.join "" (List.map basic.renderChar results)
basic.renderGuesses : [[basic.Result]] -> Text
basic.renderGuesses : [[basic.Result]] -> Text
basic.renderGuesses resultList =
List.map basic.renderRow resultList |> Text.join "\n"
Here's what a running test of what we've written so far might look like:
basic.renderTest : '{IO, Exception} ()
basic.renderTest : '{IO, Exception} ()
basic.renderTest = do
use basic Guess.fromText
use basic.Guess score
guess1 = Guess.fromText "hello"
guess2 = Guess.fromText "there"
target = basic.Target.fromText "world"
results1 = score guess1 target
results2 = score guess2 target
printLine (basic.renderGuesses [results1, results2])
wordle/main> run renderTest