I am trying to solve the "24" game. The point of the game is to generate 4 random integers from 1-9 and ask the player to use addition, subtraction, multiplication, or division to get the number 24. My code runs fine until a player enters a number, and then I get "Command not found". Can someone please take a look at this:
def evaluate (input,solved_v)
input = eval (input.to_f)
#convert to a float and then evaluates it; it computes
if input == 24
solved_v = true
puts "great job! you did it!"
else
puts "please try again"
end
end
def test_entry (input)
if input.scan(%r{[^\d\s()+*/-]}).empty?
#this scan detects letters and special characters because only numbers work
true
else
false
end
end
puts
puts "try to use +, -, / or * to"
puts "get 24 from the integers provided"
puts
series = (1..4).collect{ rand(1..9)}
#generates 4 random numbers between 1 and 9
for i in series
puts i
end
puts "Please guess"
solved = false
unless solved = true
user_input = gets.chomp
if test_entry(user_input) == true
evaluate(user_input)
else
puts "invalid characters entered"
puts "please try again"
puts
end
end
There are numerous problems with your program.
Don't put spaces between your method names and parentheses.
eval takes a string argument, not a float.
Ruby passes arguments by value, so solved_v isn't going to get
returned. Make it the return value of your evaluate method. I'd also
suggest renaming your methods to express their boolean intent. See below...
Don't check boolean expressions for equality to true or false, just use them.
def correct?(input)
if eval(input) == 24
puts "great job! you did it!"
true
else
puts "please try again"
false
end
end
def good_entry?(input)
input.scan(%r{[^\d\s()+*/-]}).empty?
end
and they get used as follows
while true
user_input = gets.chomp
if good_entry?(user_input)
break if correct?(user_input)
else
...
end
end
Finally, note that you're not actually checking that the input provided by the user uses only the supplied random numbers.
Related
I just started to learn ruby and I have a small project I'm working on. I'm making a simple two number calculator. What I want to do is get the user to input two numbers and then ask them if they would like to add/subtract/multiply them. My problem is, I want a message to fire if the user doesn't type in add/subtract/multiply. Say the user types Ad instead of Add, I want the computer to say "I don't understand!" I'm not sure how to execute this.
Here is my code so far.
puts "Welcome to math.rb!"
puts "Enter a number!"
user = gets.to_i
puts "Enter another number!"
user2 = gets.to_i
puts "What would you like to do with your number? You can add, subtract, or multiply!"
math = gets.chomp.downcase
if math == "add"
puts user + user2
end
if math == "subtract"
puts user - user2
end
if math == "multiply"
puts user * user2
end
if math != ["add", "subtract", "multiply"]
puts "I don't understand"
end
Use if statement with elsif and else.
math = gets.chomp.downcase
if math == "add"
puts user + user2
elsif math == "subtract"
puts user - user2
elsif math == "multiply"
puts user * user2
else
puts "I don't understand"
end
case is also nice for this usecase.
math = gets.chomp.downcase
case math
when "add"
puts user + user2
when "subtract"
puts user - user2
when "multiply"
puts user * user2
else
puts "I don't understand"
end
You can shorten the code further and use a hash to store allowed operations, and eval to evaluate the resulting string as an arithmetic expression. Even though eval is not safe in many cases, here we strictly control its input, making it much safer.
Note that to control the input, I keep the to_i conversion (without it, the user can enter arbitrary strings, which would be unsafe to be directly eval-ed). The hash further controls the input by converting the string operators to hash values, which we completely control.
puts 'Welcome to math.rb!'
puts 'Enter a number!'
x = gets.to_i
puts 'Enter another number!'
y = gets.to_i
puts 'What would you like to do with your number? You can add, subtract, or multiply!'
op_str = gets.chomp.downcase
op_str_to_math = { 'add' => '+', 'subtract' => '-', 'multiply' => '*' }
if op_str_to_math.key? op_str
puts eval "#{x} #{op_str_to_math[op_str]} #{y}"
else
puts "I don't understand"
end
op_str_to_math = { ... } defines a hash with key => value pairs. The elements of the hash can be accessed like so:
puts op_str_to_math['add'] # prints: +
puts op_str_to_math['subtract'] # prints: -
We check if the operator is allowed by looking up the user input (op_str) among the hash keys: if op_str_to_math.key? op_str .
eval "..." evaluates the string. Because the string is double-quoted, the values in #{...} are interpolated. So if the user entered, for example, 2, 3, add, the string gets interpolated to 2 + 3, which is 5.
Finally, I replaced where appropriate all double quotes with single quotes, per Ruby coding conventions.
I'm trying to make users input a positive integer(>=1). If the user inputs a negative integer or 0, the program gonna notice and say you didn't put a valid integer, please try again. my program works so far until I try to input a string. My program break after I input a string. Does anybody know how to fix this?
I tried elsif !barsofsoap.match (/\A[-+]?[0-9]*.?[0-9]+\Z/)
puts "You didn't enter an integer, please put a positive integer", but it didn't work
loop do
puts "How many soaps are you looking to purchase? (Please put an integer)"
x = gets.chomp
barsofsoap = Integer(x) rescue false
if barsofsoap >= 1
break
else
puts "You didn't put an integer, please put an integer and try again."
end
end
I hope the program will not break if the user inputs a string.
The reason why your current code fails is because you use false as rescue value. You can't compare false >= 1. Sticking close to your original code you might wrap the comparison into a begin/end block.
loop do
puts "How many soaps are you looking to purchase? (Please put an integer)"
x = gets.chomp
begin
barsofsoap = Integer(x)
if barsofsoap >= 1
break
end
rescue ArgumentError => _error
puts "You didn't put an integer, please put an integer and try again."
end
end
Or simplifying the code:
loop do
puts "How many soaps are you looking to purchase? (Please put an integer)"
break if (barsofsoap = gets.to_i).positive?
puts "Invalid input. Provide a positive integer."
end
The above code doesn't detect if the input is a string or an integer. By replacing Integer(x) with x.to_i it would simply return 0 if invalid. Since you want the user to provide an positive integer it still requests them to provide a new integer.
nil.to_i #=> 0
''.to_i #=> 0
'asdf'.to_i #=> 0
'12.34'.to_i #=> 12
Like shown above the string '12.34' will produce the value 12. Depending on your requirements you might not want to use this value and instead mark it as invalid input. In this case you could use a regex to check the provided input.
loop do
puts "How many soaps are you looking to purchase? (Please put an integer)"
input = gets
break barsofsoap = input.to_i if input.match?(/\A[1-9]\d*\Z/)
puts "Invalid input. Provide a positive integer."
end
The regex /\A[1-9]\d*\Z/ will match the following:
\A Matches start of string.
[1-9] Matches one of the digits 1-9.
\d* Matches zero or more digits 0-9.
\Z Matches end of string. If the string ends with a newline, it matches just before the newline.
You try this: num.positive?
see here: https://ruby-doc.org/core-2.6/Numeric.html#method-i-positive-3F
Also you can try convert the text to an integer and validate by doing this:
num.is_a?(Integer)
https://ruby-doc.org/core-2.6/Object.html#method-i-is_a-3F
Or even better since input is always text, you can use a regex to make sure only numbers are entered: "12345" =~ /\A\d+\Z/
How about try this:
loop do
puts "How many soaps are you looking to purchase? (Please put an integer)"
barsofsoap = gets.chomp
if barsofsoap.match(/^[\d]+$/).nil?
puts "You didn't put an integer, please put an integer and try again."
elsif barsofsoap.to_i.positive?
break
else
puts "You didn't put an integer, please put an integer and try again."
end
end
Other option, using n.to_i.to_s == n to check the input is an Integer and moving the loop into a method:
def get_input_integer
loop do
puts "Enter a positive Integer"
n = gets.chomp
if n.to_i.to_s == n
return n.to_i if n.to_i.positive?
puts "It's not positive"
else
puts "It's not an integer"
end
end
end
x = get_input_integer
If the user types anything other than a number, and any number besides a number in range of 1 - 32, I need it to ask for input again.
It's giving me an error when I use if cut_number in 1..32
def cut_the_deck
puts "You get to cut the deck to make it even more random!"
puts "Type a number between 1 and 32. That's where the deck will be cut!"
cut_number = gets.chomp
cut_number = cut_number.to_i
if cut_number in 1..32
puts "Number in range"
else
puts "Number NOT in range"
cut_the_deck
end
end
Use between?:
if cut_number.between?(1,32) #=> true or false
Or you could do:
if (1..32).include?(cut_number)
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
I'm trying to validate that the input a user gives my program via gets is an integer. is_a?(Integer) does not work, as far as i can tell, because gets gets a string from the user, so it will always return false even if the user enters an valid integer (in string form). One would think I could simply use to_i on the input and be done with it, but that raises another issue - "75akjfas".to_i results in 75. So if I relied on to_i to solve my problems, anything starting with numbers will work.
How do I cleanly validate that the value is an integer only, and not a combination of numbers and letters? Or do I need to resort to regex for this?
print "Enter an integer (or try to sneak by something other): "
puts Integer(gets) rescue puts "Hey, that's not an integer!"
How about s.to_i.to_s == s? I'd prefer regex however.
Using regex you could do it like this:
class String
def integer?
!!(self =~ /^[-+]?[0-9]+$/)
end
end
You could use Integer() but you need to be careful with hex, binary, or octal input. http://ruby-doc.org/core-2.3.1/Kernel.html#method-i-Integer
def valid_integer?(string)
begin
!!Integer(string)
rescue ArgumentError
false
end
end
Check this code example for how to use the checked string input by the user
puts "Enter a number: "
user_input = gets.chomp
check = (user_input.to_i.to_s == user_input)
while (!check ) do
puts("Wrong Input, Pls, Enter a number: " )
user_input = gets.chomp
check = (user_input.to_i.to_s == user_input)
end
if user_input.to_i < 0
puts "Number is negative"
elsif user_input.to_i > 0
puts "Number is positve"
else
puts "Number is Zero"
end
Ruby can do it without esoteric solutions:
Integer is a integer
1970.is_a?Integer
true
String is not a integer
"1970".is_a?Integer
false
String to integer is a integer
"1970".to_i.is_a?Integer
true