Booleans vs Strings

Booleans vs Strings

This is an addendum to the previous tutorials on simple variables and equality comparisons. It’s very common for people to use booleans instead of strings in their script when strings are a better choice to reduce errors while still remembering multiple options.

Difficulty Level: Beginner

As this tutorial builds off of the prior tutorials Simple Variable Types in Ren’Py and Equality Comparisons in Ren’Py, you should read those first before this tutorial.

Rock-Paper-Scissors

Consider the following situation:

During your game, a round of rock-paper-scissors is played which determines who gets to eat the last cookie. The player gets to choose which of rock, paper, or scissors they would like to play, and if they win, they earn the cookie. You’re keeping track of the results like so:

default npc_rock = False
default npc_paper = False
default npc_scissors = False
default player_wins = False
label start:
    # Round 1
    # You could do some logic here, like using
    # renpy.random to randomly select which of
    # rock/paper/scissors the non-player character
    # (NPC) should use, but here it's set directly.

    $ npc_paper = True

label rock_paper_scissors:
    # Show which move the NPC chose
    show paper_img
    menu:
        "Rock":
            if npc_rock:
                "It's a tie! Go again."
                jump rock_paper_scissors
            if npc_paper:
                $ player_wins = False
            if npc_scissors:
                $ player_wins = True
        "Paper":
            if npc_rock:
                $ player_wins = True
            if npc_paper:
                "It's a tie! Go again."
                jump rock_paper_scissors
            if npc_scissors:
                $ player_wins = False
        "Scissors":
            if npc_rock:
                $ player_wins = False
            if npc_paper:
                $ player_wins = True
            if npc_scissors:
                "It's a tie! Go again."
                jump rock_paper_scissors

    if player_wins:
        "You won!"
        jump later_on
    else:
        "You lost!"
        jump no_cookie_for_you

There’s a lot happening here, but most of it is just repeat code for all the options. For example, let’s look at the code under the “Rock” choice in the menu:

"Rock":
    if npc_rock:
        "It's a tie! Go again."
        jump rock_paper_scissors
    if npc_paper:
        $ player_wins = False
    if npc_scissors
        $ player_wins = True

First, the game checks if npc_rock aka did the NPC play “Rock”? If so, and the player chose rock, then it’s a tie and the game will jump to the rock_paper_scissors label so the player can try again, no penalty.

Next, the game checks if npc_paper (did the NPC play “Paper”?). If they did, and the player chose rock, then the player loses, so the game sets player_wins to False.

Finally, the game checks if npc_scissors (the NPC played scissors). The player played rock, so they win. The game sets player_wins to True and continues on.

After the player has played the game, the game continues as usual, either jumping to the label later_on if the player won, or the label no_cookie_for_you if the player lost.

All seems pretty normal so far. Later in your script, the characters decide to play another game of rock-paper-scissors, this time for the last piece of pizza. You decide to use the same format for this new round:

label later_on:
    # Set up the NPC's move
    $ npc_rock = True

label rock_paper_scissors2:
    show rock_img
    # This menu is the same as the original
    menu:
        "Rock":
            if npc_rock:
                "It's a tie! Go again."
                jump rock_paper_scissors2
            if npc_paper:
                $ player_wins = False
            if npc_scissors:
                $ player_wins = True
        "Paper":
            if npc_rock:
                $ player_wins = True
            if npc_paper:
                "It's a tie! Go again."
                jump rock_paper_scissors2
            if npc_scissors:
                $ player_wins = False
        "Scissors":
            if npc_rock:
                $ player_wins = False
            if npc_paper:
                $ player_wins = True
            if npc_scissors:
                "It's a tie! Go again."
                jump rock_paper_scissors2

    if player_wins:
        "You won!"
        jump eat_the_pizza
    else:
        "You lost!"
        jump no_pizza_for_you

Can you spot what the issue is here?

