Skip to content

Instantly share code, notes, and snippets.

@shawna-p
Created February 16, 2022 04:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shawna-p/c5a3a96a6ebe2dea63a5ee689b33ebcd to your computer and use it in GitHub Desktop.
Save shawna-p/c5a3a96a6ebe2dea63a5ee689b33ebcd to your computer and use it in GitHub Desktop.

Conditionals and Comparisons in Ren'Py, Part 3

Welcome back to the third part of the tutorial on conditional statements and comparisons in Ren'Py. Now that you know how to write conditional statements using if/elif/else (Part 1), how to check if strings or integers are the same, and the concept of truthy/falsey for non-boolean values (Part 2), we'll look into numerical comparison and how to check more than one expression at once.

As with the last tutorial, it's recommended that you first look over the Complete Guide to Ren'Py Variables. This guide also assumes you have a basic idea of how to write choice menus. If you need a quick refresher, you can check out the section on Menus, Labels, and Jumps in the Ren'Py quickstart. And, if you haven't already, definitely check out the first two parts of this tutorial, Conditionals and Comparisons in Ren'Py, Part 1 & 2!

A quiz at the end will test your knowledge of comparisons, with additional questions combining the information in this tutorial and the earlier parts on conditionals to check your understanding of these concepts.

Conditionals and Comparisons in Ren'Py

Part 1 - Conditional Statements

Part 2 - Equality and Truthy/Falsey

Part 3 - Numerical Comparison & Combining Comparisons (You are here!)

Part 4 - Combining Everything (Coming soon!)

Numerical Comparison

If you're dealing with numbers like integers or floats, sometimes you might want to check whether a number is in a particular range, or if it's over or under a threshold. From our example list in Part 2, this includes things like:

  • If the player has at least 8 affection points with Xia, she'll ask them on a date
  • If the player has 10 or more bad end points, they get the bad end. If they have less than 10 bad end points but more than 0, they get the normal end. Otherwise, they get the good end.
  • If the player has enough money, they can buy a flower.

You may already be familiar with symbols like <, >, ≤, ≥. They're used in things like y > 5 (y is greater than 5) or x ≤ 2 (x is less than or equal to 2), or even 130 ≤ height ≤ 170 (height is between 130 and 170, inclusive of the end points).

Ren'Py uses < for "less than" and > for "greater than", as you'd expect from above. However, in coding, becomes <= (less than or equal to) and becomes >= (greater than or equal to). Note that the position of the = is important; =< and => are incorrect and will cause an error. You can think of the order as following how you'd say it - "greater than" (<) "or equal to" (=) aka <=. There's no need for double == here, since it's accompanied by a > or < symbol. You might also remember the "not equal" comparison != did something similar - all three additional symbols (!, <, >) come before the = in their operator (!=, <=, >=).

With this knowledge, we can compare numbers:

default zoran_love_points = 0
label start():
    "Zoran shuffles awkwardly, looking down at his feet."
    menu:
        "Make a joke.":
            $ zoran_love_points += 1
            "He smiles when you finish telling the joke."
        "Tell him to get over himself.":
            $ zoran_love_points -= 1
            "He looks almost angry at you."
            "Then he sighs deeply."
    if zoran_love_points >= 1:
        "Zoran" "Yeah, maybe I needed that. Thanks."

For this statement, we check if the player has 1 or more love points with Zoran through their past choices (if zoran_love_points >= 1). If they do, then Zoran trusts their opinion enough to not get upset even if the player told him to get over himself.

It's also fine to use these comparisons for floats, because they're not checking for exact values but ranges e.g.

