2

I'm writing a multiple choice quiz where the user has to choose an answer by entering a letter from a to d. Each question prompt looks like this:

What is the last letter in alphabet?
(a) Y
(b) R
(c) Z
(d) Q

If the user enters anything else, I want to show a message and print the whole question again until the input is a, b, c or d.

Here's what I've tried: (simplified)

question = "What is the last letter in alphabet?\n(a) Y\n(b) R\n(c) Z\n(d) Q"

puts question
answer = gets.chomp.to_s
while answer > "d"
  puts "Please enter a, b, c, or d"
  puts
  puts question
  answer = gets.chomp.to_s
end

It works fine when entering e, f, g etc. but it doesn't catch input like ab, 1, Z, or when the user just presses enter.

Stefan
  • 109,145
  • 14
  • 143
  • 218
ajay_speed
  • 79
  • 9
  • 1
    Why are you talking about prompting the user for a name and when the rest of your question is about prompting for a letter between a and d? What is the actual goal here? – David Grayson Sep 23 '22 at 07:38
  • @ajay_speed instead of adding "this is only an example" you should remove the unrelated example and explain your actual problem right away. You can also remove any information that's not relevant to the specific problem like those difficulty levels (they are important for your game but not for input validation). See [ask] – Stefan Sep 23 '22 at 10:02
  • Some tips in that regard: describe your problem briefly, be specific. Show what you have so far and explain what is already working and what is not yet working. When showing code, simplify it as much as possible. Remove anything unrelated to the actual problem. – Stefan Sep 23 '22 at 11:03
  • @Stefan , have made some changes on my question. – ajay_speed Sep 23 '22 at 11:36
  • 1
    @ajay_speed since you seem to be struggling with the structure of your question, I've re-written it the way I would've put it. Feel free to edit it as needed or revert it if you don't like it. I hope this isn't too intrusive. – Stefan Sep 23 '22 at 11:50
  • @Stefan Thank you so much , I will follow this structure and detailing – ajay_speed Sep 23 '22 at 12:34
  • Your example was just a description of a different problem which you didn't actually care about solving, so it was a big distraction and risk for anyone trying to *help you*. It's like if I said: "I often have trouble getting to school. For example, sometimes the road is closed. What should I do if my car doesn't start?" Note that our answers ALSO get downvoted if they are perceived to have answered the wrong question or not fully anwered the question. – David Grayson Sep 23 '22 at 15:23
  • Debug questions require a [mre]--cut & paste & runnable code including initialization; desired & actual output (including verbatim error messages); tags & versions; clear specification & explanation. For debug that includes the least code you can give that is code that you show is OK extended by code that you show is not OK. [ask] [Help] When you get a result you don't expect, find the first point in the execution where the state of the variables is not what you expect & say what you expected & why, justified by documentation. (Debugging fundamental.) PS You've been told this multiple times. – philipxy Oct 12 '22 at 03:41

3 Answers3

3

You have only four allowed inputs. Check for those directly. Even if as inelegantly as if !(answer == "a" || answer == "b" ...). Anything else will accept some invalid input. In particular, if answer > "d" allows "ab" as a valid answer.

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
3

Your approach doesn't work because answer > "d" compares both strings character-wise using their (Unicode) code points. To understand what this means, take a look at the Basic Latin chart: (it's equivalent to an ASCII table)

Basic Latin chart

"d" has a codepoint of U+0064. Any smaller codepoint – i.e every character in the chart before "d" – is regarded smaller. This includes all (regular) digits, all (basic latin) uppercase letters and several symbols:

"0" > "d"  #=> false
"Z" > "d"  #=> false

You could add a lower bound like answer < "a" || answer > "d" but this would still allow all strings starting with one of the allowed characters, e.g.:

"apple" < "a" || "apple" > "d" #=> false

To actually limit the answer to the four allowed values, you have to compare the string to each of them. You could combine these comparisons:

answer == 'a' || answer == 'b' || answer == 'c' || answer == 'd'

use a loop over an array of allowed values:

['a', 'b', 'c', 'd'].any? { |letter| answer == letter }

check whether the array includes the answer:

['a', 'b', 'c', 'd'].include?(answer)

# or

%w[a b c d].include?(answer)

or use a regular expression to match a to d:

answer.match?(/\A[a-d]\z/)

Note that the examples above become true if answer is between a and d. You could either negate the condition via !(...):

while !(%w[a b c d].include?(answer))
  # ...
end

or use until instead of while:

until %w[a b c d].include?(answer)
  # ...
end
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • [Why should I not upload images of code/data/errors when asking a question?](https://meta.stackoverflow.com/q/285551/3404097) [Why are images of text, code and mathematical expressions discouraged?](https://meta.stackexchange.com/q/320052/266284) – philipxy Oct 12 '22 at 03:43
3

Other answers have answered the question given in the title of your question.

I would like to suggest that you consider adopting some variation of the following code.

def get_answer(question, answers)
  choices_to_answers = construct_choices_to_answers(answers)
  loop do 
    puts "#{question}"
    display_choices(choices_to_answers)
    print "answer: "
    choice = gets.chomp
    puts choice
    break choices_to_answers[choice] if choices_to_answers.key?(choice)
    puts "\nThat answer is invalid."
  puts "Enter a letter between 'a' and '#{choices_to_answers.keys.last}'\n"
  end
end
def construct_choices_to_answers(answers)
  ('a'..('a'.ord + answers.size - 1).chr).to_a.zip(answers).to_h
end
def display_choices(choices_to_answers)
  choices_to_answers.each { |k,v| puts "(#{k}): #{v}" }
end

Suppose the possible answers were as follows.

answers = %w| Y R Z Q |
  #=> ["Y", "R", "Z", "Q"]

Then

choices_to_answers = construct_choices_to_answers(answers)
  #=> {"a"=>"Y", "b"=>"R", "c"=>"Z", "d"=>"Q"}

and

display_choices(choices_to_answers)

prints

(a): Y
(b): R
(c): Z
(d): Q

Here I've assumed that the choices are always consecutive letters beginning with "a". If that assumption is correct there is no need to manually associate those letters with the possible answers.


Let me now show a possible conversation with the method (with answers as defined above).

question = "What is the last letter in the alphabet?"
get_answer(question, answers)
  #=> "R"

The following is displayed.

What is the last letter in the alphabet?
(a): Y
(b): R
(c): Z
(d): Q    
answer: e

That answer is invalid.
Enter a letter between 'a' and 'd'

What is the last letter in the alphabet?
(a): Y
(b): R
(c): Z
(d): Q
answer: b

You might then construct an array of hashes that provide the question, possible answers, correct answer, possibly the weight of the question and a boolean indicating whether a correct answer was given.

questions = [
  ...
  { question: "What is the last letter in the alphabet?",
    answers: ["Y", "R", "Z", "Q"],
    correct_answer: "Z",
    weight: 1,
    correct_answer_given: nil
  },
  ...
]

The value of :correct_answer_given for each question is updated to true or false when the question is answered. Once all questions have been answered questions can be used to determine a mark for the exam.


If you wish to assign a number to each question you could write

question_number = 0
questions.each do |h|
  question_number += 1
  g = h.merge(question: "#{question_number}. #{h[:question]}")
  answer = get_answer(question, g[:answers])      
  h[:correct_answer_given] = h[:correct_answer] == answer
end

If question_number #=> 12 and

h[:question]
  #=> "What is the last letter in the alphabet?",

then

g[:question]
  #=> "12. What is the last letter in the alphabet?"
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100