Ruby : Loop, Case and iterations problems ( Rock , Paper, Scissors ) - ruby-on-rails

I´m ruby student since 1 month and i´m stuck with one part of my code. The project is based on a game ( rock, paper, scissor) but i´m facing of a problem that i cannot solve for moment. I would like to add one parameters to this game especially when the user enter a wrong input by displaying a message but with the condition i added it´s not working.
elsif player_choice != 'r' || player_choice != 'p' || player_choice != 's' || player_choice != 'q' || player_choice != 'x'
puts "wrong input"
So if you have some advice or some hint to share with me it will be great ! ( see below the entire code ).
Thank you very much.
#intro
puts "***** WELCOME TO PAPER SCISSORS ROCKS GAME *****"
puts "Input p = Paper, r = Rocks, s = Scissors, x = Display your score , q = Quit the game. "
25.times { print "-" }
puts
#scores
playerScore = 0
cpuScore = 0
CHOICES = {'p' => 'Paper', 'r' => 'Rock', 's' => 'Scissors', 'x' => 'score','q' => 'quit' }
CHOICE_CPU = {'p' => 'Paper', 'r' => 'Rock', 's' => 'Scissors'}
loop do
# player picks
begin
puts "Select your pick: (p/r/s/x/q)"
player_choice = gets.chomp.downcase
end until CHOICES.keys.include?(player_choice)
# computer picks
cpu_choice = CHOICE_CPU.keys.sample
def throw_message(winning_choice)
case winning_choice
when 'p'
puts "Paper wraps Rock!"
when 'r'
puts "Rock smashes Scissors!"
when 's'
puts "Scissors cuts Paper!"
when 'x'
puts "Live score"
when 'q'
puts "you decide to quit the game"
end
end
#display scores
if player_choice == 'x'
throw_message(player_choice)
puts "PLAYER : #{playerScore} CPU : #{cpuScore}"
#quit the game
elsif player_choice == 'q'
throw_message(player_choice)
break
# tie result
elsif player_choice == cpu_choice
puts "It's a Tie ! "
#player win
elsif (player_choice == 'p' && cpu_choice == 'r') || (player_choice == 'r' && cpu_choice == 's') || (player_choice == 's' && cpu_choice == 'p')
throw_message(playe·r_choice)
puts "You Win"
playerScore +=1
#display invalid input
elsif player_choice != 'r' || player_choice != 'p' || player_choice != 's' || player_choice != 'q' ||
player_choice != 'x'
puts "wrong input"
#cpu win
else throw_message(cpu_choice)
puts "Computer Win"
cpuScore +=1
end
end

Move the CHOICES.keys.include?(player_choice) check to the top of the main if/else logic. If you validate your input as early as possible, the rest of the code can assume the input is good; there's no need to spell out all the possible choices again.
I'm using a case/when because it's easier to read than if/elsif. throw_message is defined outside the loop, inside the loop its being redefined repeatedly. And I've removed choices from throw_message which don't have to do with the game; this avoids repeating the full set of choices.
def throw_message(winning_choice)
case winning_choice
when 'p'
puts "Paper wraps Rock!"
when 'r'
puts "Rock smashes Scissors!"
when 's'
puts "Scissors cuts Paper!"
end
end
def player_wins?(player_choice, cpu_choice)
return player_choice == 'p' && cpu_choice == 'r') ||
player_choice == 'r' && cpu_choice == 's') ||
player_choice == 's' && cpu_choice == 'p')
end
loop do
# player picks
puts "Select your pick: (p/r/s/x/q)"
player_choice = gets.chomp.downcase
# cpu picks
cpu_choice = CHOICE_CPU.keys.sample
case
when !CHOICES.keys.include?(player_choice)
puts "wrong input"
when player_choice == 'x'
puts "Live score"
puts "PLAYER : #{playerScore} CPU : #{cpuScore}"
when player_choice == 'q'
puts "you decide to quit the game"
break
when player_choice == cpu_choice
puts "It's a tie!"
when player_wins?(player_choice, cpu_choice)
throw_message(player_choice)
puts "You Win"
playerScore +=1
else
throw_message(cpu_choice)
puts "Computer Win"
cpuScore +=1
end
end