# This is 3.0 (float) instead of just 3 (an integer)
# so we can get a float result when we do
# correct_answers/total_answers.
# (Otherwise Ren'Py would round the result down
# to the nearest integer).
define total_answers = 3.0
default correct_answers = 0
default grade = 0.0
label start:
    menu:
        "What year was Ren'Py first released?"
        "2021":
            # Wrong! Ren'Py was first released in 2004
            pass
        "2004":
            $ correct_answers += 1
    menu:
        "Why is it bad to use == to compare floats?"
        "Decimals can have rounding errors.":
            $ correct_answers += 1
        "You shouldn't compare any numbers with ==":
            # Wrong! It's fine to compare integers with ==
            # but don't use it for floats!
            pass
    menu:
        "Can you put an else clause before an elif?"
        "Yes":
            # Wrong! The `if` is required, the `elif` clauses are
            # optional, and the `else` clause, if included, must
            # go at the end as a "catch-all" for the remaining possibilities.
            pass
        "No":
            $ correct_answers += 1

    # Divide the # of correct answers by the # of total
    # answers to get a number like 0.6666667 for 2/3
    $ grade = correct_answers / total_answers
    if grade >= 0.7:
        "Amazing!"
    elif grade > 0.5:
        "Not bad!"
    elif grade > 0.3:
        "Hmm, maybe try again."
    else:
        "Study up some more next time!"

Here you can see that we have comparisons like if grade >= 0.7 (if grade is greater than or equal to 0.7) and elif grade > 0.3 (if grade is strictly greater than 0.3).

Note that this illustrates a very useful feature of if/elif/else-style conditional statements, which is that only one expression in an if/elif/else can be True, and the rest will be skipped. We've seen this before in some previous examples, but let's really take a look at this and see how it works now that we're dealing with number ranges.

More specifically, what this means is that the above conditional statement is the same as:

if grade >= 0.7:
    "Amazing!"
elif 0.7 >= grade > 0.5:
    "Not bad!"
elif 0.5 > grade > 0.3:
    "Hmm, maybe try again."
else:
    "Study up some more next time!"

Let's look at it more closely.

So, first, we check if grade >= 0.7. If grade is equal to something like 0.9, then 0.9 >= 0.7 is True so the player will see the line "Amazing!" and the game will continue with whatever content is after the conditional statement.

However, if grade is equal to 0.6, then 0.6 >= 0.7 is False, because 0.6 is not greater than or equal to 0.7. In this case, we'll skip the block underneath if grade >= 0.7 and move on to elif grade > 0.5.

Now we look at this line and compare it - is 0.6 > 0.5? Yes! 0.6 is greater than 0.5. So this statement is True and the player sees the line "Not bad!".

This is the same as elif 0.7 >= grade > 0.5 seen above, because we already know that if we're checking elif grade > 0.5 then the first if statement, grade >= 0.7, wasn't True. If it had been True, then the player would have seen "Amazing!" and the game wouldn't have bothered to check elif grade > 0.5. So, since it wasn't, then the number must be less than 0.7.

There is an example in the Complete Guide to Ren'Py Variables which demonstrates this property as well:

if bad_end_points > 10:
    # The player made more than 10 bad choices
    jump super_bad_end
elif good_end_points > 10:
    # The player made more than 10 good choices
    jump excellent_end
elif bad_end_points > 5:
    # The player made 6-10 bad choices
    jump bad_end
elif good_end_points > 5:
    # The player made 6-10 good choices
    jump good_end
else:
    # The player didn't get more than 5 good or bad points
    jump normal_end

elif bad_end_points > 5 comes after the line if bad_end_points > 10, so we know that if we got to the clause elif bad_end_points > 5, then bad_end_points is at most 10, because if it was more, then the first expression would have been True and we wouldn't be checking this next one. Since elif bad_end_points > 5 is looking for strictly greater than 5, and we know that it can't be more than 10, then this statement will only be True if bad_end_points is between 6 and 10, inclusive.

Similarly, the only way we'd get to the else clause is if the player had 5 or fewer good or bad end points (so, 0-5), since if they had more than 10 one of the first two expressions would have been True, and if they had more than 5 the third or fourth expressions would have been True. Since we got to the else clause, that means that none of the previous expressions were True, so the player has between 0 and 5 good end points and 0-5 bad end points.

Note on Strings

Besides just numbers, you can actually use <=, > etc with strings! In the case of strings, it compares them alphabetically, with a few notable caveats. This is, however, used very infrequently. Click below if you'd like to learn more about it.

