TEACH POPCOURSE5 - Boolean expressions and conditionals

Contents


POPBUGS ex: making bugs run on `autopilot'

All bugs in LIB POPBUGS have some notion of how they should behave when not under user control. By default, a bug just moves forwards one space in each bugworld `cycle'. Provided you have LIB POPBUGS loaded, you can test this out with

1 -> pb_new_world;
3 -> pb_cycles;
The assignment of 3 to `pb_cycles' says `run the bugworld for three cycles'. The single bug moves forwards one space in each cycle.

The behaviour of a bug is fixed by the value of its `behaviour' attribute. So to change its behaviour we have to change the value of this attribute. Let's say we would like to change the bug's behaviour so that it constantly moves in a circle. First we define a procedure that implements a single, rotational move forwards.

define circle;
   pb_direction + 20 -> pb_direction;
   1 -> pb_steps;
enddefine;
Then we arrange for this procedure to become the value of the bug's `behaviour' attribute. Given that the default name of the initial bug in the bugworld is `bug1' we can achieve this with the following assignment to `pb_spec':

[[name bug1][behaviour circle]] -> pb_spec;
Alternatively, if the bug we want to affect is the only bug in the bugworld, we can just assign the procedure's name directly to the variable `pb_behaviour'.

"circle" -> pb_behaviour;
In a similar way, we can change the position of the main bug by the assignment of a list (i.e., a pair of coordinates) to `pb_position'. For example to place the bug in north-west quadrant we might do

[40 40] -> pb_position;

After loading the `circle' procedure and doing the various assignment, the bug will move in a circle. You can test this with

100 -> pb_cycles;
The ability to associate arbitrary procedures with particular bugs opens up all sort of possibilities for bugworld simulations. However, to take advantage of them we need to first work through some additional features of POP-11.

Imagine that we want the bug to always veer to the right in the top half of the world and to the left in the bottom half of the world. To obtain this behaviour we need some way of telling POP-11 to do something (make the bug veer right) only if a particular condition holds (namely that the bug is in the top half of the space). We can achieve this using a variant of the `if' command, illustrated in the definition below. (If you load this definition and then the commands that come after, the bug will `snake' across the space.)

define snake;
   if pb_position(2) > 50 then
      pb_direction - 20 -> pb_direction;
   endif;
   if 50 > pb_position(2)  then
      pb_direction + 20 -> pb_direction;
   endif;
   1 -> pb_steps;
enddefine;


 1 -> pb_new_world;
 [[name bug1][position [90 53]][behaviour snake]] -> pb_spec;
 100 -> pb_cycles;
Notice that the `if' commands in the procedure do not have a `matches' command embedded between the `if' and the `then'. Instead they have triples made up of a subscript `pb_position(2)', the greater-than symbol and the number 50. These triples form `boolean expressions'. A mathematical expression such as `2 + 2' has a numeric value but a boolean expression has a truth value, which means that it is either `true'or `false'.

A simple example is `3 > 2'. The symbol in the middle is the `greater-than' symbol so the expression reads `3 is greater than 2'. This is actually true, and the value of the expression produced by POP-11 confirms this!

3 > 2 ==>
** <true>
But

2 > 3 ==>
** <false>
The angle-brackets are included to show that true and false are special objects.


The conditional `if'

(This section repeats material from above and is included for people NOT doing POPBUGS exercises.)

Imagine that we want to be able to find out which of two variables has the biggest value in it, and then print out its name. To do this we need to take an action (print out a name) if and only if one value is bigger than all the rest. This can be achieved using a POP-11 command known as the `if' command. This command is `complex' because it has various parts that all have to go in the right sequence.

Imagine that we have two variables `Fred' and `Jill' and that these two contain the amount of time (in minutes) that Fred and Jill have used the phone in the past month. To decide which of the two is the biggest phone user we use an `if' command like this.

if Fred > Jill then
   "Fred" ==>
endif;
When executed this will print out `Fred' only if the value of the variable `Fred' is bigger than the value of the variable `Jill'.


Boolean expressions

