1

I wrote a simple guess the number game. But it keeps looping even when I input the correct number. Please help, thanks!

puts "Pick a number between 0 - 1000."
user_guess = gets.chomp.to_i

my_num = rand(831)
guess_count = 0

until user_guess == my_num do
  if user_guess == my_num
    guess_count += 1
    puts "you got it!"
  elsif  user_guess <= 830
    guess_count += 1
    puts "higher"
  else user_guess >= 1000
    guess_count += 1
    puts "lower"
  end 
end

puts "You guessed my number in #{guess_count} attempts. Not bad"
pjs
  • 18,696
  • 4
  • 27
  • 56
bulk_jedi
  • 11
  • 1

2 Answers2

1

The part of the code that asks for a number from the user is outside the loop, so it will not repeat after the answer is checked. If you want to ask the user to guess again when their guess is wrong, that code needs to be inside the loop.

my_num = rand(831)
guess_count = 0
keep_going = true


while keep_going do
  puts "Pick a number between 0 - 1000."
  user_guess = gets.chomp.to_i
  if user_guess == my_num
    guess_count += 1
    puts "you got it!"
    keep_going = false
  elsif  user_guess <= 830
    guess_count += 1
    puts "higher"
  else user_guess >= 1000
    guess_count += 1
    puts "lower"
  end 
end


puts "You guessed my number in #{guess_count} attempts. Not bad"

This code still has some bugs in it that stops the game from working correctly though, see if you can spot what they are.

Tobias Cohen
  • 19,893
  • 7
  • 54
  • 51
1

As @Tobias has answered your question I would like to take some time to suggest how you might make your code more Ruby-like.

Firstly, while you could use a while or until loop, I suggest you rely mainly on the method Kernel#loop for most loops you will write. This simply causes looping to continue within loop's block until the keyword break is encountered1. It is much like while true or until false (commonly used in some languages) but I think it reads better. More importantly, the use of loop protects computations within its block from prying eyes. (See the section Other considerations below for an example of this point.)

You can also exit loop's block by executing return or exit, but normally you will use break.

My second main suggestion is that for this type of problem you use a case statement rather than an if/elsif/else/end construct. Let's first do that using ranges.

Use a case statement with ranges

my_num = rand(831)
guess_count = 0

loop do
  print "Pick a number between 0 and 830: "
  guess_count += 1
  case gets.chomp.to_i
  when my_num
    puts "you got it!"
    break
  when 0..my_num-1
    puts "higher"
  else
    puts "lower"
  end
end

There are a few things to note here.

  • I used print rather than puts so the user will enter their response on on the same line as the prompt.
  • guess_count is incremented regardless of the user's response so that can be done before the case statement is executed.
  • there is no need to assign the user's response (gets.chomp.to_i) to a variable.
  • case statements compare values with the appropriate case equality method ===.

With regard to the last point, here we are comparing an integer (gets.chomp.to_i) with another integer (my_num) and with a range (0..my_num-1). In the first instance, Integer#=== is used, which is equivalent to Integer#==. For ranges the method Range#=== is used.

Suppose, for example, that my_num = 100 and gets.chomp.to_i #=> 50 The case statement then reads as follows.

case 50
when 100
  puts "you got it!"
  break
when 0..99
  puts "higher"
else
  puts "lower"
end

Here we find that 100 == 50 #=> false and (0..99) === 50 #=> true, so puts "higher" is displayed. (0..99) === 50 returns true because the integer (on the right of ===) is covered by the range (on the left). That is not the same as 50 === (0..90), which loosely reads, "(0..99) is a member of 50", so false is returned.

Here are a couple more examples of how case statements can be used to advantage because of their reliance on the triple equality method.

case obj
when Integer
  obj + 10
when String
  obj.upcase
when Array
  obj.reverse
...
end

case str
when /\A#/
  puts "A comment"
when /\blaunch missiles\b/
  big_red_button.push
...
end 

Use a case statement with the spaceship operator <=>

The spaceship operator is used by Ruby's Array#sort and Enumerable#sort methods, but has other uses, as in case statements. Here we can use Integer#<=> to compare two integers.

my_num = rand(831)
guess_count = 0

loop do
  print "Pick a number between 0 and 830: "
  case gets.chomp.to_i <=> my_num
  when 0
    puts "you got it!"
    break
  when -1         
    puts "higher"
  else # 1
    puts "lower"
  end
end

In other applications the spaceship operator might be used to compare strings (String#<=>), arrays (Array#<=>), Date objects (Date#<=>) and so on.

Use a hash

Hashes can often be used as an alternative to case statements. Here we could write the following.

response = { -1=>"higher", 0=>"you got it!", 1=>"lower" }
my_num = rand(831)
guess_count = 0

loop do
  print "Pick a number between 0 and 830: "
  guess = gets.chomp.to_i
  puts response[guess <=> my_num]
  break if guess == my_num
end

Here we need the value of gets.chomp.to_i twice, so I've saved it to a variable.

Other considerations

Suppose we write the following:

i = 0
while i < 5
  i += 1
  j = i
end
j #=> 5

j following the loop is found to equal 5.

If we instead use loop:

i = 0
loop do
  i += 1
  j = i
  break if i == 5
end
j #=> NameError (undefined local variable or method 'j')

Although while and loop both have access to i, but loop confines the values of local variables created in its block to the block. That's because blocks create a new scope, which is good coding practice. while and until do not use blocks. We generally don't want code following the loop to have access to local variables created within the loop, which is one reason for favouring loop over while and until.

Lastly, the keyword break can also be used with an argument whose value is returned by loop. For example:

def m
  i = 0
  loop do
    i += 1
    break 5*i if i == 10
  end
end

m #=> 50

or

i = 0
n = loop do
  i += 1
  break 5*i if i == 10
end
n #=> 50

1. If you examine the doc for Kernel#loop you will see that executing break from within loop's block is equivalent to raising a StopIteration exception.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100