This kind of comparison is very uncommon for most games, and it won't be included in the quiz at the end of this tutorial.

Click for information on using less than/greater than for strings

For example, you can do the following:

if 'apple' < 'banana':
    # This is the one which will display
    "apple comes before banana in the dictionary."
else:
    # This won't be shown because the above condition is True
    "banana comes before apple in the dictionary."

So, 'apple' comes before 'banana' because 'a' comes before 'b'

Shorter words come earlier than longer ones:

if 'key' < 'keyhole':
    # This is the one which will display
    "key comes before keyhole in the dictionary."
else:
    # This won't be shown because the above condition is True
    "keyhole comes before key in the dictionary."

So, 'key' comes before 'keyhole' because 'key' is shorter.

The caveat I mentioned is that < > etc-style comparisons for strings do NOT take into account capitalization the way you might expect. In ASCII, A comes before a in ASCII codes (ASCII code 65 vs 97 - you can actually test this out on a computer! Hold down ALT and type 65 to get A, and ALT+97 to get a).

The reason I'm explaining this is because if you recall, "Apple" != "apple", since the comparison is case-sensitive. Well, the same is true here - if you try to compare "Apple" vs "apple" vs "APPLE", the results may not be what you expect:

"Apple" == "apple"  # False; comparison is case-sensitive so these aren't the same
"apple" < "apple"   # False; these strings are identical, so it isn't accurate
                    # to say that 'apple' comes strictly before 'apple'.
"apple" <= "apple"  # True; "apple" == "apple" is True and <= means less than
                    # OR equal to, which is the important part, since these are
                    # equal.
"Apple" <= "apple"  # True; A comes before a in the ASCII 'dictionary' ordering
"Apple" < "apple"   # Also True; "Apple" and "apple" aren't equal, and "Apple"
                    # comes before "apple" due to ASCII ordering
"Apple" < "APPLE"   # False; the first letter is the same (A) so we compare p < P
                    # p comes *after* P in ASCII ordering (capital letters are
                    # all earlier than lowercase), so p < P is False.
                    # Notably, P=ALT+80, p=ALT+112 (and 112 < 80 is False).
"APPLE" < "apple"   # True; a comes after A in ASCII ordering.

So, with this knowledge, what is the outcome of the following comparisons?

"Foxtrot" <= "fox"
  1. True
  2. False
Click for answer

This is True! Although "foxtrot" <= "fox" would be False, because as mentioned, shorter words come before longer ones if they begin with the same letters, in this case it's most important to actually start by looking at the first letter of each word..

How Ren'Py will compare them is basically going letter-by-letter to see if there's any difference and one string comes before the other. So, it begins by comparing "F" <= "f". This is True, because as mentioned, capital letters come before lowercase letters. And that's actually all the information we need to conclude that yes, "Foxtrot" comes before "fox".

"iPod" <= "iPhone"
  1. True
  2. False
Click for answer

This is False! To understand why, let's step through how Ren'Py would evaluate this.

First, we look at the first character in each string. For both, we have i and i, so Ren'Py compares "i" <= "i". "i" isn't less than "i", but it is equal to "i", so there's still potential for these strings to match or for the first one to be less than the second to make the whole condition True, so we need to keep looking.

Next, we look at the second character: "P" <= "P". Same thing here - these letters are the same, so we need to keep checking.

Now, on the third character, we compare "o" <= "h". This is different! "o" comes after "h" in the alphabet, and these are both lowercase letters, so we know that "o" is "larger" than "h". That means that "o" <= "h" is False - they aren't equal, and "o" is larger than "h" - which means the overall comparison of "iPod" <= "iPhone" is also False.

In summary, it's important to remember that just like when comparing if two strings are equal, capitalization is very important when using <=, <, >, and >= to compare strings, since they are compared using their corresponding ASCII codes to determine the result of a "less than"/"greater than" comparison. If capitalization is the same and the first letters are the same, shorter words will come before longer ones.

Combining more than one comparison

