packages feed

swarm-0.6.0.0: src/swarm-scenario/Swarm/Game/Robot/Walk.hs

{-# LANGUAGE OverloadedStrings #-}

-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Walkability exceptions
module Swarm.Game.Robot.Walk where

import Control.Monad (unless)
import Data.Aeson
import Data.List.NonEmpty qualified as NE
import Data.Set (Set)
import Data.Set qualified as S
import GHC.Generics (Generic)
import Swarm.Game.Entity (EntityName)
import Swarm.Language.Capability (Capability)

-- | A 'Blacklist' that is empty is the typical behavior,
-- in which walkability is
-- fully determined by an entity's 'Unwalkable' or 'Liquid' property.
-- A 'Whitelist' ignores those properties, and even blank terrain
-- is considered unwalkable.
-- Note that a 'Whitelist' that is empty would allow no movement whatsoever.
data Inclusions a
  = Whitelist a
  | Blacklist a
  deriving (Show, Eq, Functor, Generic, ToJSON)

emptyExceptions :: Monoid a => Inclusions a
emptyExceptions = Blacklist mempty

type WalkabilityExceptions a = Inclusions (Set a)

instance (FromJSON a, Ord a) => FromJSON (WalkabilityExceptions a) where
  parseJSON = withObject "walkable" $ \v -> do
    whitelist <- v .:? "only" .!= []
    blacklist <- v .:? "never" .!= []

    unless (null whitelist || null blacklist) $
      fail "Cannot specify both a whitelist and blacklist"

    let exceptionList =
          maybe
            (Blacklist blacklist) -- Note: empty blacklist is the typical case
            (Whitelist . NE.toList)
            (NE.nonEmpty whitelist)

    return $ S.fromList <$> exceptionList

-- | Properties of a robot used to determine whether an entity is walkable
data WalkabilityContext
  = WalkabilityContext
      (Set Capability)
      -- | which entities are unwalkable by this robot
      (WalkabilityExceptions EntityName)
  deriving (Show, Eq, Generic, ToJSON)