Simple rock, paper, scissors game - ruby

I wrote a "Rock, paper, scissors" game:
puts "Hello, this is a rock, papers, scissors game. Let's play."
puts "Player 1, plase enter your choice: \n"
puts "r for rock. \np for paper. \ns for scissors."
p1 = gets.chomp.downcase
puts "Player 2, please enter your choice: \n"
puts "r for rock. \np for paper. \ns for scissors."
p2 = gets.chomp.downcase
if p1 == 'r' && p2 == 's'
puts "Player 1 wins."
elsif p1 == 'r' && p2 == 'p'
puts "Player 2 wins."
elsif p1 == 'r' && p2 == 'r'
puts "Tie."
elsif p1 == 'p' && p2 == 'r'
puts "Player 1 wins."
elsif p1 == 'p' && p2 == 's'
puts "Player 2 wins."
elsif p1 == 'p' && p2 == 'p'
puts "Tie."
elsif p1 == 's' && p2 == 'r'
puts "Player 2 wins."
elsif p1 == 's' && p2 == 'p'
puts "Player 1 wins."
elsif p1 == 's' && p2 == 's'
puts "Tie."
end
It works, however, that's a lot of elsifs, and I know that this is possible with case...when statements, the thing is that I can't figure out how.
I was trying to use a return statement depending on the input: "return 0 for rock, 1 for paper and 2 for scissors", and then use a conditional to say something like "hey, if player one returns 1 and player 2 also returns 1, then puts 'tie'", and the same for the other possible results.
I was trying to associate a number to the result: return - 1 when player one wins, return 0 for a tie, and return 2 for player two wins.
I did it like this, but it's kind of the same, and I feel that it's so bad:
case p1
when p1 == 'r' && p2 == 'r'
result = 0
when p1 == 'r' && p2 == 'p'
result = 1
when p1 == 'r' && p2 == 's'
result = -1
when p1 == 'p' && p2 == 'r'
result = -1
when p1 == 'p' && p2 == 'p'
result = 0
when p1 == 'p' && p2 == 's'
result = 1
when p1 == 's' && p2 == 'r'
result = 1
when p1 == 's' && p2 == 'p'
result = -1
when p1 == 's' && p2 == 's'
result = 0
end
if result == -1
puts "P1 wins"
elsif result == 0
puts "Tie"
elsif result == 1
puts "P2 wins"
end
I would appreciate any kind of help.

An array can be used as a ring, with each item having
a weaker item to its right, and a stronger one to its left.
weapons = ['paper', 'rock', 'scissors']
select weapons by your favorite means
w1 = weapons[rand(weapons.length)]
w2 = weapons[rand(weapons.length)]
rotate array till w1 is at the center
while weapons[1] != w1
weapons.rotate! 1
end
now the result is indicated by the index of w2 in weapons array, conveniently.
verbs = ['is beat by', 'ties', 'beats']
puts "#{w1} #{verbs[weapons.index(w2)]} #{w2}"
Example output from a few runs:
paper beats rock
paper ties paper
rock beats scissors
scissors beats paper
rock is beat by paper
You could get creative and add a hash of verb-arrays, one for each weapon, using w1 as the key so that it would output (for example) paper covers rock, etc.

You can try something like this :
if (p1 == p2)
puts "Tie"
elsif (p1 == 'r' && p2 == 'p') ||
(p1 == 'p' && p2 == 's') ||
(p1 == 's' && p2 == 'r')
puts "P2 wins"
else
puts "P1 wins"
end

You could also do the ruby equivalent of saying
If p1 = 'r' then
goto R
endif
If p1 = 'p' then
goto S
endif
etc.
then at your goto locations later
R:
if p2 = 'r' then
puts "tie"
etc.
however the idea of p1 = p2 then ... is a good one.

Here's another way:
WINNERS = {s: :p, p: :r, r: :s}
# => {:s=>:p, :p=>:r, :r=>:s}
def win_lose_or_tie?(me, you)
return "I win! :-)" if WINNERS[me] == you
return "you win! :-(" if WINNERS[you] == me
"it's a tie :-|"
end
keys = WINNERS.keys
keys.product(keys).each { |me, you|
puts "If I play #{me} and you play #{you}, then #{win_lose_or_tie?(me, you)}" }
# If I play s and you play s, then it's a tie :-|
# If I play s and you play p, then I win! :-)
# If I play s and you play r, then you win! :-(
# If I play p and you play s, then you win! :-(
# If I play p and you play p, then it's a tie :-|
# If I play p and you play r, then I win! :-)
# If I play r and you play s, then I win! :-)
# If I play r and you play p, then you win! :-(
# If I play r and you play r, then it's a tie :-|

