packages feed

puzzle-draw 0.1.0.1 → 0.1.0.2

raw patch · 16 files changed

+171/−44 lines, 16 filesdep +vector-spacePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: vector-space

API changes (from Hackage documentation)

- Data.Puzzles.Grid: outsideclues :: OutsideClues (Maybe a) -> [((Int, Int), a)]
- Diagrams.Puzzles.Grid: border :: (Backend b R2, Renderable (Path R2) b) => Size -> Diagram b R2
- Text.Puzzles.Util: instance FromJSON TapaClue
+ Data.Puzzles.Elements: JapBlack :: JapVal
+ Data.Puzzles.Elements: JapInt :: Int -> JapVal
+ Data.Puzzles.Elements: data JapVal
+ Data.Puzzles.Elements: instance Show JapVal
+ Data.Puzzles.Grid: instance Eq a => Eq (OutsideClues a)
+ Data.Puzzles.Grid: instance Functor OutsideClues
+ Data.Puzzles.Grid: multiOutsideClues :: OutsideClues [a] -> [((Int, Int), a)]
+ Data.Puzzles.Grid: outsideClues :: OutsideClues (Maybe a) -> [((Int, Int), a)]
+ Data.Puzzles.Grid: outsideSize :: OutsideClues a -> (Int, Int)
+ Data.Puzzles.PuzzleTypes: Coral :: PuzzleType
+ Data.Puzzles.PuzzleTypes: JapaneseSums :: PuzzleType
+ Diagrams.Puzzles.Draw: Pixels :: Unit
+ Diagrams.Puzzles.Draw: Points :: Unit
+ Diagrams.Puzzles.Draw: data Unit
+ Diagrams.Puzzles.Draw: diagramWidth :: Diagram b R2 -> Double
+ Diagrams.Puzzles.Draw: toOutputWidth :: Unit -> Double -> Double
+ Diagrams.Puzzles.PuzzleGrids: outsideIntGrid :: (Backend b R2, Renderable (Path R2) b) => OutsideClues [Int] -> Diagram b R2
+ Diagrams.Puzzles.PuzzleTypes: coral :: (Backend b R2, Renderable (Path R2) b) => RenderPuzzle b (OutsideClues [Int]) ShadedGrid
+ Diagrams.Puzzles.PuzzleTypes: japanesesums :: (Backend b R2, Renderable (Path R2) b) => RenderPuzzle b (OutsideClues [Int]) (SGrid JapVal)
+ Diagrams.Puzzles.Widths: gridresd :: Double
+ Text.Puzzles.PuzzleTypes: coral :: ParsePuzzle (OutsideClues [Int]) ShadedGrid
+ Text.Puzzles.PuzzleTypes: japanesesums :: ParsePuzzle (OutsideClues [Int]) (SGrid JapVal)
+ Text.Puzzles.Util: ParseTapaClue :: TapaClue -> ParseTapaClue
+ Text.Puzzles.Util: instance FromChar JapVal
+ Text.Puzzles.Util: instance FromJSON ParseTapaClue
+ Text.Puzzles.Util: newtype ParseTapaClue
+ Text.Puzzles.Util: parseMultiOutsideClues :: FromJSON a => Value -> Parser (OutsideClues [a])
+ Text.Puzzles.Util: unParseTapaClue :: ParseTapaClue -> TapaClue

Files

