![]() | Monkeys at Keyboards: APE Antics © Michael James Heron and Pauline Belford | ||||
| Topic: Java Programming Level: 1 Version: alpha | |||||
7 - Select Your Destiny | |||||
| Previous | Table of Contents | Next |
| Forum |
| Chapter Objectives |
By the end of this chapter, the reader will be able to:
|
At the moment, we are still telling Pacman what to do all of the time - even when we're making use of for loops, we're still telling him what to do and how many times to do it - we've just found a way to reduce the amount of typing that takes. What we need now is a way to push more of the work onto Pacman so that we have more time for ourselves to be Lords and Ladies of leisure, relaxing luxuriantly on large, fluffy quilted pillows as nubile nymphs feed us peeled grapes. That's the life for us, I'm sure you'll agree! In this chapter we'll look at how to make Pacman take decisions based on the world around him - for this, we need a new family of programming structures, and these are called selection structures. The first of these we will look at is known as the if statement.
The if-statement is used to change the flow of execution through a program by making a section of the code conditional on a particular... well... condition. So, 'if the way in front of you is clear, move forward', or 'If there is a ghost in front of you, turn around'. All of these are examples of conditional statements - they will only be executed if the condition indicated is met. Much like with a for loop, an if statement has a particular syntax, and that syntax looks like this:
The condition for the if statement is phrased as something that is either true or false - if it is true, the code belonging to the if statement will be executed. If it's false, then the code belonging to the if statement will be skipped and execution of the program will continue with the first piece of code to follow the statement. Java has a primitive data-type in its library called the Boolean, and the Boolean can hold one of two values - true or false. Our if condition is expecting a Boolean value to tell it whether or not the code should be executed. At the moment we don't have an awful lot of opportunities to make use of this structure, so let's look at some of the methods available in APE to make use of this new programming tool.
APE contains a few methods designed to make effective use of selection, and these methods allow Pacman to sense what there is in front of him. There are three of these, and they operate in a fairly similar way. The first of these is isObstacleAhead. This takes no parameters, and returns a Boolean value if the square ahead of Pacman contains anything that Pacman cannot move over - so for example, walls. It won't trigger on anything outside the bounds of the map though (for example, the void that borders that map). Let's give it a try on the map ping_pong:
Let's say we wanted to make Pacman move one hundred steps between the two walls. How would we do that? Well, with our previous set of knowledge, we'd need to write a nested for loop:
So, what's wrong with that? Well, nothing especially except that we're still doing all of the work. We're telling Pacman how many times to move and when to turn. What if the distance between the walls changes? For the ping_pong map it's simple enough - the distance between the walls is ten steps. For ping_pong_revisited, the distance between the walls is seven steps - how would you change your code to make him take exactly 100 steps with that map? Consider how we could get this working using the new isObstacleAhead method - we pawn off some of the work onto Pacman, and say 'If you see an obstacle ahead, turn twice' which gets him facing the other way. That's all we have to do - tell him how to respond if there is an obstacle, and the same code will work without modification for both of the ping_pong maps:
So, for every step Pacman takes, he checks to see if there is an obstacle ahead of him. If there is, he turns left twice. Every single iteration he makes a move regardless of what happened with the if statement - only the code within the if statement is conditional on there being an obstacle ahead of him. The condition between the brackets is evaluated for truth by Java when the program is running, and in this context that means that it looks to see what is the value returned from the isObstacleAhead method. The method return true if there is an obstacle, and in that case we say that the condition has evaluated to true. The code within the braces is only executed when a condition evaluates to true. If the method returns false, then we say that the condition has evaluated to false, and the code is not executed. For this simple method call, it's just a case of comparing what the method returns to determine if the code belonging to the if statement will be executed, but we can also provide Java with things it has to evaluate for itself. To understand how these work, we need to look at a new family of Java symbols - these are called comparison operators.
There are five of these symbols that are used for the comparison of primitive data types (especially numerical data types):
These comparisons do exactly what they say on the tin - they compare one value against another. In such comparisons, the term on the left hand side is known as the left hand value (duh), and the term on the right hand side is the right hand value. Let's look at a simple example of when this might be useful - we'll start-up Pacman on a blank map and let the user tell us how many times to move him. If they choose a number that would cause us to send Pacman in the Void, then we'll give them an error message instead:
Here we are comparing one variable against a distinct value, but we could just as easily compare two distinct values, or two variables. Whenever we want to compare one primitive value against another, this is how we do it.
There is a second form of the isObstacleAhead method available - and for that one, we pass in a String indicating for which kind of obstacle we are looking. If the method returns false, then there is no obstacle of that kind ahead of Pacman. If it returns true, then the square ahead of Pacman contains the indicated contents. For the second form of the method, there is no need to restrict yourself purely to obstacles - you can check for absolutely anything ahead of Pacman. You can check to see if it's an empty square, if it contains a dot, or a pill, or a ghost - absolutely anything.
The list of valid objects in the APE Pacman universe is as follows:
You can also use the sister method 'getObstacleAhead' - this will return a string indicating what obstacle lies ahead of Pacman in the square ahead:
These methods may seem quite simplistic, but they serve as the bedrock of the functionality required to make Pacman a genuinely dynamic little creature that is capable of making decisions based on the information around him. We'll see this power evolve over the next few chapters.
Sometimes we don't want to model a piece of code that is purely conditional - sometimes we want to choose between one of two mutually exclusive options. 'If this is true, do some stuff. Otherwise, do some things'. The if statement comes in a number of different flavours, and the first of these is an if-else structure:
In this case, we do the same validation as before, but rather than compensating for invalid input we simply tell the user we won't tolerate their foolishness:
Use the if statement to provide conditional code that may or may not be executed. Use an if-else statement to provide for a pair of mutually exclusive options - one or the other will be executed. At this point, it may be helpful to start looking at how we would write this in pseudo code - sometimes it's clearer to outline how comparisons will work in English before we start to translate them into code. For the purposes of this book, we will use the following format to define an if-else structure:
So for example:
We will use this format whenever defining if-else statements in pseudo code. You may also wish to make use of it when outlining your own decision processes within a program.
Mutually exclusive options are all well and good, but what happens if we want to give a slightly more complicated way to deal with a range of options. Let's extend the pseudo code we saw in the last section to show how an if-else if works:
For example:
This would translate into the following code:
We can also add a general 'catch all' at the end in the form of a terminating else statement - if none of the conditions match, then the else will be executed.
It is important to note that only one of these conditions will ever be executed in the structure - once it finds a condition that evaluates to true, it doesn't bother checking any of the others. Only one will ever be used.
Sometimes we want to be able to base an if statement's execution on more than one condition - for example, if we want to check that a variable falls within two values. Unfortunately, Java is stupid and it cannot make sense of perfectly valid mathematical expressions such as:
Java won't know how to interpret that, and so it will give you an error. Instead, you must simplify it a little and break it up into two separate conditions. 'If X is greater than 10' and 'if X is less than 100'. To deal with this common eventuality, Java offers a family of what are known as logical operators, and these can be used to join single conditionals together into a compound condition which has more than one part to it. The first of these we will look at is the and operator, and it takes the form of two ampersands side by side: &&. We would write the check above as follows:
When Java encounters a compound conditional, it tries to assess it as a whole - the code belonging to the if statement will only be executed if every condition in the compound evaluates correctly. This is where it starts to become complicated. The logical operators that we use indicate to Java how each of the conditionals making up the compound must evaluate in order for the whole to be true. There are two main kinds of logical operators - AND, and OR. There are others (such as the Exclusive Or (XOR) operator, but we won't look at these).
We can build compound conditions of any level of complexity using these two operators, but it starts to get a more complicated the more compounds we use. Let's look first at a simple example:
In this case, we check to see if one or the other condition is true: We will make a move if the square ahead is a dot, or if it is a pill. If it is neither, then we don't move. We can use a truth table to clarify as to what each part of a compound conditional must evaluate in order for the whole to evaluate to true. For a two part compound for an or statement, it looks like this:
Let's look at that in the context of the condition we have above:
If any of the conditions evaluates to true, then the compound evaluates to true and the code within the if statement will be executed. Only if both of the conditions evaluate to false will the compound condition also evaluate to false. We use a truth table for AND operators too, except the table looks a little different:
Only if both conditions as part of an AND compound are true will the whole evaluate to true. We can extend compound conditions as long as we like using further operators, but the logic can become quite tricky for this. We can also use brackets to enforce precedence of logical comparisons, but that's a topic for another day perhaps.
We will finish up this chapter with a look at the last of the selection structures. This is known as the switch statement, and it is largely a syntactical nicety - it doesn't do anything that an if-else if structure can't do, but it does it neater. Switch statements are used to select between a range of different courses of action that are dependant on the value of a primitive variable. The switch statement has the following form:
An important proviso is this: A SWITCH STATEMENT WILL NOT WORK ON STRINGS. It will work on individuals characters within a string, but it won't work on a string as a whole. It works only on primitive data types: ints, chars, and doubles. This restricts its usefulness, but it is still very handy in certain circumstances. When Java encounters a switch statement, it looks at the value of the variable used for the switch (called some variable) above, and then it looks for a case statement that matches the value it has for some variable. If it finds one, it executes the code it finds belonging to that case statement, until it sees a break. If it doesn't find one, it executes the code in the default case. So, for example:
Here, we prompt the user for a number and store that in the variable num. We then check the value of that variable in our switch statement. If it's a 1, then Java looks at each of the case statements until it finds one that matches the value it has (case 1:). When it finds this, it executes all of the code beneath that case statement until it finds a break statement. If the value is one, it turns left. If we enter a number we don't have a case for, the default case will be executed and Pacman will plaintively exclaim 'I don't know what to do!'. If we omit the break statements from the switch statement, the behaviour is very different - the break statements are therefore very important. Switch has a 'fall through' architecture, which means that all of the cases beneath the selected case will be executed unless the break statements are there. It is however very rare that you might want to make use of this fall through architecture, but it can be useful on occasion:
Selection structures allow us to make Pacman respond to the world around him. When combined with loops, they give us an immensely powerful way to make Pacman deal with maps as he finds them... we don't need to give him explicit instructions to solve a map, we just tell him how to respond to the things he finds and off he goes. In the next chapter, you'll look at making use of the conditional statements we have seen in this chapter to create flexible loops using while statements. From that point on, Pacman becomes a powerful instrument for us to set loose on any map that may be thrown at us. Exciting stuff! Further ReadingThe following table details further reading on the topic in this chapter, and also any external resources that you may find useful.
|
| Previous | Table of Contents | Next |
© 2004-2006 Michael James Heron and Pauline Belford