9 min read

Decision Making

The Crash Course

Introduction

In the Hello World tutorial, we talked about how the flow of execution generally works from top to bottom, one statement at a time. In the next few tutorials, we’ll see some tools to allow more than just executing statements one after the next.

In this tutorial, we’ll look at ways to skip certain statements, based on the state of variables in our code, allowing us to make decisions and follow alternate paths.

The if statement

Imagine you are making a game where, depending on how many points the player gets in a level, they earn stars.

Star Ratings in a game with levels

Let’s say we’ve been tracking the score as the game progressed, and now it is time to decide how many stars to give for any given point total.

Let’s suppose that we give out three stars (the most possible) only if they score a perfect 2000, two stars if they score more than 1500, one star is they score more than 1000, and no stars if they score less than that.

It is easy to imagine the basic setup. We’ll need a variable to store the number of points (and we’ll ignore how this gets decided right now). We’ll also need a variable to store the number of stars they’ve earned.

int points = 1560;
int stars = 0; // We'll initialize to zero for now, and change it later.

But how do we build code that will determine the right number of stars?

The if statement is our primary tool for decision making in C#. This is easier to show than describe, so here is a simple example of an if statement:

if (points == 2000)
    stars = 3;

You could read this aloud as “If points equals 2000, then stars is assigned a value of 3.”

Each if statement starts with theif keyword, followed by a set of parentheses that contains some sort of expression whose type is bool. The == operator is but one of many types of operators that is used in an if statement, but it is an important one. It evaluates to true if the thing on each side are equal to each other, and it evaluates to false if they are not equal.

If the condition is true, the statement following the if statement will run. Otherwise it will not.

The indentation above (bringing the statement guarded by the if inward with a tab or several spaces) makes it clear that this stars = 3; statement only runs sometimes.

Whitespace does not matter in C#, so a second style that is common for if statements is this:

if (points == 2000) stars = 3;

The following is legal, but I’d strongly discourage it:

if (points == 2000)
stars = 3;

With that code, it is easy to accidentally assume that stars = 30; runs always, when it does not.

Block Statements

So what happens when you need to guard multiple statements with an if? Do you just do this?

if (points == 2000) stars = 3;
if (points == 2000) Console.WriteLine("A perfect score!");

That technically works, but isn’t very clean or easy to maintain. There is a better way.

C# has a concept called a block statement . A block statement is a way to bundle many statements together, and use them anywhere a single statement is allowed. You form a block by using a set of curly braces ({ and }):

{
    stars = 3;
    Console.WriteLine("A perfect score!");
}

Block statements like this are very often combined with if statements, and are the most convenient way to protect many statements inside an if:

if (points == 2000)
{
    stars = 3;
    Console.WriteLine("A perfect score!");
}

In fact, some people prefer just always using a block statement with an if. They’ll use the curly braces even if there is only one statement. While it is not necessary, it does have the effect of reducing the chances of accidentally thinking a statement is protected by the if when it isn’t:

if (points == 2000)
    stars = 3;
    Console.WriteLine("A perfect score!");

In the code above, only the stars = 30; statement is ran conditionally. The Console.WriteLine will happen every time. By using the curly braces, you reduce the chances of accidentally misinterpreting something as being part of an if statement when it actually isn’t.

else Statements

The else statement is if’s counterpart. An else statement is used in conjunction with an if to say, “Well if the condition isn’t right for the if, then do this other stuff instead.” For example:

if (points == 2000)
    Console.WriteLine("A perfect score!");
else
    Console.WriteLine("Good work, but you still have room for improvement!");

An else statement, can also use a block:

if (points == 2000)
{
    stars = 3;
    Console.Writeline("A perfect score!");
}
else
{
    stars = 0;
    Console.WriteLine("Good work, but you still have room for improvement!");
}

Chaining if/else Statements

Another common tactic is to chain many if and else statements together. This is useful when you have several buckets that things can land in, rather than just one or two.

Let’s assume, for a second, that we’ve already figured out how to determine if the player gets 0, 1, 2, or 3 stars (we’re still coming to that), and want to just display some text in response:

