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.
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'.
Fred > JillThis 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
** Fredto indicate that Fred is a bigger user than Jill. If we do
if Jill > Fred then "Jill" ==> endif;POP-11 prints nothing.
if 3 > 2 then [yes] ==> endif; ** [yes]but
if 3 <= 2 then [yes] ==> endifprints 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'.
if 4 < 3 then 1 ==> else 2 ==> endif;causes
** 2to be printed out.
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.
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 -> inputsthe 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.)
[[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.
vars Fred, Jill, Bob; 345 -> Fred; 220 -> Jill; 561 -> Bob;
if 99 then 1 ==> endif;