Combining Comparisons in Ren’Py

Combining Comparisons in Ren’Py

Now that you’ve got quite a lot of tools under your belt, let’s look at some examples of ways you can combine the different comparisons you’ve learned about in the previous tutorials.

Difficulty Level: Beginner

This tutorial builds on concepts from previous tutorials on variables and conditional statements. If these concepts are unfamiliar to you, you can start with A Quick Primer on Variables in Ren’Py and work your way back here.

Parentheses and Order of Operations

One important additional tool you can use when combining many comparisons are parentheses, also known as round brackets (). Using parentheses can help clarify what order certain comparisons and evaluations should be made in. You might remember an acronym from math class that went something like PEMDAS or BEDMAS – basically, Parentheses, Exponents, Multiplication, Division, Addition, Subtraction (the BEDMAS version calls parentheses “Brackets”). This is known as  order of operations, and tells you how to evaluate a math equation like (x + 2) / 4 -> evaluate the parentheses first (x+2), then divide by 4. If it had instead been x + 2 / 4, then you would do the division first (2 / 4), since Division comes before Addition, and then do the addition (x + 0.5).

The same logic extends to programming. There’s order of operations for addition, subtraction, exponents etc in the exact way that they exist for math, but we also have operation priority for things like <= and or.

The order of operations for Python (and Ren’Py) are as follows:

OperatorDescriptionExample
ParenthesesAnything inside ()(2 + 4) / 2 = 3
ExponentsThings like x2. Written as x**2 in code.4**2 + 3 = 19
NegationNegative numbers like -2-3**2 = -9
Multiplication, Division, RemainderMultiplication is represented by * e.g. 2*3, division by / e.g. 2/3, and remainder with % e.g. 2%3.3 / 10.0 + 4 = 4.3
Addition and Subtraction+ and -, respectively.2 + 3 - 4 = 1
<<=>=>!===Comparisons. This also includes membership tests (which we haven’t covered; they involve lists).2 >= 1 + 2 = False
notAs in, not xnot 3 > 5 = True (becomes not False because 3 > 5 is False).
andThe and operator.2 == 2 and 3 > 2 = True
orThe or operator.2 < 1 or 2+1 == 3 = True
A table demonstrating order of operations.

I’ve left out a few of the operators we haven’t gone over yet, but if you’d like to learn more you can see the official Python documentation for the full list.

You can also use parentheses to split up a really long conditional statement onto multiple lines. We’ll see some examples this as well as using them to control order-of-operation below.

Examples

Example 1

Let’s go back to some of the examples mentioned earlier, which involved combining comparisons.

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.

There are two variables here. They might both be booleans, or they might be integers or some other value. For this example, let’s make it so has_life_jacket is a boolean (True/False, either the player has a life jacket or they don’t), and time_left is an integer that counts down to 0 until the player doesn’t have time left. If time_left is zero, they’re out of time and the ship sinks.

if time_left > 0 or has_life_jacket:
    "Luckily, you were rescued in time to avoid a watery grave."
else:
    "The ship gave an almighty lurch and you were tossed into the sea."
    "Without a life jacket, you struggled to remain afloat in the icy water."
    jump bad_end_1

In this case, if time_left is strictly greater than 0 (they had more than 0 time left) OR the player had a life jacket, they make it out of the ship alive. Otherwise, they end up at the bad_end_1 label (where they meet an unfortunate end).

As mentioned earlier, parentheses can help clarify what order these checks occur in:

if (time_left > 0) or has_life_jacket:

or has lowest operational priority, so this first checks if time_left > 0. If so, the whole statement is True (so Ren’Py actually won’t even bother checking the value of has_life_jacket, since it just needs one operand separated by or to be True).

Otherwise, if time_left > 0 is not true, (time_left <= 0), then the game checks has_life_jacket. If that is False as well, the whole statement is False (since it would evaluate to if False or False, rendering the whole thing False).

Example 2

If the player has at least two points of mana and doesn’t have any status effects (aka their status effect is “healthy”), they can cast a spell to make a campfire.

if mana >= 2 and status_effect == "healthy":
    "You muttered an incantation to get the campfire burning."
    "The flames sprang to life beneath your hands."
elif mana >= 2:
    "You knew you had the magical reserves to light the fire..."
    "...but in the moment, you couldn't muster the energy."
