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 theansi library hosted on share.We'll use that for producing green and yellow text.
Pull that library down and put the code fromansi
in alib.ansi
namespace where your wordle code islocated.
See if you can write a function that renders thestubs.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 thestubs.Target
,andstubs.Guess
values, tallying up their results, and printing them to the console with theprintLine
function found in base. You can call it whatever you'd like but you should use therun
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.
Conceptually, namespaces are similar to a directory tree structure. They help organize a codebase. Unison namespaces represent a mapping between terms and their definitions. Each namespace gets a hash which represents the terms that are found within it, and changes to the terms will change the corresponding namespace hash.
In Unison, incorporating library code unto your own is as simple as adding a namespace from another codebase.
The following commands will pull all of the code inside.stew.ansi
into a namespace in your wordle project calledlib.ansi
.
.> cd .wordle
.worldle> pull stew.public.projects.ansi.releases.v1 lib.ansi
The namespacelib.ansi
is arelative namespace,meaning it will be created in the namespace where the ucm command was issued. If we prefix that with a dot, as in.lib.ansi
,we would be downloading the code to afully qualified namespacestarting from the root of the codebase.
By convention, all of the dependencies of a project should be located in alib
namespace.
Here's an example of how you can set the background color of text using the ansi library.
bg.blue.span "Hello world"
The functions likebg.blue.span
orfg.yellow.span
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 indicateprintLine
has twoability 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 outan 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 theIO
ability is special because it can only be supplied by the Unison runtime itself. You can run a function which performs theIO
andException
abilities with the UCMrun
command.Instead of wrapping the code that performs an ability with a handler function in the program, you'll issue therun
command in the UCM console itself. The run command expects adelayed computationwhich has a final return type of Unit,()
.
Resources
Solution
Thestubs.renderChar
function uses the ansi library to set the foreground and background colors with functions prefixedfg
andbg
,respectively. These functions operate onText
values so we first have to turn theChar
back intoText
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.
Bothbasic.renderRow
andbasic.renderGuesses
make use of theText.join
function to concatenateText
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])
.> run renderTest