🎨 Render a colorized result

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:

You may have to play around with different colors in the foreground or background to get a readable colorized letter depending on your console.

Terminus library usage help
Terminus library usage help

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.

More on ANSI escape codes

📚 printLine and the IO ability
📚 printLine 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:

  1. An interface which specifies the abstract operations of an effectful computation
  2. 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

🔑 Walk through rendering and running what we have so far
🔑 Walk through rendering and running what we have so far

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.

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 results =
  Text.join "" (List.map basic.renderChar results)
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 = 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

Next steps

👉 Validation for user guesses