elif status_effect == "healthy":
    "Without the mana to light a fire with magic, you were stuck rubbing sticks together to light it by hand."
else:
    "Utterly exhausted, you laid on the dirt by the unlit campfire and passed out."

Here we have two operands joined by an and – the first is mana >= 2, aka “is mana greater than or equal to 2″. If so, the player has at least two (or more) mana (aka “magical reserves” or magic power), so they can light a fire with magic. However, if they have a status effect of anything other than “healthy”, then they’re too tired to light the fire. The player’s status effect is stored in the variable status_effect as a string. If they are healthy, then status_effect will be equal to "healthy". So in the second expression, we check status_effect == "healthy" to make sure they don’t have any negative status effects.

If the player both has 2+ mana and is healthy, they can light the fire with magic. elif mana >= 2 is only checked if the earlier condition, if mana >= 2 and status_effect == "healthy" was False, so if the game gets to this check we know that at least one of mana >= 2 or status_effect == "healthy" was False (otherwise if they were both True, then the player would have lit the fire with magic). If elif mana >= 2 is True, we can be sure that status_effect == "healthy" was False, aka status_effect is not equal to "healthy" (again, if it wasn’t, then the first expression would have been True and we wouldn’t have even gotten to the elif clause). So in this case, the player has enough mana to spare, but they’re too tired to actually light the fire.

Third is elif status_effect == "healthy". If we’ve gotten this far, we know that the player doesn’t have enough mana. If they had, one of the earlier expressions would have been True. So at this point, the player doesn’t have at least two mana for the fire spell. If they are still healthy (status_effect == "healthy"), they can try to light the fire by hand instead.

Finally we have the else clause. If we’ve gotten here, we know that the player doesn’t have enough mana and they have some sort of status effect (otherwise elif status_effect == "healthy" would have been True and we wouldn’t have gotten to this else clause). In this case, the player has neither the mana nor the energy to light a fire and passes out instead.

To make it clearer which operands are being evaluated first in the expression, we can use parentheses e.g.

if (mana >= 2) and (status_effect == "healthy"):
    "You muttered an incantation to get the campfire burning."
    "The flames sprang to life beneath your hands."
elif (mana >= 2):
    "You knew you had the magical reserves to light the fire..."
    "...but in the moment, you couldn't muster the energy."
elif (status_effect == "healthy"):
    "Without the mana to light a fire with magic, you were stuck rubbing sticks together to light it by hand."
else:
    "Utterly exhausted, you laid on the dirt by the unlit campfire and passed out."

This allows us to see that mana >= 2 is evaluated, then status_effect == "healthy" is evaluated, and finally the and is evaluated to see if both operands are True.

Example 3

You can actually combine as many operators and operands as you like, though it can get a little unwieldy! Consider the following conditional check from Mass Effect 3, which the player must pass to get the best outcome for one of the missions:

  • Shepard has at least 4 bars of reputation
    • reputation >= 4
  • Tali and Legion are present on Shepard’s ship
    • tali_on_ship = True
    • legion_on_ship = True
  • The player has completed the mission Rannoch: Geth Fighter Squadrons
    • geth_fighter_squadrons = True
  • The player has at least 5 “trust” points built up with the quarians
    • quarian_trust >= 5

Let’s first look at what this conditional might be written like without any further formatting:

if reputation >= 4 and tali_on_ship and legion_on_ship and geth_fighter_squadrons and quarian_trust >= 5:
    "Convince the quarian fleet to cease fire."

Lots of checks! It’s a bit hard to parse with the line so long, right? It’s actually tradition in most coding languages to keep your code under 80 characters wide – many code editors have an option to add a faint column to indicate where this limit is. VS Code calls them “rulers”, and you can see how to set them up here.

However, Python and Ren’Py get upset when you split up lines arbitrarily, because they use spacing to figure out how to run your code. So how do you split up this long conditional statement?

First, you’ll add parentheses to the beginning and end of the expression (so the whole thing is inside it apart from the if):

if (reputation >= 4 and tali_on_ship and legion_on_ship and geth_fighter_squadrons and quarian_trust >= 5):

Since it’s inside parentheses, Ren’Py won’t complain when you indent some of the operands on a new line. The parentheses indicate that this whole statement is grouped together, even across new lines.

