By working through the steps in this handout, you will develop a Haskell program to draw a picture made up of many overlapping squares of different sizes and colors.
data Square = Square Point Int RGBThis says that a
Square
will be specified by a Point
(the
center), an Int
(the distance from the center to any of the
sides, i.e., half the length of a side), and a color expressed
as red, green, and blue components. The type Int
is one of the
built-in types (it is a variant of Integer
that gives up the very
large values in exchange for greater efficiency); each of the other
types is already defined in the Graphics Library, so we just need to
know how to use them.
Copy the Haskell script file named hasklab.hs
from the folder
I:\CSC122\Public\
into your own directory on the I:
drive.
Now start WinHugs and :load
your copy of the script. Once it has
loaded (it may take a while, since it has to load all of the parts of
the graphics library as well), you should open it in an editor with the
command :edit
. From now on, each time you close the editor and
return to Hugs, it should automatically load the new version of your
file.
When you look at the script, it should start with the following lines:
-- CSC122, Haskell Lab -- <your name> -- <the date> import SOEGraphics data Square = Square Point Int RGBChange the comments to include your name and the date, save the file, and close the editor.
Square
object.
To use the Square
constructor function, we need a Point
,
an Int
, and an RGB
. A Point
is simply a pair of
integers, so we can use something like (200, 100)
. An Int
is just a value like 50
. An RGB
needs three integers in
the range 0 to 255; to get white, we can use the RGB
constructor
as follows: RGB 255 255 255
. Putting all this together, we can
form a white square, 100 units on each side, centered on the point
(200, 100), by using the expression
Square (200, 100) 50 (RGB 255 255 255)
. If you type this in, the
system will complain that it cannot find an appropriate ``show''
function. This is OK; we can still use the Square
type in a
program, and we can check that we are using it correctly by only
requesting the type of the above expression:
:type Square (200, 100) 50 (RGB 255 255 255)
.
Square
objects.
The Haskell Graphics library provides a function polygon
which
takes a list of Point
s and produces a filled-in shape with the
given points as vertices. The script already contains a convenience
function named fillSquare
which takes a Square
and makes
the appropriate call to polygon
:
fillSquare (Square (x,y) d rgb) = withRGB rgb (polygon [(x-d,y-d), (x-d,y+d), (x+d,y+d), (x+d,y-d)])As usual, we write the function to match its argument against a pattern so that the variables in the pattern will be bound to the corresponding values for the
Square
we wish to draw. The center point will be
(x,y)
, the distance from the center to each side will be
d
, and the color will be rgb
. We call the polygon
function with an appropriate list of points for the four corners of the
square, and pass the result to the withRGB
function to apply a
color to the shape. If you try to apply fillSquare
to the square
mentioned previously,
fillSquare (Square (200,100) 50 (RGB 255 255 255))
, the system
should again complain about the lack of an appropriate ``show''
function. The result is of the type Draw ()
, which the system
might also call Graphic
. The square has not been drawn yet, but
this Graphic
value packages up all the information that the
Graphics Library will need to display it. The next step will do so.
showGraphic
, which is already defined in your script. For
reference, here is the definition:
showGraphic g = runGraphics $ do { w <- openWindow "Graphics Window" (400,300); drawInWindow w g; getKey w; closeWindow w }Try applying
showGraphic
to the result of various calls to
fillSquare
. Each time you do, it should create a graphics window,
display a filled square, and wait for you to hit any key before closing
the window. For testing, it will be convenient to define some sample
squares. Add some more lines like the following to your script and reload:
redGiant = Square (150,150) 100 (RGB 255 0 0) whiteDwarf = Square (250,250) 25 (RGB 255 255 255)Now you can easily try drawing squares by entering
showGraphic (fillSquare redGiant)
, etc., at the Main>
prompt in Hugs. However, this only lets you draw one square at a time.
To combine several squares in one picture, we need the
overGraphic
function. It will be easiest to use this as a binary
operator, as follows:
showGraphic ((fillSquare redGiant) `overGraphic` (fillSquare whiteDwarf))
.
Notice how the squares are combined, and see what happens if you reverse the
order of the squares.
Square
s. We will need a function
which takes a list of squares, fills each one, and combines them all
into a single Graphic
. As usual when working with a list of
things, we will define the function by recursion. Here is the base case,
which uses the special value emptyGraphic
to produce a blank
picture:
fillSquareList [] = emptyGraphicThe recursive case will have the following form:
fillSquareList (s : ss) =When this pattern matches,
s
will be the first square on the
list, and ss
will be a list of the remaining squares. Write an
appropriate right-hand side for this case of the function. You will need
to use fillSquare
to produce a Graphic
from s
, and
fillSquareList
to produce another Graphic
from ss
;
then these two Graphic
s will need to be combined into one with
overGraphic
. When you have added the function to your script, try
it out with showGraphic (fillSquareList [redGiant, whiteDwarf])
.
diagonalSquares (x,y) d rgb 0 = [] diagonalSquares (x,y) d rgb n = Square (x,y) d rgb : diagonalSquares (x+d,y+d) d rgb (n-1)Try this:
showGraphic (fillSquareList (diagonalSquares (50,50) 25 (RGB 255 255 255) 9))
.
The cases for this function mean that diagonalSquares (x,y) d rgb n
will produce a list of n
squares (because the list is empty
when n
is 0
, and it gets one extra element for each
recursive call as n
counts down to 0
). The first one will
be centered at (x,y)
, with dimension d
and color
rgb
. Succeeding squares will be offset by adding d
to the
x
and y
coordinates of the center; the effect will be that
each square will be centered on the lower-right corner of the previous
one. You should get a picture that looks like a white staircase.
vanishingSquares
which is similar to diagonalSquares
except replace the argument d
in the recursive call (to
vanishingSquares
, of course) with the expression (d `quot` 2)
.
To try this out, you will probably want to start with a larger
initial square; something like this should work:
showGraphic (fillSquareList (vanishingSquares (200,100) 80 (RGB 255 255 255) 7))
++
. Here
is a skeleton of the code for you to fill in:
squareDesign (x,y) d rgb 0 = squareDesign (x,y) d rgb n = Square (x,y) d rgb : (squareDesign (x-d,y-d) (d `quot` 2) rgb (n-1) ++ squareDesign (x-d,y+d) (d `quot` 2) rgb (n-1) ++ squareDesign ++ squareDesign )When you have the finished function saved in your script file, load it into Hugs and try the following:
showGraphic (fillSquareList (squareDesign (200,150) 70 (RGB 255 255 255) 5))
5
to 6
to draw one more level, but it
will probably crash if you try to do 7 or more levels (each level has
four times as many squares, so there are 4096 squares at level 7).
(RGB r g b)
instead of the simple variable rgb
. Call the new function
colorDesign
.
2
; for
example, if you modify colorDesign
, then your new function should
be named colorDesign2
.
When you are done, make sure your script file with all of the above code
is saved in your folder on the I:
drive.