To understand what is going on here we have to look at the various parts of the command. There are two main parts. There is the part between the `if' and the `then', and there is the part between the `then' and the `endif'. The former part contains

Fred > Jill
This is an expression involving the symbol `>. Like the expressions we looked at before, this one has a variable on the left and a variable on the right and a special symbol in the middle. This symbol is an `operator' like `+', `-' etc.; however it is a `boolean operator', which means that it produces a boolean value, i.e., a truth value.

There are two truth values and they are <true> and <false>. For convenience, <true> is permanently stored as the value of the variable `true'; similarly for <false>. Thus

true ==>
prints out

** <true>
and

false ==>
prints out

** <false>
These values correspond to `yes' and `no'. The expression `Fred > Jill' has the value <true> if and only if the value of `Fred' is greater than the value of `Jill'. You can see this if you mark and execute the following.

vars Fred, Jill, Bob;
5 -> Fred;
2 -> Jill;
4 -> Bob;
Fred > Jill ==>
The result printed out is

** <true>
since the value of `Fred' in this case is greater than the value of `Jill'. (Note that POP11 is case sensitive, so `Jill' is not the same variable as `jill'.)

If we now do

if Fred > Jill then
   "Fred" ==>
endif;
POP-11 prints out

** Fred
to indicate that Fred is a bigger user than Jill. If we do

if Jill > Fred then
   "Jill" ==>
endif;
POP-11 prints nothing.


Other boolean operators

In POP-11, `if' commands are allowed to have any boolean expression in between the `if' and the `then'. `matches' can be used with `if' because it is a boolean expression. The commands after the `then' are only executed if the value of boolean expression is true. Thus

if 3 > 2 then [yes] ==> endif;
** [yes]
but

if 3 <= 2 then [yes] ==> endif
prints nothing.

A boolean expression is usually constructed by placing two expressions on either side of a boolean operator, e.g., the greater-than symbol. There are many boolean operators. Two that will be particularly useful are the `=', which tests whether two items are the same, and the `/=' (not-equals) which tests whether two items are different. These behave as follows.

2 = 2 ==>
** <true>
but

2 = 3 ==>
** <false>
and

2 /= 3 ==>
** <true>
Experiment: type in an `if' command that prints out the value 100 if the value of the variable `y' is equal to the value of the variable `x'. Check that the command works by assigning the same value to both `x' and `y'.


Using `else'

In many situations we want to be able to execute some commands if the value of an expression is not true. An easy way to achieve this is to include an `else' section in an `if' command. This is placed immediately before the `endif'. It is made up of the word `else', followed by an arbitrarily long sequence of commands. The commands are executed only if the main condition is not true. Thus

if 4 < 3 then
   1 ==>
else
   2 ==>
endif;
causes

** 2
to be printed out.


Complex boolean expressions

Sometimes we want to test several conditions in one go. We can do this by constructing a complex boolean expression using the `and' operator. An `and' expression is just like any other boolean expression except that its value is <true> only if there is an expression on the left whose value is <true>, and there is an expression on the right whose value is also <true>. Thus

3 < 4 and 2 > 1 ==>
prints out

** <true>
We can combine `and' expressions together like mathematical expressions. So

5 > 2 and 2 > 1 and 5 > 10 ==>
prints out

** <false>
because 5 is not greater than 10.

In addition to `and' expressions, POP-11 also allows `or' expressions. These work just like `and' expressions except that they have the value <true> if either one of the surrounding expressions has the value true. Thus

1 > 2 or 2 > 1 ==>
prints out

** <true>
Experiment: write an `if' command that prints out 100 if the value of `x' is greater than the value of `y' or vice versa. Check that the command only prints nothing if the values of the two variables are the same.


POPBUGS ex: implementing wall-avoidance

Going back to the `snake' procedure above, we can now see that the first `if' tests to see whether the bug's Y coordinate (i.e., its position on the north-south dimension) is greater than the middle value of 50. If it is, the bug is `south of the equator', the value of the expression is true, the pb_direction variable is decremented and the bug is thus turned to the right. If the bug is north of the equator, the value of the expression is false, the value of pb_direction is incremented and the bug is thus turned to the left. This achieves the desired snaking behaviour.

