Homework 5a - Quilt

For this project, you'll combine decomposition with code that draws lines, ovals etc. to make some neat patterns. All parts of HW5 are due Wed Jan 17th 11:55 p.m. Due to the Mon holiday, the LaIR is closed Sun eve, but is open starting Mon eve.

Warmups: "parsehw" on Server

We have a small warmup section this week, concentrating on the nested-string-loop pattern. On the Experimental server, look at the "parsehw" section, and do the three problems in there to get started. The "binary2" problem depends on the example we will do in Friday lecture.

Warmups Turn In Turn In Warmups to Paperless


To get started, download quilt.zip. Open that .zip file to get a "quilt" folder containing the quilt.py file for this program. Open the quilt folder with the PyCharm open... menu command to get started.


For the quilt program, we provide working main() function. Depending on the command line arguments, main() calls one or another of your functions as shown below.

The quilt drawing is made of three different "patch" drawings: rects, bars, and eye. Each patch type is drawn by its own function, and these functions are the first three parts of the project.

a. draw_rects()

def draw_rects(canvas, left, top, width, height):

This patch is just made of two rectangles - a blue one in back with a smaller yellow one in front.

When you run your quilt.py program from the command line, and the first arg is '-rects', the provided main() calls your draw_rects() function, passing in the width/height numbers typed on the command line.

To better test what your functions, main() divides the window into 4 quadrants, and calls your draw_rects() function once to draw in the upper left quadrant, and a second time to draw in the lower right quadrant. Close the graphics window to exit the python program when done.

$ python3 quilt.py -rects 300 200

alt: blue and yellow rects

Draw the first, blue rectangle with its upper left corner at the position specified by the left, top parameters, and its size from the width, height parameters. Color it color='darkblue'.

Then fill a second rectangle, its upper left corner width/4 to the right and height/4 below the corner of the first. The second rectangle should be color 'yellow', with width and height half the values of the first rectangle. So in effect, the yellow rectangle is one quarter the area of the blue rectangle, and centered over it.

Viewed another way, this function has parameters left, top, width, height, each parameter representing a number that is passed in to this function by its caller. The parameters hold the numbers which specify what the function is supposed to do. Your code uses the numbers in those parameters to position and size the rectangles.

alt: two rects

Divide And Conquer aside: you don't want to type in a big program (or any large project) and then test the whole thing at the end. The best practice is to proceed in smaller pieces — build a part of it, and test that part right away. Then build and test the next part, and so on. CS106A project writeups naturally follow this best practice, talking about project sub-parts, and "milestone" tests of each part as you gradually build up the whole program. These graphics functions do not lend themselves to Doctests, so we are testing them by running them individually and looking at the output.

b. draw_bars()

def draw_bars(canvas, left, top, width, height, n):

This function draws a rectangle and then a series vertical bars (see examples below).

1. Draw a rectangle frame on the passed in canvas with the rectangle's upper left corner at the given left,top and extending the given width and height. Draw the rectangle with color='lightblue'.

2. Draw n black vertical lines on the canvas. The first line should be on the left edge of the patch, covering the left side line of the border rectangle. The last line should be at the right edge, covering the right side line of the border rectangle. All the other lines should be evenly spaced between the 2 sides. The n parameter is guaranteed to be at least 2.

Here is a command line run of the program to draw the bars patch, 300 pixels wide, 200 pixels hight, with n of 10:

$ python3 quilt.py -bars 300 200 10

alt: quilt 10 bars 300x200

Here is a run of the same size, but asking for 17 bars

$ python3 quilt.py -bars 300 200 17

alt: quilt 17 bars 300x200

And here is a run asking for 8 bars with patch size 200x200

$ python3 quilt.py -bars 200 200 8

alt: quilt 8 bars 200x200

c. draw_eye()

def draw_eye(canvas, left, top, width, height, n):

1. Draw the lightblue border rectangle, the same as for the bars.

