2.5 The If Command¶
This section covers the video “Alternative/Dynamic Actions: The If Command” by mcmonkey.
Table of Contents
- Before We Start
- 1. A First Look at the If Command
- 2. How to Write an If/Else Block
- 3. The Middle Ground: Else If
- 4. Variations on the If/Else If/Else Chain
- 5. Complex If/Else If Conditions
The video uses the old, deprecated braced syntax. The modern syntax uses colons and proper indentation. We will still use the example found in the video, but corrected to reflect the new modern syntax.
At the time the video was made, grouping of logical expressions was not available in Denizen. Currently, grouping is available to use. That is covered by this guide, in the third part of Part 5: Complex If/Else If Conditions.
If you’ve seen, written, or studied code at all, one of the first things you learned was the
if statement. If you
haven’t, the question you should have now is, “What if I want this script to run conditionally? What if I want my script
to be dynamic, and not do the same thing over and over again?”
To anyone starting Denizen, this is the opportunity of the lifetime. A surefire fan favorite, the if command will help us accomplish what has already been accomplished in countless programs.
The if command allows our script to react in dynamic ways. Essentially, it will run its associated script block only if certain conditions are met. If the conditions are not met, then the associated script block won’t run. Simple, isn’t it?
If you’ve coded before, you can guess that there is another sort of command that should come with the if command. If you haven’t coded before, then you’re in for a nice treat.
This other command is the else command. Any script block associated with the else command will run if and only if every condition in its associated if command fails.
Wait. Not only are we using the word “if” way too much for four short paragraphs, we’re talking about making the if and else commands run certain blocks of script. How can we assign lines of script to these two commands? How can Denizen know which blocks of script to run and not to run? Answering these questions will require an example.
To take a break from task scripts, we’ll use a new script type: the world script. Don’t fret! We’ll cover that in the Section 2.7.
Consider the following world script:
1 2 3 4 5 6 7 8 9 10
script2: type: world events: on player breaks sand: - if true: - narrate pass - else: - narrate fail - narrate "a b c d e f g" - determine cancelled
Unlike our previous examples, this script can be tested without using
/ex run SCRIPT_CONTAINER_NAME. This script
will automatically run whenever you break a sand block. And don’t worry about running out of sand blocks. The
- determine cancelled at the end will prevent you from actually breaking the sand block. So you can go ham on that
block, but stay with us.
When you break a sand block, you should expect to see something like this:
There’s something missing, isn’t there? You should notice that
- narrate fail never ran. Why is that? We
specifically wrote that in there, so why did it do nothing? Did I just trick you into a useless switch scenario? … Yes
I did, but let me explain.
Take a look closer at our script in Figure 2.5.1. The if command has the argument
true”. This is a boolean value. Every if command’s arguments must eventually resolve into a boolean
value, which can be either true or false. If the conditions in the if command eventually resolve to
true, then anything in the script block associated with that if command runs. Otherwise, that script
block doesn’t run, and Denizen moves on to the next unindented command.
In Figure 2.5.1, our if command is given the boolean value
true, so it runs its
script block. Then Denizen reaches the else command. Remember what we said about the else
command and when it runs. Did the if command’s arguments resolve to
No. Since every condition did not fail, the else command’s script block cannot run.
If you feel so inclined, you can replace the
true with a
false and re-run the script. Then, you will see that
the if command’s script block does not run while the else command’s script block runs.
Of course, the unindented narrate and determine commands are not associated with either the if or else commands, so they run regardless of the result of either the if or else command.
As you were looking back at Figure 2.5.1, you should have noticed three things.
- Indentation is used to indicate which lines of script are associated with the if and else commands.
- The else command is placed directly underneath the if command’s script block.
- The if command takes an argument, while the else command does not.
The first point is self-explanatory. As mentioned in Section 1.3, indentation is primarily used as a way to associate things with each other. Extra indentation in a script block causes those lines to become associated with the command immediately above the indented block. Of course, it doesn’t make sense that this works with every command. You’ll learn about more commands that use indented script blocks later.
The second point is a little more subtle. The else command relies on the idea that for it to run, there has to be an if condition that fails first. There cannot be any extra unindented lines of script in between the else and if, as that will cause the else command to have no if command to depend upon. To make this a little more clear, let’s look at the following script snippet:
1 2 3 4 5 6
- if false: - narrate pass - announce "You thought there would be an else here, but it was me!" - else: - narrate fail - narrate "a b c d e f g"
When the else command is run in the above example, it looks for the very first command above itself that has the same indentation. That very first command would be the announce command. So where’s the thing that lets the else command say “A condition failed, so we’ll run this other bit of script”? According to Denizen, nowhere. So it throws an error.
As a human, you would point to the if command above that announce command and say, “Isn’t that the if command you’re looking for?”. Denizen doesn’t see that. All it sees is an else command without an if command. So be careful, and make sure that you don’t have any commands that break up an if/else chain.
The third point is also self-explanatory. As we have mentioned twice now, the else command runs its script block only if every condition in its associated if command fails. Why does it need an argument? It’ll run when everything else in its if command fails.
But what if we don’t want it to do that? What if we want an additional condition after the if command? What if we want a more complex chain of script blocks that run based on a variety of conditions? Putting if/else commands inside of other if/else commands seems like a pain. So… let’s put the two commands together!
If you thought we were going to introduce another command, you’re wrong. We’re going to reuse the else command and transform it into its middle ground, an else if. The else if does rely on an if command, but it has a unique function. It will run when the if command’s conditions fails, but has its own conditions to check. Only after every if and else if fail will the else command run.
We’re going to modify Figure 2.5.1 a bit. Consider this edit:
1 2 3 4 5 6 7 8 9 10 11 12
script2: type: world events: on player breaks sand: - if false: - narrate pass - else if true: - narrate wee - else: - narrate fail - narrate "a b c d e f g" - determine cancelled
We know that the if command fails, since its condition resolves to
false. The next command read is the
else if command. Notice how it also takes a boolean argument, just like a standard if command.
In this case, the else if command’s condition resolves to
true, so it runs its script block.
And finally, our last else command. We know that the else command’s script block only runs if all of its associated if command’s conditions fail. So how does this tie in with the else if?
Quite simply, the else command’s script block won’t run. Because of the introduction of the else
if command, all of the if and else if commands’ conditions must fail before the
else command’s script block can run. If any of the if or else if commands’
conditions resolve to
true, the else command won’t run its script block.
There are many ways to write an if/else if/else chain. You can have as many else if commands as you want, from zero to a few hundred thousand (but you should avoid having that many else if commands). You can have an if/else if chain without an else command. You can just have an if command all by itself!
The following figures demonstrate this well.
1 2 3 4 5
no_else_or_else_ifs: type: task script: - if true: - narrate pass
1 2 3 4 5 6 7
no_else_ifs: type: task script: - if true: - narrate pass - else: - narrate fail
1 2 3 4 5 6 7
no_else: type: task script: - if false: - narrate pass - else if true: - narrate wee
1 2 3 4 5 6 7 8 9 10 11 12 13
many_else_ifs_no_else: type: task script: - if false: - narrate pass - else if false: - narrate wee - else if false: - narrate oopsies - else if true: - narrate *crash* - else if false: - narrate ouchies
1 2 3 4 5 6 7 8 9 10 11
many_else_ifs: type: task script: - if false: - narrate pass - else if false: - narrate wee - else if false: - narrate oopsies - else: - narrate fail
Note that in all of the examples, each if/else if/else chain only ever has a maximum of one if and one else command.
When all’s said and done, we still haven’t really covered something important. I said that the arguments of the
if and else if commands must eventually resolve into a boolean value of either
false. We’ve only been explicitly writing out “
true” and “
false” so far. Can this possibly get more complex?
But of course it can!
In general, there are two ways to write a condition for the if command. It can either be one value that
false, or a value being compared to another.
Consider this modification on Figure 2.5.1:
1 2 3 4 5 6 7 8 9 10
script2: type: world events: on player breaks sand: - if <util.random.int.to> == 3: - narrate pass - else: - narrate fail - narrate "a b c d e f g" - determine cancelled
If you recall from Section 2.1, the
<util.random.int.to> tag returns a random integer from 1 to 5. So every time you break a sand block, a number
from 1 to 5 is chosen.
<util.random.int.to> == 3 part is a logical expression. Logical expressions eventually resolve
to a boolean value, depending on the operator used. In this case, the logical expression
== 3 directly compares the result of
3. If the comparison succeeds, the
expression will evaluate to
true. Otherwise, it evaluates to
Not all logical expressions are comparisons! Sometimes, it can be a single value (such as “
true”) or a single
== symbol is a type of comparison operator. This specific operator compares two values and sees if they
exactly match each other. In this case, the if command is seeing if the randomly chosen number exactly
Below is a quick table displaying the different types of comparison operators. Additional information can be found in the Glossary.
Checks to see if two values are completely equal to each other.
Checks to see if two values are not equal to each other.
Checks to see if one value is less than the other.
Checks to see if one value is less than or equal to the other.
Checks to see if one value is greater than the other.
Checks to see if one value is greater than or equal to the other.
Checks to see if the given value is of a particular type.
Available types can be found here.
Cool beans. Now we have an impressive arsenal of comparisons at our disposal. But… what if we want to do multiple comparisons at once? Well, you’re in luck. That can be accomplished using logical operators!
Consider this modification on Figure 2.5.10:
1 2 3 4 5 6 7 8 9 10
script2: type: world events: on player breaks sand: - if 3 == 3 && 4 == 4: - narrate pass - else: - narrate fail - narrate "a b c d e f g" - determine cancelled
We have two comparisons here. We are seeing if
3 is exactly equal to
3, and if
4 is exactly equal to
So what is the
&& doing? Well, go back to the second sentence of this paragraph and look at the word after the
comma. Spoilers, I’m talking about the word “and”.
The logical operator
&& combines the result of the expressions
3 == 3 and
4 == 4. If both expressions
true, then the entire logical expression
3 == 3 && 4 == 4 evaluates to true. However, if either or
both of them evaluate to
false, then the entire logical expression evaluates to
One of the three logical operators does not combine two or more logical expressions.
! logical operator inverts the result of a boolean value. So if our theoretical tag
Below is a quick table displaying the different types of logical operators. Additional information can be found in the Glossary.
Checks if every conditional expression evaluates to
If even one condition evaluates to
Checks if at least one conditional expression evaluates to
If all conditions evaluate to
Takes a boolean value and returns the opposite boolean value.
You are probably tired of me saying “What if”, but what if we wanted to use
|| at the same time?
We absolutely can! But with what we know right now, that isn’t possible. Consider the following script snippet:
- if true && true || false: - narrate "Well what am I supposed to do now?"
What is Denizen supposed to do? We have two logical operators that act in completely opposite ways. We might say, “Just go left to right!” But Denizen doesn’t do that. It will attempt to evaluate the entire expression. So, what can we do?
If we can combine multiple logical expressions into one using logical operators, would it not make sense if we could separate the entire logical expression into multiple parts? That’s what grouping does. It separates the expression into parts that are evaluated first.
We’re going to have our way with Figure 2.5.1 just one more time. Consider this edit:
1 2 3 4 5 6 7 8 9 10
grouping_example: type: world events: on player breaks sand: - if true && ( true || false ): - narrate pass - else: - narrate fail - narrate "a b c d e f g" - determine cancelled
When Denizen parses this script, it will see that the logical expression
true || false should be evaluated first.
The following is an approximate depiction of how grouping works:
- Given the expression
true && ( true || false ), Denizen sees that there is a group
( true || false ). It will evaluate the expression in that group first.
- The expression
true || falseevaluates to
true(see Combining Two or More Expressions for more information).
- Denizen replaces the group
( true || false )with the result of its encapsulated expression. Therefore,
( true || false )is replaced with
- Denizen looks at the whole expression again.
true && ( true || false )is equivalent to
true && true. This new expression evaluates to
So now you know everything you need to know about the if and else commands. You know about how to write them, what logical expressions are, what operators you can use, and what grouping is. With this, you have the tools to create a dynamic bit of script that can react differently depending on the situation. You can make your script as complex or simple as you want!
We’re ready to brave a new (newer?) frontier!