8. Making a Proper Mood Light
The fade-up part of the
mood light is very good, but you don’t want it to suddenly change from
white to black each time around. What you would like is for it to fade
smoothly up and down. If you were telling Mrs. Update what to do, you
would say something like this:
"Make the value of redIntensity
bigger each time that you are called. When the value reaches 255, start
making it smaller each time you are called until it reaches 0, at which
point you should start making it bigger again. Do the same with blue
and green."
Mrs. Update would think
about this for a while and decide that she needs to keep track of two
things for each color: the current intensity value (in the range 0 to
255) and something that lets her remember whether she is counting up or
counting down for that color. Then, each time she is called, she can
follow a sequence like this:
If we are counting up, increase the value of redIntensity.
If we are counting down, decrease the value of redIntensity.
If redIntensity is 255, change to counting down.
If redIntensity is 0, change to counting up.
This is an algorithm. It provides a sequence of operations that is used to solve a problem. In this case, we wanted to make the value of redIntensity move up to 255 and down again in steps of 1.
Of course, Mrs. Update is
not a person but a C# method, so now we have to convert these steps into
C#. The first thing that we need to do is work out what data we need to
store. We
need the intensity value and also a way of remembering if we are
counting up or down. Here’s the code that declares the needed variables:
// The Game World - our color values
byte redIntensity = 0;
bool redCountingUp = true;
You have seen the redIntensity variable before; what you haven’t seen is the way that we can set it to 0 when we declare it. The redCountingUp variable is new, though. It is of a new type (C# has hundreds of different types, you’ll be pleased to hear). This is the bool type, which is special because it can hold only two possible values: true or false. It allows programs to perform what is called Boolean algebra, which consists of calculations involving only the values true and false. Such calculations are usually used to drive decisions along the lines of "If itIsRaining is true and robWillBeGoingOutside is true, I should call the takeMyUmberella method."
In this case, the bool type is perfect because redCountingUp is either true or false and nothing else. The program uses it to make decisions in the Update
method so that it can behave according to the data. This ability to
make decisions is what makes computers truly useful, in that they can
change what they do in response to their situation. To make decisions in
your programs, you have to use conditional statements.
9. Making Decisions in Your Program
You have seen two kinds of statement so far. One calls a method to do something (you use this to call the Clear
method), and the other changes the value of a variable (you use this to
increase the intensity of your colors). Now you are going to use a
conditional construction that can change what the program does depending
on the particular situation.
9.1. Creating Conditional Statements
Figure 5 shows how a conditional construction fits together. Conditional constructions start with the word if.
This is followed by a condition in brackets. The condition produces a
Boolean result, which can be either true or false. You can use a
variable of bool type directly here.
If the condition is true (that is, the variable redCountingUp holds the value true
in this case), the statement following the condition is performed. The
result is that when this statement is obeyed, the value of redIntensity
gets bigger if the program is counting up. The condition can be any
value that gives a Boolean result, including this rather stupid code:
if (true) redIntensity++;
The preceding
code is completely legal C# code and compiles with no problem. When the
program runs, the condition is true, and the statement increases the red
intensity value. This is very stupid code, though, as the test might as
well not be there. You could also write the following:
if (false) redIntensity++;
In this code, the
statement following the condition is never obeyed because the condition
is always false. This C# code compiles all right, but if you look very
closely at the Microsoft Visual Studio display, you might notice that it
is trying to tell you something, as shown in Figure 6.
If the error window in Figure 2-10
is not displayed, you can open it by selecting the View menu and
clicking Error List in that menu. Alternatively you can use the key
combination Ctrl+W+E.
When the compiler
has finished trying to convert your C# source code into a program that
can be run on the computer, it tells you how many mistakes that it
thinks it has found. There are two kinds of mistakes. An error
is a mistake that prevents what you have written from being made into a
program. Errors are really bad things like spelling identifiers wrong,
using the wrong kind of brackets, and the like.
The other kind of mistake is called a warning. This is where the compiler thinks you might have done something wrong, but it does not prevent your program from running. Figure 2-10 shows the warning message for a program with a test for (false) in it.
What the compiler is
telling you is that it has managed to work out that the statement after
the test will never be reached. This is because it is impossible for the
value false to be true. The compiler
is warning you that although the code is legal C# code, what it does
might actually not be what you want.
Note:
Our
Great Programmer has very strong opinions on compiler warnings; she
reckons that your code should compile with no warnings at all. Warnings
usually mean that your solution is imperfect in some way, and you should
always take steps to investigate and resolve them.
9.2. Adding an Else Part
The condition you have created is only half correct. If the program is not counting up, it must make the value of redIntensity smaller. You can use the -- operator to do this, but we need to add extra code to the condition. You need to add an else part. Figure 7 shows another form of the if condition, with the else part added.
The two statements are separated by a new key word, else. The new code means that if the program is counting up (that is, redCountingUp is true), the value gets bigger, but if the program is counting down (that is, redCountingUp is false), the value gets smaller. The else part is optional; you must add one only if you need it.
9.3. Testing Values
The program must also manage the value in redCountingUp
so that when it reaches the upper limit, it starts to count down, and
when it reaches the lower limit, it starts to count up again. In other
words:
When redIntensity reaches 255, set redCountingUp to false.
When redIntensity reaches 0, set redCountingUp to true.
To do this, you need another kind of condition, one that performs a comparison. Figure 8 shows how such comparisons are created. This performs the first of these two tests.
The key to understanding what is happening is the == comparison operator. When the program evaluates this condition, the values on the left and right of the ==
operator are compared. If they are the same, the result of the
comparison is true, and the statement that follows the condition is
performed. If they are different, the result of the comparison is false,
and the statement that follows the comparison is ignored.
The sequence == is the comparison operator. It is completely different from the =
operator, which we know as the "gozzinta." It is important that you
don’t get these two confused. Unfortunately, you have both a gozzinta
and a comparison taking place in the if statement because you want to put a new value into redCountingUp if the comparison succeeds.
Fortunately, the
compiler can usually detect when you use the wrong operator and produce a
message. There are other comparison operators that can test to see if
one value is greater or less than another; we will use these later. An if statement that uses a comparison operator can have an else
part; it is just that we don’t need one here. The final code to make
our red intensity value move up and down ends up as follows:
if (redIntensity == 255) redCountingUp = false;
if (redIntensity == 0) redCountingUp = true;
if (redCountingUp) redIntensity++; else redIntensity--;
The program needs a
second test to change the direction of the counting when the bottom
limit of the intensity value is reached. The tests are performed before
the intensity value is updated. This is because when the program starts
running we want it to work correctly for any initial value of redIntensity. If the starting value is 255 the program must count down. If the starting value is 0 the program must count up.
Note:
Pay very careful
attention to the three statements shown in this section. Go back and
read our original instructions to Mrs. Update and make sure you are
absolutely clear how these have been converted into C# statements that
perform the job. You will notice that Mrs. Update’s original design has
had to be changed so that it works with any starting value.
10. The Completed Mood Light
You now have the code that lets you create a smoothly pulsing mood light:
// The Game World - our color values
byte redIntensity = 0;
bool redCountingUp = true;
byte greenIntensity = 0;
bool greenCountingUp = true;
byte blueIntensity = 0;
bool blueCountingUp = true;
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
// Update each color in turn
if (redIntensity == 255) redCountingUp = false;
if (redIntensity == 0) redCountingUp = true;
if (redCountingUp) redIntensity++; else redIntensity--;
if (greenIntensity == 255) greenCountingUp = false;
if (greenIntensity == 0) greenCountingUp = true;
if (greenCountingUp) greenIntensity++; else greenIntensity--;
if (blueIntensity == 255) blueCountingUp = false;
if (blueIntensity == 0) blueCountingUp = true;
if (blueCountingUp) blueIntensity++; else blueIntensity--;
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
Color backgroundColor;
backgroundColor =
new Color(redIntensity, greenIntensity, blueIntensity);
graphics.GraphicsDevice.Clear(backgroundColor);
base.Draw(gameTime);
}
These versions of Update and Draw produce a program that smoothly fades the screen between black and white.
10.1. A Proper Funky Mood Light
Going from black to
white and back is all very well, but it would be nice to have some
additional variety to our light. It turns out that this is very easy to
achieve. At the moment, the red, green, and blue intensities are all the
same values, counting up from 0 to 255 and back down again. This just
gives shades of gray. What you want is different combinations and the
color intensities going up and down at different times. You can do this
by changing the starting values of our intensity values and update
directions:
byte redIntensity = 0;
bool redCountingUp = true;
byte greenIntensity = 80;
bool greenCountingUp = false;
byte blueIntensity = 160;
bool blueCountingUp = true;
Rather
than all the colors starting at 0 and counting up, the green value now
starts at 80 and counts down, and the blue value starts at 160. This
means that instead of just different shades of gray, you now have lots
of other colors being presented. This provides a very groovy display. If
you change the values in your program to the ones shown in this
section, you can get a much more interesting-looking display. You can
even try values of your own and see what they look like.
For a much longer-lasting
display, we need to change the rate at which the three colors are
updated. This is not actually very hard to do, so I’ve written an
"Ultimate Mood Light" that you can take a look at.
11. Finding Program Bugs
Your younger brother
has been reading this book and typing in the programs on his computer.
He has just come and told you that the book is rubbish because the
programs don’t work. He has written an Update method and is complaining that for him the red value only gets brighter. You ask him to show you the code and you see this:
if (redIntensity == 255) redCountingUp = true;
if (redIntensity == 0) redCountingUp = true;
if (redCountingUp) redIntensity++; else redIntensity++;
At first glance, it
looks fine, and the C# compiler is quite happy that it is legal, but it
is obviously not working. There is a bug in the program. Note that the
bug is not there because the computer has made a mistake, so the
instructions themselves must be faulty. You don’t want to bother the
Great Programmer, as she seems to be busy playing Halo on her Xbox, so
you take a look, bearing in mind something she said recently.
Note:
A good way to find out
what a program is doing is to behave like the computer and "run" the
program yourself. By working through the statements by hand, keeping
track of the variables and making the changes to them that the program
does, you can often find out what is wrong.
Your younger brother
has actually made two mistakes in copying the program from these pages.
See if you can find them by working through the statements.
Figure 9 highlights the errors that your younger brother has made.
The two errors both have
the same effect, they cause the screen to get brighter all the time. If
you fixed only one of them the program would still appear broken.