I'd use a case/when statement like:
result = case [p1, p2]
when %w[r r], %w[p p], %w[s s]
0
when %w[r p], %w[p s], %w[s r]
1
when %w[r s], %w[p r], %w[s p]
-1
end
puts case result
when 0
"Tie"
when -1
"P1 wins"
when 1
"P2 wins"
end
But, after writing that this makes more sense:
puts case [p1, p2]
when %w[r r], %w[p p], %w[s s]
'Tie'
when %w[r p], %w[p s], %w[s r]
'P1 wins'
when %w[r s], %w[p r], %w[s p]
'P2 wins'
end
Here's a test:
[
%w[r r], %w[p p], %w[s s],
%w[r p], %w[p s], %w[s r],
%w[r s], %w[p r], %w[s p]
].each do |p1, p2|
puts case [p1, p2]
when %w[r r], %w[p p], %w[s s]
'Tie'
when %w[r p], %w[p s], %w[s r]
'P1 wins'
when %w[r s], %w[p r], %w[s p]
'P2 wins'
end
end
# >> Tie
# >> Tie
# >> Tie
# >> P1 wins
# >> P1 wins
# >> P1 wins
# >> P2 wins
# >> P2 wins
# >> P2 wins

Related

Ruby Case Statement: Return which player won — Rock, Paper, Scissors