Related

How to make a general method to check for winners in Tic-Tac-Toe

I made a Tic-Tac-Toe game in Ruby. The method below checks for a winner in the vertical columns.
How do I make it so that this method can be applied to boards of different sizes, like 4x4, 6x6?
def vertical_check(array)
result = nil
if (array[0][0] == "X" && array[1][0] == "X" && array[2][0] == "X") ||
(array[0][1] == "X" && array[1][1] == "X" && array[2][1] == "X") ||
(array[0][2] == "X" && array[1][2] == "X" && array[2][2] == "X")
result = "X"
elsif (array[0][0] == "O" && array[1][0] == "O" && array[2][0] == "O") ||
(array[0][1] == "O" && array[1][1] == "O" && array[2][1] == "O") ||
(array[0][2] == "O" && array[1][2] == "O" && array[2][2] == "O")
result = "O"
else
result = nil
end
return result
end
The following is a failed attempt:
def vertical_check_x(array)
result = nil
index = 0
index2 = 0
until result != nil || index == array.length
while array[index][index2] == "X"
index += 1
end
if index == array.length
result = "X"
else
result = nil
index = array.length
end
index2 += 1
end
return result
end
def vertical_check_o(array)
result = nil
index = 0
index2 = 0
until result != nil || index == array.length
while array[index][index2] == "O"
index += 1
end
if index -1 == array.length
result = "O"
else
result = nil
index = array.length
end
index2 += 1
end
return result
end
def vertical_check(array)
result = vertical_check_x(array)
if result == nil
result = vertical_check_o(array)
end
return result
end
To quickly find a winner in given array, count the number of unique elements, confirm that there is only one unique element and if it is only X or O:
def winner arr
return arr[0] if arr.uniq.length == 1 && ['X', 'O'].include?(arr[0])
nil
end
The next problem is selecting the rows, columns and diagonals for an nxn array.
Rows are easy:
rows = arr.map {|row| row}
Columns are as follows - you select elements with the same index for each row:
cols = n.times.collect {|i| arr.map {|row| row[i]}}
Next are diagonals. There are two diagonals, one starts from leftmost corner and the other from the rightmost.
The leftmost diagonal has the sequence as:
(0, 0) -> (1, 1) -> (2, 2) ....
See the pattern?
diag = n.times.collect {|i| arr[i][i]}
The rightmost diagonal has pattern that goes like this (for a 3x3):
(0, 2) -> (1, 1) -> (2, 0)
For a 4x4, it's like this:
(0, 3) -> (1, 2) -> (2, 1) -> (3, 0)
So, the pattern for an nxn is:
(0, n-1-0) -> (1, n-1-1) -> (2, n-1-2) -> ... (i, n-1-i) ... -> (n-1, 0)
So:
diag = n.times.collect {|i| arr[i][n - 1 - i]}
Now, you can just do something like:
w = rows.map {|r| winner r}.compact[0]
for each array to get the winner.

Error with virtual tree program ruby

