After going through the codecademy ruby section "A Night at the Movies", I wanted to extend the case-statement to allow input again. By the end my code was:
movies = {
living_torah: 5,
ushpizin: 5
}
def input #method for gets.chomp
gets.chomp.downcase
end
puts "To exit please type 'Quit' or 'Exit'"
puts 'Please type "add", "display", "update" or "delete".'
choice = input
case choice
when "add"
puts "Movie Title please:"
title = input.to_sym
puts "How would you rate it?"
rating = input.to_i
if movies[title].nil?
movies[title] = rating
puts "Movie: '#{title.to_s.capitalize}' added with a Rating of # {rating}."
else
puts "That Movie already exists. Try updating it."
end
when "update"
puts "Movie Title please:"
title = input.to_sym
if movies[title].nil?
puts "That Title doesn't exist. Please 'add' it."
else
puts "Your Movie was found. How would you rate it?"
rating = input.to_i
movies[title] = rating
puts "Movie: '#{title.to_s.capitalize}' updated with a Rating of #{rating}."
end
when "display"
movies.each { |movie, rating| puts "#{movie}: #{rating}" }
when "delete"
puts "Which Movie would you like to delete?"
title = input.to_sym
if movies[title].nil?
puts "That Title doesn't exist. Please 'add' it."
else
movies.delete(title)
puts "The Movie '#{title.to_s.capitalize}' has been deleted."
end
when "exit", "quit"
exit
else
puts "Invalid choice."
end
I added the "exit" case independently of the exercise hoping to C.R.U.D. until explicitly exiting the program. How would I change the code to be able to restart/reuse the case-statement indefinitely?
(Also, is there a simpler/shorter way to produce the same results as this case-statement?)
Well, you can put the entire case statement inside of a loop. Something like:
loop do
puts "To exit please type 'Quit' or 'Exit'"
puts 'Please type "add", "display", "update" or "delete".'
choice = input
case choice
# ...
when 'exit', 'quit'
break
end
end
However, large case statements like this are not idiomatic Ruby. You might consider more dynamic options, such as using object.send(method_name, args...).
Additionally, its also best to place your code inside of a class or module. This makes it easier to understand and keeps things organized. This is called encapsulation.
In the example below, you can see that a single method is responsible for a single piece of functionality, and the class as a whole is responsible for managing the delegation of its tasks. This is called the single responsibility principle.
class MyCode
# store the current state for this object in an accessor.
# `attr_accessor` defines a read-write property.
attr_accessor :running
def add_choice
# your "add" code here
end
def update_choice
# "update" code
end
def exit_choice
# change the state of this class by marking `running` as false
self.running = false
end
# `alias_method` defines a method called `quit_choice` that
# runs the same code as `exit_choice`.
alias_method :quit_choice, :exit_choice
# reads a single input from the user and returns it,
# in a normalized form.
# "Add" -> "add", "Do Something" -> "do_something"
def read_choice
STDIN.gets.chomp.downcase.strip.gsub(/\s+/, '_')
end
# Process a single command from the user.
def process_choice
choice = read_choice
# the methods that correspond to user input are named
# following the same pattern. "add" -> "add_choice"
method_name = [choice, 'choice'].join('_').to_sym
# check if the method actually exists.
if self.respond_to? method_name
# call the method named by `method_name`
self.send(method_name)
else
# the method doesn't exist.
# that means the input was unrecognized.
puts "Invalid choice #{choice}"
end
end
# this method acts as a "run loop" that continues execution
# until the `running` state changes.
def choose
# define the initial state.
self.running = true
# process a single input as long as the state hasn't changed.
process_choice while self.running
end
end
Put a loop around it.
loop do
choice = input
case choice
.
.
.
when "exit", "quit"
break
else
puts "Invalid choice"
end
end
Related
I created a gem that looks for a recipe with user input. I have almost 1000 recipes available to search for. How can I validate the user input when it does not match the names of my recipes?
as an example when the user type nabucodonosor or vocka the method load_recipe_by_ingridients return empty and I wish I could fix that. I'm using Ruby vanilla.. no rails
def start
puts "Hey there! you hungry? lets find some recipes ideas for you."
list_recipe_by_ingredients
show_summary
while #input != "exit"
if #input == "back"
list_recipe_by_ingredients
elsif valid_input?
puts Recipe.find_by_number(#input).summary
else
puts "Ops! not a valid number. try again."
end
ask_for_choices
end
end
def load_recipe_by_ingredients
puts "Search for recipes with the main ingredient, example: milk, pizza, eggs flour, ect."
#input = gets.strip.downcase
puts " "
Recipe.search_for_recipes(#input).each.with_index(1) do |recipe, index|
puts "#{index}. #{recipe.title}"
end
end
A pretty simple solution is to loop until a recipe is found, and once one or more recipes are found you break out of the loop and then render them:
def load_recipe_by_ingredients
recipes = []
loop do
puts 'Search for recipes with the main ingredient, example: milk, pizza, eggs flour, etc.'
input = gets.strip.downcase
puts
recipes = Recipe.search_for_recipes(input)
break if recipes.any?
puts 'No recipes found, please try again'
end
recipes.each.with_index(1) do |recipe, index|
puts "#{index}. #{recipe.title}"
end
end
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'm really new to this but I’ve written a small program that will CRUD a movie to a list the only problem is it is not saving the changes made. how can I make this happen?
error = "movie not found"
movies = {
Mazerunner: 1
}
error = "movie not found"
puts "welcome to CrudMovies"
puts "enter a command"
puts "type add to add a movie to the list"
choice = gets.chomp.downcase
case choice
when "add"
puts "what movie would you like to add"
title = gets.chomp
if movies[title.to_sym].nil?
puts "what would you like the rating of #{rating} (1-4)to be?"
rating = gets.chomp
movies[title.to_sym] = rating.to_i
puts "#{title} was added with a rating of #{rating}"
else
puts "that movie already exists"
end
when "update"
puts "what movie would you like to update? (case sensitive)"
title = gets.chomp
if movies[title.to_sym].nil?
puts "#{error}"
else
puts "what is the movie rating would you like to update?"
movies[title.to symb] = rating.to_i
puts "#{title}'s rating has been updated to #{rating}"
end
when "display"
movies.each do |x, y|
puts "#{x} Rating:#{y}"
end
when "destroy"
puts "what movie would you like to erase?"
title = gets.chomp
if movies[title.to_sym].nil?
puts "#{error}"
else
movies.delete(title.to_sym)
puts "the movie no longer exists"
end
else
puts "command not recognized"
end
I'm going to assume that by "Save" you mean stored in the movies hash. The answer to that is that is it is in fact being stored. Only your script exits after you perform an operation, so you never get to see the updated movies.
To see the desired result you're going to want to wrap the majority of this in an infinite loop to prevent the script from naturally exiting.
Consider the following as an example:
store = []
while true
puts "Enter something:"
choice = gets.chomp
store.push choice
puts "Your choices so far are: #{store.inspect}"
end
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
The code below works completely fine as long as users enter in the method name. I'd like to avoid requiring users to enter the name of the method at the various gets.chomp prompts.
I thought that using a case statement to translate the user input into method calls would work, but I keep getting a .include? NoMethodDefined error.
class Foo
def initialize(start_action)
#start = start_action
end
def play
next_action = #start
while true
case next_action.include?
when beginning
next_action = beginning
when "instruct"
next_action = instructions # returns instructions as
# the method that's called below
when "users"
next_action = users # returns users as the
# method that's called below
else
puts "Unknown command."
next_action = # some placeholder method call that gets the user
# back to being able to make another choice
end
puts "\n----------"
next_action = method(next_action).call
end
def beginning
puts "This is the beginning."
next_action = gets.chomp
end
def instructions
puts "These are the instructions"
# code to display instructions omitted
next_action = gets.chomp
end
def users
puts "Here are your users"
# code to display users omitted
next_action = gets.chomp
end
end
start = Foo.new(:beginning)
start.play
Any advice or help is appreciated.
On the first pass through your loop, next_action is the symbol :beginning and symbols don't have an include? method.
In addition I think you've misunderstood how case statements work - even removing the first error your code will then complain that you're passing 0 arguments to include? (instead of 1)
I think you instead mean something like
case next_action
when /instruct/
..
when /users
..
else
..
end
Which will test next action against each regular repression in turn