Functional Problem Solving (CSC 151 2016S) : Assignments
Primary: [Front Door] [Schedule] - [Academic Honesty] [Disabilities] [Email] - [FAQ] [Teaching & Learning] [Grading] [Taking Notes] [Rubric]
Current: [Assignment] [EBoard] [Lab] [Outline] [Reading]
Sections: [Assignments] [EBoards] [Labs] [Outlines] [Readings] - [Examples] [Handouts]
Reference: [Setup] [Remote] [VM] [Errors] - [Functions A-Z] [Functions By Topic] - [Racket] [Scheme Report (R5RS)] [R6RS] [TSPL4]
Related Courses: [Curtsinger (2016S)] [Davis (2013F)] [Rebelsky (2015F)] [Weinman (2014F)]
Misc: [SamR] [Glimmer Labs] [CS@Grinnell] [Grinnell] - [Issue Tracker (Course)]
In helping students with their projects, we've developed a number of techniques that other students may find useful. This document gathers some of those techniques.
You may have noticed the drawing->image always
produces a new image on which the drawings are rendered. For their
project images, many students wish to render a drawing onto an
existing image, which can be done with the procedure
(
drawing-render!
drawing image)
image-transform! and image-compute
We've seen two kinds of procedures that work with the pixels in an
image. The procedures image-transform! and
image-variant grab the color of
each pixel and apply a function to that color. In contrast,
image-compute grabs the position
of each pixel and builds each pixel from that position. But what if
we want to do both? For example, what if we want to transform colors
differently based on their position? For that purpose, we provide
the procedure image-redo!, which takes two
parameters, an image and a function of three parameters (col, row, and
color), and applies the function at each pixel.
While you can transform a portion of an image using
image-redo!, you may find it easier to select
part of the image and use image-transform!, as
image-transform! only affects the portion of an
image that is selected. (image-redo! should work
similarly.)
As you may have noted, image-select-rectangle! and
image-select-ellipse! will not work unless the width
and height of the area selected are at least 1.
>(image-select-rectangle! 80 REPLACE 100 200 0.5 0.5)image-select-rectangle!: width and height must be at least 1
When writing recursive procedures that select areas, you may end up with a width or height that is too small. You have two obvious solutions: (1) You can avoid the selection (and corresponding actions) altogether or (2) You can ensure that any selection you do has a width and height of at least one.
You can use when to ensure that you only call the
selection procedure (and anything else) when
width and
height are at least 1. For example,
(when (and (>= width 1.0) (>= height 1.0)) (image-select-rectangle! canvas REPLACE left top width height) (image-fill! canvas)
When we simply need to use 1 when width or
height is smllaer than 1, we can use the old
trick of using max.
(image-select-rectangle! canvas REPLACE left top (max width 1) (max height 1)) (image-fill! canvas)
Right now, if you try to stroke or fill something, and there's nothing selected on the image, you get an error message or inappropriate behavior.
>(image-select-nothing! canvas)>(image-stroke! canvas)gimp-edit-stroke: failed to execute
What's the solution?
You can check whether anything is selected with the
image-has-selection? procedure.
(when (image-has-selection? canvas) (image-stroke! canvas)
The current implementation of turtles is relatively slow to accommodate
the possibility that each turtle has a different brush. If you are
willing to have the turtle use the current brush and color (that is,
the ones you normally get when you just do image-draw-line!), you can use the following alternative implementation of the
turtle-forward! procedure, which seems to be
about eight to ten times faster. (No, you don't need
to understand how it works; it adds a very different model of
computation.)
;;; Procedure:
;;; turtle-forward-quick!
;;; Parameters:
;;; turtle, a turtle
;;; distance, a real number
;;; Purpose:
;;; Move the turtle forward a bit more quickly than with
;;; turtle-forward! To get more speed, we use the current
;;; color and brush, rather than the turtle's color and brush.
;;; Produces:
;;; turtle, the same turtle
;;; Preconditions:
;;; turtle is a valid turtle on a valid image
;;; Postconditions:
;;; turtle has advanced by distance.
;;; If the turtle's brush is down, a line has been drawn from the
;;; turtle's old position to the turtle's new position.
(define turtle-forward-quick!
(let ((d2r (/ pi 180)))
(lambda (turtle distance)
(let ([col (turtle ':col)]
[row (turtle ':row)]
[angle (turtle ':angle)])
(let ([newcol (+ col (* distance (cos (* d2r angle))))]
[newrow (+ row (* distance (sin (* d2r angle))))])
(when (turtle ':pen?)
(image-draw-line! (turtle ':world)
col row
newcol newrow))
(turtle ':set-col! newcol)
(turtle ':set-row! newrow))
turtle))))
We've found that it is quite easy to draw an N-sided regular polygon
with a turtle. We simply repeat the steps of moving forward a
particular amount and turning by 360/N. But turtles only draw
the outline of a polygon. What if we want to select the polygon?
We start by finding a list of the vertices in the polygon. We then
use ( to select them. Once the
polygon is selected, we can do what we want with it.
image-select-polygon!
image
selection-type)
But how do we get the list of vertices? We can calculate the vertices. We can also use turtles to draw the polygon, and ask them to report back on their positions.
One technique is to compute the positions of the vertices of the polygon. It is easiest to compute those positions from the center of the polygon. In that case, we can just think of the angle to each vertex, use sine and cosine to break the angle into horizontal and vertical components, and scale and shift appropriately. But the angles should be obvious. For example, for a hexagon they are pi*1/3, pi*2/3, pi, pi*4/3, and pi*5/3.
But for those who don't want to do the math, we can just
use turtles and a few other techniques.. Basically, you
need to make a list of the points in the polygon and then use
image-select-polygon! to do the selection. I've
written a simple “log” library that you can use to gather
points from the turtle. You can use turtle-point
to get a single point and put it in the log.
The code below includes a full
turtle-select-polygon! procedure as well as two
examples of its use.
#lang racket
(require gigls/unsafe)
;;; Procedure:
;;; log-new
;;; Parameters:
;;; [None]
;;; Purpose:
;;; Create a new log of values.
;;; Produces:
;;; log, a log
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; We can apply log-add! and log-get to log.
(define log-new
(lambda ()
(vector null)))
;;; Procedure:
;;; log-add!
;;; Parameters:
;;; log, a log
;;; val, a value
;;; Purpose:
;;; Adds val to the log
;;; Produces:
;;; [Nothing; called for the side effects]
;;; Preconditions:
;;; (log-get log) is a value that we will call old-contents
;;; Postconditions:
;;; We have added val to the end of the log. That is,
;;; (log-get log) = (append old-contents (list val))
(define log-add!
(lambda (log val)
(vector-set! log 0 (cons val (vector-ref log 0)))))
;;; Procedure:
;;; log-get
;;; Parameters:
;;; log, a log
;;; Purpose:
;;; Get the list of values added to the log
;;; Produces:
;;; values, a list
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; values contains all of the values added by log-add!,
;;; in the same order.
(define log-get
(lambda (log)
(reverse (vector-ref log 0))))
;;; Procedure:
;;; turtle-select-polygon!
;;; Parameters:
;;; turtle, a turtle
;;; sides, a positive integer
;;; side-length, a positive real number
;;; operation, one of ADD, SUBTRACT, REPLACE, INTERSECT
;;; Purpose:
;;; Selects a polygon using a turtle. If the turtle is
;;; up, also draws the polygon.
;;; Produces:
;;; [Nothing; called for the side effect]
;;; Point:
;;; Partially intended as an illustration of how to select
;;; parts of an image using turtles.
(define turtle-select-polygon!
(lambda (turtle sides side-length operation)
(let ([points (log-new)]
[angle (/ 360 sides)])
; Build the list of points
(let kernel! ([side 0])
(when (< side sides)
(turtle-forward! turtle side-length)
(turtle-turn! turtle angle)
(log-add! points (turtle-point turtle))
(kernel! (+ side 1))))
; Select!
(image-select-polygon! (turtle-world turtle)
operation
(log-get points)))))
;;; Procedure:
;;; example-1
;;; Parameters:
;;; [None]
;;; Purpose:
;;; Provide an example of image-select-polygon!
;;; Produces:
;;; image, an image
(define example-1
(lambda ()
(let* ([world (image-new 200 200)]
[turtle (turtle-new world)])
(image-show world)
(turtle-teleport! turtle 50 50)
(turtle-up! turtle)
; square
(turtle-select-polygon! turtle 4 100 REPLACE)
(context-set-fgcolor! (irgb 63 0 63))
(image-fill! world)
; pentagon
(turtle-select-polygon! turtle 5 80 REPLACE)
(context-set-fgcolor! (irgb 127 0 127))
(image-fill! world)
; hexagon
(turtle-select-polygon! turtle 6 60 REPLACE)
(context-set-fgcolor! (irgb 191 0 191))
(image-fill! world)
; septagon
(turtle-select-polygon! turtle 7 45 REPLACE)
(context-set-fgcolor! (irgb 255 0 255))
(image-fill! world)
; octagon
(turtle-select-polygon! turtle 8 30 REPLACE)
(context-set-fgcolor! (irgb 255 127 255))
(image-fill! world)
; novagon
(turtle-select-polygon! turtle 9 20 REPLACE)
(context-set-fgcolor! (irgb 255 191 255))
(image-fill! world)
; Clean up and return the image
(image-select-nothing! world)
world)))
(define example-2
(lambda ()
(let* ([world (image-new 200 200)]
[turtle (turtle-new world)]
[maxsides 20]
[minsides 4]
[maxlen 120]
[scale 0.8]
[colors (vector (irgb 63 63 63)
(irgb 127 127 127)
(irgb 191 191 191))]
[numcolors (vector-length colors)])
; Put the turtle somewhere useful
(image-show world)
(turtle-teleport! turtle 50 30)
(turtle-up! turtle)
; Draw lots of polygons
(for-each (lambda (i)
(turtle-select-polygon! turtle (+ i minsides)
(* maxlen (expt scale i))
REPLACE)
(context-set-fgcolor! (vector-ref colors (mod i numcolors)))
(image-fill! world)
(image-select-nothing! world))
(iota (+ 1 (- maxsides minsides))))
; Return the image
world)))
The procedures you've used so far in CSC 151 are based upon some underlying procedures that the Gimp provides - procedures that are more powerful, but with even less error checking and worse documentation. Some of you may still find these procedures useful. You can get a list of all of the Gimp procedures with the Gimp Procedure Browser, which should be available under the Help menu in Gimp.
If you want further information on how to use any of these procedures, please talk to me. I'm also including some examples below.
A few projects work with modifying an existing image. For those
projects, it's necessary to scale the image to the appropriate size.
You can use ( to rescale it.
gimp-image-scale
image width
height)
Note that you may only use gimp-image-scale to
get an existing image to the right shape. It should be used only
immediately after a call to image-load. If
you use gimp-image-scale elsewhere (e.g., to
achieve the scale/stretch goals of the assignment), you will lose
credit.
One of the most common tasks I've helped students with is
selections. Once you have a selection in a form GIMP likes, you
can modify it with one of the many
gimp-item-transform-___ procedures, particularly
gimp-item-transform-rotate.
You'll note that all of these procedures require an “item”. But what is an item? It's a number that GIMP uses to refer to a layer and to some other related things, such as selections.
Here's a procedure I wrote to convert the current selection to an item.
;;; Procedure:
;;; image-selection-as-item
;;; Parameters:
;;; image, an image
;;; Purpose:
;;; Get an "item" that corresponds to the currently selected
;;; area of the image.
;;; Produces:
;;; item, an item id
;;; Preconditions:
;;; Something is selected on image.
;;; Postcondition:
;;; item can be used by the PDB procedures that expect an item.
;;; Ponderings:
;;; Should probably be followed by a transformation (gimp-item-transform-___)
;;; and then (gimp-image-flatten) and probably (context-update-displays!)
(define image-selection-as-item
(lambda (image)
(gimp-edit-copy-visible image)
(car (gimp-edit-paste (image-get-layer image) 2))))
Once you've gotten the item, you use it with the various
gimp-item-transform-___ procedures.
For example, you might rotate it with
(.
The “gimp-item-transform-rotate
item
angle-in-radians
auto-center
x
y)auto-center” parameter
specifies whether we rotate around the center of the selection
(use the value 1) or around the point
(x,y) (use the
value 0).
Once you've finished doing things with the selection, you need
to call gimp-image-flatten to confirm the
change and context-update-displays! to see
the result.
Here's a simple example.
> (define kitten (image-show (image-load "/home/rebelsky/Desktop/kitten.jpg"))) > (image-select-ellipse! kitten REPLACE 100 100 200 100) > (define item (image-selection-as-item kitten)) > (gimp-item-transform-rotate item (/ pi 4) 1 100 100) > (gimp-image-flatten kitten) > (image-select-ellipse! kitten REPLACE 100 100 200 50) > (define item (image-selection-as-item kitten)) > (gimp-item-transform-rotate item (- (/ pi 4)) 0 100 100) > (gimp-image-flatten kitten) > (context-update-displays!)
As a further example of the use of this procedure, I've created a simple procedure that draws a rotated ellipse.
;;; Procedure:
;;; bean!
;;; Parameters:
;;; image, an image
;;; x, a real number
;;; y, a real number
;;; width, a positive real number
;;; height, a positive real number
;;; angle, a real number
;;; Purpose:
;;; Draw a "bean" (a rotated ellipse) centered at (x,y).
;;; Produces:
;;; [Nothing; called for the side effect]
;;; Preconditions:
;;; Some part of the resulting ellipse should be within the bounds
;;; of the image.
;;; (<= width (image-width image))
;;; (<= height (image-height image))
;;; Postconditions:
;;; The image now contains the described figure.
;;; Problems:
;;; In order to see the result, you may need to call context-update-displays!
(define bean!
(let ([d2r (/ pi 180)])
(lambda (image x y width height angle)
; Select the right size ellipse, centered at (0,0). We use (0,0) as
; top-left to selet the largest possible ellipse.
(image-select-ellipse! image REPLACE 0 0 width height)
; Convert the selection to an item for gimp-item-transform-...
(let ([item (image-selection-as-item image)])
; Rotate to the correct angle and shift to the correct position.
; We're using gimp-item-transform-2d, rather than gimp-item-rotate,
; because it gives us a bit more freedom (in particular, we can be sure
; to select within the image, more or less)
(gimp-item-transform-2d item (/ width 2) (/ height 2) 1 1 (* angle d2r) x y)
; Fill with the foreground color
(image-fill! image)
; And finish up!
(gimp-image-flatten image)
(void)))))
You'll find that you can use the various gimp-item-transform-___ procedures to do all sorts of interesting things. You may
not use them as the only way of accomplishing the
scale/stretch goal of the project (although you may use them for scaling
and stretching as part of your project).
You may find it useful to copy a part of an image to elsewhere in an image or to another image. Here's a procedure that helps you do so. You can study the details to do variants.
;;; Procedure: ;;; overlay-rectangle! ;;; Parameters: ;;; source, an image ;;; target, an image ;;; source-x, a real number ;;; source-y, a real number ;;; target-x, a real number ;;; target-y, a real number ;;; width, a positive integer ;;; height, a positive integer ;;; Purpose: ;;; Copy a width-by-height rectangle from source to target ;;; Produces: ;;; [Nothing; called for the side effect.] ;;; Preconditions: ;;; The selections must be within the given images. ;;; Postconditions: ;;; For all 0 <= i < width, 0 <= j < height, ;;; (image-get-pixel target (+ i target-x) (+ j target-y)) = ;;; (image-get-pixel source (+ i target-x) (+ j target-y)) ;;; Ponderings: ;;; Does not show the update to the image. You will need to call ;;; (context-update-displays!) afterwards. (define overlay-rectangle! (lambda (source target source-x source-y target-x target-y width height) ; Copy from the source (image-select-rectangle! source REPLACE source-x source-y width height) (gimp-edit-copy (image-get-layer source)) (image-select-nothing! source) ; Paste into the target (image-select-rectangle! target REPLACE target-x target-y width height) (gimp-edit-paste (image-get-layer target) 1) (gimp-image-flatten target) (image-select-nothing! target)))
Many of you found (or will find) it useful to draw many lines with regular spacing. Here's a procedure that I've found useful.
;;; Procedure:
;;; lines!
;;; Parameters:
;;; image, an image
;;; n, an integer
;;; sx1, a real number ("source" x1)
;;; sy1, a real number ("source" y1)
;;; sx2, a real number
;;; sy2, a real number
;;; tx1, a real number ("target" x1)
;;; ty1, a real number
;;; tx2, a real number
;;; ty2, a real number
;;; Purpose:
;;; Draw n lines with the current brush and color.
;;; One endpoint of each line is evenly spaced along the line
;;; from (sx1,xy1) to (sx2,sy2).
;;; The other endpoint of each line is evenly spaced along the
;;; line from (tx1,ty1) to (tx2,ty2).
;;; Preconditions:
;;; n >= 2
;;; Postconditions:
;;; The image contains n additional lines.
;;; One line is from (xs1,ys1) to (xt1,yt1)
;;; One line is from (xs2,ys2) to (xt2,yt2)
;;; The remaining lines are evenly distributed between those points.
(define lines!
(lambda (image n sx1 sy1 sx2 sy2 tx1 ty1 tx2 ty2)
(let ([delta-sx (/ (- sx2 sx1) (- n 1))]
[delta-sy (/ (- sy2 sy1) (- n 1))]
[delta-tx (/ (- tx2 tx1) (- n 1))]
[delta-ty (/ (- ty2 ty1) (- n 1))])
(for-each (lambda (i)
(image-draw-line! image
(+ sx1 (* i delta-sx))
(+ sy1 (* i delta-sy))
(+ tx1 (* i delta-tx))
(+ ty1 (* i delta-ty))))
(iota n)))))
And here's a simple procedure to let you explore some of the different things you can do with those lines. In addition to drawing the lines, it shows you the lines that provide starting and ending points.
; (lines-example n sx1 sy1 sx2 sy2 tx1 ty1 tx2 ty2)
; Build an example of the lines procedure.
(define lines-example
(lambda (n width height sx1 sy1 sx2 sy2 tx1 ty1 tx2 ty2)
(let ([image (image-show (image-new width height))])
(context-set-brush! "2. Hardness 050" 3)
(context-set-fgcolor! "black")
(lines! image n sx1 sy1 sx2 sy2 tx1 ty1 tx2 ty2)
(context-set-brush! "2. Hardness 100" 5)
(context-set-fgcolor! "red")
(image-draw-line! image sx1 sy1 sx2 sy2)
(context-set-fgcolor! "blue")
(image-draw-line! image tx1 ty1 tx2 ty2))))
And here are a few examples that might get you started in thinking about the power of this procedure.
> (lines-example 20 400 300
0 0
0 299
0 299
399 299)
> (lines-example 20 400 300
0 299
399 0
399 0
399 299)
> (lines-example 20 400 300
0 150
200 0
0 299
399 0)
> (lines-example 20 400 300
0 0
0 299
399 299
399 0)
Mediascheme supports HSV colors as well as RGB colors. You can read more about HSV colors online. Basically, we have a hue (an angle on the color wheel, expressed in degrees), a saturation (how much of the color we have, on a 0-1 scale), and a value (which I can never explain).
In Mediascheme, you represent HSV colors as a list. The hue must be
an integer. For example, here's red: '(0 1 1) and
here's blue-red '(300 1 1).
As you might expect, you convert HSV colors to RGB colors with
hsv->rgb.
To illustrate HSV colors, here's a simple program that draws a rainbow blend.
(define simple-rainbow
(lambda (width height)
(let* ([hscale (/ 1 width)]
[vscale (/ 1 height)]
[hue-multiplier (* hscale 360)]
[sat-multiplier vscale])
(image-compute (lambda (col row)
(hsv->irgb (list (round (* col hue-multiplier))
(* row sat-multiplier)
1)))
width height))))
This idea was inspired by one team who wanted to have the turtle draw with a color blend and another team who wanted a brush that used a color blend.
Unfortunately, switching colors as you go is slow. However, we can draw in a color that we don't use otherwise (I've chosen black) and then go back through with `image-redo!` to change all the values in that color to a value within the blend.
Here's a procedure that does just that.
;;; Procedure:
;;; black-to-rainbow!
;;; Parameters:
;;; image, an image
;;; Purpose:
;;; Convert all the black (or very dark) colors
;;; to a rainbow blend.
;;; Produces:
;;; [Nothing; called for the side effects]
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; The black (or close to black) colors in the
;;; image now show some strange rainbow blend.
(define black-to-rainbow!
(let ([blackish? (lambda (color)
(and (< (irgb-red color) 16)
(< (irgb-green color) 16)
(< (irgb-blue color) 16)))])
(lambda (image)
(let* ([hscale (/ 1 (image-width image))]
[vscale (/ 1 (image-height image))]
[hue-multiplier (* hscale 360)]
[sat-multiplier vscale])
(image-redo!
image
(lambda (col row color)
(if (blackish? color)
(hsv->irgb (list (round (* col hue-multiplier))
(* row sat-multiplier)
1))
color)))))))
Here's a somewhat silly example of the proceudre in use.
(define example
(let ([spiral! (lambda (turtle)
(for-each (lambda (i)
(turtle-forward-quick! turtle 5)
(turtle-turn! turtle (/ i 9)))
(iota 110)))])
(lambda ()
(let* ([image (image-show (image-new 200 200))]
[spirurtle (turtle-new image)])
(context-set-brush! "2. Hardness 100" 10)
; Draw one spiral for the background
(turtle-teleport! spirurtle 10 30)
(context-set-fgcolor! "red")
(spiral! spirurtle)
; Draw another spiral for the background
(turtle-teleport! spirurtle 30 70)
(turtle-face! spirurtle 0)
(context-set-fgcolor! "blue")
(spiral! spirurtle)
; Draw another spiral for the color blend
(turtle-teleport! spirurtle 20 50)
(turtle-face! spirurtle 0)
(context-set-fgcolor! "black")
(spiral! spirurtle)
; And make the color blend
(black-to-rainbow! image)
image))))
Some of you wanted to just select a polygon directly, rather than bothering with turtles. Amazingly, once you work out the trig, the points of a polygon are relatively easy to compute. Given a center point, (x,y), a radius, r, and the angle of the line to one of the vertices, angle, we can simple compute x+r*sin(angle) and y+r*cos(angle). What are the different angles we use? 0, 2*pi/sides, 2*(2*pi/sides), 3*(2*pi/sides), etc. We can even add a starting angle.
Since we have a regular sequence of angles, we can use
map and iota to build the
list of angles (or the list of points).
Once we have the list of points, we can use image-select-polygon!.
;;; Procedure:
;;; image-select-regular-polygon!
;;; Parameters:
;;; image, an image
;;; operation, one of REPLACE, ADD, SUBTRACT, and INTERSECT
;;; x, a real number
;;; y, a real number
;;; sides, an integer
;;; radius, a real number
;;; theta, a real number
;;; Purpose:
;;; Selects a regular polygon of sides sides, centered at (x,y), with
;;; each point radius away from the center.
;;; Produces:
;;; [Nothing; called for the side effect]
;;; Preconditions:
;;; sides >= 3
;;; Some portion of the selection is on the image
;;; Postconditions:
;;; The image now contains the described selection.
;;; Ponderings:
;;; It would probably make more sense to make the side-length a parameter,
;;; but I'm too lazy to convert side-length to radius. It's also easier
;;; to stretch the regular polygon if we work with a radius.
(define image-select-regular-polygon!
(lambda (image operation x y sides radius theta)
(let ([delta-angle (/ (* 2 pi) sides)])
(image-select-polygon! image operation
(map (lambda (i)
(let ([angle (+ theta (* i delta-angle))])
(cons (+ x (* radius (cos angle)))
(+ y (* radius (sin angle))))))
(iota sides))))))
As the comments suggest, it's easy to stretch one of these shapes. How? Use a different horizontal and vertical radius. Details are left up to the reader.
You can, of course, do many things with this procedure. Here's a quick example I threw together that ended up being a bit more powerful than I expected.
; (example sides copies)
; A silly example with image-select-regular-polygon! Selects lots of
; polygons of decreasing radius and draws them in different colors
(define example
(let* ([min-radius 30]
[max-radius 120]
[delta-angle (/ pi 9)])
(lambda (sides copies)
(let ([image (image-show (image-new 200 200))]
[delta-hue (/ 360 copies)]
[delta-radius (/ (- max-radius min-radius) copies)])
(context-set-brush! "2. Hardness 050" 5)
(map (lambda (i)
(image-select-regular-polygon! image REPLACE
100 100
sides
(- max-radius (* i delta-radius))
(* i delta-angle))
(context-set-fgcolor! (hsv->irgb (list (round (* i delta-hue)) 1 1)))
(image-fill! image)
(context-set-fgcolor! "black")
(image-stroke! image))
(iota copies))
(image-select-nothing! image)
(context-update-displays!)
image))))
If you are willing to try to understand a more complex approach,
don't care that you can see what the turtle is doing, and probably
restructure your code, there is another way to make turtles even
faster. Just as in the case in which we made polygons, we're going
to log the points the turtle stops. But we won't even attempt to
draw the lines. We're going to use a variant of the
turtle-forward! procedure that expects a
log as a parameter. (You should only put values into that log
using this procedure.)
;;; Procedure:
;;; turtle-forward-log!
;;; Parameters:
;;; turtle, a turtle
;;; amt, a real number
;;; log, a log
;;; Purpose:
;;; Move the turtle forward the given amount without showing its
;;; movement. Then log the new position.
;;; Produces:
;;; [Nothing; called for the side effect]
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; The turtle has moved the appropriate amount.
;;; The log now contains the (col,row) of the turtle.
(define turtle-forward-log!
(let ((d2r (/ pi 180)))
(lambda (turtle distance log)
(let ([col (turtle ':col)]
[row (turtle ':row)]
[angle (turtle ':angle)])
(let ([newcol (+ col (* distance (cos (* d2r angle))))]
[newrow (+ row (* distance (sin (* d2r angle))))])
(turtle ':set-col! newcol)
(turtle ':set-row! newrow)
(log-add! log newcol)
(log-add! log newrow)
turtle)))))
Now, we can use some procedures to show the log we've built.
;;; Procedure:
;;; image-show-path!
;;; Parameters:
;;; image, an image
;;; path, a vector of the form #(x1 y1 x2 y2 ...)
;;; Purpose:
;;; Show the path on the given image
;;; Produces:
;;; [Nothing; called for the side effect]
(define image-show-path!
(lambda (image path)
(gimp-paintbrush-default (image-get-layer image)
(vector-length path)
path)))
;;; Procedure:
;;; image-show-log!
;;; Parameters:
;;; image, an image
;;; log, a log whose elements were created by turtle-forward-log!
;;; Purpose:
;; Show the path of the turtle.
;;; Produces:
;;; [Nothing; called for the side effects.]
;;; Preconditions:
;;; At least one point has been logged with turtle-forward-log!.
;;; Postconditions:
;;; image contains a rendering of that log.
(define image-show-log!
(lambda (image log)
(image-show-path! image (list->vector (log-get log)))))
Here's a very quick example of its use.
; (faster-example turtle)
; An example using turtle-forward-log!
(define faster-example
(let ([steps (iota 800)])
(lambda (turtle)
(let ([stuff (log-new)])
(for-each (lambda (i)
(turtle-forward-log! turtle 3 stuff)
(turtle-turn! turtle (mod i 150)))
steps)
(image-show-log! (turtle-world turtle) stuff)
(context-update-displays!)))))