Chris Stryczynski

Software Developer / Consultant

Draw Stuff With Haskell using Gloss

Posted on: 28/08/2016

https://github.com/chrissound/DrawStuffWithHaskellArticle

Lets go!

Create a cabal sandbox: cabal sandbox init

Create the necessary cabal files. You could copy the below files or run through cabal init.

drawStuffWithHaskell.cabal

-- Initial img.cabal generated by cabal init.  For further documentation, 
-- see http://haskell.org/cabal/users-guide/

name:                drawStuffWithHaskell
version:             0.1.0.0
-- synopsis:            
-- description:         
license:             MIT
license-file:        LICENSE
author:              Chris Stryczynski
maintainer:          chris@trycatchchris.co.uk
-- copyright:           
-- category:            
build-type:          Simple
-- extra-source-files:  
cabal-version:       >=1.10

executable img
  main-is:             Main.hs
  -- other-modules:       
  -- other-extensions:    
  build-depends:        base >=4.8
                        , gloss
                        , bytestring
                        , containers
                        , random
  -- hs-source-dirs:      
  default-language:    Haskell2010

Draw a circle

Main.hs

module Main(main) where

import Graphics.Gloss

window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)

background :: Color
background = white

drawing :: Picture
drawing = circle 80

main :: IO ()
main = display window background drawing
img1

Draw four circles

But how? With some googling about we find pictures. Wtf is pictures? Open the cabal REPL cabl repl:

import Graphics.Gloss
:t pictures

Output:

pictures :: [Picture] -> Picture

It takes an array (list?) of elements of type Picture and returns a single Picture.

Main.hs

module Main(main) where

import Graphics.Gloss

window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)

background :: Color
background = white

drawing :: Picture
drawing = pictures
    [
        circle 10
        , circle 20
        , circle 30
        , circle 40
        , circle 50
    ]

main :: IO ()
main = display window background drawing
img3

Functionify

Ooooo.

For some reason the first parameter to circle must be a Float (instead of Int).

module Main(main) where

import Graphics.Gloss

window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)

background :: Color
background = white

getCircle :: Float -> Picture
getCircle size = circle size

get5Circles :: [Picture]
get5Circles = map getCircle [10,20..50]

drawing :: Picture
drawing = pictures get5Circles

main :: IO ()
main = display window background drawing

Functionify

Main.hs

module Main(main) where

import Graphics.Gloss

window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)

background :: Color
background = white

getCircle :: Float -> Picture
getCircle size = circle size

getCircles :: Int -> [Picture]
getCircles count = map getCircle (take count [10,20..])

drawing :: Picture
drawing = pictures (getCircles 5)

main :: IO ()
main = display window background drawing

Type annotations are optional. Yes holy shit!

Main.hs

module Main(main) where

import Graphics.Gloss

window = InWindow "Nice Window" (200, 200) (10, 10)

background = white

getCircle size = circle size

getCircles count = map getCircle (take count [10,20..])

drawing = pictures (getCircles 5)

main = display window background drawing

Lets go deeper.

I’ve brought back the type annotations for readability. Let’s create a new data type and lets create a new function getAngle that returns an angle between two points of the outline of the circle and the center. getAngle takes two parameters. Notice the slightly different type signature. Basically Haskell function’s return is of the last type specified in the type signature.

So in other words getAngle is a function that takes two parameters both of type Integer and returns a type of Float.

If you’re interested in the logic behind this you can read up about ‘Haskell function currying’.

Main.hs

module Main(main) where

import Graphics.Gloss

data Point = Point Float Float deriving Show

window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)

background :: Color
background = white

getCircle :: Float -> Picture
getCircle size = circle size

getCircles :: Int -> [Picture]
getCircles count = map getCircle (take count [10,20..])

getAngle :: Integer -> Integer -> Float
getAngle pointIndex pointCount = 2 * pi * (fromInteger pointIndex / fromInteger pointCount)

drawing :: Picture
drawing = pictures (getCircles 5)

main :: IO ()
main = display window background drawing

Draw some more circle at particular points

A new thing we’re using here is a where statement, which as far as I can tell is just syntactic sugar at this point. However a rather cool thing is we can reference other values within the expression without them being defined previously. This rather amazing feature is thanks to Haskell`s lazy loading. Here is an example:

x = a+b where
    a = c+d
    b = c*3
    c = 5
    d = 4

The above will have define x as the value 24 (c+d)+(c3) = (5 + 4)+(53) = 24

We also need the help of fromIntegral to help us convert between numeric types. We’re also using an anonymous function with the map.

And we’re also using Translate which is a data constructor, basically a function that constructs a data type, hence the reason it’s capitalized (it’s a requirement of Haskell syntax).

Oh and we’re also using pattern matching with the PointCoordinate data type, (to get the x and y values).

Main.hs

module Main(main) where

import Graphics.Gloss

data PointCoordinate = PointCoordinate Float Float deriving Show

window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)

background :: Color
background = white

getCircle :: Float -> Picture
getCircle size = circle size

getAngle :: Integer -> Integer -> Float
getAngle pointIndex pointCount = 2 * pi * (fromInteger pointIndex / fromInteger pointCount)

getCirclePoint :: Float -> Float -> PointCoordinate
getCirclePoint radius angle = PointCoordinate
    (radius * sin(angle))
    (radius * cos(angle))

getTranslatedCircle :: Integer -> Integer -> Picture
getTranslatedCircle index indexCount = Translate x y (getCircle unitLength)
    where (PointCoordinate x y) = getCirclePoint radius (getAngle index indexCount)
          radius = unitLength + (unitLength * ((fromIntegral index) / (fromIntegral indexCount)))
          unitLength = 30


drawing :: Picture
drawing = pictures (map (\pointIndex -> getTranslatedCircle pointIndex pointCount) [1..pointCount])
    where pointCount = 10

main :: IO ()
main = display window background drawing
img3
Comments

No comments, yet!

Submit a comment