Now it’s starting to get interesting, and our robots are taking shape.
This workshop we’ll start following a ball (or alien?), and introduce the next tool in our toolbox - The while
loop!
We’ll finish by hunting some aliens, and counting how many different coloured ones we see:
What are they and why do we need them?
While conditionals have allowed us to make code that reacts to its environment, we were still somewhat limited. For example, in the maze workshop from last week, your solution likely just copy/pasted the same code three times over, to clear the 3 gates.
Coming back to the cooking recipe, you are often instructed to continue repeating a task until a certain condition is met, for example whisking a mixture until all the lumps are gone. Python has a very similar tool called the while
loop.
The while
loop follows almost identical structure to the if
statement, with an expression, :
, and indent:
1
2
3
4
5
6
7
x = int(input("x: "))
while x > 0:
print(f"x is {x}.")
x = x - 1
print(f"Now {x}.")
1
2
3
4
5
6
7
8
### Output ###
>>> x: 5
>>> x is 5.
>>> x is 4.
>>> x is 3.
>>> x is 2.
>>> x is 1.
>>> Now 0.
However, rather than executing the indented code only if the expression x > 0
is True
, the while
loop keeps executing the indented code as long as the expression x > 0
remains True
. The above code is the same as the following trail of conditionals:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
x = int(input("x: "))
# Check condition
if x > 0:
# Execute indented code
print(f"x is {x}.")
x = x - 1
# Check condition
if x > 0:
# Execute indented code
print(f"x is {x}.")
x = x - 1
# Check condition
if x > 0:
# Execute indented code
print(f"x is {x}.")
x = x - 1
# This continues on forever...
print(f"Now {x}.")
Now we can have programs that run forever, so how do we stop them?
If you are running your programs through the command line, then you can stop them by pressing Ctrl + C.
If you are running them in EV3Sim, then you can stop them by exiting the simulation, or restarting the bot.
Bert has some code to make his robot move repeatedly in a square:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import time
from ev3dev2.motor import LargeMotor, OUTPUT_A, OUTPUT_B
left = LargeMotor(OUTPUT_A)
right = LargeMotor(OUTPUT_B)
# Get in position
left.on_for_seconds(40, 0.4, block=False)
right.on_for_seconds(40, 0.4)
# Rotate left, wait for robots sliding a bit.
time.sleep(0.1)
left.on_for_seconds(-10, 2.37, block=False)
right.on_for_seconds(10, 2.37)
time.sleep(0.1)
left.on_for_seconds(40, 2.398, block=False)
right.on_for_seconds(40, 2.398)
# Repeat forever
while True:
# Rotate right, wait for robots sliding a bit.
time.sleep(0.1)
left.on_for_seconds(10, 2.37, block=False)
right.on_for_seconds(-10, 2.37)
time.sleep(0.1)
# move in a line
left.on_for_seconds(60, 3, block=False)
right.on_for_seconds(60, 3)
Try recreating Bert’s bot and ensuring this code works. He now wants the robot to move in a diamond pattern, as this gif demonstrates:
Can you help him?
First modify the stuff outside the while
to position the bot correctly (Forward, then rotate 45 degrees to the right). In the while loop, make it so that we move forward, and then rotate left 90 degrees (We already rotate right 90 degrees in the loop, so what do we change?)
Using Infrared Sensors
In the previous workshop we began working with color and ultrasonic sensors. This workshop we’ll work with a very powerful and slightly more complicated sensor - The infrared sensor.
The infrared (or IR) sensor is what we use to track the ball when playing a game of soccer. It does this by looking for the ball with 5 small sensors, all pointing in slightly different directions. So if the left sensors are more strongly sensing the ball than the right sensors, we know the ball is to the left of the robot.
To connect to the infrared sensors requires a bit more effort in our programming:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from ev3dev2.sensor import Sensor, INPUT_1
# Connect to the sensor
infrared = Sensor(INPUT_1, driver_name="ht-nxt-ir-seek-v2")
# Set the mode to what we want
infrared.mode = "AC-ALL"
# Gather information
direction = infrared.value(0)
very_left = infrared.value(1)
slightly_left = infrared.value(2)
middle = infrared.value(3)
slightly_right = infrared.value(4)
very_right = infrared.value(5)
print(
f"Direction: {direction}.",
f"Sensor values: {very_left}, {slightly_left}, {middle}, {slightly_right}, {very_right}."
)
Rather than using something specific, like we did with ColorSensor
and UltrasonicSensor
, we instead need to use the basic Sensor
connection, and specify a driver name.
What’s worse is that rather than having useful methods like .rgb
and distance_centimeters
, we gather different bits of data using the value
call.
As you can see in the code, value(0)
is the predicted direction of the ball. This is a value from 0 to 9, with 0 meaning no signal, and 1 to 9 meaning far left to far right (with 5 meaning centered).
value(1), value(2)
and so on then give you the specific signal strengths for the 5 smaller sensors, from left to right. These also range from 0 to 9, where 0 means no signal, and 9 means highest possible signal.
Using the soccer simulation and an infrared sensor, code a robot that follows the ball.
You can use the following template code to get started:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import time
from ev3dev2.motor import LargeMotor, OUTPUT_A, OUTPUT_B
from ev3dev2.sensor import Sensor, INPUT_1
# Two motors means we have some control over our direction.
left = LargeMotor(OUTPUT_A)
right = LargeMotor(OUTPUT_B)
ir_sensor = Sensor(INPUT_1, driver_name="ht-nxt-ir-seek-v2")
ir_sensor.mode = "AC-ALL"
while True:
direction = ir_sensor.value(0)
if direction == 0:
# Start rotating the bot around, so that we eventually find the ball.
# Something like this?
left.on(30)
right.on(-30)
elif direction < 5:
# TODO: The ball is to the left of us. Turn to it.
pass
# continue ...
# Wait a bit before checking again.
time.sleep(0.05)
Here’s what it should look like, with motors and sensor info removed:
When direction < 5
, we want to move forward, but also to the left a bit. Something like left.on(20)
and right.on(60)
will probably work. Now just fill in direction == 5
and direction > 5
.
Advanced Usage
while
loops already open up a bunch of possibilities. For example, we can keep track of counters in our while
loops to run the indented code with different variable values:
1
2
3
4
5
6
7
x = 1
# Print x * x while it is <= 50.
while x * x <= 50:
square = x * x
print(f"{x}^2 = {square}")
x = x + 1
1
2
3
4
5
6
7
8
### Output ###
>>> 1^2 = 1
>>> 2^2 = 4
>>> 3^2 = 9
>>> 4^2 = 16
>>> 5^2 = 25
>>> 6^2 = 36
>>> 7^2 = 49
You can download the puzzle here.
After understanding the code above, you want to modify it to print 3 different sequences:
- Part 1 (Easy): Print the cube numbers
x * x * x
less than 100. (1, 8, 27, …)
Modify x * x <= 100
and square = x * x
.
- Part 2 (Medium): Print every second square number less than 100. (1, 9, 25, …)
Modify x = x + 1
so that on the second time we reach this point, x = 3
.
- Part 3 (Hard): Print all powers of 2 less than 200. (2, 4, 8, 16, …)
Modify x = x + 1
, so that on the i
th time we run this indented code, x has the value 2 to the power of i (2 * 2 * 2 ...
i
times).
Just like we found out with if
and else
, we can have while
loops within while
loops, by indenting further and further:
1
2
3
4
5
6
7
8
9
10
11
12
13
x = 1
# Repeat 3 times
while x <= 3:
y = 1
print(f"Here are the first {x} numbers!")
# Repeat x times
while y <= x:
print(y)
y = y + 1
x = x + 1
1
2
3
4
5
6
7
8
9
10
### Output ###
>>> Here are the first 1 numbers!
>>> 1
>>> Here are the first 2 numbers!
>>> 1
>>> 2
>>> Here are the first 3 numbers!
>>> 1
>>> 2
>>> 3
You can download the puzzle here.
Jacob the mathemagician is back! Although this time he isn’t really doing a trick, just reciting his times tables.
He has asked you to write a program to make this easier for him, so that Jacob can enter two numbers (x
and y
), and then the program prints the entire times table for numbers up to x
times numbers up to y
.
We had a loop before that let us repeat code x
many times. Using two loops, one inside the other, we can repeat the innermost code x * y
times, by having the outer loop repeat x
times and the inner loop repeat y
times:
1
2
3
4
5
6
7
8
counter1 = 1
while counter1 <= x:
counter2 = 1
while counter2 <= y:
# Do something with counter1 and counter2 here
# This will get run x * y times.
counter2 = counter2 + 1
counter1 = counter1 + 1
Well, since counter1
and counter2
are ranging from 1
to x
and 1
to y
, why not print counter1 * counter2
?
1
2
3
4
5
6
7
counter1 = 1
while counter1 <= x:
counter2 = 1
while counter2 <= y:
# print counter1 * counter2
counter2 = counter2 + 1
counter1 = counter1 + 1
You can download the puzzle here.
A classic and simple game to play is higher / lower. The computer will choose a number between 1 and 100, and you have to guess that number. You’ve been asked to implement this game for your friend Rachel.
You need to modify the code given so that it correctly prints either “Higher”, “Lower”, or “Correct”, based on the guesses given, until the user gets the number correct. After this, the program should exit.
Test the game with a few different values to make sure it works.
If you want to repeat something (like ask for guesses) until a certain condition is met, we need while condition:
.
When should condition
change? Should condition be a variable?
Create a variable, guessed_correctly = False
, to store whether player 2 has guessed the number yet. Then you can put most of the code inside a loop that starts with while not guessed_correctly:
Project time!
You can download the puzzle here.
You and your team has recently discovered that multiple different coloured aliens are inhabiting Mars! Luckily, you can send a rover over to investigate. These Aliens emit infrared waves (exactly the same as the ball earlier, what a coincidence!) that your robot can use to locate them.
Your task is to move right next to the alien, and identify whether they are red, green, blue or black. If the alien is red, green or blue, you should print this (“Alien is red!” or something), and then keep searching for the next alien.
If the alien is black, your program should then print how many red, green and blue Aliens you’ve seen, and then finish the program.
Provided you got that working, the next thing you need to worry about is detecting when you are close enough to the robot to detect it’s colour. Here are two options:
- Attach an ultrasonic sensor to your robot, and you are close to the robot when the sensor says you are less than ten centimeters away, or something like this.
- When the colour sensor does not read white as the current ground colour, you must be in one of the circles.
Use this to then record the colour of the robot, print it, and then continue searching.
Modifying the solution you had earlier, you might have the following snippet of code in your while loop:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
while ...
direction = ir_sensor.value(0)
...
if direction == 5:
# Straight ahead
is_close = # Do one of the checks outlined in Hint 2
if is_close:
left_motor.off()
right_motor.off()
r, g, b = color_sensor.rgb
if r > g + b:
print("Red!")
red_count = red_count + 1
elif ...# Complete this yourself.
# Wait a bit for the robot to start following again.
time.sleep(0.3)
else:
# Go forward
left_motor.on(60)
right_motor.on(60)