Using Key Value pairs in Hash as question and answer - ruby

I'm working on an assignment in my code bootcamp, it involves ruby.
Create a program with a hash of countries & capitals such as the following:
cos_n_caps = {
"USA" => "Washington, DC",
"Canada"=>"Ottawa",
"United Kingdom"=>"London",
"France"=>"Paris",
"Germany"=>"Berlin",
"Egypt"=>"Cairo",
"Ghana"=>"Accra",
"Kenya"=>"Nairobi",
"Somalia"=>"Mogadishu",
"Sudan"=>"Khartoum",
"Tunisia"=>"Tunis",
"Japan"=>"Tokyo",
"China"=>"Beijing",
"Thailand"=>"Bangkok",
"India"=>"New Delhi",
"Philippines"=>"Manila",
"Australia"=>"Canberra",
"Kyrgyzstan"=>"Bishkek"
}
Ask the user for the capital of each country, and tell them if they are correct. Also, keep score and give them their score at the end of the quiz.
I want to know if I can somehow cycle through the list of keys and ask for user_input after each key and then check again value.
I've tried to use hash.for_each{|key| puts key}, but I don't know how to ask for user_input between the keys.
This is what I was going to do, unless I can find something easier:
s = "What is the capital of"
score = 0
count = 0
until count == 1
puts "#{s} USA"
a = gets.chomp.downcase
if a == c["USA"].downcase
puts "Congrats"
score += 1
count += 1
else
puts "nope"
count +=1
end
end

Use Hash#each to loop through each pair of countries and capitals. In that loop, use Kernel#gets to read their answer and String#chomp to remove the newline off their answer.
cos_n_caps.each do |country,capital|
puts "What is the capital of #{country}?"
answer = gets.chomp
if capital.downcase == answer.downcase
puts "Right!"
else
puts "Sorry, it's #{capital}."
end
end

Related

why is my input method for the hangman game failing to function properly?

