packages feed

swarm-0.7.0.0: src/swarm-topography/Swarm/Game/Scenario/Topography/Grid.hs

-- |
-- SPDX-License-Identifier: BSD-3-Clause
module Swarm.Game.Scenario.Topography.Grid (
  Grid (..),
  NonEmptyGrid (..),
  gridToVec,
  mapWithCoords,
  mapWithCoordsNE,
  allMembers,
  allMembersNE,
  mapRowsNE,
  getRows,
  mkGrid,
  getNonEmptyGrid,
)
where

import Control.Lens.Indexed (FunctorWithIndex, imap)
import Data.Aeson (ToJSON (..))
import Data.Foldable qualified as F
import Data.List.NonEmpty (NonEmpty)
import Data.List.NonEmpty qualified as NE
import Data.Maybe (fromMaybe)
import Data.Semigroup
import Data.Tuple.Extra (both)
import Data.Vector qualified as V
import GHC.Generics (Generic)
import Swarm.Game.World.Coords
import Prelude hiding (zipWith)

newtype NonEmptyGrid c = NonEmptyGrid (NonEmpty (NonEmpty c))
  deriving (Generic, Show, Eq, Functor, Foldable, Traversable, ToJSON)

instance FunctorWithIndex Coords NonEmptyGrid where
  imap f (NonEmptyGrid g) =
    NonEmptyGrid $
      imap (\i -> imap (\j -> f (Coords $ both fromIntegral (i, j)))) g

data Grid c
  = EmptyGrid
  | Grid (NonEmptyGrid c)
  deriving (Show, Eq, Functor, Foldable, Traversable)

mkGrid :: [[a]] -> Grid a
mkGrid rows = fromMaybe EmptyGrid $ do
  rowsNE <- NE.nonEmpty =<< mapM NE.nonEmpty rows
  return $ Grid $ NonEmptyGrid rowsNE

getNonEmptyGrid :: Grid a -> Maybe (NonEmptyGrid a)
getNonEmptyGrid = \case
  EmptyGrid -> Nothing
  Grid x -> Just x

getRows :: Grid a -> [[a]]
getRows EmptyGrid = []
getRows (Grid (NonEmptyGrid g)) = NE.toList . NE.map NE.toList $ g

-- | Since the derived 'Functor' instance applies to the
-- type parameter that is nested within lists, we define
-- an explicit function for mapping over the enclosing lists.
mapRowsNE ::
  (NonEmpty (NonEmpty a) -> NonEmpty (NonEmpty b)) ->
  NonEmptyGrid a ->
  NonEmptyGrid b
mapRowsNE f (NonEmptyGrid rows) = NonEmptyGrid $ f rows

allMembersNE :: NonEmptyGrid c -> NonEmpty c
allMembersNE (NonEmptyGrid g) = sconcat g

allMembers :: Grid a -> [a]
allMembers EmptyGrid = []
allMembers g = F.toList g

mapWithCoordsNE :: (Coords -> a -> b) -> NonEmptyGrid a -> NonEmpty b
mapWithCoordsNE f = allMembersNE . imap f

mapWithCoords :: (Coords -> a -> b) -> Grid a -> [b]
mapWithCoords _ EmptyGrid = []
mapWithCoords f (Grid g) = NE.toList $ mapWithCoordsNE f g

-- | Converts linked lists to vectors to facilitate
-- random access when assembling the image
gridToVec :: Grid a -> V.Vector (V.Vector a)
gridToVec g = V.fromList . map V.fromList $ getRows g

instance (ToJSON a) => ToJSON (Grid a) where
  toJSON EmptyGrid = toJSON ([] :: [a])
  toJSON (Grid g) = toJSON g