{-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE AllowAmbiguousTypes #-} -------------------------------------------------------------------------------- module Xanthous.Generators.Level.Util ( MCells , Cells , CellM , randInitialize , initializeEmpty , numAliveNeighborsM , numAliveNeighbors , fillOuterEdgesM , cloneMArray , floodFill , regions , fillAll , fillAllM , fromPoints , fromPointsM ) where -------------------------------------------------------------------------------- import Xanthous.Prelude hiding (Foldable, toList, for_) -------------------------------------------------------------------------------- import Data.Array.ST import Data.Array.Unboxed import Control.Monad.ST import Control.Monad.Random import Data.Monoid import Data.Foldable (Foldable, toList, for_) import qualified Data.Set as Set import Data.Semigroup.Foldable import Linear.V2 -------------------------------------------------------------------------------- import Xanthous.Util (foldlMapM', maximum1, minimum1) import Xanthous.Data (Dimensions, width, height) -------------------------------------------------------------------------------- type MCells s = STUArray s (V2 Word) Bool type Cells = UArray (V2 Word) Bool type CellM g s a = RandT g (ST s) a randInitialize :: RandomGen g => Dimensions -> Double -> CellM g s (MCells s) randInitialize dims aliveChance = do res <- initializeEmpty dims for_ [0..dims ^. width] $ \i -> for_ [0..dims ^. height] $ \j -> do val <- (>= aliveChance) <$> getRandomR (0, 1) lift $ writeArray res (V2 i j) val pure res initializeEmpty :: RandomGen g => Dimensions -> CellM g s (MCells s) initializeEmpty dims = lift $ newArray (0, V2 (dims ^. width) (dims ^. height)) False -- | Returns the number of neighbors of the given point in the given array that -- are True. -- -- Behavior if point is out-of-bounds for the array is undefined, but will not -- error numAliveNeighborsM :: forall a i m . (MArray a Bool m, Ix i, Integral i) => a (V2 i) Bool -> V2 i -> m Word numAliveNeighborsM cells pt@(V2 x y) = do cellBounds <- getBounds cells getSum <$> foldlMapM' (fmap (Sum . fromIntegral . fromEnum) . boundedGet cellBounds) neighborPositions where boundedGet :: (V2 i, V2 i) -> (Int, Int) -> m Bool boundedGet bnds _ | not (inRange bnds pt) = pure True boundedGet (V2 minX minY, V2 maxX maxY) (i, j) | (x <= minX && i < 0) || (y <= minY && j < 0) || (x >= maxX && i > 0) || (y >= maxY && j > 0) = pure True | otherwise = let nx = fromIntegral $ fromIntegral x + i ny = fromIntegral $ fromIntegral y + j in readArray cells $ V2 nx ny -- | Returns the number of neighbors of the given point in the given array that -- are True. -- -- Behavior if point is out-of-bounds for the array is undefined, but will not -- error numAliveNeighbors :: forall a i . (IArray a Bool, Ix i, Integral i) => a (V2 i) Bool -> V2 i -> Word numAliveNeighbors cells pt@(V2 x y) = let cellBounds = bounds cells in getSum $ foldMap (Sum . fromIntegral . fromEnum . boundedGet cellBounds) neighborPositions where boundedGet :: (V2 i, V2 i) -> (Int, Int) -> Bool boundedGet bnds _ | not (inRange bnds pt) = True boundedGet (V2 minX minY, V2 maxX maxY) (i, j) | (x <= minX && i < 0) || (y <= minY && j < 0) || (x >= maxX && i > 0) || (y >= maxY && j > 0) = True | otherwise = let nx = fromIntegral $ fromIntegral x + i ny = fromIntegral $ fromIntegral y + j in cells ! V2 nx ny neighborPositions :: [(Int, Int)] neighborPositions = [(i, j) | i <- [-1..1], j <- [-1..1], (i, j) /= (0, 0)] fillOuterEdgesM :: (MArray a Bool m, Ix i) => a (V2 i) Bool -> m () fillOuterEdgesM arr = do (V2 minX minY, V2 maxX maxY) <- getBounds arr for_ (range (minX, maxX)) $ \x -> do writeArray arr (V2 x minY) True writeArray arr (V2 x maxY) True for_ (range (minY, maxY)) $ \y -> do writeArray arr (V2 minX y) True writeArray arr (V2 maxX y) True cloneMArray :: forall a a' i e m. ( Ix i , MArray a e m , MArray a' e m , IArray UArray e ) => a i e -> m (a' i e) cloneMArray = thaw @_ @UArray <=< freeze -------------------------------------------------------------------------------- -- | Flood fill a cell array starting at a point, returning a list of all the -- (true) cell locations reachable from that point floodFill :: forall a i. ( IArray a Bool , Ix i , Enum i , Bounded i , Eq i ) => a (V2 i) Bool -- ^ array -> (V2 i) -- ^ position -> Set (V2 i) floodFill = go mempty where go :: Set (V2 i) -> a (V2 i) Bool -> (V2 i) -> Set (V2 i) go res arr@(bounds -> arrBounds) idx@(V2 x y) | not (inRange arrBounds idx) = res | not (arr ! idx) = res | otherwise = let neighbors = filter (inRange arrBounds) . filter (/= idx) . filter (`notMember` res) $ V2 <$> [(if x == minBound then x else pred x) .. (if x == maxBound then x else succ x)] <*> [(if y == minBound then y else pred y) .. (if y == maxBound then y else succ y)] in foldl' (\r idx' -> if arr ! idx' then r <> (let r' = r & contains idx' .~ True in r' `seq` go r' arr idx') else r) (res & contains idx .~ True) neighbors {-# SPECIALIZE floodFill :: UArray (V2 Word) Bool -> (V2 Word) -> Set (V2 Word) #-} -- | Gives a list of all the disconnected regions in a cell array, represented -- each as lists of points regions :: forall a i. ( IArray a Bool , Ix i , Enum i , Bounded i , Eq i ) => a (V2 i) Bool -> [Set (V2 i)] regions arr | Just firstPoint <- findFirstPoint arr = let region = floodFill arr firstPoint arr' = fillAll region arr in region : regions arr' | otherwise = [] where findFirstPoint :: a (V2 i) Bool -> Maybe (V2 i) findFirstPoint = fmap fst . headMay . filter snd . assocs {-# SPECIALIZE regions :: UArray (V2 Word) Bool -> [Set (V2 Word)] #-} fillAll :: (IArray a Bool, Ix i, Foldable f) => f i -> a i Bool -> a i Bool fillAll ixes a = accum (const fst) a $ (, (False, ())) <$> toList ixes fillAllM :: (MArray a Bool m, Ix i, Foldable f) => f i -> a i Bool -> m () fillAllM ixes a = for_ ixes $ \i -> writeArray a i False fromPoints :: forall a f i. ( IArray a Bool , Ix i , Functor f , Foldable1 f ) => f (i, i) -> a (i, i) Bool fromPoints points = let pts = Set.fromList $ toList points dims = ( (minimum1 $ fst <$> points, minimum1 $ snd <$> points) , (maximum1 $ fst <$> points, maximum1 $ snd <$> points) ) in array dims $ range dims <&> \i -> (i, i `member` pts) fromPointsM :: (MArray a Bool m, Ix i, Element f ~ i, MonoFoldable f) => NonNull f -> m (a i Bool) fromPointsM points = do arr <- newArray (minimum points, maximum points) False fillAllM (otoList points) arr pure arr