Working on a Codewar challenge: Rock, Paper,Scissors. The aim is to return which player won! In case of a draw return Draw!.
For example:
rps('scissors','paper') // Player 1 won!
rps('scissors','rock') // Player 2 won!
rps('paper','paper') // Draw!
my code:
def rps(p1, p2)
case p1...p2
when (p1 == 'rock' && p2 == 'scissors') ||
(p1 == 'scissors' && p2 == 'paper') ||
(p1 == 'paper' && p2 == 'rock')
return "Player 1 won!"
when (p1 == 'scissors' && p2 == 'rock') ||
(p1 == 'paper' && p2 == 'scissors') ||
(p1 == 'rock' && p2 == 'paper')
else (p1 == p2)
return "Draw!"
end
end
outcome:
player 1 win
Expected: "Player 1 won!", instead got: "Draw!"
Expected: "Player 1 won!", instead got: "Draw!"
Expected: "Player 1 won!", instead got: "Draw!"
player 2 win
Expected: "Player 2 won!", instead got: "Draw!"
Expected: "Player 2 won!", instead got: "Draw!"
Expected: "Player 2 won!", instead got: "Draw!"
draw
Test Passed: Value == "Draw!"
Test Passed: Value == "Draw!"
Test Passed: Value == "Draw!"
What do I need to change in my code for it to return "Player 1 Won!" and "Player 2 Won!"?
The case expression in Ruby works like this (see section 11.5.2.2.4 The case expression of the ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification for details):
case object_of_interest
when category1
some_expression
when category2
some_other_expression
when category3
another_expression
else
an_entirely_different_expression
end
is (roughly) equivalent to
temp = object_of_interest
if category1 === temp
some_expression
elsif category2 === temp
some_other_expression
elsif category3 === temp
another_expression
else
an_entirely_different_expression
end
The === operator (I call it the case subsumption operator, but that is something I made up, not an official term) checks whether the right-hand operand can be subsumed under the receiver. What do I mean by that? Imagine, you had a drawer labelled with the receiver, would it make sense to put the right-hand operand into it?
So, if you have
foo === bar
this asks "if I have a drawer labelled foo, would it make sense to put bar into it?", or "if I interpret foo as describing a set, would bar be a member of that set?"
The default implementation of Object#=== is more or less just simple equality, it looks a bit like this (see also section 15.3.1.3.2 Kernel#=== of the ISO/IEC 30170:2012 Ruby Language Specification):
class Object
def ===(other)
equals?(other) || self == other
end
end
This is not exactly helpful, because it does not show what I mean by case subsumption. For that, we have to look at some of the overrides. For example, Module#=== (see also section 15.2.2.4.5 Module#=== of the ISO/IEC 30170:2012 Ruby Language Specification):
mod === obj → true or false
Case Equality—Returns true if obj is an instance of mod or an instance of one of mod's descendants. Of limited use for modules, but can be used in case statements to classify objects by class.
So, it is equivalent to
class Module
def ===(other)
other.kind_of?(self)
end
end
So, it implements the question "If I had a drawer named mod, would it make sense to put other in it" as "Is other an instance of mod", and we can see that it makes sense:
Integer === 3 #=> true
Integer === 'Hello' #=> false
String === 3 #=> false
String === 'Hello' #=> true
If I have a drawer labelled Integer, does it make sense to put 3 in it? Yes, it does. What about 'Hello'? No, it does not.
Another example is Range#=== (see also section 15.2.14.4.2 Range#=== of the ISO/IEC 30170:2012 Ruby Language Specification):
rng === obj → true or false
Returns true if obj is between begin and end of range, false otherwise (same as cover?).
So, it implements the question "If I had a drawer named rng, would it make sense to put other in it" as "Does the range rng cover other", and we can see that it makes sense:
0..2 === 1 #=> true
0..2 === 3 #=> false
If I have a drawer labelled 0..2, does it make sense to put 1 in it? Yes, it does. What about 3? No, it does not.
The last example is Regexp#=== (see also section 15.2.15.7.4 Regexp#=== of the ISO/IEC 30170:2012 Ruby Language Specification). Imagine a Regexp describing an infinite set of languages that match that Regexp:
/el+/ === 'Hello' #=> true
/el+/ === 'World' #=> false
If I have a drawer labelled /el+/, does it make sense to put 'Hello' in it? Yes, it does. What about 'World'? No, it does not.
So, your case expression:
case p1...p2
when p1 == 'rock' && p2 == 'scissors' || p1 == 'scissors' && p2 == 'paper' || p1 == 'paper' && p2 == 'rock'
return 'Player 1 won!'
when p1 == 'scissors' && p2 == 'rock' || p1 == 'paper' && p2 == 'scissors' || p1 == 'rock' && p2 == 'paper'
else
p1 == p2
return 'Draw!'
end
Note that else doesn't have a conditional. It literally means "in every other case", so there is no need for a conditional. So, the p1 == p2 is actually the first expression inside the else block. However, since it has no side-effects, and its value is completely ignored, it doesn't actually do anything at all.
Let's simplify that to:
case p1...p2
when something_that_is_either_true_or_false
return 'Player 1 won!'
when something_that_is_either_true_or_false
else
return 'Draw!'
end
This is equivalent to
temp = p1...p2
if something_that_is_either_true_or_false === temp
return 'Player 1 won!'
elsif something_that_is_either_true_or_false === temp
else
return 'Draw!'
end
Now, depending on how exactly you call the rps method, those conditional expressions may be either true or false, but we don't know exactly which. What we do know, however, is that neither TrueClass#=== nor FalseClass#=== override Object#===, so they still have the same semantics: they simply test for equality. So, this is actually equivalent to
if something_that_is_either_true_or_false == p1...p2
return 'Player 1 won!'
elsif something_that_is_either_true_or_false == p1...p2
else
return 'Draw!'
end
Here's the thing: a boolean will never be equal to a range. They aren't even the same type! What you are effectively asking in your case expression is
"If I have a drawer labelled true, does it make sense to put p1...p2 into it?" or "If I have a drawer labelled false, does it make sense to put p1...p2 into it?" It doesn't matter which of the two situations you have, because the answer is "No" in both cases.
So, there is, in fact, no possible way in which any of the when clauses can ever be true. Therefore, your case expression will always evaluate the else clause, or put another way, your entire method is completely equivalent to
def rps(_, _) # ignore all arguments
'Draw!'
end
There is a second form of the case expression: when you leave out the object_of_interest, then it becomes equivalent to a series of conditionals, like this:
case
when conditional1
some_expression
when conditional2
some_other_expression
when conditional3
another_expression
else
an_entirely_different_expression
end
is (roughly) equivalent to
if conditional1
some_expression
elsif conditional2
some_other_expression
elsif conditional3
another_expression
else
an_entirely_different_expression
end
So, one way to fix your method, is to simply delete the p1...p2:
case # that is all we need to change to make it work
when p1 == 'rock' && p2 == 'scissors' || p1 == 'scissors' && p2 == 'paper' || p1 == 'paper' && p2 == 'rock'
return 'Player 1 won!'
when p1 == 'scissors' && p2 == 'rock' || p1 == 'paper' && p2 == 'scissors' || p1 == 'rock' && p2 == 'paper'
else
p1 == p2
return 'Draw!'
end
That is literally all we need to change to make it work. Just delete that single expression. We can simplify it a bit further: first, as discussed above, we can remove the p1 == p2, because it doesn't do anything. Secondly, the case expression is an expression. It is not a statement. (In fact, there are no statements in Ruby. Everything is an expression.)
Ergo, the case expression evaluates to a value, in particular, it evaluates to the value of the branch that was taken. So, whenever you see something like
case foo
when bar
return baz
when qux
return frobz
end
that is equivalent to
return case foo
when bar
baz
when qux
frobz
end
Also, the last expression evaluated in a method body (or block body, lambda body, class definition body, or module definition body) is the value of the whole method (block, lambda, class definition, module definition), so in your case, the return is redundant there, too.
Lastly, your second when has no body, so it isn't doing anything, which means we can just remove it.
Which means the entire method becomes
case
when p1 == 'rock' && p2 == 'scissors' || p1 == 'scissors' && p2 == 'paper' || p1 == 'paper' && p2 == 'rock'
'Player 1 won!'
else
'Draw!'
end
Since there is only one condition, it doesn't really make sense for it to be a case expression at all, so we make it a conditional expression instead:
if p1 == 'rock' && p2 == 'scissors' || p1 == 'scissors' && p2 == 'paper' || p1 == 'paper' && p2 == 'rock'
'Player 1 won!'
else
'Draw!'
end
An alternative way of solving this with a case expression would be to actually use the fact that, when not specifically overridden, === just means equality:
case [p1, p2]
when ['rock', 'scissors'], ['scissors', 'paper'], ['paper', 'rock']
'Player 1 won!'
when ['scissors', 'rock'], ['paper', 'scissors'], ['rock', 'paper']
'Player 2 won!'
else
'Draw!'
end
This is not the completed code, but I believe you'll get the idea
def game(player1, player2)
hash_game = {
'scissors' => {
'rock' => 'loss',
'paper' => 'win'
},
'rock' => {
...
},
'paper' => {
...
}
}
result = hash_game[player1][player2]
"Player 1 #{result}!"
end
Consider writing that as follows.
WINNERS = { rock: :scissors, paper: :rock, scissors: :paper }
#=> {:rock=>:scissors, :paper=>:rock, :scissors=>:paper
def rps(p1,p2)
puts case
when WINNERS[p1] == p2
"Player 1 won!"
when WINNERS[p2] == p1
"Player 2 won!"
else
"Draw!"
end
end
rps(:rock, :scissors) -> Player 1 won!
rps(:rock, :paper) -> Player 2 won!
rps(:rock, :rock) -> Draw!
rps(:paper, :rock) -> Player 1 won!
rps(:paper, :scissors) -> Player 2 won!
rps(:paper, :paper) -> Draw!
rps(:scissors, :paper) -> Player 1 won!
rps(:scissors, :rock) -> Player 2 won!
rps(:scissors, :scissors) -> Draw!
Similar to what #megas said, I would personally create a static class variable for each game possibility like so
##GAMES = {}.tap do |h|
win, lose, draw = ["Player 1 Wins!", "Player 2 Wins!", "Draw!"]
h["rock"] = { "rock" => draw, "paper" => lose, "scissors" => win }
h["paper"] = { "rock" => win, "paper" => draw, "scissors" => lose }
h["scissors"] = { "rock" => lose, "paper" => win, "scissors" => draw }
end
Note that tap just lets you run code and alter the object your "tapping" before returning the object itself. This would allow you to just do
def play_game(p1, p2)
puts ##GAMES[p1][p2]
end
This method also has the added benefit of being easy to change the messages for each outcome, and even add more outcomes, in case you wanted to implement "Rock Paper Scissors Lizard Spock" or something.