You forgot to reset the NPC’s original choice! So by the time the game gets to the rock_paper_scissors2 label, both npc_paper and npc_rock are True!

What does that even mean? You can’t play both rock and paper at the same time. Now let’s look at what happens the second time when the player chooses Scissors:

"Scissors":
    if npc_rock:
        # This is True!
        $ player_wins = False
    if npc_paper:
        # This is also True!
        $ player_wins = True
    if npc_scissors:
        "It's a tie! Go again."
        jump rock_paper_scissors2

Since both npc_rock and npc_paper are Trueplayer_wins is first set to False, and then set to True! Even though the NPC was supposed to play rock and you showed rock_img to the player, the player now inexplicably wins the round even though they played scissors when the NPC played rock (and rock beats scissors).

One way to try to fix this is to make sure you reset each of the rock/paper/scissors variables before starting a new game e.g.

label later_on:
    # Set up the NPC's move
    $ npc_rock = True
    $ npc_paper = False
    $ npc_scissors = False

But this still leaves you vulnerable to accidental configurations like more than one value being True or all three being False if you’re not careful, and it’s already a lot of extra typing even though you only have three options. This is where strings come in!

Here’s a rewrite of the rock-paper-scissors game, using strings:

default npc_move = "rock" # New!
default player_wins = False
label start:
    # Round 1
    # You could do some logic here, like using
    # renpy.random to randomly select which of
    # rock/paper/scissors the non-player character
    # (NPC) should use, but here it's set directly.

    $ npc_move = "paper" # New!

label rock_paper_scissors:
    # Show which move the NPC chose
    show paper_img
    menu:
        "Rock":
            if npc_move == "rock":
                "It's a tie! Go again."
                jump rock_paper_scissors
            elif npc_move == "paper":
                $ player_wins = False
            else:
                $ player_wins = True
        "Paper":
            if npc_move == "rock":
                $ player_wins = True
            elif npc_move == "paper":
                "It's a tie! Go again."
                jump rock_paper_scissors
            else:
                $ player_wins = False
        "Scissors":
            if npc_move == "rock":
                $ player_wins = False
            elif npc_move == "paper":
                $ player_wins = True
            else:
                "It's a tie! Go again."
                jump rock_paper_scissors

    if player_wins:
        "You won!"
        jump later_on
    else:
        "You lost!"
        jump no_cookie_for_you

Now if you set npc_move to "rock" later on for the second round, it’s impossible to have conflicts with the previous game of rock-paper-scissors, since either npc_move is equal to "rock" or it’s equal to "paper" or "scissors", and it’s impossible for it to be equal to more than one. Note that == compares two values directly to see if they’re equal/the same, so in the menu a line like if npc_move == "rock" is True if npc_move has the value "rock" (since "rock" == "rock"). if npc_move = "rock" with only one = is incorrect programming syntax and will cause an error, because only one = is used to set the value of a variable, not to compare with other values (See Equality Comparisons in Ren’Py for more on this if you haven’t read it already).

I’ve also cleaned up all the if statements to be if/elif/else, which is more efficient and makes it clearer that these options are mutually exclusive. Now we’ve got a proper system going for the rock-paper-scissors game, with no chance of accidentally letting the NPC make two moves at once.

Summary

When choosing what variable type to use, make sure you consider whether the concepts are mutually exclusive or not. If you have more than two options and they are all mutually exclusive, you should probably be using strings instead of booleans. If there is a configuration of your boolean variables which shouldn’t be possible (e.g. both went_to_park and went_to_store are True even though the player can only ever go to one of the two locations), reconsider if you can use just one boolean (e.g. either they went_to_park or didn’t, and if went_to_park is False you know they went to the store) or if a string would be more appropriate.

Next Steps

Hopefully by now you’re feeling comfortable with all the variable types introduced so far and the different ways to compare them. Next you’ll learn about numerical comparisons and combining more than one expression to make more complex conditional statements in Numerical Comparisons and Combinations in Ren’Py.

Leave a Reply