CHANGES.md view
@@ -1,3 +1,8 @@+0.1.0.2: 20140419+-----------------++* add new puzzle types: Japanese Sums, Coral+ 0.1.0.1: 20140409 ----------------- 
puzzle-draw.cabal view
@@ -1,5 +1,5 @@ name:                puzzle-draw-version:             0.1.0.1+version:             0.1.0.2 synopsis:            Creating graphics for pencil puzzles. description:         puzzle-draw is a library for drawing pencil puzzles                      using Diagrams. It aims to provide a utility layer@@ -47,7 +47,8 @@                        containers >= 0.5 && < 0.6,                        hashable >= 1.2 && < 1.3,                        text >= 1.1 && < 1.2,-                       SVGFonts >= 1.4 && < 1.5+                       SVGFonts >= 1.4 && < 1.5,+                       vector-space >= 0.8   hs-source-dirs:      src   ghc-options:         -Wall 
src/Data/Puzzles/Compose.hs view
@@ -61,6 +61,8 @@ handle f AfternoonSkyscrapers = f R.afternoonskyscrapers D.afternoonskyscrapers handle f CountNumbers         = f R.countnumbers        D.countnumbers handle f Tapa                 = f R.tapa                D.tapa+handle f JapaneseSums         = f R.japanesesums        D.japanesesums+handle f Coral                = f R.coral               D.coral  -- | Handler that parses a puzzle from a YAML value, and renders. drawPuzzle :: PuzzleHandler b (Value -> Parser (Diagram b R2))
src/Data/Puzzles/Elements.hs view
@@ -55,3 +55,6 @@  newtype TapaClue = TapaClue [Int]     deriving Show++data JapVal = JapBlack | JapInt Int+    deriving Show
src/Data/Puzzles/Grid.hs view
@@ -9,6 +9,7 @@ import Data.Foldable (Foldable, fold) import Data.Traversable (Traversable, traverse) import Control.Applicative ((<$>))+import Data.VectorSpace  import Data.Puzzles.GridShape hiding (size, cells) import qualified Data.Puzzles.GridShape as GS@@ -86,18 +87,30 @@  -- | Clues along the outside of a square grid. data OutsideClues a = OC { left :: [a], right :: [a], bottom :: [a], top :: [a] }-    deriving Show+    deriving (Show, Eq) +instance Functor OutsideClues where+    fmap f (OC l r b t) = OC (fmap f l) (fmap f r) (fmap f b) (fmap f t)++outsideSize :: OutsideClues a -> (Int, Int)+outsideSize (OC l _ _ t) = (length t, length l)+ -- | Convert outside clues to association list mapping coordinate to value.-outsideclues :: OutsideClues (Maybe a) -> [((Int, Int), a)]-outsideclues (OC l r b t) = mapMaybe liftMaybe . concat $+outsideClues :: OutsideClues (Maybe a) -> [((Int, Int), a)]+outsideClues o@(OC l r b t) = mapMaybe liftMaybe . concat $                              [ zipWith (\ y c -> ((-1, y), c)) [0..h-1] l                              , zipWith (\ y c -> (( w, y), c)) [0..h-1] r                              , zipWith (\ x c -> (( x,-1), c)) [0..w-1] b                              , zipWith (\ x c -> (( x, h), c)) [0..w-1] t                              ]   where-    w = length b-    h = length l+    (w, h) = outsideSize o     liftMaybe (p, Just x)  = Just (p, x)     liftMaybe (_, Nothing) = Nothing++multiOutsideClues :: OutsideClues [a] -> [((Int, Int), a)]+multiOutsideClues = concatMap distrib . outsideClues . fmap Just . dired+  where+    dired (OC l r b t) = OC (z (-1,0) l) (z (1,0) r) (z (0,-1) b) (z (0,1) t)+    z x ys = zip (repeat x) ys+    distrib (o, (d, xs)) = zip [o ^+^ i *^ d | i <- [0..]] xs
src/Data/Puzzles/PuzzleTypes.hs view
@@ -35,6 +35,8 @@                 | AfternoonSkyscrapers                 | CountNumbers                 | Tapa+                | JapaneseSums+                | Coral     deriving (Show, Eq)  typeNames :: [(PuzzleType, String)]@@ -62,6 +64,8 @@             , (AfternoonSkyscrapers, "afternoonskyscrapers")             , (CountNumbers, "countnumbers")             , (Tapa, "tapa")+            , (JapaneseSums, "japanesesums")+            , (Coral, "coral")             ]  -- | Look up a puzzle type by name.
src/Diagrams/Puzzles/Draw.hs view
@@ -4,11 +4,18 @@     PuzzleSol,     RenderPuzzle,     OutputChoice(..),-    draw+    draw,+    Unit(..),+    diagramWidth,+    toOutputWidth,   ) where  import Diagrams.Prelude+import Diagrams.BoundingBox +import Diagrams.Puzzles.Lib+import Diagrams.Puzzles.Widths+ type RenderPuzzle b p s = (p -> Diagram b R2, (p, s) -> Diagram b R2)  type PuzzleSol b = (Diagram b R2, Maybe (Diagram b R2))@@ -21,6 +28,38 @@ draw :: (Backend b R2, Renderable (Path R2) b) =>         PuzzleSol b -> OutputChoice -> Maybe (Diagram b R2) draw (p, ms) = fmap (bg white) . d-    where d DrawPuzzle   = Just p-          d DrawSolution = ms-          d DrawExample  = ms >>= \s -> return $ p ||| strutX 2.0 ||| s+  where+    fixup = alignPixel . border borderwidth+    d DrawPuzzle   = Just (fixup p)+    d DrawSolution = fixup <$> ms+    d DrawExample  = ms >>= \s -> return $ fixup p ||| strutX 2.0 ||| fixup s++data Unit = Pixels | Points++cmtopoint :: Double -> Double+cmtopoint = (* 28.3464567)++diagramWidth :: Diagram b R2 -> Double+diagramWidth = fst . unr2 . boxExtents . boundingBox++toOutputWidth :: Unit -> Double -> Double+toOutputWidth u w = case u of Pixels -> fromIntegral wpix+                              Points -> wpt+  where+    wpix = round (gridresd * w) :: Int  -- grid square size 40px+    wpt = cmtopoint (0.8 * w)     -- grid square size 0.8cm++alignPixel :: (Backend b R2, Renderable (Path R2) b) => Diagram b R2 -> Diagram b R2+alignPixel = scale (1/gridresd) . align' . scale gridresd+  where+    align' d = maybe id grow (getCorners $ boundingBox d) $ d+    grow (bl, tr) = mappend $ phantoml (nudge bl False) (nudge tr True)+    nudge p dir = let (px, py) = unp2 p in p2 (nudge' px dir, nudge' py dir)+    nudge' x True  = fromIntegral (ceiling (x - 0.5) :: Int) + 0.5+    nudge' x False = fromIntegral (floor   (x + 0.5) :: Int) - 0.5+    phantoml p q = phantom' $ p ~~ q++-- | Add a phantom border of the given width around a diagram.+border :: (Backend b R2, Renderable (Path R2) b) => Double -> Diagram b R2 -> Diagram b R2+border w = extrudeEnvelope (w *^ unitX) . extrudeEnvelope (-w *^ unitX)+         . extrudeEnvelope (w *^ unitY) . extrudeEnvelope (-w *^ unitY)
src/Diagrams/Puzzles/Elements.hs view
@@ -142,6 +142,8 @@     south = strokeLocLoop shape # lw 0 # fc gray     west = reflectAbout (p2 (0, 0)) (r2 (1, 1)) south +-- | Draws the digits of a tapa clue, ordered+--   left to right, top to bottom. drawTapaClue :: (Backend b R2, Renderable (Path R2) b) =>                 TapaClue -> Diagram b R2 drawTapaClue (TapaClue [x]) = drawInt x@@ -152,7 +154,7 @@   where     p n = centerXY (p' n)     p' 2 = p2 (-1/4, 1/4) ~~ p2 (1/4, -1/4)-    p' 3 = reflectX . rotate (1/6 @@ turn) $ triangle 0.8-    p' 4 = rotate (1/8 @@ turn) $ square 0.7+    p' 3 = reflectX . rotateBy (1/6) $ triangle 0.8+    p' 4 = reflectX . rotateBy (3/8) $ square 0.7     p' 1 = error "singleton clues handled separately"     p' _ = error "invalid tapa clue"
src/Diagrams/Puzzles/Grid.hs view
@@ -19,8 +19,8 @@ -- | Draw a Slither Link style grid of dots of the specified size. slithergrid :: (Backend b R2, Renderable (Path R2) b) =>                Size -> Diagram b R2-slithergrid s@(x, y) = dots <> phantom' (border s)-    where dots = hcatsep . replicate (x + 1) . vcatsep . replicate (y + 1) $ dot+slithergrid (x, y) =+    hcatsep . replicate (x + 1) . vcatsep . replicate (y + 1) $ dot  -- | The inner grid lines of a square grid of the specified size. gridlines :: Size -> Path R2@@ -46,7 +46,6 @@ grid' gridstyle s =     outframe s     <> stroke (gridlines s) # lw gridwidth # gridstyle-    <> phantom' (border s)  -- | Draw a square grid with default grid line style. grid :: (Backend b R2, Renderable (Path R2) b) =>@@ -76,7 +75,7 @@ --   of cells given by coordinates. atCentres :: (Transformable a, Monoid a, V a ~ R2) =>              (t -> a) -> [(Coord, t)] -> a-atCentres dc = translate (r2 (0.5, 0.5)) . atVertices dc+atCentres dc = translate (r2 (1/2, 1/2)) . atVertices dc  atCentres' :: (Transformable a, V a ~ R2) => SGrid a -> [a] atCentres' = translate (r2 (1/2, 1/2)) . atVertices'@@ -89,14 +88,6 @@  atVertices' :: (Transformable a, V a ~ R2) => SGrid a -> [a] atVertices' g = [ (g ! c) # translatep c | c <- cells g ]---- | Frame a rectangle of the given size with a border of width---   'borderwidth'.-border :: (Backend b R2, Renderable (Path R2) b) => Size -> Diagram b R2-border (w, h) = stroke . translate (r2 (-bw, -bw)) . alignBL-               $ rect (fromIntegral w + 2 * bw) (fromIntegral h + 2 * bw)-  where-    bw = borderwidth  edge :: Edge -> Path R2 edge (E c d) = rule d # translate (r2i c)
src/Diagrams/Puzzles/PuzzleGrids.hs view
@@ -63,14 +63,13 @@                  (t -> Diagram b R2) -> SGrid (Tightfit t) -> Diagram b R2 drawTightGrid d g = atCentres (drawTight d) (values g)                     <> grid (size g)-                    <> phantom' (border (sx + 2, sy + 2) # translate (r2 (-1,-1)))+                    <> phantom' (stroke $ p2i (-1,-1) ~~ p2i (sx + 1, sy + 1))     where (sx, sy) = size g  drawSlalomGrid :: (Backend b R2, Renderable (Path R2) b) =>                   SGrid (Clue Int) -> Diagram b R2 drawSlalomGrid g = atVertices drawSlalomClue (clues g)                    <> grid (w-1, h-1)-                   <> phantom' (border (size g)) # translate (r2 (-1/2,-1/2))     where (w, h) = size g  drawSlalomDiags :: (Backend b R2, Renderable (Path R2) b) =>@@ -84,3 +83,8 @@ drawCrosses = atCentres (if' drawCross mempty) . values   where     if' a b x = if x then a else b++outsideIntGrid :: (Backend b R2, Renderable (Path R2) b) =>+                  OutsideClues [Int] -> Diagram b R2+outsideIntGrid = atCentres (scale 0.8 . drawInt) . multiOutsideClues+                 <> grid . outsideSize
src/Diagrams/Puzzles/PuzzleTypes.hs view
@@ -6,10 +6,11 @@     sudoku, thermosudoku, pyramid, kpyramid, slither,     liarslither, tightfitskyscrapers, wordloop, wordsearch,     curvedata, doubleback, slalom, compass, boxof2or3,-    afternoonskyscrapers, countnumbers, tapa,+    afternoonskyscrapers, countnumbers, tapa, japanesesums,+    coral,   ) where -import Diagrams.Prelude hiding (Loop)+import Diagrams.Prelude hiding (Loop, coral)  import Diagrams.Puzzles.PuzzleGrids import Diagrams.Puzzles.Draw@@ -112,9 +113,9 @@                        RenderPuzzle b (OutsideClues (Maybe Int), SGrid (Tightfit ()))                                       (SGrid (Tightfit Int)) tightfitskyscrapers = (,)-    (atCentres drawInt . outsideclues . fst+    (atCentres drawInt . outsideClues . fst      <> drawTightGrid (const mempty) . snd)-    (atCentres drawInt . outsideclues . fst . fst+    (atCentres drawInt . outsideClues . fst . fst      <> drawTightGrid drawInt . snd)  wordgrid :: (Backend b R2, Renderable (Path R2) b) =>@@ -186,3 +187,19 @@     (tapaGrid . fst <> drawShadedGrid . snd)   where     tapaGrid = atCentres drawTapaClue . values <> grid . size++japanesesums :: (Backend b R2, Renderable (Path R2) b) =>+                RenderPuzzle b (OutsideClues [Int]) (SGrid JapVal)+japanesesums = (,)+    outsideIntGrid+    (japcells . snd <> outsideIntGrid . fst)+  where+    japcells = atCentres japcell . values+    japcell JapBlack = fillBG gray+    japcell (JapInt x) = drawInt x++coral :: (Backend b R2, Renderable (Path R2) b) =>+                RenderPuzzle b (OutsideClues [Int]) ShadedGrid+coral = (,)+    outsideIntGrid+    (drawShadedGrid . snd <> outsideIntGrid . fst)
src/Diagrams/Puzzles/Pyramid.hs view
@@ -9,7 +9,6 @@ import Data.Puzzles.Pyramid import Diagrams.Puzzles.Lib import Diagrams.Puzzles.Widths-import Diagrams.Puzzles.Grid (border)  pgray :: Colour Double pgray = blend 0.6 white black@@ -28,8 +27,7 @@ row (R cs s) = centerX . hcat . map (cellc s) $ cs  pyramid :: (Backend b R2, Renderable (Path R2) b) => Pyramid -> Diagram b R2-pyramid p = phantom' (border (s, s)) <> (alignBL . vcat . map row . unPyr $ p)-    where s = psize p+pyramid = alignBL . vcat . map row . unPyr  kropki :: (Backend b R2, Renderable (Path R2) b) => KropkiDot -> Diagram b R2 kropki None = mempty@@ -46,5 +44,4 @@           dots = interleave (map phantom' clues') (map kropki ks)  kpyramid :: (Backend b R2, Renderable (Path R2) b) => RowKropkiPyramid -> Diagram b R2-kpyramid p = phantom' (border (s, s)) <> (alignBL . vcat . map krow . unKP $ p)-    where s = psize (plainpyramid p)+kpyramid = alignBL . vcat . map krow . unKP
src/Diagrams/Puzzles/Widths.hs view
@@ -3,6 +3,9 @@ gridres :: Int gridres = 40 +gridresd :: Double+gridresd = fromIntegral gridres+ onepix :: Double onepix = 1 / fromIntegral gridres @@ -18,4 +21,4 @@  edgewidth, borderwidth :: Double edgewidth = 3 * onepix-borderwidth = 1 / 4 + onepix / 2+borderwidth = 1 / 2
src/Text/Puzzles/PuzzleTypes.hs view
@@ -5,7 +5,7 @@     sudoku, thermosudoku, pyramid, kpyramid, slither,     liarslither, tightfitskyscrapers, wordloop, wordsearch,     curvedata, doubleback, slalom, compass, boxof2or3,-    afternoonskyscrapers, countnumbers, tapa,+    afternoonskyscrapers, countnumbers, tapa, japanesesums, coral,   ) where  import Prelude hiding (sequence)@@ -123,5 +123,11 @@ countnumbers = (parseGrid, parseGrid)  tapa :: ParsePuzzle (SGrid TapaClue) ShadedGrid-tapa = (\v -> unRG <$> parseJSON v,+tapa = (\v -> fmap unParseTapaClue . unRG <$> parseJSON v,         parseShadedGrid)++japanesesums :: ParsePuzzle (OutsideClues [Int]) (SGrid JapVal)+japanesesums = (parseMultiOutsideClues, parseGrid)++coral :: ParsePuzzle (OutsideClues [Int]) ShadedGrid+coral = (parseMultiOutsideClues, parseShadedGrid)
src/Text/Puzzles/Util.hs view
@@ -9,7 +9,7 @@ import Control.Monad hiding (sequence)  import Data.Hashable-import Data.Maybe (catMaybes)+import Data.Maybe (catMaybes, fromMaybe) import qualified Data.Map as Map import qualified Data.HashMap.Strict as HMap import Data.Traversable (traverse, sequence, sequenceA, Traversable)@@ -245,6 +245,10 @@                 | c `elem` "─└┌"  = pure . HalfDirs $ [H]                 | otherwise       = pure . HalfDirs $ [] +instance FromChar JapVal where+    parseChar c | c `elem` ['x', 'X']  = pure JapBlack+    parseChar c = JapInt <$> parseChar c+ -- parses a string like --  ┌┐┌─┐ --  ││└┐│@@ -397,7 +401,17 @@     splitBorder (Square w h) = Map.partitionWithKey         (\(x, y) _ -> x < w - 1 && y < h - 1) -instance FromJSON TapaClue where+newtype ParseTapaClue = ParseTapaClue { unParseTapaClue :: TapaClue }++instance FromJSON ParseTapaClue where     parseJSON v = do xs <- parseJSON v                      guard $ length xs > 0 && length xs <= 4-                     return $ TapaClue xs+                     return . ParseTapaClue . TapaClue $ xs++parseMultiOutsideClues :: FromJSON a => Value -> Parser (OutsideClues [a])+parseMultiOutsideClues (Object v) = rev <$> raw+  where+    raw = OC <$> v `ml` "left" <*> v `ml` "right" <*> v `ml` "bottom" <*> v `ml` "top"+    v' `ml` k = fromMaybe [] <$> v' .:? k+    rev (OC l r b t) = OC (reverse . map reverse $ l) (reverse r) b (map reverse t)+parseMultiOutsideClues _ = empty
tests/tests.hs view
@@ -8,10 +8,11 @@  import Text.Puzzles.Puzzle import Data.Puzzles.Elements (Thermometer)-import Text.Puzzles.Util (parseChar)+import Text.Puzzles.Util (parseChar, parseMultiOutsideClues) import Text.Puzzles.PuzzleTypes import qualified Data.Puzzles.Grid as Grid import Data.Puzzles.Pyramid (PyramidSol(..))+import Data.Puzzles.Grid (multiOutsideClues, OutsideClues(..))  import Diagrams.Puzzles.PuzzleGrids import Diagrams.Prelude@@ -27,7 +28,7 @@  tests :: TestTree tests = testGroup "Tests"-    [ parseUtilTests, parseTests, parseDataTests, renderTests ]+    [ parseUtilTests, parseTests, parseDataTests, renderTests, dataTests ]  testParsePzl, testParseSol, testNonparsePzl, testNonparseSol ::     (Show a, Show b) => String -> ParsePuzzle a b -> Value -> TestTree@@ -109,6 +110,13 @@     test_content (PyramidSol rs) =         rs == [[3], [8,5], [1,9,4], [3,2,7,3], [1,2,4,3,6]] +test_multioutside :: Assertion+test_multioutside = Right oc @=? res+  where+    res = parseEither parseMultiOutsideClues multioutside+    oc = OC [[3], [1, 2]] [[1, 0], []] [[0, 0, 1]] [[1, -1]]+         :: OutsideClues [Int]+ parseDataTests :: TestTree parseDataTests = testGroup "Parsing tests (full puzzles, result checks)"     [ testCase "parse tightfit, correct size" $ test_tightfit_1 @? "error in puzzle"@@ -120,6 +128,7 @@                                             [ [(0, 1), (1, 0), (2, 0)]                                             , [(0, 2), (1, 3), (2, 4)]                                             , [(4, 0), (4, 1), (3, 2)] ]+    , testCase "parse multioutsideclues" $ test_multioutside     ]  -- this used to cause a rendering crash, though it's@@ -137,4 +146,21 @@ renderTests = testGroup "Rendering tests"     [ testCase "don't break rendering invalid slalom solution"          $ testBreakSlalom @? "just testing against errors"+    ]++testMultiOutsideClues :: Assertion+testMultiOutsideClues = multiOutsideClues (OC l r b t) `sorteq` res+  where+    sorteq xs ys = sort xs @?= sort ys+    l = [[1, 2], [3]]+    r = [[], [1, 0]]+    b = [[0, 0, 1]]+    t = [[1, -1]]+    res = [ ((-1,0), 1), ((-2,0), 2), ((-1,1), 3),+            ((1,1), 1), ((2,1), 0), ((0,-1), 0), ((0,-2), 0), ((0,-3), 1),+            ((0,2), 1), ((0,3), -1) ] :: [((Int, Int), Int)]++dataTests :: TestTree+dataTests = testGroup "Generic tests for the Data modules"+    [ testCase "multiOutsideClues" testMultiOutsideClues     ]