packages feed

minesweeper-0.4: Core.hs

-- | Core data types and functions for the game.
module Core
    ( MMap
    , emptyMMap
    , setSum
    , solutions'
    ) where

-------------------------------------------

import Place
import PlaceSet
import qualified PlaceMap as M

import Data.Function
import Data.Maybe
import Data.List hiding ((\\), insert, delete)
import qualified Data.List as List


-------------------------------------------

data Constraint
    = Constraint
        { places_      :: PlaceSet
        , value        :: Int
        }

    deriving Eq

data ConstraintView
    = BigConstraint PlaceSet Int
    | SmallConstraint PlaceSet Int
    | Clear Place
    | Mine Place

view :: Constraint -> ConstraintView
view (Constraint ps v)
    | size ps == 1 && v == 1    = Mine $ head $ placeSetToList ps
    | size ps == 1 && v == 0    = Clear $ head $ placeSetToList ps
    | size ps > big = BigConstraint ps v
    | otherwise     = SmallConstraint ps v

big :: Int
big = 10


---------------------------------------

constraint :: [Place] -> Int -> Constraint
constraint ps v 
    = Constraint (listToPlaceSet ps) v

subConstraint :: Constraint -> Constraint -> Bool
subConstraint a b = places_ a `isSubsetOf` places_ b

similarConstraint :: Constraint -> Constraint -> Bool
similarConstraint a b = places_ a == places_ b


-- csak akkor, ha subConstraint!
(.-.) :: Constraint -> Constraint -> Constraint
Constraint ps v .-. Constraint ps' v' = Constraint (ps \\ ps') (v-v')