Using conditional commands, we can write a bug procedure that will enable the bug to move around the world without crashing into the walls. The following procedure illustrates a simple approach. It ensures that whenever the bug gets close to the edge of the space, it reverses its direction. However, so as to ensure that it does not simply move to and fro on the same line, the direction reversal is not quite `perfect', i.e., the heading is only changed by 170 degrees rather than 180. Test this out by loading the following.

define avoid_walls;
   if pb_position(1) > 90
   or pb_position(1) < 10
   or pb_position(2) > 90
   or pb_position(2) < 10 then
      pb_direction + 170 -> pb_direction;
      1 -> pb_steps;
   endif;
   1 -> pb_steps;
enddefine;

"avoid_walls" -> pb_behaviour;
 1000 -> pb_cycles;
Of course the procedure above will fail if the bugworld contains any objects. This can be confirmed with the following.

1 -> pb_new_world;
[[name bug1] [behaviour avoid_walls]] -> pb_spec;
[[dimensions [20 20]][shape box]] -> pb_spec;
1000 -> pb_cycles;
To overcome this problem we need to make the bug take account of new objects that are introduced into the bugworld. This can be done by utilising the bug's sensors. By default, each bug has one simulated infra-red, proximity detector, pointing straight ahead. To see this, do

3 -> pb_display_level;
The display should now include a dashed line. This represents the bug's range-finder ray. The numbers on the ray show where it has intercepted an obstacle. (The number is actually the internal number of the intercepted object.)

The current sensory inputs to the main bug are always stored in the variable `pb_sensor_inputs'.

pb_sensor_inputs ==>
** [0.910463]
By modifying the `avoid_walls' procedure so that it takes account of the numbers in `pb_sensor_inputs' it is possible to ensure that the bug never crashes into any obstacle. (The construction of this procedure is an exercise for this file - see below.)

Note that the numbers in `pb_sensor_inputs' are recomputed each time you access the variable. So if you take a copy of this variable by doing an assignment like

pb_sensor_inputs -> inputs
the value of `inputs' will become incorrect as soon as there is any change in the bugworld. (The simple solution to this is to never take a copy of this variable.)


POPBUGS ex: Using colour-sensitive sensors

By default the main bug has a single proximity sensor pointing straight ahead. However, we can, if we like, customise the bug's sensory equipment. To do this we have to explicitly set the value of the bug's `sensors' attribute. The value we provide should be a list of sublists, each of which describes a single sensor. The first element of each sublist (i.e., sensor spec) should be a number specifying the position of the sensor on the surface of the bug. The number is the offset (in degrees) of the sensor's position on the bug's surface. If the sublist includes no other information, the described sensor is assumed to be a standard proximity sensory. Thus to set up the main bug with a single proximity sensor pointing directly to the bug's left we should do this

[[name bug1] [sensors [[90]]] ] -> pb_spec;

We specify the number 90 here because `left' is exactly 90 degrees anti-clockwise from straight ahead. (To have the sensor ray displayed, assign the value 3 to pb_display_level.)

If we want the bug to have two proximity sensors, one pointing directly left and one pointing directly right, then we would do

[[name bug1] [sensors [[90] [270]]] ] -> pb_spec;

In addition to proximity sensors, we can also create colour sensors. For example, to set up a bug with a single sensor which detects the colour of the nearest object directly ahead, we would do

[[name bug1] [sensors [[0 nearest col]]]] -> pb_spec;
Provided there are no other objects in the world, the pb_sensor_inputs list will now just contain the number 0.753. This number is a POPBUGS `colour code.' It is, in fact, the code for black. This is because the nearest obstacle is the bugworld boundary and the default colour of this is black. Note that you can find out the code for any colour using the `pbcol' VED command. For example, you could give the VED command `pbcol red' to find out the colour code for red. Alternatively, if you want to get a code within POP-11 you can do things like

pb_colour_code(red) ==> ** 0.76

pb_colour_code(green) ==> ** 0.782

Colour sensors and proximity sensors can be mixed together in any combination. To set up a bug with two proximity sensors and two colour sensors we would do something like

[[name bug1]
    [sensors [[15][15 nearest col][-15][-15 nearest col]]]] -> pb_spec;