I have just started learning ruby and have made this program following a tutorial.
I keep getting an error when trying to run and can't find an answer.
The program is suppose to be able to pick fruit, count the fruit, give the height and grow.
C:\Sites\testfolder>ruby orangetree.rb
orangetree.rb:2:in `initialize': wrong number of arguments (1 for 0) (ArgumentEr
ror)
from orangetree.rb:51:in `new'
from orangetree.rb:51:in `<class:OrangeTree>'
from orangetree.rb:1:in `<main>'
C:\Sites\testfolder>
Here is the program
class OrangeTree
def initialize
#age = 0
#tall = 0
#fruit = 0
puts 'You have planted a new tree!'
def height
puts 'The tree is ' + #tall.to_s + 'foot tall.'
end
def pickAFruit
if #fruit <= 1
puts 'There is not enough fruit to pick this year.'
else
puts 'You pick an orange from the tree.'
#fruit = #fruit - 1
end
end
def countOranges
puts 'The tree has ' + #fruit.to_s + 'pieces of fruit'
end
def oneYearPasses
#age = #age + 1
#tall = #tall + 3
#fruit = 0
if dead?
puts 'The Orange Tree Dies'
exit
end
if #age > 2
#fruit = #age*10
else
#fruit = 0
end
end
private
def dead?
#age > 5
end
end
tree = OrangeTree.new 'tree'
command = ''
while command != 'exit'
puts 'please enter a command for the virual tree'
command = gets.chomp
if command == 'tree height'
tree.height
elsif command == 'pick fruit'
tree.pickAFruit
elsif command == 'wait'
tree.oneYearPasses
elsif command == 'count fruit'
tree.countOranges
elsif command == 'exit'
exit
else
puts 'Cant understand your command, try again'
end
end
end
Can anybody help?
You have some syntax errors. I have fixed them below. The syntax errors were:
You had an extra end on the last line (for closing the class declaration)
You were passing an argument to OrangeTree.new ("tree") that was unexpected. This is what the wrong number of arguments (1 for 0) error message in your question is referring to.
You were missing an end to close your initialize method declaration (after the puts 'You have planted a new tree!' line)
I have also fixed the indentation, which makes the code more readable and much easier to spot syntax errors like this.
class OrangeTree
def initialize
#age = 0
#tall = 0
#fruit = 0
puts 'You have planted a new tree!'
end
def height
puts 'The tree is ' + #tall.to_s + 'foot tall.'
end
def pickAFruit
if #fruit <= 1
puts 'There is not enough fruit to pick this year.'
else
puts 'You pick an orange from the tree.'
#fruit = #fruit - 1
end
end
def countOranges
puts 'The tree has ' + #fruit.to_s + 'pieces of fruit'
end
def oneYearPasses
#age = #age + 1
#tall = #tall + 3
#fruit = 0
if dead?
puts 'The Orange Tree Dies'
exit
end
if #age > 2
#fruit = #age*10
else
#fruit = 0
end
end
private
def dead?
#age > 5
end
end
tree = OrangeTree.new
command = ''
while command != 'exit'
puts 'please enter a command for the virual tree'
command = gets.chomp
if command == 'tree height'
tree.height
elsif command == 'pick fruit'
tree.pickAFruit
elsif command == 'wait'
tree.oneYearPasses
elsif command == 'count fruit'
tree.countOranges
elsif command == 'exit'
exit
else
puts 'Cant understand your command, try again'
end
end

How to use .map in Rails

Pig Latin
Rule 1: If a word begins with a vowel sound, add an "ay" sound to
the end of the word.
Rule 2: If a word begins with a consonant sound, move it to the end
of the word, and then add an "ay" sound to the end of the word.
The following program works in ruby. But I'm confused on how to use the "map" function? Please see the code as follows:
def translate(sentence)
if sentence.include?(" ")
words = sentence.split(" ").map do |word|
translate_word(word)
end
return words.join(" ")
else single_word = sentence
translate_word(single_word)
end
end
The above sentences works! but if I use:
words = sentence.split(" ")
words.map do |word|
translate_word(word)
end
It DOESN'T work! Why? I thought they were the same...
def translate_word(w)
vowels = %w[a e i o u]
consonants = ("a".."z").to_a - vowels
if vowels.include?(w[0])
w + "ay"
elsif consonants.include?(w[0]) && vowels.include?(w[1]) && w[1] != "u"
w[1..-1] + w[0] + "ay"
elsif (consonants.include?(w[0]) && consonants.include?(w[1]) && vowels.include?(w[2]) && w[2] != "u") || (w[0] == "q" && w[1] == "u")
w[2..-1] + w[0..1] + "ay"
elsif (consonants.include?(w[0]) && consonants.include?(w[1]) && consonants.include?(w[2]) && vowels.include?(w[3]))
w[3..-1] + w[0..2] + "ay"
elsif consonants.include?(w[0]) && w[1] == "q" && w[2] == "u"
w[3..-1] + w[0..2] + "ay"
end
end
#map function returns a new object which you are dismissing.
To save the result you should assign it back to words like this:
words = sentence.split(" ")
words = words.map do |word|
translate_word(word)
end
Or use #map! instead.

