puzzle-draw 0.1.0.2 → 0.1.0.3
raw patch · 14 files changed
+286/−41 lines, 14 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Data.Puzzles.GridShape: neighbours :: GridShape a => a -> Cell a -> [Cell a]
+ Data.Puzzles.Elements: PrimeDiag :: (Bool, Bool) -> PrimeDiag
+ Data.Puzzles.Elements: newtype PrimeDiag
+ Data.Puzzles.Grid: filterG :: (a -> Bool) -> (Grid s a) -> (Grid s a)
+ Data.Puzzles.GridShape: D :: Dir'
+ Data.Puzzles.GridShape: E' :: a -> Dir' -> Edge' a
+ Data.Puzzles.GridShape: L :: Dir'
+ Data.Puzzles.GridShape: R :: Dir'
+ Data.Puzzles.GridShape: U :: Dir'
+ Data.Puzzles.GridShape: data Dir'
+ Data.Puzzles.GridShape: data Edge' a
+ Data.Puzzles.GridShape: edgeNeighbours :: GridShape a => a -> Cell a -> [Cell a]
+ Data.Puzzles.GridShape: edges :: Foldable f => f (Cell Square) -> (Cell Square -> Bool) -> ([Edge' (Vertex Square)], [Edge' (Vertex Square)])
+ Data.Puzzles.GridShape: instance Eq Dir'
+ Data.Puzzles.GridShape: instance Eq a => Eq (Edge' a)
+ Data.Puzzles.GridShape: instance Ord Dir'
+ Data.Puzzles.GridShape: instance Ord a => Ord (Edge' a)
+ Data.Puzzles.GridShape: instance Show Dir'
+ Data.Puzzles.GridShape: instance Show a => Show (Edge' a)
+ Data.Puzzles.GridShape: orientedEdge :: (Cell Square) -> (Cell Square) -> (Edge' (Vertex Square))
+ Data.Puzzles.GridShape: squareNeighbours :: [(Int, Int)] -> Square -> (Cell Square) -> [Cell Square]
+ Data.Puzzles.GridShape: vertexNeighbours :: GridShape a => a -> Cell a -> [Cell a]
+ Data.Puzzles.PuzzleTypes: Labyrinth :: PuzzleType
+ Data.Puzzles.PuzzleTypes: MaximalLengths :: PuzzleType
+ Data.Puzzles.PuzzleTypes: PrimePlace :: PuzzleType
+ Diagrams.Puzzles.Elements: drawPrimeDiag :: (Backend b R2, Renderable (Path R2) b) => PrimeDiag -> Diagram b R2
+ Diagrams.Puzzles.Grid: edgePath :: Edge' (Vertex Square) -> Path R2
+ Diagrams.Puzzles.Grid: fence :: [Double] -> Double -> Path R2
+ Diagrams.Puzzles.Grid: fullgridlines :: Size -> Path R2
+ Diagrams.Puzzles.Grid: irregularGrid :: (Backend b R2, Renderable (Path R2) b) => SGrid a -> Diagram b R2
+ Diagrams.Puzzles.Grid: irregularGridPaths :: SGrid a -> (Path R2, Path R2)
+ Diagrams.Puzzles.Grid: plaingrid :: (Backend b R2, Renderable (Path R2) b) => Size -> Diagram b R2
+ Diagrams.Puzzles.Lib: mirror :: (Transformable t, V t ~ R2) => t -> t
+ Diagrams.Puzzles.PuzzleGrids: outsideGrid :: (Backend b R2, Renderable (Path R2) b) => OutsideClues [String] -> Diagram b R2
+ Diagrams.Puzzles.PuzzleTypes: labyrinth :: (Backend b R2, Renderable (Path R2) b) => RenderPuzzle b (SGrid (Clue Int), [Edge]) (SGrid (Clue Int))
+ Diagrams.Puzzles.PuzzleTypes: maximallengths :: (Backend b R2, Renderable (Path R2) b) => RenderPuzzle b (OutsideClues (Maybe Int)) Loop
+ Diagrams.Puzzles.PuzzleTypes: primeplace :: (Backend b R2, Renderable (Path R2) b) => RenderPuzzle b (SGrid PrimeDiag) (SGrid Int)
+ Text.Puzzles.PuzzleTypes: labyrinth :: ParsePuzzle (SGrid (Clue Int), [Edge]) (SGrid (Clue Int))
+ Text.Puzzles.PuzzleTypes: maximallengths :: ParsePuzzle (OutsideClues (Maybe Int)) Loop
+ Text.Puzzles.PuzzleTypes: primeplace :: ParsePuzzle (SGrid PrimeDiag) (SGrid Int)
+ Text.Puzzles.Util: Empty :: Empty
+ Text.Puzzles.Util: PlainNode :: PlainNode
+ Text.Puzzles.Util: blankToMaybe :: Either Blank a -> Maybe a
+ Text.Puzzles.Util: blankToMaybe' :: Either Blank' a -> Maybe a
+ Text.Puzzles.Util: data Empty
+ Text.Puzzles.Util: data PlainNode
+ Text.Puzzles.Util: instance FromChar Empty
+ Text.Puzzles.Util: instance FromChar PlainNode
+ Text.Puzzles.Util: instance FromChar PrimeDiag
+ Text.Puzzles.Util: parseCellEdges :: FromChar a => Value -> Parser (SGrid a, [Edge])
+ Text.Puzzles.Util: parseCharOutside :: FromChar a => Value -> Parser (OutsideClues a)
+ Text.Puzzles.Util: parseClueGrid' :: FromChar a => Value -> Parser (SGrid (Clue a))
+ Text.Puzzles.Util: parseEdgeGrid :: (FromChar a, FromChar b) => Value -> Parser (SGrid a, SGrid b, [Edge])
+ Text.Puzzles.Util: parseIrregGrid :: FromChar a => Value -> Parser (SGrid a)
+ Text.Puzzles.Util: parseLine :: FromChar a => String -> Parser [a]
+ Text.Puzzles.Util: rectToClueGrid' :: Rect (Either Blank' a) -> SGrid (Clue a)
+ Text.Puzzles.Util: rectToIrregGrid :: Rect (Either Empty a) -> SGrid a
+ Text.Puzzles.Util: reorientOutside :: OutsideClues a -> OutsideClues a
- Diagrams.Puzzles.PuzzleTypes: coral :: (Backend b R2, Renderable (Path R2) b) => RenderPuzzle b (OutsideClues [Int]) ShadedGrid
+ Diagrams.Puzzles.PuzzleTypes: coral :: (Backend b R2, Renderable (Path R2) b) => RenderPuzzle b (OutsideClues [String]) ShadedGrid
- Text.Puzzles.PuzzleTypes: coral :: ParsePuzzle (OutsideClues [Int]) ShadedGrid
+ Text.Puzzles.PuzzleTypes: coral :: ParsePuzzle (OutsideClues [String]) ShadedGrid
Files
- CHANGES.md +6/−0
- puzzle-draw.cabal +1/−1
- src/Data/Puzzles/Compose.hs +3/−0
- src/Data/Puzzles/Elements.hs +3/−0
- src/Data/Puzzles/Grid.hs +3/−0
- src/Data/Puzzles/GridShape.hs +58/−9
- src/Data/Puzzles/PuzzleTypes.hs +6/−0
- src/Diagrams/Puzzles/Elements.hs +9/−0
- src/Diagrams/Puzzles/Grid.hs +39/−4
- src/Diagrams/Puzzles/Lib.hs +3/−0
- src/Diagrams/Puzzles/PuzzleGrids.hs +6/−0
- src/Diagrams/Puzzles/PuzzleTypes.hs +29/−4
- src/Text/Puzzles/PuzzleTypes.hs +12/−1
- src/Text/Puzzles/Util.hs +108/−22
CHANGES.md view
@@ -1,3 +1,9 @@+0.1.0.3: 20140519+-----------------++* add new puzzle types: Maximal Lengths, Prime Place, Magic Labyrinth++ 0.1.0.2: 20140419 -----------------
puzzle-draw.cabal view
@@ -1,5 +1,5 @@ name: puzzle-draw-version: 0.1.0.2+version: 0.1.0.3 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
src/Data/Puzzles/Compose.hs view
@@ -63,6 +63,9 @@ handle f Tapa = f R.tapa D.tapa handle f JapaneseSums = f R.japanesesums D.japanesesums handle f Coral = f R.coral D.coral+handle f MaximalLengths = f R.maximallengths D.maximallengths+handle f PrimePlace = f R.primeplace D.primeplace+handle f Labyrinth = f R.labyrinth D.labyrinth -- | 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
@@ -58,3 +58,6 @@ data JapVal = JapBlack | JapInt Int deriving Show++-- | Diagonal marking for Prime Place: forward diag?, backward diag?+newtype PrimeDiag = PrimeDiag (Bool, Bool)
src/Data/Puzzles/Grid.hs view
@@ -44,6 +44,9 @@ instance Traversable (Grid s) where traverse f (Grid s m) = Grid s <$> (traverse f m) +filterG :: (a -> Bool) -> (Grid s a) -> (Grid s a)+filterG p (Grid s m) = Grid s (Map.filter p m)+ -- | Initialize a square grid from a list of lists. The grid -- might be incomplete if some rows are shorter. fromListList :: [[a]] -> Grid Square a
src/Data/Puzzles/GridShape.hs view
@@ -3,6 +3,11 @@ -- | Grid shapes. module Data.Puzzles.GridShape where +import Data.Foldable (Foldable)+import qualified Data.Foldable as F+import Data.List (partition)+import Data.VectorSpace ((^+^))+ -- | The geometry of a grid. class Show (Cell a) => GridShape a where type GridSize a :: *@@ -12,7 +17,8 @@ size :: a -> GridSize a cells :: a -> [Cell a] vertices :: a -> [Vertex a]- neighbours :: a -> Cell a -> [Cell a]+ vertexNeighbours :: a -> Cell a -> [Cell a]+ edgeNeighbours :: a -> Cell a -> [Cell a] -- | A standard square grid, with cells and vertices -- indexed by pairs of integers in mathematical coordinates.@@ -21,6 +27,12 @@ data Square = Square !Int !Int deriving Show +squareNeighbours :: [(Int, Int)] -> Square -> (Cell Square) -> [Cell Square]+squareNeighbours deltas (Square w h) c = filter inBounds . map (add c) $ deltas+ where+ inBounds (x, y) = x >= 0 && x < w && y >= 0 && y < h+ add (x, y) (x', y') = (x + x', y + y')+ instance GridShape Square where type GridSize Square = (Int, Int) type Cell Square = (Int, Int)@@ -29,14 +41,15 @@ size (Square w h) = (w, h) cells (Square w h) = [(x, y) | x <- [0..w-1], y <- [0..h-1]] vertices (Square w h) = [(x, y) | x <- [0..w], y <- [0..h]]- neighbours (Square w h) c = filter inBounds . map (add c) $ deltas- where- inBounds (x, y) = x >= 0 && x < w && y >= 0 && y < h- deltas = [ (dx, dy)- | dx <- [-1..1], dy <- [-1..1]- , dx /= 0 || dy /= 0- ]- add (x, y) (x', y') = (x + x', y + y')+ vertexNeighbours = squareNeighbours [ (dx, dy)+ | dx <- [-1..1], dy <- [-1..1]+ , dx /= 0 || dy /= 0+ ]+ edgeNeighbours = squareNeighbours [ (dx, dy)+ | dx <- [-1..1], dy <- [-1..1]+ , dx /= 0 || dy /= 0+ , dx == 0 || dy == 0+ ] -- | Edge direction in a square grid, vertical or horizontal. data Dir = V | H@@ -49,3 +62,39 @@ type Coord = Cell Square type Size = GridSize Square++-- | Oriented edge direction in a square grid.+data Dir' = U | D | L | R+ deriving (Eq, Ord, Show)++-- | An oriented edge in a square grid.+-- @a@ should be @Cell Square@ or @Vertex Square@.+data Edge' a = E' a Dir'+ deriving (Eq, Ord, Show)++-- | The edge between two neighbouring cells, with the first cell+-- on the left.+orientedEdge :: (Cell Square) -> (Cell Square) -> (Edge' (Vertex Square))+orientedEdge (x,y) (x',y')+ | x' == x && y' == y+1 = E' (x,y+1) R+ | x' == x && y' == y-1 = E' (x+1,y) L+ | x' == x+1 && y' == y = E' (x+1,y+1) D+ | x' == x-1 && y' == y = E' (x,y) U+ | otherwise = error $ "not neighbours: " +++ show (x,y) ++ " " ++ show (x',y')++-- | @edges@ computes the outer and inner edges of a set of cells.+-- The set is given via fold and membership predicate, the result+-- is a pair @(outer, inner)@ of lists of edges, where the outer+-- edges are oriented such that the outside is to the left.+edges :: Foldable f =>+ f (Cell Square) -> (Cell Square -> Bool) ->+ ([Edge' (Vertex Square)], [Edge' (Vertex Square)])+edges cs isc = F.foldr f ([], []) cs+ where+ f c (outer, inner) = (newout ++ outer, newin ++ inner)+ where+ nbrs = [ c ^+^ d | d <- [(-1,0), (0,1), (1,0), (0,-1)] ]+ (ni, no) = partition isc nbrs+ newout = map (orientedEdge c) no+ newin = map (orientedEdge c) . filter ((>=) c) $ ni
src/Data/Puzzles/PuzzleTypes.hs view
@@ -37,6 +37,9 @@ | Tapa | JapaneseSums | Coral+ | MaximalLengths+ | PrimePlace+ | Labyrinth deriving (Show, Eq) typeNames :: [(PuzzleType, String)]@@ -66,6 +69,9 @@ , (Tapa, "tapa") , (JapaneseSums, "japanesesums") , (Coral, "coral")+ , (MaximalLengths, "maximallengths")+ , (PrimePlace, "primeplace")+ , (Labyrinth, "magiclabyrinth") ] -- | Look up a puzzle type by name.
src/Diagrams/Puzzles/Elements.hs view
@@ -158,3 +158,12 @@ p' 4 = reflectX . rotateBy (3/8) $ square 0.7 p' 1 = error "singleton clues handled separately" p' _ = error "invalid tapa clue"++drawPrimeDiag :: (Backend b R2, Renderable (Path R2) b) =>+ PrimeDiag -> Diagram b R2+drawPrimeDiag (PrimeDiag d) = stroke p # lw (3 * onepix) # lc (blend 0.5 gray white)+ where+ p = case d of (False, False) -> mempty+ (True, False) -> ur+ (False, True) -> dr+ (True, True) -> ur <> dr
src/Diagrams/Puzzles/Grid.hs view
@@ -3,6 +3,7 @@ module Diagrams.Puzzles.Grid where import Data.Char (isUpper)+import qualified Data.Map as M import Diagrams.Prelude @@ -22,13 +23,23 @@ slithergrid (x, y) = hcatsep . replicate (x + 1) . vcatsep . replicate (y + 1) $ dot +fence :: [Double] -> Double -> Path R2+fence xs h = decoratePath xspath (repeat v)+ where+ xspath = fromVertices [ p2 (x, 0) | x <- xs ]+ v = alignB (vrule h)+ -- | The inner grid lines of a square grid of the specified size. gridlines :: Size -> Path R2-gridlines (w, h) = (decoratePath xaxis . repeat . alignB . vrule . fromIntegral $ h)- <> (decoratePath yaxis . repeat . alignL . hrule . fromIntegral $ w)- where xaxis = fromVertices [ p2 (fromIntegral x, 0) | x <- [1..w-1] ]- yaxis = fromVertices [ p2 (0, fromIntegral y) | y <- [1..h-1] ]+gridlines (w, h) = fence' w h <> mirror (fence' h w)+ where+ fence' n l = fence (map fromIntegral [1..n-1]) (fromIntegral l) +fullgridlines :: Size -> Path R2+fullgridlines (w, h) = fence' w h <> mirror (fence' h w)+ where+ fence' n l = fence (map fromIntegral [0..n]) (fromIntegral l)+ -- | Draw a frame around the outside of a rectangle. outframe :: Renderable (Path R2) b => Size -> Diagram b R2 outframe (w, h) = strokePointLoop r # lw fw@@ -52,6 +63,11 @@ Size -> Diagram b R2 grid = grid' id +-- | Draw a square grid with thin frame.+plaingrid :: (Backend b R2, Renderable (Path R2) b) =>+ Size -> Diagram b R2+plaingrid s = stroke (fullgridlines s) # lw gridwidth+ bgdashing :: (Semigroup a, HasStyle a) => [Double] -> Double -> Colour Double -> a -> a bgdashing ds offs c x = x # dashing ds offs <> x # lc c@@ -70,6 +86,25 @@ dashedgrid = grid' $ bgdashing dashes dashoffset white' where white' = blend 0.95 white black++edgePath :: Edge' (Vertex Square) -> Path R2+edgePath (E' v R) = p2i v ~~ p2i (v ^+^ (1,0))+edgePath (E' v L) = p2i v ~~ p2i (v ^+^ (-1,0))+edgePath (E' v U) = p2i v ~~ p2i (v ^+^ (0,1))+edgePath (E' v D) = p2i v ~~ p2i (v ^+^ (0,-1))++irregularGridPaths :: SGrid a -> (Path R2, Path R2)+irregularGridPaths (Grid _ m) = (toPath outer, toPath inner)+ where+ (outer, inner) = edges (M.keysSet m) (flip M.member m)+ toPath = mconcat . map edgePath++irregularGrid :: (Backend b R2, Renderable (Path R2) b) =>+ SGrid a -> Diagram b R2+irregularGrid g = stroke outer # lw (3 * gridwidth) # lineCap LineCapSquare <>+ stroke inner # lw gridwidth+ where+ (outer, inner) = irregularGridPaths g -- | In a square grid, use the first argument to draw things at the centres -- of cells given by coordinates.
src/Diagrams/Puzzles/Lib.hs view
@@ -38,6 +38,9 @@ p2i :: (Int, Int) -> P2 p2i = p2 . (fromIntegral *** fromIntegral) +mirror :: (Transformable t, V t ~ R2) => t -> t+mirror = reflectAbout (p2 (0, 0)) (r2 (1, 1))+ -- | Interleave two lists. interleave :: [a] -> [a] -> [a] interleave [] _ = []
src/Diagrams/Puzzles/PuzzleGrids.hs view
@@ -84,6 +84,12 @@ where if' a b x = if x then a else b ++outsideGrid :: (Backend b R2, Renderable (Path R2) b) =>+ OutsideClues [String] -> Diagram b R2+outsideGrid = atCentres (scale 0.8 . drawText) . multiOutsideClues+ <> grid . outsideSize+ outsideIntGrid :: (Backend b R2, Renderable (Path R2) b) => OutsideClues [Int] -> Diagram b R2 outsideIntGrid = atCentres (scale 0.8 . drawInt) . multiOutsideClues
src/Diagrams/Puzzles/PuzzleTypes.hs view
@@ -7,7 +7,7 @@ liarslither, tightfitskyscrapers, wordloop, wordsearch, curvedata, doubleback, slalom, compass, boxof2or3, afternoonskyscrapers, countnumbers, tapa, japanesesums,- coral,+ coral, maximallengths, primeplace, labyrinth ) where import Diagrams.Prelude hiding (Loop, coral)@@ -199,7 +199,32 @@ japcell (JapInt x) = drawInt x coral :: (Backend b R2, Renderable (Path R2) b) =>- RenderPuzzle b (OutsideClues [Int]) ShadedGrid+ RenderPuzzle b (OutsideClues [String]) ShadedGrid coral = (,)- outsideIntGrid- (drawShadedGrid . snd <> outsideIntGrid . fst)+ outsideGrid+ (drawShadedGrid . snd <> outsideGrid . fst)++maximallengths :: (Backend b R2, Renderable (Path R2) b) =>+ RenderPuzzle b (OutsideClues (Maybe Int)) Loop+maximallengths = (,)+ g+ (solstyle . drawDualEdges . snd <> g . fst)+ where+ g = atCentres drawInt . outsideClues+ <> grid . outsideSize++primeplace :: (Backend b R2, Renderable (Path R2) b) =>+ RenderPuzzle b (SGrid PrimeDiag) (SGrid Int)+primeplace = (,)+ g+ (atCentres drawInt . values . snd <> g . fst)+ where+ g = irregularGrid <> atCentres drawPrimeDiag . values++labyrinth :: (Backend b R2, Renderable (Path R2) b) =>+ RenderPuzzle b (SGrid (Clue Int), [Edge]) (SGrid (Clue Int))+labyrinth = (,)+ (atCentres drawInt . clues . fst <> g)+ (atCentres drawInt . clues . snd <> g . fst)+ where+ g = drawEdges . snd <> plaingrid . size . fst
src/Text/Puzzles/PuzzleTypes.hs view
@@ -6,6 +6,7 @@ liarslither, tightfitskyscrapers, wordloop, wordsearch, curvedata, doubleback, slalom, compass, boxof2or3, afternoonskyscrapers, countnumbers, tapa, japanesesums, coral,+ maximallengths, primeplace, labyrinth ) where import Prelude hiding (sequence)@@ -129,5 +130,15 @@ japanesesums :: ParsePuzzle (OutsideClues [Int]) (SGrid JapVal) japanesesums = (parseMultiOutsideClues, parseGrid) -coral :: ParsePuzzle (OutsideClues [Int]) ShadedGrid+coral :: ParsePuzzle (OutsideClues [String]) ShadedGrid coral = (parseMultiOutsideClues, parseShadedGrid)++maximallengths :: ParsePuzzle (OutsideClues (Maybe Int)) Loop+maximallengths = (\v -> fmap blankToMaybe <$> parseCharOutside v,+ parseEdges)++primeplace :: ParsePuzzle (SGrid PrimeDiag) (SGrid Int)+primeplace = (parseIrregGrid, parseIrregGrid)++labyrinth :: ParsePuzzle (SGrid (Clue Int), [Edge]) (SGrid (Clue Int))+labyrinth = (parseCellEdges, parseClueGrid')
src/Text/Puzzles/Util.hs view
@@ -36,6 +36,9 @@ class FromString a where parseString :: String -> Parser a +parseLine :: FromChar a => String -> Parser [a]+parseLine = mapM parseChar+ instance FromChar Int where parseChar c | isDigit c = digitToInt <$> parseChar c@@ -114,11 +117,6 @@ p = sequence . map (mapM (parseString . T.unpack)) $ ls parseJSON _ = empty -instance FromChar MasyuPearl where- parseChar '*' = pure MBlack- parseChar 'o' = pure MWhite- parseChar _ = empty- data Space = Space instance FromChar Space where@@ -127,6 +125,7 @@ data Blank = Blank data Blank' = Blank'+data Empty = Empty instance FromChar Blank where parseChar '.' = pure Blank@@ -137,10 +136,25 @@ parseChar '-' = pure Blank' parseChar _ = empty +instance FromChar Empty where+ parseChar ' ' = pure Empty+ parseChar _ = empty+ instance FromString Blank where parseString "." = pure Blank parseString _ = empty +data PlainNode = PlainNode++instance FromChar PlainNode where+ parseChar 'o' = pure PlainNode+ parseChar _ = empty++instance FromChar MasyuPearl where+ parseChar '*' = pure MBlack+ parseChar 'o' = pure MWhite+ parseChar _ = empty+ instance FromChar SlalomDiag where parseChar '/' = pure SlalomForward parseChar '\\' = pure SlalomBackward@@ -165,9 +179,25 @@ rectToSGrid :: Rect a -> SGrid a rectToSGrid (Rect w h ls) = Grid (Square w h) (listListToMap ls) +blankToMaybe :: Either Blank a -> Maybe a+blankToMaybe = either (const Nothing) Just++blankToMaybe' :: Either Blank' a -> Maybe a+blankToMaybe' = either (const Nothing) Just+ rectToClueGrid :: Rect (Either Blank a) -> SGrid (Clue a)-rectToClueGrid = fmap (either (const Nothing) Just) . rectToSGrid+rectToClueGrid = fmap blankToMaybe . rectToSGrid +rectToClueGrid' :: Rect (Either Blank' a) -> SGrid (Clue a)+rectToClueGrid' = fmap blankToMaybe' . rectToSGrid++rectToIrregGrid :: Rect (Either Empty a) -> SGrid a+rectToIrregGrid = fmap fromRight . filterG isRight . rectToSGrid+ where+ isRight = either (const False) (const True)+ fromRight (Right r) = r+ fromRight _ = error "no way"+ newtype Shaded = Shaded { unShaded :: Bool } instance FromChar Shaded where@@ -184,6 +214,12 @@ parseClueGrid :: FromChar a => Value -> Parser (SGrid (Clue a)) parseClueGrid v = rectToClueGrid <$> parseJSON v +parseClueGrid' :: FromChar a => Value -> Parser (SGrid (Clue a))+parseClueGrid' v = rectToClueGrid' <$> parseJSON v++parseIrregGrid :: FromChar a => Value -> Parser (SGrid a)+parseIrregGrid v = rectToIrregGrid <$> parseJSON v+ parseSpacedClueGrid :: FromString a => Value -> Parser (SGrid (Clue a)) parseSpacedClueGrid v = rectToClueGrid . unSpaced <$> parseJSON v @@ -215,6 +251,38 @@ parseGridChars :: FromChar a => SGrid Char -> Parser (SGrid a) parseGridChars = traverse parseChar +-- | Parse a grid with edges and values at nodes and in cells.+--+-- E.g. o-*-*-o+-- |1|2 3+-- *-o+-- to a grid of masyu pearls, a grid of integers, and some edges.+parseEdgeGrid :: (FromChar a, FromChar b) =>+ Value -> Parser (SGrid a, SGrid b, [Edge])+parseEdgeGrid v = uncurry (,,) <$>+ parseBoth <*>+ parsePlainEdges v+ where+ parseBoth = do+ g <- parseGrid v+ (gn, gc) <- halveGrid g+ gn' <- parseGridChars gn+ gc' <- parseGridChars gc+ return (gn', gc')+ both f (x, y) = (f x, f y)+ halveGrid (Grid (Square w h) m)+ | odd w && odd h = pure $ (Grid snode mnode, Grid scell mcell)+ | otherwise = empty+ where+ w' = (w + 1) `div` 2+ h' = (h + 1) `div` 2+ snode = Square w' h'+ scell = Square (w' - 1) (h' - 1)+ (mnode, mcell) =+ both (Map.mapKeys (both (`div` 2))) .+ Map.partitionWithKey (const . uncurry (&&) . both even) $+ m+ -- | Parse a grid of edges with values at the nodes. -- -- E.g. o-*-*-o@@ -223,20 +291,18 @@ -- to a grid of masyu pearls and some edges. parseNodeEdges :: FromChar a => Value -> Parser (SGrid a, [Edge])-parseNodeEdges v = (,) <$>- (parseGridChars =<< halveGrid =<< parseGrid v) <*>- parsePlainEdges v+parseNodeEdges v = proj13 <$> parseEdgeGrid v where- halveGrid (Grid (Square w h) m)- | odd w && odd h = pure $ Grid s' m'- | otherwise = empty- where- s' = Square ((w + 1) `div` 2) ((h + 1) `div` 2)- m' = Map.mapKeys (both (`div` 2))- . Map.filterWithKey (const . (uncurry (&&) . both even))- $ m- both f (x, y) = (f x, f y)+ proj13 :: (SGrid a, SGrid Empty, [Edge]) -> (SGrid a, [Edge])+ proj13 (x,_,z) = (x,z) +parseCellEdges :: FromChar a =>+ Value -> Parser (SGrid a, [Edge])+parseCellEdges v = proj23 <$> parseEdgeGrid v+ where+ proj23 :: (SGrid PlainNode, SGrid a, [Edge]) -> (SGrid a, [Edge])+ proj23 (_,y,z) = (y,z)+ data HalfDirs = HalfDirs {unHalfDirs :: [Dir]} instance FromChar HalfDirs where@@ -288,10 +354,10 @@ [] -> pure Nothing [q] -> pure (Just q) _ -> fail "multiple successors"- succs p = filter (test ((==) . succ) p) . neighbours s $ p- isStart p = not . any (test ((==) . pred) p) . neighbours s $ p+ succs p = filter (test ((==) . succ) p) . vertexNeighbours s $ p+ isStart p = not . any (test ((==) . pred) p) . vertexNeighbours s $ p test f p q = maybe False (f (m' Map.! p)) (Map.lookup q m')- isAlmostIsolated p = all disjointSucc . neighbours s $ p+ isAlmostIsolated p = all disjointSucc . vertexNeighbours s $ p where disjointSucc q = null $ intersect (succs p) (succs' q) succs' q = maybe [] (const $ succs q) (Map.lookup q m')@@ -408,10 +474,30 @@ guard $ length xs > 0 && length xs <= 4 return . ParseTapaClue . TapaClue $ xs +reorientOutside :: OutsideClues a -> OutsideClues a+reorientOutside (OC l r b t) = OC (reverse l) (reverse r) b t++parseCharOutside :: FromChar a => Value -> Parser (OutsideClues a)+parseCharOutside (Object v) = reorientOutside <$>+ (OC <$>+ pfield "left" <*> pfield "right" <*>+ pfield "bottom" <*> pfield "top" )+ where+ pfield f = parseLine . fromMaybe [] =<< v .:? f+parseCharOutside _ = empty+ 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)+ rev (OC l r b t) = reorientOutside $+ OC (map reverse l) r b (map reverse t) parseMultiOutsideClues _ = empty++instance FromChar PrimeDiag where+ parseChar '.' = pure $ PrimeDiag (False, False)+ parseChar '/' = pure $ PrimeDiag (True, False)+ parseChar '\\' = pure $ PrimeDiag (False, True)+ parseChar 'X' = pure $ PrimeDiag (True, True)+ parseChar _ = empty