packages feed

hsqml-demo-manic-0.3.4.0: src/Main.hs

{-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable, TypeFamilies #-}
module Main where

import Control.Concurrent (forkIO)
import Control.Exception (evaluate)
import Control.Applicative ((<$>))
import Control.Monad
import Control.Monad.Random
import Data.Function
import Data.Typeable
import Data.Text (Text)
import qualified Data.Text as T
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Data.List
import Graphics.QML
import Graphics.QML.Objects.ParamNames

import Paths_hsqml_demo_manic

data Bearing = North | East | South | West
    deriving (Typeable, Eq, Enum, Bounded, Show, Read)

instance Marshal Bearing where
    type MarshalMode Bearing c d = ModeTo c
    marshaller = toMarshaller (\x -> case x of
        North -> 0 :: Int
        East  -> 90
        South -> 180
        West  -> 270)

data Orientation =
    Horizontal | Vertical deriving (Eq, Enum, Bounded, Show, Read)

data Colour = Green | Yellow | Brown | Blue
    deriving (Typeable, Eq, Enum, Bounded, Show, Read)

instance Marshal Colour where
    type MarshalMode Colour c d = ModeTo c
    marshaller = toMarshaller (\x -> case x of
        Green  -> T.pack "lime" :: Text
        Yellow -> T.pack "yellow"
        Brown  -> T.pack "darkgoldenrod"
        Blue   -> T.pack "blue")

data Tile
    = Start Colour Bearing
    | End Colour Bearing
    | Corner Bearing
    | Straight Orientation
    | Cross
    deriving (Eq, Show, Read)

data NumberedTile = NumberedTile Tile Int deriving Typeable

newtype TileSource = TileSource [NumberedTile] deriving Typeable

instance Marshal NumberedTile where
    type MarshalMode NumberedTile c d = ModeObjBidi NumberedTile c
    marshaller = bidiMarshallerIO (return . fromObjRef) newObjectDC

instance DefaultClass NumberedTile where
    classMembers = [
        defPropertyConst "tile" (\(NumberedTile tile _) -> return tile),
        defPropertyConst "idx" (\(NumberedTile _ idx) -> return idx)]

instance Marshal TileSource where
    type MarshalMode TileSource c d = ModeObjBidi TileSource c
    marshaller = bidiMarshallerIO (return . fromObjRef) newObjectDC

instance DefaultClass TileSource where
    classMembers = [
        defPropertyConst "top" (\(TileSource ts) -> return $ head ts),
        defPropertyConst "next" (\(TileSource ts) ->
            return $ TileSource $ tail ts),
        defMethod "topN" (\(TileSource ts) n ->
            return $ take n ts :: IO [NumberedTile])]

newTileSource :: IO TileSource
newTileSource = do
    g <- newStdGen
    return $ TileSource $ zipWith NumberedTile (randomTiles g) [0..]

randomEnum :: forall g e. (RandomGen g, Enum e, Bounded e) => Rand g e
randomEnum = toEnum <$> getRandomR (fromEnum lo, fromEnum hi)
    where lo = minBound :: e
          hi = maxBound :: e

randomTile :: (RandomGen g) => Rand g Tile
randomTile = join $ fromList [
    (fmap Corner randomEnum, 4),
    (fmap Straight randomEnum, 2),
    (return Cross, 1)]

randomTiles :: (RandomGen g) => g -> [Tile]
randomTiles = unfoldr (Just . runRand randomTile)

instance Marshal Tile where
    type MarshalMode Tile c d = ModeBidi c
    marshaller = bidiMarshaller (read . T.unpack) (T.pack . show)

data Point = Pt !Int !Int deriving (Typeable, Eq, Ord, Show)

instance DefaultClass Point where
    classMembers = [
        defPropertyConst "x" (\(Pt x _) -> return x),
        defPropertyConst "y" (\(Pt _ y) -> return y)]

instance Marshal Point where
    type MarshalMode Point c d = ModeObjBidi Point c
    marshaller = bidiMarshallerIO (return . fromObjRef) newObjectDC 

data Grid = Grid Int Int (Map Point Tile) deriving (Typeable, Show)

place :: Point -> Tile -> Grid -> Grid
place pt tile (Grid w h grid) = Grid w h $ Map.insert pt tile grid

placeIfFree :: Point -> Tile -> Grid -> Grid
placeIfFree pt tile (Grid w h grid) = Grid w h $ Map.alter (\oldTile ->
    if isJust oldTile then oldTile else Just tile) pt grid

data PickedSignal deriving Typeable

instance SignalKeyClass PickedSignal where
    type SignalParams PickedSignal = Point -> IO ()

instance DefaultClass Grid where
    classMembers = [
        defPropertyConst "width" (\(Grid w _ _) -> return w),
        defPropertyConst "height" (\(Grid _ h _) -> return h),
        defMethod "place" (\grid x y tile ->
            return $ place (Pt x y) tile grid :: IO Grid),
        defMethod "plumb" (return . plumbGrid :: Grid -> IO PlumbedGrid),
        defMethod "pick" (\grid tiles currX currY vol -> void $ forkIO $ do
            let (Pt x y) = pickMove (fromObjRef grid) tiles (Pt currX currY) vol
            x' <- evaluate x
            y' <- evaluate y
            let pos = Pt x' y'
            fireSignal (Proxy :: Proxy PickedSignal) grid pos),
        defSignalNamedParams "picked" (Proxy :: Proxy PickedSignal) $
            fstName "pos"]

instance Marshal Grid where
    type MarshalMode Grid c d = ModeObjBidi Grid c
    marshaller = bidiMarshallerIO (return . fromObjRef) newObjectDC 

data PlumbedGrid = PlumbedGrid [Plumb] [Leak] (Map Point Int)
    deriving Typeable

isPlacable :: PlumbedGrid -> Int -> Point -> Bool
isPlacable (PlumbedGrid _ _ indexMap) n pt =
    maybe True (> n) $ Map.lookup pt indexMap

instance DefaultClass PlumbedGrid where
    classMembers = [
        defPropertyConst "plumbing" (\(PlumbedGrid ps _ _) -> return ps),
        defPropertyConst "leaks" (\(PlumbedGrid _ ls _) -> return ls),
        defPropertyConst "maxIndex" (\(PlumbedGrid ps _ _) ->
            return . minimum . filter (>= 0) .
            map (maximum . map (\(Plumb _ i _ _ _) -> i)) $
            groupBy ((==) `on` (\(Plumb c _ _ _ _) -> c)) ps),
        defMethod "isPlacable" (\pGrid n x y ->
            return $ isPlacable pGrid n (Pt x y) :: IO Bool)]

instance Marshal PlumbedGrid where
    type MarshalMode PlumbedGrid c d = ModeObjBidi PlumbedGrid c
    marshaller = bidiMarshallerIO (return . fromObjRef) newObjectDC 

data Plumb = Plumb Colour Int Point (Maybe Bearing) (Maybe Bearing)
    deriving (Typeable, Show)

instance DefaultClass Plumb where
    classMembers = [
        defPropertyConst "colour" (\(Plumb col _ _ _ _) -> return col),
        defPropertyConst "idx" (\(Plumb _ idx _ _ _) -> return idx),
        defPropertyConst "x" (\(Plumb _ _ (Pt x _) _ _) -> return x),
        defPropertyConst "y" (\(Plumb _ _ (Pt _ y) _ _) -> return y),
        defPropertyConst "entryA" (\(Plumb _ _ _ theta _) -> return theta),
        defPropertyConst "exitA" (\(Plumb _ _ _ _ theta) -> return theta)]

instance Marshal Plumb where 
    type MarshalMode Plumb c d = ModeObjBidi Plumb c
    marshaller = bidiMarshallerIO (return . fromObjRef) newObjectDC

newtype Leak = Leak {unLeak :: Plumb}

instance Marshal Leak where 
    type MarshalMode Leak c d = ModeObjBidi Plumb c
    marshaller = bidiMarshaller Leak unLeak

bend :: Bearing -> Bearing
bend North = East
bend East = South
bend South = West
bend West = North

invert :: Bearing -> Bearing
invert = bend . bend

revBend :: Bearing -> Bearing
revBend = bend . bend . bend

nextPos :: Maybe Bearing -> Point -> Point
nextPos (Just North) (Pt x y) = Pt x (y-1)
nextPos (Just East) (Pt x y) = Pt (x+1) y
nextPos (Just South) (Pt x y) = Pt x (y+1)
nextPos (Just West) (Pt x y) = Pt (x-1) y
nextPos Nothing p = p

plumbTile :: (Point -> Maybe Tile) -> PrePlumb -> Maybe Plumb
plumbTile getTile (PrePlumb col idx curr flow) = case getTile curr of
    Just (Start c theta)
        | flow == Nothing && col == c ->
            Just $ Plumb col idx curr Nothing (Just theta) 
    Just (End c theta)
        | flow == Just (invert theta) && col == c ->
            Just $ Plumb col idx curr (Just theta) Nothing
    Just (Straight Horizontal)
        | flow == Just East || flow == Just West ->
            Just $ Plumb col idx curr (fmap invert flow) flow
    Just (Straight Vertical)
        | flow == Just North || flow == Just South ->
            Just $ Plumb col idx curr (fmap invert flow) flow
    Just (Corner theta)
        | flow == Just (invert theta)  ->
            Just $ Plumb col idx curr (Just theta) (Just $ bend theta)
        | flow == Just (revBend theta) ->
            Just $ Plumb col idx curr (Just $ bend theta) (Just theta)
    Just Cross -> Just $ Plumb col idx curr (fmap invert flow) flow
    _ -> Nothing

data Terminal = Success | EmptyTile | BadPipe deriving Show

data PrePlumb = PrePlumb Colour Int Point (Maybe Bearing) deriving Show

unPlumb :: Plumb -> PrePlumb
unPlumb (Plumb col idx curr inflow _) =
    PrePlumb col idx curr (fmap invert inflow)

plumbOn :: Plumb -> PrePlumb
plumbOn (Plumb col idx curr _ flow) =
    PrePlumb col (idx+1) (nextPos flow curr) flow

plumbStep ::
    (Point -> Maybe Tile) -> PrePlumb -> Either Terminal (Plumb, PrePlumb)
plumbStep tileFn pre@(PrePlumb _ _ curr flow) = case tileFn curr of
    Just _  -> case plumbTile tileFn pre of
        Just plumb -> Right (plumb, plumbOn plumb)
        Nothing -> maybe (Left Success) (const $ Left BadPipe) flow
    Nothing -> Left EmptyTile

unfoldrEither :: (b -> Either c (a, b)) -> b -> (c, [a])
unfoldrEither f b  = case f b of
    Right (a,b') -> (a:) <$> unfoldrEither f b'
    Left c       -> (c, [])

plumbStart :: Grid -> Colour -> Point -> Maybe Bearing -> (Terminal, [Plumb])
plumbStart (Grid _ _ grid) col start flow =
    unfoldrEither (plumbStep (`Map.lookup` grid)) $
    PrePlumb col 0 start flow

plumbAllStarts :: Grid -> [[Plumb]]
plumbAllStarts grid = foldStarts (\col curr ps ->
    snd (plumbStart grid col curr Nothing) : ps) [] grid

foldStarts :: (Colour -> Point -> a -> a) -> a -> Grid -> a
foldStarts f s (Grid _ _ gridMap) =
    Map.foldWithKey (\curr tile a -> case tile of
        Start col _ -> f col curr a
        _           -> a) s gridMap

plumbLeak :: [Plumb] -> Maybe Leak
plumbLeak ps =
    case last ps of
        Plumb col idx end _ (Just flow) ->
            Just $ Leak $ Plumb col (idx+1) end Nothing $ Just flow
        _ -> Nothing

stripPlumbed :: [Plumb] -> Grid -> Grid
stripPlumbed ps (Grid w h grid) =
    Grid w h $ foldl' (flip $ \(Plumb _ _ p theta _) ->
        Map.update (f theta) p) grid ps
    where f (Just North) Cross = Just $ Straight Horizontal
          f (Just East)  Cross = Just $ Straight Vertical
          f (Just South) Cross = Just $ Straight Horizontal
          f (Just West)  Cross = Just $ Straight Vertical
          f _ _ = Nothing

spareParts :: Grid -> [Plumb]
spareParts (Grid _ _ grid) =
    Map.foldWithKey sparePart [] grid

sparePart :: Point -> Tile -> [Plumb] -> [Plumb]
sparePart p (Start col theta) xs = Plumb col (-1) p Nothing (Just theta) : xs
sparePart p (End col theta) xs = Plumb col (-1) p (Just theta) Nothing : xs
sparePart p (Corner theta) xs =
    Plumb Green (-1) p (Just theta) (Just $ bend theta) : xs
sparePart p (Straight Horizontal) xs =
    Plumb Green (-1) p (Just East) (Just West) : xs
sparePart p (Straight Vertical) xs =
    Plumb Green (-1) p (Just North) (Just South) : xs
sparePart p Cross xs =
    Plumb Green (-1) p (Just East) (Just West) :
    Plumb Green (-1) p (Just North) (Just South) : xs

buildIndexMap :: [Plumb] -> Map Point Int
buildIndexMap = foldr (\plumb gridMap -> case plumb of
    Plumb _ i p _ (Just _) -> Map.alter (Just . maybe i (min i)) p gridMap
    Plumb _ _ p _ Nothing  -> Map.insert p (-1) gridMap) Map.empty

plumbGrid :: Grid -> PlumbedGrid
plumbGrid grid =
    let plumbs = plumbAllStarts grid
        leaks = mapMaybe plumbLeak plumbs
        indexMap = buildIndexMap $ plumb ++ filter (\(Plumb _ _ _ _ flow) ->
            isNothing flow) spares
        plumb = concat plumbs
        spares = spareParts $ stripPlumbed plumb grid
    in PlumbedGrid (plumb ++ spares) leaks indexMap

nextPosFollowPlumb :: Grid -> Colour -> Point -> Maybe Bearing -> [PrePlumb]
nextPosFollowPlumb grid@(Grid w h _) col start flow =
    dropOutOfBounds $ case reverse <$> plumbStart grid col start flow of
        (EmptyTile, []) -> [PrePlumb col 0 start flow]
        (EmptyTile, ps) -> plumbOn (head ps) : map unPlumb ps
        (Success, ps) -> map unPlumb ps
        _ -> []
    where dropOutOfBounds pps@(PrePlumb _ _ (Pt x y) _:_) =
              if x >= 0 && y >=0 && x < w && y < h then pps else []
          dropOutOfBounds [] = []

neighbours :: Grid -> Colour -> Point -> [[PrePlumb]]
neighbours grid@(Grid _ _ gridMap) col start =
    filter (not . null) $ case Map.lookup start gridMap of
        Just _  -> [nextPosFollowPlumb grid col start Nothing]
        Nothing -> map ((\flow ->
            nextPosFollowPlumb grid col (nextPos flow start) flow) . Just)
            [North,East,South,West]

findCosts ::
    Grid -> Colour -> Point -> Map Point (Int, [PrePlumb])
findCosts grid col start =
    fst $ fromJust $ find (null . snd) $
    iterate explore (Map.empty, [(start, [], -1)])
    where explore (cache, open) =
              foldr update (cache, []) open 
          update (curr, pps, cost') (cache, open) =
              case fromMaybe (maxBound, []) $ Map.lookup curr cache of
                  (cost, _) | cost' < cost ->
                      (Map.insert curr (cost', pps) cache,
                       map (\pps'@(PrePlumb _ _ curr' _:_) ->
                           (curr', pps' ++ pps, cost' + 1))
                           (neighbours grid col curr) ++ open)
                  _ -> (cache, open)

findGoal ::
    Grid -> Map Point (Int, a) -> Colour -> Maybe (Point, Int, a)
findGoal (Grid _ _ gridMap) costs col =
    fmap (\((a,b),c) -> (a,c,b)) $
    foldr (\(curr,(cost,x)) prevBest -> case prevBest of
        Just best | cost >= snd best -> prevBest
        _                            -> Just ((curr,x),cost)) Nothing $
    Map.toList $ Map.mapMaybeWithKey (\curr tile ->
        case tile of
            End col' _ | col' == col -> Map.lookup curr costs
            _                        -> Nothing) gridMap

findPath :: Grid -> Colour -> Point -> Maybe ([Plumb], Int)
findPath grid col start =
    let costs = findCosts grid col start
        goal = findGoal grid costs col
    in fmap (\(_,cost,pps) -> (plumbPre $ reverse pps,cost)) goal

plumbPre :: [PrePlumb] -> [Plumb]
plumbPre pps = zipWith3 f pps (tail pps) [0..]
    where f (PrePlumb col _ curr flow) (PrePlumb _ _ _ nextFlow) idx =
              Plumb col idx curr (fmap invert flow) nextFlow

gridHeuristic :: Int -> Grid -> Int
gridHeuristic vol grid = snd $ foldStarts (\col start (grid',heur) ->
    let plumbedLen = length $ snd $ plumbStart grid' col start Nothing
        (path, pathLen) = fromMaybe ([], 1000) $ findPath grid' col start
        remain = min (plumbedLen - vol) 4
        bonus = 9*remain - (remain*remain)
        grid'' = foldr placePlumb grid' path
    in (grid'', heur + 2*bonus + 4*plumbedLen - pathLen)) (grid, 0) grid

pickMove :: Grid -> TileSource -> Point -> Int -> Point
pickMove grid (TileSource (NumberedTile tile _:_)) start vol =
    fst $ head $ sortMoves start vol $ searchMoves grid vol tile

searchMoves :: Grid -> Int -> Tile -> [(Point, Grid)]
searchMoves grid@(Grid w h _) vol tile =
    map (\pt -> (pt, place pt tile grid)) $
    filter (isPlacable pGrid vol) $ liftM2 Pt [0..w-1] [0..h-1]
    where pGrid = plumbGrid grid

sortMoves :: Point -> Int -> [(Point, Grid)] -> [(Point, Grid)]
sortMoves start vol =
    map fst . sortBy (flip compare `on` snd) .
    map (\x -> (,) x $ (+ (distance start (fst x) / (-100.0))) $ fromIntegral $ gridHeuristic vol $ snd x)

distance :: Point -> Point -> Double
distance (Pt x1 y1) (Pt x2 y2) =
    sqrt $ (x*x)+(y*y)
    where x = fromIntegral (x1-x2)
          y = fromIntegral (y1-y2)

placePlumb :: Plumb -> Grid -> Grid
placePlumb (Plumb _ _ curr (Just inflow) (Just flow))
    | inflow == bend flow = placeIfFree curr $ Corner flow
    | inflow == revBend flow = placeIfFree curr $ Corner inflow
    | otherwise = placeIfFree curr Cross
placePlumb _ = id

debugMain :: Grid -> IO ()
debugMain myGrid = do
    clazz <- newClass [
        defMethod' "newTileSource" (\_ -> newObjectDC =<< newTileSource),
        defMethod' "newGrid" (\_ -> newObjectDC myGrid),
        defMethod' "sparePart" (\_ tile ->
            return $ sparePart (Pt 0 0) tile [] :: IO [Plumb])]
    object <- newObject clazz ()
    -- setQtArgs "hsqml-manic" ["-qmljsdebugger=port:9999,block"]
    qml <- getDataFileName "Main.qml"
    runEngineLoop $ defaultEngineConfig {
        initialDocument = fileDocument qml,
        contextObject = Just $ anyObjRef object
    }

newGrid :: Grid
newGrid = Grid 8 8 $ Map.fromList [
    (Pt 4 0,Start Yellow South),
    (Pt 3 7,End Yellow North),
    (Pt 0 3, Start Brown East),
    (Pt 7 4, End Brown West)]

main :: IO ()
main = debugMain newGrid