How to fix "Inconsistent indentation detected"

I am trying to submit code for a challenge I have at my course, and I get the following error:
1) Q2. Rock, paper, scissors has acceptable code quality
Failure/Error: expect(code_quality.acceptable?).to(eq(true), code_quality.problems)
Inspecting 1 file
C
Offenses:
questions/question_2.rb:26:3: C: Layout/IndentationConsistency: Inconsistent indentation detected.
if p1 == "scissors" && p2 == "paper" || ...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1 file inspected, 1 offense detected
Diff:
## -1,2 +1,2 ##
-true
+false
# ./spec/question_2_spec.rb:87:in `block (2 levels) in <top (required)>'
My code is below, along with the description for the request:
puts "Player 1 input move"
p1 = gets.chomp()
puts "Player 2 input move"
p2 = gets.chomp()
if p1 == "scissors" && p2 == "paper" ||
p1 == "paper" && p2 == "rock" ||
p1 == "rock" && p2 == "scissors"
puts "Player 1 wins"
elsif p1 == "rock" && p2 == "paper" ||
p1 == "paper" && p2 == "scissors" ||
p1 == "scissors" && p2 == "rock"
puts "Player 2 wins"
elsif p1 == "scissors" && p2 == "scissors" ||
p1 == "paper" && p2 == "paper" ||
p1 == "rock" && p2 == "rock"
puts "It's a draw"
end
**TASK DESCRIPTION
# Write a program that lets two players play Rock, Paper, Scissors. The program should:
# * Ask player 1 for their move. They can input `rock`, `paper` or
# `scissors`.
# * Ask player 2 for their move. They can input `rock`, `paper` or
# `scissors`.
# * Calculates who has won. `rock` beats `scissors`, `paper` beats
# `rock`, `scissors` beat `paper`.
# * If player 1 has won, `puts`es `Player 1 wins`.
# * If player 2 has won, `puts`es `Player 2 wins`.
# * If the game is a draw, `puts`es `It's a draw`.
#
# * Note: You can assume that players will input one of the three
# possible moves described above.
#
# * Note: When you run the automated tests, the tests will simulate
# the user input. You shouldn't need to enter any input manually.
# If the tests hang when you run them, it probably means your code
# doesn't work correctly, yet.
#
# * Note: You can assume the players will only ever input `rock`,
# `paper` or 'scissors'
I assume it is not spacing because I swapped what comes after if once, checked and double checked spaces as well as played around with the overall spacing of the code.
The main line that throws the error is:
if p1 == "scissors" && p2 == "paper" ||
What I am doing wrong?

