packages feed

Fungi-1.0: Space/Space.hs

module Space.Space (
    Space
  , mkSpace
  , cellAt
  , putCell
  , travelBy
  , minMaxCoords
  , putSpaceAt
  ) where

import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as BS
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Vector

import Space.Cell

import Text.PrettyShow

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

data Conservative a = Precise !a | Imprecise !a
  deriving (Show, Eq, Ord)

isPrecise :: Conservative a -> Bool
isPrecise (Precise _) = True
isPrecise _ = False

toImprecise :: Conservative a -> Conservative a
toImprecise (Precise x) = Imprecise x
toImprecise c = c

conservativeVal :: Conservative a -> a
conservativeVal (Precise x) = x
conservativeVal (Imprecise x) = x

instance Functor Conservative where
  fmap f (Precise x) = Precise $ f x
  fmap f (Imprecise x) = Imprecise $ f x

instance (PrettyShow a) => PrettyShow (Conservative a) where
  pshow (Precise x) = "Precise " ++ pshow x
  pshow (Imprecise x) = "Imprecise" ++ pshow x

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

type CellMap i = Map [i] (Cell i)

data Space i = Space {
    spaceMap :: CellMap i
  , spaceDim :: Int
  , conservativeMinMaxCoords :: !(Conservative (Vector i, Vector i))
  }
  deriving (Show, Eq)

instance (PrettyShow i, Integral i) => PrettyShow (Space i) where
  pshow s = concat [ []
    , "(Space"
    , " "
    , "(min,max)=" ++ pshow (conservativeMinMaxCoords s)
    , " "
    , "assocs=" ++ pshow (Map.assocs . spaceMap $ s)
    , ")"
    ]

mkSpace :: (Integral i) => Int -> ByteString -> Space i
mkSpace dim str
  | dim <= 0  = error "Space.Space.mkSpace given a non-positive dimension"
  | otherwise = case BS.foldl' f (0, 0, 0, space, '\0') str of
      (_, _, _, s, _) -> s
  where
    origin = mkVector $ replicate dim 0
    space = Space {
        spaceMap = Map.empty
      , spaceDim = dim
      , conservativeMinMaxCoords = Precise (origin, origin)
      }
    f (x, y, z, s, p) c = case c of
      ' ' -> (x + 1, y, z, s, c)
      '\r'-> guardDim 2 (0, y + 1, z, s, c)
      '\n'-> guardDim 2 $ if p == '\r'
        then (x, y, z, s, c)
        else (0, y + 1, z, s, c)
      '\f'-> guardDim 3 (0, 0, z + 1, s, c)
      _   -> let
        v = take dim $ [x, y, z] ++ repeat 0
        s' = putCell s (charToCell c) $ mkVector v
        in (x + 1, y, z, s', c)
      where
        guardDim n t = if dim >= n
          then t
          else (x, y, z, s, c)

cellAt :: (Integral i) => Space i -> Vector i -> Cell i
cellAt s pos = Map.findWithDefault (charToCell ' ') (unVector pos) $ spaceMap s

updateMinMaxCoords :: (Ord i) => (Vector i, Vector i) -> Vector i -> (Vector i, Vector i)
updateMinMaxCoords (minPos, maxPos) pos = (zipWithV min minPos pos, zipWithV max maxPos pos)

putCell :: (Num i, Ord i) => Space i -> Cell i -> Vector i -> Space i
putCell s c pos
  | charToCell ' ' == c = if strictInBounds s pos
    then s {
        spaceMap = Map.delete (unVector pos) m
      }
    else if inBounds s pos
      then s {
          spaceMap = Map.delete (unVector pos) m
        , conservativeMinMaxCoords = toImprecise $ conservativeMinMaxCoords s
        }
      else s
  | otherwise = s {
        spaceMap = Map.insert (unVector pos) c m
      , conservativeMinMaxCoords = fmap (`updateMinMaxCoords` pos) $ conservativeMinMaxCoords s
      }
  where
    m = spaceMap s

inBounds :: (Ord i) => Space i -> Vector i -> Bool
inBounds s pos = minPos <=~ pos && pos <=~ maxPos
  where
    (<=~) = liftOrd (<=)
    (minPos, maxPos) = conservativeVal $ conservativeMinMaxCoords s

strictInBounds :: (Ord i) => Space i -> Vector i -> Bool
strictInBounds s pos = minPos <~ pos && pos <~ maxPos
  where
    (<~) = liftOrd (<)
    (minPos, maxPos) = conservativeVal $ conservativeMinMaxCoords s

travelBy :: (Integral i) => Vector i -> Vector i -> Space i -> Vector i
(pos `travelBy` delta) s = if inBounds s pos'
  then pos'
  else wrap pos delta s
  where
    dim = spaceDim s
    pos' = takeV dim (pos + delta)

wrap :: (Integral i) => Vector i -> Vector i -> Space i -> Vector i
wrap pos delta s = (delta +) . head . dropWhile (inBounds s) . iterate (subtract delta) $ pos

minMaxCoords :: (Integral i) => Space i -> (Vector i, Vector i, Space i)
minMaxCoords s = if isPrecise $ conservativeMinMaxCoords s
  then (cMinPos, cMaxPos, s)
  else case Map.lookup (unVector cMinPos) m >> Map.lookup (unVector cMaxPos) m of
    Just _ -> (cMinPos, cMaxPos, s)
    Nothing -> let
      (minPos, maxPos) = Map.foldWithKey f (0, 0) $ spaceMap s
      f k _ z = updateMinMaxCoords z $ mkVector k
      s' = s { conservativeMinMaxCoords = Precise (minPos, maxPos) }
      in (minPos, maxPos, s')
  where
    m = spaceMap s
    (cMinPos, cMaxPos) = conservativeVal $ conservativeMinMaxCoords s

putSpaceAt :: (Integral i) => Vector i -> Space i -> Space i -> Space i
putSpaceAt pos baseSpace = Map.foldWithKey f baseSpace . spaceMap
  where
    f pos' cell space = putCell space cell (pos + mkVector pos')