How to Keep Doing Things Until You Need To
Computers are fast calculating machines. They manage to accomplish very simple tasks very quickly. A complex task is just a big amount of simple tasks very well organized. Organized how? Through programming. In this chapter we will learn how to instruct Python –and DrawBot– to accomplish repetitive commands without making our instructions themselves repetitive. Follow me!
In chapter 5, we have seen how to draw basic shapes on the DrawBot canvas. As you remember, we can draw a line using:
newPage(100, 100) stroke(0) line((0, 20), (100, 20))
Given that we have to specify numerically the position of each shape on the canvas, drawing a pattern could be very boring. Let’s see what does mean to draw a pattern of crossed lines in this way…
newPage(100, 100) stroke(0) # vertical line((10, 0), (10, 100)) line((20, 0), (20, 100)) line((30, 0), (30, 100)) line((40, 0), (40, 100)) line((50, 0), (50, 100)) line((60, 0), (60, 100)) line((70, 0), (70, 100)) line((80, 0), (80, 100)) line((90, 0), (90, 100)) # horizontal line((0, 10), (100, 10)) line((0, 20), (100, 20)) line((0, 30), (100, 30)) line((0, 40), (100, 40)) line((0, 50), (100, 50)) line((0, 60), (100, 60)) line((0, 70), (100, 70)) line((0, 80), (100, 80)) line((0, 90), (100, 90))
The code works, but writing it was not fun, right? Using identifiers we could avoid to type many times the same numbers, but this way we still have to invoke too many times the same function.
Let’s say we now decide to get this pattern a little darker. There are two options: to make the lines thicker or to increase the amount of lines. Now the issue now is: if we want to squeeze a couple of lines extra in our pattern, we have to edit almost every line of the previous code. This is not convenient at all. Luckily Python offers a synthetic and elegant syntax to instruct iterative tasks to the interpreter typing as little as possible. Consider the possible alternative:
canvasSide = 100 gap = 10 newPage(canvasSide, canvasSide) stroke(0) currentPos = gap while currentPos < canvasSide: line((0, currentPos), (canvasSide, currentPos)) line((currentPos, 0), (currentPos, canvasSide)) currentPos += gap
This solution brings a number of benefits:
- it is compact and therefore elegant
- if we need to change the amount of shapes we only need to edit a little
- it is far more explicit concerning our intentions (drawing a pattern of lines, not just a bunch arbitrary lines)
- it is generally easier to edit, scale and reuse
Python offers two different iteration constructs. We have just seen the
while syntax in action, which is similar to the conditional construct (
else) we have presented in the last chapter. This construct allows a general repetition based upon the evaluation of a boolean expression. We could define it a “conditional iteration”: keep doing this, until it’s necessary.
The syntax is the following one:
while condition: body
According to the colon syntax explored in the previous chapter, the body is considered as such if:
- the condition is followed by a colon
- the lines which form it are indented four spaces rightwards (without exceptions)
while loop begins with the evaluation of the condition following the
while keyword. If the boolean expression evaluates to
True, the body of the loop is performed. After the body execution, the condition is tested again. If the result is
True, the body is performed, again. As soon as the condition evaluates to
False, the body is skipped and the interpreter continues its journey beyond the body of the loop. Take into account that, until some action into the body does not change the state of the condition, the loop will keep going. FOREVA.
Python provides two protected keywords to control the flow of an iteration:
break statement breaks out of the innermost enclosing the while loop (but also the for loop which we will encounter soon). Let’s consider:
index = 0 while True: index += 1 if index == 20: break
As soon as
20, the body of the conditional construct (
if) is executed. The break statement stops the loop and the interpreter continues just after the
while body. Consider this piece of code just as an example, since it’s better to evaluate as much as possible into the
index = 0 while index <= 20: index += 1
Writing a fully functioning while block could be tricky at the beginning of your learning path. In fact, if the
while body does not change somehow the
while condition, your loop will run endlessly. If this is not your desired output, this is quite bad because you will have to force quit DrawBot. There is no other way from within the application to stop the interpreter before the end of a running script. A good solution for this issue could be to use a parachute technique.
A parachute is a temporary safety measure which stops your interpreter from looping endlessly making use of a break statement. Consider the following example:
safetyLimit = 200 # this value is arbitrary parachute = 0 while True: # this will loop endlessly parachute += 1 if parachute == safetyLimit: print("it's time to open the parachute") break
Then, once your
while block proves to be working properly, you can get rid of the parachute.
continue statement is used to skip part of the code into the body for the current iteration only. The loop does not terminate but continues to the next iteration of the loop.
index = 0 while index <= 20: index += 1 if index % 2 == 0: print('found even number') continue print('found odd number') print('outside the while loop')
Try to cover the entire canvas with a striped black & white pattern. The user should be able to control the density of the pattern and the direction of lines (horizontal or vertical)
Try to cover the entire canvas with a zig-zag line. The line moves horizontally starting at top left canvas vertex. Allow the user to control the density of wave lines.
Create a 200x200pt canvas. Draw a sequence of squares from top to bottom using a while loop. The square side should be adjustable through a variable. Odd squares should be light gray, even tiles should be dark gray.
Extend the previous exercise. Nest two while statements and draw a pattern made of squares across the entire canvas. Try to get a chessboard effect.
Write a program able to cover a rectangle (so, not necessarily the canvas) with vertical black lines. The user should be able to control the total coverage of the rectangle (from 0% to 100%) and line thickness. The step between each line should be calculated according to these variables. A nice plus would be to have the first and the last line of the pattern touching the edges of the rectangle without any overflow. You are free to allow some rounding to get a better visual result
Draw a leaf white shape using only black lines.
Extend the previous exercise: draw a white circle using only black straight lines.
Create a sequence of horizontal gradients. You cannot use any automatic gradient function, try to achieve this visual effect through separate shapes. The gradients should have alternate direction.
Create a pattern of horizontal lines. The lines should be thicker in the middle as you can see in the example image. Allow the user to control the density of the pattern, the maximum value of the thickness and a canvas margin.
Your goal is to draw a boustrophedonic (from left to right and from right to left in alternate lines) sequence of ovals. Start from the lower left canvas corner and alternate ovals color (dark gray, light gray). Increase the oval radius according to its position within the drawing sequence of the line (little at the beginning, larger at the end). The vertical and horizontal distance between the ovals is constant. Give the user the option to set a margin value for the canvas and try to use only one while construct. You can solve it avoiding two nested while loops, a basic while loop is sufficient.
Write a program able to draw a ruler of a specific measure unit (either PostScript points or millimeters) across the entire canvas. The ruler ticks should be of different lengths according to a given rhythm. Use a while loop. Numbers displayed along the ruler are a plus.