Increment variables within a method?

I'm trying to build a rock, paper, scissors game. Could someone please tell me or point me in the right direction of where I'm going wrong with this code?
I want the players_score and cpu_score variables to increment by 1 each time either one wins a round.
choice = ["rock", "paper", "Scissors"]
players_score = 0
cpu_score = 0
def win_lose(a,b,c,d)
if a == "rock" && b == "scissors"
c+=1
puts "YOU WIN!!"
elsif a == "scissors" && b == "rock"
d+=1
puts "YOU LOSE!!"
elsif a =="paper" && b == "rock"
c+=1
puts "YOU WIN!!"
elsif a =="rock" && b == "paper"
d+=1
puts "YOU LOSE!!"
elsif a == "scissors" && b == "paper"
c+=1
puts "YOU WIN!!"
elsif a == "paper" && b == "scissors"
d+=1
puts "YOU LOSE!!"
else a == b
puts "Its a Draw this time!!"
end
end
while players_score < 2 && cpu_score < 2
print "Lets play. Plese choose rock, paper or scissors: "
players = gets.chomp.downcase
puts "You have #{players}"
cpu = choice.sample.downcase
puts "Computer has #{cpu}"
win_lose(players, cpu, players_score, cpu_score)
puts "scores are player #{players_score} , cpu #{cpu_score}"
end
Return a value from win_lose, and use that to determine increment the variable, e.g.:
def win_lose(a,b,c,d)
if a == "rock" && b == "scissors"
c+=1
winner = 'player'
elsif a == "scissors" && b == "rock"
d+=1
winner = 'computer'
# [.. etc ..]
else a == b
winner = 'draw'
end
return winner
end
player_score = 0
cpu_score = 0
choice = ["rock", "paper", "Scissors"]
while players_score < 2 && cpu_score < 2
print "Lets play. Plese choose rock, paper or scissors: "
players = gets.chomp.downcase
puts "You have #{players}"
cpu = choice.sample.downcase
puts "Computer has #{cpu}"
winner = win_lose(players, cpu, players_score, cpu_score)
if winner == 'player'
player_score += 1
puts "YOU WIN!!"
elsif winner == 'computer'
cpu_score += 1
puts "YOU LOSE!!"
else
puts "It's a draw"
end
puts "scores are player #{players_score} , cpu #{cpu_score}"
end
As you can see, this also allows you to not repeat the same "YOU WIN!!" sentence over and over again. I moved that outside the win_lose function so that this function does only one thing: determine the winner or loser, rather than two things: determine the winner or loser, and inform them of this.
This distinction is perhaps not important for such a small program, but as your programs grow it will be critical to keep your program understandable.

Rock Paper Scissors in Python.... do i use chained or nested conditional to get this to run?

Am i supposed to create a chained conditional or nested?
player1 = raw_input ("?")
player2 = raw_input ("?")
if (player1 == 'rock' and player2 == 'scissors'):
print "Player 1 wins."
elif (player1 == 'rock' and player2 == 'rock'):
print "Tie"
elif (player1 == 'scissors' and player2 == 'paper'):
print "Player 1 wins."
elif (player2 == 'scissors' and player2 == 'scissors'):
print "Tie"
elif (player1 == 'paper' and player2 == 'paper'):
print "Tie"
elif (player1 == 'paper' and player2 == 'scissors'):
print "Player 2 wins."
elif (player1 == 'rock'and player2 == 'paper'):
print "Player 2 wins."
elif (player1 == 'paper' and player2 == 'rock'):
print "Player 2 wins."
elif (player1 == 'scissors' and player2 == 'rock'):
print "Player 2 wins."
else:
print "This is not a valid object selection."
The question should probably be migrated to CodeReview, but until then, I'll put my answer here...
Instead of making all those checks nested or chained, you can use or to combine different conditions yielding the same result, e.g. one player winning. Also, the check whether player 1 wins over player 2 is the same as the other way around, so you can put this into a function.
This will make you code shorter, more readable and easier to maintain.
ROCK, PAPER, SCISSORS = "rock", "paper", "scissors"
def valid(s):
""" s is a valid input """
return s in [ROCK, PAPER, SCISSORS]
def wins(p1, p2):
""" p1 wins over p2 """
return (p1 == ROCK and p2 == SCISSORS or
p1 == SCISSORS and p2 == PAPER or
p1 == PAPER and p2 == ROCK)
player1 = raw_input ("Player 1: ").strip().lower()
player2 = raw_input ("Player 2: ").strip().lower()
if valid(player1) and valid(player2):
if wins(player1, player2):
print "Player 1 wins."
elif wins(player2, player1):
print "Player 2 wins."
else:
print "Tie."
else:
print "This is not a valid object selection."