all values same sign validation

User should insert all the values either positive or negative.
How may i set same sign validation ?
Right i have written this on before_save ..
unless (self.alt_1 >= 0 && self.alt_2 >=0 && self.alt_3 >= 0 &&
self.alt_4 >= 0 && self.alt_5 >= 0 && self.alt_6 >= 0) ||
(self.alt_1 <= 0 && self.alt_2 <=0 && self.alt_3 <= 0 &&
self.alt_4 <= 0 && self.alt_5 <= 0 && self.alt_6 <= 0)
self.errors.add_to_base(_("All values sign should be same."))
end
first_sign = self.alt_1 <=> 0
(2..6).each do |n|
unless (self.send("alt_#{n}") <=> 0) == first_sign
errors.add_to_base(_("All values' signs should be same."))
break
end
end
With this method we first get the sign of alt_1, and then see if the signs of the rest of the elements (alt_2 through alt_6) match. As soon as we find one that doesn't match we add the validation error and stop. It will run a maximum of 6 iterations and a minimum of 2.
Another more clever, but less efficient method, is to use the handy method Enumerable#all?, which returns true if the block passed to it returns true for all elements:
range = 1..6
errors.add_to_base(_("All values' signs should be same.")) unless
range.all? {|n| self.send("alt_#{n}") >= 0 } ||
range.all? {|n| self.send("alt_#{n}") <= 0 }
Here we first check if all of the elements are greater than 0 and then if all of the elements are less than 0. This method iterates a maximum of 12 times and a minimum of 6.
Here's a slightly different approach for you:
irb(main):020:0> def all_same_sign?(ary)
irb(main):021:1> ary.map { |x| x <=> 0 }.each_cons(2).all? { |x| x[0] == x[1] }
irb(main):022:1> end
=> nil
irb(main):023:0> all_same_sign? [1,2,3]
=> true
irb(main):024:0> all_same_sign? [1,2,0]
=> false
irb(main):025:0> all_same_sign? [-1, -5]
=> true
We use the spaceship operator to obtain the sign of each number, and we make sure that each element has the same sign as the element following it. You could also rewrite it to be more lazy by doing
ary.each_cons(2).all? { |x| (x[0] <=> 0) == (x[1] <=> 0) }
but that's less readable in my opinion.
unless
[:<=, :>=].any? do |check|
# Check either <= or >= for all values
[self.alt1, self.alt2, self.alt3, self.alt4, self.alt5, self.alt6].all? do |v|
v.send(check, 0)
end
end
self.errors.add_to_base(_("All values sign should be same."))
end

Help refactoring this nasty Ruby if/else statement

