module Pictures ( Picture , horse , flipV , flipH , above , beside , superimpose , invertColour , scale , black , white ) where import Test.SmallCheck import Test.SmallCheck.Series import Data.List import Control.Monad newtype Picture = Picture [[Char]] deriving Eq -- The example used in Craft2e: a polygon which looks like a horse. Here -- taken to be a 16 by 12 rectangle. horse :: Picture horse = Picture [" ## ", " ## # ", " ## # ", " # # ", " # # # ", " # ### # ", " # # ## ", " # # ", " # # ", " # # ", " # # ", " ## "] -- Completely white and black pictures. white :: Picture white = Picture [" "] black = Picture ["#"] -- Getting a picture onto the screen. instance Show Picture where show (Picture p) = "\n" ++ concatMap (\x -> "|" ++ x ++ "|\n") p -- SmallCheck generator instance Serial Picture where series d = do size <- [0..d] map (fromList size) $ replicateM (size^2) [' ', '#'] coseries = error "Picture.coseries" fromList :: Int -> [Char] -> Picture fromList size cs = Picture $ unfoldr f cs where f [] = Nothing f xs = Just $ splitAt size xs -- Transformations of pictures. pic :: ([[Char]] -> [[Char]]) -> Picture -> Picture pic f (Picture p) = Picture $ f p pic2 :: ([[Char]] -> [[Char]] -> [[Char]]) -> Picture -> Picture -> Picture pic2 f (Picture p1) (Picture p2) = Picture $ f p1 p2 -- Reflection in a vertical mirror. flipV :: Picture -> Picture flipV = pic $ map reverse -- Reflection in a horizontal mirror. flipH :: Picture -> Picture flipH = pic reverse -- Rotation through 180 degrees, by composing vertical and horizontal -- reflection. Note that it can also be done by flipV.flipH, and that we -- can prove equality of the two functions. rotate :: Picture -> Picture rotate = flipH . flipV -- One picture above another. To maintain the rectangular property, -- the pictures need to have the same width. above :: Picture -> Picture -> Picture above = pic2 (++) -- One picture next to another. To maintain the rectangular property, -- the pictures need to have the same height. beside :: Picture -> Picture -> Picture beside = pic2 $ zipWith (++) -- Superimose one picture above another. Assume the pictures to be the same -- size. The individual characters are combined using the combine function. superimpose :: Picture -> Picture -> Picture superimpose = pic2 $ zipWith (zipWith combine) -- For the result to be '.' both components have to the '.'; otherwise -- get the '#' character. combine :: Char -> Char -> Char combine topCh bottomCh = if (topCh == ' ' && bottomCh == ' ') then ' ' else '#' -- Inverting the colours in a picture; done pointwise by invert... invertColour :: Picture -> Picture invertColour = pic $ map (map invert) -- ... which works by making the result '.' unless the input is '.'. invert :: Char -> Char invert ch = if ch == ' ' then '#' else ' ' scale :: Int -> Picture -> Picture scale n = pic $ \ls -> map (>>= replicate n) ls >>= replicate n