2. Draw a yellow oval, with the upper left of its bounding box at left,top and extending for width,height. The oval should just barely cover up the border rectangle on the four sides.

3. Draw n black lines starting from the center of the oval. Use int div // to get an exact x,y for the center of the oval. Extend each black line to n points on the bottom edge of the border rectangle, distributed along the edge evenly in the same way as the bars patch.

The following command line calls your draw_eye(), 300 x 200, 10 lines:

$ python3 quilt.py -eye 300 200 10

alt: quilt eye 300x200 10 lines

Milestone: 3x Patches

Build and debug your code until it can draw all 3 patch types correctly for various sizes and values of N.

d. Quilting Bee

The last part uses decomposition to build the whole quilt. See the similar example in lecture.

The draw_quilt() function creates a canvas of the passed in width and height. The quilt will be made of an n-by-n grid of sub-rectangles, each the same size. We'll call the width and height of each sub-rectangle the sub_width and sub_height.

Compute the sub_width and sub_height for the quilt. For example if the overall canvas is 800 pixels wide and n is 11, the sub_width is 88, i.e. 800 divided by 11, rounding down is 88. Use the int division // operator.

Many times we have written a nested y/x loop to go over all the pixels in an image. In this case we want a nested loop over the row/col numbers of the patches, e.g. row=0 is the top row of patches, row=1 is the next row of patches and so on.

    # loop over rows and columns of patches
    for row in range(n):
        for col in range(n):
            # Draw one patch of the grid of patches

What is the x,y pixel coordinate of upper left corner of the patch at row,col? What is its width and height? What do you do to draw a patch there?

Milestone: Eye Test

Inside the row/col loop, call draw_eye() to draw an eye for every patch in the quilt to test that the row/col logic is working. Below is the command line to call your draw_quilt() function, here creating a 500 x 300 canvas with n of 5. That creates 5 x 5 grid of patches, and each patch also uses n of 5 (we're using n for two things here - the number of patches, and the internal n of each patch).

$ python3 quilt.py -quilt 500 300 5

alt: quilt drawing 5 x 5 grid of eye

Patch Rotation

We want a scheme to draw a different patch type in every spot in the grid. There is a standard programming trick for this situation, where we want to cycle through patches like: rects, eye, bars, rects, eye, bars, rects, ...

Inside the loops, compute a choice int from the row and col numbers like this:

    choice = (row + col) % 3

The % modulo operator divides by 3 and returns the remainder. The result of % n is always in the range 0, 1, .. n-1. So in the nested loops choice will run through a pattern of the numbers 0, 1, 2.

Use choice to rotate between the different patch types. We will go with the order: rects, eye, bars, since it looks the best. Change the inside of the row/col loop: if choice is 0, draw rects for that patch, if choice is 1 draw eye, and if choice is 2 draw bars. That is the deliverable state for your quilt program.

Here is the command line to run draw_quilt():

$ python3 quilt.py -quilt 600 400 6

alt: quilt 6 x 6 grid of patches

Here's a quilt with a relatively low value of n=3 giving it a more abstract look. One is reminded of our earlier research in area of art suitable for hotels.

$ python3 quilt.py -quilt 400 400 3

alt: quilt 3 x 3 grid of patches

All Done

As a final test, try a nice big quilt like this

$ python3 quilt.py -quilt 1200 800 10


When all of the code is working, you should be able to draw a quilt with various sizes and various values of n. This program puts together loops, applied math, and drawing, and leverages decomposition for sub-problems.

Turn in - hold on to your quilt.py to turn in with part (b).


Acknowledgements: Stanford senior lecturer Julie Zelenski (teaches CS106B) created the original "quilt" assignment, with different quilt-square types, each drawn by its own function, as a graphical way to play with decomposition. (see the Nifty Assignments archive) Nick Parlante created this version, adapting the ideas to Python, proportionate drawing, and the command-line material for testing milestones.