swarm-0.1.0.0: src/Swarm/Game/Display.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- Orphan Hashable instances needed to derive Hashable Display
-- |
-- Module : Swarm.Game.Display
-- Copyright : Brent Yorgey
-- Maintainer : byorgey@gmail.com
--
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Utilities for describing how to display in-game entities in the TUI.
module Swarm.Game.Display (
-- * The display record
Priority,
Display,
-- ** Fields
defaultChar,
orientationMap,
curOrientation,
displayAttr,
displayPriority,
invisible,
-- ** Rendering
displayChar,
renderDisplay,
hidden,
-- ** Construction
defaultTerrainDisplay,
defaultEntityDisplay,
defaultRobotDisplay,
) where
import Brick (AttrName, Widget, str, withAttr)
import Control.Lens hiding (Const, from, (.=))
import Data.Hashable (Hashable)
import Data.Map (Map)
import Data.Map qualified as M
import Data.Yaml
import GHC.Generics (Generic)
import Swarm.Language.Syntax (Direction (..))
import Swarm.TUI.Attr (entityAttr, robotAttr, worldPrefix)
import Swarm.Util (maxOn, (?))
-- | Display priority. Entities with higher priority will be drawn on
-- top of entities with lower priority.
type Priority = Int
-- Some orphan instances we need to be able to derive a Hashable
-- instance for Display
instance Hashable AttrName
-- | A record explaining how to display an entity in the TUI.
data Display = Display
{ _defaultChar :: Char
, _orientationMap :: Map Direction Char
, _curOrientation :: Maybe Direction
, _displayAttr :: AttrName
, _displayPriority :: Priority
, _invisible :: Bool
}
deriving (Eq, Ord, Show, Generic, Hashable)
instance Semigroup Display where
d1 <> d2
| _invisible d1 = d2
| _invisible d2 = d1
| otherwise = maxOn _displayPriority d1 d2
makeLensesWith (lensRules & generateSignatures .~ False) ''Display
-- | The default character to use for display.
defaultChar :: Lens' Display Char
-- | For robots or other entities that have an orientation, this map
-- optionally associates different display characters with
-- different orientations. If an orientation is not in the map,
-- the 'defaultChar' will be used.
orientationMap :: Lens' Display (Map Direction Char)
-- | The display caches the current orientation of the entity, so we
-- know which character to use from the orientation map.
curOrientation :: Lens' Display (Maybe Direction)
-- | The attribute to use for display.
displayAttr :: Lens' Display AttrName
-- | This entity's display priority. Higher priorities are drawn
-- on top of lower.
displayPriority :: Lens' Display Priority
-- | Whether the entity is currently invisible.
invisible :: Lens' Display Bool
instance FromJSON Display where
parseJSON = withObject "Display" $ \v ->
Display
<$> v .:? "char" .!= ' '
<*> v .:? "orientationMap" .!= M.empty
<*> v .:? "curOrientation"
<*> (fmap (worldPrefix <>) <$> v .:? "attr") .!= entityAttr
<*> v .:? "priority" .!= 1
<*> v .:? "invisible" .!= False
instance ToJSON Display where
toJSON d =
object $
[ "char" .= (d ^. defaultChar)
, "attr" .= (d ^. displayAttr)
, "priority" .= (d ^. displayPriority)
]
++ ["orientationMap" .= (d ^. orientationMap) | not (M.null (d ^. orientationMap))]
++ ["invisible" .= (d ^. invisible) | d ^. invisible]
-- | Look up the character that should be used for a display.
displayChar :: Display -> Char
displayChar disp = case disp ^. curOrientation of
Nothing -> disp ^. defaultChar
Just dir -> M.lookup dir (disp ^. orientationMap) ? (disp ^. defaultChar)
-- | Render a display as a UI widget.
renderDisplay :: Display -> Widget n
renderDisplay disp = withAttr (disp ^. displayAttr) $ str [displayChar disp]
-- | Modify a display to use a @?@ character for entities that are
-- hidden/unknown.
hidden :: Display -> Display
hidden = (defaultChar .~ '?') . (curOrientation .~ Nothing)
-- | The default way to display some terrain using the given character
-- and attribute, with priority 0.
defaultTerrainDisplay :: Char -> AttrName -> Display
defaultTerrainDisplay c attr =
defaultEntityDisplay c
& displayPriority .~ 0
& displayAttr .~ attr
-- | Construct a default display for an entity that uses only a single
-- display character, the default entity attribute, and priority 1.
defaultEntityDisplay :: Char -> Display
defaultEntityDisplay c =
Display
{ _defaultChar = c
, _orientationMap = M.empty
, _curOrientation = Nothing
, _displayAttr = entityAttr
, _displayPriority = 1
, _invisible = False
}
-- | Construct a default robot display for a given orientation, with
-- display characters @"X^>v<"@, the default robot attribute, and
-- priority 10.
--
-- Note that the 'defaultChar' is used for direction 'DDown'
-- and is overriden for the special base robot.
defaultRobotDisplay :: Display
defaultRobotDisplay =
Display
{ _defaultChar = 'X'
, _orientationMap =
M.fromList
[ (DEast, '>')
, (DWest, '<')
, (DSouth, 'v')
, (DNorth, '^')
]
, _curOrientation = Nothing
, _displayAttr = robotAttr
, _displayPriority = 10
, _invisible = False
}
instance Monoid Display where
mempty = defaultEntityDisplay ' ' & invisible .~ True