I have this method where it gets an input from the user and it checks it against a while condition. if the user inputted anything that isnt a string or if the user inputted a character that was longer than 1 the method would prompt the user again for a valid input, basically adhering to the hangman rules. Heres the code
class Hangman
def initialize
dictionary = File.open('5desk.txt',"r")
line = dictionary.readlines
#word = line[rand(1..line.length)]
#length = #word.length
random = #word.length - rand(#word.length/2)
random.times do
#word[rand(#word.length)] = "_"
end
end
This method fails to function properly.
def get_input
puts #word
puts "Letter Please?"
#letter = gets.chomp
while !#letter.kind_of? String || #letter.length != 1
puts "Invalid input,try again!"
#letter = gets.chomp
end
end
end
Game = Hangman.new
Game.get_input
class Hangman
Stop right there! Why create a class considering that you would only create a single instance of it? There's no need for one. A few methods and one instance variable are sufficient.
Generate secret words randomly
I assume the file '5desk.txt' contains one secret words per line and you will be selecting one randomly. So begin by gulping the entire file into an array held by an instance variable (as opposed to reading the file line-by-line). I assume '5desk.txt1' contains the three words shown below.
#secret_words = File.readlines('5desk.txt', chomp: true)
#=> ["cat", "violin", "whoops"]
See the doc for the class method IO::readlines1,2. The option chomp: true removes the newline character from the end of each line.
This method closes the file after it has been read. (You used File::open. When doing so you need to close the file when you are finished with it: f = File.open(fname)...f.close.)
You need a method to randomly choose a secret_word.
def fetch_secret_word
#secret_words.sample
end
fetch_secret_word
#=> "violin"
See Array#sample. You could have instead used
#secret_words[rand(#secret_words.size)]
See Kernel#rand. The first and last words in #secret_words are #secret_words[0] and #secret_words[#secret_words.size-1]. Therefore, where you wrote
#word = line[rand(1..line.length)]
it should have been
#word = line[rand(0..line.length-1)]
which is the same as
#word = line[rand(line.length)]
Now let's create a method for playing the game, passing an argument that equals the maximum number of incorrect guesses the player has before losing.
def play_hangman(max_guesses)
First get a secret word:
secret_word = fetch_secret_word
Let us suppose that secret_word #=> "violin"
Initialize objects
Next, initialize the number of incorrect guesses and an image of the secret word:
incorrect_guesses = 0
secret_word_image = "-" * secret_word.size
#=> "------"
So we now have
def play_hangman(max_guesses)
secret_word = fetch_secret_word
incorrect_guesses = 0
secret_word_image = "-" * secret_word.size
Loop over guesses
Now we need to loop over the player's guesses. I suggest you use Kernel#loop, in conjuction with the keyword break for all your looping needs. (For now, forget about while and until, and never use for.) The first thing we will do in the loop is to obtain the guess of a letter from the player, which I'll do by calling a method:
loop do
guess = get_letter(secret_word_image)
...<to be completed>
end
def get_letter(secret_word_image)
loop do
puts secret_word_image
print "Gimme a letter: "
letter = gets.chomp.downcase
break letter if letter.match?(/[a-z]/)
puts "That's not a letter. Try again."
end
end
guess = secret_letter(secret_word_image)
#=> "b"
Here this method returns "b" (the guess) and displays:
------
Gimme a letter: &
That's not a letter. Try again.
------
Gimme a letter: 3
That's not a letter. Try again.
------
Gimme a letter: b
See if letter guessed is in secret word
Now we need to see which if any of the hidden letters equal letter. Again, let's make this a method3.
def hidden_letters(guess, secret_word, secret_word_image)
(0..secret_word.size-1).select do |i|
guess == secret_word[i] && secret_word_image[i] = '-'
end
end
Suppose guess #=> "i". Then:
idx = hidden_letters(guess, secret_word, secret_word_image)
#=> [1,4]
There are two "i"'s, at indices 1 and 4. Had there been no hidden letters "i" the method would have returned an empty array.
Before continuing let's look at our play_hangman is coming along.
def play_hangman(max_guesses)
secret_word = fetch_secret_word
incorrect_guesses = 0
secret_word_image = "-" * secret_word.size
loop do
unless secret_word_image.include?('-')
puts "You've won. The secret word is '#{secret_word}'!"
break
end
guess = get_letter(secret_word_image)
idx = hidden_letters(guess, secret_word, secret_word_image)
...<to be completed>
end
Process a guess
We now have to carry out one course of action if the array idx is empty and another if it is not.
case idx.size
when 0
puts "Sorry, no #{guess}'s"
incorrect_guesses += 1
if incorrect_guesses == max_guesses
puts "Oh, my, you've used up all your guesses, but"
puts "we'd like you take home a bar of soap"
break
else
puts idx.size == 1 ? "There is 1 #{guess}!" :
"There are #{idx} #{guess}'s!"
idx.each { |i| secret_word_image[i] = guess }
if secret_word_image == secret_word
puts "You've won!! The secret word is '#{secret_word}'!"
break
end
end
Complete method
So now let's look at the full method (which calls fetch_secret_word, get_letter and hidden_letters).
def play_hangman(max_guesses)
secret_word = fetch_secret_word
incorrect_guesses = 0
secret_word_image = "-" * secret_word.size
loop do
guess = get_letter(secret_word_image)
idx = hidden_letters(guess, secret_word, secret_word_image)
case idx.size
when 0
puts "Sorry, no #{guess}'s"
incorrect_guesses += 1
if incorrect_guesses == max_guesses
puts "Oh, my, you've used up all your guesses,\n" +
"but we'd like you take home a bar of soap"
return
end
else
puts idx.size == 1 ? "There is 1 #{guess}!" :
"There are #{idx.size} #{guess}'s!"
idx.each { |i| secret_word_image[i] = guess }
if secret_word_image == secret_word
puts "You've won!! The secret word is '#{secret_word}'!"
return
end
end
end
end
Play the game!
Here is a example play of the game.
play_hangman(4)
------
Gimme a letter: #
That's not a letter. Try again.
------
Gimme a letter: e
Sorry, no e's
------
Gimme a letter: o
There is 1 o!
--o---
Gimme a letter: i
There are 2 i's!
-io-i-
Gimme a letter: l
There is 1 l!
-ioli-
Gimme a letter: v
There is 1 v!
violi-
Gimme a letter: r
Sorry, no r's
violi-
Gimme a letter: s
Sorry, no s's
violi-
Gimme a letter: t
Sorry, no t's
Oh, my, you've used up all your guesses,
but we'd like you take home a bar of soap
1 The class File has no (class) method readlines. So how can we write File.readlines? It's because File is a subclass of IO (File.superclass #=> IO) and therefore inherits IO's methods. One commonly sees IO class methods invoked with File as the receiver.
2 Ruby's class methods are referenced mod::meth (e.g., Array::new), where mod is the name of a module (which may be a class) and meth is the method. Instance methods are referenced mod#meth (e.g., Array#join).
3 Some Rubyists prefer to write (0..secret_word.size-1) with three dots: (0...secret_word.size). I virtually never use three dots because I find it tends to create bugs. The one exception is when creating an infinite range that excludes the endpoint (e.g., 1.0...1.5).

Check if integer is positive in ruby

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

unable to reach method inside if else statement ruby

