Monkeys at Keyboards: APE Antics
© Michael James Heron and Pauline Belford
Topic: Java Programming
Level: 1
Version: alpha

You know she threw away a hairbrush yesterday? It's mine now - I pet it like a cat.
Penny Arcade

7 - Select Your Destiny

PreviousTable of ContentsNext
Forum


Chapter Objectives

By the end of this chapter, the reader will be able to:

  • make use of if statements to implement conditional activities.
  • make use of Pacman's environment sensing methods.


7.1

Introduction

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.

7.2

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:

if (condition) { 
// something to happen if the condition is met
}

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.

7.3

A New APE Method: isObstacleAhead

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:

ping_pong.map
Fig 7.1: ping_pong.map

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:

import draconia.APE.pacman.*; 

public class SelectiveMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
main.setMap ("ping_pong");
main.showGame();
for (int i = 0; i < 100; i++) {
if (main.isObstacleAhead()) {
main.turnLeft();
main.turnLeft();
}

main.move();
}

}

}

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:

import draconia.APE.pacman.*; 

public class SelectiveMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
main.setMap ("ping_pong");
main.showGame();
for (int i = 0; i < 100; i++) {
if (main.isObstacleAhead()) {
main.turnLeft();
main.turnLeft();
}

main.move();
}

}

}

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.

7.4

Comparison Operators

There are five of these symbols that are used for the comparison of primitive data types (especially numerical data types):

Comparison Operators
Fig 7.2: Comparison Operators

Terminology Alert!

There are five core comparison operators, and these are used to compare primitive data types against each other. Technically the equivalence operator (==) is also a comparison operator, but in the context of this book we will mean the five operators defined above whenever we refer to comparison operators.


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:

import draconia.APE.pacman.*; 

public class ValidationMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
int movesToMake;
main.setMap ("blank");
main.showGame();
movesToMake = main.getIntFromUser ("How far should Pacman roam?");
if (movesToMake > 19) {
main.sendMessage ("No, he shall die! Setting number of moves to nineteen instead.");
movesToMake = 19;
}

for (int i = 0; i < movesToMake; i++) {
main.move();
}

}

}

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.

7.5

The isObstacleAhead Method Revisited

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.

import draconia.APE.pacman.*; 

public class SensingMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
main.setMap ("valid_symbols");
main.showGame();
if (main.isObstacleAhead ("dot") == true) {
main.move();
}

}

}

The list of valid objects in the APE Pacman universe is as follows:

Valid APE Objects
Fig 7.3: Valid APE Objects

You can also use the sister method 'getObstacleAhead' - this will return a string indicating what obstacle lies ahead of Pacman in the square ahead:

String obs; 
obs = main.getObstacleAhead();
main.sendMessage ("Obstacle Ahead is " + obs);

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.

7.6

The If-Else Structure

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:

if (movesToMake < 19) { 
for (int i = 0; i < movesToMake; i++) {
main.move();
}

}

else {
main.sendMessage ("No, he shall die! You Shall Not Pass.");
}

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:

import draconia.APE.pacman.*; 

public class SecondValidationMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
int movesToMake;
main.setMap ("blank");
main.showGame();
movesToMake = main.getIntFromUser ("How far should Pacman roam?");
if (movesToMake < 19) {
for (int i = 0; i < movesToMake; i++) {
main.move();
}

}

else {
main.sendMessage ("No, he shall die! You Shall Not Pass.");
}

}

}

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:

if condition then
do something
otherwise
do something else

So for example:

if there is a wall in front of Pacman then
turn around
otherwise
move forward

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.

7.7

The If-Else-If Structure

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:

if condition then
do something
otherwise is a second condition is true
do something else
otherwise if a third condition is true
do a third thing

For example:

if there is a blue ghost in front of Pacman then
turn left
turn left
move forward
otherwise if there is a wall in front of Pacman then
turn around
otherwise if the space ahead of Pacman is empty then
move forward

This would translate into the following code:

if (main.isObstacleAhead (“blue ghost”) == true) { 
main.turnLeft();
main.turnLeft();
main.move();
}

else if (main.isObstacleAhead ("wall") == true) {
main.turnLeft();
}

else if (main.isObstacleAhead ("empty") == true) {
main.move();
}

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.

if (main.isObstacleAhead (“blue ghost”) == true) { 
main.turnLeft();
main.turnLeft();
main.move();
}

else if (main.isObstacleAhead ("wall") == true) {
main.turnLeft();
}

else if (main.isObstacleAhead ("empty") == true) {
main.move();
}

else {
main.sendMessage ("I don't know what to do! I'm scared and alone!");
}

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.

7.8

Compound Conditionals

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:

if (10 < x > 100) { 
}

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:

if (x > 10 && x < 100) { 
}

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).

Logical Operators
Fig 7.4: Logical Operators

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:

if (main.isObstacleAhead ("dot") || main.isObstacleAhead ("pill")) { 
main.move();
}

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:

Truth Table for the Or Operator
Fig 7.5: Truth Table for the Or Operator

Let's look at that in the context of the condition we have above:

Conditions for Truth Table
Fig 7.6: Conditions for Truth Table

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:

Truth Table for the And Operator
Fig 7.7: Truth Table for the And Operator

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.

7.9

The Switch Statement

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:

switch (some variable) { 
case value_one:
do something;
break;
case value_two:
do something else;
break;
case value_three:
do a third thing:
break;
default:
do a general catchall thing;
}

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:

import draconia.APE.pacman.*; 

public class SwitchMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
int num;
main.setMap ("blank");
main.showGame();
num = main.getIntFromUser ("Please give me a number.");
switch (num) {
case 1:
main.turnLeft();
break;
case 2:
main.turnRight();
break;
case 3:
main.move();
break;
default:
main.sendMessage ("I don't know what to do!");
break;
}

}

}

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:

import draconia.APE.pacman.*; 

public class PacmanSingsChristmasCarols {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
int num;
main.setMap ("blank");
main.showGame();
num = main.getIntFromUser ("Which day of Christmas?");
main.sendMessage ("On the first day of Christmas, my true love sent to me...
");
switch (num) {
case 12:
System.out.println ("Twelve drummers drumming.");
case 11:
System.out.println ("Eleven pipers piping.");
case 10:
System.out.println ("Ten Lords-a-Leapin'");
case 9:
System.out.println ("Nine ladies dancing.");
case 8:
System.out.println ("Eight maids-a-milking.");
case 7:
System.out.println ("Seven swans-a-swimming.");
case 6:
System.out.println ("Six Geese-a-laying.");
case 5:
System.out.println ("Fiiiiive Goooooollldd Riiinnnggss");
case 4:
System.out.println ("Four calling birds.");
case 3:
System.out.println ("Three french hens.");
case 2:
System.out.println ("Two turtle doves.");
default:
System.out.println ("And a partridge in a pear tree!");
}

}

}

7.10

Conclusion

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 Reading

The following table details further reading on the topic in this chapter, and also any external resources that you may find useful.

ResourceDescription
Example Programs from this chapterThis is a zip file of all the programs shown in this chapter.

PreviousTable of ContentsNext

© 2004-2006 Michael James Heron and Pauline Belford