I am trying to make a number guessing game in Ruby but the program exits after I type in yes when I want to play again. I tried using the catch and throw but it would not work. Could I please get some help.
Here is my code.
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
until $g==$a
puts "Guess the number between 0-10."
$g=gets.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets()
if $s=="yes"
$c=true
end
if $c==true
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
Game.new.Play
Edit: Here's my new code after trying suggestions:
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
while $s=="yes"
until $g==$a
puts "Guess the number between 0-10."
$g=gets.chomp.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets.chomp
if $s=="yes"
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
end
Game.new.Play
Your first problem is here:
$s=gets()
if $s=="yes"
$c=true
end
The gets method will read the next line including the new line character '\n', and you compare it to only "yes":
> gets
=> "yes\n"
The idiomatic way to fix this in Ruby is the chomp method:
> gets.chomp
=> "yes"
That said, your code has two other deficiencies.
You may come from a language such as PHP, Perl, or even just Bash scripting, but Ruby doesn't require the dollar sign before variables. Using a $ gives a variable global scope, which is likely not what you want. In fact, you almost never want a variable to have global scope.
Ruby uses three types of symbol prefixes to indicate scope - # for instance, ## for class, and $ for global. However the most common type of variable is just local which doesn't need any prefix, and what I would suggest for your code.
I have always been told that it is very bad practice to use exceptions for control structure. Your code would be better served with a while/break structure.
When you do gets(), it retrieves the full line with a '\n' in the end. You need to trim the new line character by using:
$g=gets.chomp.to_i
Same for other gets
Based on your updated code (where you fixed the newline problem shown by others), your new problem is that you have wrapped all your game inside while $s=="true". The very first time your code is run, $s is nil (it has never been set), and so you never get to play. If you used local variables instead of global variables (s instead of $s) this would have become more obvious, because the code would not even have run.
Here's one working way that I would re-write your game.
class Game
def play
keep_playing = true
while keep_playing
answer = rand(11) # Make a new answer each time
puts answer if $DEBUG # we don't normally let the user cheat
loop do # keep going until I break from the loop
puts "Guess the number between 0-10."
guess = gets.to_i # no need for chomp here
if guess>answer
puts "The number you guessed is too high."
elsif guess<answer
puts "The number you guessed is too low."
else
puts "Correct you won!!!",
"Would you like to play again?"
keep_playing = gets.chomp.downcase=="yes"
break
end
end
end
end
end
Game.new.play
I know this doesn't really answer your question about why your code isn't working, but after seeing the code you posted I just had to refactor it. Here you go:
class Game
def initialize
#answer = rand(11)
end
def play
loop do
guess = get_guess
display_feedback guess
break if guess == #answer
end
end
def self.play_loop
loop do
Game.new.play
break unless play_again?
end
end
private
def get_guess
puts "Guess the number between 0-10."
return gets.chomp.to_i
end
def display_feedback(guess)
if guess > #answer
puts "The number you guessed is too high."
elsif guess < #answer
puts "The number you guessed is too low."
elsif guess == #answer
puts "Correct you won!!!"
end
end
def self.play_again?
puts "Would you like to play again?"
return gets.chomp == "yes"
end
end
Game.play_loop
Related
I have a program that displays a numbered list and asks the user to input either a number or name from the list, and loops a block until the user enters "exit", after which it ends.
I want to add a line or two that puts an error message like, "Sorry, I don't seem to understand your request" if the user inputs something that is not on the list (name/number) and is not the word "exit".
I can't seem to figure it out. Any advice? My current code is below.
def start
display_books
input = nil
while input != "exit"
puts ""
puts "What book would you more information on, by name or number?"
puts ""
puts "Enter list to see the books again."
puts "Enter exit to end the program."
puts ""
input = gets.strip
if input == "list"
display_books
elsif input.to_i == 0
if book = Book.find_by_name(input)
book_info(book)
end
elsif input.to_i > 0
if book = Book.find(input.to_i)
book_info(book)
end
end
end
puts "Goodbye!!!"
end
Seems that you should add an elsif statement in this if:
if book = Book.find_by_name(input)
book_info(book)
elsif input != 'exit'
puts "Sorry, I don't seem to understand your request"
end
A good template for an interpreter is to build around Ruby's very capable case statement:
loop do
case (gets.chomp.downcase)
when 'list'
display_books
when /\Afind\s+(\d+)/
if book = Book.find($1.to_i)
book_info(book)
end
when /\Afind\s+(.*)/
if book = Book.find_by_name($1)
book_info(book)
end
when 'exit'
break
else
puts "Not sure what you're saying."
end
end
Although this involves regular expressions, which can be a bit scary, it does give you a lot of flexibility. \A represents "beginning of string" as an anchor, and \s+ means "one or more spaces". This means you can type in find 99 and it will still work.
You can create a whole command-line interface with it if you take the time to specify the commands clearly. Things like show book 17 and delete book 17 are all possible with a bit of tinkering.
I have a code below:
secret_number = 8
user_input = ""
def number_guesser(user_input)
while user_input != secret_number
puts "Guess a number between 1 and 10:"
user_input = gets.chomp
if user_input != secret_number
puts "Wrong! Try again."
else
puts "You guessed correctly!"
end
end
end
number_guesser(user_input)
when I tried to run the above program it showed as below:
****undefined local variable or method secret_number' for main:Object
(repl):211:innumber_guesser'
(repl):221:in `'****
Any ideas?
You can't use a local variable like that inside another scope such as a method, it's two different contexts. Instead you need to pass that in if you want to use it.
It's a simple change:
def number_guesser(user_input, secret_number)
# ...
end
Then just feed that argument in.
You'll note that user_input isn't really necessary as a parameter, you can always initialize and use that locally, so it's actually pointless as an argument.
The pattern to use in that case:
loop do
input = gets.chomp
# Prompting...
break if input == secret_number
# Guessed wrong...
end
So I've been messing around with Ruby for the first time after finishing the codecademy course up to "Object Oriented Programming, Part I" and I decided to start making a calculator. For some reason though, I get this error:
calc.rb:13:in `addition': undefined local variable or method `user_input' for main:Object (NameError)
from calc.rb:21:in `<main>'
I'm confused why it doesn't see my "user_input" array. Is it out of the scope of the method? Did I initialize it wrong?
Here's the code so you can see for yourself, it's obviously nothing sophisticated and it's not finished. I'm just trying to test for addition right now.
#!/usr/bin/env ruby
user_input = Array.new
puts "Would you like to [a]dd, [s]ubtract, [m]ultiply, or [d]ivide? "
type_of_math = gets.chomp
def addition
operator = :+
puts "Please enter the numbers you want to add (enter \"=\" to stop adding numbers): "
until gets.chomp == "="
user_input << gets.chomp.to_i
end
sum = user_input.inject(operator)
return sum
end
case type_of_math
when "a"
addition
when "s"
puts "Test for subtraction"
when "m"
puts "Test for multiplication"
when "d"
puts "Test for division"
else
puts "Wrong"
end
Consider this untested variation on your code. It's more idiomatic:
def addition
user_input = []
puts 'Please enter the numbers you want to add (enter "=" to stop adding numbers): '
loop do
input = gets.chomp
break if input == '='
user_input << input
end
user_input.map(&:to_i).inject(:+)
end
Notice that it puts user_input into the method. It also uses the normal [] direct assignment of an empty array to initialize it. Rather than chomp.to_i each value as it's entered it waits to do that until after the loop exits.
Instead of while loops, consider using loop do. They tend to be more easily seen when scanning code.
Also notice there's no return at the end of the method. Ruby automatically returns the last value seen.
I decided to make a number-guessing game. Here is the code.
print "Guess a number from 1-20. You have 5 guesses!"
guess1=gets.chomp
guess1=guess1.to_i
random=1 + rand(20)
random=random.to_i
if guess1 == random
puts "Correct! You win!!!"
sleep(5)
Kernel.exit
elsif guess1 > random
puts "Wrong! Too high. Try again!"
else
puts "Wrong! Too low. Try again!"
end
guess2=gets.chomp
guess2=guess2.to_i
if guess2 == random
puts "Correct! You win!!!"
sleep(5)
Kernel.exit
elsif guess2 > random
puts "Wrong! Too high. Try again!"
else
puts "Wrong! Too low. Try again!"
end
guess3=gets.chomp
guess3=guess3.to_i
if guess3 == random
puts "Correct! You win!!!"
sleep(5)
Kernel.exit
elsif guess3 > random
puts "Wrong! Too high. Try again!"
else
puts "Wrong! Too low. Try again!"
end
guess4=gets.chomp
guess4=guess4.to_i
if guess4 == random
puts "Correct! You win!!!"
sleep(5)
Kernel.exit
elsif guess4 > random
puts "Wrong! Too high. Try again!"
else
puts "Wrong! Too low. Try again!"
end
guess5=gets.chomp
guess5=guess5.to_i
if guess5 == random
puts "Correct! You win!!!"
sleep(5)
Kernel.exit
elsif guess5 > random
puts "Wrong! Too high. Game over!"
sleep(5)
Kernel.exit
else
puts "Wrong! Too low. Game over!"
sleep(5)
Kernel.exit
end
How would I add a try-again option at the end that would restart the game?
As Sergio said, a very clean solution would be to learn about functions, and divide up your game logic, so that you can call the function(s) when needed.
Another solution would be to use loops, specifically a do...while loop.
Side note: The begin...while style of looping in the tutorial link I posted is not recommended by Matz, the creator of Ruby. He recommends the loop do...break end style, which is what I will demonstrate.
I'll let you read the tutorials to learn more about whats going on, but the gist is that this particular style of loop will run your code once, and it will either loop back and run it again, or exit the loop and the program will end, depending on the result of our "control" variable.
Unfortunately, because of the way you've written your code, wrapping the program in a simple do...while is very awkward because of the Kernel.exit lines, and the fact that you've hard coded 5 guesses which are run in sequence. Since my answer involves loops, I'll quickly show you a good way to refactor the code without too much pain. Please note the comments in the code to understand what's going on.
loop do # The start of the main game loop
random=1 + rand(20)
random=random.to_i
guess_count = 0 # Tracks the number of times the user has guessed
print "Guess a number from 1-20. You have 5 guesses!"
while guess_count < 5 # The guess loop; keep looping up to a max of 5 times
guess=gets.chomp
guess=guess.to_i
if guess == random
puts "Correct! You win!!!"
sleep(5) # 5 seconds is a very long time, I would reduce this to 1 at most
break # This causes the 'guess loop' to end early
elsif guess > random
puts "Wrong! Too high. Try again!"
else
puts "Wrong! Too low. Try again!"
end
guess_count+= 1 # increment the guess count if the user guessed incorrectly
end
puts "Would you like to play again? (y/n)"
play_again = gets.chomp
break if play_again != "y" # exit the main loop if the user typed anything except "y"
end # The end of the main loop, and thus the entire program
# No need for Kernal.exit. The program is done at this point.
Note: I removed your comments so they don't interfere with the explanation. Feel free to put them back in your version.
Take a look at loops in the tutorials for more details. Hope thats clear.
What you want to do is make the main thread of your game a loop:
loop do
# Prompt
end
This will execute endlessly whatever is inside it until an exception is raised or the program is interrupted.
Now, to make it effective, you'll want to start wrapping up your game logic in an Object.
For instance
class GuessingGame
def initialize
#secret = 1 + rand(20)
#guesses_remaining = 5
end
def is_it?(number)
#guesses_remaining -+ 1
#secret == number
end
end
Now, when you start the game look, you just make GuessingGame.new, and after the game is over, by running out of guesses, or getting it right, you can just prompt to retry and make a new guessing game
First, extract the repeated part into a method:
def correctly_guessed?(target)
guess = gets.to_i
if guess == target then puts "Correct! You win!!!"; sleep(5)
elsif guess > target then puts "Wrong! Too high. Try again!"
else puts "Wrong! Too low. Try again!"
end
end
Then, restructure your code:
loop do
print "Guess a number from 1-20. You have 5 guesses!"
target = 1 + rand(20)
5.times{break if correctly_guessed?(target)}
print "Restart the game? (Y to restart)"
break unless gets.chomp == "Y"
end
I'm currently learning ruby from the Learn Ruby the hard way tutorial. And in that exercise, the author ask us to add things to a simple game. However, I was trying this to improve the bear_room method by doing something like this:
while true
print "> "
choice = gets.chomp.downcase!
if choice.include? ("taunt")
dead("The bear looks at you then slaps your face off.")
elsif choice.include? "taunt" && !bear_moved
puts "The bear has moved from the door. You can go through it now."
bear_moved = true
elsif choice.include? "taunt" && bear_moved
dead("The bear gets pissed off and chews your leg off.")
elsif choice.include? "open" && bear_moved
However, when I write this:
choice = gets.chomp.downcase!
It gives me this error when executing:
ex35.rb:44:in `bear_room': undefined method `include?' for nil:NilClass (NoMethodError)
from ex35.rb:99:in `start'
from ex35.rb:109:in `<main>'
But if do something like this:
choice = gets.chomp.downcase
or this:
choice = gets.chomp
choice.downcase!
It works. Why is that? I would appreciate any kind of help. Also, how works the while true bit? That really gets me confused.
Here is the rest of the program in case that you need it. I'm going to leave "separated" the mentioned method to make it easier to read.
# Creates the 'gold_room' method, so it can be called later.
def gold_room
puts "This room is full of gold. How much do you take?"
print "> "
choice = gets.chomp
# Converts the 'choice' variable to integer type.
choice.to_i
# Checks if 'choice' is equals to 0, OR greater or equal to 1.
if choice == '0' || choice >= '1'
# Saves the integer 'choice' variable in the 'how_much' variable.
how_much = choice.to_i
else
dead("Man, learn to type a number.")
end
# Checks if the 'how_much' variable is lesser than 50, and executes the code below if so.
if how_much < 50
puts "Nice, you're not greedy, you win!"
exit(0)
elsif how_much >= 50 && how_much < 100
puts "Mmm, ok that's enough. Get out!"
else
dead("You greedy bastard!")
end
end
################### bear_room method ###################
# Creates the 'bear_room' method.
def bear_room
puts "There is a bear here."
puts "The bear has a bunch of honey."
puts "The fat bear is in front of another door."
puts "How are you going to move the bear?"
puts "1. Taunt the bear."
puts "2. Steal the bear's honey. "
# Declares the 'bear_moved' variable as a boolean, initialize it to false.
bear_moved = false
while true
print "> "
choice = gets.chomp.downcase
if choice.include? ("taunt")
dead("The bear looks at you then slaps your face off.")
elsif choice.include? "taunt" && !bear_moved
puts "The bear has moved from the door. You can go through it now."
bear_moved = true
elsif choice.include? "taunt" && bear_moved
dead("The bear gets pissed off and chews your leg off.")
elsif choice.include? "open" && bear_moved
gold_room
else
puts "I got no idea what that means."
end
end
end
############### end of method ###############
# Defines the 'cthulhu_room' method.
def cthulhu_room
puts "Here you see the great evil Cthulhu."
puts "He, it, whatever stares at you and you go insane."
puts "Do you flee for your life or eat your head?"
print "> "
choice = gets.chomp
# Checks if the user's input contains the word 'flee'. If so, executes the code below.
if choice.include? "flee"
start
# Checks if the user's input contains the word 'head'. If so, executes the code below instead.
elsif choice.include? "head"
dead("Well that was tasty!")
else
# Otherwise, calls the 'cthulhu_room' method again.
cthulhu_room
end
end
# Defines the 'dead' method. It takes one argument (why). Example: dead("Well that was tasty!")
def dead(why)
puts why, "Nice."
# Succesfully finish the program.
exit(0)
end
# Defines the 'start' method, wich is where the game begins. Duh.
def start
puts "You are in a dark room."
puts "There is a door to your right and left."
puts "Which one do you take?"
print "> "
choice = gets.chomp
# Start the branching. It checks the user's input, and saves that on the 'choice' variable, which is used along the whole program in the other methods.
if choice == "left"
# Calls the 'bear_room' method.
bear_room
elsif choice == "right"
# Calls the 'cthulhu_room' method.
cthulhu_room
else
dead("You stumble around the room until you starve.")
end
end
# Start the game.
start
---------------------------------------------------------------------------
TL;DR: What's the difference between choice = gets.chomp.downcase! and
choice = gets.chomp
choice.downcase!
PS: The comments are part of the exercise. Please, if you have any type of correction (about the comments, how I made the question, code in general, etc) please tell me so I can improve. Thanks and sorry for the length!
-------------------------------------------------------------------------
That is because if the string did not change upon calling downcase! it returns nil. Thus when you try and call include? it says that nil does not have such a method.
Thus, it is safest to use the "non-destructive" version of downcase. The downcase! method mutates the string in place if it can.
Check the docs for further reading.