module Text.Puzzles.Util where
import Prelude hiding (sequence)
import Control.Applicative
import Control.Arrow
import Control.Monad hiding (sequence)
import Data.Hashable
import Data.Maybe (catMaybes)
import qualified Data.Map as Map
import qualified Data.HashMap.Strict as HMap
import Data.Traversable (traverse, sequence, sequenceA, Traversable)
import Data.Foldable (Foldable, fold)
import Data.Monoid ((<>))
import Data.List (intersect)
import Data.Char (digitToInt, isAlpha, isDigit)
import Text.Read (readMaybe)
import qualified Data.Text as T
import Data.Yaml
import Data.Puzzles.Grid
import Data.Puzzles.GridShape hiding (size)
import Data.Puzzles.Elements
class FromChar a where
parseChar :: Char -> Parser a
instance FromChar Char where
parseChar = pure
class FromString a where
parseString :: String -> Parser a
instance FromChar Int where
parseChar c
| isDigit c = digitToInt <$> parseChar c
| otherwise = fail $ "expected a digit, got '" ++ [c] ++ "'"
newtype Alpha = Alpha { unAlpha :: Char }
deriving (Show, Ord, Eq)
instance FromChar Alpha where
parseChar c
| isAlpha c = Alpha <$> parseChar c
| otherwise = empty
data Rect a = Rect !Int !Int [[a]]
deriving Show
instance Functor Rect where
fmap f (Rect w h ls) = Rect w h (map (map f) ls)
instance FromChar a => FromJSON (Rect a) where
parseJSON (String t) = Rect w h <$> filled
where
ls = map T.stripEnd . T.lines $ t
w = maximum . map T.length $ ls
h = length ls
filledc = map (T.unpack . T.justifyLeft w ' ') ls
filled = sequence . map (mapM parseChar) $ filledc
parseJSON _ = empty
data Border a = Border [a] [a] [a] [a]
deriving Show
instance Foldable Border where
fold (Border l r b t) = fold l <> fold r <> fold b <> fold t
instance Traversable Border where
sequenceA (Border l r b t) = Border <$> sequenceA l
<*> sequenceA r
<*> sequenceA b
<*> sequenceA t
instance Functor Border where
f `fmap` (Border l r b t) = Border (f <$> l) (f <$> r) (f <$> b) (f <$> t)
data BorderedRect a b = BorderedRect !Int !Int [[a]] (Border b)
deriving Show
instance (FromChar a, FromChar b) => FromJSON (BorderedRect a b) where
parseJSON v = do
Rect w h ls <- parseJSON v
let b = Border (reverse . map head . middle h $ ls)
(reverse . map last . middle h $ ls)
(middle w . last $ ls)
(middle w . head $ ls)
ls' = map (middle w) . middle h $ ls
mapM_ ((parseChar :: Char -> Parser Space) . flip ($) ls)
[head . head, head . last, last . head, last . last]
lsparsed <- sequence . map (mapM parseChar) $ ls'
bparsed <- sequenceA . fmap parseChar $ b
return $ BorderedRect (w2) (h2) lsparsed bparsed
where
middle len = take (len 2) . drop 1
newtype SpacedRect a = SpacedRect { unSpaced :: Rect a }
instance FromString a => FromJSON (SpacedRect a) where
parseJSON (String t) = if w == wmin then SpacedRect . Rect w h <$> p
else empty
where
ls = map T.words . T.lines $ t
w = maximum . map length $ ls
wmin = minimum . map length $ ls
h = length ls
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
parseChar ' ' = pure Space
parseChar _ = empty
data Blank = Blank
instance FromChar Blank where
parseChar '.' = pure Blank
parseChar '-' = pure Blank
parseChar _ = empty
instance FromString Blank where
parseString "." = pure Blank
parseString "-" = pure Blank
parseString _ = empty
instance FromChar SlalomDiag where
parseChar '/' = pure SlalomForward
parseChar '\\' = pure SlalomBackward
parseChar _ = empty
instance (FromChar a, FromChar b) => FromChar (Either a b) where
parseChar c = Left <$> parseChar c <|> Right <$> parseChar c
instance (FromString a, FromString b) => FromString (Either a b) where
parseString c = Left <$> parseString c <|> Right <$> parseString c
instance FromChar a => FromChar (Maybe a) where
parseChar = optional . parseChar
listListToMap :: [[a]] -> Map.Map (Cell Square) a
listListToMap ls = Map.fromList . concat
. zipWith (\y -> zipWith (\x -> (,) (x, y)) [0..]) [h1,h2..]
$ ls
where
h = length ls
rectToSGrid :: Rect a -> SGrid a
rectToSGrid (Rect w h ls) = Grid (Square w h) (listListToMap ls)
rectToClueGrid :: Rect (Either Blank a) -> SGrid (Clue a)
rectToClueGrid = fmap (either (const Nothing) Just) . rectToSGrid
newtype Shaded = Shaded { unShaded :: Bool }
instance FromChar Shaded where
parseChar 'x' = pure . Shaded $ True
parseChar 'X' = pure . Shaded $ True
parseChar _ = pure . Shaded $ False
parseShadedGrid :: Value -> Parser (SGrid Bool)
parseShadedGrid v = rectToSGrid . fmap unShaded <$> parseJSON v
parseGrid :: FromChar a => Value -> Parser (SGrid a)
parseGrid v = rectToSGrid <$> parseJSON v
parseClueGrid :: FromChar a => Value -> Parser (SGrid (Clue a))
parseClueGrid v = rectToClueGrid <$> parseJSON v
parseSpacedClueGrid :: FromString a => Value -> Parser (SGrid (Clue a))
parseSpacedClueGrid v = rectToClueGrid . unSpaced <$> parseJSON v
parsePlainEdges :: Value -> Parser [Edge]
parsePlainEdges v = readEdges <$> parseGrid v
readEdges :: SGrid Char -> [Edge]
readEdges g = horiz ++ vert
where (w, h) = size g
w' = w `div` 2
h' = h `div` 2
isHoriz (x, y) = g ! (2 * x + 1, 2 * y) == '-'
isVert (x, y) = g ! (2 * x, 2 * y + 1) == '|'
horiz = [ E (x, y) H | x <- [0 .. w' 1]
, y <- [0 .. h']
, isHoriz (x, y)
]
vert = [ E (x, y) V | x <- [0 .. w']
, y <- [0 .. h' 1]
, isVert (x, y)
]
parseGridChars :: FromChar a => SGrid Char -> Parser (SGrid a)
parseGridChars = traverse parseChar
parseNodeEdges :: FromChar a =>
Value -> Parser (SGrid a, [Edge])
parseNodeEdges v = (,) <$>
(parseGridChars =<< halveGrid =<< parseGrid v) <*>
parsePlainEdges 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)
data HalfDirs = HalfDirs {unHalfDirs :: [Dir]}
instance FromChar HalfDirs where
parseChar c | c == '└' = pure . HalfDirs $ [V, H]
| c `elem` "│┘" = pure . HalfDirs $ [V]
| c `elem` "─└┌" = pure . HalfDirs $ [H]
| otherwise = pure . HalfDirs $ []
parseEdges :: Value -> Parser [Edge]
parseEdges v = do
Grid _ m <- rectToSGrid . fmap unHalfDirs <$> parseJSON v
return [ E p d | (p, ds) <- Map.toList m, d <- ds ]
type ThermoRect = Rect (Either Blank (Either Int Alpha))
partitionEithers :: Ord k => Map.Map k (Either a b) -> (Map.Map k a, Map.Map k b)
partitionEithers = Map.foldrWithKey insertEither (Map.empty, Map.empty)
where
insertEither k = either (first . Map.insert k) (second . Map.insert k)
parseThermos :: SGrid Alpha -> Parser [Thermometer]
parseThermos (Grid s m) = catMaybes <$> mapM parseThermo (Map.keys m)
where
m' = fmap unAlpha m
parseThermo :: Cell Square -> Parser (Maybe Thermometer)
parseThermo p | not (isStart p) = pure Nothing
| not (isAlmostIsolated p) = fail $ show p ++ " not almost isolated"
| otherwise = Just <$> parseThermo' p
parseThermo' :: Cell Square -> Parser Thermometer
parseThermo' p = do
q <- next p
maybe (fail "no succ for thermo bulb") (fmap (p:) . parseThermo'') q
parseThermo'' :: Cell Square -> Parser Thermometer
parseThermo'' p = do
q <- next p
maybe (pure [p]) (fmap (p:) . parseThermo'') q
next :: Cell Square -> Parser (Maybe (Cell Square))
next p = case succs p of
[] -> 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
test f p q = maybe False (f (m' Map.! p)) (Map.lookup q m')
isAlmostIsolated p = all disjointSucc . neighbours s $ p
where
disjointSucc q = null $ intersect (succs p) (succs' q)
succs' q = maybe [] (const $ succs q) (Map.lookup q m')
parseThermoGrid :: ThermoRect -> Parser (SGrid Int, [Thermometer])
parseThermoGrid (Rect w h ls) = (,) (Grid s ints) <$>
(parseThermos $ Grid s alphas)
where
s = Square w h
(ints, alphas) = partitionEithers . snd . partitionEithers $
listListToMap ls
newtype Tight = Tight { unTight :: Tightfit () }
instance FromChar Tight where
parseChar '.' = pure . Tight $ Single ()
parseChar '/' = pure . Tight $ UR () ()
parseChar '\\' = pure . Tight $ DR () ()
parseChar _ = empty
parseTightOutside :: Value -> Parser (OutsideClues (Maybe Int),
SGrid (Tightfit ()))
parseTightOutside v = do
BorderedRect w h ls b <- parseJSON v
:: Parser (BorderedRect Tight (Either Blank Int))
return (outside . fmap (either (const Nothing) Just) $ b,
fmap unTight . rectToSGrid $ Rect w h ls)
where outside (Border l r b t) = OC l r b t
instance FromChar a => FromString (Tightfit a) where
parseString [c] = Single <$> parseChar c
parseString (c: '/':d:[]) = UR <$> parseChar c <*> parseChar d
parseString (c:'\\':d:[]) = DR <$> parseChar c <*> parseChar d
parseString _ = empty
parseTightIntGrid :: Value -> Parser (SGrid (Tightfit Int))
parseTightIntGrid v = rectToSGrid . unSpaced <$> parseJSON v
newtype PMarkedWord = PMW {unPMW :: MarkedWord}
parseNWords :: Int -> String -> Parser [String]
parseNWords n s | length ws == n = pure ws
| otherwise = empty
where
ws = words s
instance FromJSON PMarkedWord where
parseJSON v = PMW <$> (MW <$>
((,) <$> ((!!0) <$> x) <*> ((!!1) <$> x)) <*>
((,) <$> ((!!2) <$> x) <*> ((!!3) <$> x)))
where x = parseJSON v >>= parseNWords 4 >>= mapM parseString
instance FromString Int where
parseString s = maybe empty pure $ readMaybe s
newtype PCompassC = PCC {unPCC :: CompassC}
instance FromJSON PCompassC where
parseJSON (String t) = comp . map T.unpack . T.words $ t
where c "." = pure Nothing
c x = Just <$> parseString x
comp [n, e, s, w] = PCC <$> (CC <$> c n <*> c e <*> c s <*> c w)
comp _ = empty
parseJSON _ = empty
newtype RefGrid a = RefGrid { unRG :: SGrid a }
data Ref = Ref { unRef :: Char }
deriving Show
instance FromChar Ref where
parseChar c | isAlpha c = pure (Ref c)
parseChar _ = empty
hashmaptomap :: (Eq a, Hashable a, Ord a) => HMap.HashMap a b -> Map.Map a b
hashmaptomap = Map.fromList . HMap.toList
compose :: (Ord a, Ord b) => Map.Map a b -> Map.Map b c -> Maybe (Map.Map a c)
compose m1 m2 = sequence . Map.map (flip Map.lookup m2) $ m1
instance FromJSON a => FromJSON (RefGrid a) where
parseJSON (Object v) = RefGrid <$> do
Grid s refs <- fmap (fmap ((:[]) . unRef)) . rectToClueGrid <$>
(v .: "grid" :: Parser (Rect (Either Blank Ref)))
m <- hashmaptomap <$> v .: "clues"
case compose (Map.mapMaybe id refs) m of
Nothing -> mzero
Just m' -> return $ Grid s m'
parseJSON _ = empty
parseAfternoonGrid :: Value -> Parser (SGrid Shade)
parseAfternoonGrid v = do
(Grid s _ , es) <- parseNodeEdges v :: Parser (SGrid Char, [Edge])
let (m, b) = splitBorder s $ toMap es
guard $ Map.null b
return $ Grid (shrink s) m
where
shrink (Square w h) = Square (w1) (h1)
toShade V = Shade False True
toShade H = Shade True False
merge (Shade a b) (Shade c d)
| a && c || b && d = error "shading collision"
| otherwise = Shade (a || c) (b || d)
toMap es = Map.fromListWith
merge
[(p, toShade d) | E p d <- es]
splitBorder (Square w h) = Map.partitionWithKey
(\(x, y) _ -> x < w 1 && y < h 1)