if (stars == 0)
    Console.WriteLine("Try again!");
else if (stars == 1)
    Console.WriteLine("Nice!");
else if (stars == 2)
    Console.WriteLine("Fantastic!");
else
    Console.WriteLine("Perfection!");

There is no limit to how many you can string together. And, of course, you can use a block statement in any or all of the above if you want or have the need.

Relational Operators: ==, !=, <, >, <=, and >=

While we’ve seen that == is used to check if two things are exactly equal, there are other similar operators that we should know.

The != operator is the opposite of ==, and tells you if two things are not equal to each other.

if (score != 0)
    Console.WriteLine("It could have been worse!");

The < operator is the “less than” operator, and tells you if the thing on the left is less than the thing on the right. Similarly, the > operator is the “greater than” operator, and tells you if the thing on the left is greater than the thing on the right.

note

While == and != work on most types, including string, the < and > operators are more limited, and only work on numbers and things that have a distinct ordering.

if (score > 1500)
    stars = 2;

This will store a value of 2 into stars, but only if the score is more than 1500.

But wait! Our original description said, “You get two stars if you score at least 1500.” In this case, 1500 actually wouldn’t get two stars. We could change the if statement’s condition to say if (score > 1499), there’s a better way.

The <= operator checks if the thing on the left is less than or equal to the thing on the right, while >= checks if the thing on the left is greater than or equal to the thing on the right. So a better check would be:

if (score >= 1500)
    stars = 2;

At this point, we have enough tools in our arsenal to actually write out a solution to our problem of awarding stars based on points:

int points = 1560;
int stars;

if (points == 2000)
    stars = 3;
else if (points >= 1500)
    stars = 2;
else if (points >= 1000)
    stars = 1;
else
    stars = 0;

Remember, the order matters here. The way this code is structured, we’ll only land in one of these categories. (If we just made separate if statements without elses, then we could land in multiple categories.) If we rearrange the order, we’ll get different (and incorrect, in this case) results.

The bool Type

The bool type is especially important in decision making. As we have seen, the condition of an if statement must be an expression of the type bool.

There are two notable things that come out of that.

The first is that we could use a bool variable directly in an if statement:

bool hasBeatenLevel = true;

if (hasBeatenLevel)
{
    Console.WriteLine("You beat the level!");
    currentLevel++;
}

Second (and related), these expressions whose type is bool can be stored in a bool variable whenever that is needed or desirable:

bool hasBeatenLevel = score >= requiredPoints;
if (hasBeatenLevel)
{
    // ...
}

This can help break apart complicated expressions into smaller parts.

Conditional Operators

There are a few other operators that make it easy to build complicated Boolean expressions.

First, the ! operator (the not operator) is used to invert any other Boolean expression. What was true becomes false. What was false becomes true.

bool hasBeatenLevel = score >= requiredPoints;
bool hasFailedLevel = !hasBeatenLevel;

The && operator (the and operator) and the || operator (the or operator) are used to combine two other Boolean expressions. && evaluates to true if both sides are true (and false if both or either are false). || evaluates to true only if either side is true (including if both sides are true).

if (killCount >= 10 && score >= 1000)
    Console.WriteLine("You killed at least 10 enemies and scored at least 1000 points, so you beat the level!");

if (killCount < 10 || score < 1000)
    Console.WriteLine("Either you didn't kill enough enemies or didn't score enough points (or both) to make it past this level.");

These operators are called conditional operators and can be combined in as long of chains as you need.

Nesting

An if statement can contain any statement it wants, and that includes another if statement:

if (killCount >= 10)
{
    if (score >= 1000) Console.WriteLine("You killed enough enemies and scored enough points to advance!");
    else Console.WriteLine("You killed enough enemies, but did not score enough points to advance.");
}

When you put something inside another of the same type, it is called nesting . You could say that the above code has a nested if statement.

You can nest if statements as deeply as you have a need for, but keep in mind that deep nesting can make code hard to understand.