Robot Notes
Robot Notes
1 Robot world
We will learn programming by solving problems
faced by a robot living in a small grid-like world.
Our robot is capable of only four basic actions:
moving one step forward, turning left, picking up
and putting down beepers. Through the magic of
programming, you will learn to have him combine
those four basic actions in order to perform very
complicated tasks.
Before we learn how to do all this, let’s have a We can now start giving instructions to the
look at the robot’s world. Open a Python inter- robot. In Python, you give instructions to a robot
preter window, and type in this code: by writing the robot’s name, a dot, and the com-
mand:
from cs1robots import * hubo.move()
create_world()
And immediately hubo will move one step forward:
A new window will open showing a new robot
world, like this:
hubo.turn_left()
hubo = Robot()
∗ These lecture notes are edited versions of the RUR- You can now control your robot by entering com-
PLE lessons by André Roberge, which can be found at mands one by one—the robot executes each com-
http://rur-ple.sourceforge.net/. mand as soon as you have entered it.
1
2 First program
from cs1robots import *
But controlling a robot by typing in commands on create_world()
a keyboard is not programming. “Programming” a hubo = Robot()
robot means to design a whole sequence of instruc- hubo.set_trace("blue")
tions (an algorithm) for the robot, which is then hubo.set_pause(2)
executed automatically by the robot to solve some ...
task.
Let’s try this: Type the following text and save The number in parentheses is the length of the
it as a file square.py: break in seconds.
2
There are a few ways to write comments in a pro-
gram. I’m going to teach you the simplest way used
in Python: If a line starts with a hash symbol #,
then this line is a comment and will be ignored by
the Python interpreter:
# My first program
5 Turning left and turning right It becomes quite tedious to write and read this
code. This is because we repeat ourselves; in other
We already saw that our robot can turn left, as long
words, the same sequence of instructions appears
as it is by exactly 90 degrees. There is no other
in many different places in the program. One basic
steering mechanism—our robots are rather cheap.
rule of programming is: Do not repeat yourself !
In Python, one can give a simple name to a se-
Task: (Square) Write a program that makes hubo ries of instructions. For example, we can define a
walk around a bigger square of size two, like this: turn_right command:
from cs1robots import *
create_world()
hubo = Robot()
hubo.turn_left()
def turn_right():
hubo.turn_left()
hubo.turn_left()
hubo.turn_left()
hubo.move()
So how can we make the robot turn right instead turn_right()
of left? The only possible way to do this is by exe- hubo.move()
cuting three left-turns. The following program let’s turn_right()
our robot walk around a square in clock-wise direc- hubo.move()
tion: turn_right()
hubo.move()
from cs1robots import *
create_world() A function definition starts with the Python key-
hubo = Robot() word def, the name of the function, a pair of paren-
hubo.turn_left() theses, and a colon. The body of the function are
hubo.move() the instructions that are executed when the func-
# make a right turn tion is called. Each line in the body has to be in-
hubo.turn_left() dented by the exact same amount (it doesn’t matter
hubo.turn_left() how much—I always use two spaces).
hubo.turn_left() A keyword like def is a special word that can be
hubo.move() used in Python only to define a new function (it is
# make a right turn for instance not allowed to define a function named
hubo.turn_left() “def”).
hubo.turn_left()
hubo.turn_left() Task: (StepBack) Write a function step_back
hubo.move() that makes hubo go backward one step, so that
# make a right turn
hubo.move()
hubo.turn_left()
step_back()
hubo.turn_left()
hubo.turn_left()
hubo.move() will leave the robot at exactly the same position
and orientation it started with:
3
Note that we had to tell Python to create a robot
that already carries one beeper. (Try what happens
without this.)
6 Beepers
Beepers are small objects (drawn fairly big on the
screen) that make a small beeping sound when they Avoid repetition by defining useful functions.
are turned on. A robot can only hear a beeper if Maybe you would rather like to build a more
he is standing right on top of them, on the same interesting world. You can do that using the
corner. He can pick them up and carry them in his edit_world() function:
pockets, or he can put them down.
You can instruct the robot to pick up a beeper from cs1robots import *
through the pick_beeper() command, or to put create_world(avenues = 20, streets = 5)
some down, through the drop_beeper() command. edit_world()
If you ask a robot to pick up beepers where there save_world("myworld.wld")
are none, or to put down beepers if he doesn’t carry
any, you will get an error message and your program The edit_world function places your world in an
will terminate. editable state. You can click on the world to add
The following small program will drop a beeper and remove walls. If you click with the left mouse
at the intersection of avenue 3 and street 1: button on an intersection, a beeper is placed there.
from cs1robots import * You can remove beepers with the right mouse but-
create_world() ton. Press the Return key when you are done edit-
hubo = Robot(beepers = 1) ing. Your program will then continue and save your
hubo.set_trace("blue") world. (Again, you can call save_world() without
giving a filename. Then Python will let you choose
hubo.move()
a file when the program is run.)
hubo.move()
hubo.drop_beeper() Note that you can only create or load a world
hubo.move() once in a Python program or from the Python in-
hubo.move() terpreter. To load another world, you have to rerun
your program, or restart the interpreter.
4
8 Top-down design This approach to solving a given problem is called
top-down design, because we started at the top of
Let’s solve the following problem: Hubo delivers
the problem, with an outline for a solution, and
newspapers in his local neighborhood. Have him
then focused on each part of the solution one-by-
climb the stairs to the front door of a house, drop
one.
the newspaper (represented by a beeper) on the top
step, and return to his starting point as illustrated
below. The world file is newspaper.wld : Task: (Harvest1) It’s harvest time! Have the
robot pick up all the carrots (represented by beep-
ers) in this garden. The world file is harvest1.wld.
Use top-down design.
Task: (Newspaper1) Finish this program by writ- Task: (Harvest2) Redo the Harvest1 task above
ing functions to perform all the smaller tasks. using for-loops.
5
Task: (Harvest3) Some problem as Harvest2, but • The colon (:) precedes the series of instructions
for the harvest2.wld world file: that the robot must follow if the condition is
true;
• The series of instructions to follow in that case
is indented, just like we had in the case of func-
tion definitions.
Let’s look at a simple example. Suppose we want
the robot to take 9 steps, picking up any beepers
that are there along the way. (We suppose that
there can be at most one beeper at a given spot.)
For example, the starting position might be like the
following:
for i in range(9):
move_and_pick()
It would be nice if we could just run the progrem Task: (Harvest4) Modify your program for the
we wrote in task Harvest2. But it doesn’t work, Harvest2 task so that it works for the world har-
because calling pick_beeper() when there is no vest3.wld. Note that the new program should also
beeper causes an error. automatically work for the original harvest1.wld
If only hubo could check if there is a beeper, and world. Try it!
pick it up only when a beeper is really there. . . To be able to harvest in the fall, you have to
The Python keyword if does exactly this: put seeds in the ground in the spring. Can we
write a program that will put seed potatoes in the
if hubo.on_beeper(): ground, but skip any spot where there already is
hubo.pick_beeper() a potato? In other words, starting with the world
harvest3.wld, we should end up with the world of
Let’s look at the meaning of the above code: harvest1.wld !
• The instruction if implies that some condi- To do this, hubo needs to drop a beeper if there
tion, whose value is true or false, is going to is no beeper at the current location. The following
follow; code will do this:
• hubo.on_beeper() is a condition (or test) if not hubo.on_beeper():
which is true if hubo is on top of a beeper and hubo.drop_beeper()
false otherwise;
6
The Python word not inverts the sense
of the condition: If hubo.on_beeper() from cs1robots import *
is true, then not hubo.on_beeper() is create_world(avenues = 5, streets = 5)
false. If hubo.on_beeper() is false, then
not hubo.on_beeper() is true. This is the hubo = Robot()
simplest example of a Python expression. hubo.set_trace("blue")
def move_or_turn():
Task: (Plant1) Write a program that will plant if hubo.front_is_clear():
potatoes so that the field will look like harvest1.wld hubo.move()
at the end. It should skip any spot where there else:
already is a potato. (Note that you have to cre- hubo.turn_left()
ate your robot with sufficiently many beepers by
using hubo = Robot(beepers=36). Try your pro- for i in range(20):
gram with an empty world and with the worlds har- move_or_turn()
vest1.wld and harvest3.wld.
On a small world this gives the following end result:
11 What else?
In addition to being able to find out if he is standing
next to one or more beepers, a robot can see if there
is a wall in front of him, blocking his way. He can
also turn his head to his left or his right and see if
there is a wall there. You can ask him to have a
look with the following tests:
hubo.front_is_clear()
hubo.left_is_clear()
We can make this more interesting by doing a
hubo.right_is_clear()
“dance” if we can move forward, and dropping a
beeper if we have to turn. The following program
Let’s use the first one to have the robot explore his does just that, going only partly around the world:
world. We will have him follow the boundary of
his world by asking him to move forward, if there hubo = Robot(beepers = 10)
is no wall in front of him, and turn left otherwise.
The following simple program is the basis of what def dance():
is needed: for i in range(4):
hubo.turn_left()
if hubo.front_is_clear():
def move_or_turn():
hubo.move()
if hubo.front_is_clear():
else:
dance()
hubo.turn_left()
hubo.move()
else:
We learnt a new Python keyword here: else. It hubo.turn_left()
introduces an alternative sequence of instructions. hubo.drop_beeper()
In this example, if there is no wall in front of hubo,
then hubo.front_is_clear() is true, and the se- for i in range(18):
quence after the if line is executed. If there is move_or_turn()
a wall, then hubo.front_is_clear() is false, and
the sequence after the else line is executed. Do Notice how the instructions dance() and
not forget the colon after else, and watch out for hubo.move() are aligned after the if statement
the correct indentation! and indented from it, indicating that they belong
Let’s repeat this simple conditional instruction in the same block of instructions. The instructions
many times to go around the world: hubo.turn_left() and hubo.drop_beeper()
7
are similarly aligned, indented from the else
statement to which they belong. The result of def move_or_turn():
running the program is indicated below. if hubo.front_is_clear():
dance()
hubo.move()
else:
hubo.turn_left()
hubo.drop_beeper()
def move_or_turn():
if hubo.front_is_clear():
dance()
hubo.move()
else:
hubo.turn_left()
hubo.drop_beeper() So, as you can see, much information is given
through blank spaces (that is, the indentation of the
instructions within blocks). Through practice, you
Now, the definition of move_or_turn() includes a will learn to use this to your advantage and realise
choice resulting in either a dance and a move for- that Python allows you to write very readable code
ward, or a left turn, followed every time by the in- by indenting instructions.
struction hubo.drop_beeper(). The result of run- Our robot has become quite good at jumping
ning this program is indicated below: hurdles. He now enters races of different lengths:
short sprints and long races. He knows that he has
reached the finish line when he is next to a beeper.
Below, you will find three such race courses; the
world files are hurdles1.wld, hurdles2.wld, and hur-
dles3.wld :
8
If we have more than three choices, all we need
def move_jump_or_finish(): to do is add other elif statements
# test for end of race
if not hubo.on_beeper(): def move_jump_or_finish():
# is there a hurdle? # test for end of race
if hubo.front_is_clear(): if hubo.on_beeper():
hubo.move() pass # race is over - do nothing
else: # is there a hurdle?
jump_one_hurdle() elif hubo.front_is_clear():
hubo.move()
with an appropriate jump_one_hurdle() function, elif hubo.right_is_clear(): # never
so that, other than definitions, the only instruction pass
we need is: else:
jump_one_hurdle()
for i in range(20):
move_jump_or_finish()
Since we follow the bottom wall,
hubo.right_is_clear() is always false, so
Note that, in the above definition, the code is get-
the pass instruction is always ignored. Note that
ting more and more indented as we introduce addi-
if we had used hubo.left_is_clear() instead, we
tional tests.
would have gotten stuck forever as soon as we had
reached the first hurdle. Try it for yourself!
12 If, else, if, else. . .
The previous hurdles task required you to write
an if/else within another one, all this because we
13 For a while. . .
wanted to give three choices to the robot: finish, When we want to repeat some instructions until a
move or jump. You may have noticed that this certain condition is satisfied, Python gives us a sim-
forced us to indent the code further and further. pler way to write this using a new keyword: while.
Imagine what would happen if we wanted to give Let me show you first how this would look using
10 mutually exclusive choices; the resulting code pseudocode:
would become hard to read. To help us in such
situations, Python offers a keyword that represents While not on beeper,
the combination of an else statement followed by ... keep moving;
an if clause. That keyword is elif, which we can otherwise,
think of as an abbreviation for else if. With this ... stop.
new keyword, the above code can be written as:
def move_jump_or_finish(): You should agree that this expresses the same idea
# test for end of race as before. Using Python code, here is how we would
if hubo.on_beeper(): actually write it:
pass # race is over - do nothing
# is there a hurdle? while not hubo.on_beeper():
elif hubo.front_is_clear(): hubo.move()
hubo.move()
else:
jump_one_hurdle() No more need for a for-loop with a fixed number of
repetitions.
We can now better see, as they are indented the
same way, that there are three possible choices. The Task: (Hurdles4) Use a while loop to rewrite the
else condition is executed only if all the previous hurdles3 program so that you don’t have to use a
conditions are false, so there is no condition associ- for-loop of fixed length. In other words, the core of
ated with it. your program should look like the following:
The keyword pass which we introduced here is
Python’s way of saying “do nothing.” It is neces- while not hubo.on_beeper():
sary, because it is not possible to write an empty se- move_or_jump()
quence of instructions (an empty block ) in Python.
9
Task: (Harvest5) Modify your program for the think that we are done. So we need to make one
Harvest4 task so that it also works when there is move after dropping the beeper:
more than one carrot on one place, as in world file
harvest4.wld below. All carrots must be harvested, hubo.drop_beeper()
and it should also work for the previous worlds har- hubo.move()
vest1.wld and harvest3.wld. while not hubo.on_beeper():
if hubo.front_is_clear():
hubo.move()
else:
hubo.turn_left()
This does not work—the program terminates im- Task: (InfiniteLoop) Create a world where this
mediately after dropping the beeper. The problem program fails.
is that we drop the beeper, and then immedately We need to have him move after turning right:
10
15 Clarifying our intent
hubo.drop_beeper()
We seem to have designed a program that works
hubo.move()
in all situations we are likely to encounter. This
while not hubo.on_beeper():
program, before we forget, is to allow the robot to
if hubo.right_is_clear():
explore his world, going around once. While the
turn_right()
program is rather short, and its structure should
hubo.move()
be clear at this point, it might not be so obvious to
elif hubo.front_is_clear():
someone who just happened to see it for the first
hubo.move()
time.
else:
We discussed before that our goal should be to
hubo.turn_left()
write programs so that they can be understood
by a human. It’s probably a good idea either to
But this still does not always work: add comments and/or to introduce more meaning-
ful words. Let’s start by adding comments, some-
what more verbose than we think we might need.
11
become clearer, and writing the comments differ- hurdle (world file hurdles4.wld ) illustrated below—
ently. which the program we wrote before for jumping
over hurdles could not handle!
# This program lets the robot go
# around his world counterclockwise,
# stopping when he comes back to his
starting point.
def turn_right():
for i in range(3):
hubo.turn_left() Even better, the same modified program can find
the exit to a maze (world file maze1.wld ):
def mark_starting_point_and_move():
hubo.drop_beeper()
while not hubo.front_is_clear():
hubo.turn_left()
hubo.move()
def follow_right_wall():
if hubo.right_is_clear():
# Keep to the right
turn_right()
hubo.move()
elif hubo.front_is_clear():
# move following the right wall
hubo.move()
else:
# follow the wall
hubo.turn_left()
17 Stepwise refinement
# end of definitions, begin solution We started with a simple problem to solve (going
around a rectangular world) and, by improving lit-
mark_starting_point_and_move() tle by little (also called stepwise refinement), we
manage to write a program that could be used to
while not hubo.on_beeper(): solve many other apparently unrelated problems.
follow_right_wall() At each step, we kept the changes small, and made
sure we had a working solution, before considering
Isn’t this clearer? This will also make it much easier more complex problems. We also used more de-
for us to change (“maintain”) the program if we scriptive names for parts of the algorithm which
want to change its behaviour in the future. made the program easier to read and, hopefully, to
understand. This is a strategy you should use when
writing your own programs:
16 Hurdles and mazes Steps to follow when writing a program:
Change the program you just wrote to remove the • start simple;
drop_beeper() instruction. After saving it, try • introduce small changes, one at a time;
this slightly modified program with the following • make sure that each of the changes you have
hurdle race (world file hurdles3.wld ): introduced do not invalidate the work you have
done before;
• add appropriate comments that don’t simply
repeat what each instruction does; and
• choose descriptive names.
12
rain and he remembered that the windows in his Verify to see if the program you wrote to help
house were all open. So he went back to his house Ami close the windows in her house can be used by
and stopped in front of the door, unsure of how to Hubo to do the same for his house. If not, change
proceed. it so that it works for both Ami and Hubo.
Task: (Rain2) Ami, Hubo’s friend, lives in a big- Important: To put all the trash in one pile, you
ger house. Ami was playing outside with Hubo will need to use the test hubo.carries_beepers()
when it started raining. Help Ami close the win- which I hadn’t told you about ... yet! Try some-
dows in her house. The starting position is: thing like
while hubo.carries_beepers():
# do something
13
We have two robots now—the light blue one
is hubo, the green one is ami (available colors
are gray, green, yellow, purple, blue, and
20 The robot’s compass light_blue).
If you run this interactively in the Python inter-
Our robot has a built-in compass, which allows him preter, you can now control both robots manually:
to tell if he is facing in the north direction or not. When you say hubo.move(), Hubo makes a step.
You can use this test in an if or while statement: When you say ami.move(), then Ami makes a step.
Each robot is a Python object, and by call-
if hubo.facing_north(): ing methods of the object using the dot-notation
# do something you can inquire about the state of the object
(as in ami.on_beeper()), or ask the object to
perform some action and change its state (as in
hubo.turn_left()).
Task: (Return) Write a program that will allow Starting with the situation above, we can now
Hubo to return to his usual starting position (av- write a small race:
enue 1, street 1, facing east), starting from any po-
sition and orientation in an empty world. You can for i in range(8):
create robots with a given position and orientation hubo.move()
like this: ami.move()
14
The function call turn_right(ami) executes the
function turn_right with the parameter robot set
to refer to the robot ami.
15