In this article, I will write a haskell program that displays a snowflake haskell. This is given as a problem in one of the best books on Haskell – The Haskell School of Expression, by Proffessor Paul Hudak. One can see a sample snowflake here on the math forum.
I am using a haskell package called gtk2hs ,which contains a special module needed for the code of that book.
module Snowflake where import Graphics.SOE.Gtk
1 Setting the stage
I will start with a “command” (haskell expressions/functions that yield actions when they are applied) that is given in the book.
This command takes a window as an input and waits for the user to press the space bar. Once it is pressed, it will close the window.
This is used in a very elegant piece of code that displays Sierpinski’s triangle
spaceClose :: Window -> IO ()
spaceClose w
= do k<- getKey w
if k == ' ' then closeWindow w
else spaceClose w
Now a function that limits the size of the smallest ’star’.An example for how to define global constants in haskell
minSize :: Int minSize = 8
2 Recursive star
The idea is to draw a Star of David – by overlapping two equilateral triangles such that
their circum-centers coincide. Now, in this Graphics module that I am using, one can draw a polygon by invoking a polygon command with a list of x-y co-ordinates.
But due to some sort of quirkiness with the definition of polygon this module, it is necessary that the last element in the list is the same as the first pair of co-ordinates.
Hence, in order to draw a triangle, I will have to invoke the polygon command using a list containing 4 co-ordinates.
So, I will define my data type as a record containing 2 sets of lists (one for each triangle) and also a size.
data StarOfDavid = SoD {trA :: [(Int,Int)],trB ::[(Int,Int)],szSoD::Int}
That done, I now need a function which takes a pair of x,y co-ordinates and the size (that is the radius of the circum-circle) and give me
This function is based on elementary trignometry and is pretty straight forward.
the StarOfDavid
getVertices :: (Int,Int) ->Int -> StarOfDavid
getVertices (x,y) r = SoD {trA = [(x,y-r), (x - (mulCos30 r), y + r `div` 2),(x + (mulCos30 r), y + r `div` 2 ),(x,y-r)],
trB = [(x,y+r),(x - (mulCos30 r), y - r `div` 2),(x + (mulCos30 r), y - r `div` 2),(x,y+r)],
szSoD = r}
mulCos30 ::Int -> Int
mulCos30 x = round ( (fromIntegral x) * 0.866 )
Now I need a command that takes a Window and a record corresponding to StarOfDavid, and draw it on the window, if and only if, the size is greater than the minSize defined earlier. Also, since the fractal can be drawn by drawing six stars at one-third the size at each of the six ‘corners’ of the Star and repeating the process till we reach the minimum size.
To implement all that in haskell is simple and elegant as below.
drawStarOfDavid :: Window -> StarOfDavid -> Color -> IO()
drawStarOfDavid w sod c =
if (szSoD sod) <= minSize then return ()
else do drawInWindow w (withColor c (polygon (trA sod)))
drawInWindow w (withColor c (polygon (trB sod)))
sequence_ (map (\x-> drawStarOfDavid w x c) (nextStarCenters sod))
nextCenters :: StarOfDavid -> [(Int,Int)]
nextCenters dos = (take 3 (trA dos) ) ++ (take 3 (trB dos))
nextStarCenters :: StarOfDavid -> [StarOfDavid]
nextStarCenters dos = map (\x-> getVertices x newsiz ) (nextCenters dos)
where newsiz = div (szSoD dos) 3
3 The main method
Finally, the main method, a command that invokes the drawStarOfDavid command.
main
= runGraphics (
do w <- openWindow
"Snowflake Fractal" (400,400)
drawStarOfDavid w (getVertices (250,250) 150) Cyan
spaceClose w
)
That’s it. Of course the picture that is generated is mono-chromatic and not as colorful as the math-forum one that I linked earlier.
(Also, the site has been generated using latex and hevea).