Here we have specified the offsets as 15 and -15. This ensures that the sensors will be arranged so that we have one colour sensor and one proximity sensor pointing slightly to the left, and one colour sensor and one proximity sensor pointing slightly to the right. After this assignment the pb_sensor_inputs list will be made up of proximity values interspersed with colour codes, e.g.

pb_sensor_inputs ==>
** [0.670849 0.009 0.942231 0.333]
We can exploit this colour-sensitivity to make the bug more `wary' of red objects than green objects, say. To do this, we would modify the way in which the bug's procedure accesses the proximity values and also the way in which it responds to a high proximity value. A simple approach involves checking to see whether either of the two colour inputs are the code for red (normally 0.76), and then modifying the proximity-threshold which controls whether or not the bug turns in the next cycle.

vars threshold, inputs;

define avoid_red;
   pb_sensor_inputs -> inputs;
   if inputs(2) =0.76
   or inputs(4) =0.76 then
       0.5 -> threshold;
   else
       0.88 -> threshold;
   endif;
   if inputs(1) > threshold or inputs(3) > threshold then
      pb_direction - 25 -> pb_direction;
   else
      1 -> pb_steps;
   endif;
enddefine;

1 -> pb_new_world;
[[name bug1][sensors [[15][15 nearest col][-15][-15 nearest col]]]]
    -> pb_spec;
[[name bug1][behaviour avoid_red]] -> pb_spec;
[[name r][colour red][dimensions [20 20]][position [15 15]]] -> pb_spec;
[[name g][colour green][dimensions [10 10]][position [80 80]]] -> pb_spec;
1000 -> pb_cycles;
The default value of an obstacle's `behaviour' attribute is the word `static'. This means, in effect, that they cannot be moved around. However, we can change this by setting the value to be `passive'. This renders the object movable. For example,

[[name g][behaviour passive][trail_colour yellow]] -> pb_spec;
makes the object called `g' movable and gives it a distinctive trail colour. Driving the bug into the green object will now have the effect of `showing' it forwards.

Experiment: push the small, green object to the centre of the space.


Tip of the day

When the cursor is placed inside a procedure definition you can give various commands which relate to the `current procedure'. For example, `mcp' will mark the range enclosing the procedure. `lcp' will load the procedure and `jcp' will tidy up the formatting of the procedure.


Exercises

To test your knowledge of the material in this file you should do the following exercises. First, copy them into a file of your own. Then, edit in answers underneath each question and print out the file. Answers which involve writing POP-11 code should include some explanatory text.

  1. Imagine we have three people: Fred, Jill and Bob. For each person we have a variable of the same name that contains the total telephone expenditure of that person. Write code (i.e., a sequence of commands) that would print out 1 if Fred is the biggest spender, 2 if Jill is and 3 if Bob is. The variable initializations are as follows.

    vars Fred, Jill, Bob;
    345 -> Fred;
    220 -> Jill;
    561 -> Bob;
    
  2. What behaviour is produced by the following command and what can you deduce from it.

    if 99 then 1 ==> endif;
    
  3. Write code that will print hello if the variables `a' and `b' have the value <true>, or if variables `c' and `d' do, and print goodbye otherwise.

  4. Write code that will print out the fifth element of the list `numbers' if the second element of that list is greater than 9, or if the third element is less than 43.

POPBUGS ex

  1. Write a bug-behaviour procedure that enables the bug to roam around the space without crashing into any obstacles or walls (not just ones of a certain colour).

  2. Write a bugworld initialization procedure that creates at least two movable, green objects and at least two non-movable, red obstacles. Then write a bug procedure that will cause the bug to push all the green objects up against the boundary of the bugworld.

Revision exercises

Do not do the following exercises on a first reading of this file. Save them for revision purposes.


Moving on

If you have got this far you know enough POP-11 to be able to write complex programs. (The subset of POP-11 introduced is called the `bugcourse subset'.) The next few files introduce some additional features of the language which make certain programming tasks easier. If you want to learn these extra features go on to TEACH POPCOURSE6. Otherwise, jump to POPCOURSE10, which deals with the online help system.


Page created on: Fri Apr 26 09:34:53 BST 2002
Feedback to Chris Thornton
hits this year