There aren’t set-in-stone rules for how you can split the operands up onto multiple lines, but there are some things to keep in mind. For starters, you should make sure the conditional is still readable at a glance, so something like

# Incorrect 1
# (Please don't do this)
if (reputation >=
        4 and tali_on_ship
        and
        legion_on_ship and geth_fighter_squadrons and
        quarian_trust
        >= 5):

while it will technically run, is just awful to look at and much more confusing than it needs to be.

Since this is my tutorial, I’ll show you how I like to format long expressions. You can tweak it from there as you see fit.

# Correct 1
if (reputation >= 4
        and tali_on_ship
        and legion_on_ship
        and geth_fighter_squadrons
        and quarian_trust >= 5):
    "Convince the quarian fleet to cease fire."

As you can see, I split it up on the and operator to make it clear where each individual operand is. Depending on how much space you want to save, it’s also okay to include more than one operand on a line, especially if they’re related:

# Correct 2
if (reputation >= 4 and quarian_trust >= 5
        and tali_on_ship and legion_on_ship
        and geth_fighter_squadrons):
    "Convince the quarian fleet to cease fire."

You may also notice I like to indent the additional lines two levels over from the start of the if statement. This is to keep it more visually distinct from the line below it, which executes if the statement is True. Otherwise, it would look like this:

# So-so 1
if (reputation >= 4 and quarian_trust >= 5
    and tali_on_ship and legion_on_ship
    and geth_fighter_squadrons):
    "Convince the quarian fleet to cease fire."

This is also fine, but the lack of visual distance between the end of the expression and the start of the resulting block makes it harder to parse at a glance.

You might have also noticed I reordered some of the individual operands inside the expression (moving quarian_trust >= 5 up beside reputation >= 4). This is fine because we’re checking if all these operands are True, so the particular order won’t matter here.

You can also cleverly arrange individual operands to make your code faster by putting the options most likely to be False first. This is because as soon as Ren’Py encounters a False operand in a string of operands connected with and, it knows the whole condition is False, so it stops checking the rest of the operands. If you have a condition like if has_lucky_charm and points > 100, but it’s almost guaranteed the player has a lucky charm at that point and the player only has a 1% chance of having over 100 points, you can save the game engine the second check 99% of the time by switching the order to if points > 100 and has_lucky_charm, since it will only need to check has_lucky_charm in the event that points > 100 is True.

Once again, I’ll also add some parentheses around the earlier example to more clearly indicate which parts of the condition are being executed in what order:

# Correct 3
if ((reputation >= 4) and (quarian_trust >= 5)
        and tali_on_ship and legion_on_ship
        and geth_fighter_squadrons):
    "Convince the quarian fleet to cease fire."

If you’re interested in conventions for formatting Python code (which also applies to Ren’Py code), you can look at the official PEP 8 Style Guide for Python.

Example 4

Now, what happens if you want to evaluate a condition in an order it wouldn’t normally be evaluated in? Remember, for example, that not is evaluated before and and or (you can see the order of operations table above to confirm this). What if you wanted to do the following:

If the player didn’t eat the berries or drink the juice, they won’t be drowsy from the effects of the faerie magic.

So, informally, you need something like:

# (Not actual code!)
if (player did not have berries or juice):
    "You prodded at Ashwin, but they seemed unresponsive."
    "In fact, they appeared to be fast asleep."
else:
    "You felt oddly drowsy."

With your earlier tools, you might try something like:

# Incorrect 1
if not ate_berries or drank_juice:

But, now that you know more about order of operations, you can see that the actual execution order would be:

# Incorrect 1b
if (not ate_berries) or (drank_juice):

This is because not has higher priority than or, so not ate_berries gets evaluated first. This means that this whole statement will evaluate to True if either a) the player did NOT eat berries or b) the player DID drink juice (or c) if they both didn’t have berries and also drank the juice).

You need this to be True only if the player didn’t partake in any food or drink at all. So, perhaps you can add another not to this:

# Incorrect 2
if not ate_berries or not drank_juice:

What’s wrong with this statement? Well, the two operands are split up with an or operator, which means that the actual evaluation order looks like:

# Incorrect 2b
if (not ate_berries) or (not drank_juice):

