✅ Validate user guesses

While we can now createbasic.Targetandbasic.Guessvalues and print theResultof 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 thedictionary : Set Textrepresenting 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 withreadLine
📚Getting user input withreadLine

We learned how to print a response to the console withIOusingprintLine—nowwe need togetthe user's response. The function for that isreadLinefrom thebaselibrary.

readLineis adelayed 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 theAskability
📚Using theAskability

In the signature provided forstubs.getUserInput,we're using theAskability to represent the data we receive when we read the dictionary file into memory.Askhas one request constructor,askwhich we can call when we need to inspect the dictionary data. Otherwise, theAskability requirement can simply be passed through the function call chain.

Use theAskability 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 forbasic.getUserInputis 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 thereadLinefunction 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 withText.normalize.

The first condition that we check is the length of the user's input. If it's not5,we start thebasic.getUserInputfunction over again.

The second condition is if the user's guess is in the dictionary, represented by aSetofText.We're using theAskability 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 withbasic.overrideLookup.The dictionary provided doesn't do a great job at identifying pluralized versions of words or past-tense verb forms. (See thechallenge optionsfor 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 dictionarySet,or if the user accepts the override will we then call thebasic.Guess.fromTextfunction we wrote earlier.

Next steps

👉Game loop state and dictionary