So I have this big, hairy if/else statement. I pass a tracking number to it, and then it determines what type of tracking number it is.
How can I simplify this thing? Specifically wanting to reduce the number of lines of codes.
if num_length < 8
tracking_service = false
else
if number[1, 1] == 'Z'
tracking_service = 'ups'
elsif number[0, 1] == 'Q'
tracking_service = 'dhl'
elsif number[0, 2] == '96' && num_length == 22
tracking_service = 'fedex'
elsif number[0, 1] == 'H' && num_length == 11
tracking_service = 'ups'
elsif number[0, 1] == 'K' && num_length == 11
tracking_service = 'ups'
elsif num_length == 18 || num_length == 20
check_response(number)
else
case num_length
when 17
tracking_service = 'dhlgm'
when 13,20,22,30
tracking_service = 'usps'
when 12,15,19
tracking_service = 'fedex'
when 10,11
tracking_service = 'dhl'
else
tracking_service = false
end
end
end
Yes, I know. It's nasty.
Try this. I rewrote it using case and regular expressions. I also used :symbols instead of "strings" for the return values, but you can change that back.
tracking_service = case number
when /^.Z/ then :ups
when /^Q/ then :dhl
when /^96.{20}$/ then :fedex
when /^[HK].{10}$/ then :ups
else
check_response(number) if num_length == 18 || num_length == 20
case num_length
when 17 then :dhlgm
when 13, 20, 22, 30 then :usps
when 12, 15, 19 then :fedex
when 10, 11 then :dhl
else false
end
end
Depending on whether or not the tracking code is a ruby object, you could also put helper's in it's class definition:
class TrackingCode < String
# not sure if this makes sense for your use case
def ups?
self[1,1] == 'Z'
end
def dhl?
self[0,1] == 'Q'
end
def fedex?
self.length == 22 && self[0, 2] == '96'
end
# etc...
end
Then your conditional becomes:
if number.ups?
# ...
elsif number.dhl?
# ...
elseif number.fedex?
end
One simplified conditional where you are operating on the implied feature of the tracking code. Likewise, if you were to take a looping approach, your loop would also be cleaner:
%w(ups? dhl? fedex?).each do |is_code|
return if number.send(is_code)
end
or even:
%w(ups? dhl? fedex?).each do |is_code|
yield if number.send(is_code)
end
This method looks like it was written for speed. You can use a minhash as a substitute, but I think the code is fairly clean and doesn't require a refactor. Rubyists tend to be disgusted by needless structure, but oftentimes it's needed to model real-world situations and/or provides a performance boost. The keyword should be needless.
Whilst longer than jtbandes solution, you might like this as it's a bit more declarative:
class Condition
attr_reader :service_name, :predicate
def initialize(service_name, &block)
#service_name = service_name
#predicate = block
end
end
CONDITIONS = [
Condition.new('ups') { |n| n[1] == 'Z' },
Condition.new('dhl') { |n| n[0] == 'Q' },
Condition.new('fedex') { |n| n[0..1] == '96' && n.size == 22 },
Condition.new('ups') { |n| n[0] == 'H' && n.size == 11 },
Condition.new('ups') { |n| n[0] == 'K' && n.size == 11 },
Condition.new('dhlgm') { |n| n.size == 17 },
Condition.new('usps') { |n| [13, 20, 22, 30].include?(n.size) },
Condition.new('fedex') { |n| [12, 15, 19].include?(n.size) },
Condition.new('dhl') { |n| [10, 11].include?(n.size) },
]
def tracking_service(tracking_number)
result = CONDITIONS.find do |condition|
condition.predicate.call(tracking_number)
end
result.service_name if result
end
I haven't dealt with the check_response method call here as I feel you should probably handle that elsewhere (assuming it does something other than return a tracking service name).
I believe this is sufficiently complex to deserve its own method.
BTW, if the length is 20 then the original function returns whatever check_response(n) returns, yet then attempts (and will always fail) to return 'usps'.
#lenMap = Hash.new false
#lenMap[17] = 'dhlgm'
#lenMap[13] = #lenMap[20] = #lenMap[22] = #lenMap[30] = 'usps'
#lenMap[12] = #lenMap[15] = #lenMap[19] = 'fedex'
#lenMap[10] = #lenMap[11] = 'dhl'
def ts n
len = n.length
return false if len < 8
case n
when /^.Z/
return 'ups'
when /^Q/
return 'dhl'
when /^96....................$/
return 'fedex'
when /^[HK]..........$/
return 'ups'
end
return check_response n if len == 18 or len == 20
return #lenMap[len]
end
# test code...
def check_response n
return 'check 18/20 '
end
%w{ 1Zwhatever Qetcetcetc 9634567890123456789012 H2345678901
K2345678901 hownowhownowhownow hownowhownowhownow90
12345678901234567
1234567890123
12345678901234567890
1234567890123456789012
123456789012345678901234567890
123456789012
123456789012345
1234567890123456789
1234567890
12345678901 }.each do |s|
puts "%32s %s" % [s, (ts s).to_s]
end

Resources