Ruby Koan 151 raising exceptions

I'm going through the ruby koans, I'm on 151 and I just hit a brick wall.
Here is the koan:
# You need to write the triangle method in the file 'triangle.rb'
require 'triangle.rb'
class AboutTriangleProject2 < EdgeCase::Koan
# The first assignment did not talk about how to handle errors.
# Let's handle that part now.
def test_illegal_triangles_throw_exceptions
assert_raise(TriangleError) do triangle(0, 0, 0) end
assert_raise(TriangleError) do triangle(3, 4, -5) end
assert_raise(TriangleError) do triangle(1, 1, 3) end
assert_raise(TriangleError) do triangle(2, 4, 2) end
end
end
Then in triangle.rb we have:
def triangle(a, b, c)
# WRITE THIS CODE
if a==b && a==c
return :equilateral
end
if (a==b && a!=c) || (a==c && a!=b) || (b==c && b!=a)
return :isosceles
end
if a!=b && a!=c && b!=c
return :scalene
end
if a==0 && b==0 && c==0
raise new.TriangleError
end
end
# Error class used in part 2. No need to change this code.
class TriangleError < StandardError
end
I am beyond confused - any help at all would be much appreciated!
EDIT: To complete this koan, I need to put something in the TriangleError class - but I have no idea what
UPDATE: Here is what the koan karma thing is saying:
<TriangleError> exception expected but none was thrown.
A triangle should not have any sides of length 0. If it does, it's either a line segment or a point, depending on how many sides are 0.
Negative length doesn't make sense.
Any two sides of a triangle should add up to more than the third side.
See 3, and focus on the "more".
You shouldn't need to change the TriangleError code, AFAICS. Looks like your syntax is just a little wacky. Try changing
raise new.TriangleError
to
raise TriangleError, "why the exception happened"
Also, you should be testing the values (and throwing exceptions) before you do anything with them. Move the exception stuff to the beginning of the function.
You forgot the case when a,b, or c are negative:
def triangle(a, b, c)
raise TriangleError if [a,b,c].min <= 0
x, y, z = [a,b,c].sort
raise TriangleError if x + y <= z
[:equilateral,:isosceles,:scalene].fetch([a,b,c].uniq.size - 1)
end
Ended up doing this:
def triangle(a, b, c)
a, b, c = [a, b, c].sort
raise TriangleError if a <= 0 || a + b <= c
[nil, :equilateral, :isosceles, :scalene][[a, b, c].uniq.size]
end
Thanks to commenters here :)
def triangle(a, b, c)
[a, b, c].permutation do |sides|
raise TriangleError unless sides[0] + sides[1] > sides[2]
end
case [a,b,c].uniq.size
when 3; :scalene
when 2; :isosceles
when 1; :equilateral
end
end
I like Cory's answer. But I wonder if there's any reason or anything to gain by having four tests, when you could have two:
raise TriangleError, "Sides must by numbers greater than zero" if (a <= 0) || (b <= 0) || (c <= 0)
raise TriangleError, "No two sides can add to be less than or equal to the other side" if (a+b <= c) || (a+c <= b) || (b+c <= a)
You don't need to modify the Exception. Something like this should work;
def triangle(*args)
args.sort!
raise TriangleError if args[0] + args[1] <= args[2] || args[0] <= 0
[nil, :equilateral, :isosceles, :scalene][args.uniq.length]
end
I wanted a method that parsed all arguments effectively instead of relying on the order given in the test assertions.
def triangle(a, b, c)
# WRITE THIS CODE
[a,b,c].permutation { |p|
if p[0] + p[1] <= p[2]
raise TriangleError, "Two sides of a triangle must be greater than the remaining side."
elsif p.count { |x| x <= 0} > 0
raise TriangleError, "A triangle cannot have sides of zero or less length."
end
}
if [a,b,c].uniq.count == 1
return :equilateral
elsif [a,b,c].uniq.count == 2
return :isosceles
elsif [a,b,c].uniq.count == 3
return :scalene
end
end
Hopefully this helps other realize there is more than one way to skin a cat.
After try to understand what I must to do with koan 151, I got it with the first posts, and get lot fun to check everyone solution :) ... here is the mine:
def triangle(a, b, c)
array = [a, b, c].sort
raise TriangleError if array.min <= 0 || array[0]+array[1] <= array[2]
array.uniq!
array.length == 1 ? :equilateral: array.length == 2 ? :isosceles : :scalene
end
Koan is a very interesting way to learn Ruby
You definately do not update the TriangleError class - I am stuck on 152 myself. I think I need to use the pythag theorem here.
def triangle(a, b, c)
# WRITE THIS CODE
if a == 0 || b == 0 || c == 0
raise TriangleError
end
# The sum of two sides should be less than the other side
if((a+b < c) || (a+c < b) || (b+c < a))
raise TriangleError
end
if a==b && b==c
return :equilateral
end
if (a==b && a!=c) || (a==c && a!=b) || (b==c && b!=a)
return :isosceles
end
if(a!=b && a!=c && b!=c)
return :scalene
end
end
# Error class used in part 2. No need to change this code.
class TriangleError < StandardError
end
In fact in the following code the condition a <= 0 is redundant. a + b will always be less than c if a < 0 and we know that b < c
raise TriangleError if a <= 0 || a + b <= c
I don't think I see this one here, yet.
I believe all the illegal triangle conditions imply that the longest side can't be more than half the total. i.e:
def triangle(a, b, c)
fail TriangleError, "Illegal triangle: [#{a}, #{b}, #{c}]" if
[a, b, c].max >= (a + b + c) / 2.0
return :equilateral if a == b and b == c
return :isosceles if a == b or b == c or a == c
return :scalene
end
This one did take some brain time. But here's my solution
def triangle(a, b, c)
# WRITE THIS CODE
raise TriangleError, "All sides must be positive number" if a <= 0 || b <= 0 || c <= 0
raise TriangleError, "Impossible triangle" if ( a + b + c - ( 2 * [a,b,c].max ) <= 0 )
if(a == b && a == c)
:equilateral
elsif (a == b || b == c || a == c)
:isosceles
else
:scalene
end
end
I ended up with this code:
def triangle(a, b, c)
raise TriangleError, "impossible triangle" if [a,b,c].min <= 0
x, y, z = [a,b,c].sort
raise TriangleError, "no two sides can be < than the third" if x + y <= z
if a == b && b == c # && a == c # XXX: last check implied by previous 2
:equilateral
elsif a == b || b == c || c == a
:isosceles
else
:scalene
end
end
I don't like the second condition/raise, but I'm unsure how to improve it further.
You could also try to instance the exception with:
raise TriangleError.new("All sides must be greater than 0") if a * b * c <= 0
Here is what I wrote and it all worked fine.
def triangle(a, b, c)
# WRITE THIS CODE
raise TriangleError, "Sides have to be greater than zero" if (a == 0) | (b == 0) | (c == 0)
raise TriangleError, "Sides have to be a postive number" if (a < 0) | (b < 0) | (c < 0)
raise TriangleError, "Two sides can never be less than the sum of one side" if ((a + b) < c) | ((a + c) < b) | ((b + c) < a)
raise TriangleError, "Two sides can never be equal one side" if ((a + b) == c) | ((a + c) == b) | ((b + c) == a)
return :equilateral if (a == b) & (a == c) & (b == c)
return :isosceles if (a == b) | (a == c) | (b == c)
return :scalene
end
# Error class used in part 2. No need to change this code.
class TriangleError < StandardError
end
You have to check that the new created triangle don't break the "Triangle inequality". You can ensure this by this little formula.
if !((a-b).abs < c && c < a + b)
raise TriangleError
end
When you get the Error:
<TriangleError> exception expected but none was thrown.
Your code is probably throwing an exception while creating a regular triangle in this file. about_triangle_project.rb
For the Koan about_triangle_project_2.rb there's no need to change TriangleError class. Insert this code before your triangle algorithm to pass all tests:
if ((a<=0 || b<=0 || c<=0))
raise TriangleError
end
if ((a+b<=c) || (b+c<=a) || (a+c<=b))
raise TriangleError
end
Here is my version... :-)
def triangle(a, b, c)
if a <= 0 || b <= 0 || c <= 0
raise TriangleError
end
if a + b <= c || a + c <= b || b + c <= a
raise TriangleError
end
return :equilateral if a == b && b == c
return :isosceles if a == b || a == c || b == c
return :scalene if a != b && a != c && b != c
end
This is what I ended up with. It is sort of a combination of a few of the above examples with my own unique take on the triangle inequality exception (it considers the degenerate case as well). Seems to work.
def triangle(a, b, c)
raise TriangleError if [a,b,c].min <= 0
raise TriangleError if [a,b,c].sort.reverse.reduce(:-) >= 0
return :equilateral if a == b && b == c
return :isosceles if a == b || a == c || b == c
return :scalene
end
Here is my elegant answer, with a lot of help from the comments above
def triangle(a, b, c)
test_tri = [a,b,c]
if test_tri.min <=0
raise TriangleError
end
test_tri.sort!
if test_tri[0]+ test_tri[1] <= test_tri[2]
raise TriangleError
end
if a == b and b == c
:equilateral
elsif a != b and b != c and a != c
:scalene
else
:isosceles
end
end
#(1)Any zero or -ve values
if [a,b,c].any? { |side_length| side_length <= 0 }
raise TriangleError
end
#(2)Any side of a triangle must be less than the sum of the other two sides
# a < b+c, b < a+c and c < a+b a valid triangle
# a >= b+c, b >= a+c and c >= a+b an invalid triangle
total_of_side_lengths = [a,b,c].inject {|total,x| total += x}
if [a,b,c].any? { |side_length| side_length >= (total_of_side_lengths - side_length)}
raise TriangleError
end
Not that this question needed another answer; however, I think this is the simplest and most readable solution. Thanks to all those before me.
def triangle(a, b, c)
a, b, c = [a, b, c].sort
raise TriangleError, "all sides must > 0" unless [a, b, c].min > 0
raise TriangleError, "2 smaller sides together must the > 3rd side" unless a + b > c
return :equilateral if a == b && a == c
return :isosceles if a == b || a == c || b == c
return :scalene
end
# Error class used in part 2. No need to change this code.
class TriangleError < StandardError
end
def triangle(a, b, c)
sides = a, b, c # Assigns variable signs (array) to all arguments.
begin
raise TriangleError if sides.inject(:+) <= 0 # Raise an error if all sides added together are less than or equal to 0. (the triangle would be invalid).
raise TriangleError if sides.any?(&:negative?) #Raise an error if there are any negative sides.
sides.each {|side| (side < (sides.inject(:+) - side) ? nil : (raise TriangleError))} # For the final check, Raise an error if any single side is greater than the other two sides added together. It can be broken down like this if side is less than (remaining sides - side we're comparing) raise an error, else, nil.
return :equilateral if sides.uniq.length == 1
return :isosceles if sides.uniq.length == 2
return :scalene if sides.uniq.length == 3
resuce TriangleError
end
end
your previous triangle method should appear here
class TriangleError < StandardError
end
def triangle(x,y,z)
if(x>=y+z||y>=x+z||z>=x+y)
raise TriangleError,"impossible triangle"
elsif(x==0&&y==0&&z==0)||(x<0||y<0||z<0)
raise TriangleError,"length cannot be zero or negative"
elsif(x==y&&x==z)
:equilateral
elsif(x==y||y==z||x==z)
:isosceles
else
:scalene
end
end
My solution, I think it's one of the more readable ones:
def triangle(a, b, c)
a, b, c = [a, b, c].sort
if a <= 0 or c >= a + b
raise TriangleError
end
case [a, b, c].uniq.length
when 1
:equilateral
when 2
:isosceles
when 3
:scalene
end
end
Leon wins on fancy elegance, Benji for his knowledge of the Array API. Here's my brute elegant answer:
def triangle(a, b, c)
[a, b, c].each { | side | raise TriangleError, "Sides must be positive" unless side > 0 }
raise TriangleError, "Two sides can never be less than or equal to third side" if ((a + b) <= c) | ((a + c) <= b) | ((b + c) <= a)
return :equilateral if (a == b) && (b == c)
return :isosceles if (a == b) || (b == c) || (a == c)
return :scalene
end
No Need to change the TriangleError code for either challenge. You just need to check for invalid triangles and raise the error if the triangle isn't.
def triangle(a, b, c)
if a==0 && b==0 && c==0
raise TriangleError, "This isn't a triangle"
end
if a <0 or b < 0 or c <0
raise TriangleError, "Negative length - thats not right"
end
if a + b <= c or a + c <= b or b + c <= a
raise TriangleError, "One length can't be more (or the same as) than the other two added together. If it was the same, the whole thing would be a line. If more, it wouldn't reach. "
end
# WRITE THIS CODE
if a == b and b == c
return :equilateral
end
if (a==b or b == c or a == c)
return :isosceles
end
:scalene
end
There are some absolutely brilliant people on StackOverflow...I'm reminded of that every time I visit :D
Just to contribute to the conversation, here's the solution I came up with:
def triangle(a, b, c)
raise TriangleError if [a,b,c].min <= 0
x,y,z = [a,b,c].sort
raise TriangleError if x + y <= z
equal_sides = 0
equal_sides +=1 if a == b
equal_sides +=1 if a == c
equal_sides +=1 if b == c
# Note that equal_sides will never be 2. If it hits 2
# of the conditions, it will have to hit all 3 by the law
# of associativity
return [:scalene, :isosceles, nil, :equilateral][equal_sides]
end
Here's my solution... honestly I can't think of a more concise and readable one!
def triangle(a, b, c)
raise TriangleError unless a > 0 && b > 0 && c > 0
raise TriangleError if a == b && a + b <= c
raise TriangleError if a == c && a + c <= b
return :equilateral if a == b && b == c
return :isosceles if a == b || b == c || c == a
:scalene
end
Rules:
size must be > 0
Total of any 2 sides, must be bigger that the 3rd
Code:
raise TriangleError if ( [a,b,c].any? {|x| (x <= 0)} ) or ( ((a+b)<=c) or ((b+c)<=a) or ((a+c)<=b))
[:equilateral, :isosceles, :scalene].fetch([a,b,c].uniq.size - 1)

Resources