Now that you can directly compare values with == and !=, and compare numbers with < >, <= and >=, we'll also look at combining comparisons. Remember earlier we had some statements like:

  1. If the player is carrying a life jacket or they made it to the top of the ship in time to get in a life boat, they survive.
  2. If the player gave Zoran the book Pride & Prejudice and has at least 5 trust points with Zoran, Zoran will share a story from his childhood.

Note the language used to describe these comparisons - "or" and "and" are actually Python concepts we can use directly! We'll start with using them for Booleans (True/False), since that's the simplest to understand, and in the next part you'll see how to combine them with all the previous things we've learned to create complex conditional statements.

and

Consider the following statement:

If the player went to the park and they own a dog, then we know that they took their dog to the park. The dialogue should reflect this.

So, say we have two booleans, went_to_park = True and owns_dog = True.

If we want to check that both of them are True, we can do:

if went_to_park and owns_dog:
    "Your dog was sleeping in the corner of the room, tired after your walk in the park earlier that day."

If either or both of went_to_park or owns_dog is False, then this statement won't play. Let's construct a more elaborate example, with some elif and else clauses to see what happens:

if went_to_park and owns_dog:
    "Your dog was sleeping in the corner of the room, tired after your walk in the park earlier that day."
elif went_to_park:
    "You sat down on the couch, tired after your walk in the park earlier that day."
elif owns_dog:
    "Your dog seemed restless after being inside all day while you went to the movies."
else:
    "You sat down on the couch, still thinking about the movie you'd seen earlier."

So, let's break it down.

  • Case 1 - the player has gone to the park and owns a dog
    • Both went_to_park and owns_dog are True, so the condition if went_to_park and owns_dog is also True.
    • This means that the player sees the line "Your dog was sleeping in the corner of the room, tired after your walk in the park earlier that day."
  • Case 2 - the player has gone to the park, but doesn't own a dog
    • went_to_park is True, but owns_dog is False. This means that if went_to_park and owns_dog is False, because the player doesn't own a dog.
    • The next expression, elif went_to_park, is True, because the player did go to the park. So they see the line "You sat down on the couch, tired after your walk in the park earlier that day."
  • Case 3 - the player owns a dog, but didn't go to the park
    • owns_dog is True, but went_to_park is False, so if went_to_park and owns_dog is False because the player didn't go to the park
    • Next, elif went_to_park is False, because the player didn't go to the park.
    • Next, elif owns_dog is True - the player does own a dog. So they see the line "Your dog seemed restless after being inside all day while you went to the movies."
  • Case 4 - the player didn't go to the park, and they don't own a dog
    • went_to_park is False and owns_dog is False, so if went_to_park and owns_dog is False, because the player neither went to the park nor owns a dog
    • elif went_to_park is False, because the player didn't go to the park
    • elif owns_dog is False, because the player doesn't own a dog
    • else then must execute, since everything before it was False. So the player sees the line "You sat down on the couch, still thinking about the movie you'd seen earlier."

Basically, for and, both operands on either side of the and operator need to be True in order for the whole statement to be True. It's not enough for the player to have just gone to the park, or just to own a dog - they must both own a dog and have gone to the park for the first if went_to_park and owns_dog expression to evaluate to True.

operator - special symbols or words that indicate some kind of computation should take place. You've seen operators in math before; they include things like +, -, and /. Those math operators exist in programming languages, as well as a few other ones such as and, as we've seen here, and or, as we'll see in the next section.

operand - what we call the values the operator acts on. For example, in the expression 2 + 4, 2 and 4 are the operands, which are acted on by the operator +. The operator, in this case, adds the two operands together and the result is the number 6 (2 + 4 = 6).

or

or is similar to and, but less strict - only one of the operands on either side of the or operator has to be True (or both). Consider the following:

If the player helped Ashwin with their groceries or mowed their lawn, then they'll mention how the player helped them out and agree to return the favour.

if helped_ash_groceries or mowed_ash_lawn:
    "Ash" "I guess you did help me out earlier today..."
    "Ash" "So I can spare some time to help you, too."
    jump moving_day_ash
else:
    "Ash" "No, sorry, I'm just too busy."
    "Ash" "Maybe you can ask Xia or Zoran."
    jump no_help

