puzzle-draw-0.3.0.0: src/tools/checkpuzzle.hs
{-# LANGUAGE FlexibleContexts #-}
module Main where
import Parse.Puzzle
import Data.PuzzleTypes (typeNames, PuzzleType(..))
import Data.CmdLine (exitErr, readPuzzle, checkType)
import Data.Elements (KropkiDot(..), digitList)
import Data.GridShape (Edge, N, C, rows, edgesM, unorient, ends, dualE)
import Data.Grid (Grid)
import qualified Parse.PuzzleTypes as T
import Options.Applicative
import Control.Monad
import Data.Maybe
import qualified Data.Map.Strict as Map
import Data.List (intercalate, sort)
import System.Exit
import System.Environment (getProgName)
import qualified Data.Yaml as Y
optListTypes :: Parser (a -> a)
optListTypes =
infoOption
(unlines' . sort . map snd $ typeNames)
(long "list-types"
<> help "List supported puzzle types")
where
unlines' = intercalate "\n"
data PuzzleOpts = PuzzleOpts
{ _type :: Maybe String
, _input :: FilePath
}
puzzleOpts :: Parser PuzzleOpts
puzzleOpts = PuzzleOpts
<$> (optional . strOption $
(long "type" <> short 't'
<> metavar "TYPE"
<> help "Puzzle type, overriding type in input file"))
<*> argument str
(metavar "INPUT"
<> help "Puzzle file in .pzl format")
defaultOpts :: Parser a -> IO a
defaultOpts optsParser = do
prog <- getProgName
let p = info (helper <*> optListTypes <*> optsParser)
(fullDesc
<> progDesc "Command-line puzzle checking."
<> header prog)
execParser p
main :: IO ()
main = do
opts <- defaultOpts puzzleOpts
mp <- readPuzzle (_input opts)
TP mt pv msv _ <- case mp of Left e -> exitErr $
"parse failure: " ++ show e
Right p -> return p
t <- checkType $ _type opts `mplus` mt
sv <- maybe (exitErr $ "need solution") return msv
let es = Y.parseEither (check t) (pv, sv)
case es of Left err -> exitErr $ "parse failure: " ++ err
Right [] -> exitSuccess
Right es' -> mapM_ putStrLn es' >> exitFailure
check :: PuzzleType -> (Y.Value, Y.Value) -> Y.Parser [String]
check t (pv, sv) =
case t of
ABCtje -> checkABCtje (pv, sv)
Kropki -> checkKropki (pv, sv)
_ -> return []
checkABCtje :: (Y.Value, Y.Value) -> Y.Parser [String]
checkABCtje (pv, sv) = do
p <- fst T.abctje $ pv
s <- snd T.abctje $ sv
return . catMaybes . map (\c -> c p s) $
[ solutionKeys, values ]
where
solutionKeys (ds, _) s = let have = sort (digitList ds)
want = sort (map fst (s))
in if have /= want then Just "unequal digit lists" else Nothing
values (_, ws) vs = (\es -> case es of [] -> Nothing
_ -> Just $ intercalate ", " es)
. catMaybes
. map (\(w, v) -> if val w == v
then Nothing
else Just (w ++ " should be " ++ show (val w)))
$ ws
where
l c = fromMaybe 0 . lookup c . map (\(x, y) -> (y, x)) $ vs
val = sum . map l
checkKropki :: (Y.Value, Y.Value) -> Y.Parser [String]
checkKropki (pv, sv) = do
p <- fst T.kropki $ pv
s <- snd T.kropki $ sv
return . catMaybes . map (\c -> c p s) $
[ match, latin, dots ]
where
match :: Map.Map (Edge N) KropkiDot -> Grid C Int -> Maybe String
match p s = if solEdges == puzEdges then
Nothing
else
Just "puzzle and solution shape don't match"
where
solEdges = let
(outer, inner) = edgesM s
in
sort (inner ++ map unorient outer)
puzEdges = sort (Map.keys p)
latin _ s = either Just (const Nothing) . mapM_ latinRow . rows $ s
latinRow ds = if sort ds == [1 .. length ds] then
Right ()
else
Left $ "row not 1..N: " ++ show ds
dots p s = either Just (const Nothing) . mapM_ (checkEdge s) . Map.toList $ p
checkEdge :: Grid C Int -> (Edge N, KropkiDot) -> Either String ()
checkEdge s (e, d) =
let
(a, b) = ends (dualE e)
okDot x y =
let
white = x - y == 1 || x - y == -1
black = x == y * 2 || y == x * 2
in
case d of
KBlack -> black
KWhite -> white
KNone -> not black && not white
in
case (Map.lookup a s, Map.lookup b s) of
(Just x, Just y) -> if okDot x y then Right () else Left ("bad dot between: " ++ show a ++ "," ++ show b)
_ -> if d == KNone then Right () else Left "dot on the edge"