So this statement is True if either a) the player did NOT eat berries or b) the player did NOT drink juice. That seems all right, until you realize that it’s True if either of these conditions are True – you only want this condition to be True if the player didn’t eat or drink any food at all. This statement will be True if the player just drank juice but didn’t eat berries, and it will also be True if the player ate berries but didn’t drink the juice.

So where did this go wrong?

Well, if you negate an or, it actually turns into an and. To see how, let’s reword the earlier example.

If the player didn’t eat the berries AND they didn’t drink the juice, they won’t be drowsy from the effects of the magic.

In code, that would look like:

# Correct 1
if not ate_berries and not drank_juice:

Now you’re checking that the player did NOT eat berries and did NOT drink juice. With parentheses for clarity, that looks like:

# Correct 1b
if (not ate_berries) and (not drank_juice):

So how can you shorten it? What did I mean when I said “negating or turns it into and“?

Again, let’s go back to the thing we’re trying to do: if the player didn’t eat berries or drink juice, they won’t be drowsy from the magic.

In English, we can see that the “didn’t” applies to both the actions “eat berries” and “drink juice”. In code, because not has higher operational priority than and and or, you need to specify how you want it to apply to your expression with parentheses.

# Correct 2
if not (ate_berries or drank_juice):

This is almost the same as Incorrect 1, but note the parentheses – it changes how this expression is evaluated. The game evaluates the expression inside the parentheses first, because it has the highest priority.

So, ate_berries or drank_juice. This expression is True if a) the player ate berries and drank juice, b) the player just ate berries but didn’t drink juice, and c) the player didn’t eat berries but did drink juice. It is False if the player did not eat berries and did not drink juice.

Armed with that knowledge, we can see that after the expression in parentheses is evaluated, we end up with either if not (True) or if not (False).

if not (False), as mentioned, evaluates to True because if something isn’t False, then it must be True (and vice versa). And in which case did the expression in parentheses end up being False? Oh yes – in the case where the player did not eat berries or drink juice. Perfect! So this entire expression is only True in the case where the player didn’t have any of the food or drink. If they have one or the other, the expression in parentheses is True, and then if not (True) evaluates to False, since they did have some food and/or drink.

This is something to be mindful of as you write your conditional statements. Note also that it would be perfectly valid to do something like:

if ate_berries or drank_juice:
    "You felt oddly drowsy."
else:
    "You prodded at Ashwin, but they seemed unresponsive."
    "In fact, they appeared to be fast asleep."

Sometimes if you’re having trouble with a particular condition, it can be good to step back and see if you can reword it to make it easier to write!

Bonus example

To get an idea of how the not/and/or relationship works in the other direction, consider the following:

if not washed_dishes or not swept_floor:
    "Your stepmother cast a critical eye over the kitchen with a sneer."
    "Stepmother" "It seems you didn't care to clean up before inviting me in."

Here, if the player didn’t wash the dishes or sweep the floor, their stepmother will be unhappy with the mess. If you wanted to take the not outside of this so it applies to both operands, it would become:

if not (washed_dishes and swept_floor):
    "Your stepmother cast a critical eye over the kitchen with a sneer."
    "Stepmother" "It seems you didn't care to clean up before inviting me in."

In both cases, both washed_dishes and swept_floor must be True in order for the stepmother to not comment on the mess.

You can think of this kind of like how in math you might have something like -4 - 6 = -10

If you then want to factor the - outside of the left side, you would get -(4 + 6) = -10. Note how the - in - 6 turned into a +? That’s the same kind of relationship and and or have when you take the not out and apply it to the whole statement in parentheses.

Summary

Order of operations is particularly important to keep in mind as you make more complex conditional statements. If you’re ever unsure of the execution order (or just want to make the execution order more obvious), you can use parentheses, which have the highest operational order.

Keep readability in mind as you write your conditional statements. You can enclose the entire expression of a conditional statement inside parentheses so that you can split it up onto multiple lines.

Next Steps

As per usual, there is a quiz to test your knowledge on conditional statements, variables, and all the various comparison tools you’ve picked up: Combining Comparisons Quiz. If you’re feeling confident with conditionals and comparisons, you can also look at Conditionals and Choice Menus in Ren’Py to learn how to use them in choice menus.

Leave a Reply