Let's look at what happens for the different combinations of helped_ash_groceries and mowed_ash_lawn:

  • Case 1 - the player both mowed Ashwin's lawn and also helped them with groceries
    • mowed_ash_lawn is True and helped_ash_groceries is True. We only needed one or the other to be True, but it's fine that they're both True - Ashwin will agree to help and the player will jump to moving_day_ash
  • Case 2 - the player mowed Ashwin's lawn but didn't help them with groceries
    • mowed_ash_lawn is True, but helped_ash_groceries is False. However, we only need one of the two to be True, since we're using or. So, Ashwin will agree to help the player and they'll jump to the moving_day_ash label
  • Case 3 - the player didn't mow Ashwin's lawn, but they did help them with groceries
    • mowed_ash_lawn is False, but helped_ash_groceries is True. Again, we just need one of the two to be True, so Ashwin will agree to help and the player will jump to moving_day_ash
  • Case 4 - the player did not mow Ashwin's lawn and didn't help them with their groceries
    • mowed_ash_lawn is False and helped_ash_groceries is False. Neither of the two operands is True, so the whole thing is False. Ashwin says they are too busy and the player goes to the no_help label

and and or in Practice

Note that you can use as many and and or operators in a conditional statement as you like to connect many operands. The outcome of evaluating such an expression will depend on which of and or or you're using, as well as the value (True/False) of each operand.

and:

if True and True:
    # True

if True and False:
    # False

if False and True:
    # False

if False and False:
    # False

if True and True and True and True:
    # True only if *all* operands are True

if True and False and False and False:
    # False; all operands must be True

or:

if True or True:
    # True

if True or False:
    # True

if False or True:
    # True

if False or False:
    # False

if True or True or True or True:
    # True

if True or False or False or False:
    # True; at least one True makes the whole condition True

Quiz

Rather than some of the longer questions explored in previous quizzes, this quiz will be pretty short to test your understanding of the concepts here before we move on to the biggest challenge - putting everything together.

Question 1

Which statement will be shown after the following code is run?

default num_cookies = 3
label start():
    if num_cookies <= 2:
        "Statement 1"
    elif num_cookies > 3:
        "Statement 2"
    else:
        "Statement 3"
  1. Statement 1
  2. Statement 2
  3. Statement 3
  4. There's an error in the code
Click for answer

The answer is 3. Statement 3.

First, we check if num_cookies <= 2. num_cookies begins with a value of 3 and 3 <= 2 isn't true (3 isn't less than or equal to 2), so that's False and we check the following clause.

Now we look at num_cookies > 3. 3 > 3 also isn't True; 3 isn't strictly greater than 3. If this had been num_cookies >= 3 then it would have been True, but the absence of = here means it is false.

So then we end up at the final clause, else, which is True for any situations which didn't fulfill the earlier clauses, thus "Statement 3" is shown to the player.

Question 2

Which statement will be shown after the following code is run?

default is_sunny = True
default has_tickets = False
label start():
    if is_sunny and has_tickets:
        # Statement 1
        "On such a nice day, you ought to use those tickets to see the city gardens."
    elif is_sunny or has_tickets:
        # Statement 2
        "It was a shame, but it didn't seem like you'd be able to go to the city gardens."
    else:
        # Statement 3
        "With your plans dashed, you decided to watch a movie."
  1. Statement 1
  2. Statement 2
  3. Statement 3
  4. There's an error in the code
Click for answer

The answer is 2. Statement 2.

Both is_sunny and has_tickets need to be True in order for Statement 1 to be shown, and has_tickets is False, so we'll skip over that clause.

Next, we check if is_sunny or has_tickets. Remember that an or operator only needs one of its operands to be True in order for the whole expression to be True - in our case, is_sunny is True, so this expression is True and we see Statement 2. We then skip over the else clause and Statement 3 because an earlier clause (the second one) was True.

Conclusion

And that's all for Part 3! In the next part, we'll finally put all these pieces together and look at some complex conditional statements in practice. Then you'll have all the tools you need to go forth and create unique branching gameplay.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment