In a game of rock-paper-scissors, each player chooses to play Rock (R), Paper (P), or Scissors (S). The rules are: Rock breaks Scissors, Scissors cuts Paper, but Paper covers Rock. In a round of rock-paper-scissors, each player's name and strategy is encoded as an array of two elements. Create a RockPaperScissors class with a class method winner that takes two 2-element arrays like those above, and returns the one representing the winner:
RockPaperScissors.winner(['Armando','P'], ['Dave','S']) # => ['Dave','S']
If either player's strategy is something other than "R", "P" or "S" (case-SENSITIVE), the method should raise a 'RockPaperScissors::NoSuchStrategyError' exception and provide the message: "Strategy must be one of R,P,S"
If both players use the same strategy, the first player is the winner.
I have my code below. My code is not comparing the two strings correctly in the line
(#p1[1,1]==rules["#{p}"]?#p1:#p2).
Please help me out.
class RockPaperScissors
def winner(p1,p2)
#p1 = p1
#p2 = p2
p = (#p1[1,1]+#p2[1,1]).sort
rules = Hash.new(0)
rules = {"PR"=>"R","PS"=>"S", "RS"=>"R", "PP"=>"1","RR"=>"1","SS"=>"1"}
if rules["#{p}"].nil?
raise RockPaperScissors::NoSuchStrategyError,"Strategy must be one of R,P,S"
elseif rules["#{p}"]=="1"
return #p1
else
print #p1[1,1]
print rules["#{p}"]
#p1[1,1]==rules["#{p}"]?#p1:#p2
end
end
end
t = RockPaperScissors.new
print t.winner(['Armando','R'], ['Dave','S'])
Some general tips: You don't need [1,1], [1] or .last would be better. Also no need to initialize rules to a new hash, you can only keep the line rules = {"PR"=>"R".... puts is more commonly used than print. You're overthinking this a bit. Maybe clean up your code, try to simplify it with the tips posted above and see if this gets you unstuck.
Once you are done, have a look at what an idiomatic Ruby solution could look like, but don't submit it as your solution:
module RockPaperScissors
VALID_STRATEGIES = %i(R P S)
RULES = { R: :S, P: :R, S: :P }
def self.winner(p1, p2)
choice1, choice2 = p1.last.intern, p2.last.intern
unless [choice1, choice2].all? { |s| VALID_STRATEGIES.include? s }
raise RockPaperScissors::NoSuchStrategyError, "Strategy must be one of R,P,S"
end
return p1 if choice1 == choice2
RULES[choice1] == choice2 ? p1 : p2
end
end
When you use the [1,1] on an array, you receive an array of size 1 starting from index 1:
[1,2,3][1,1]
# => [2]
[1,2,3][1]
# => 2
Because of that when you compare it to the rules, you never get true, since no rule is an array...
["S"] == "S"
# => false
So to fix your code, instead of
p = (#p1[1,1]+#p2[1,1]).sort
# ...
(#p1[1,1]==rules["#{p}"]?#p1:#p2)
You should try:
p = (#p1[1]+#p2[1]).sort
# ...
(#p1[1]==rules[p]?#p1:#p2
Related
Currently, I'm having print like this
print ((stamp_amount[0], 'first mark') unless stamp_amount[0].zero?), (', ' if !stamp_amount[0].zero? && !stamp_amount[1].zero?),
((stamp_amount[1], 'second mark') unless stamp_amount[1].zero?)
stamp_amount is an array with 2 integer values
Let's say in the current situation stamp_amount[0] = 10 and stamp_amount[1] = 3
Output preview:
10 first mark, 3 second mark
So if stamp_amount[0] = 0 the 10 first mark, part won't be show. Same if stamp_amount[1] = 0 the , 3 second mark part won't be shown
For me, it seems a little bit incorrect in terms of theory. Could you please suggest me the more correct or less painful print of this? :)
Cheers!
Your code is trying to join a sequence of up to two elements with a separator. The joining is a solved problem, see Array#join.
The problem can be then reduced to "how can I produce the correct sequence, given my stamp_amount input". Now this can be done in a thousand ways. Here's one:
def my_print(stamp_amount)
ary = [
!stamp_amount[0].zero? && stamp_amount[0],
!stamp_amount[1].zero? && stamp_amount[1],
].select{|elem| elem }
ary.join(', ')
end
my_print([10, 3]) # => "10, 3"
my_print([0, 3]) # => "3"
my_print([10, 0]) # => "10"
my_print([0, 0]) # => ""
Here's another
ary = []
ary << stamp_amount[0] unless stamp_amount[0].zero?
ary << stamp_amount[1] unless stamp_amount[1].zero?
ary.join(', ')
Here's yet another. This version can handle stamp_amount of any length.
ary = stamp_amount.reject(&:zero?)
ary.join(', ')
I'd go with the third, but the second one may be the easiest to understand for a beginner.
Use the select, as an alternative to reject (shown in part 3 of the answer by Sergio Tulentsev). It is just asa readable, and depending on the context and on the future changes to the code, you may prefer one versus the other.
puts stamp_amount.select{ |a| !a.zero? }.join(", ")
A few examples of inputs and outputs are:
stamp_amount output
--------------------------------------------------------------------------
10, 3 10, 3
10, 0 10
0, 3 3
0, 0 (prints an empty line, because the selected array is empty)
You're calculating zero? on index points more often than is needed, but the first thing I would look at refactoring here is the readability of the code. It might be nicer to calculate the message to print outside of the print method and explain what is happening with variable names.
# rubocop is going to complain about variable assignment like this
first_amount, second_amount = *stamp_amount
We can actually use the reason rubocop prefers the .zero? over == 0 or .empty? method to guide our development. zero? is in essence just empty? but it communicates the meaning of what you are attempting to do in a better manner. I would use this reasoning when assigning strings to variables that explain what they are doing.
some_name_that_explains_what_this_is_0 = "#{first_amount} piecu centu marka"
some_name_that_explains_what_this_is_1 = "#{second_amount} tris centu marka"
Your current code is confusing as you have the possibility of printing a string like "10 tris centu marka" which does not make lexical sense and probably not what you are after considering tis evaluates to 'second mark', which would pose an issue if the first value is zero. We also could reject zero integers before we start converting them to strings.
array = [1, 0].reject(&:zero?)
Now we can take the array and do something like:
string = []
array.each_with_index { |e, i| string << "#{e} #{Ordinalize.new(i).ordinalize} mark" }
message = string.join(', ')
print(message)
# ord class
class Ordinalize
def initialize(value)
#value = value
end
def ordinalize
mapping[#value]
end
def mapping
# acounting for zero index
['first', 'second']
end
end
where we are calculating the ordinalization and letting our new class handle the sentence structure for us.
Outputs:
[1, 0] => "1 first mark"
[0, 1] => "1 first mark"
[1, 2] => "1 first mark, 2 second mark"
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).
I'm creating a rock, scissors, paper program for my first program with ruby.
I'm thinking of using a hash called #wins to determine if a certain combination wins. If both hands are the same, the result is a Draw!. Otherwise, the result is Lose!.
I'm struggling to implement the algorithm to judge the results. How do I check if a given combination exists within the #wins hash to judge wether it's a win or now?
class Hand
attr_accessor :shape
##shapes = [:rock, :scissors, :paper]
def generate
#shape = ##shapes[rand(3)]
end
end
class Game
#wins = {rock: :scissors, scissors: :paper, paper: :rock}
def judge(p1, p2)
'Win!' if (a way to see if a given combination exists within a #wins hash) # Not working
'Draw!' if p1 == p2 # Not working
else 'Lose!'
end
end
player_hand = Hand.new
player_hand.shape = ARGV.join.to_sym
puts player_hand.shape # Debug
computer_hand = Hand.new
computer_hand.shape = computer_hand.generate
puts computer_hand.shape # Debug
game = Game.new
puts game.judge(player_hand.shape, computer_hand.shape)
def judge(p1, p2)
case
when #wins[p1] == p2 then "Win"
when #wins[p2] == p1 then "Lose"
else "Draw"
end
end
Input should be a string:
"abcd#gmail.com"
Output should be an Array of strings:
["abcd#gmail.com",
"a.bcd#gmail.com",
"ab.cd#gmail.com",
"abc.d#gmail.com",
"a.b.cd#gmail.com",
"a.bc.d#gmail.com",
"a.b.c.d#gmail.com"]
The idea: "Make every possible combination in the first string part ("abcd") with a dot. Consecutive dots are not allowed. There are no dots allowed in the beginning and in the end of the first string part ("abcd")"
This is what I've came up with so far:
text,s = "abcd".split""
i=0
def first_dot(text)
text.insert 1,"."
end
def set_next_dot(text)
i = text.rindex(".")
text.delete_at i
text.insert(i+1,".")
end
My approach was
write a function, that sets the first dot
write a function that sets the next dot
...(magic)
I do not know how to put the pieces together. Any Idea? Or perhaps a better way?
thanx in advance
edit:
I think I found the solution :)
I will post it in about one hour (it's brilliant -> truth tables, binary numbers, transposition)
...and here the solution
s = "abc"
states = s.length
possibilites = 2**states
def set_space_or_dot(value)
value.gsub("0","").gsub("1",".")
end
def fill_with_leading_zeros(val, states)
if val.length < states
"0"*(states-val.length)+val
else
val
end
end
a = Array.new(possibilites,s)
a = a.map{|x| x.split ""}
b = [*0...possibilites].map{|x| x.to_s(2).to_s}
b = b.map{|x| fill_with_leading_zeros x,states}
b = b.map{|x| x.split ""}
c = []
for i in 0 ... a.size
c[i] = (set_space_or_dot (a[i].zip b[i]).join).strip
end
Changing pduersteler answer a little bit:
possibilities = []
string = "abcd#example.com"
(string.split('#')[0].size-1).times do |pos|
possibility = string.dup
possibilities << possibility.insert(pos+1, '.')
end
How about this (probably needs a bit more fine-tuning to suit your needs):
s = "abcd"
(0..s.size-1).map do |i|
start, rest = [s[0..i], s[(i+1)..-1]]
(0..rest.size-1).map { |j| rest.dup.insert(j, '.') }.map { |s| "#{start}#{s}"}
end.flatten.compact
#=> ["a.bcd", "ab.cd", "abc.d", "ab.cd", "abc.d", "abc.d"]
An option would be to iterate n times through your string moving the dot, where n is the amount of chars minus 1. This is what you're doing right now, but without defining two methods.
Something like this:
possibilities = []
string = "abcd#example.com"
(string.split('#')[0].size-1).times do |pos|
possibilities << string.dup.insert(pos+1, '.')
end
edit
Now tested. THanks to the comments, you need to call .dup on the string before the insert. Otherwise, the dot gets inserted into the string and will stay there for each iteration causing a mess. Calling .dup onthe string will copy the string and works on the copy instead, leaving the original string untouched.
I'm new to Ruby, so the answer is probably pretty simple. Not to me though
I am taking an array of strings (A) and matching it against another array of strings (B) to see if a given string from (A) exists as a substring within a string from B.
The compare seems to work however, I only get back a result from the last (A) string compared.
What might this be?
def checkIfAvailableOnline(film)
puts "Looking for " + film
lowerCaseFilm = film.downcase
#iterate through the linesarray scanning for the film in question
for line in #linesArray
#get the line in lowercase
lowerCaseLine = line.downcase
#look for the film name as a substring within the line
results = lowerCaseLine.scan(lowerCaseFilm)
if results.length > 0
#availableOnlineArray << results
end
end
end
#-----------------------------------------
listFilmsArray.each {|line| checkIfAvailableOnline(line)}
Given a list of film names:
FILM_NAMES = [
'Baked Blue Tomatoes',
'Fried Yellow Tomatoes',
'The thing that ate my homework',
'In a world where',
]
Then to find all film names containing a substring, ignoring case:
def find_films_available_online(partial_film_name)
FILM_NAMES.find_all do |film_name|
film_name.downcase[partial_film_name.downcase]
end
end
p find_films_available_online('tomatoes')
# => ["Baked Blue Tomatoes", "Fried Yellow Tomatoes"]
p find_films_available_online('godzooka')
# => []
To find out if a film name is available online:
def available_online?(partial_film_name)
!find_films_available_online(partial_film_name).empty?
end
p available_online?('potatoes') # => false
p available_online?('A World') # => true
To find out which of a list of partial film names are available online:
def partial_film_names_available_online(partial_film_names)
partial_film_names.find_all do |partial_film_name|
available_online?(partial_film_name)
end
end
p partial_film_names_available_online [
'tomatoes',
'potatoes',
'A World',
]
# => ["tomatoes", "A World"]
A more rubyish way to do this is:
Given an array of films we are looking for:
#films = ["how to train your dragon", "kung fu panda", "avatar"]
Given an array of lines that may contain the films we are looking for:
#lines_array = ["just in kung fu panda", "available soon how to train your dragon"]
Return the film name early if it exists in a line or false if it doesn't after searching all the lines:
def online_available(film)
#lines_array.each do |l|
l.downcase.include?(film) ? (return film) : false
end
false
end
Check for the films in the lines rejecting the ones that returned false, print them and ultimately return an array of the matches we found:
def films_available
available = #films.collect{ |x| p "Looking for: #{x}"; online_available(x) }
.reject{ |x| x == false }
available.each{|x| p "Found: #{x}"}
available
end
It is considered bad style to use camel-case in method names with Ruby but you know what they say about opinions.
.each is an internal iterator and I'm pretty sure the "for" loop will run slower than the enumerable each method that arrays inherit.