โœ… Validate user guesses

While we can now create basic.Target and basic.Guess values and print the Result of their evaluation to the console, we're missing a key part of the Wordle experience! A user should not be able to guess a word that's shorter or longer than five characters, and the user should not be able to guess a nonsense word. If the user tries to do this, they should get a helpful message and have the opportunity to guess again.

Given the signature below, write the function, stubs.getUserInput:

It should do the following:

  • Read the user input from the console
  • Check its length
    • Check if it's a real word using the Set Text representing valid 5 letter words.
      • Let the user override the dictionary validation (pluralization and past-tense aren't well reflected)
  • Continue to prompt the user until the user enters a valid guess
๐Ÿ“š Getting user input with readLine
๐Ÿ“š Getting user input with readLine

We learned how to print a response to the console with IO using printLineโ€”now we need to get the user's response. The function for that is readLine from the base library.

readLine is a delayed computation, so it won't try to read the user's input unless we call it with the ! operator. The ! operator is syntactic sugar for calling a function with no arguments, () -> a.

๐ŸŽฅ Running the console in a loop
๐ŸŽฅ Running the console in a loop

Watch an example of running input and output in a loop:

๐Ÿ“š Using the Ask ability
๐Ÿ“š Using the Ask ability

In the signature provided for stubs.getUserInput, we're using the Ask ability to represent the data we receive when we read the dictionary file into memory. Ask has one request constructor, ask which we can call when we need to inspect the dictionary data. Otherwise, the Ask ability requirement can simply be passed through the function call chain.

Use the Ask ability when you need to pass around data in an environment, but don't want to provide it explicitly as an argument to each upstream function.

Resources

Solutions

๐Ÿ”‘ Walk through rendering and running what we have so far
๐Ÿ”‘ Walk through rendering and running what we have so far

The implementation for basic.getUserInput is a series of if/else clauses. (Alternatively, you could use pattern matching.)

basic.getUserInput : '{IO, Exception, Ask (Set Text)} basic.Guess
basic.getUserInput _ =
  use Nat !=
  use basic getUserInput
  use basic.Guess fromText
  input = !readLine
  normalized = Text.normalize input
  if Text.size normalized != 5 then
    printLine "๐Ÿ– guess must be a 5 letter word"
    !getUserInput
  else
    dict = ask
    if Set.contains normalized dict then fromText normalized
    else
      acceptOverride = basic.overrideLookup normalized ()
      if acceptOverride then fromText normalized else !getUserInput

We have to call the readLine function with ! because it's a delayed computation and would otherwise never prompt the user for input. We then strip trailing whitespace and lowercase the input by default with Text.normalize.

The first condition that we check is the length of the user's input. If it's not 5, we start the basic.getUserInput function over again.

The second condition is if the user's guess is in the dictionary, represented by a Set of Text. We're using the Ask ability to provide this information. We'll provide the dictionary to the Ask ability in a later step.

Finally, we provide the user an override option with basic.overrideLookup. The dictionary provided doesn't do a great job at identifying pluralized versions of words or past-tense verb forms. (See the challenge options for some ideas about how to solve this.)

basic.overrideLookup : Text -> '{IO, Exception} Boolean
basic.overrideLookup normalized _ =
  use Text ++
  printLine
    ("๐Ÿค” That wasn't in the dictionary.\nWould you like to use "
      ++ normalized
      ++ " as your guess?")
  !basic.yesNo

Only if the word is 5 letters long, present in the dictionary Set, or if the user accepts the override will we then call the basic.Guess.fromText function we wrote earlier.

Next steps

๐Ÿ‘‰ Game loop state and dictionary