I am currently working on building a word guessing game. There is a word and a user would guess a character in the word. If the character the user guesses is in fact in the word then feedback would be given back. For example the word is "cookie" and the user enters "o" then feedback would be
_ o o _ _ _
the number of attempts is equal to the length of the word in this case 6. If the user guesses a character that is not part of the word for example "z" a message would display error. However I am having problems accessing the if and else statements, they work only for the first input. here is my code:
class Game
attr_reader :word
attr_accessor :guess_counts, :guesses, :user_input, :guess_array
def initialize(word)
#word = word
#guesses = ""
end
def tries
#tries = #word.length
end
def guesses(user_input)
#user_input = user_input
#guesses << #user_input
end
def underscored
return #word.tr('^' + #guesses, '_').chars.join(' ')
end
end
# user interface
puts "Please enter a word to initialize the Guessing The Word game"
secret_word = gets.chomp
game = Game.new(secret_word)
puts "you have #{game.tries} attemps left"
guesses = []
tries = secret_word.length
while tries > 0
puts "Plese enter a letter you believe is in the secret word"
letter = gets.chomp
game.guesses(letter)
guesses << letter
guess = guesses[0]
if !secret_word.include? guess
puts "Letter not in word"
tries -= 1
puts "you have #{tries} left "
next
elsif secret_word.include? guess
p game.underscored
else
puts "you lost"
end
end
as you see in this image when I input "o" it says letter not in word but if I start with the letter "o" it would display
The problem is you are always grabbing the first element in the array of guesses when you wrote:
guess = guesses[0]
So the expression !secret_word.include? guess is:
always true when the first guess is correct, and then is
always false when the first answer is incorrect
To fix this you probably want grab the last guess; ie.
guess = guesses.last
# `.last` is convenience method for accessing last element of an array; like so
#
# guess = guesses[-1]
or the current letter for your if statement: i.e.
if !secret_word.include? letter
# [...]
elsif secret_word.include? letter
# [...]

Rock, paper, scissors game in Ruby

I am trying to get this program to run:
cpucount = 0
playercount = 0
tiecount = 0
playerchoice =
while playerchoice != "n"
puts "Chose your Weapon. Paper (0), Rock (1), Scissors (2)"
player1 = 0 #gets
cpuplayer = 2#rand(3)
puts player1
puts cpuplayer
if player1 == 0 and cpuplayer == 1
puts "You Win"
playercount +=1
elsif player1 == 1 and cpuplayer == 2
puts "You Win!"
playercount +=1
elsif player1 == 2 and cpuplayer == 0
puts "You Win!"
playercount +=1
elsif player1 == cpuplayer
puts "You tied!"
tiecount +=1
else
puts "You lose"
cpucount +=1
end
puts cpucount
puts playercount
puts tiecount
puts "Do you want to play again? y/n?"
playerchoice = gets
puts playerchoice
end
but there are a few issues.
First, regardless of whether I select "y" to continue to another round or "n" to quit, it still runs another round.
Second, the logic is fine when I manually input the values for player1 and cpuplayer, but when I use the rand method and the user input, the program takes those and then the logic doesn't work.
Any help would be appreciated.
In your input statement which is using gets you need to take into account the newline that is placed in the string, and the fact that it is a string. When the player is inputting it, it is coming in as text, not an integer. A simple way to do this is to make it an integer on input, via
player1 = gets.to_i
That will guarantee that the conditional logic you use to test against integers is not going to fail because you are comparing a string.
The newline that is coming in with the playerchoice input needs get chomped to make that happy for comparison. So, there is another method to get rid of newlines.
playerchoice = gets.chomp
Try assigning the playerchoice variable in the following way:
playerchoice = gets.chomp
The #gets method by itself will output any carriage returns that come with the user's input, which is why if you were to inspect the returned value for playerchoice, you'd see that instead of "n", the value returned is actually "n\n", causing your comparison to resume looping the game. Calling #chomp on that value strips out the carriage return characters (\n, \r, \r\n), which should allow the game to end if "n" is typed in by the user.
Hope it helps!

Misbehaving Case Statement

I'm messing around in Ruby some more. I have a file containing a class with two methods and the following code:
if __FILE__ == $0
seq = NumericSequence.new
puts "\n1. Fibonacci Sequence"
puts "\n2. Pascal\'s Triangle"
puts "\nEnter your selection: "
choice = gets
puts "\nExcellent choice."
choice = case
when 1
puts "\n\nHow many fibonacci numbers would you like? "
limit = gets.to_i
seq.fibo(limit) { |x| puts "Fibonacci number: #{x}\n" }
when 2
puts "\n\nHow many rows of Pascal's Triangle would you like?"
n = gets.to_i
(0..n).each {|num| seq.pascal_triangle_row(num) \
{|row| puts "#{row} "}; puts "\n"}
end
end
How come if I run the code and supply option 2, it still runs the first case?
Your case syntax is wrong. Should be like this:
case choice
when '1'
some code
when '2'
some other code
end
Take a look here.
You also need to compare your variable against strings, as gets reads and returns user input as a string.
Your bug is this: choice = case should be case choice.
You're providing a case statement with no "default" object, so the first clause, when 1, always returns true.
Effectively, you've written: choice = if 1 then ... elsif 2 then ... end
And, as Mladen mentioned, compare strings to strings or convert to int: choice = gets.to_i

Resources