A Radial Series Code

In the last post, I introduced a series of images that were created for our introductory course’s final project. In this post, I’m going to walk through the student’s code and point out things they did particularly well—and things that could be improved.

The student’s source code can be viewed here.

STYLE

First, I can’t resist a lecture on documentation. Not only has this student provided the purposes of each of her functions, she’s also given us headers, helpful comments and an appendix at the end. Even though documenting can be a pain when you’re trying to work through the code, it saves you time in the long run when you come back to something you’ve written before and try to make sense of it (you’ll see what I mean when I try to work through my final project code in a later post).

A good example of documentation for the select-polygon procedure, using the 6 P’s:

;;; Procedure:
;;;   select-polygon!
;;; Parameters:
;;;   image, an image
;;;   center-x, a real number
;;;   center-y, a real number
;;;   sides, an integer
;;;   side-length, a real number
;;;   rotate-angle, a real number
;;; Purpose:
;;;   create a selection within "image"
;;; Produces:
;;;   nothing; selects an area within "image"
;;; Preconditions:
;;;   sides >= 3
;;;   side-length >= 1
;;; Postconditions:
;;;   part of "image" is selected
;;;   the selection is centered at ("center-x", "center-y")
;;;   the selection has "sides" number of sides
;;;   each side is "side-length" pixels in measure
;;;   the selection is rotated "rotate-angle" degrees from its default orientation
;;;      at the default orientation, one interior angle is bisected by the line y = "center-y"
;;; Notes:
;;;   "select-polygon!" functions as intended with non-integer and non-exact values of center-x, center-y, side-length, and rotate-angle
(define select-polygon!
  (lambda (image center-x center-y sides side-length rotate-angle)

The second thing this student does well is use helper procedures. From the coder’s perspective, helper procedures allow for faster coding because actions can be repeated without retyping bits of code. Consider the second procedure, which determines the digit in the hundreds place for n (e.g. if n is 783, second returns 7). It’s a relatively simple function, but she calls it 12 times within the image-series procedure. If she had chosen not to make it a helper function, her code would have been clustered, difficult for both the viewer and programmer to understand.

To simplify the code even further, the student could have added a variable for (second n) to the let statement since the value of (second n) was consistent throughout the call to image-series. This strategy would have made the code more efficient since it wouldn’t have to call the second procedure each of the 12 times its result is needed—it would only have to invoke the procedure once and store it into a variable for later.

Another advantage of helper functions is the ability to copy and paste your helper procedures into other programs if you want to reuse them. If the student was writing another program and wanted to access the second digit of a number again, she would only have to refer back to her previously written code and copy the second procedure over to the new program.

Here’s an example of the helper procedure second:

;Determine the second digit of a 4-digit number
(define second
  (lambda (n)
    (string->number (list->string (list #\0 (string-ref (convert n) 1))))))

TECHNIQUES

In the introductory post for this series, I talked about how the student made good use of code to rotate, scale and repeat images. In this section, I’m going to talk about how the student coded each of these actions.

Rotation

To rotate each of the polygons a set number of degrees each time a new shape is drawn, the student uses three procedures.

The first procedure, select-polygon, creates a polygon on ‘image’ with its center at (center-x, center-y). The polygon has side lengths defined by the parameter side-length and if  rotate-angle does not equal 0, the polygon is rotated that many degrees around the center point (e.g. if rotate-angle equals 10, the polygon is rotated 10 degrees from center).

(define select-polygon!
  (lambda (image center-x center-y sides side-length rotate-angle)
      (image-select-polygon! image REPLACE (point-list (merge (x-coords center-x sides side-length rotate-angle) (y-coords center-y sides side-length rotate-angle) null)))))

Already we see how easily images can be rotated—just change the ‘rotate-angles’ parameter. For example, we could write:

(select-polygon! image 50 50 7 25 0)

(select-polygon! image 50 50 7 25 25)

(select-polygon! image 50 50 7 25 50)

This would create 3 congruent polygons, each rotated 25 degrees apart with the first polygon centered on the canvas. Yet if we want 100 different rotated polygons (just look at how many appear in the header), even this solution is tedious. Thus we need another procedure that will change the rotate-angle value for us for a set number of polygons.

The procedure the student created for this purpose is called recurse. The recurse function takes the same parameters as select-polygon, but adds a few more so that the caller can alter the copies even further.

;Recurses to create multiple polygons with desired differences in characteristics
(define recurse
  (lambda (image center-x center-y sides side-length rotate-angle color1 color2 type copies
                 center-ratio length-ratio rotate-amount)
    (if (> side-length 2)
        (when (> copies 0)
          (make-polygon! image center-x center-y sides side-length rotate-angle color1 color2 type)
          (recurse image (* center-ratio center-x) (* center-ratio center-y) sides (* length-ratio side-length) (+ rotate-amount rotate-angle) (rgb-lighter color1) (rgb-darker color2) type (- copies 1)
                   center-ratio length-ratio rotate-amount))))) 

Recurse takes a parameter called copies and generates that many polygons, changing each successive polygon by the options specified in the parameters (color, number of sides, etc.). The make-polygon! procedure is similar to select-polygon, but it fills the selected polygon with color—I won’t go into detail here.

The only parameter we’re concerned about for this section is rotate-amount, which, as we see in the highlighted lines, is added to the rotate-angle of the previous polygon. This sum becomes the rotate-amount for the next polygon drawn. To use recurse to generate the same polygons from our example above, we would call the function with 3 as the copies parameter and 25 as the rotate-amount, since each successive polygon is 25 degrees apart.

Now we know how to draw a rotated polygon and make copies of it that are rotated y degrees further: the solution in code is much quicker than its alternative by hand. Instead of copying and pasting however many polygons we want and then rotating the polygons (a process that can be frustrating when multiple shapes are very close together), we simply call our function. Not only are our rotation amounts exact (i.e. not generated by the frustrating ‘drag and drop’ rotation tool of most image-manipulation softwares), all of our polygons are automatically superimposed upon each other and do not need further alteration.

Scaling and Coloring

Similar steps can be taken to scale each copy of the polygon. In the recurse procedure above, the parameters side-length and length-ratio function like rotate-angle and rotate-amount, respectively. Each copy of the first polygon has sides-lengths given by (side-length * length-ratio), where side-length is the length of the previous copy’s sides. This would be similar to writing

(select-polygon! image 50 50 7 100 0)

(select-polygon! image 50 50 7 50 0)

(select-polygon! image 50 50 7 25 0)

if we used our inefficient strategy from above. Note how the scaling is consistent: each successive polygon is half the size of the one before it. This pattern indicates that we can use use recurse to generate the same polygons for us, by calling the function with 3 as the copies parameter, 100 as the side-length (the length of the first polygon), and 0.5 as the length-ratio.

For filling the polygons with color, we only need to specify the color of the original polygon. We don’t need a second parameter for color amount since it’s already written into the Scheme library: the bit of recurse that says (rgb-lighter color1) calls a procedure that has a preset value for the amount to lighten the color. Thus a way to systematically lighten each successive polygon is already included within the Scheme library.

We’ve learned how to make x copies of a polygon and rotate, scale and lighten each successive copy. These 4 tasks are at the heart of the student’s strategy for making images. If we return to one of the examples from the post, we see how each image uses this strategy, and we can imagine how tedious it would have been to implement by hand.

Advertisement

One thought on “A Radial Series Code

  1. Pingback: Circles in Space Code « Media Scripting

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s