Reputation: 45
I'm trying out this code but getting nil everytime (It's a simplyfied version of a Black Jack game): Is it because I'm comparing two variables in a case statement?
def end_game_message(player_score, bank_score)
message = ""
case player_score
when player_score == 21
message = "Black Jack!"
when player_score > bank_score
message = "You win!"
when player_score > 21
message = "You lose!"
when player_score < bank_score
message = "You lose"
when player_score == bank_score
message = "Push"
end
message
end
puts end_game_message(21, 15)
Thanks in advance for any help!
Upvotes: 0
Views: 874
Reputation: 110755
def end_game_message(player_score, bank_score)
case player_score
when 21
bank_score == 21 ? "Push" : "Black Jack!"
when (22..)
"You lose!"
when (..bank_score-1)
bank_score <= 21 ? "You lose!" : "You win"
when (bank_score+1..)
"You win!"
else
"Push"
end
end
end_game_message(21,18) #=> "Black Jack!"
end_game_message(21,21) #=> "Push"
end_game_message(22,22) #=> "You lose!"
end_game_message(12,22) #=> "You win!"
end_game_message(18,19) #=> "You lose!"
end_game_message(19,18) #=> "You win!"
end_game_message(18,18) #=> "Push"
(..bank_score-1)
and (bank_score+1..)
are beginless and endless ranges, introduced in Ruby v2.7. They are not essential, of course, as they could be replaced with (0..bank_score-1)
and (bank_score+1..30)
. (...bank_score)
could be used in place of (..bank_score-1)
but my own style guide states that three-dot ranges are to be avoided except where the end of an infinite range is to be excluded.
Upvotes: 2
Reputation: 369624
There are actually several different questions in your question.
The first is in the title:
Can I use comparison between two variables inside a Ruby case statement?
And the answer to that is "Yes, of course, why wouldn't you?" You can use any Ruby expression.
Okay, well, if we want to be really pedantic, the answer is "No", for two reasons:
case
statement, only a case
expression. (In fact, Ruby doesn't have statements at all.)(This may sound overly pedantic, but understanding the difference between variables and objects is fundamental not only in Ruby, but in programming in general, as is the difference between expressions and statements.)
Your next question is, why you get nil
. The answer to that is that the last expression in your code is
puts end_game_message(21, 15)
So, your code evaluates to the return value of Kernel#puts
, and Kernel#puts
is defined to always return nil
.
The question that I think you are really asking but isn't actually in your question, is "Why does the case
expression not work how I think it does?"
You simply need to remember how the case
expression is evaluated. There are two different forms of the case
expression. (See section 11.5.2.2.4 The case
expression) of the ISO Ruby Language Specification, for example.
The simpler form is what the ISO Ruby Language Specification calls the case
-expression-without-expression, which looks like this:
case # Look ma, no expression
when bar then :bar
when baz then :baz
else :qux
end
And is evaluated like this:
if bar then :bar
elsif baz then :baz
else :qux
end
The other form is the case
-expression-with-expression
case foo # In this case, there is an expression here
when bar then :bar
when baz then :baz
else :qux
end
Which is evaluated like this:
if bar === foo then :bar
elsif baz === foo then :baz
else :qux
end
In your case, you are using the case
-expression-with-expression, so your case
expression is evaluated like this:
if (player_score == 21) === player_score
message = "Black Jack!"
elsif (player_score > bank_score) === player_score
message = "You win!"
elsif (player_score > 21) === player_score
message = "You lose!"
elsif (player_score < bank_score) === player_score
message = "You lose"
elsif (player_score == bank_score) === player_score
message = "Push"
end
Since player_score == 21
, player_score > bank_score
, player_score > 21
, player_score < bank_score
, and player_score == bank_score
all evaluate to either true
or false
and player_score
is a number, all the branches in your case
expression are actually checking something like this:
some_boolean === some_number
Which is never true!
So, how do we fix this?
The simplest fix would be to use the case
-expression-without-expression instead, and to do that, the only thing we need to do, is delete the expression after the case
:
case
when player_score == 21
message = "Black Jack!"
when player_score > bank_score
message = "You win!"
when player_score > 21
message = "You lose!"
when player_score < bank_score
message = "You lose"
when player_score == bank_score
message = "Push"
end
This will be evaluated like this:
if player_score == 21
message = "Black Jack!"
elsif player_score > bank_score
message = "You win!"
elsif player_score > 21
message = "You lose!"
elsif player_score < bank_score
message = "You lose"
elsif player_score == bank_score
message = "Push"
end
Which will do what you want.
If you want to keep the case
-expression-with-expression, you need to make sure that the when
-argument of each when
-clause is something that responds to ===
in a way that makes sense for your comparison.
We could, for example, use Range
s and Integer
s. The Range#===
method checks whether the argument is inside the Range
and the Integer#===
method checks whether the argument is numerically equal, so we could re-write the case
expression like this:
case player_score
when 21
message = "Black Jack!"
when ((bank_score + 1)..)
message = "You win!"
when (22..)
message = "You lose!"
when ...bank_score
message = "You lose"
when bank_score
message = "Push"
end
Since the cases are evaluated top to bottom and you can evaluate multiple conditions in the same case, we can re-shuffle these a bit to make them nicer to read:
case player_score
when 21
message = "Black Jack!"
when bank_score
message = "Push"
when 21.., ...bank_score
message = "You lose!"
when (bank_score..)
message = "You win!"
end
Also, remember that the case
expression is an expression, meaning that it evaluates to a value, namely it evaluates to the value of the branch that was evaluated. Therefore, we can "pull out" the assignment to message
:
message = case player_score
when 21
"Black Jack!"
when bank_score
"Push"
when 21.., ...bank_score
"You lose!"
when (bank_score..)
"You win!"
end
Note that this has slightly different semantics than what we had before: Before, the assignment was only evaluated when one of the branches was evaluated, otherwise, the value of message
stayed what it was before. Whereas in this form, the assignment is always evaluated, and if none of the branches gets evaluated, the case
expression evaluates to nil
.
However, the way the case
expression is written, all cases are covered, so there will always be a non-nil
value assigned.
Once we have made this transformation, we notice that at the beginning of the method, message is assigned the empty string ""
and then immediately gets assigned a different value. So, we can get rid of the first assignment, since its effects are immediately overwritten anyway.
And once we have done that, we see that we assign to the message
variable and then immediately return it. So, we might just as well return the value of the case
expression instead, like this:
def end_game_message(player_score, bank_score)
case player_score
when 21
"Black Jack!"
when bank_score
"Push"
when 21.., ...bank_score
"You lose!"
when (bank_score..)
"You win!"
end
end
Upvotes: 1
Reputation: 724
IMO, you should use if
instead of case
in this case. If you'd like to use case
, however, the code might look like this:
def end_game_message(player_score, bank_score)
case player_score
when 21
"Black Jack!"
when -> s { s > bank_score }
"You win!"
when -> s { s > 21 }
"You lose!"
when -> s { s < bank_score }
"You lose"
when -> s { s == bank_score }
"Push"
end
end
puts end_game_message(21, 15)
The key point is to give Proc
object to when
clause. In this case, s
is player_score
(value given to case
clause`).
(And minor improvement: case
statement returns value so you don't have to assign a message to local variable)
Upvotes: 6