connectionStrength :: Constraint -> Constraint -> [Constraint]
connectionStrength (Constraint ps v) (Constraint ps' v') = [Constraint l i | i<-[min_ .. max_]]     where 

    min_ = maximum [0, v - (size ps - m), v' - (size ps' - m)]
    max_ = minimum [m, v, v']

    m = size l
    l = intersection ps ps'

constraintSolutions :: Constraint -> Integer
constraintSolutions (Constraint ps v) = binom (size ps) v


binom :: Int -> Int -> Integer
binom m n = binoms !! m !! min n (m-n)

binoms :: [[Integer]]
binoms = iterate f [1]  where f l = zipWith (+) (l++[0]) ([0]++l)


-------------------------------------------


data Constraints
    = Unsolvable
    | Solvable 
        [Constraint]        -- big constraints (usually one) 
        PlaceSet            -- cleared places
        PlaceSet            -- places with mines
        (M.PlaceMap [Constraint])   -- other constraints

---------------------------------------

addConstraintSimple :: Constraint -> Constraints -> Constraints
addConstraintSimple _ Unsolvable = Unsolvable 
addConstraintSimple c (Solvable bc cl mi m) = case view c of
    Clear p             -> Solvable bc (insert p cl) mi m
    Mine  p             -> Solvable bc cl (insert p mi) m
    BigConstraint _ _   -> Solvable (c:bc) cl mi m
    SmallConstraint ps _-> Solvable bc cl mi $ foldr f m $ placeSetToList ps
 where

    f p m' = case M.lookup p m' of
        Nothing     -> M.insert p [c] m'
        Just cs     -> M.insert p (c:cs) m'

deleteConstraint :: Constraint -> Constraints -> Constraints
deleteConstraint _ Unsolvable = Unsolvable 
deleteConstraint c (Solvable bc cl mi m) = case view c of
    Clear p             -> Solvable bc (delete p cl) mi m
    Mine  p             -> Solvable bc cl (delete p mi) m
    BigConstraint _ _   -> Solvable (deleteBy similarConstraint c bc) cl mi m
    SmallConstraint ps _-> Solvable bc cl mi $ foldr f m $ placeSetToList ps  
 where

    f p m' = case M.lookup p m' of
        Just cs -> case deleteBy similarConstraint c cs of
            []      -> M.delete p m'
            l       -> M.insert p l m'

deleteConstraints :: [Constraint] -> Constraints -> Constraints
deleteConstraints cs m = foldr deleteConstraint m cs

dependentConstraints :: Constraint -> Constraints -> [Constraint]
dependentConstraints c@(Constraint ps _) (Solvable bc cl mi m)
    = List.delete c $ l ++ filter (not . disjunct ps . places_) bc
 where  
    l    = map toClear (placeSetToList $ intersection cl ps) 
        ++ map toMine  (placeSetToList $ intersection mi ps) 
        ++ nub (concatMap f $ placeSetToList ps) 

    toClear p = constraint [p] 0
    toMine  p = constraint [p] 1

    f p = case M.lookup p m of
        Just cs     -> cs
        Nothing     -> []

---------------------------------------

-- calculate number of different solutions
solutions :: Constraints -> Integer
solutions Unsolvable = 0
solutions (Solvable bc _ _ cs) = product (map (constraintSolutions . fst) indep) * case dep of
        []  -> 1
        ((c, xs): _)   -> let
                        cs' = minimumBy (compare `on` length) $ map (connectionStrength c) xs
                    in sum [solutions (addConstraint y s'') | y<-cs']
 where
    s' = Solvable bc empty empty cs
    s'' = deleteConstraints (map fst indep) s'

    (indep, dep) = partition (null . snd) $ map (`dependentConstraints_` s') $ nub (concatMap snd $ M.toList cs) ++ bc

dependentConstraints_ c cs = (c, dependentConstraints c cs)

solvable :: Constraints -> Bool
solvable Unsolvable = False
solvable (Solvable bc _ _ cs) = case concatMap snd (M.toList cs) ++ bc of
    []                         -> True
    (c: _) -> case dependentConstraints c s' of
        []    -> solvable (deleteConstraint c s')
        (x:_)   -> let
                        cs' = connectionStrength c x
                    in or [solvable (addConstraint y s') | y<-cs']
 where
    s' = Solvable bc empty empty cs

-- may be faster?
omittable :: Constraint -> Constraints -> Bool
omittable c@(Constraint ps v) cs = all (not . solvable) [addConstraint (Constraint ps i) cs | i <- [v-1,v-2..0] ++ [v+1..size ps]]
{-
    - if  p1 + ... + pI = n  has no solution except for  n = n0,  then  p1 + ... + pI = n0  is not needed:
             pA + pB = 1, pB + pC = 1, pC + pD = 1, pD + pA = 1      
        -->  pA + pB = 1, pB + pC = 1, pC + pD = 1
    - ...
-}

---------------------------------------

addConstraintSmart :: Constraint -> Constraints -> Constraints
addConstraintSmart c cs
    | not (solvable cs') = Unsolvable
--    | omittable c cs     = cs     -- not really needed
    | otherwise          = cs'
 where
    cs' = addConstraint c cs


addConstraint :: Constraint -> Constraints -> Constraints
addConstraint _ Unsolvable = Unsolvable 
addConstraint n@(Constraint ps v) c
    | v<0 || v>s                = Unsolvable
    | s==0                      = c
    | s>1 && v==0               = addConstraints [constraint [p] 0 | p<- placeSetToList ps] c
    | s>1 && v==s               = addConstraints [constraint [p] 1 | p<- placeSetToList ps] c
    | any ((/= v) . value) similar  = Unsolvable
    | not (null similar)        = c
    | any (==0) str             = Unsolvable
    | ((x, [y]): _) <- oneStrength
            = addConstraints [n .-. y, x .-. y, y] $ deleteConstraint x c
    | otherwise = addConstraints [x .-. n | x <- supDomains] $ addConstraintSimple n $ deleteConstraints supDomains c 
 where
    s = size ps

    supDomains  = [x | x <- dep, n `subConstraint` x]

    oneStrength  = filter ((==1) . length . snd) strength
    str = map (length . snd) strength

    strength = [(x, connectionStrength n x) | x <- dep, x `subConstraint` n]

    (similar, dep) = partition (similarConstraint n) $ dependentConstraints n c


addConstraints :: [Constraint] -> Constraints -> Constraints
addConstraints l c = foldr addConstraint c l



-----------------------------------------------

data MMap = MMap Constraints Integer

emptyMMap :: MMap
emptyMMap = mkMMap $ Solvable [] empty empty M.empty

mkMMap :: Constraints -> MMap
mkMMap c = MMap (if s==0 then Unsolvable else c) s
 where
    s = solutions c

solutions' :: MMap -> Integer
solutions' (MMap _ i) = i

setSum  :: PlaceSet -> Int -> MMap -> MMap
setSum ps v (MMap c _) = mkMMap $ addConstraint (Constraint ps v) c

---------------------------------------