@comment(Hey, EMACS, this is -*- SCRIBE -*- input) @make(6001) @modify(excounter, numbered [Exercise @1], referenced [@1]) @PageHeading(even, left "@Value(Page)", right "6.001 -- Hewlett-Packard Summer 1985") @PageHeading(odd, Left "Problem Set 3", right "@value(page)") @begin(center) HEWLETT-PACKARD Summer 1985 Problem Set 3 Originally Prepared As MASSACHUSETTS INSTITUTE OF TECHNOLOGY Department of Electrical Engineering and Computer Science 6.001 Structure and Interpretation of Computer Programs Fall Semester, 1984; Problem Set 3 @end(center) @blankspace(0.25 in) @begin(figure) @blankspace(3 inches) @caption{Tiling patterns (made by Andy diSessa and Doug Hill). This assignment asks you to implement a graphics language for drawing figures such as these.} @tag(tiling-patterns) @end(figure) Write up and turn in the following exercises from the text: @begin(itemize) Exercise 2-3: Representing pairs as procedures Exercise 2-17: Defining @a[reverse] Exercise 2-18: @a[Square-list] Exercise 2-19: Debugging @a[square-list] Exercise 2-20: @a[Mapcar] Exercise 2-28: List structures Exercise 2-30: Extending the differentiation procedure @end(itemize) @section(Lecture Notes: The Square-Limit Language) The laboratory assignment for this week begins on page @pageref(laboratory-assignment) of this handout. For the assignment, you are to implement a variation of Peter Henderson's ``Escher Square-Limit'' language, which will be discussed in lecture on Thursday, September 27. This handout begins with a listing of the procedures that implement Henderson's language. The explanation given here is very minimal, since this will be covered in lecture. This simple graphics language is due to Peter Henderson, of Oxford University. One interesting feature of the implementation is that it represents figures as procedures. A ``picture'' is defined to be a procedure that takes a rectangle as input and causes something to be drawn, scaled to fit in the rectangle. A rectangle will be represented by three vectors: an origin, a ``horizontal'' vector, and a ``vertical'' vector. Any rectangle defines a linear transformation coordinate map by @begin(example) (1,0) --> horizontal (0,1) --> vertical @end(example) that is, a general point (x,y) gets mapped to the vector @begin(example) * + * @end(example) offset by the origin of the rectangle. We represent a rectangle by a list of three vectors: the location of the origin of the rectangle, the vector from that origin to the end of the ``horizontal'' axis, and the vector to the end of the ``vertical'' axis. @begin(example) (define make-rect list) ;; origin-vect H-vect V-vect (define origin first) (define horiz second) (define vert third) @end(example) Using this map, points with coordinates between 0 and 1 end up inside the rectangle. Here is a procedure that takes a rectangle as input and returns the corresponding coordinate transformation: @begin(programexample) (define (coord-map rect) (lambda (point) (+vect (+vect (scale (xcor point) (horiz rect)) (scale (ycor point) (vert rect))) (origin rect)))) @end(programexample) A picture is defined by a collection of line segments. Segments are represented as pairs of points, and points (vectors) are represented as pairs of numbers: @begin(programexample) (define make-vector cons) (define xcor car) (define ycor cdr) (define make-segment cons) (define seg-start car) (define seg-end cdr) @end(programexample) Given a rectangle as input, the picture draws the image of each line segment inside the rectangle. Here is a procedure that constructs the picture, given a list of segments: @begin(programexample) (define (make-picture seglist) (lambda (rect) (mapc (lambda (segment) (drawline ((coord-map rect) (seg-start segment)) ((coord-map rect) (seg-end segment)))) seglist))) (define empty-picture (make-picture '())) @end(programexample) To rotate a picture 90 degrees counterclockwise, we need only draw the picture with respect to a new rectangle: @begin(programexample) V --------------- --------------- H'=V | | | | | | | | ---o | | -------/ | ---> | | | | | | | | --------------- --------------- O H V'=-H O'=O+H (define (rotate90 pict) (lambda (rect) (pict (make-rect (+vect (origin rect) ;O' (horiz rect)) (vert rect) ;H' (scale -1 (horiz rect)))))) ;V' (define (repeated proc n) (lambda (thing) (if (= n 0) thing ((repeated proc (-1+ n)) (proc thing))))) (define rotate180 (repeated rotate90 2)) (define rotate270 (repeated rotate90 3)) @end(programexample) Horizontal flip is also drawing with respect to a new rectangle: @begin(programexample) V --------------- --------------- V'=V | | | | | | | | | | | -------/ | ---> | \------- | | | | | --------------- --------------- O H H'=-H O'=O+H (define (flip pict) (lambda (rect) (pict (make-rect (+vect (origin rect) (horiz rect)) (scale -1 (horiz rect)) (vert rect))))) @end(programexample) The @a[together] operation takes two pictures and combines them into a single picture, lying superimposed. @begin(programexample) (define (together pict1 pict2) (lambda (rect) (pict1 rect) (pict2 rect))) @end(programexample) The @a[beside] operation takes two pictures with relative widths and scales them to fit in a single rectangle. Input is percentage of horizontal devoted to the first picture @begin(programexample) (define (beside pict1 pict2 a) (lambda (rect) (pict1 (make-rect (origin rect) (scale a (horiz rect)) (vert rect))) (pict2 (make-rect (+vect (origin rect) (scale a (horiz rect))) (scale (- 1 a) (horiz rect)) (vert rect))))) @end(programexample) @a[Above] is defined in terms of @a[beside] @begin(programexample) (define (above pict1 pict2 a) (rotate270 (beside (rotate90 pict1) (rotate90 pict2) a))) @end(programexample) Here are some operations defined in terms of the basic ones above: @begin(programexample) (define (4pict pict1 rot1 pict2 rot2 pict3 rot3 pict4 rot4) (beside (above ((repeated rotate90 rot1) pict1) ((repeated rotate90 rot2) pict2) .5) (above ((repeated rotate90 rot3) pict3) ((repeated rotate90 rot4) pict4) .5) .5)) (define (4same pict rot1 rot2 rot3 rot4) (4pict pict rot1 pict rot2 pict rot3 pict rot4)) (define (up-push pict n) (if (= n 0) pict (above (up-push pict (-1+ n)) pict .25))) (define (right-push pict n) (if (= n 0) pict (beside pict (right-push pict (-1+ n)) .75))) (define (corner-push pict n) (if (= n 0) pict (above (beside (up-push pict n) (corner-push pict (-1+ n)) .75) (beside pict (right-push pict (-1+ n)) .75) .25))) (define (square-limit pict n) (4same (corner-push pict n) 1 2 0 3)) @end(programexample) The rectangle that defines the original screen of the Chipmunk is defined as: @begin(programexample) (define screen (make-rect (make-vector -190 -190) (make-vector 380 0) (make-vector 0 380))) @end(programexample) A useful test pattern is that outlining a rectangle: @begin(programexample) (define outline-picture (make-picture (list (make-segment (make-vector 0 0) (make-vector 0 1)) (make-segment (make-vector 0 1) (make-vector 1 1)) (make-segment (make-vector 1 1) (make-vector 1 0)) (make-segment (make-vector 1 0) (make-vector 0 0))))) @end(programexample) With the above, one could, for example, cause the outline rectangle to be drawn on the full (square) screen by: @begin(programexample) (outline-picture screen) @end(progamexample) The following procedure, which applies its argument to the screen after clearing the graphics screen, will save some typing. @begin(programexample) (define (draw pict) (clear-graphics) (pict screen)) @end(progamexample) The definitions below present a few simple (and not very interesting) patterns. To gain a better understanding of the square-limit language, you can play with them, using various combinations of the operators, and define new primitives. All the code in this description of the square-limit language may be loaded by using @b[load problem set] and specifying @a[problem set number] as @a[squares]. Note that this description (and code and suggestions) are for your edification only. There is nothing to hand in related to the square-limit language. @begin(programexample) (define wedge (make-picture (list (make-segment (make-vector 0 0) (make-vector .5 .5)) (make-segment (make-vector .5 .5) (make-vector 1 0))))) (define line (make-picture (list (make-segment (make-vector 0 .5) (make-vector 1 .5))))) (define star (make-picture (list (make-segment (make-vector 0 .5) (make-vector .5 0)) (make-segment (make-vector .5 0) (make-vector 1 .5)) (make-segment (make-vector 1 .5) (make-vector .5 1)) (make-segment (make-vector .5 1) (make-vector 0 .5))))) @end(programexample) @section(Laboratory Assignment: A Drawing Language Based on Triangles) @label(laboratory-assignment) For this assignment, you are to implement a language similar to Henderson's, except based on triangles rather than squares. We begin by describing the implementation, and listing the procedures that have been installed on the Chipmunk system for you to work with. The implementation of the language is very similar to the one described above. @paragraph(Points and segments) Points (vectors) are represented as pairs of numbers, and segments are represented as pairs of points: @begin(programexample) (define make-vector cons) (define xcor car) (define ycor cdr) (define zero-vector (make-vector 0 0)) (define make-segment cons) (define seg-start car) (define seg-end cdr) @end(programexample) We use will the operations of vector addition, subtraction, and scaling a vector by a number: @begin(programexample) (define (+vect v1 v2) (make-vector (+ (xcor v1) (xcor v2)) (+ (ycor v1) (ycor v2)))) (define (-vect v1 v2) (+vect v1 (scale -1 v2))) (define (scale x v) (make-vector (* x (xcor v)) (* x (ycor v)))) @end(programexample) We will also make use of an operation called @a[shift], which takes as arguments two vectors @a[o1] and @a[o2] and a vector @a[v] which is meant to represent a point relative to an ``origin'' @a[o1]. @a[Shift] returns a vector that represents the same point as @a[v], but with respect to the ``origin'' @a[o2]. See figure @ref(shift). @begin(programexample) (define (shift v o1 o2) (-vect (+vect v o1) o2)) @end(programexample) @begin(figure) @blankspace(3 inches) @caption(Shifting a vector from one origin to another.) @tag(shift) @end(figure) The following procedure takes 2 points as arguments and draws a line between them on the Chipmunk screen. @begin(programexample) (define (drawline start end) (position-pen (xcor start) (ycor start)) (draw-line-to (xcor end) (ycor end))) @end(programexample) @paragraph(Triangles) @begin(programexample) (define make-triangle list) (define origin car) (define side1 cadr) (define side2 caddr) @end(triangle) A triangle is represented as a triple of vectors -- an origin and two sides. See figure @ref(triangle). The origin will be represented as a vector whose coordinates give the coordinates of the origin with respect to some external coordinate system, such as the graphics screen coordinates. The two sides are specified as vectors which give the @i[offsets] of the other two vertices of the triangle from the origin point. In other words, they specify the coordinates of the other two vertices with respect to a coordinate system whose ``origin'' is at the @a[origin] of the triangle. As shown in figure @ref(triangle), we can express the points of a triangle in terms of so-called @i[triangular coordinates], which are pairs @a[x], @a[y] such that @a[x+y]@lessequal()1. Another way to say this is that each point of the triangle can be expressed as a vector @begin(example) x Side@-[1](Tri) + y Side@-[2](Tri) @end(example) where @a[x+y]@lessequal()1. Still another way to express this is that mapping the point in the plane whose coordinates are @a[x],@a[y] to the point given by @begin(example) Origin(Tri) + x Side@-[1](Tri) + y Side@-[2](Tri) @end(example) maps one-half of the unit square onto the given triangle. @begin(programexample) (define (coord-map triangle) (lambda (point) (+vect (+vect (scale (xcor point) (side1 triangle)) (scale (ycor point) (side2 triangle))) (origin triangle)))) @end(programexample) @begin(figure) @blankspace(3 inches) @caption(A triangle represented as origin and two sides; triangular coordinates.) @tag(triangle) @end(figure) Finally, we define a standard @a[screen-triangle], which is an isosceles triangle whose base and height are the edges of the Chipmunk screen: @begin(programexample) (define screen-lower-left (make-vector -190 -190)) (define screen-lower-right (make-vector 190 -190)) (define screen-upper-left (make-vector -190 190)) (define screen-lower-edge (-vect screen-lower-right screen-lower-left)) (define screen-left-edge (-vect screen-upper-left screen-lower-left)) (define screen-triangle (make-triangle screen-lower-left screen-lower-edge (+vect screen-left-edge (scale 0.5 screen-lower-edge)))) @end(programexample) @paragraph(Pictures) As in the square-limit language, a picture is a procedure, this time a procedure on triangles, which draws inside the given triangle. @a[Make-picture] constructs a picture from a list of segments: @begin(programexample) (define (make-picture seglist) (lambda (triangle) (mapc (lambda (segment) (drawline ((coord-map triangle) (seg-start segment)) ((coord-map triangle) (seg-end segment)))) seglist))) @end(programexample) We will also define some simple pictures to use as drawing elements: the empty picture, a picture that just outlines the triangle, one that connects the center of the triangle to the midpoints of the sides, one that draws a ``band'' across the triangle, and one that draws a ``V'' shape. Figure @ref(sample-pictures) shows how these figures are specified in terms of triangular coordinates. @begin(programexample) (define empty-picture (make-picture '())) (define outline-picture (let ((v1 (make-vector 0 0)) (v2 (make-vector 0 1)) (v3 (make-vector 1 0))) (make-picture (list (make-segment v1 v2) (make-segment v2 v3) (make-segment v3 v1))))) (define midpoints (let ((center (make-vector (/ 1 3) (/ 1 3))) (m1 (make-vector (/ 1 2) 0)) (m2 (make-vector 0 (/ 1 2))) (m3 (make-vector (/ 1 2) (/ 1 2)))) (make-picture (list (make-segment m1 center) (make-segment m2 center) (make-segment m3 center))))) (define band (let ((a1 (make-vector .4 0)) (a2 (make-vector .6 0)) (b1 (make-vector 0 .4)) (b2 (make-vector 0 .6))) (make-picture (list (make-segment a1 b1) (make-segment a2 b2))))) (define v-shape (let ((m1 (make-vector (/ 2 9) (/ 2 9))) (m2 (make-vector (/ 4 9) (/ 4 9))) (a1 (make-vector (/ 1 3) 0)) (a2 (make-vector (/ 2 3) 0)) (b1 (make-vector 0 (/ 1 3))) (b2 (make-vector 0 (/ 2 3)))) (make-picture (list (make-segment a1 m1) (make-segment m1 b1) (make-segment a2 m2) (make-segment m2 b2))))) @end(programexample) @begin(figure) @blankspace(4 inches) @caption(Sample pictures, specified using triangular coordinates.) @tag(sample-pictures) @end(figure) @paragraph(Means of combination) We define a means of combination, called @a[split], which takes as arguments two pictures and a @a[ratio] between 0 and 1. When given a triangle as argument, the @a[split] picture first splits the triangle by dividing @a[side1] of the triangle as specified by the @a[ratio]. Then it draws one picture in each subtriangle, as shown in figure @ref(split). @begin(figure) @blankspace(3 inches) @caption(The @a[split] combination of two pictures.) @tag(split) @end(figure) The procedure for @a[split] must compute the new origin and sides for each of the two component triangles. @begin(programexample) (define (split pict1 pict2 ratio) (lambda (triangle) (define p (scale ratio (side1 triangle))) (define oa (origin triangle)) (define ob (shift p oa zero-vector)) (pict1 (make-triangle oa p (side2 triangle))) (pict2 (make-triangle ob (shift (side1 triangle) oa ob) (shift (side2 triangle) oa ob))))) @end(programexample) Observe how @a[split] computes the two subtriangles, so that these can be handed to the appropriate @a[pict1] and @a[pict2]: We first compute the vector @a[p], which represents be point on the side where the triangle is to be split. (Note that @a[p] will be given as a vector with respect to the origin of the triangle to be split.) The first subtriangle has origin @a[oa] the same as the original triangle, @a[side1] given by @a[p], and @a[side2] which is the same as the @a[side2] of the original triangle. The second subtriangle has an origin @a[ob] at the point designated by @a[p], and the other two vertices at the same points as the original triangle. However, according to our representation of triangles, we must express the origin in ``external'' (screen) coordinates, and express the other two vertices as offsets from the origin of the triangle. This is where @a[shift] comes in handy: With respect to the origin @a[oa] of the original triangle, the origin of our second subtriangle is given by the vector @a[p]. So to find the coordinates for @a[ob], we @a[shift] @a[p] from the origin @a[oa] to an origin at the zero-vector. Similarly, we can find the correct @a[side1] and @a[side2] for the second subtriangle by using the fact that these are the vectors running from @a[ob] to the endpoints of the sides of the original triangle: With respect to the @i[old] origin @a[oa], these are just the vectors @a[side1] and @a[side2]. So to find the offsets from the @i[new] origin @a[ob], we @a[shift] @a[side1] and @a[side2] from @a[oa] to @a[ob]. @a[Split] illustrates a useful general method for specifying new triangles: Find the vertices of the triangle with respect to some fixed coordinate system (such as the triangle that is being decomposed); then use @a[shift] to express points in the external coordinate system (this is required when specifying an origin for a new triangle), or as offsets from a new origin (this is required when specifying the sides of a new triangle). @section(To do in lab) Begin by installing the code for problem set 3 on your floppy disk, using the @a[load-problem-set] operation documented in the Chipmunk manual. The file to be loaded contains all of the code listed above in section @ref(laboratory-assignment). You will not need to edit or modify any of it. The ``modifications file'' for problem set 3 that will be installed on your disk is an empty file, since for this problem set you will be writing new procedures, rather than modifying procedures that we supply. @paragraph(Part 1) Draw some pictures to test the procedures. To draw a picture, clear the graphics screen by calling @a[clear-graphics], and run the picture (procedure) using @a[screen-triangle] as an argument. Use @a[split] to make various combinations of the pre-defined elementary pictures. @paragraph(Part 2) Use @a[split] to define an operation called @a[push] which is analogous to @a[right-push] of the square-limit language. Test your procedure by trying it out on some of the simple figures provided. Turn in a listing of the procedure. @paragraph(Part 3) For this part, you are to define a @a[rotate] operator on pictures. The rotation of a picture draws the picture in the specified triangle, but ``rotated'' so that a different vertex of the triangle is taken as the origin. See figure @ref(super-and-rotate). Note that the test pictures @a[outline-picture] and @a[midpoints] won't look any different when rotated, so you should use @a[v-shape] or @a[band] to test your procedure. Observe that, using @a[rotate], you can also rotate a picture ``in the opposite direction'' by applying @a[rotate] twice, using @a[repeated]. (Compare the rotation operators in the square-limit language.) Turn in listings of the @a[rotate] procedure. @begin(figure) @blankspace(3 inches) @caption(Rotating and superimposing pictures.) @tag(super-and-rotate) @end(figure) @paragraph(Part 4) Define an operator @a[three-fold] which, given a picture, produces the superposition of three pictures -- the original picture, the picture rotated once, and the picture rotated twice. (See figure @ref[super-and-rotate].) Test your procedure by defining @a[3v] and @a[3band] to be the three-fold superpositions, respectively, of @a[v-shape] and @a[band]. @paragraph(Part 5) Figure @ref(3split) illustrates a means of combination called @a[3split], which takes as its arguments three pictures and two numbers @a[r] and @a[s], such that @a[r+s <= 1]. It combines the pictures as shown in the figure. (The orientation of the small triangles is unspecified -- in implementing @a[3split] you can choose whichever orientations you wish.) For this part, you are to implement @a[3split] and turn in a listing of your procedure. This procedure can be rather tricky, especially if you are not used to expressing points in terms of vectors, so think about it before you start coding. You can also make good use of the @a[shift] procedure, as in the implementation of @a[split] discussed above. Also, a good way to test your procedure is to run it with all four pictures as the @a[outline-picture], and to draw the result. @begin(figure) @blankspace(3 inches) @caption(Using @a[3split] to combine 3 pictures.) @tag(3split) @end(figure) @paragraph(Part 6) The following @a[equal-split] procedure uses @a[3split] to replicate a single picture 3 times, splitting the original triangle at the point where the medians intersect (i.e., the point who triangular coordinates are (1/3, 1/3). @begin(programexample) (define (equal-split pict) (3split pict pict pict (/ 1 3) (/ 1 3))) @end(programexample) We could imagine a generalization of @a[equal-split], which uses the same picture three times, but with each of the small triangles rotated by applying @a[rotate] 0, 1, or 2 times, as specified. One way to do this is to define a procedure similar to @a[equal-split], but which takes as arguments a picture, and also three numbers -- each of which is 0, 1, or 2 -- that specify the number of times to repeat the @a[rotate] operation in generating each picture in the small triangles. (You can use @a[repeated] to accomplish the repetitions.) An alternative method is to use a higher-order procedure, which, given the three rotation numbers, will generate a procedure that takes a picture as argument. In other words, if we call this higher-order procedure @a[make-splitter], then writing @begin(programexample) (define equal-split (make-splitter 0 0 0)) @end(programexample) should be an equivalent way to define @a[equal-split], and @begin(programexample) (define equal-split (make-splitter 1 0 2)) @end(programexample) should produce a procedure that is like @a[equal-split], but which rotates the first and third small picture before drawing them. For this part, you are to implement and turn in a listing of the @a[make-splitter] procedure. As a hint, notice the @a[make-splitter] will have the following form: @begin(programexample) (define (make-splitter r1 r2 r3) (lambda (pict) ...)) @end(programexample) As a test, try drawing @begin(programexample) (define mesh ((make-splitter 0 1 2) band)) @end(programexample) Turn in a listing of @a[make-splitter]. @paragraph(Part 7) Suppose we use @a[3split] recursively: split a picture which is itself a split, and so on. Here is an extended version of the @a[equal-split] procedure given above, which performs this splitting to a specified number of levels: @begin(programexample) (define (rec-equal-split pict levels) (if (= levels 0) pict (let ((p (rec-equal-split pict (-1+ levels)))) (3split p p p (/ 1 3) (/ 1 3))))) @end(programexample) Now suppose we want to generalize this procedure as in part 6, that is to have a higher-order procedure that will create things like @a[rec-equal-split], except where the splitting ratios of the sides can be other than one half. We want a procedure @a[make-rec-splitter], such that @a[rec-equal-split] could be defined as @begin(programexample) (define rec-equal-split (make-rec-splitter (/ 1 3) (/ 1 3))) @end(programexample) Test your procedure by drawing some figures, and turn in a listing. @paragraph(Part 8) The procedures you have implemented give you a wide choice of things to experiment with. For example, even if you specify that you will start with a single picture element, and perform a @a[3split] to three levels using a fixed splitting point (@a[r], @a[s]), there are still a tremendous number of variations, since for each @a[3split] you can choose to rotate each of the component pictures. Thus, each level gives you 9 choices (three orientations for each of three pictures) and all three levels thus produces 9@+[3] possibilities, although many of these will be the same, due to symmetry of the component figures. There is nothing to turn in for this part of the assignment, but we hope you will spend some time experimenting. The patterns in figure @ref(tiling-patterns) were produced in just this way, using picture elements similar to the ones you have here. What kinds of patterns can you discover?